Merge branch 'release/4.1.4'

This commit is contained in:
James Cole 2016-10-30 08:56:35 +01:00
commit 0a6f299ae6
185 changed files with 7591 additions and 5886 deletions

View File

@ -2,7 +2,37 @@
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

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

@ -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,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->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->description = $journal->description;
$entry->date = $journal->date->format('Y-m-d');
$entry->amount = TransactionJournal::amount($journal);
$entry->budget = new EntryBudget($journal->budgets->first());
$entry->category = new EntryCategory($journal->categories->first());
$entry->bill = new EntryBill($journal->bill);
// 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;
$sources = TransactionJournal::sourceAccountList($journal);
$destinations = TransactionJournal::destinationAccountList($journal);
$entry->sourceAccount = new EntryAccount($sources->first());
$entry->destinationAccount = new EntryAccount($destinations->first());
// 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;
foreach ($sources as $source) {
$entry->sourceAccounts->push(new EntryAccount($source));
}
foreach ($destinations as $destination) {
$entry->destinationAccounts->push(new EntryAccount($destination));
// 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;
// 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\JournalTaskerInterface;
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 JournalTaskerInterface $tasker */
$tasker = app(JournalTaskerInterface::class);
$this->journals = $tasker->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

@ -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,
];
$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']);
$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();
// translations:
$this->middleware(
function ($request, $next) {
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', trans('firefly.accounts'));
return $next($request);
}
);
}
/**
@ -146,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');
@ -200,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;
@ -363,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,9 +37,16 @@ class ConfigurationController extends Controller
{
parent::__construct();
$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();
/** @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;
$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

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

View File

@ -38,8 +38,16 @@ class BillController extends Controller
public function __construct()
{
parent::__construct();
$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, new Carbon);
$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));
}
$bill->lastPaidAmount = $average;
$bill->paidInPeriod = ($start <= $bill->lastFoundMatch) && ($end >= $bill->lastFoundMatch);
// 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->nextExpectedMatch = $repository->nextExpectedMatch($bill, $lastDate);
}
);

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('hideBudgets', true);
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.budgets'));
View::share('mainTitleIcon', 'fa-tasks');
View::share('hideBudgets', true);
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;
@ -283,12 +294,12 @@ class BudgetController extends Controller
/**
* @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);
}
@ -319,14 +331,16 @@ class BudgetController extends Controller
/**
* @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'));

View File

@ -43,8 +43,16 @@ class CategoryController extends Controller
public function __construct()
{
parent::__construct();
$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

@ -46,11 +46,19 @@ class Controller extends BaseController
View::share('hideBills', false);
View::share('hideTags', false);
// save some formats:
// 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();
$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();
$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,9 +130,7 @@ class HomeController extends Controller
return redirect(route('new-user.index'));
}
$title = 'Firefly';
$subTitle = trans('firefly.welcomeBack');
$mainTitleIcon = 'fa-fire';
$transactions = [];
$frontPage = Preferences::get(
'frontPageAccounts', $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray()
@ -164,36 +164,29 @@ 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;';
}
/**

View File

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

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();
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.piggyBanks'));
View::share('mainTitleIcon', 'fa-sort-amount-asc');
return $next($request);
}
);
}
/**
@ -364,18 +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,
'note' => $request->get('note'),
];
$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();
@ -400,16 +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,
'note' => $request->get('note'),
];
$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();
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.preferences'));
View::share('mainTitleIcon', 'fa-gear');
return $next($request);
}
);
}
/**

View File

@ -36,8 +36,15 @@ class ProfileController extends Controller
{
parent::__construct();
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.profile'));
View::share('mainTitleIcon', 'fa-user');
return $next($request);
}
);
}
/**
@ -45,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'));
}
/**
@ -55,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'));
}
/**

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,9 +49,19 @@ class ReportController extends Controller
{
parent::__construct();
$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!');
@ -206,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
@ -226,15 +219,8 @@ 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);
@ -245,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'
)
@ -268,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:
@ -288,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'
)
);
}
@ -305,11 +281,6 @@ 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);
@ -328,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();
$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();
$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('hideTags', true);
$this->middleware(
function ($request, $next) {
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);
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();
$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

@ -59,8 +59,6 @@ class SingleController extends Controller
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'));
@ -75,11 +73,13 @@ class SingleController extends Controller
$this->piggyBanks = app(PiggyBankRepositoryInterface::class);
$this->attachments = app(AttachmentHelperInterface::class);
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat');
return $next($request);
}
);
}
/**

View File

@ -26,6 +26,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use Illuminate\Http\Request;
use Log;
use Preferences;
use Session;
use Steam;
@ -62,8 +63,7 @@ class SplitController extends Controller
public function __construct()
{
parent::__construct();
View::share('mainTitleIcon', 'fa-share-alt');
View::share('title', trans('firefly.split-transactions'));
// some useful repositories:
$this->middleware(
@ -73,6 +73,8 @@ class SplitController extends Controller
$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'));
return $next($request);
}
@ -264,6 +266,7 @@ class SplitController extends Controller
$return = [];
$transactions = $request->get('transactions');
foreach ($transactions as $transaction) {
$return[] = [
'description' => $transaction['description'],
'source_account_id' => $transaction['source_account_id'] ?? 0,
@ -273,9 +276,9 @@ class SplitController extends Controller
'amount' => round($transaction['amount'] ?? 0, 2),
'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : 0,
'category' => $transaction['category'] ?? '',
'user' => auth()->user()->id, // needed for accounts.
];
}
Log::debug(sprintf('Found %d splits in request data.', count($return)));
return $return;
}

View File

@ -35,9 +35,17 @@ class TransactionController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
View::share('title', trans('firefly.transactions'));
View::share('mainTitleIcon', 'fa-repeat');
return $next($request);
}
);
}
/**

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

@ -14,8 +14,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use Input;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
/**
* Class AccountFormRequest
@ -45,7 +44,6 @@ class AccountFormRequest extends Request
'accountType' => $this->input('what'),
'virtualBalance' => round($this->input('virtualBalance'), 2),
'virtualBalanceCurrency' => intval($this->input('amount_currency_id_virtualBalance')),
'user' => auth()->user()->id,
'iban' => trim($this->input('iban')),
'accountNumber' => trim($this->input('accountNumber')),
'accountRole' => $this->input('accountRole'),
@ -62,15 +60,17 @@ class AccountFormRequest extends Request
*/
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

@ -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
@ -39,7 +38,6 @@ class BudgetFormRequest extends Request
{
return [
'name' => trim($this->input('name')),
'user' => auth()->user()->id,
'active' => intval($this->input('active')) == 1,
];
}
@ -49,10 +47,11 @@ class BudgetFormRequest extends Request
*/
public function rules()
{
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$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'));
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()
{
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$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'));
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

@ -16,7 +16,6 @@ namespace FireflyIII\Http\Requests;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionType;
use Input;
/**
* Class JournalFormRequest
@ -44,7 +43,6 @@ class JournalFormRequest extends Request
{
$data = [
'what' => $this->get('what'), // type. can be 'deposit', 'withdrawal' or 'transfer'
'user' => auth()->user()->id,
'date' => new Carbon($this->get('date')),
'tags' => explode(',', $this->getFieldOrEmptyString('tags')),
'currency_id' => intval($this->get('amount_currency_id_amount')),
@ -80,7 +78,7 @@ class JournalFormRequest extends Request
*/
public function rules()
{
$what = Input::get('what');
$what = $this->get('what');
$rules = [
'what' => 'required|in:withdrawal,deposit,transfer',
'date' => 'required|date',

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

View File

@ -13,8 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Models\RuleGroup;
use Input;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
/**
* Class RuleFormRequest
@ -33,12 +32,33 @@ class RuleFormRequest extends Request
return auth()->check();
}
/**
* @return array
*/
public function getRuleData(): array
{
return [
'title' => trim($this->get('title')),
'active' => intval($this->get('active')) == 1,
'trigger' => trim($this->get('trigger')),
'description' => trim($this->get('description')),
'rule-triggers' => $this->get('rule-trigger'),
'rule-trigger-values' => $this->get('rule-trigger-value'),
'rule-trigger-stop' => $this->get('rule-trigger-stop'),
'rule-actions' => $this->get('rule-action'),
'rule-action-values' => $this->get('rule-action-value'),
'rule-action-stop' => $this->get('rule-action-stop'),
'stop_processing' => intval($this->get('stop_processing')) === 1,
];
}
/**
* @return array
*/
public function rules()
{
/** @var RuleGroupRepositoryInterface $repository */
$repository = app(RuleGroupRepositoryInterface::class);
$validTriggers = array_keys(config('firefly.rule-triggers'));
$validActions = array_keys(config('firefly.rule-actions'));
@ -46,8 +66,8 @@ class RuleFormRequest extends Request
$contextActions = join(',', config('firefly.rule-actions-text'));
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title';
if (RuleGroup::find(Input::get('id'))) {
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title,' . intval(Input::get('id'));
if (!is_null($repository->find(intval($this->get('id')))->id)) {
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title,' . intval($this->get('id'));
}
$rules = [

View File

@ -20,8 +20,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Models\RuleGroup;
use Input;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
/**
* Class RuleGroupFormRequest
@ -40,15 +39,27 @@ class RuleGroupFormRequest extends Request
return auth()->check();
}
/**
* @return array
*/
public function getRuleGroupData(): array
{
return [
'title' => trim($this->input('title')),
'description' => trim($this->input('description')),
];
}
/**
* @return array
*/
public function rules()
{
/** @var RuleGroupRepositoryInterface $repository */
$repository = app(RuleGroupRepositoryInterface::class, [auth()->user()]);
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title';
if (RuleGroup::find(Input::get('id'))) {
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title,' . intval(Input::get('id'));
if (!is_null($repository->find(intval($this->get('id')))->id)) {
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title,' . intval($this->get('id'));
}
return [

View File

@ -13,8 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Requests;
use Carbon\Carbon;
use FireflyIII\Models\Tag;
use Input;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
/**
* Class TagFormRequest
@ -38,7 +37,7 @@ class TagFormRequest extends Request
*/
public function collectTagData() :array
{
if (Input::get('setTag') == 'true') {
if ($this->get('setTag') == 'true') {
$latitude = $this->get('latitude');
$longitude = $this->get('longitude');
$zoomLevel = $this->get('zoomLevel');
@ -69,11 +68,13 @@ class TagFormRequest extends Request
*/
public function rules()
{
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$idRule = '';
$tagRule = 'required|min:1|uniqueObjectForUser:tags,tag';
if (Tag::find(Input::get('id'))) {
if (!is_null($repository->find(intval($this->get('id')))->id)) {
$idRule = 'belongsToUser:tags';
$tagRule = 'required|min:1|uniqueObjectForUser:tags,tag,' . Input::get('id');
$tagRule = 'required|min:1|uniqueObjectForUser:tags,tag,' . $this->get('id');
}
return [

View File

@ -25,6 +25,7 @@ use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\Tag;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
/**
@ -595,13 +596,24 @@ Breadcrumbs::register(
Breadcrumbs::register(
'transactions.show', function (BreadCrumbGenerator $breadcrumbs, TransactionJournal $journal) {
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
$what = strtolower($journal->transactionType->type);
$breadcrumbs->parent('transactions.index', $what);
$breadcrumbs->push($journal->description, route('transactions.show', [$journal->id]));
}
);
Breadcrumbs::register(
'transactions.convert', function (BreadCrumbGenerator $breadcrumbs, TransactionType $destinationType, TransactionJournal $journal) {
$breadcrumbs->parent('transactions.show', $journal);
$breadcrumbs->push(
trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]),
route('transactions.convert', [strtolower($destinationType->type), $journal->id])
);
}
);
/**
* SPLIT
*/

View File

@ -369,6 +369,11 @@ class CsvSetup implements SetupInterface
foreach ($results as $rowIndex => $row) {
// skip first row?
if ($rowIndex === 0 && $config['has-headers']) {
continue;
}
// run specifics here:
// and this is the point where the specifix go to work.
foreach ($config['specifics'] as $name => $enabled) {

View File

@ -58,9 +58,12 @@ class Preference extends Model
$data = Crypt::decrypt($value);
} catch (DecryptException $e) {
Log::error('Could not decrypt preference.', ['id' => $this->id, 'name' => $this->name, 'data' => $value]);
throw new FireflyException('Could not decrypt preference #' . $this->id . '.');
throw new FireflyException(
sprintf('Could not decrypt preference #%d. If this error persists, please run "php artisan cache:clear" on the command line.', $this->id)
);
}
return json_decode($data);
}

View File

@ -15,6 +15,7 @@ namespace FireflyIII\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* FireflyIII\Models\TransactionType
@ -43,6 +44,25 @@ class TransactionType extends Model
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/**
* @param string $type
*
* @return Model|null|static
*/
public static function routeBinder(string $type)
{
if (!auth()->check()) {
throw new NotFoundHttpException;
}
$transactionType = self::where('type', $type)->first();
if (!is_null($transactionType)) {
return $transactionType;
}
throw new NotFoundHttpException;
}
/**
* @return bool
*/

View File

@ -388,7 +388,7 @@ class AccountRepository implements AccountRepositoryInterface
// create it:
$newAccount = new Account(
[
'user_id' => $data['user'],
'user_id' => $this->user->id,
'account_type_id' => $accountType->id,
'name' => $data['name'],
'virtual_balance' => $data['virtualBalance'],
@ -417,13 +417,12 @@ class AccountRepository implements AccountRepositoryInterface
protected function storeInitialBalance(Account $account, array $data): TransactionJournal
{
$amount = $data['openingBalance'];
$user = $data['user'];
$name = $data['name'];
$opposing = $this->storeOpposingAccount($amount, $user, $name);
$opposing = $this->storeOpposingAccount($amount, $name);
$transactionType = TransactionType::whereType(TransactionType::OPENING_BALANCE)->first();
$journal = TransactionJournal::create(
[
'user_id' => $data['user'],
'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id,
'transaction_currency_id' => $data['openingBalanceCurrency'],
'description' => 'Initial balance for "' . $account->name . '"',
@ -458,16 +457,14 @@ class AccountRepository implements AccountRepositoryInterface
/**
* @param float $amount
* @param int $user
* @param string $name
*
* @return Account
*/
protected function storeOpposingAccount(float $amount, int $user, string $name):Account
protected function storeOpposingAccount(float $amount, string $name):Account
{
$type = $amount < 0 ? 'expense' : 'revenue';
$opposingData = [
'user' => $user,
'accountType' => $type,
'name' => $name . ' initial balance',
'active' => false,

View File

@ -19,6 +19,7 @@ use DB;
use FireflyIII\Helpers\Collection\Account as AccountCollection;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
@ -305,6 +306,8 @@ class AccountTasker implements AccountTaskerInterface
* - Expense accounts (where money is spent) should only return earnings (the account gets money).
* - Revenue accounts (where money comes from) should only return expenses (they spend money).
*
*
*
* @param array $accounts
* @param Carbon $start
* @param Carbon $end
@ -325,6 +328,7 @@ class AccountTasker implements AccountTaskerInterface
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0);
}
)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)
@ -361,6 +365,8 @@ class AccountTasker implements AccountTaskerInterface
* @param Carbon $end
* @param bool $incoming
*
* Opening balances are ignored.
*
* @return Collection
*/
protected function financialReport(array $accounts, Carbon $start, Carbon $end, bool $incoming): Collection
@ -371,12 +377,14 @@ class AccountTasker implements AccountTaskerInterface
$query = Transaction
::distinct()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
->leftJoin(
'transactions as other_side', function (JoinClause $join) use ($joinModifier) {
$join->on('transaction_journals.id', '=', 'other_side.transaction_journal_id')->where('other_side.amount', $joinModifier, 0);
}
)
->leftJoin('accounts as other_account', 'other_account.id', '=', 'other_side.account_id')
->where('transaction_types.type','!=', TransactionType::OPENING_BALANCE)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)

View File

@ -13,6 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Attachment;
use Carbon\Carbon;
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\User;
@ -63,6 +64,24 @@ class AttachmentRepository implements AttachmentRepositoryInterface
return $this->user->attachments()->get();
}
/**
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getBetween(Carbon $start, Carbon $end): Collection
{
$query = $this->user
->attachments()
->leftJoin('transaction_journals', 'attachments.attachable_id', '=', 'transaction_journals.id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->get(['attachments.*']);
return $query;
}
/**
* @param Attachment $attachment
* @param array $data

View File

@ -13,6 +13,7 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Attachment;
use Carbon\Carbon;
use FireflyIII\Models\Attachment;
use Illuminate\Support\Collection;
@ -36,6 +37,14 @@ interface AttachmentRepositoryInterface
*/
public function get(): Collection;
/**
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getBetween(Carbon $start, Carbon $end): Collection;
/**
* @param Attachment $attachment
* @param array $attachmentData

View File

@ -312,25 +312,11 @@ class BillRepository implements BillRepositoryInterface
}
/**
* Get all journals that were recorded on this bill between these dates.
*
* @param Bill $bill
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getJournalsInRange(Bill $bill, Carbon $start, Carbon $end): Collection
{
return $bill->transactionJournals()->before($end)->after($start)->get();
}
/**
* @param $bill
*
* @return string
*/
public function getOverallAverage($bill): string
public function getOverallAverage(Bill $bill): string
{
$journals = $bill->transactionJournals()->get();
$sum = '0';
@ -347,6 +333,21 @@ class BillRepository implements BillRepositoryInterface
return $avg;
}
/**
* @param Bill $bill
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getPaidDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection
{
$dates = $bill->transactionJournals()->before($end)->after($start)->get(['transaction_journals.date'])->pluck('date');
return $dates;
}
/**
* Between start and end, tells you on which date(s) the bill is expected to hit.
*
@ -451,21 +452,6 @@ class BillRepository implements BillRepositoryInterface
return $avg;
}
/**
* @param Bill $bill
*
* @return \Carbon\Carbon
*/
public function lastFoundMatch(Bill $bill): Carbon
{
$last = $bill->transactionJournals()->orderBy('date', 'DESC')->first();
if ($last) {
return $last->date;
}
return Carbon::now()->addDays(2); // in the future!
}
/**
* Given a bill and a date, this method will tell you at which moment this bill expects its next
* transaction. Whether or not it is there already, is not relevant.
@ -610,7 +596,7 @@ class BillRepository implements BillRepositoryInterface
'name' => $data['name'],
'match' => $data['match'],
'amount_min' => $data['amount_min'],
'user_id' => $data['user'],
'user_id' => $this->user->id,
'amount_max' => $data['amount_max'],
'date' => $data['date'],
'repeat_freq' => $data['repeat_freq'],

View File

@ -114,22 +114,20 @@ interface BillRepositoryInterface
public function getJournals(Bill $bill, int $page, int $pageSize = 50): LengthAwarePaginator;
/**
* Get all journals that were recorded on this bill between these dates.
* @param Bill $bill
*
* @return string
*/
public function getOverallAverage(Bill $bill): string;
/**
* @param Bill $bill
* @param Carbon $start
* @param Carbon $end
*
* @return Collection
*/
public function getJournalsInRange(Bill $bill, Carbon $start, Carbon $end): Collection;
/**
* @param $bill
*
* @return string
*/
public function getOverallAverage($bill): string;
public function getPaidDatesInRange(Bill $bill, Carbon $start, Carbon $end): Collection;
/**
* Between start and end, tells you on which date(s) the bill is expected to hit.
@ -157,14 +155,6 @@ interface BillRepositoryInterface
*/
public function getYearAverage(Bill $bill, Carbon $date): string;
/**
* @param Bill $bill
*
* @return \Carbon\Carbon
*/
public function lastFoundMatch(Bill $bill): Carbon;
/**
* Given a bill and a date, this method will tell you at which moment this bill expects its next
* transaction. Whether or not it is there already, is not relevant.

View File

@ -19,6 +19,7 @@ use FireflyIII\Events\UpdatedBudgetLimit;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\User;
@ -26,6 +27,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
use Log;
/**
* Class BudgetRepository
@ -347,79 +349,75 @@ class BudgetRepository implements BudgetRepositoryInterface
*/
public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string
{
// first collect actual transaction journals (fairly easy)
$query = $this->user
->transactionJournals()
// collect amount of transaction journals, which is easy:
$budgetIds = $budgets->pluck('id')->toArray();
$accountIds = $accounts->pluck('id')->toArray();
Log::debug('spentInPeriod: Now in spentInPeriod for these budgets: ', $budgetIds);
Log::debug('spentInPeriod: and these accounts: ', $accountIds);
Log::debug(sprintf('spentInPeriod: Start date is "%s", end date is "%s"', $start->format('Y-m-d'), $end->format('Y-m-d')));
$fromJournalsQuery = TransactionJournal
::leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin(
'transactions as source', function (JoinClause $join) {
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')->where('source.amount', '<', 0);
'transactions', function (JoinClause $join) {
$join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', '0');
}
)
->leftJoin(
'transactions as destination', function (JoinClause $join) {
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')->where('destination.amount', '>', 0);
}
);
$query->whereNull('source.deleted_at');
$query->whereNull('destination.deleted_at');
$query->where('transaction_journals.completed', 1);
if ($end >= $start) {
$query->before($end)->after($start);
}
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->where(
// source.account_id in accountIds XOR destination.account_id in accountIds
function (Builder $query) use ($accountIds) {
$query->where(
function (Builder $q1) use ($accountIds) {
$q1->whereIn('source.account_id', $accountIds)
->whereNotIn('destination.account_id', $accountIds);
}
)->orWhere(
function (Builder $q2) use ($accountIds) {
$q2->whereIn('destination.account_id', $accountIds)
->whereNotIn('source.account_id', $accountIds);
}
);
}
);
}
if ($budgets->count() > 0) {
$budgetIds = $budgets->pluck('id')->toArray();
$query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$query->whereIn('budget_transaction_journal.budget_id', $budgetIds);
}
// that should do it:
$ids = $query->distinct()->get(['transaction_journals.id'])->pluck('id')->toArray();
$first = '0';
if (count($ids) > 0) {
$first = strval(
$this->user->transactions()
->whereIn('transaction_journal_id', $ids)
->where('amount', '<', '0')
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->whereNull('transaction_journals.deleted_at')
->whereNull('transactions.deleted_at')
->sum('amount')
);
}
// then collection transactions (harder)
$query = $this->user->transactions()
->where('transactions.amount', '<', 0)
->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'));
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->whereIn('transactions.account_id', $accountIds);
}
->where('transaction_journals.user_id', $this->user->id)
->where('transaction_types.type', 'Withdrawal');
// add budgets:
if ($budgets->count() > 0) {
$budgetIds = $budgets->pluck('id')->toArray();
$query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
$query->whereIn('budget_transaction.budget_id', $budgetIds);
$fromJournalsQuery->whereIn('budget_transaction_journal.budget_id', $budgetIds);
}
$second = strval($query->sum('transactions.amount'));
// add accounts:
if ($accounts->count() > 0) {
$fromJournalsQuery->whereIn('transactions.account_id', $accountIds);
}
$first = strval($fromJournalsQuery->sum('transactions.amount'));
Log::debug(sprintf('spentInPeriod: Result from first query: %s', $first));
unset($fromJournalsQuery);
// collect amount from transactions:
/**
* select transactions.id, budget_transaction.budget_id , transactions.amount
*
*
* and budget_transaction.budget_id in (1,61)
* and transactions.account_id in (2)
*/
$fromTransactionsQuery = Transaction
::leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id')
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->whereNull('transactions.deleted_at')
->whereNull('transaction_journals.deleted_at')
->where('transactions.amount', '<', 0)
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('transaction_journals.user_id', $this->user->id)
->where('transaction_types.type', 'Withdrawal');
// add budgets:
if ($budgets->count() > 0) {
$fromTransactionsQuery->whereIn('budget_transaction.budget_id', $budgetIds);
}
// add accounts:
if ($accounts->count() > 0) {
$fromTransactionsQuery->whereIn('transactions.account_id', $accountIds);
}
$second = strval($fromTransactionsQuery->sum('transactions.amount'));
Log::debug(sprintf('spentInPeriod: Result from second query: %s', $second));
Log::debug(sprintf('spentInPeriod: FINAL: %s', bcadd($first, $second)));
return bcadd($first, $second);
}
@ -500,7 +498,7 @@ class BudgetRepository implements BudgetRepositoryInterface
{
$newBudget = new Budget(
[
'user_id' => $data['user'],
'user_id' => $this->user->id,
'name' => $data['name'],
]
);

View File

@ -241,6 +241,7 @@ class CategoryRepository implements CategoryRepositoryInterface
if (count($types) > 0) {
$query->transactionTypes($types);
}
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id');
@ -275,7 +276,7 @@ class CategoryRepository implements CategoryRepositoryInterface
}
$second = $query->get(['transaction_journals.*']);
$second = $query->get(['transaction_journals.*','transaction_types.type as transaction_type_type']);
$complete = $complete->merge($first);
$complete = $complete->merge($second);
@ -433,7 +434,7 @@ class CategoryRepository implements CategoryRepositoryInterface
{
$newCategory = Category::firstOrCreateEncrypted(
[
'user_id' => $data['user'],
'user_id' => $this->user->id,
'name' => $data['name'],
]
);

View File

@ -26,7 +26,9 @@ use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
use Log;
use Preferences;
/**
* Class JournalRepository
@ -51,6 +53,41 @@ class JournalRepository implements JournalRepositoryInterface
$this->user = $user;
}
/**
* @param TransactionJournal $journal
* @param TransactionType $type
* @param Account $source
* @param Account $destination
*
* @return MessageBag
*/
public function convert(TransactionJournal $journal, TransactionType $type, Account $source, Account $destination): MessageBag
{
// default message bag that shows errors for everything.
$messages = new MessageBag;
$messages->add('source_account_revenue', trans('firefly.invalid_convert_selection'));
$messages->add('destination_account_asset', trans('firefly.invalid_convert_selection'));
$messages->add('destination_account_expense', trans('firefly.invalid_convert_selection'));
$messages->add('source_account_asset', trans('firefly.invalid_convert_selection'));
if ($source->id === $destination->id || is_null($source->id) || is_null($destination->id)) {
return $messages;
}
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
$sourceTransaction->account_id = $source->id;
$sourceTransaction->save();
$destinationTransaction->account_id = $destination->id;
$destinationTransaction->save();
$journal->transaction_type_id = $type->id;
$journal->save();
Preferences::mark();
$messages = new MessageBag;
return $messages;
}
/**
* @param TransactionJournal $journal
*
@ -95,7 +132,6 @@ class JournalRepository implements JournalRepositoryInterface
return $entry;
}
/**
* @param array $data
*
@ -107,7 +143,7 @@ class JournalRepository implements JournalRepositoryInterface
$transactionType = TransactionType::where('type', ucfirst($data['what']))->first();
$journal = new TransactionJournal(
[
'user_id' => $data['user'],
'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id,
'transaction_currency_id' => $data['currency_id'],
'description' => $data['description'],
@ -182,7 +218,7 @@ class JournalRepository implements JournalRepositoryInterface
// store actual journal.
$journal = new TransactionJournal(
[
'user_id' => $data['user'],
'user_id' => $this->user->id,
'transaction_type_id' => $transactionType->id,
'transaction_currency_id' => $data['amount_currency_id_amount'],
'description' => $data['description'],
@ -267,6 +303,7 @@ class JournalRepository implements JournalRepositoryInterface
$journal->description = $data['journal_description'];
$journal->date = $data['date'];
$journal->save();
Log::debug(sprintf('Updated split journal #%d', $journal->id));
// unlink all categories:
$journal->categories()->detach();
@ -282,8 +319,6 @@ class JournalRepository implements JournalRepositoryInterface
}
Log::debug(sprintf('Could not store meta field "%s" with value "%s" for journal #%d', json_encode($key), json_encode($value), $journal->id));
}
return $journal;
}
@ -297,6 +332,7 @@ class JournalRepository implements JournalRepositoryInterface
// store each transaction.
$identifier = 0;
Log::debug(sprintf('Count %d transactions in updateSplitJournal()', count($data['transactions'])));
foreach ($data['transactions'] as $transaction) {
Log::debug(sprintf('Split journal update split transaction %d', $identifier));
$transaction = $this->appendTransactionData($transaction, $data);
@ -478,7 +514,7 @@ class JournalRepository implements JournalRepositoryInterface
if (strlen($data['source_account_name']) > 0) {
$sourceType = AccountType::where('type', 'Revenue account')->first();
$sourceAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
);
return [
@ -488,7 +524,7 @@ class JournalRepository implements JournalRepositoryInterface
}
$sourceType = AccountType::where('type', 'Cash account')->first();
$sourceAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
['user_id' => $this->user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
);
return [
@ -587,7 +623,7 @@ class JournalRepository implements JournalRepositoryInterface
$destinationType = AccountType::where('type', AccountType::EXPENSE)->first();
$destinationAccount = Account::firstOrCreateEncrypted(
[
'user_id' => $data['user'],
'user_id' => $this->user->id,
'account_type_id' => $destinationType->id,
'name' => $data['destination_account_name'],
'active' => 1,
@ -601,7 +637,7 @@ class JournalRepository implements JournalRepositoryInterface
}
$destinationType = AccountType::where('type', 'Cash account')->first();
$destinationAccount = Account::firstOrCreateEncrypted(
['user_id' => $data['user'], 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
['user_id' => $this->user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
);
return [

View File

@ -13,7 +13,10 @@ declare(strict_types = 1);
namespace FireflyIII\Repositories\Journal;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\MessageBag;
/**
* Interface JournalRepositoryInterface
@ -32,6 +35,15 @@ interface JournalRepositoryInterface
*/
public function delete(TransactionJournal $journal): bool;
/**
* @param TransactionJournal $journal
* @param TransactionType $type
* @param array $data
*
* @return MessageBag
*/
public function convert(TransactionJournal $journal, TransactionType $type, Account $source, Account $destination): MessageBag;
/**
* Find a specific journal
*

View File

@ -16,6 +16,7 @@ namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
use Crypt;
use DB;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\User;

View File

@ -175,6 +175,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
*/
public function store(array $data): PiggyBank
{
$data['order'] = $this->getMaxOrder() + 1;
$piggyBank = PiggyBank::create($data);
$this->updateNote($piggyBank, $data['note']);

View File

@ -233,7 +233,7 @@ class RuleRepository implements RuleRepositoryInterface
// start by creating a new rule:
$rule = new Rule;
$rule->user()->associate($data['user_id']);
$rule->user()->associate($this->user->id);
$rule->rule_group_id = $data['rule_group_id'];
$rule->order = ($order + 1);

View File

@ -79,6 +79,21 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
return true;
}
/**
* @param int $ruleGroupId
*
* @return RuleGroup
*/
public function find(int $ruleGroupId): RuleGroup
{
$group = $this->user->ruleGroups()->find($ruleGroupId);
if (is_null($group)) {
return new RuleGroup;
}
return $group;
}
/**
* @return Collection
*/
@ -226,7 +241,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
$newRuleGroup = new RuleGroup(
[
'user_id' => $data['user_id'],
'user_id' => $this->user->id,
'title' => $data['title'],
'description' => $data['description'],
'order' => ($order + 1),

View File

@ -42,6 +42,13 @@ interface RuleGroupRepositoryInterface
*/
public function destroy(RuleGroup $ruleGroup, RuleGroup $moveTo = null): bool;
/**
* @param int $ruleGroupId
*
* @return RuleGroup
*/
public function find(int $ruleGroupId): RuleGroup;
/**
* @return Collection
*/

View File

@ -0,0 +1,141 @@
<?php
/**
* SetDestinationAccount.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\Rules\Actions;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Log;
/**
* Class SetDestinationAccount
*
* @package FireflyIII\Rules\Action
*/
class SetDestinationAccount implements ActionInterface
{
private $action;
/** @var TransactionJournal */
private $journal;
/** @var Account */
private $newDestinationAccount;
/** @var AccountRepositoryInterface */
private $repository;
/**
* TriggerInterface constructor.
*
* @param RuleAction $action
*/
public function __construct(RuleAction $action)
{
$this->action = $action;
}
/**
* @param TransactionJournal $journal
*
* @return bool
*/
public function act(TransactionJournal $journal): bool
{
$this->journal = $journal;
$this->repository = app(AccountRepositoryInterface::class, [$journal->user]);
$count = $journal->transactions()->count();
if ($count > 2) {
Log::error(sprintf('Cannot change destination account of journal #%d because it is a split journal.', $journal->id));
return true;
}
// journal type:
$type = $journal->transactionType->type;
// if this is a deposit or a transfer, the destination account must be an asset account or a default account, and it MUST exist:
if (($type === TransactionType::DEPOSIT || $type === TransactionType::TRANSFER) && !$this->findAssetAccount()) {
Log::error(
sprintf(
'Cannot change destination account of journal #%d because no asset account with name "%s" exists.',
$journal->id, $this->action->action_value
)
);
return true;
}
// if this is a withdrawal, the new destination account must be a expense account and may be created:
if ($type === TransactionType::WITHDRAWAL) {
$this->findExpenseAccount();
}
Log::debug(sprintf('New destination account is #%d ("%s").', $this->newDestinationAccount->id, $this->newDestinationAccount->name));
// update destination transaction with new destination account:
// get destination transaction:
$transaction = $journal->transactions()->where('amount', '>', 0)->first();
$transaction->account_id = $this->newDestinationAccount->id;
$transaction->save();
Log::debug(sprintf('Updated transaction #%d and gave it new account ID.', $transaction->id));
return true;
}
/**
* @return bool
*/
private function findAssetAccount(): bool
{
$account = $this->repository->findByName($this->action->action_value, [AccountType::DEFAULT, AccountType::ASSET]);
if (is_null($account->id)) {
Log::debug(sprintf('There is NO asset account called "%s".', $this->action->action_value));
return false;
}
Log::debug(sprintf('There exists an asset account called "%s". ID is #%d', $this->action->action_value, $account->id));
$this->newDestinationAccount = $account;
return true;
}
/**
*
*/
private function findExpenseAccount()
{
$account = $this->repository->findByName($this->action->action_value, [AccountType::REVENUE]);
if (is_null($account->id)) {
// create new revenue account with this name:
$data = [
'name' => $this->action->action_value,
'accountType' => 'expense',
'virtualBalance' => 0,
'active' => true,
'iban' => null,
];
$account = $this->repository->store($data);
}
Log::debug(sprintf('Found or created expense account #%d ("%s")', $account->id, $account->name));
$this->newDestinationAccount = $account;
}
}

View File

@ -0,0 +1,140 @@
<?php
/**
* SetSourceAccount.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\Rules\Actions;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Log;
/**
* Class SetSourceAccount
*
* @package FireflyIII\Rules\Action
*/
class SetSourceAccount implements ActionInterface
{
private $action;
/** @var TransactionJournal */
private $journal;
/** @var Account */
private $newSourceAccount;
/** @var AccountRepositoryInterface */
private $repository;
/**
* TriggerInterface constructor.
*
* @param RuleAction $action
*/
public function __construct(RuleAction $action)
{
$this->action = $action;
}
/**
* @param TransactionJournal $journal
*
* @return bool
*/
public function act(TransactionJournal $journal): bool
{
$this->journal = $journal;
$this->repository = app(AccountRepositoryInterface::class, [$journal->user]);
$count = $journal->transactions()->count();
if ($count > 2) {
Log::error(sprintf('Cannot change source account of journal #%d because it is a split journal.', $journal->id));
return true;
}
// journal type:
$type = $journal->transactionType->type;
// if this is a transfer or a withdrawal, the new source account must be an asset account or a default account, and it MUST exist:
if (($type === TransactionType::WITHDRAWAL || $type === TransactionType::TRANSFER) && !$this->findAssetAccount()) {
Log::error(
sprintf(
'Cannot change source account of journal #%d because no asset account with name "%s" exists.',
$journal->id, $this->action->action_value
)
);
return true;
}
// if this is a deposit, the new source account must be a revenue account and may be created:
if ($type === TransactionType::DEPOSIT) {
$this->findRevenueAccount();
}
Log::debug(sprintf('New source account is #%d ("%s").', $this->newSourceAccount->id, $this->newSourceAccount->name));
// update source transaction with new source account:
// get source transaction:
$transaction = $journal->transactions()->where('amount', '<', 0)->first();
$transaction->account_id = $this->newSourceAccount->id;
$transaction->save();
Log::debug(sprintf('Updated transaction #%d and gave it new account ID.', $transaction->id));
return true;
}
/**
* @return bool
*/
private function findAssetAccount(): bool
{
$account = $this->repository->findByName($this->action->action_value, [AccountType::DEFAULT, AccountType::ASSET]);
if (is_null($account->id)) {
Log::debug(sprintf('There is NO asset account called "%s".', $this->action->action_value));
return false;
}
Log::debug(sprintf('There exists an asset account called "%s". ID is #%d', $this->action->action_value, $account->id));
$this->newSourceAccount = $account;
return true;
}
/**
*
*/
private function findRevenueAccount()
{
$account = $this->repository->findByName($this->action->action_value, [AccountType::REVENUE]);
if (is_null($account->id)) {
// create new revenue account with this name:
$data = [
'name' => $this->action->action_value,
'accountType' => 'revenue',
'virtualBalance' => 0,
'active' => true,
'iban' => null,
];
$account = $this->repository->store($data);
}
Log::debug(sprintf('Found or created revenue account #%d ("%s")', $account->id, $account->name));
$this->newSourceAccount = $account;
}
}

View File

@ -93,7 +93,7 @@ class ExpandedForm
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$value = $this->fillFieldValue($name, $value);
$value = round($this->fillFieldValue($name, $value), 2);
$options['step'] = 'any';
$defaultCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency();
$currencies = Amt::getAllCurrencies();

158
composer.lock generated
View File

@ -410,35 +410,35 @@
},
{
"name": "doctrine/annotations",
"version": "v1.2.7",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/annotations.git",
"reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535"
"reference": "30e07cf03edc3cd3ef579d0dd4dd8c58250799a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535",
"reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535",
"url": "https://api.github.com/repos/doctrine/annotations/zipball/30e07cf03edc3cd3ef579d0dd4dd8c58250799a5",
"reference": "30e07cf03edc3cd3ef579d0dd4dd8c58250799a5",
"shasum": ""
},
"require": {
"doctrine/lexer": "1.*",
"php": ">=5.3.2"
"php": "^5.6 || ^7.0"
},
"require-dev": {
"doctrine/cache": "1.*",
"phpunit/phpunit": "4.*"
"phpunit/phpunit": "^5.6.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"psr-0": {
"Doctrine\\Common\\Annotations\\": "lib/"
"psr-4": {
"Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -474,20 +474,20 @@
"docblock",
"parser"
],
"time": "2015-08-31 12:32:49"
"time": "2016-10-24 11:45:47"
},
{
"name": "doctrine/cache",
"version": "v1.6.0",
"version": "v1.6.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6"
"reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/f8af318d14bdb0eff0336795b428b547bd39ccb6",
"reference": "f8af318d14bdb0eff0336795b428b547bd39ccb6",
"url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3",
"reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3",
"shasum": ""
},
"require": {
@ -544,7 +544,7 @@
"cache",
"caching"
],
"time": "2015-12-31 16:37:02"
"time": "2016-10-29 11:16:17"
},
{
"name": "doctrine/collections",
@ -1275,16 +1275,16 @@
},
{
"name": "league/csv",
"version": "8.1.1",
"version": "8.1.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
"reference": "3b22a40804aa0bc5224ffb2f5e8248edf0a9a38c"
"reference": "33447984f7a7038fefaa5a6177e8407b66bc85b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/3b22a40804aa0bc5224ffb2f5e8248edf0a9a38c",
"reference": "3b22a40804aa0bc5224ffb2f5e8248edf0a9a38c",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/33447984f7a7038fefaa5a6177e8407b66bc85b4",
"reference": "33447984f7a7038fefaa5a6177e8407b66bc85b4",
"shasum": ""
},
"require": {
@ -1298,7 +1298,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "8.0-dev"
"dev-master": "8.1-dev"
}
},
"autoload": {
@ -1328,7 +1328,7 @@
"read",
"write"
],
"time": "2016-09-05 08:16:07"
"time": "2016-10-27 11:21:24"
},
{
"name": "league/flysystem",
@ -2170,7 +2170,7 @@
},
{
"name": "symfony/class-loader",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/class-loader.git",
@ -2226,16 +2226,16 @@
},
{
"name": "symfony/console",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0"
"reference": "c99da1119ae61e15de0e4829196b9fba6f73d065"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
"reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
"url": "https://api.github.com/repos/symfony/console/zipball/c99da1119ae61e15de0e4829196b9fba6f73d065",
"reference": "c99da1119ae61e15de0e4829196b9fba6f73d065",
"shasum": ""
},
"require": {
@ -2283,11 +2283,11 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
"time": "2016-09-28 00:11:12"
"time": "2016-10-06 01:44:51"
},
{
"name": "symfony/debug",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
@ -2344,16 +2344,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5"
"reference": "28b0832b2553ffb80cabef6a7a812ff1e670c0bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c0c00c80b3a69132c4e55c3e7db32b4a387615e5",
"reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/28b0832b2553ffb80cabef6a7a812ff1e670c0bc",
"reference": "28b0832b2553ffb80cabef6a7a812ff1e670c0bc",
"shasum": ""
},
"require": {
@ -2400,11 +2400,11 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
"time": "2016-07-19 10:45:57"
"time": "2016-10-13 06:28:43"
},
{
"name": "symfony/finder",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
@ -2453,16 +2453,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97"
"reference": "f21e5a8b88274b7720779aa88f9c02c6d6ec08d7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/5114f1becca9f29e3af94374f1689c83c1aa3d97",
"reference": "5114f1becca9f29e3af94374f1689c83c1aa3d97",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/f21e5a8b88274b7720779aa88f9c02c6d6ec08d7",
"reference": "f21e5a8b88274b7720779aa88f9c02c6d6ec08d7",
"shasum": ""
},
"require": {
@ -2502,20 +2502,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
"time": "2016-09-21 20:55:10"
"time": "2016-10-24 15:52:44"
},
{
"name": "symfony/http-kernel",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69"
"reference": "c235f1b13ba67012e283996a5427f22e2e04be14"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/dc339d6eebadfa6e39c52868b4d4a715eff13c69",
"reference": "dc339d6eebadfa6e39c52868b4d4a715eff13c69",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/c235f1b13ba67012e283996a5427f22e2e04be14",
"reference": "c235f1b13ba67012e283996a5427f22e2e04be14",
"shasum": ""
},
"require": {
@ -2523,7 +2523,7 @@
"psr/log": "~1.0",
"symfony/debug": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0",
"symfony/http-foundation": "~2.8.8|~3.0.8|~3.1.2|~3.2"
"symfony/http-foundation": "~2.8.13|~3.1.6|~3.2"
},
"conflict": {
"symfony/config": "<2.8"
@ -2584,7 +2584,7 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
"time": "2016-10-03 19:01:06"
"time": "2016-10-27 02:38:31"
},
{
"name": "symfony/polyfill-mbstring",
@ -2755,7 +2755,7 @@
},
{
"name": "symfony/process",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
@ -2804,7 +2804,7 @@
},
{
"name": "symfony/routing",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
@ -2879,16 +2879,16 @@
},
{
"name": "symfony/translation",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "93013a18d272e59dab8e67f583155b78c68947eb"
"reference": "ff1285087397d2f64041b35e591f3025881c90cd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/93013a18d272e59dab8e67f583155b78c68947eb",
"reference": "93013a18d272e59dab8e67f583155b78c68947eb",
"url": "https://api.github.com/repos/symfony/translation/zipball/ff1285087397d2f64041b35e591f3025881c90cd",
"reference": "ff1285087397d2f64041b35e591f3025881c90cd",
"shasum": ""
},
"require": {
@ -2939,20 +2939,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2016-09-06 11:02:40"
"time": "2016-10-18 04:30:12"
},
{
"name": "symfony/var-dumper",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10"
"reference": "4dc2f03b480c43f1665d3317d827a04ed6ffd11e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/70bfe927b86ba9999aeebd829715b0bb2cd39a10",
"reference": "70bfe927b86ba9999aeebd829715b0bb2cd39a10",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/4dc2f03b480c43f1665d3317d827a04ed6ffd11e",
"reference": "4dc2f03b480c43f1665d3317d827a04ed6ffd11e",
"shasum": ""
},
"require": {
@ -3002,20 +3002,20 @@
"debug",
"dump"
],
"time": "2016-09-29 14:13:09"
"time": "2016-10-18 15:46:07"
},
{
"name": "twig/twig",
"version": "v1.26.1",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d"
"reference": "3c6c0033fd3b5679c6e1cb60f4f9766c2b424d97"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
"reference": "a09d8ee17ac1cfea29ed60c83960ad685c6a898d",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/3c6c0033fd3b5679c6e1cb60f4f9766c2b424d97",
"reference": "3c6c0033fd3b5679c6e1cb60f4f9766c2b424d97",
"shasum": ""
},
"require": {
@ -3028,7 +3028,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.26-dev"
"dev-master": "1.27-dev"
}
},
"autoload": {
@ -3063,7 +3063,7 @@
"keywords": [
"templating"
],
"time": "2016-10-05 18:57:41"
"time": "2016-10-25 19:17:17"
},
{
"name": "vlucas/phpdotenv",
@ -3880,16 +3880,16 @@
},
{
"name": "phpunit/phpunit",
"version": "5.6.1",
"version": "5.6.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7"
"reference": "cd13b23ac5a519a4708e00736c26ee0bb28b2e01"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60c32c5b5e79c2248001efa2560f831da11cc2d7",
"reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/cd13b23ac5a519a4708e00736c26ee0bb28b2e01",
"reference": "cd13b23ac5a519a4708e00736c26ee0bb28b2e01",
"shasum": ""
},
"require": {
@ -3958,7 +3958,7 @@
"testing",
"xunit"
],
"time": "2016-10-07 13:03:26"
"time": "2016-10-25 07:40:25"
},
{
"name": "phpunit/phpunit-mock-objects",
@ -4534,7 +4534,7 @@
},
{
"name": "symfony/css-selector",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@ -4587,16 +4587,16 @@
},
{
"name": "symfony/dom-crawler",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
"reference": "bb7395e8b1db3654de82b9f35d019958276de4d7"
"reference": "59eee3c76eb89f21857798620ebdad7a05ad14f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/bb7395e8b1db3654de82b9f35d019958276de4d7",
"reference": "bb7395e8b1db3654de82b9f35d019958276de4d7",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/59eee3c76eb89f21857798620ebdad7a05ad14f4",
"reference": "59eee3c76eb89f21857798620ebdad7a05ad14f4",
"shasum": ""
},
"require": {
@ -4639,20 +4639,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
"time": "2016-08-05 08:37:39"
"time": "2016-10-18 15:46:07"
},
{
"name": "symfony/yaml",
"version": "v3.1.5",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3"
"reference": "7ff51b06c6c3d5cc6686df69004a42c69df09e27"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/368b9738d4033c8b93454cb0dbd45d305135a6d3",
"reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3",
"url": "https://api.github.com/repos/symfony/yaml/zipball/7ff51b06c6c3d5cc6686df69004a42c69df09e27",
"reference": "7ff51b06c6c3d5cc6686df69004a42c69df09e27",
"shasum": ""
},
"require": {
@ -4688,7 +4688,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-09-25 08:27:07"
"time": "2016-10-24 18:41:13"
},
{
"name": "webmozart/assert",

View File

@ -22,12 +22,13 @@ return [
'single_user_mode' => true,
],
'chart' => 'chartjs',
'version' => '4.1.1',
'version' => '4.1.4',
'csv_import_enabled' => true,
'maxUploadSize' => 5242880,
'allowedMimes' => ['image/png', 'image/jpeg', 'application/pdf'],
'resend_confirmation' => 3600,
'confirmation_age' => 14400, // four hours
'list_length' => 10,
'export_formats' => [
'csv' => 'FireflyIII\Export\Exporter\CsvExporter',
@ -136,6 +137,7 @@ return [
'bill' => 'FireflyIII\Models\Bill',
'budget' => 'FireflyIII\Models\Budget',
'category' => 'FireflyIII\Models\Category',
'transaction_type' => 'FireflyIII\Models\TransactionType',
'currency' => 'FireflyIII\Models\TransactionCurrency',
'limitrepetition' => 'FireflyIII\Models\LimitRepetition',
'piggyBank' => 'FireflyIII\Models\PiggyBank',
@ -183,6 +185,9 @@ return [
'set_description' => 'FireflyIII\Rules\Actions\SetDescription',
'append_description' => 'FireflyIII\Rules\Actions\AppendDescription',
'prepend_description' => 'FireflyIII\Rules\Actions\PrependDescription',
'set_source_account' => 'FireflyIII\Rules\Actions\SetSourceAccount',
'set_destination_account' => 'FireflyIII\Rules\Actions\SetDestinationAccount',
],
'rule-actions-text' => [
'set_category',

4
crowdin.yaml Normal file
View File

@ -0,0 +1,4 @@
files:
-
source: /resources/lang/en_US/*.php
translation: /resources/lang/%locale_with_underscore%/%original_file_name%

View File

@ -0,0 +1,24 @@
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/firefly-iii/public
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/firefly-iii/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Order allow,deny
allow from all
</Directory>
</VirtualHost>

View File

@ -16,12 +16,12 @@ function showHelp(e) {
$('#helpTitle').html('Please hold...');
$('#helpModal').modal('show');
$('#helpTitle').html('Help for this page');
$.getJSON('help/' + encodeURI(route)).done(function (data) {
$('#helpBody').html(data.text);
$('#helpTitle').html(data.title);
$('#helpBody').html(data);
}).fail(function () {
$('#helpBody').html('<p class="text-danger">No help text could be found.</p>');
$('#helpTitle').html('Sorry...');
$('#helpTitle').html('Apologies');
});
return false;
}

View File

@ -8,11 +8,6 @@
/* globals hideable */
/**
* Created by sander on 01/04/16.
*/
$(function () {
"use strict";

View File

@ -1,4 +1,4 @@
/* globals startDate, endDate, reportType, accountIds */
/* globals startDate, showOnlyTop, showFullList, endDate, reportType, accountIds, inOutReportUrl, accountReportUrl */
/*
* all.js
* Copyright (C) 2016 thegrumpydictator@gmail.com
@ -7,21 +7,80 @@
* of the MIT license. See the LICENSE file for details.
*/
/**
* Created by sander on 01/04/16.
*/
$(function () {
"use strict";
// find the little info buttons and respond to them.
$('.firefly-info-button').click(clickInfoButton);
// load the account report, which this report shows:
loadAccountReport();
// load income / expense / difference:
loadInOutReport();
// trigger info click
triggerInfoClick();
// trigger list length things:
listLengthInitial();
});
function triggerInfoClick() {
"use strict";
// find the little info buttons and respond to them.
$('.firefly-info-button').unbind('clicl').click(clickInfoButton);
}
function listLengthInitial() {
"use strict";
$('.overListLength').hide();
$('.listLengthTrigger').unbind('click').click(triggerList)
}
function triggerList(e) {
"use strict";
var link = $(e.target);
var table = link.parent().parent().parent().parent();
console.log('data-hidden = ' + table.attr('data-hidden'));
if (table.attr('data-hidden') === 'no') {
// hide all elements, return false.
table.find('.overListLength').hide();
table.attr('data-hidden', 'yes');
link.text(showFullList);
return false;
}
// show all, return false
table.find('.overListLength').show();
table.attr('data-hidden', 'no');
link.text(showOnlyTop);
return false;
}
function loadInOutReport() {
"use strict";
console.log('Going to grab ' + inOutReportUrl);
$.get(inOutReportUrl).done(placeInOutReport).fail(failInOutReport);
}
function placeInOutReport(data) {
"use strict";
$('#incomeReport').removeClass('loading').html(data.income);
$('#expenseReport').removeClass('loading').html(data.expenses);
$('#incomeVsExpenseReport').removeClass('loading').html(data.incomes_expenses);
listLengthInitial();
triggerInfoClick();
}
function failInOutReport() {
"use strict";
console.log('Fail in/out report data!');
$('#incomeReport').removeClass('loading').addClass('general-chart-error');
$('#expenseReport').removeClass('loading').addClass('general-chart-error');
$('#incomeVsExpenseReport').removeClass('loading').addClass('general-chart-error');
}
function loadAccountReport() {
"use strict";
$.get(accountReportUrl).done(placeAccountReport).fail(failAccountReport);

View File

@ -1,16 +1,52 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds, lineChart, categoryReportUrl, balanceReportUrl */
$(function () {
"use strict";
drawChart();
// click open the top X income list:
$('#showIncomes').click(showIncomes);
// click open the top X expense list:
$('#showExpenses').click(showExpenses);
loadCategoryReport();
loadBalanceReport();
});
function loadCategoryReport() {
"use strict";
console.log('Going to grab ' + categoryReportUrl);
$.get(categoryReportUrl).done(placeCategoryReport).fail(failCategoryReport);
}
function loadBalanceReport() {
"use strict";
console.log('Going to grab ' + categoryReportUrl);
$.get(balanceReportUrl).done(placeBalanceReport).fail(failBalanceReport);
}
function placeBalanceReport(data) {
"use strict";
$('#balanceReport').removeClass('loading').html(data);
listLengthInitial();
triggerInfoClick();
}
function placeCategoryReport(data) {
"use strict";
$('#categoryReport').removeClass('loading').html(data);
listLengthInitial();
triggerInfoClick();
}
function failBalanceReport() {
"use strict";
console.log('Fail balance report data!');
$('#balanceReport').removeClass('loading').addClass('general-chart-error');
}
function failCategoryReport() {
"use strict";
console.log('Fail category report data!');
$('#categoryReport').removeClass('loading').addClass('general-chart-error');
}
function drawChart() {
"use strict";
@ -19,46 +55,3 @@ function drawChart() {
// draw account chart
lineChart('chart/account/report/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'account-balances-chart');
}
function showIncomes() {
"use strict";
if (incomeRestShow) {
// hide everything, make button say "show"
$('#showIncomes').text(showTheRest);
$('.incomesCollapsed').removeClass('in').addClass('out');
// toggle:
incomeRestShow = false;
} else {
// show everything, make button say "hide".
$('#showIncomes').text(hideTheRest);
$('.incomesCollapsed').removeClass('out').addClass('in');
// toggle:
incomeRestShow = true;
}
return false;
}
function showExpenses() {
"use strict";
if (expenseRestShow) {
// hide everything, make button say "show"
$('#showExpenses').text(showTheRestExpense);
$('.expenseCollapsed').removeClass('in').addClass('out');
// toggle:
expenseRestShow = false;
} else {
// show everything, make button say "hide".
$('#showExpenses').text(hideTheRestExpense);
$('.expenseCollapsed').removeClass('out').addClass('in');
// toggle:
expenseRestShow = true;
}
return false;
}

View File

@ -1,15 +1,10 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, year, month, columnChart, lineChart, stackedColumnChart */
$(function () {
"use strict";
drawChart();
// click open the top X income list:
$('#showIncomes').click(showIncomes);
// click open the top X expense list:
$('#showExpenses').click(showExpenses);
});
@ -159,47 +154,3 @@ function readCookie(name) {
function eraseCookie(name) {
createCookie(name, "", -1);
}
function showIncomes() {
"use strict";
if (incomeRestShow) {
// hide everything, make button say "show"
$('#showIncomes').text(showTheRest);
$('.incomesCollapsed').removeClass('in').addClass('out');
// toggle:
incomeRestShow = false;
} else {
// show everything, make button say "hide".
$('#showIncomes').text(hideTheRest);
$('.incomesCollapsed').removeClass('out').addClass('in');
// toggle:
incomeRestShow = true;
}
return false;
}
function showExpenses() {
"use strict";
if (expenseRestShow) {
// hide everything, make button say "show"
$('#showExpenses').text(showTheRestExpense);
$('.expenseCollapsed').removeClass('in').addClass('out');
// toggle:
expenseRestShow = false;
} else {
// show everything, make button say "hide".
$('#showExpenses').text(hideTheRestExpense);
$('.expenseCollapsed').removeClass('out').addClass('in');
// toggle:
expenseRestShow = true;
}
return false;
}

View File

@ -1,4 +1,4 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, year, month, columnChart, lineChart, stackedColumnChart */
var chartDrawn;
var budgetChart;
@ -7,10 +7,6 @@ $(function () {
chartDrawn = false;
drawChart();
// click open the top X income list:
$('#showIncomes').click(showIncomes);
// click open the top X expense list:
$('#showExpenses').click(showExpenses);
});
@ -86,52 +82,5 @@ function clickBudgetChart(e) {
}
// if chart drawn is true, add new data to existing chart.
// console.log('Budget id is ' + budgetId);
// $('#budget_chart').empty();
// columnChart('chart/budget/period/' + budgetId + '/' + reportType + '/' + startDate + '/' + endDate + '/' + accountIds, 'budget_chart');
return false;
}
function showIncomes() {
"use strict";
if (incomeRestShow) {
// hide everything, make button say "show"
$('#showIncomes').text(showTheRest);
$('.incomesCollapsed').removeClass('in').addClass('out');
// toggle:
incomeRestShow = false;
} else {
// show everything, make button say "hide".
$('#showIncomes').text(hideTheRest);
$('.incomesCollapsed').removeClass('out').addClass('in');
// toggle:
incomeRestShow = true;
}
return false;
}
function showExpenses() {
"use strict";
if (expenseRestShow) {
// hide everything, make button say "show"
$('#showExpenses').text(showTheRestExpense);
$('.expenseCollapsed').removeClass('in').addClass('out');
// toggle:
expenseRestShow = false;
} else {
// show everything, make button say "hide".
$('#showExpenses').text(hideTheRestExpense);
$('.expenseCollapsed').removeClass('out').addClass('in');
// toggle:
expenseRestShow = true;
}
return false;
}

View File

@ -1,4 +1,4 @@
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, expenseRestShow:true, incomeRestShow:true, year, month, hideTheRest, showTheRest, showTheRestExpense, hideTheRestExpense, columnChart, lineChart, stackedColumnChart */
/* globals google, startDate ,reportURL, endDate , reportType ,accountIds , picker:true, minDate, year, month, columnChart, lineChart, stackedColumnChart */
$(function () {

View File

@ -58,11 +58,11 @@ function sortStop(event, ui) {
});
if (parent.hasClass('rule-triggers')) {
$.post('rules/rules/trigger/reorder/' + ruleId, {_token: token, triggers: entries}).fail(function () {
$.post('rules/trigger/order/' + ruleId, {_token: token, triggers: entries}).fail(function () {
alert('Could not re-order rule triggers. Please refresh the page.');
});
} else {
$.post('rules/rules/action/reorder/' + ruleId, {_token: token, actions: entries}).fail(function () {
$.post('rules/action/order/' + ruleId, {_token: token, actions: entries}).fail(function () {
alert('Could not re-order rule actions. Please refresh the page.');
});

View File

@ -136,13 +136,13 @@ function resetSplits() {
// ends with ][description]
$.each($('input[name$="][description]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transaction[' + i + '][description]');
input.attr('name', 'transactions[' + i + '][description]');
console.log('description is now ' + input.attr('name'));
});
// ends with ][destination_account_name]
$.each($('input[name$="][destination_account_name]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transaction[' + i + '][destination_account_name]');
input.attr('name', 'transactions[' + i + '][destination_account_name]');
console.log('destination_account_name is now ' + input.attr('name'));
});
// ends with ][source_account_name]
@ -154,19 +154,19 @@ function resetSplits() {
// ends with ][amount]
$.each($('input[name$="][amount]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transaction[' + i + '][amount]');
input.attr('name', 'transactions[' + i + '][amount]');
console.log('amount is now ' + input.attr('name'));
});
// ends with ][budget_id]
$.each($('input[name$="][budget_id]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transaction[' + i + '][budget_id]');
input.attr('name', 'transactions[' + i + '][budget_id]');
console.log('budget_id is now ' + input.attr('name'));
});
// ends with ][category]
$.each($('input[name$="][category]"]'), function (i, v) {
var input = $(v);
input.attr('name', 'transaction[' + i + '][category]');
input.attr('name', 'transactions[' + i + '][category]');
console.log('category is now ' + input.attr('name'));
});
}

View File

@ -22,7 +22,7 @@ return [
|
*/
'failed' => 'These credentials do not match our records.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
'failed' => 'Falscher Benutzername und/oder falsches Passwort.',
'throttle' => 'Zu viele Login-Versuche. Bitte versuchen Sie es in :seconds Sekunde(n) erneut.',
];

View File

@ -73,6 +73,7 @@ return [
'field_supports_markdown' => 'This field supports <a href="https://en.support.wordpress.com/markdown-quick-reference/">Markdown</a>.',
// repeat frequencies:
'repeat_freq_yearly' => 'yearly',
'repeat_freq_monthly' => 'monatlich',
'weekly' => 'wöchentlich',
'quarterly' => 'vierteljährlich',
@ -99,7 +100,6 @@ return [
'export_format_csv' => 'Durch Komma getrennte Werte (CSV-Datei)',
'export_format_mt940' => 'MT940 compatible format',
'export_included_accounts' => 'Exportiere die Überweisungen von diesem Konto',
'include_config_help' => 'Zum einfachen re-importieren in Firefly III',
'include_old_uploads_help' => 'Firefly III löscht nicht die originalen CSV-Dateien, welche zuvor importiert wurden. Sie können dem Export hinzugefügt werden.',
'do_export' => 'Export',
'export_status_never_started' => 'Der Export hat noch nicht begonnen',
@ -238,6 +238,10 @@ return [
'rule_action_set_description_choice' => 'Set description to..',
'rule_action_append_description_choice' => 'Append description with..',
'rule_action_prepend_description_choice' => 'Prepend description with..',
'rule_action_set_source_account_choice' => 'Set source account to...',
'rule_action_set_source_account' => 'Set source account to :action_value',
'rule_action_set_destination_account_choice' => 'Set destination account to...',
'rule_action_set_destination_account' => 'Set destination account to :action_value',
// tags
'store_new_tag' => 'Neuen Tag speichern',
@ -349,6 +353,41 @@ return [
'title_transfer' => 'Überweisungen',
'title_transfers' => 'Überweisungen',
// convert stuff:
'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal',
'convert_is_already_type_Deposit' => 'This transaction is already a deposit',
'convert_is_already_type_Transfer' => 'This transaction is already a transfer',
'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal',
'convert_to_Deposit' => 'Convert ":description" to a deposit',
'convert_to_Transfer' => 'Convert ":description" to a transfer',
'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit',
'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer',
'convert_options_DepositTransfer' => 'Convert a deposit into a transfer',
'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal',
'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal',
'convert_options_TransferDeposit' => 'Convert a transfer into a deposit',
'transaction_journal_convert_options' => 'Convert this transaction',
'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit',
'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer',
'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal',
'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer',
'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit',
'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal',
'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.',
'convert_please_set_asset_destination' => 'Please pick the asset account where the money will go to.',
'convert_please_set_expense_destination' => 'Please pick the expense account where the money will go to.',
'convert_please_set_asset_source' => 'Please pick the asset account where the money will come from.',
'convert_explanation_withdrawal_deposit' => 'If you convert this withdrawal into a deposit, :amount will be deposited into <a href=":sourceRoute">:sourceName</a> instead of taken from it.',
'convert_explanation_withdrawal_transfer' => 'If you convert this withdrawal into a transfer, :amount will be transferred from <a href=":sourceRoute">:sourceName</a> to a new asset account, instead of being paid to <a href=":destinationRoute">:destinationName</a>.',
'convert_explanation_deposit_withdrawal' => 'If you convert this deposit into a withdrawal, :amount will be removed from <a href=":destinationRoute">:destinationName</a> instead of added to it.',
'convert_explanation_deposit_transfer' => 'If you convert this deposit into a transfer, :amount will be transferred from an asset account of your choice into <a href=":destinationRoute">:destinationName</a>.',
'convert_explanation_transfer_withdrawal' => 'If you convert this transfer into a withdrawal, :amount will go from <a href=":sourceRoute">:sourceName</a> to a new destination as an expense, instead of to <a href=":destinationRoute">:destinationName</a> as a transfer.',
'convert_explanation_transfer_deposit' => 'If you convert this transfer into a deposit, :amount will be deposited into account <a href=":destinationRoute">:destinationName</a> instead of being transferred there.',
'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal',
'converted_to_Deposit' => 'The transaction has been converted to a deposit',
'converted_to_Transfer' => 'The transaction has been converted to a transfer',
// create new stuff:
'create_new_withdrawal' => 'Erstelle eine neue Ausgabe',
'create_new_deposit' => 'Erstelle ein neues Einkommen',
@ -426,10 +465,10 @@ return [
'stored_new_bill' => 'Neue Rechung ":name" gespeichert',
'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.',
'rescanned_bill' => 'Rescanned everything.',
'bill_date_little_relevance' => 'Der einzige Teil dieses Datum, der von Firefly verwendet wird, ist der Tag. Er ist nur sinnvoll, wenn ihre Rechnung immer am selben Tag im Monat anfallen. Wenn der Zahlungstag der Rechnung variiert geben sie einfach den Ersten des Monats an.',
'average_bill_amount_year' => 'Durchschnittliche Rechnungssumme (:year)',
'average_bill_amount_overall' => 'Durchschnittliche Rechnungssumme (gesamt)',
'not_or_not_yet' => 'Not (yet)',
'not_expected_period' => 'Not expected this period',
// accounts:
'details_for_asset' => 'Details für Girokonto ":name"',
'details_for_expense' => 'Details for expense account ":name"',
@ -604,8 +643,8 @@ return [
'in' => 'Rein',
'out' => 'Raus',
'topX' => 'top :number',
'showTheRest' => 'Alles anzeigen',
'hideTheRest' => 'Nur die Top :number anzeigen',
'show_full_list' => 'Show entire list',
'show_only_top' => 'Show only top :number',
'sum_of_year' => 'Summe des Jahres',
'sum_of_years' => 'Summe der Jahre',
'average_of_year' => 'Durchschnitt des Jahres',
@ -619,7 +658,6 @@ return [
'more_info_help' => 'Weitere Informationen über diese Art von Berichten finden Sie in der Hilfe. Drücken Sie hierfür das (?)-Symbol in der oberen rechten Ecke.',
'report_included_accounts' => 'Eingezogene Konten',
'report_date_range' => 'Zeitraum',
'report_include_help' => 'In allen Fällen gilt: Überweisungen zu gemeinsamen Konten gelten als Ausgaben und Überweisungen von gemeinsamen Konten gelten als Einnahmen.',
'report_preset_ranges' => 'Pre-set ranges',
'shared' => 'Shared',
'fiscal_year' => 'Geschäftsjahr',
@ -630,6 +668,9 @@ return [
'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end',
'no_audit_activity' => 'No activity was recorded on account <a href=":url" title=":account_name">:account_name</a> between :start and :end.',
'audit_end_balance' => 'Account balance of <a href=":url" title=":account_name">:account_name</a> at the end of :end was: :balance',
'reports_extra_options' => 'Extra options',
'report_has_no_extra_options' => 'This report has no extra options',
'reports_submit' => 'View report',
// charts:
'chart' => 'Chart',

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