Compare commits

..

154 Commits
4.0.2 ... 4.1.4

Author SHA1 Message Date
James Cole
0a6f299ae6 Merge branch 'release/4.1.4' 2016-10-30 08:56:35 +01:00
James Cole
73f87e30c2 Changelog.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-30 08:52:09 +01:00
James Cole
838ece2c89 Merge pull request #368 from JC5/l10n_develop
New Crowdin translations
2016-10-30 07:32:23 +01:00
James Cole
d8b88ea2c0 New translations 2016-10-30 07:20:17 +01:00
James Cole
5908951b75 New translations 2016-10-30 07:20:17 +01:00
James Cole
0b41f4c4d2 New translations 2016-10-30 07:20:16 +01:00
James Cole
35439d4fbc New translations 2016-10-30 07:20:16 +01:00
James Cole
fdce40310f New translations 2016-10-30 07:20:15 +01:00
James Cole
6b4785ae32 New translations 2016-10-30 07:20:15 +01:00
James Cole
f74e8e9cb7 New translations 2016-10-30 07:20:13 +01:00
James Cole
5a4eb7e09e New translations 2016-10-30 07:20:12 +01:00
James Cole
3bc4df03cc New translations 2016-10-30 07:20:11 +01:00
James Cole
5d585132fb New translations 2016-10-30 07:20:11 +01:00
James Cole
eff4905883 New translations 2016-10-30 07:20:10 +01:00
James Cole
8923ac4fe3 New translations 2016-10-30 07:20:09 +01:00
James Cole
073535e5ed New translations 2016-10-30 07:20:09 +01:00
James Cole
d304b90ca6 New translations 2016-10-30 07:20:08 +01:00
James Cole
816c26e14e New translations 2016-10-30 07:20:07 +01:00
James Cole
b1244ffa01 New translations 2016-10-30 07:20:07 +01:00
James Cole
fc1342bff9 New translations 2016-10-30 07:20:06 +01:00
James Cole
d7b95194b5 New translations 2016-10-30 07:20:04 +01:00
James Cole
b58bdeccd2 New translations 2016-10-30 07:20:04 +01:00
James Cole
f260b9bdee New translations 2016-10-30 07:20:02 +01:00
James Cole
fb1bdc9ec5 New translations 2016-10-30 07:20:01 +01:00
James Cole
697eff48fc New translations 2016-10-30 07:20:00 +01:00
James Cole
c05019339a Approved. Step name: Proofread 2016-10-30 07:20:00 +01:00
James Cole
8438efaf41 Approved. Step name: Proofread 2016-10-30 07:19:59 +01:00
James Cole
81c019cc99 Translated 2016-10-30 07:19:59 +01:00
James Cole
c773fdc435 Approved. Step name: Proofread 2016-10-30 07:19:58 +01:00
James Cole
c1406f51f1 Approved. Step name: Proofread 2016-10-30 07:19:58 +01:00
James Cole
92affd3440 Approved. Step name: Proofread 2016-10-30 07:19:57 +01:00
James Cole
d3da0652ef Approved. Step name: Proofread 2016-10-30 07:19:57 +01:00
James Cole
e3fbbd6cf1 Translated 2016-10-30 07:19:56 +01:00
James Cole
fcff13470c Approved. Step name: Proofread 2016-10-30 07:19:56 +01:00
James Cole
e3061ee7e7 Approved. Step name: Proofread 2016-10-30 07:19:55 +01:00
James Cole
0ee305fc4a Approved. Step name: Proofread 2016-10-30 07:19:55 +01:00
James Cole
58b93fd0c4 Approved. Step name: Proofread 2016-10-30 07:19:53 +01:00
James Cole
b30217fa2d Approved. Step name: Proofread 2016-10-30 07:19:52 +01:00
James Cole
ae48eec3a2 Translated 2016-10-30 07:19:52 +01:00
James Cole
948233ba27 Translated 2016-10-30 07:19:52 +01:00
James Cole
c2db9b183a New translations 2016-10-30 07:19:51 +01:00
James Cole
6d2b88fa0b New translations 2016-10-30 07:19:51 +01:00
James Cole
1d5da825c5 New translations 2016-10-30 07:19:50 +01:00
James Cole
330c9b53d6 New translations 2016-10-30 07:19:50 +01:00
James Cole
751fe7d4fb New translations 2016-10-30 07:19:49 +01:00
James Cole
9df1fc6e5d New translations 2016-10-30 07:19:48 +01:00
James Cole
8d660f1701 New translations 2016-10-30 07:19:46 +01:00
James Cole
4d61d3c4aa New translations 2016-10-30 07:19:46 +01:00
James Cole
0457088c99 New translations 2016-10-30 07:19:45 +01:00
James Cole
8e575da74e New translations 2016-10-30 07:19:45 +01:00
James Cole
48ed28888e Translated 2016-10-30 07:19:43 +01:00
James Cole
4084b1124e Translated 2016-10-30 07:19:42 +01:00
James Cole
60ba607027 Translated 2016-10-30 07:19:42 +01:00
James Cole
3df2c11b4a Translated 2016-10-30 07:19:41 +01:00
James Cole
c93221923a Translated 2016-10-30 07:19:41 +01:00
James Cole
375317e932 New translations 2016-10-30 07:19:40 +01:00
James Cole
7ce527957a New translations 2016-10-30 07:19:40 +01:00
James Cole
6946521199 New translations 2016-10-30 07:19:39 +01:00
James Cole
18ee20e680 Update crowdin file [skip ci]
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-30 06:37:00 +01:00
James Cole
c53da15219 Update composer.lock in anticipation of new release.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-30 06:20:18 +01:00
James Cole
d4995e342f Fixed #330 2016-10-30 06:14:07 +01:00
James Cole
c9f14da294 Translations. 2016-10-29 17:30:55 +02:00
James Cole
e9c2446cba Debug log for #366 2016-10-29 16:16:10 +02:00
James Cole
35f179625c New queries for #366 2016-10-29 16:11:54 +02:00
James Cole
39749aa113 First code set for #330 2016-10-29 15:14:33 +02:00
James Cole
ba65e982fd Forgot to include model [skip ci] 2016-10-29 11:42:04 +02:00
James Cole
b50e5d7e59 Can also change destination in new rule. 2016-10-29 09:22:51 +02:00
James Cole
a3148dc172 Code for #321 2016-10-29 09:03:14 +02:00
James Cole
73f1491d2d Updates for translations. 2016-10-29 07:44:46 +02:00
James Cole
28eb54dc96 Initial split for report options. 2016-10-28 19:01:52 +02:00
James Cole
21fb426524 Make sure date is localised. 2016-10-28 18:16:30 +02:00
James Cole
d5710ca809 Update Crowdin configuration file 2016-10-28 14:06:41 +02:00
James Cole
0ba6cdda17 Merge pull request #364 from schoentoon/develop
Added dockerfile
2016-10-27 19:40:27 +02:00
James Cole
afdcfa8525 Tweak reports. 2016-10-26 19:45:10 +02:00
James Cole
5db4f8512b Tweak reports. 2016-10-26 19:37:19 +02:00
James Cole
dc0c1b73bc Better view for expenses. 2016-10-26 19:32:19 +02:00
James Cole
f999257095 New verify database routine. 2016-10-26 19:32:07 +02:00
James Cole
7182909e28 Keep the box [skip ci] 2016-10-26 16:54:52 +02:00
James Cole
fe3f015171 Add more stuff to ajax controllers, making report controller simpler. 2016-10-26 16:46:43 +02:00
Toon Schoenmakers
5bb668be63 Added dockerfile 2016-10-26 15:07:36 +02:00
James Cole
01de147900 Display message about common error. 2016-10-26 06:41:50 +02:00
James Cole
a7e5fcc806 Move some stuff over to AJAX thing. 2016-10-25 18:53:54 +02:00
James Cole
e2d187d74b Various small bug fixes. 2016-10-24 18:01:15 +02:00
James Cole
48b0620629 New help thing. 2016-10-23 17:33:53 +02:00
James Cole
19e9f382e4 Add some rounding to make forms more neat. 2016-10-23 16:56:18 +02:00
James Cole
446eaf6588 Some code cleanup [skip ci] 2016-10-23 14:58:39 +02:00
James Cole
78deb1420d Some fixes for bills. 2016-10-23 14:56:05 +02:00
James Cole
e092515dff Better export. 2016-10-23 12:55:07 +02:00
James Cole
81f6fef978 Add new line to files [skip ci] 2016-10-23 12:42:44 +02:00
James Cole
6a2f8fa9ee No use models directly. 2016-10-23 12:41:54 +02:00
James Cole
a79a8c8874 Various small upgrades. 2016-10-23 12:37:12 +02:00
James Cole
c39659b064 Remove a lot of references to user id. 2016-10-23 12:19:32 +02:00
James Cole
9a30fbd05a Move stuff to request classes for #339 2016-10-23 12:10:22 +02:00
James Cole
83f48418f6 Small updates [skip ci] 2016-10-23 09:57:04 +02:00
James Cole
bcd7b41c91 Simplified export. 2016-10-23 09:44:14 +02:00
James Cole
cefb7d12bc Merge branch 'release/4.1.3' 2016-10-22 22:45:27 +02:00
James Cole
3c0c15103e This fixes #361 2016-10-22 22:44:57 +02:00
James Cole
a8a8afc2be More for #339 2016-10-22 22:03:00 +02:00
James Cole
49e32abd3f Move some code for #339 2016-10-22 21:40:31 +02:00
James Cole
7977eefaca Merge branch 'release/4.1.2' 2016-10-22 20:52:54 +02:00
James Cole
f1fa6c3108 Fixed a bug in the store transaction routine. 2016-10-22 20:50:20 +02:00
James Cole
2fa0d55f39 Merge branch 'release/4.1.1' 2016-10-22 12:03:34 +02:00
James Cole
5bff509346 New translations.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 12:03:11 +02:00
James Cole
a147e9b74a Fix edit screen.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 11:53:34 +02:00
James Cole
0d87f7c4ca Better implementation of markdown.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 11:50:33 +02:00
James Cole
8c675615df Support markdown in notes.
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 11:47:47 +02:00
James Cole
7edd1bff40 Merge branch 'release/4.1.0' 2016-10-22 10:21:15 +02:00
James Cole
3bfcb1f3ab New change log. [skip ci]
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 10:20:25 +02:00
James Cole
7b6c63e6a8 New version number [skip ci]
Signed-off-by: James Cole <thegrumpydictator@gmail.com>
2016-10-22 10:15:50 +02:00
James Cole
5500e5b0aa Remove debug classes 2016-10-22 10:13:56 +02:00
James Cole
e4d249e73c Piggy bank supports notes (#350) 2016-10-22 10:13:49 +02:00
James Cole
091f6e918b Fix some reported issues. 2016-10-22 09:44:47 +02:00
James Cole
5d9b68c3e7 Various code cleanup [skip ci] 2016-10-22 09:39:31 +02:00
James Cole
12a6a61100 Various code cleanup [skip ci] 2016-10-22 09:33:03 +02:00
James Cole
7ce3b8d4ef Updated events, fixes #345 2016-10-22 09:31:27 +02:00
James Cole
3d9b855849 Force larval 5.3.18 2016-10-22 07:52:17 +02:00
James Cole
2346d2ec05 Fine tuning split edit screens. 2016-10-22 07:28:31 +02:00
James Cole
a4c081c8a5 Fix unset variable. [skip ci] 2016-10-21 22:01:42 +02:00
James Cole
316980efbd Fix unset variable. [skip ci] 2016-10-21 22:00:45 +02:00
James Cole
a05bc0eed0 Fix route [skip ci] 2016-10-21 21:57:10 +02:00
James Cole
4d1c271da6 Renamed a route [skip ci] 2016-10-21 21:54:57 +02:00
James Cole
0dd7ecbfbe Remove code no longer used. 2016-10-21 21:43:12 +02:00
James Cole
0dc188b083 Removed old code 2016-10-21 21:41:50 +02:00
James Cole
6a553f77f3 Large update to fix split journals. 2016-10-21 21:41:31 +02:00
James Cole
a74cef439b For simplicity, split controller. 2016-10-21 19:20:03 +02:00
James Cole
9a3cd27700 Many updates to get split transactions and normal transactions working side by side. 2016-10-21 19:06:22 +02:00
James Cole
801c7c0ab6 Remove unused function. 2016-10-21 13:22:45 +02:00
James Cole
a95a4e783a Fix and simplify bill repos 2016-10-21 13:20:51 +02:00
James Cole
af1ee9db93 This fixes bills unpaid. 2016-10-21 07:29:25 +02:00
James Cole
fcdb6fd2a7 Do loop instead of while loop [skip ci] 2016-10-21 06:41:33 +02:00
James Cole
97c0fb389d More logs [skip ci] 2016-10-21 06:38:00 +02:00
James Cole
a9c3992331 Back to old method [skip ci] 2016-10-21 06:33:56 +02:00
James Cole
a38e057fa7 Rewrote some methods to fix #341 2016-10-21 06:26:12 +02:00
James Cole
f83aaf77f1 Improve bill things for issue #341 2016-10-20 21:40:45 +02:00
James Cole
d92768ecbf This code fixes #349 2016-10-20 19:10:43 +02:00
James Cole
b9308cd74a Test flash messages. 2016-10-20 16:51:05 +02:00
James Cole
78b577bc9d Better ip info [skip ci] 2016-10-18 06:50:35 +02:00
James Cole
7d247897ed Should correctly show user info. [skip ci] 2016-10-18 06:47:48 +02:00
James Cole
5dcbdec491 Update composer file and update routine. [skip ci] 2016-10-15 18:52:21 +02:00
James Cole
9bf980431e Remove unused methods. 2016-10-15 14:07:51 +02:00
James Cole
da60bfbcff Better text [skip ci] 2016-10-15 14:05:56 +02:00
James Cole
92553cbc7e Add icon, missing translation [skip ci] 2016-10-15 12:41:45 +02:00
James Cole
8e48e53f17 Restucturing some code. 2016-10-15 12:39:34 +02:00
James Cole
2f9a4bb79a Better text 2016-10-15 12:39:11 +02:00
James Cole
ac968dd6cd Extended the user admin. 2016-10-15 07:11:53 +02:00
James Cole
6e4f2c0c8a Small script to upgrade transactions. 2016-10-15 06:19:21 +02:00
James Cole
d662c18ed7 Fix sorting in chart. [skip ci] 2016-10-14 20:07:15 +02:00
James Cole
e4ea234707 New revenue accounts chart. 2016-10-14 20:01:17 +02:00
James Cole
0b526c0168 New revenue chart 2016-10-14 19:59:10 +02:00
James Cole
2acde5c72a Option to show deposit accounts on the front page. 2016-10-14 19:52:30 +02:00
James Cole
ec8cf2c459 New preferences screen. 2016-10-14 19:48:19 +02:00
James Cole
3598780d54 This should at least catch #357 2016-10-14 19:18:00 +02:00
James Cole
35dd8ac6e6 Revert "This should at least catch #357"
This reverts commit 5ff7c7ffab.
2016-10-14 19:16:39 +02:00
James Cole
5ff7c7ffab This should at least catch #357 2016-10-14 19:16:28 +02:00
264 changed files with 13032 additions and 10282 deletions

View File

@@ -2,7 +2,58 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [4.1.4] - 2016-10-30
### Added
- New Dockerfile thanks to @schoentoon
- Added changing the destination account as rule action.
- Added changing the source account as rule action.
- Can convert transactions into different types.
### Changed
- Changed the export routine to be more future-proof.
- Improved help routine.
- Integrated CrowdIn translations.
- Simplified reports
- Change error message to refer to solution.
### Fixed
- #367 thanks to @HungryFeline
- #366 thanks to @3mz3t
- #362 and #341 thanks to @bnw
- #355 thanks to @roberthorlings
## [4.1.3] - 2016-10-22
### Fixed
- Some event handlers called the wrong method.
## [4.1.2] - 2016-10-22
### Fixed
- A bug is fixed in the journal event handler that prevented Firefly III from actually storing journals.
## [4.1.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

42
Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
FROM php:7-apache
RUN apt-get update -y && \
apt-get install -y --no-install-recommends libcurl4-openssl-dev \
zlib1g-dev \
libjpeg62-turbo-dev \
libpng12-dev \
libicu-dev \
libmcrypt-dev \
libedit-dev \
libtidy-dev \
libxml2-dev \
libsqlite3-dev \
libbz2-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip bcmath xml mbstring pdo_sqlite pdo_mysql bz2
# Enable apache mod rewrite..
RUN a2enmod rewrite
# Setup the Composer installer
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer && \
curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig && \
php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" && \
chmod +x /tmp/composer-setup.php && \
php /tmp/composer-setup.php && \
mv composer.phar /usr/local/bin/composer && \
rm -f /tmp/composer-setup.{php,sig}
ADD . /var/www/firefly-iii
RUN chown -R www-data:www-data /var/www/
ADD docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
USER www-data
WORKDIR /var/www/firefly-iii
RUN composer install --no-scripts --no-dev
USER root

View 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;
}
}

View File

@@ -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;
}

View 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++;
}
}
}
}

View File

@@ -15,6 +15,7 @@ namespace FireflyIII\Console\Commands;
use Crypt;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
@@ -84,6 +85,9 @@ class VerifyDatabase extends Command
// transfers with budgets.
$this->reportTransfersBudgets();
// report on journals with the wrong types of accounts.
$this->reportIncorrectJournals();
}
/**
@@ -202,6 +206,45 @@ class VerifyDatabase extends Command
}
}
private function reportIncorrectJournals()
{
$configuration = [
// a withdrawal can not have revenue account:
TransactionType::WITHDRAWAL => [AccountType::REVENUE],
// deposit cannot have an expense account:
TransactionType::DEPOSIT => [AccountType::EXPENSE],
// transfer cannot have either:
TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE],
];
foreach ($configuration as $transactionType => $accountTypes) {
$set = TransactionJournal
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->leftJoin('users', 'users.id', '=', 'transaction_journals.user_id')
->where('transaction_types.type', $transactionType)
->whereIn('account_types.type', $accountTypes)
->whereNull('transaction_journals.deleted_at')
->get(['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type', 'transaction_types.type']);
foreach ($set as $entry) {
$this->error(
sprintf(
'Transaction journal #%d (user #%d, %s) is of type "%s" but ' .
'is linked to a "%s". The transaction journal should be recreated.',
$entry->id,
$entry->user_id,
$entry->email,
$entry->type,
$entry->a_type
)
);
}
}
}
/**
* Any deleted transaction journals that have transactions that are NOT deleted:
*/

View File

@@ -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,
];

View File

@@ -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];
}
}

View File

@@ -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;
}

View File

@@ -1,6 +1,6 @@
<?php
/**
* UserIsConfirmed.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 UserIsConfirmed
* Class ConfirmedUser
*
* @package FireflyIII\Events
*/
class UserIsConfirmed extends Event
class ConfirmedUser extends Event
{
use SerializesModels;
@@ -29,7 +29,7 @@ class UserIsConfirmed extends Event
public $user;
/**
* Create a new event instance.
* Create a new event instance. This event is triggered when a user confirms their new account.
*
* @param User $user
* @param string $ipAddress

View File

@@ -1,6 +1,6 @@
<?php
/**
* ResendConfirmation.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 ResendConfirmation
* Class RegisteredUser
*
* @package FireflyIII\Events
*/
class ResendConfirmation extends Event
class RegisteredUser extends Event
{
use SerializesModels;
@@ -29,7 +29,7 @@ class ResendConfirmation extends Event
public $user;
/**
* Create a new event instance.
* Create a new event instance. This event is triggered when a new user registers.
*
* @param User $user
* @param string $ipAddress

View File

@@ -1,6 +1,6 @@
<?php
/**
* UserIsDeleted.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 UserIsDeleted
* Class ResentConfirmation
*
* @package FireflyIII\Events
*/
class UserIsDeleted extends Event
class ResentConfirmation extends Event
{
use SerializesModels;
@@ -29,7 +29,7 @@ class UserIsDeleted extends Event
public $user;
/**
* Create a new event instance.
* Create a new event instance. This event is triggered when a users wants a new confirmation.
*
* @param User $user
* @param string $ipAddress

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -13,11 +13,10 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Collector;
use Amount;
use Carbon\Carbon;
use Crypt;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Collection;
@@ -31,12 +30,14 @@ use Storage;
*/
class AttachmentCollector extends BasicCollector implements CollectorInterface
{
/** @var string */
private $explanationString = '';
/** @var Carbon */
private $end;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $exportDisk;
/** @var AttachmentRepositoryInterface */
private $repository;
/** @var Carbon */
private $start;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $uploadDisk;
@@ -69,34 +70,17 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
$this->exportAttachment($attachment);
}
// put the explanation string in a file and attach it as well.
$file = $this->job->key . '-Source of all your attachments explained.txt';
$this->exportDisk->put($file, $this->explanationString);
$this->getFiles()->push($file);
return true;
}
/**
* @param Attachment $attachment
* @param Carbon $start
* @param Carbon $end
*/
private function explain(Attachment $attachment)
public function setDates(Carbon $start, Carbon $end)
{
/** @var TransactionJournal $journal */
$journal = $attachment->attachable;
$args = [
'attachment_name' => e($attachment->filename),
'attachment_id' => $attachment->id,
'type' => strtolower($journal->transactionType->type),
'description' => e($journal->description),
'journal_id' => $journal->id,
'date' => $journal->date->formatLocalized(strval(trans('config.month_and_day'))),
'amount' => Amount::formatJournal($journal, false),
];
$string = trans('firefly.attachment_explanation', $args) . "\n";
Log::debug('Appended explanation string', ['string' => $string]);
$this->explanationString .= $string;
$this->start = $start;
$this->end = $end;
}
/**
@@ -112,10 +96,8 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
$decrypted = Crypt::decrypt($this->uploadDisk->get($file));
$exportFile = $this->exportFileName($attachment);
$this->exportDisk->put($exportFile, $decrypted);
$this->getFiles()->push($exportFile);
$this->getEntries()->push($exportFile);
// explain:
$this->explain($attachment);
} catch (DecryptException $e) {
Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage());
}
@@ -143,7 +125,7 @@ class AttachmentCollector extends BasicCollector implements CollectorInterface
*/
private function getAttachments(): Collection
{
$attachments = $this->repository->get();
$attachments = $this->repository->getBetween($this->start, $this->end);
return $attachments;
}

View File

@@ -27,7 +27,7 @@ class BasicCollector
/** @var ExportJob */
protected $job;
/** @var Collection */
private $files;
private $entries;
/**
* BasicCollector constructor.
@@ -36,24 +36,24 @@ class BasicCollector
*/
public function __construct(ExportJob $job)
{
$this->files = new Collection;
$this->job = $job;
$this->entries = new Collection;
$this->job = $job;
}
/**
* @return Collection
*/
public function getFiles(): Collection
public function getEntries(): Collection
{
return $this->files;
return $this->entries;
}
/**
* @param Collection $files
* @param Collection $entries
*/
public function setFiles(Collection $files)
public function setEntries(Collection $entries)
{
$this->files = $files;
$this->entries = $entries;
}

View File

@@ -25,7 +25,7 @@ interface CollectorInterface
/**
* @return Collection
*/
public function getFiles(): Collection;
public function getEntries(): Collection;
/**
* @return bool
@@ -33,9 +33,9 @@ interface CollectorInterface
public function run(): bool;
/**
* @param Collection $files
* @param Collection $entries
*
*/
public function setFiles(Collection $files);
public function setEntries(Collection $entries);
}

View File

@@ -0,0 +1,348 @@
<?php
/**
* JournalCollector.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\Export\Collector;
use Carbon\Carbon;
use Crypt;
use DB;
use FireflyIII\Models\Transaction;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
/**
* Class JournalCollector
*
* @package FireflyIII\Export\Collector
*/
class JournalCollector extends BasicCollector implements CollectorInterface
{
/** @var Collection */
private $accounts;
/** @var Carbon */
private $end;
/** @var Carbon */
private $start;
/** @var Collection */
private $workSet;
/**
* @return bool
*/
public function run(): bool
{
/*
* Instead of collecting journals we collect transactions for the given accounts.
* We left join the OPPOSING transaction AND some journal data.
* After that we complement this info with budgets, categories, etc.
*
* This is way more efficient and will also work on split journals.
*/
$this->getWorkSet();
/*
* Extract:
* possible budget ids for journals
* possible category ids journals
* possible budget ids for transactions
* possible category ids for transactions
*
* possible IBAN and account numbers?
*
*/
$journals = $this->extractJournalIds();
$transactions = $this->extractTransactionIds();
// extend work set with category data from journals:
$this->categoryDataForJournals($journals);
// extend work set with category cate from transactions (overrules journals):
$this->categoryDataForTransactions($transactions);
// same for budgets:
$this->budgetDataForJournals($journals);
$this->budgetDataForTransactions($transactions);
$this->setEntries($this->workSet);
return true;
}
/**
* @param Collection $accounts
*/
public function setAccounts(Collection $accounts)
{
$this->accounts = $accounts;
}
/**
* @param Carbon $start
* @param Carbon $end
*/
public function setDates(Carbon $start, Carbon $end)
{
$this->start = $start;
$this->end = $end;
}
/**
* @param array $journals
*
* @return bool
*/
private function budgetDataForJournals(array $journals): bool
{
$set = DB::table('budget_transaction_journal')
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id')
->whereIn('budget_transaction_journal.transaction_journal_id', $journals)
->get(
[
'budget_transaction_journal.budget_id',
'budget_transaction_journal.transaction_journal_id',
'budgets.name',
'budgets.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_journal_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
if (isset($array[$obj->transaction_journal_id])) {
$obj->budget_id = $array[$obj->transaction_journal_id]['id'];
$obj->budget_name = $array[$obj->transaction_journal_id]['name'];
}
}
);
return true;
}
/**
* @param array $transactions
*
* @return bool
*/
private function budgetDataForTransactions(array $transactions): bool
{
$set = DB::table('budget_transaction')
->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction.budget_id')
->whereIn('budget_transaction.transaction_id', $transactions)
->get(
[
'budget_transaction.budget_id',
'budget_transaction.transaction_id',
'budgets.name',
'budgets.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_id] = ['id' => $obj->budget_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
// first transaction
if (isset($array[$obj->id])) {
$obj->budget_id = $array[$obj->id]['id'];
$obj->budget_name = $array[$obj->id]['name'];
}
}
);
return true;
}
/**
* @param array $journals
*
* @return bool
*/
private function categoryDataForJournals(array $journals): bool
{
$set = DB::table('category_transaction_journal')
->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id')
->whereIn('category_transaction_journal.transaction_journal_id', $journals)
->get(
[
'category_transaction_journal.category_id',
'category_transaction_journal.transaction_journal_id',
'categories.name',
'categories.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_journal_id] = ['id' => $obj->category_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
if (isset($array[$obj->transaction_journal_id])) {
$obj->category_id = $array[$obj->transaction_journal_id]['id'];
$obj->category_name = $array[$obj->transaction_journal_id]['name'];
}
}
);
return true;
}
/**
* @param array $transactions
*
* @return bool
*/
private function categoryDataForTransactions(array $transactions): bool
{
$set = DB::table('category_transaction')
->leftJoin('categories', 'categories.id', '=', 'category_transaction.category_id')
->whereIn('category_transaction.transaction_id', $transactions)
->get(
[
'category_transaction.category_id',
'category_transaction.transaction_id',
'categories.name',
'categories.encrypted',
]
);
$set->each(
function ($obj) {
$obj->name = $obj->encrypted === 1 ? Crypt::decrypt($obj->name) : $obj->name;
}
);
$array = [];
foreach ($set as $obj) {
$array[$obj->transaction_id] = ['id' => $obj->category_id, 'name' => $obj->name];
}
$this->workSet->each(
function ($obj) use ($array) {
// first transaction
if (isset($array[$obj->id])) {
$obj->category_id = $array[$obj->id]['id'];
$obj->category_name = $array[$obj->id]['name'];
}
}
);
return true;
}
/**
* @return array
*/
private function extractJournalIds(): array
{
return $this->workSet->pluck('transaction_journal_id')->toArray();
}
/**
* @return array
*/
private function extractTransactionIds()
{
$set = $this->workSet->pluck('id')->toArray();
$opposing = $this->workSet->pluck('opposing_id')->toArray();
$complete = $set + $opposing;
return array_unique($complete);
}
/**
*
*/
private function getWorkSet()
{
$accountIds = $this->accounts->pluck('id')->toArray();
$this->workSet = Transaction
::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin(
'transactions AS opposing', function (JoinClause $join) {
$join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id')
->where('opposing.amount', '=', DB::raw('transactions.amount * -1'))
->where('transactions.identifier', '=', 'opposing.identifier');
}
)
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
->leftJoin('accounts AS opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', 'transaction_types.id')
->leftJoin('transaction_currencies', 'transaction_journals.transaction_currency_id', '=', 'transaction_currencies.id')
->whereIn('transactions.account_id', $accountIds)
->where('transaction_journals.user_id', $this->job->user_id)
->where('transaction_journals.date', '>=', $this->start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $this->end->format('Y-m-d'))
->where('transaction_journals.completed', 1)
->whereNull('transaction_journals.deleted_at')
->whereNull('transactions.deleted_at')
->whereNull('opposing.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transactions.identifier', 'ASC')
->get(
[
'transactions.id',
'transactions.amount',
'transactions.description',
'transactions.account_id',
'accounts.name as account_name',
'accounts.encrypted as account_name_encrypted',
'transactions.identifier',
'opposing.id as opposing_id',
'opposing.amount AS opposing_amount',
'opposing.description as opposing_description',
'opposing.account_id as opposing_account_id',
'opposing_accounts.name as opposing_account_name',
'opposing_accounts.encrypted as opposing_account_encrypted',
'opposing.identifier as opposing_identifier',
'transaction_journals.id as transaction_journal_id',
'transaction_journals.date',
'transaction_journals.description as journal_description',
'transaction_journals.encrypted as journal_encrypted',
'transaction_journals.transaction_type_id',
'transaction_types.type as transaction_type',
'transaction_journals.transaction_currency_id',
'transaction_currencies.code AS transaction_currency_code',
]
);
}
}

View File

@@ -26,16 +26,14 @@ use Storage;
*/
class UploadCollector extends BasicCollector implements CollectorInterface
{
/** @var string */
private $expected;
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $exportDisk;
private $importKeys = [];
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $uploadDisk;
/** @var string */
private $vintageFormat;
/**
*
* AttachmentCollector constructor.
*
* @param ExportJob $job
@@ -51,50 +49,74 @@ class UploadCollector extends BasicCollector implements CollectorInterface
$this->exportDisk = Storage::disk('export');
// file names associated with the old import routine.
$this->expected = 'csv-upload-' . auth()->user()->id . '-';
$this->vintageFormat = sprintf('csv-upload-%d-', auth()->user()->id);
// for the new import routine:
$this->getImportKeys();
}
/**
* Is called from the outside to actually start the export.
*
* @return bool
*/
public function run(): bool
{
// grab upload directory.
$files = $this->uploadDisk->files();
// collect old upload files (names beginning with "csv-upload".
$this->collectVintageUploads();
foreach ($files as $entry) {
$this->processUpload($entry);
// then collect current upload files:
$this->collectModernUploads();
return true;
}
/**
* This method collects all the uploads that are uploaded using the new importer. So after the summer of 2016.
*
* @return bool
*/
private function collectModernUploads(): bool
{
$set = $this->job->user->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
$keys = [];
if ($set->count() > 0) {
$keys = $set->pluck('key')->toArray();
}
foreach ($keys as $key) {
$this->processModernUpload($key);
}
return true;
}
/**
* This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016.
*
* @return bool
*/
private function getImportKeys()
private function collectVintageUploads():bool
{
$set = auth()->user()->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
if ($set->count() > 0) {
$keys = $set->pluck('key')->toArray();
$this->importKeys = $keys;
// grab upload directory.
$files = $this->uploadDisk->files();
foreach ($files as $entry) {
$this->processVintageUpload($entry);
}
Log::debug('Valid import keys are ', $this->importKeys);
return true;
}
/**
* This method tells you when the vintage upload file was actually uploaded.
*
* @param string $entry
*
* @return string
*/
private function getOriginalUploadDate(string $entry): string
private function getVintageUploadDate(string $entry): string
{
// this is an original upload.
$parts = explode('-', str_replace(['.csv.encrypted', $this->expected], '', $entry));
$parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry));
$originalUpload = intval($parts[1]);
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
@@ -102,33 +124,17 @@ class UploadCollector extends BasicCollector implements CollectorInterface
}
/**
* Tells you if a file name is a vintage upload.
*
* @param string $entry
*
* @return bool
*/
private function isImportFile(string $entry): bool
private function isVintageImport(string $entry): bool
{
$name = str_replace('.upload', '', $entry);
if (in_array($name, $this->importKeys)) {
Log::debug(sprintf('Import file "%s" is in array', $name), $this->importKeys);
return true;
}
Log::debug(sprintf('Import file "%s" is NOT in array', $name), $this->importKeys);
return false;
}
/**
* @param string $entry
*
* @return bool
*/
private function isOldImport(string $entry): bool
{
$len = strlen($this->expected);
$len = strlen($this->vintageFormat);
// file is part of the old import routine:
if (substr($entry, 0, $len) === $this->expected) {
if (substr($entry, 0, $len) === $this->vintageFormat) {
return true;
}
@@ -137,49 +143,62 @@ class UploadCollector extends BasicCollector implements CollectorInterface
}
/**
* @param $entry
* @param string $key
*
* @return bool
*/
private function processUpload(string $entry)
{
// file is old import:
if ($this->isOldImport($entry)) {
$this->saveOldImportFile($entry);
}
// file is current import.
if ($this->isImportFile($entry)) {
$this->saveImportFile($entry);
}
}
/**
* @param string $entry
*/
private function saveImportFile(string $entry)
private function processModernUpload(string $key): bool
{
// find job associated with import file:
$name = str_replace('.upload', '', $entry);
$job = auth()->user()->importJobs()->where('key', $name)->first();
$content = '';
try {
$content = Crypt::decrypt($this->uploadDisk->get($entry));
} catch (DecryptException $e) {
Log::error('Could not decrypt old import file ' . $entry . '. Skipped because ' . $e->getMessage());
$job = $this->job->user->importJobs()->where('key', $key)->first();
if (is_null($job)) {
return false;
}
if (!is_null($job) && strlen($content) > 0) {
// find the file for this import:
$content = '';
try {
$content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key)));
} catch (DecryptException $e) {
Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage()));
}
if (strlen($content) > 0) {
// add to export disk.
$date = $job->created_at->format('Y-m-d');
$file = sprintf('%s-Old %s import dated %s.%s', $this->job->key, strtoupper($job->file_type), $date, $job->file_type);
$this->exportDisk->put($file, $content);
$this->getFiles()->push($file);
$this->getEntries()->push($file);
}
return true;
}
/**
* If the file is a vintage upload, process it.
*
* @param string $entry
*
* @return bool
*/
private function processVintageUpload(string $entry): bool
{
if ($this->isVintageImport($entry)) {
$this->saveVintageImportFile($entry);
return true;
}
return false;
}
/**
* This will store the content of the old vintage upload somewhere.
*
* @param string $entry
*/
private function saveOldImportFile(string $entry)
private function saveVintageImportFile(string $entry)
{
$content = '';
try {
@@ -190,10 +209,10 @@ class UploadCollector extends BasicCollector implements CollectorInterface
if (strlen($content) > 0) {
// add to export disk.
$date = $this->getOriginalUploadDate($entry);
$date = $this->getVintageUploadDate($entry);
$file = $this->job->key . '-Old import dated ' . $date . '.csv';
$this->exportDisk->put($file, $content);
$this->getFiles()->push($file);
$this->getEntries()->push($file);
}
}

View File

@@ -1,68 +0,0 @@
<?php
/**
* ConfigurationFile.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\Export;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Models\ExportJob;
use Storage;
/**
* Class ConfigurationFile
*
* @package FireflyIII\Export
*/
class ConfigurationFile
{
/** @var \Illuminate\Contracts\Filesystem\Filesystem */
private $exportDisk;
/** @var ExportJob */
private $job;
/**
* ConfigurationFile constructor.
*
* @param ExportJob $job
*/
public function __construct(ExportJob $job)
{
$this->job = $job;
$this->exportDisk = Storage::disk('export');
}
/**
* @return string
*/
public function make(): string
{
$fields = array_keys(Entry::getFieldsAndTypes());
$types = Entry::getFieldsAndTypes();
$configuration = [
'date-format' => 'Y-m-d', // unfortunately, this is hard-coded.
'has-headers' => true,
'map' => [], // we could build a map if necessary for easy re-import.
'roles' => [],
'mapped' => [],
'specifix' => [],
];
foreach ($fields as $field) {
$configuration['roles'][] = $types[$field];
}
$file = $this->job->key . '-configuration.json';
$this->exportDisk->put($file, json_encode($configuration, JSON_PRETTY_PRINT));
return $file;
}
}

View File

@@ -13,8 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Export\Entry;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Collection;
use Crypt;
/**
* To extend the exported object, in case of new features in Firefly III for example,
@@ -35,98 +34,77 @@ use Illuminate\Support\Collection;
*/
final class Entry
{
/** @var string */
public $amount;
/** @var EntryBill */
public $bill;
/** @var EntryBudget */
public $budget;
/** @var EntryCategory */
public $category;
/** @var string */
// @formatter:off
public $journal_id;
public $date;
/** @var string */
public $description;
/** @var EntryAccount */
public $destinationAccount;
/** @var Collection */
public $destinationAccounts;
/** @var EntryAccount */
public $sourceAccount;
/** @var Collection */
public $sourceAccounts;
public $currency_code;
public $amount;
public $transaction_type;
public $source_account_id;
public $source_account_name;
public $destination_account_id;
public $destination_account_name;
public $budget_id;
public $budget_name;
public $category_id;
public $category_name;
// @formatter:on
/**
* Entry constructor.
*/
private function __construct()
{
$this->sourceAccounts = new Collection;
$this->destinationAccounts = new Collection;
}
/**
* @param TransactionJournal $journal
* @param $object
*
* @return Entry
*/
public static function fromJournal(TransactionJournal $journal)
public static function fromObject($object): Entry
{
$entry = new self;
$entry = new self;
$entry->description = $journal->description;
$entry->date = $journal->date->format('Y-m-d');
$entry->amount = TransactionJournal::amount($journal);
// journal information:
$entry->journal_id = $object->transaction_journal_id;
$entry->description = $object->journal_encrypted === 1 ? Crypt::decrypt($object->journal_description) : $object->journal_description;
$entry->amount = round($object->amount, 2); // always positive
$entry->date = $object->date;
$entry->transaction_type = $object->transaction_type;
$entry->currency_code = $object->transaction_currency_code;
$entry->budget = new EntryBudget($journal->budgets->first());
$entry->category = new EntryCategory($journal->categories->first());
$entry->bill = new EntryBill($journal->bill);
// source information:
$entry->source_account_id = $object->account_id;
$entry->source_account_name = $object->account_name_encrypted === 1 ? Crypt::decrypt($object->account_name) : $object->account_name;
$sources = TransactionJournal::sourceAccountList($journal);
$destinations = TransactionJournal::destinationAccountList($journal);
$entry->sourceAccount = new EntryAccount($sources->first());
$entry->destinationAccount = new EntryAccount($destinations->first());
foreach ($sources as $source) {
$entry->sourceAccounts->push(new EntryAccount($source));
}
// destination information
$entry->destination_account_id = $object->opposing_account_id;
$entry->destination_account_name = $object->opposing_account_encrypted === 1 ? Crypt::decrypt($object->opposing_account_name)
: $object->opposing_account_name;
foreach ($destinations as $destination) {
$entry->destinationAccounts->push(new EntryAccount($destination));
// category and budget
$entry->category_id = $object->category_id ?? '';
$entry->category_name = $object->category_name ?? '';
$entry->budget_id = $object->budget_id ?? '';
$entry->budget_name = $object->budget_name ?? '';
// update description when transaction description is different:
if (!is_null($object->description) && $object->description != $entry->description) {
$entry->description = $entry->description . ' (' . $object->description . ')';
}
return $entry;
}
/**
* @return array
*/
public static function getFieldsAndTypes(): array
{
// key = field name (see top of class)
// value = field type (see csv.php under 'roles')
return [
'description' => 'description',
'amount' => 'amount',
'date' => 'date-transaction',
'source_account_id' => 'account-id',
'source_account_name' => 'account-name',
'source_account_iban' => 'account-iban',
'source_account_type' => '_ignore',
'source_account_number' => 'account-number',
'destination_account_id' => 'opposing-id',
'destination_account_name' => 'opposing-name',
'destination_account_iban' => 'opposing-iban',
'destination_account_type' => '_ignore',
'destination_account_number' => 'account-number',
'budget_id' => 'budget-id',
'budget_name' => 'budget-name',
'category_id' => 'category-id',
'category_name' => 'category-name',
'bill_id' => 'bill-id',
'bill_name' => 'bill-name',
];
}
}

View File

@@ -1,49 +0,0 @@
<?php
/**
* EntryAccount.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\Export\Entry;
use FireflyIII\Models\Account;
/**
* Class EntryAccount
*
* @package FireflyIII\Export\Entry
*/
class EntryAccount
{
/** @var int */
public $accountId;
/** @var string */
public $iban;
/** @var string */
public $name;
/** @var string */
public $number;
/** @var string */
public $type;
/**
* EntryAccount constructor.
*
* @param Account $account
*/
public function __construct(Account $account)
{
$this->accountId = $account->id;
$this->name = $account->name;
$this->iban = $account->iban;
$this->type = $account->accountType->type;
$this->number = $account->getMeta('accountNumber');
}
}

View File

@@ -1,43 +0,0 @@
<?php
/**
* EntryBill.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\Export\Entry;
use FireflyIII\Models\Bill;
/**
* Class EntryBill
*
* @package FireflyIII\Export\Entry
*/
class EntryBill
{
/** @var int */
public $billId = '';
/** @var string */
public $name = '';
/**
* EntryBill constructor.
*
* @param Bill $bill
*/
public function __construct(Bill $bill = null)
{
if (!is_null($bill)) {
$this->billId = $bill->id;
$this->name = $bill->name;
}
}
}

View File

@@ -1,43 +0,0 @@
<?php
/**
* EntryBudget.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\Export\Entry;
use FireflyIII\Models\Budget;
/**
* Class EntryBudget
*
* @package FireflyIII\Export\Entry
*/
class EntryBudget
{
/** @var int */
public $budgetId = '';
/** @var string */
public $name = '';
/**
* EntryBudget constructor.
*
* @param Budget $budget
*/
public function __construct(Budget $budget = null)
{
if (!is_null($budget)) {
$this->budgetId = $budget->id;
$this->name = $budget->name;
}
}
}

View File

@@ -1,42 +0,0 @@
<?php
/**
* EntryCategory.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\Export\Entry;
use FireflyIII\Models\Category;
/**
* Class EntryCategory
*
* @package FireflyIII\Export\Entry
*/
class EntryCategory
{
/** @var int */
public $categoryId = '';
/** @var string */
public $name = '';
/**
* EntryCategory constructor.
*
* @param Category $category
*/
public function __construct(Category $category = null)
{
if (!is_null($category)) {
$this->categoryId = $category->id;
$this->name = $category->name;
}
}
}

View File

@@ -16,7 +16,6 @@ namespace FireflyIII\Export\Exporter;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Export\Entry\EntryAccount;
use FireflyIII\Models\ExportJob;
use Illuminate\Support\Collection;
use League\Csv\Writer;
use SplFileObject;
@@ -62,110 +61,24 @@ class CsvExporter extends BasicExporter implements ExporterInterface
$writer = Writer::createFromPath(new SplFileObject($fullPath, 'a+'), 'w');
$rows = [];
// Count the maximum number of sources and destinations each entry has. May need to expand the number of export fields:
$maxSourceAccounts = 1;
$maxDestAccounts = 1;
/** @var Entry $entry */
foreach ($this->getEntries() as $entry) {
$sources = $entry->sourceAccounts->count();
$destinations = $entry->destinationAccounts->count();
$maxSourceAccounts = max($maxSourceAccounts, $sources);
$maxDestAccounts = max($maxDestAccounts, $destinations);
}
$rows[] = array_keys($this->getFieldsAndTypes($maxSourceAccounts, $maxDestAccounts));
// get field names for header row:
$first = $this->getEntries()->first();
$headers = array_keys(get_object_vars($first));
$rows[] = $headers;
/** @var Entry $entry */
foreach ($this->getEntries() as $entry) {
// order is defined in Entry::getFieldsAndTypes.
$current = [$entry->description, $entry->amount, $entry->date];
$sourceData = $this->getAccountData($maxSourceAccounts, $entry->sourceAccounts);
$current = array_merge($current, $sourceData);
$destData = $this->getAccountData($maxDestAccounts, $entry->destinationAccounts);
$current = array_merge($current, $destData);
$rest = [$entry->budget->budgetId, $entry->budget->name, $entry->category->categoryId, $entry->category->name, $entry->bill->billId,
$entry->bill->name];
$current = array_merge($current, $rest);
$rows[] = $current;
$line = [];
foreach ($headers as $header) {
$line[] = $entry->$header;
}
$rows[] = $line;
}
$writer->insertAll($rows);
return true;
}
/**
* @param int $max
* @param Collection $accounts
*
* @return array
*/
private function getAccountData(int $max, Collection $accounts): array
{
$current = [];
for ($i = 0; $i < $max; $i++) {
/** @var EntryAccount $source */
$source = $accounts->get($i);
$currentId = '';
$currentName = '';
$currentIban = '';
$currentType = '';
$currentNumber = '';
if ($source) {
$currentId = $source->accountId;
$currentName = $source->name;
$currentIban = $source->iban;
$currentType = $source->type;
$currentNumber = $source->number;
}
$current[] = $currentId;
$current[] = $currentName;
$current[] = $currentIban;
$current[] = $currentType;
$current[] = $currentNumber;
}
unset($source);
return $current;
}
/**
* @param int $sources
* @param int $destinations
*
* @return array
*/
private function getFieldsAndTypes(int $sources, int $destinations): array
{
// key = field name (see top of class)
// value = field type (see csv.php under 'roles')
$array = [
'description' => 'description',
'amount' => 'amount',
'date' => 'date-transaction',
];
for ($i = 0; $i < $sources; $i++) {
$array['source_account_' . $i . '_id'] = 'account-id';
$array['source_account_' . $i . '_name'] = 'account-name';
$array['source_account_' . $i . '_iban'] = 'account-iban';
$array['source_account_' . $i . '_type'] = '_ignore';
$array['source_account_' . $i . '_number'] = 'account-number';
}
for ($i = 0; $i < $destinations; $i++) {
$array['destination_account_' . $i . '_id'] = 'account-id';
$array['destination_account_' . $i . '_name'] = 'account-name';
$array['destination_account_' . $i . '_iban'] = 'account-iban';
$array['destination_account_' . $i . '_type'] = '_ignore';
$array['destination_account_' . $i . '_number'] = 'account-number';
}
$array['budget_id'] = 'budget-id';
$array['budget_name'] = 'budget-name';
$array['category_id'] = 'category-id';
$array['category_name'] = 'category-name';
$array['bill_id'] = 'bill-id';
$array['bill_name'] = 'bill-name';
return $array;
}
private function tempFile()
{

View File

@@ -15,13 +15,13 @@ namespace FireflyIII\Export;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Export\Collector\AttachmentCollector;
use FireflyIII\Export\Collector\JournalCollector;
use FireflyIII\Export\Collector\UploadCollector;
use FireflyIII\Export\Entry\Entry;
use FireflyIII\Models\ExportJob;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Collection;
use Log;
use Storage;
use ZipArchive;
@@ -40,8 +40,6 @@ class Processor
/** @var bool */
public $includeAttachments;
/** @var bool */
public $includeConfig;
/** @var bool */
public $includeOldUploads;
/** @var ExportJob */
public $job;
@@ -68,7 +66,6 @@ class Processor
$this->accounts = $settings['accounts'];
$this->exportFormat = $settings['exportFormat'];
$this->includeAttachments = $settings['includeAttachments'];
$this->includeConfig = $settings['includeConfig'];
$this->includeOldUploads = $settings['includeOldUploads'];
$this->job = $settings['job'];
$this->journals = new Collection;
@@ -84,8 +81,9 @@ class Processor
{
/** @var AttachmentCollector $attachmentCollector */
$attachmentCollector = app(AttachmentCollector::class, [$this->job]);
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
$attachmentCollector->run();
$this->files = $this->files->merge($attachmentCollector->getFiles());
$this->files = $this->files->merge($attachmentCollector->getEntries());
return true;
}
@@ -95,9 +93,13 @@ 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 JournalCollector $collector */
$collector = app(JournalCollector::class, [$this->job]);
$collector->setDates($this->settings['startDate'], $this->settings['endDate']);
$collector->setAccounts($this->settings['accounts']);
$collector->run();
$this->journals = $collector->getEntries();
Log::debug(sprintf('Count %d journals in collectJournals() ', $this->journals->count()));
return true;
}
@@ -111,7 +113,7 @@ class Processor
$uploadCollector = app(UploadCollector::class, [$this->job]);
$uploadCollector->run();
$this->files = $this->files->merge($uploadCollector->getFiles());
$this->files = $this->files->merge($uploadCollector->getEntries());
return true;
}
@@ -122,22 +124,11 @@ class Processor
public function convertJournals(): bool
{
$count = 0;
/** @var TransactionJournal $journal */
foreach ($this->journals as $journal) {
$this->exportEntries->push(Entry::fromJournal($journal));
foreach ($this->journals as $object) {
$this->exportEntries->push(Entry::fromObject($object));
$count++;
}
return true;
}
/**
* @return bool
*/
public function createConfigFile(): bool
{
$this->configurationMaker = app(ConfigurationFile::class, [$this->job]);
$this->files->push($this->configurationMaker->make());
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
return true;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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');
}
}
}

View File

@@ -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 updateRepetition(UpdatedBudgetLimit $event): bool
{
$budgetLimit = $event->budgetLimit;
$end = $event->end;
@@ -104,6 +104,7 @@ class BudgetLimitEventHandler
$repetition->save();
}
}
return true;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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 logoutUser(): 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -26,47 +26,39 @@ class Help implements HelpInterface
{
/**
*
* @param string $key
* @param string $route
* @param string $language
*
* @return string
*/
public function getFromCache(string $key): string
public function getFromCache(string $route, string $language): string
{
return Cache::get($key);
return Cache::get('help.' . $route . '.' . $language);
}
/**
* @param string $language
* @param string $route
*
* @return array
* @return string
*/
public function getFromGithub(string $language, string $route): array
public function getFromGithub(string $language, string $route): string
{
$uri = sprintf('https://raw.githubusercontent.com/JC5/firefly-iii-help/master/%s/%s.md', $language, $route);
$routeIndex = str_replace('.', '-', $route);
$title = trans('help.' . $routeIndex);
$content = [
'text' => '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>',
'title' => $title,
];
$result = Requests::get($uri);
$uri = sprintf('https://raw.githubusercontent.com/firefly-iii/help/master/%s/%s.md', $language, $route);
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
$result = Requests::get($uri);
if ($result->status_code === 200) {
$content['text'] = $result->body;
$content = $result->body;
}
if (strlen(trim($content['text'])) == 0) {
$content['text'] = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
if (strlen(trim($content)) == 0) {
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
}
$converter = new CommonMarkConverter();
$content['text'] = $converter->convertToHtml($content['text']);
$converter = new CommonMarkConverter();
$content = $converter->convertToHtml($content);
return $content;
@@ -84,27 +76,26 @@ class Help implements HelpInterface
}
/**
*
* @param string $route
* @param string $language
*
* @return bool
*/
public function inCache(string $route):bool
public function inCache(string $route, string $language):bool
{
return Cache::has('help.' . $route . '.title') && Cache::has('help.' . $route . '.text');
return Cache::has('help.' . $route . '.' . $language);
}
/**
*
* @param string $route
* @param string $language
* @param array $content
* @param string $content
*
* @internal param $title
*/
public function putInCache(string $route, string $language, array $content)
public function putInCache(string $route, string $language, string $content)
{
Cache::put('help.' . $route . '.text.' . $language, $content['text'], 10080); // a week.
Cache::put('help.' . $route . '.title.' . $language, $content['title'], 10080);
Cache::put('help.' . $route . '.' . $language, $content, 10080); // a week.
}
}

View File

@@ -21,19 +21,20 @@ interface HelpInterface
{
/**
* @param string $key
* @param string $route
* @param string $language
*
* @return string
*/
public function getFromCache(string $key): string;
public function getFromCache(string $route, string $language): string;
/**
* @param string $language
* @param string $route
*
* @return array
* @return string
*/
public function getFromGithub(string $language, string $route):array;
public function getFromGithub(string $language, string $route):string;
/**
* @param string $route
@@ -44,15 +45,16 @@ interface HelpInterface
/**
* @param string $route
* @param string $language
*
* @return bool
*/
public function inCache(string $route): bool;
public function inCache(string $route, string $language ): bool;
/**
* @param string $route
* @param string $language
* @param array $content
* @param string $content
*/
public function putInCache(string $route, string $language, array $content);
public function putInCache(string $route, string $language, string $content);
}

View File

@@ -102,7 +102,8 @@ class ReportHelper implements ReportHelperInterface
$billLine->setHit(true);
}
if ($billLine->isActive()) {
// non active AND non hit? do not add:
if ($billLine->isActive() || $billLine->isHit()) {
$collection->addBill($billLine);
}
}

View File

@@ -15,9 +15,11 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use ExpandedForm;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\AccountFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties;
@@ -44,8 +46,16 @@ class AccountController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', trans('firefly.accounts'));
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', trans('firefly.accounts'));
return $next($request);
}
);
}
/**
@@ -114,12 +124,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];
@@ -147,7 +156,7 @@ class AccountController extends Controller
'ccMonthlyPaymentDate' => $account->getMeta('ccMonthlyPaymentDate'),
'openingBalanceDate' => $openingBalanceDate,
'openingBalance' => $openingBalanceAmount,
'virtualBalance' => round($account->virtual_balance, 2),
'virtualBalance' => $account->virtual_balance,
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'accounts');
@@ -201,6 +210,9 @@ class AccountController extends Controller
*/
public function show(AccountTaskerInterface $tasker, ARI $repository, Account $account)
{
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
return $this->redirectToOriginalAccount($account);
}
// show journals from current period only:
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$subTitle = $account->name;
@@ -298,23 +310,8 @@ class AccountController extends Controller
*/
public function store(AccountFormRequest $request, ARI $repository)
{
$accountData = [
'name' => trim($request->input('name')),
'accountType' => $request->input('what'),
'virtualBalance' => round($request->input('virtualBalance'), 2),
'virtualBalanceCurrency' => intval($request->input('amount_currency_id_virtualBalance')),
'active' => true,
'user' => auth()->user()->id,
'iban' => trim($request->input('iban')),
'accountNumber' => trim($request->input('accountNumber')),
'accountRole' => $request->input('accountRole'),
'openingBalance' => round($request->input('openingBalance'), 2),
'openingBalanceDate' => new Carbon((string)$request->input('openingBalanceDate')),
'openingBalanceCurrency' => intval($request->input('amount_currency_id_openingBalance')),
];
$account = $repository->store($accountData);
$data = $request->getAccountData();
$account = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_new_account', ['name' => $account->name])));
Preferences::mark();
@@ -346,22 +343,8 @@ class AccountController extends Controller
*/
public function update(AccountFormRequest $request, ARI $repository, Account $account)
{
$accountData = [
'name' => $request->input('name'),
'active' => $request->input('active'),
'user' => auth()->user()->id,
'iban' => $request->input('iban'),
'accountNumber' => $request->input('accountNumber'),
'accountRole' => $request->input('accountRole'),
'virtualBalance' => round($request->input('virtualBalance'), 2),
'openingBalance' => round($request->input('openingBalance'), 2),
'openingBalanceDate' => new Carbon((string)$request->input('openingBalanceDate')),
'openingBalanceCurrency' => intval($request->input('amount_currency_id_openingBalance')),
'ccType' => $request->input('ccType'),
'ccMonthlyPaymentDate' => $request->input('ccMonthlyPaymentDate'),
];
$repository->update($account, $accountData);
$data = $request->getAccountData();
$repository->update($account, $data);
Session::flash('success', strval(trans('firefly.updated_account', ['name' => $account->name])));
Preferences::mark();
@@ -393,4 +376,29 @@ class AccountController extends Controller
return '';
}
/**
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws FireflyException
*/
private function redirectToOriginalAccount(Account $account)
{
/** @var Transaction $transaction */
$transaction = $account->transactions()->first();
if (is_null($transaction)) {
throw new FireflyException('Expected a transaction. This account has none. BEEP, error.');
}
$journal = $transaction->transactionJournal;
/** @var Transaction $opposingTransaction */
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
if (is_null($opposingTransaction)) {
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.');
}
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
}
}

View File

@@ -37,8 +37,15 @@ class ConfigurationController extends Controller
{
parent::__construct();
View::share('title', strval(trans('firefly.administration')));
View::share('mainTitleIcon', 'fa-hand-spock-o');
$this->middleware(
function ($request, $next) {
View::share('title', strval(trans('firefly.administration')));
View::share('mainTitleIcon', 'fa-hand-spock-o');
return $next($request);
}
);
}
@@ -66,10 +73,10 @@ class ConfigurationController extends Controller
public function store(ConfigurationRequest $request)
{
// get config values:
$singleUserMode = intval($request->get('single_user_mode')) === 1 ? true : false;
$data = $request->getConfigurationData();
// store config values
FireflyConfig::set('single_user_mode', $singleUserMode);
FireflyConfig::set('single_user_mode', $data['single_user_mode']);
// flash message
Session::flash('success', strval(trans('firefly.configuration_updated')));

View File

@@ -15,6 +15,7 @@ namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\User;
use Illuminate\Http\Request;
@@ -83,6 +84,7 @@ class DomainController extends Controller
*/
public function toggleDomain(string $domain)
{
$domain = strtolower($domain);
$blocked = FireflyConfig::get('blocked-domains', [])->data;
if (in_array($domain, $blocked)) {
@@ -111,15 +113,16 @@ class DomainController extends Controller
*/
private function getKnownDomains(): array
{
$users = User::get();
$set = [];
$filtered = [];
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
$users = $repository->all();
$set = [];
$filtered = [];
/** @var User $user */
foreach ($users as $user) {
$email = $user->email;
$parts = explode('@', $email);
$domain = $parts[1];
$set[] = $domain;
$email = $user->email;
$parts = explode('@', $email);
$set[] = strtolower($parts[1]);
}
$set = array_unique($set);
// filter for already banned domains:
@@ -131,7 +134,6 @@ class DomainController extends Controller
$filtered[] = $domain;
}
}
asort($filtered);
return $filtered;
}

View File

@@ -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'
)
);
}
}

View File

@@ -42,8 +42,16 @@ class AttachmentController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-paperclip');
View::share('title', trans('firefly.attachments'));
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-paperclip');
View::share('title', trans('firefly.attachments'));
return $next($request);
}
);
}
/**
@@ -164,14 +172,8 @@ class AttachmentController extends Controller
*/
public function update(AttachmentFormRequest $request, AttachmentRepositoryInterface $repository, Attachment $attachment)
{
$attachmentData = [
'title' => $request->input('title'),
'description' => $request->input('description'),
'notes' => $request->input('notes'),
];
$repository->update($attachment, $attachmentData);
$data = $request->getAttachmentData();
$repository->update($attachment, $data);
Session::flash('success', strval(trans('firefly.attachment_updated', ['name' => $attachment->filename])));
Preferences::mark();

View File

@@ -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';
}

View File

@@ -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);

View File

@@ -41,6 +41,8 @@ class ResetPasswordController extends Controller
*/
public function __construct()
{
parent::__construct();
$this->middleware('guest');
}
}

View File

@@ -38,8 +38,16 @@ class BillController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.bills'));
View::share('mainTitleIcon', 'fa-calendar-o');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.bills'));
View::share('mainTitleIcon', 'fa-calendar-o');
return $next($request);
}
);
}
/**
@@ -137,23 +145,15 @@ class BillController extends Controller
$bills = $repository->getBills();
$bills->each(
function (Bill $bill) use ($repository, $start, $end) {
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
$bill->lastFoundMatch = $repository->lastFoundMatch($bill);
$journals = $repository->getJournalsInRange($bill, $start, $end);
// loop journals, find average:
$average = '0';
$count = $journals->count();
if ($count > 0) {
$sum = '0';
foreach ($journals as $journal) {
$sum = bcadd($sum, TransactionJournal::amountPositive($journal));
}
$average = bcdiv($sum, strval($count));
// paid in this period?
$bill->paidDates = $repository->getPaidDatesInRange($bill, $start, $end);
$bill->payDates = $repository->getPayDatesInRange($bill, $start, $end);
$lastDate = clone $start;
if ($bill->paidDates->count() >= $bill->payDates->count()) {
$lastDate = $end;
}
$bill->lastPaidAmount = $average;
$bill->paidInPeriod = ($start <= $bill->lastFoundMatch) && ($end >= $bill->lastFoundMatch);
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastDate);
}
);
@@ -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);

View File

@@ -26,6 +26,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Input;
use Log;
use Navigation;
use Preferences;
use Response;
@@ -47,9 +48,17 @@ class BudgetController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.budgets'));
View::share('mainTitleIcon', 'fa-tasks');
View::share('hideBudgets', true);
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.budgets'));
View::share('mainTitleIcon', 'fa-tasks');
return $next($request);
}
);
}
/**
@@ -191,10 +200,12 @@ class BudgetController extends Controller
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$startAsString = $start->format('Y-m-d');
$endAsString = $end->format('Y-m-d');
Log::debug('Now at /budgets');
// loop the budgets:
/** @var Budget $budget */
foreach ($budgets as $budget) {
Log::debug(sprintf('Now at budget #%d ("%s")', $budget->id, $budget->name));
$budget->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end);
$allRepetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
$otherRepetitions = new Collection;
@@ -282,13 +293,13 @@ class BudgetController extends Controller
}
/**
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
* @param BudgetRepositoryInterface $repository
* @param AccountRepositoryInterface $accountRepository
* @param Budget $budget
*
* @return View
* @throws FireflyException
*/
public function show(BudgetRepositoryInterface $repository, Budget $budget)
public function show(BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget)
{
/** @var Carbon $start */
$start = session('first', Carbon::create()->startOfYear());
@@ -300,6 +311,7 @@ class BudgetController extends Controller
$count = $journals->count();
$journals = $journals->slice($offset, $pageSize);
$journals = new LengthAwarePaginator($journals, $count, $pageSize);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$journals->setPath('/budgets/show/' . $budget->id);
@@ -310,7 +322,7 @@ class BudgetController extends Controller
/** @var LimitRepetition $entry */
foreach ($set as $entry) {
$entry->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $entry->startdate, $entry->enddate);
$entry->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $entry->startdate, $entry->enddate);
$limits->push($entry);
}
@@ -318,15 +330,17 @@ class BudgetController extends Controller
}
/**
* @param BudgetRepositoryInterface $repository
* @param Budget $budget
* @param LimitRepetition $repetition
* @param BudgetRepositoryInterface $repository
* @param AccountRepositoryInterface $accountRepository
* @param Budget $budget
* @param LimitRepetition $repetition
*
* @return View
* @throws FireflyException
*/
public function showWithRepetition(BudgetRepositoryInterface $repository, Budget $budget, LimitRepetition $repetition)
{
public function showWithRepetition(
BudgetRepositoryInterface $repository, AccountRepositoryInterface $accountRepository, Budget $budget, LimitRepetition $repetition
) {
if ($repetition->budgetLimit->budget->id != $budget->id) {
throw new FireflyException('This budget limit is not part of this budget.');
}
@@ -340,11 +354,13 @@ class BudgetController extends Controller
$journals = $journals->slice($offset, $pageSize);
$journals = new LengthAwarePaginator($journals, $count, $pageSize);
$subTitle = trans('firefly.budget_in_month', ['name' => $budget->name, 'month' => $repetition->startdate->formatLocalized($this->monthFormat)]);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$journals->setPath('/budgets/show/' . $budget->id . '/' . $repetition->id);
$repetition->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $repetition->startdate, $repetition->enddate);
$repetition->spent = $repository->spentInPeriod(new Collection([$budget]), $accounts, $repetition->startdate, $repetition->enddate);
$limits = new Collection([$repetition]);
return view('budgets.show', compact('limits', 'budget', 'repetition', 'journals', 'subTitle'));
@@ -359,11 +375,8 @@ class BudgetController extends Controller
*/
public function store(BudgetFormRequest $request, BudgetRepositoryInterface $repository)
{
$budgetData = [
'name' => $request->input('name'),
'user' => auth()->user()->id,
];
$budget = $repository->store($budgetData);
$data = $request->getBudgetData();
$budget = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_new_budget', ['name' => e($budget->name)])));
Preferences::mark();
@@ -389,12 +402,8 @@ class BudgetController extends Controller
*/
public function update(BudgetFormRequest $request, BudgetRepositoryInterface $repository, Budget $budget)
{
$budgetData = [
'name' => $request->input('name'),
'active' => intval($request->input('active')) == 1,
];
$repository->update($budget, $budgetData);
$data = $request->getBudgetData();
$repository->update($budget, $data);
Session::flash('success', strval(trans('firefly.updated_budget', ['name' => e($budget->name)])));
Preferences::mark();

View File

@@ -43,8 +43,16 @@ class CategoryController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.categories'));
View::share('mainTitleIcon', 'fa-bar-chart');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.categories'));
View::share('mainTitleIcon', 'fa-bar-chart');
return $next($request);
}
);
}
/**
@@ -264,11 +272,8 @@ class CategoryController extends Controller
*/
public function store(CategoryFormRequest $request, CRI $repository)
{
$categoryData = [
'name' => trim($request->input('name')),
'user' => auth()->user()->id,
];
$category = $repository->store($categoryData);
$data = $request->getCategoryData();
$category = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_category', ['name' => e($category->name)])));
Preferences::mark();
@@ -292,11 +297,8 @@ class CategoryController extends Controller
*/
public function update(CategoryFormRequest $request, CRI $repository, Category $category)
{
$categoryData = [
'name' => $request->input('name'),
];
$repository->update($category, $categoryData);
$data = $request->getCategoryData();
$repository->update($category, $data);
Session::flash('success', strval(trans('firefly.updated_category', ['name' => e($category->name)])));
Preferences::mark();

View File

@@ -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.
*

View File

@@ -46,10 +46,18 @@ class Controller extends BaseController
View::share('hideBills', false);
View::share('hideTags', false);
// save some formats:
$this->monthFormat = (string)trans('config.month');
$this->monthAndDayFormat = (string)trans('config.month_and_day');
$this->dateTimeFormat = (string)trans('config.date_time');
// translations:
$this->middleware(
function ($request, $next) {
$this->monthFormat = (string)trans('config.month');
$this->monthAndDayFormat = (string)trans('config.month_and_day');
$this->dateTimeFormat = (string)trans('config.date_time');
return $next($request);
}
);
}

View File

@@ -39,8 +39,16 @@ class CurrencyController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.currencies'));
View::share('mainTitleIcon', 'fa-usd');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.currencies'));
View::share('mainTitleIcon', 'fa-usd');
return $next($request);
}
);
}
/**

View File

@@ -41,8 +41,16 @@ class ExportController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-file-archive-o');
View::share('title', trans('firefly.export_data'));
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-file-archive-o');
View::share('title', trans('firefly.export_data'));
return $next($request);
}
);
}
/**
@@ -133,7 +141,6 @@ class ExportController extends Controller
'endDate' => new Carbon($request->get('export_end_range')),
'exportFormat' => $request->get('exportFormat'),
'includeAttachments' => intval($request->get('include_attachments')) === 1,
'includeConfig' => intval($request->get('include_config')) === 1,
'includeOldUploads' => intval($request->get('include_old_uploads')) === 1,
'job' => $job,
];
@@ -177,15 +184,6 @@ class ExportController extends Controller
$job->change('export_status_collected_old_uploads');
}
/*
* Generate / collect config file.
*/
if ($settings['includeConfig']) {
$job->change('export_status_creating_config_file');
$processor->createConfigFile();
$job->change('export_status_created_config_file');
}
/*
* Create ZIP file:
*/

View File

@@ -41,11 +41,9 @@ class HelpController extends Controller
*/
public function show(HelpInterface $help, string $route)
{
$language = Preferences::get('language', config('firefly.default_language', 'en_US'))->data;
$content = [
'text' => '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>',
'title' => 'Help',
];
$content = '<p>' . strval(trans('firefly.route_has_no_help')) . '</p>';
if (!$help->hasRoute($route)) {
Log::error('No such route: ' . $route);
@@ -53,11 +51,9 @@ class HelpController extends Controller
return Response::json($content);
}
if ($help->inCache($route)) {
$content = [
'text' => $help->getFromCache('help.' . $route . '.text.' . $language),
'title' => $help->getFromCache('help.' . $route . '.title.' . $language),
];
if ($help->inCache($route, $language)) {
$content = $help->getFromCache($route, $language);
Log::debug('Help text was in cache.');
return Response::json($content);
}

View File

@@ -26,7 +26,7 @@ use Log;
use Preferences;
use Route;
use Session;
use View;
/**
* Class HomeController
@@ -41,6 +41,8 @@ class HomeController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', 'Firefly III');
View::share('mainTitleIcon', 'fa-fire');
}
/**
@@ -128,19 +130,18 @@ class HomeController extends Controller
return redirect(route('new-user.index'));
}
$title = 'Firefly';
$subTitle = trans('firefly.welcomeBack');
$mainTitleIcon = 'fa-fire';
$transactions = [];
$frontPage = Preferences::get(
$subTitle = trans('firefly.welcomeBack');
$transactions = [];
$frontPage = Preferences::get(
'frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray()
);
/** @var Carbon $start */
$start = session('start', Carbon::now()->startOfMonth());
/** @var Carbon $end */
$end = session('end', Carbon::now()->endOfMonth());
$showTour = Preferences::get('tour', true)->data;
$accounts = $repository->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')
);
}
@@ -163,38 +164,43 @@ class HomeController extends Controller
public function routes()
{
// these routes are not relevant for the help pages:
$ignore = [
$ignore = ['login', 'registe', 'logout', 'two-fac', 'lost-two', 'confirm', 'resend', 'do_confirm', 'testFla', 'json.', 'piggy-banks.add',
'piggy-banks.remove', 'preferences.', 'rules.rule.up', 'rules.rule.down', 'rules.rule-group.up', 'rules.rule-group.down', 'popup.report',
'admin.users.domains.block-', 'import.json', 'help.',
];
$routes = Route::getRoutes();
echo '<pre>';
/** @var \Illuminate\Routing\Route $route */
foreach ($routes as $route) {
$name = $route->getName();
$methods = $route->getMethods();
$search = [
'{account}', '{what}', '{rule}', '{tj}', '{category}', '{budget}', '{code}', '{date}', '{attachment}', '{bill}', '{limitrepetition}',
'{currency}', '{jobKey}', '{piggyBank}', '{ruleGroup}', '{rule}', '{route}', '{unfinishedJournal}',
'{reportType}', '{start_date}', '{end_date}', '{accountList}', '{tag}', '{journalList}',
];
$replace = [1, 'asset', 1, 1, 1, 1, 'abc', '2016-01-01', 1, 1, 1, 1, 1, 1, 1, 1, 'index', 1,
'default', '20160101', '20160131', '1,2', 1, '1,2',
];
if (count($search) != count($replace)) {
echo 'count';
exit;
}
$url = str_replace($search, $replace, $route->getUri());
if (!is_null($name) && in_array('GET', $methods) && !$this->startsWithAny($ignore, $name)) {
echo '<a href="/' . $url . '" title="' . $name . '">' . $name . '</a><br>' . "\n";
if (!is_null($name) && strlen($name) > 0 && in_array('GET', $methods) && !$this->startsWithAny($ignore, $name)) {
echo sprintf('touch %s.md', $name) . "\n";
}
}
echo '</pre>';
return '<hr>';
echo '<hr />';
return '&nbsp;';
}
/**
* @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

View File

@@ -41,8 +41,15 @@ class ImportController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-archive');
View::share('title', trans('firefly.import_data_full'));
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-archive');
View::share('title', trans('firefly.import_data_full'));
return $next($request);
}
);
}
/**

View File

@@ -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;
}

View File

@@ -32,6 +32,13 @@ class NewUserController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
return $next($request);
}
);
}
@@ -107,7 +114,6 @@ class NewUserController extends Controller
'accountType' => 'asset',
'virtualBalance' => 0,
'active' => true,
'user' => auth()->user()->id,
'accountRole' => 'defaultAsset',
'openingBalance' => round($request->input('bank_balance'), 2),
'openingBalanceDate' => new Carbon,
@@ -133,7 +139,6 @@ class NewUserController extends Controller
'accountType' => 'asset',
'virtualBalance' => 0,
'active' => true,
'user' => auth()->user()->id,
'accountRole' => 'savingAsset',
'openingBalance' => round($request->input('savings_balance'), 2),
'openingBalanceDate' => new Carbon,
@@ -158,7 +163,6 @@ class NewUserController extends Controller
'accountType' => 'asset',
'virtualBalance' => round($request->get('credit_card_limit'), 2),
'active' => true,
'user' => auth()->user()->id,
'accountRole' => 'ccAsset',
'openingBalance' => null,
'openingBalanceDate' => null,

View File

@@ -45,8 +45,16 @@ class PiggyBankController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.piggyBanks'));
View::share('mainTitleIcon', 'fa-sort-amount-asc');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.piggyBanks'));
View::share('mainTitleIcon', 'fa-sort-amount-asc');
return $next($request);
}
);
}
/**
@@ -157,6 +165,7 @@ class PiggyBankController extends Controller
$subTitle = trans('firefly.update_piggy_title', ['name' => $piggyBank->name]);
$subTitleIcon = 'fa-pencil';
$targetDate = null;
$note = $piggyBank->notes()->first();
/*
* Flash some data to fill the form.
*/
@@ -168,6 +177,7 @@ class PiggyBankController extends Controller
'account_id' => $piggyBank->account_id,
'targetamount' => $piggyBank->targetamount,
'targetdate' => $targetDate,
'note' => is_null($note) ? '' : $note->text,
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'piggy-banks');
@@ -346,10 +356,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'));
}
@@ -361,17 +372,8 @@ class PiggyBankController extends Controller
*/
public function store(PiggyBankFormRequest $request, PiggyBankRepositoryInterface $repository)
{
$piggyBankData = [
'name' => $request->get('name'),
'startdate' => new Carbon,
'account_id' => intval($request->get('account_id')),
'targetamount' => round($request->get('targetamount'), 2),
'order' => $repository->getMaxOrder() + 1,
'targetdate' => strlen($request->get('targetdate')) > 0 ? new Carbon($request->get('targetdate')) : null,
];
$piggyBank = $repository->store($piggyBankData);
$data = $request->getPiggyBankData();
$piggyBank = $repository->store($data);
Session::flash('success', strval(trans('firefly.stored_piggy_bank', ['name' => e($piggyBank->name)])));
Preferences::mark();
@@ -396,15 +398,8 @@ class PiggyBankController extends Controller
*/
public function update(PiggyBankRepositoryInterface $repository, PiggyBankFormRequest $request, PiggyBank $piggyBank)
{
$piggyBankData = [
'name' => $request->get('name'),
'startdate' => is_null($piggyBank->startdate) ? $piggyBank->created_at : $piggyBank->startdate,
'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,
];
$piggyBank = $repository->update($piggyBank, $piggyBankData);
$data = $request->getPiggyBankData();
$piggyBank = $repository->update($piggyBank, $data);
Session::flash('success', strval(trans('firefly.updated_piggy_bank', ['name' => e($piggyBank->name)])));
Preferences::mark();

View File

@@ -173,7 +173,10 @@ class ReportController extends Controller
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$category = $repository->find(intval($attributes['categoryId']));
$journals = $repository->journalsInPeriod(new Collection([$category]), $attributes['accounts'], [], $attributes['startDate'], $attributes['endDate']);
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$journals = $repository->journalsInPeriod(
new Collection([$category]), $attributes['accounts'], $types, $attributes['startDate'], $attributes['endDate']
);
$view = view('popup.report.category-entry', compact('journals', 'category'))->render();
return $view;

View File

@@ -35,8 +35,16 @@ class PreferencesController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.preferences'));
View::share('mainTitleIcon', 'fa-gear');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.preferences'));
View::share('mainTitleIcon', 'fa-gear');
return $next($request);
}
);
}
/**
@@ -76,26 +84,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 +154,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) {

View File

@@ -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,16 @@ class ProfileController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.profile'));
View::share('mainTitleIcon', 'fa-user');
return $next($request);
}
);
}
/**
@@ -42,9 +52,11 @@ class ProfileController extends Controller
*/
public function changePassword()
{
return view('profile.change-password')->with('title', auth()->user()->email)->with('subTitle', trans('firefly.change_your_password'))->with(
'mainTitleIcon', 'fa-user'
);
$title = auth()->user()->email;
$subTitle = strval(trans('firefly.change_your_password'));
$subTitleIcon = 'fa-key';
return view('profile.change-password', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
@@ -52,9 +64,11 @@ class ProfileController extends Controller
*/
public function deleteAccount()
{
return view('profile.delete-account')->with('title', auth()->user()->email)->with('subTitle', trans('firefly.delete_account'))->with(
'mainTitleIcon', 'fa-user'
);
$title = auth()->user()->email;
$subTitle = strval(trans('firefly.delete_account'));
$subTitleIcon = 'fa-trash';
return view('profile.delete-account', compact('title', 'subTitle', 'subTitleIcon'));
}
/**
@@ -63,7 +77,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 +127,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;

View File

@@ -17,6 +17,7 @@ namespace FireflyIII\Http\Controllers\Report;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
@@ -36,9 +37,23 @@ class AccountController extends Controller
*/
public function accountReport(Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$accountTasker = app(AccountTaskerInterface::class);
$accountReport = $accountTasker->getAccountReport($start, $end, $accounts);
return view('reports.partials.accounts', compact('accountReport'));
$result = view('reports.partials.accounts', compact('accountReport'))->render();
$cache->store($result);
return $result;
}
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* BalanceController.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\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\BalanceReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
* Class BalanceController
*
* @package FireflyIII\Http\Controllers\Report
*/
class BalanceController extends Controller
{
/**
* @param BalanceReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return string
*/
public function balanceReport(BalanceReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('balance-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$balance = $helper->getBalanceReport($start, $end, $accounts);
$result = view('reports.partials.balance', compact('balance'))->render();
$cache->store($result);
return $result;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* CategoryController.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\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
/**
* Class CategoryController
*
* @package FireflyIII\Http\Controllers\Report
*/
class CategoryController extends Controller
{
/**
* @param ReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function categoryReport(ReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('category-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
}
$categories = $helper->getCategoryReport($start, $end, $accounts);
$result = view('reports.partials.categories', compact('categories'))->render();
$cache->store($result);
return $result;
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* InOutController.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\Report;
use Carbon\Carbon;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use Response;
/**
* Class InOutController
*
* @package FireflyIII\Http\Controllers\Report
*/
class InOutController extends Controller
{
/**
* @param ReportHelperInterface $helper
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @return \Illuminate\Http\JsonResponse
*/
public function inOutReport(ReportHelperInterface $helper, Carbon $start, Carbon $end, Collection $accounts)
{
// chart properties for cache:
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('in-out-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return Response::json($cache->get());
}
$incomes = $helper->getIncomeReport($start, $end, $accounts);
$expenses = $helper->getExpenseReport($start, $end, $accounts);
$result = [
'income' => view('reports.partials.income', compact('incomes'))->render(),
'expenses' => view('reports.partials.expenses', compact('expenses'))->render(),
'incomes_expenses' => view('reports.partials.income-vs-expenses', compact('expenses', 'incomes'))->render(),
];
$cache->store($result);
return Response::json($result);
}
}

View File

@@ -15,14 +15,12 @@ namespace FireflyIII\Http\Controllers;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Report\BalanceReportHelperInterface;
use FireflyIII\Helpers\Report\BudgetReportHelperInterface;
use FireflyIII\Helpers\Report\ReportHelperInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Account\AccountRepositoryInterface as ARI;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
@@ -39,10 +37,6 @@ use View;
*/
class ReportController extends Controller
{
/** @var BalanceReportHelperInterface */
protected $balanceHelper;
/** @var BudgetReportHelperInterface */
protected $budgetHelper;
/** @var ReportHelperInterface */
@@ -55,8 +49,18 @@ class ReportController extends Controller
{
parent::__construct();
View::share('title', trans('firefly.reports'));
View::share('mainTitleIcon', 'fa-line-chart');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.reports'));
View::share('mainTitleIcon', 'fa-line-chart');
$this->helper = app(ReportHelperInterface::class);
$this->budgetHelper = app(BudgetReportHelperInterface::class);
return $next($request);
}
);
}
@@ -67,7 +71,7 @@ class ReportController extends Controller
*/
public function index(AccountRepositoryInterface $repository)
{
$this->createRepositories();
/** @var Carbon $start */
$start = clone session('first');
$months = $this->helper->listOfMonths($start);
@@ -98,7 +102,6 @@ class ReportController extends Controller
*/
public function report(string $reportType, Carbon $start, Carbon $end, Collection $accounts)
{
$this->createRepositories();
// throw an error if necessary.
if ($end < $start) {
throw new FireflyException('End date cannot be before start date, silly!');
@@ -153,8 +156,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 = [];
@@ -208,16 +209,6 @@ class ReportController extends Controller
return view('reports.audit.report', compact('start', 'end', 'reportType', 'accountIds', 'accounts', 'auditData', 'hideable', 'defaultShow'));
}
/**
*
*/
private function createRepositories()
{
$this->helper = app(ReportHelperInterface::class);
$this->budgetHelper = app(BudgetReportHelperInterface::class);
$this->balanceHelper = app(BalanceReportHelperInterface::class);
}
/**
* @param $reportType
* @param Carbon $start
@@ -228,17 +219,10 @@ class ReportController extends Controller
*/
private function defaultMonth(string $reportType, Carbon $start, Carbon $end, Collection $accounts)
{
$incomeTopLength = 8;
$expenseTopLength = 8;
// get report stuff!
$incomes = $this->helper->getIncomeReport($start, $end, $accounts);
$expenses = $this->helper->getExpenseReport($start, $end, $accounts);
$budgets = $this->budgetHelper->getBudgetReport($start, $end, $accounts);
$categories = $this->helper->getCategoryReport($start, $end, $accounts);
$balance = $this->balanceHelper->getBalanceReport($start, $end, $accounts);
$bills = $this->helper->getBillReport($start, $end, $accounts);
$tags = $this->helper->tagReport($start, $end, $accounts);
$budgets = $this->budgetHelper->getBudgetReport($start, $end, $accounts);
$bills = $this->helper->getBillReport($start, $end, $accounts);
$tags = $this->helper->tagReport($start, $end, $accounts);
// and some id's, joined:
$accountIds = join(',', $accounts->pluck('id')->toArray());
@@ -247,12 +231,9 @@ class ReportController extends Controller
return view(
'reports.default.month',
compact(
'start', 'end', 'reportType',
'start', 'end',
'tags',
'incomes', 'incomeTopLength',
'expenses', 'expenseTopLength',
'budgets', 'balance',
'categories',
'budgets',
'bills',
'accountIds', 'reportType'
)
@@ -270,13 +251,8 @@ class ReportController extends Controller
private function defaultMultiYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts)
{
$incomeTopLength = 8;
$expenseTopLength = 8;
// list of users stuff:
$budgets = app(BudgetRepositoryInterface::class)->getActiveBudgets();
$categories = app(CategoryRepositoryInterface::class)->getCategories();
$incomes = $this->helper->getIncomeReport($start, $end, $accounts);
$expenses = $this->helper->getExpenseReport($start, $end, $accounts);
$tags = $this->helper->tagReport($start, $end, $accounts);
// and some id's, joined:
@@ -290,9 +266,7 @@ class ReportController extends Controller
return view(
'reports.default.multi-year',
compact(
'budgets', 'accounts', 'categories', 'start', 'end', 'accountIds', 'reportType',
'incomes', 'expenses',
'incomeTopLength', 'expenseTopLength', 'tags'
'budgets', 'accounts', 'categories', 'start', 'end', 'accountIds', 'reportType', 'tags'
)
);
}
@@ -307,13 +281,8 @@ class ReportController extends Controller
*/
private function defaultYear(string $reportType, Carbon $start, Carbon $end, Collection $accounts)
{
$incomeTopLength = 8;
$expenseTopLength = 8;
$incomes = $this->helper->getIncomeReport($start, $end, $accounts);
$expenses = $this->helper->getExpenseReport($start, $end, $accounts);
$tags = $this->helper->tagReport($start, $end, $accounts);
$budgets = $this->budgetHelper->budgetYearOverview($start, $end, $accounts);
$tags = $this->helper->tagReport($start, $end, $accounts);
$budgets = $this->budgetHelper->budgetYearOverview($start, $end, $accounts);
Session::flash('gaEventCategory', 'report');
Session::flash('gaEventAction', 'year');
@@ -330,8 +299,7 @@ class ReportController extends Controller
return view(
'reports.default.year',
compact(
'start', 'incomes', 'reportType', 'accountIds', 'end',
'expenses', 'incomeTopLength', 'expenseTopLength', 'tags', 'budgets'
'start', 'reportType', 'accountIds', 'end', 'tags', 'budgets'
)
);
}

View File

@@ -42,8 +42,16 @@ class RuleController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.rules'));
View::share('mainTitleIcon', 'fa-random');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.rules'));
View::share('mainTitleIcon', 'fa-random');
return $next($request);
}
);
}
/**
@@ -238,23 +246,8 @@ class RuleController extends Controller
*/
public function store(RuleFormRequest $request, RuleRepositoryInterface $repository, RuleGroup $ruleGroup)
{
// process the rule itself:
$data = [
'rule_group_id' => $ruleGroup->id,
'title' => $request->get('title'),
'user_id' => auth()->user()->id,
'trigger' => $request->get('trigger'),
'description' => $request->get('description'),
'rule-triggers' => $request->get('rule-trigger'),
'rule-trigger-values' => $request->get('rule-trigger-value'),
'rule-trigger-stop' => $request->get('rule-trigger-stop'),
'rule-actions' => $request->get('rule-action'),
'rule-action-values' => $request->get('rule-action-value'),
'rule-action-stop' => $request->get('rule-action-stop'),
'stop_processing' => $request->get('stop_processing'),
];
$data = $request->getRuleData();
$data['rule_group_id'] = $ruleGroup->id;
$rule = $repository->store($data);
Session::flash('success', trans('firefly.stored_new_rule', ['title' => $rule->title]));
@@ -341,21 +334,7 @@ class RuleController extends Controller
*/
public function update(RuleRepositoryInterface $repository, RuleFormRequest $request, Rule $rule)
{
// process the rule itself:
$data = [
'title' => $request->get('title'),
'active' => intval($request->get('active')) == 1,
'trigger' => $request->get('trigger'),
'description' => $request->get('description'),
'rule-triggers' => $request->get('rule-trigger'),
'rule-trigger-values' => $request->get('rule-trigger-value'),
'rule-trigger-stop' => $request->get('rule-trigger-stop'),
'rule-actions' => $request->get('rule-action'),
'rule-action-values' => $request->get('rule-action-value'),
'rule-action-stop' => $request->get('rule-action-stop'),
'stop_processing' => intval($request->get('stop_processing')) == 1,
];
$data = $request->getRuleData();
$repository->update($rule, $data);
Session::flash('success', trans('firefly.updated_rule', ['title' => $rule->title]));
@@ -381,7 +360,6 @@ class RuleController extends Controller
$data = [
'rule_group_id' => $repository->getFirstRuleGroup()->id,
'stop_processing' => 0,
'user_id' => auth()->user()->id,
'title' => trans('firefly.default_rule_name'),
'description' => trans('firefly.default_rule_description'),
'trigger' => 'store-journal',
@@ -410,11 +388,10 @@ class RuleController extends Controller
{
/** @var RuleGroupRepositoryInterface $repository */
$repository = app('FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface');
$repository = app(RuleGroupRepositoryInterface::class);
if ($repository->count() === 0) {
$data = [
'user_id' => auth()->user()->id,
'title' => trans('firefly.default_rule_group_name'),
'description' => trans('firefly.default_rule_group_description'),
];

View File

@@ -41,8 +41,16 @@ class RuleGroupController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.rules'));
View::share('mainTitleIcon', 'fa-random');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.rules'));
View::share('mainTitleIcon', 'fa-random');
return $next($request);
}
);
}
/**
@@ -204,12 +212,7 @@ class RuleGroupController extends Controller
*/
public function store(RuleGroupFormRequest $request, RuleGroupRepositoryInterface $repository)
{
$data = [
'title' => $request->input('title'),
'description' => $request->input('description'),
'user_id' => auth()->user()->id,
];
$data = $request->getRuleGroupData();
$ruleGroup = $repository->store($data);
Session::flash('success', strval(trans('firefly.created_new_rule_group', ['title' => $ruleGroup->title])));

View File

@@ -29,6 +29,13 @@ class SearchController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
return $next($request);
}
);
}
/**

View File

@@ -50,15 +50,22 @@ class TagController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', 'Tags');
View::share('mainTitleIcon', 'fa-tags');
View::share('hideTags', true);
$this->tagOptions = [
'nothing' => trans('firefly.regular_tag'),
'balancingAct' => trans('firefly.balancing_act'),
'advancePayment' => trans('firefly.advance_payment'),
];
View::share('tagOptions', $this->tagOptions);
$this->middleware(
function ($request, $next) {
View::share('title', 'Tags');
View::share('mainTitleIcon', 'fa-tags');
$this->tagOptions = [
'nothing' => trans('firefly.regular_tag'),
'balancingAct' => trans('firefly.balancing_act'),
'advancePayment' => trans('firefly.advance_payment'),
];
View::share('tagOptions', $this->tagOptions);
return $next($request);
}
);
}
/**

View File

@@ -0,0 +1,241 @@
<?php
/**
* ConvertController.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\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Http\Request;
use Session;
use View;
/**
* Class ConvertController
*
* @package FireflyIII\Http\Controllers\Transaction
*/
class ConvertController extends Controller
{
/** @var AccountRepositoryInterface */
private $accounts;
/**
* ConvertController constructor.
*/
public function __construct()
{
parent::__construct();
// some useful repositories:
$this->middleware(
function ($request, $next) {
$this->accounts = app(AccountRepositoryInterface::class);
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-exchange');
return $next($request);
}
);
}
/**
* @param TransactionType $destinationType
* @param TransactionJournal $journal
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
*/
public function convert(TransactionType $destinationType, TransactionJournal $journal)
{
$positiveAmount = TransactionJournal::amountPositive($journal);
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
$sourceType = $journal->transactionType;
$subTitle = trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]);
$subTitleIcon = 'fa-exchange';
// cannot convert to its own type.
if ($sourceType->type === $destinationType->type) {
Session::flash('info', trans('firefly.convert_is_already_type_' . $destinationType->type));
return redirect(route('transactions.show', [$journal->id]));
}
// cannot convert split.
if ($journal->transactions()->count() > 2) {
Session::flash('error', trans('firefly.cannot_convert_split_journl'));
return redirect(route('transactions.show', [$journal->id]));
}
// get source and destination account:
$sourceAccount = TransactionJournal::sourceAccountList($journal)->first();
$destinationAccount = TransactionJournal::destinationAccountList($journal)->first();
return view(
'transactions.convert',
compact(
'sourceType', 'destinationType', 'journal', 'assetAccounts',
'positiveAmount', 'sourceAccount', 'destinationAccount', 'sourceType',
'subTitle', 'subTitleIcon'
)
);
// convert withdrawal to deposit requires a new source account ()
// or to transfer requires
}
/**
* @param Request $request
* @param JournalRepositoryInterface $repository
* @param TransactionType $destinationType
* @param TransactionJournal $journal
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function submit(Request $request, JournalRepositoryInterface $repository, TransactionType $destinationType, TransactionJournal $journal)
{
$data = $request->all();
// cannot convert to its own type.
if ($journal->transactionType->type === $destinationType->type) {
Session::flash('error', trans('firefly.convert_is_already_type_' . $destinationType->type));
return redirect(route('transactions.show', [$journal->id]));
}
// cannot convert split.
if ($journal->transactions()->count() > 2) {
Session::flash('error', trans('firefly.cannot_convert_split_journl'));
return redirect(route('transactions.show', [$journal->id]));
}
// get the new source and destination account:
$source = $this->getSourceAccount($journal, $destinationType, $data);
$destination = $this->getDestinationAccount($journal, $destinationType, $data);
// update the journal:
$errors = $repository->convert($journal, $destinationType, $source, $destination);
if ($errors->count() > 0) {
return redirect(route('transactions.convert', [strtolower($destinationType->type), $journal->id]))->withErrors($errors)->withInput();
}
Session::flash('success', trans('firefly.converted_to_' . $destinationType->type));
return redirect(route('transactions.show', [$journal->id]));
}
/**
* @param TransactionJournal $journal
* @param TransactionType $destinationType
* @param array $data
*
* @return Account
* @throws FireflyException
*/
private function getDestinationAccount(TransactionJournal $journal, TransactionType $destinationType, array $data): Account
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$sourceAccount = TransactionJournal::sourceAccountList($journal)->first();
$destinationAccount = TransactionJournal::destinationAccountList($journal)->first();
$sourceType = $journal->transactionType;
$destination = null;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined);
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: # one
$destination = $sourceAccount;
break;
case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: # two
$destination = $accountRepository->find(intval($data['destination_account_asset']));
break;
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: # three
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: #five
$data = [
'name' => $data['destination_account_expense'],
'accountType' => 'expense',
'virtualBalance' => 0,
'active' => true,
'iban' => null,
];
$destination = $accountRepository->store($data);
break;
case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: # four
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: #six
$destination = $destinationAccount;
break;
}
return $destination;
}
/**
* @param TransactionJournal $journal
* @param TransactionType $destinationType
* @param array $data
*
* @return Account
* @throws FireflyException
*/
private function getSourceAccount(TransactionJournal $journal, TransactionType $destinationType, array $data): Account
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$sourceAccount = TransactionJournal::sourceAccountList($journal)->first();
$destinationAccount = TransactionJournal::destinationAccountList($journal)->first();
$sourceType = $journal->transactionType;
$source = new Account;
$joined = $sourceType->type . '-' . $destinationType->type;
switch ($joined) {
default:
throw new FireflyException('Cannot handle ' . $joined);
case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: # one
case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: #six
$data = [
'name' => $data['source_account_revenue'],
'accountType' => 'revenue',
'virtualBalance' => 0,
'active' => true,
'iban' => null,
];
$source = $accountRepository->store($data);
break;
case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: # two
case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: #five
$source = $sourceAccount;
break;
case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: # three
$source = $destinationAccount;
break;
case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: # four
$source = $accountRepository->find(intval($data['source_account_asset']));
break;
}
return $source;
}
}

View File

@@ -41,8 +41,16 @@ class MassController extends Controller
public function __construct()
{
parent::__construct();
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat');
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat');
return $next($request);
}
);
}
/**
@@ -209,7 +217,6 @@ class MassController extends Controller
'destination_account_id' => intval($destAccountId),
'destination_account_name' => $destAccountName,
'amount' => round($request->get('amount')[$journal->id], 4),
'user' => auth()->user()->id,
'amount_currency_id_amount' => intval($request->get('amount_currency_id_amount_' . $journal->id)),
'date' => new Carbon($request->get('date')[$journal->id]),
'interest_date' => $journal->interest_date,

View 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();
$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);
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat');
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'));
}
}

View File

@@ -15,18 +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;
@@ -42,55 +41,43 @@ 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;
/**
*
*/
public function __construct()
{
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');
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
// 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);
View::share('mainTitleIcon', 'fa-share-alt');
View::share('title', trans('firefly.split-transactions'));
$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 +89,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 +108,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 +117,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 +149,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 +157,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,152 +204,83 @@ 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();
}
/*
* Loop all transactions. Collect info ONLY from the transaction that is NOT related to
* the singular account.
*/
$index = 0;
$return = [
'description' => [],
'source_account_id' => [],
'source_account_name' => [],
'destination_account_id' => [],
'destination_account_name' => [],
'amount' => [],
'budget_id' => [],
'category' => [],
];
Log::debug('now at transactionsFromJournal');
/**
* @var int $current
* @var Transaction $transaction
*/
foreach ($transactions as $current => $transaction) {
$budget = $transaction->budgets()->first();
$category = $transaction->categories()->first();
$budgetId = 0;
$categoryName = '';
if (!is_null($budget)) {
$budgetId = $budget->id;
}
if (!is_null($category)) {
$categoryName = $category->name;
}
$budgetId = $request->old('budget_id')[$index] ?? $budgetId;
$categoryName = $request->old('category')[$index] ?? $categoryName;
$amount = $request->old('amount')[$index] ?? $transaction->amount;
$description = $request->old('description')[$index] ?? $transaction->description;
$destinationName = $request->old('destination_account_name')[$index] ?? $transaction->account->name;
$sourceName = $request->old('source_account_name')[$index] ?? $transaction->account->name;
$amount = bccomp($amount, '0') === -1 ? bcmul($amount, '-1') : $amount;
if ($transaction->account_id !== $singular->id) {
$return['description'][] = $description;
$return['destination_account_id'][] = $transaction->account_id;
$return['destination_account_name'][] = $destinationName;
$return['source_account_name'][] = $sourceName;
$return['amount'][] = $amount;
$return['budget_id'][] = intval($budgetId);
$return['category'][] = $categoryName;
// only add one when "valid" transaction
$index++;
}
$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'],
];
}
return $return;
}
/**
* @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'] ?? '',
];
}
Log::debug(sprintf('Found %d splits in request data.', count($return)));
return $return;
}
}

View File

@@ -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,195 +30,39 @@ use View;
class TransactionController extends Controller
{
/**
*
* TransactionController constructor.
*/
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);
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat');
return $next($request);
}
);
}
/**
* @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 +99,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'));
}
}

View File

@@ -70,17 +70,29 @@ class Range
// set start, end and finish:
$this->setRange();
// set view variables.
$this->configureView();
// get variables for date range:
$this->datePicker();
// set view variables.
$this->configureView();
// set more view variables:
$this->configureList();
}
return $theNext($request);
}
/**
*
*/
private function configureList()
{
$pref = Preferences::get('list-length', config('firefly.list_length', 10))->data;
View::share('listLength', $pref);
}
private function configureView()
{
$pref = Preferences::get('language', config('firefly.default_language', 'en_US'));

View File

@@ -13,8 +13,8 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Models\Account;
use Input;
use Carbon\Carbon;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
/**
* Class AccountFormRequest
@@ -33,20 +33,44 @@ class AccountFormRequest extends Request
return auth()->check();
}
/**
* @return array
*/
public function getAccountData(): array
{
return [
'name' => trim($this->input('name')),
'active' => intval($this->input('active')) === 1,
'accountType' => $this->input('what'),
'virtualBalance' => round($this->input('virtualBalance'), 2),
'virtualBalanceCurrency' => intval($this->input('amount_currency_id_virtualBalance')),
'iban' => trim($this->input('iban')),
'accountNumber' => trim($this->input('accountNumber')),
'accountRole' => $this->input('accountRole'),
'openingBalance' => round($this->input('openingBalance'), 2),
'openingBalanceDate' => new Carbon((string)$this->input('openingBalanceDate')),
'openingBalanceCurrency' => intval($this->input('amount_currency_id_openingBalance')),
'ccType' => $this->input('ccType'),
'ccMonthlyPaymentDate' => $this->input('ccMonthlyPaymentDate'),
];
}
/**
* @return array
*/
public function rules()
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$accountRoles = join(',', array_keys(config('firefly.accountRoles')));
$types = join(',', array_keys(config('firefly.subTitlesByIdentifier')));
$ccPaymentTypes = join(',', array_keys(config('firefly.ccTypes')));
$nameRule = 'required|min:1|uniqueAccountForUser';
$idRule = '';
if (Account::find(Input::get('id'))) {
if (!is_null($repository->find(intval($this->get('id')))->id)) {
$idRule = 'belongsToUser:accounts';
$nameRule = 'required|min:1|uniqueAccountForUser:' . Input::get('id');
$nameRule = 'required|min:1|uniqueAccountForUser:' . $this->get('id');
}
return [

View File

@@ -30,6 +30,18 @@ class AttachmentFormRequest extends Request
return auth()->check();
}
/**
* @return array
*/
public function getAttachmentData(): array
{
return [
'title' => trim($this->input('title')),
'description' => trim($this->input('description')),
'notes' => trim($this->input('notes')),
];
}
/**
* @return array
*/

View File

@@ -14,7 +14,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Carbon\Carbon;
use Input;
/**
* Class BillFormRequest
@@ -46,7 +45,6 @@ class BillFormRequest extends Request
'amount_currency_id_amount_max' => intval($this->get('amount_currency_id_amount_max')),
'amount_max' => round($this->get('amount_max'), 2),
'date' => new Carbon($this->get('date')),
'user' => auth()->user()->id,
'repeat_freq' => $this->get('repeat_freq'),
'skip' => intval($this->get('skip')),
'automatch' => intval($this->get('automatch')) === 1,
@@ -61,9 +59,9 @@ class BillFormRequest extends Request
{
$nameRule = 'required|between:1,255|uniqueObjectForUser:bills,name';
$matchRule = 'required|between:1,255|uniqueObjectForUser:bills,match';
if (intval(Input::get('id')) > 0) {
$nameRule .= ',' . intval(Input::get('id'));
$matchRule .= ',' . intval(Input::get('id'));
if (intval($this->get('id')) > 0) {
$nameRule .= ',' . intval($this->get('id'));
$matchRule .= ',' . intval($this->get('id'));
}
$rules = [

View File

@@ -13,8 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Models\Budget;
use Input;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
/**
* Class BudgetFormRequest
@@ -29,19 +28,30 @@ class BudgetFormRequest extends Request
*/
public function authorize()
{
// Only allow logged in users
return auth()->check();
}
/**
* @return array
*/
public function getBudgetData(): array
{
return [
'name' => trim($this->input('name')),
'active' => intval($this->input('active')) == 1,
];
}
/**
* @return array
*/
public function rules()
{
$nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name';
if (Budget::find(Input::get('id'))) {
$nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name,' . intval(Input::get('id'));
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name';
if (!is_null($repository->find(intval($this->get('id')))->id)) {
$nameRule = 'required|between:1,100|uniqueObjectForUser:budgets,name,' . intval($this->get('id'));
}
return [

View File

@@ -13,8 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Models\Category;
use Input;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
/**
* Class CategoryFormRequest
@@ -33,15 +32,26 @@ class CategoryFormRequest extends Request
return auth()->check();
}
/**
* @return array
*/
public function getCategoryData(): array
{
return [
'name' => trim($this->input('name')),
];
}
/**
* @return array
*/
public function rules()
{
$nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name';
if (Category::find(Input::get('id'))) {
$nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name,' . intval(Input::get('id'));
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name';
if (!is_null($repository->find(intval($this->get('id')))->id)) {
$nameRule = 'required|between:1,100|uniqueObjectForUser:categories,name,' . intval($this->get('id'));
}
return [

View File

@@ -30,6 +30,16 @@ class ConfigurationRequest extends Request
return auth()->check() && auth()->user()->hasRole('owner');
}
/**
* @return array
*/
public function getConfigurationData(): array
{
return [
'single_user_mode' => intval($this->get('single_user_mode')) === 1,
];
}
/**
* @return array
*/

View File

@@ -13,8 +13,6 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Input;
/**
* Class BillFormRequest
*
@@ -55,7 +53,7 @@ class CurrencyFormRequest extends Request
'name' => 'required|max:48|min:1|unique:transaction_currencies,name',
'symbol' => 'required|min:1|max:8|unique:transaction_currencies,symbol',
];
if (intval(Input::get('id')) > 0) {
if (intval($this->get('id')) > 0) {
$rules = [
'code' => 'required|min:3|max:3',
'name' => 'required|max:48|min:1',

View File

@@ -14,10 +14,8 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionType;
use Input;
/**
* Class JournalFormRequest
@@ -37,86 +35,109 @@ 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'
'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');
$what = $this->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;

View File

@@ -13,7 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Input;
use Carbon\Carbon;
/**
* Class PiggyBankFormRequest
@@ -32,6 +32,21 @@ class PiggyBankFormRequest extends Request
return auth()->check();
}
/**
* @return array
*/
public function getPiggyBankData(): array
{
return [
'name' => trim($this->get('name')),
'startdate' => new Carbon,
'account_id' => intval($this->get('account_id')),
'targetamount' => round($this->get('targetamount'), 2),
'targetdate' => strlen($this->get('targetdate')) > 0 ? new Carbon($this->get('targetdate')) : null,
'note' => trim($this->get('note')),
];
}
/**
* @return array
*/
@@ -40,8 +55,8 @@ class PiggyBankFormRequest extends Request
$nameRule = 'required|between:1,255|uniquePiggyBankForUser';
$targetDateRule = 'date';
if (intval(Input::get('id'))) {
$nameRule = 'required|between:1,255|uniquePiggyBankForUser:' . intval(Input::get('id'));
if (intval($this->get('id'))) {
$nameRule = 'required|between:1,255|uniquePiggyBankForUser:' . intval($this->get('id'));
}

Some files were not shown because too many files have changed in this diff Show More