Merge branch 'release/5.7.0'

This commit is contained in:
James Cole 2022-04-03 12:13:35 +02:00
commit e6854b9265
1073 changed files with 36364 additions and 22411 deletions

View File

@ -142,7 +142,6 @@ MAIL_ENCRYPTION=null
MAILGUN_DOMAIN= MAILGUN_DOMAIN=
MAILGUN_SECRET= MAILGUN_SECRET=
# If you are on EU region in mailgun, use api.eu.mailgun.net, otherwise use api.mailgun.net # If you are on EU region in mailgun, use api.eu.mailgun.net, otherwise use api.mailgun.net
# If you use Docker or similar, you can set this variable from a file by appending it with _FILE # If you use Docker or similar, you can set this variable from a file by appending it with _FILE
MAILGUN_ENDPOINT=api.mailgun.net MAILGUN_ENDPOINT=api.mailgun.net
@ -176,42 +175,14 @@ MAP_DEFAULT_ZOOM=6
# #
# Firefly III supports a few authentication methods: # Firefly III supports a few authentication methods:
# - 'web' (default, uses built in DB) # - 'web' (default, uses built in DB)
# - 'ldap'
# - 'remote_user_guard' for Authelia etc # - 'remote_user_guard' for Authelia etc
# Read more about these settings in the documentation. # Read more about these settings in the documentation.
# https://docs.firefly-iii.org/advanced-installation/authentication # https://docs.firefly-iii.org/advanced-installation/authentication
#
# LDAP is no longer supported :(
#
AUTHENTICATION_GUARD=web AUTHENTICATION_GUARD=web
#
# Your LDAP server may speak a dialect. You can choose between 'OpenLDAP' and 'ActiveDirectory'
# Anything else defaults to 'ActiveDirectory'
#
LDAP_DIALECT=OpenLDAP
#
# LDAP connection settings:
#
LDAP_HOST=ldap.yourserver.com
LDAP_PORT=389
LDAP_TIMEOUT=5
LDAP_SSL=false
LDAP_TLS=false
LDAP_BASE_DN="o=something,dc=site,dc=com"
LDAP_USERNAME="uid=X,ou=,o=,dc=something,dc=com"
LDAP_PASSWORD=super_secret
LDAP_AUTH_FIELD=uid
#
# If you wish to only authenticate users from a specific group, use the base DN above.
#
# If you require extra/special filters please use the LDAP_EXTRA_FILTER with a valid DN.
#
# The extra filter will only be applied after the user is authenticated.
#
LDAP_EXTRA_FILTER=
# #
# Remote user guard settings # Remote user guard settings
# #
@ -263,6 +234,13 @@ STATIC_CRON_TOKEN=
# However if you know what you're doing you can significantly speed up container start times. # However if you know what you're doing you can significantly speed up container start times.
# Set each value to true to enable, or false to disable. # Set each value to true to enable, or false to disable.
# Set this to true to build all locales supported by Firefly III.
# This may take quite some time (several minutes) and is generally not recommended.
# If you wish to change or alter the list of locales, start your Docker container with
# `docker run -v locale.gen:/etc/locale.gen -e DKR_BUILD_LOCALE=true`
# and make sure your preferred locales are in your own locale.gen.
DKR_BUILD_LOCALE=false
# Check if the SQLite database exists. Can be skipped if you're not using SQLite. # Check if the SQLite database exists. Can be skipped if you're not using SQLite.
# Won't significantly speed up things. # Won't significantly speed up things.
DKR_CHECK_SQLITE=true DKR_CHECK_SQLITE=true
@ -291,7 +269,6 @@ DKR_RUN_PASSPORT_INSTALL=true
# Leave the following configuration vars as is. # Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing. # Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII APP_NAME=FireflyIII
ADLDAP_CONNECTION=default
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
QUEUE_DRIVER=sync QUEUE_DRIVER=sync
CACHE_PREFIX=firefly CACHE_PREFIX=firefly

View File

@ -70,8 +70,9 @@ class AccountController extends Controller
* @param AutocompleteRequest $request * @param AutocompleteRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
* @throws JsonException * @throws JsonException
* @throws FireflyException
* @throws FireflyException
*/ */
public function accounts(AutocompleteRequest $request): JsonResponse public function accounts(AutocompleteRequest $request): JsonResponse
{ {

View File

@ -35,6 +35,9 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\ApiSupport; use FireflyIII\Support\Http\Api\ApiSupport;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use JsonException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/** /**
* Class AccountController * Class AccountController
@ -77,6 +80,9 @@ class AccountController extends Controller
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException * @throws FireflyException
* @throws JsonException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/ */
public function overview(DateRequest $request): JsonResponse public function overview(DateRequest $request): JsonResponse
{ {

View File

@ -34,6 +34,8 @@ use Illuminate\Routing\Controller as BaseController;
use League\Fractal\Manager; use League\Fractal\Manager;
use League\Fractal\Serializer\JsonApiSerializer; use League\Fractal\Serializer\JsonApiSerializer;
use Log; use Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\ParameterBag;
/** /**
@ -74,6 +76,8 @@ abstract class Controller extends BaseController
* Method to grab all parameters from the URI. * Method to grab all parameters from the URI.
* *
* @return ParameterBag * @return ParameterBag
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/ */
private function getParameters(): ParameterBag private function getParameters(): ParameterBag
{ {
@ -94,7 +98,7 @@ abstract class Controller extends BaseController
$obj = Carbon::parse($date); $obj = Carbon::parse($date);
} catch (InvalidDateException | InvalidFormatException $e) { } catch (InvalidDateException | InvalidFormatException $e) {
// don't care // don't care
Log::warn(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', (string) $date, $e->getMessage())); Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', $date, $e->getMessage()));
} }
} }
$bag->set($field, $obj); $bag->set($field, $obj);

View File

@ -209,8 +209,6 @@ class ListController extends Controller
* *
* @param Request $request * @param Request $request
* *
* @param Budget $budget
*
* @return JsonResponse * @return JsonResponse
* @throws FireflyException * @throws FireflyException
* @codeCoverageIgnore * @codeCoverageIgnore

View File

@ -33,6 +33,7 @@ use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer; use FireflyIII\Transformers\CurrencyTransformer;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use JsonException;
use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
@ -72,6 +73,7 @@ class ShowController extends Controller
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException * @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function index(): JsonResponse public function index(): JsonResponse
@ -106,6 +108,8 @@ class ShowController extends Controller
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function show(TransactionCurrency $currency): JsonResponse public function show(TransactionCurrency $currency): JsonResponse
@ -130,6 +134,8 @@ class ShowController extends Controller
* Show a currency. * Show a currency.
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function showDefault(): JsonResponse public function showDefault(): JsonResponse

View File

@ -33,6 +33,7 @@ use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer; use FireflyIII\Transformers\CurrencyTransformer;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use JsonException;
use League\Fractal\Resource\Item; use League\Fractal\Resource\Item;
/** /**
@ -74,6 +75,7 @@ class StoreController extends Controller
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException * @throws FireflyException
* @throws JsonException
*/ */
public function store(StoreRequest $request): JsonResponse public function store(StoreRequest $request): JsonResponse
{ {

View File

@ -75,6 +75,8 @@ class UpdateController extends Controller
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function disable(TransactionCurrency $currency): JsonResponse public function disable(TransactionCurrency $currency): JsonResponse
@ -108,6 +110,8 @@ class UpdateController extends Controller
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function enable(TransactionCurrency $currency): JsonResponse public function enable(TransactionCurrency $currency): JsonResponse

View File

@ -113,7 +113,7 @@ class BasicController extends Controller
// give new keys // give new keys
$return = []; $return = [];
foreach ($total as $entry) { foreach ($total as $entry) {
if (null === $code || (null !== $code && $code === $entry['currency_code'])) { if (null === $code || ($code === $entry['currency_code'])) {
$return[$entry['key']] = $entry; $return[$entry['key']] = $entry;
} }
} }
@ -150,7 +150,9 @@ class BasicController extends Controller
foreach ($set as $transactionJournal) { foreach ($set as $transactionJournal) {
$currencyId = (int) $transactionJournal['currency_id']; $currencyId = (int) $transactionJournal['currency_id'];
$incomes[$currencyId] = $incomes[$currencyId] ?? '0'; $incomes[$currencyId] = $incomes[$currencyId] ?? '0';
$incomes[$currencyId] = bcadd($incomes[$currencyId], bcmul($transactionJournal['amount'], '-1')); $incomes[$currencyId] = bcadd($incomes[$currencyId],
bcmul($transactionJournal['amount'], '-1')
);
$sums[$currencyId] = $sums[$currencyId] ?? '0'; $sums[$currencyId] = $sums[$currencyId] ?? '0';
$sums[$currencyId] = bcadd($sums[$currencyId], bcmul($transactionJournal['amount'], '-1')); $sums[$currencyId] = bcadd($sums[$currencyId], bcmul($transactionJournal['amount'], '-1'));
} }
@ -362,7 +364,7 @@ class BasicController extends Controller
function (Account $account) { function (Account $account) {
$includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth'); $includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth');
return null === $includeNetWorth ? true : '1' === $includeNetWorth; return null === $includeNetWorth || '1' === $includeNetWorth;
} }
); );

View File

@ -30,6 +30,8 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Binder\EitherConfigKey; use FireflyIII\Support\Binder\EitherConfigKey;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Log; use Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/** /**
* Class ConfigurationController * Class ConfigurationController
@ -94,6 +96,8 @@ class ConfigurationController extends Controller
* *
* @return array * @return array
* @throws FireflyException * @throws FireflyException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/ */
private function getDynamicConfiguration(): array private function getDynamicConfiguration(): array
{ {

View File

@ -81,7 +81,7 @@ class UserController extends Controller
return response()->json([], 500); return response()->json([], 500);
} }
if ($admin->id !== $user->id && $this->repository->hasRole($admin, 'owner')) { if ($this->repository->hasRole($admin, 'owner')) {
$this->repository->destroy($user); $this->repository->destroy($user);
return response()->json([], 204); return response()->json([], 204);

View File

@ -74,10 +74,24 @@ class MoveTransactionsRequest extends FormRequest
// validate start before end only if both are there. // validate start before end only if both are there.
$data = $validator->getData(); $data = $validator->getData();
if (array_key_exists('original_account', $data) && array_key_exists('destination_account', $data)) { if (array_key_exists('original_account', $data) && array_key_exists('destination_account', $data)) {
$this->validateMove($validator);
}
}
);
}
/**
* @param Validator $validator
* @return void
*/
private function validateMove(Validator $validator): void {
$data = $validator->getData();
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$repository->setUser(auth()->user()); $repository->setUser(auth()->user());
$original = $repository->find((int) $data['original_account']); $original = $repository->find((int) $data['original_account']);
$destination = $repository->find((int) $data['destination_account']); $destination = $repository->find((int) $data['destination_account']);
// not the same type:
if ($original->accountType->type !== $destination->accountType->type) { if ($original->accountType->type !== $destination->accountType->type) {
$validator->errors()->add('title', (string) trans('validation.same_account_type')); $validator->errors()->add('title', (string) trans('validation.same_account_type'));
@ -86,6 +100,8 @@ class MoveTransactionsRequest extends FormRequest
// get currency pref: // get currency pref:
$originalCurrency = $repository->getAccountCurrency($original); $originalCurrency = $repository->getAccountCurrency($original);
$destinationCurrency = $repository->getAccountCurrency($destination); $destinationCurrency = $repository->getAccountCurrency($destination);
// check different scenario's.
if (null === $originalCurrency xor null === $destinationCurrency) { if (null === $originalCurrency xor null === $destinationCurrency) {
$validator->errors()->add('title', (string) trans('validation.same_account_currency')); $validator->errors()->add('title', (string) trans('validation.same_account_currency'));
@ -100,6 +116,3 @@ class MoveTransactionsRequest extends FormRequest
} }
} }
} }
);
}
}

View File

@ -128,7 +128,7 @@ class GenericRequest extends FormRequest
foreach ($array as $billId) { foreach ($array as $billId) {
$billId = (int) $billId; $billId = (int) $billId;
$bill = $repository->find($billId); $bill = $repository->find($billId);
if (null !== $billId) { if (null !== $bill) {
$this->bills->push($bill); $this->bills->push($bill);
} }
} }
@ -160,7 +160,7 @@ class GenericRequest extends FormRequest
foreach ($array as $budgetId) { foreach ($array as $budgetId) {
$budgetId = (int) $budgetId; $budgetId = (int) $budgetId;
$budget = $repository->find($budgetId); $budget = $repository->find($budgetId);
if (null !== $budgetId) { if (null !== $budget) {
$this->budgets->push($budget); $this->budgets->push($budget);
} }
} }
@ -192,7 +192,7 @@ class GenericRequest extends FormRequest
foreach ($array as $categoryId) { foreach ($array as $categoryId) {
$categoryId = (int) $categoryId; $categoryId = (int) $categoryId;
$category = $repository->find($categoryId); $category = $repository->find($categoryId);
if (null !== $categoryId) { if (null !== $category) {
$this->categories->push($category); $this->categories->push($category);
} }
} }
@ -282,7 +282,7 @@ class GenericRequest extends FormRequest
foreach ($array as $tagId) { foreach ($array as $tagId) {
$tagId = (int) $tagId; $tagId = (int) $tagId;
$tag = $repository->find($tagId); $tag = $repository->find($tagId);
if (null !== $tagId) { if (null !== $tag) {
$this->tags->push($tag); $this->tags->push($tag);
} }
} }

View File

@ -50,6 +50,7 @@ class StoreRequest extends FormRequest
'name' => ['name', 'string'], 'name' => ['name', 'string'],
'active' => ['active', 'boolean'], 'active' => ['active', 'boolean'],
'order' => ['active', 'integer'], 'order' => ['active', 'integer'],
'notes' => ['notes', 'string'],
// auto budget currency: // auto budget currency:
'currency_id' => ['auto_budget_currency_id', 'integer'], 'currency_id' => ['auto_budget_currency_id', 'integer'],
@ -74,6 +75,7 @@ class StoreRequest extends FormRequest
'active' => [new IsBoolean], 'active' => [new IsBoolean],
'currency_id' => 'exists:transaction_currencies,id', 'currency_id' => 'exists:transaction_currencies,id',
'currency_code' => 'exists:transaction_currencies,code', 'currency_code' => 'exists:transaction_currencies,code',
'notes' => 'nullable|between:1,65536',
// auto budget info // auto budget info
'auto_budget_type' => 'in:reset,rollover,none', 'auto_budget_type' => 'in:reset,rollover,none',
'auto_budget_amount' => 'numeric|min:0|max:1000000000|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover', 'auto_budget_amount' => 'numeric|min:0|max:1000000000|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover',

View File

@ -51,6 +51,7 @@ class UpdateRequest extends FormRequest
'name' => ['name', 'string'], 'name' => ['name', 'string'],
'active' => ['active', 'boolean'], 'active' => ['active', 'boolean'],
'order' => ['order', 'integer'], 'order' => ['order', 'integer'],
'notes' => ['notes', 'string'],
'currency_id' => ['auto_budget_currency_id', 'integer'], 'currency_id' => ['auto_budget_currency_id', 'integer'],
'currency_code' => ['auto_budget_currency_code', 'string'], 'currency_code' => ['auto_budget_currency_code', 'string'],
'auto_budget_type' => ['auto_budget_type', 'string'], 'auto_budget_type' => ['auto_budget_type', 'string'],
@ -82,6 +83,7 @@ class UpdateRequest extends FormRequest
return [ return [
'name' => sprintf('between:1,100|uniqueObjectForUser:budgets,name,%d', $budget->id), 'name' => sprintf('between:1,100|uniqueObjectForUser:budgets,name,%d', $budget->id),
'active' => [new IsBoolean], 'active' => [new IsBoolean],
'notes' => 'nullable|between:1,65536',
'auto_budget_type' => 'in:reset,rollover,none', 'auto_budget_type' => 'in:reset,rollover,none',
'auto_budget_currency_id' => 'exists:transaction_currencies,id', 'auto_budget_currency_id' => 'exists:transaction_currencies,id',
'auto_budget_currency_code' => 'exists:transaction_currencies,code', 'auto_budget_currency_code' => 'exists:transaction_currencies,code',

View File

@ -174,6 +174,21 @@ class StoreRequest extends FormRequest
} }
} }
/**
* Adds an error to the validator when there are no repetitions in the array of data.
*
* @param Validator $validator
*/
protected function atLeastOneAction(Validator $validator): void
{
$data = $validator->getData();
$actions = $data['actions'] ?? [];
// need at least one trigger
if (!is_countable($actions) || empty($actions)) {
$validator->errors()->add('title', (string) trans('validation.at_least_one_action'));
}
}
/** /**
* Adds an error to the validator when there are no ACTIVE triggers in the array of data. * Adds an error to the validator when there are no ACTIVE triggers in the array of data.
* *
@ -231,19 +246,4 @@ class StoreRequest extends FormRequest
$validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_action')); $validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_action'));
} }
} }
/**
* Adds an error to the validator when there are no repetitions in the array of data.
*
* @param Validator $validator
*/
protected function atLeastOneAction(Validator $validator): void
{
$data = $validator->getData();
$actions = $data['actions'] ?? [];
// need at least one trigger
if (!is_countable($actions) || empty($actions)) {
$validator->errors()->add('title', (string)trans('validation.at_least_one_action'));
}
}
} }

View File

@ -57,7 +57,6 @@ class CorrectOpeningBalanceCurrencies extends Command
* Execute the console command. * Execute the console command.
* *
* @return int * @return int
* @throws JsonException
*/ */
public function handle(): int public function handle(): int
{ {
@ -136,6 +135,8 @@ class CorrectOpeningBalanceCurrencies extends Command
* @param Account $account * @param Account $account
* *
* @return TransactionCurrency * @return TransactionCurrency
* @throws JsonException
* @throws \FireflyIII\Exceptions\FireflyException
*/ */
private function getCurrency(Account $account): TransactionCurrency private function getCurrency(Account $account): TransactionCurrency
{ {

View File

@ -1,4 +1,25 @@
<?php <?php
/*
* FixIbans.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction; namespace FireflyIII\Console\Commands\Correction;

View File

@ -109,6 +109,8 @@ class DecryptDatabase extends Command
* @param string $table * @param string $table
* *
* @return bool * @return bool
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isDecrypted(string $table): bool private function isDecrypted(string $table): bool
{ {

View File

@ -27,7 +27,6 @@ use Crypt;
use FireflyIII\Models\Attachment; use FireflyIII\Models\Attachment;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Log; use Log;
use Storage; use Storage;
@ -62,10 +61,9 @@ class ScanAttachments extends Command
/** @var Attachment $attachment */ /** @var Attachment $attachment */
foreach ($attachments as $attachment) { foreach ($attachments as $attachment) {
$fileName = $attachment->fileName(); $fileName = $attachment->fileName();
try {
$encryptedContent = $disk->get($fileName); $encryptedContent = $disk->get($fileName);
} catch (FileNotFoundException $e) { if (null === $encryptedContent) {
$this->error(sprintf('Could not find data for attachment #%d: %s', $attachment->id, $e->getMessage())); Log::error(sprintf('No content for attachment #%d under filename "%s"', $attachment->id, $fileName));
continue; continue;
} }
try { try {

View File

@ -27,6 +27,7 @@ namespace FireflyIII\Console\Commands\Tools;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\Cronjobs\AutoBudgetCronjob; use FireflyIII\Support\Cronjobs\AutoBudgetCronjob;
use FireflyIII\Support\Cronjobs\BillWarningCronjob;
use FireflyIII\Support\Cronjobs\RecurringCronjob; use FireflyIII\Support\Cronjobs\RecurringCronjob;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use InvalidArgumentException; use InvalidArgumentException;
@ -90,6 +91,17 @@ class Cron extends Command
$this->error($e->getMessage()); $this->error($e->getMessage());
} }
/*
* Fire bill warning cron job
*/
try {
$this->billWarningCronJob($force, $date);
} catch (FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->error($e->getMessage());
}
$this->info('More feedback on the cron jobs can be found in the log files.'); $this->info('More feedback on the cron jobs can be found in the log files.');
return 0; return 0;
@ -150,4 +162,32 @@ class Cron extends Command
} }
} }
/**
* @param bool $force
* @param Carbon|null $date
* @throws FireflyException
*/
private function billWarningCronJob(bool $force, ?Carbon $date): void
{
$autoBudget = new BillWarningCronjob;
$autoBudget->setForce($force);
// set date in cron job:
if (null !== $date) {
$autoBudget->setDate($date);
}
$autoBudget->fire();
if ($autoBudget->jobErrored) {
$this->error(sprintf('Error in "bill warnings" cron: %s', $autoBudget->message));
}
if ($autoBudget->jobFired) {
$this->error(sprintf('"Send bill warnings" cron fired: %s', $autoBudget->message));
}
if ($autoBudget->jobSucceeded) {
$this->error(sprintf('"Send bill warnings" cron ran with success: %s', $autoBudget->message));
}
}
} }

View File

@ -109,6 +109,8 @@ class AccountCurrencies extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -72,6 +72,8 @@ class AppendBudgetLimitPeriods extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -86,6 +86,8 @@ class BackToJournals extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isMigrated(): bool private function isMigrated(): bool
{ {
@ -97,6 +99,8 @@ class BackToJournals extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -99,6 +99,8 @@ class BudgetLimitCurrency extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -96,6 +96,8 @@ class CCLiabilities extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -77,6 +77,8 @@ class CreateGroupMemberships extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -108,6 +108,8 @@ class MigrateAttachments extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -106,6 +106,8 @@ class MigrateJournalNotes extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -84,6 +84,8 @@ class MigrateRecurrenceMeta extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -77,6 +77,8 @@ class MigrateRecurrenceType extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -75,6 +75,8 @@ class MigrateTagLocations extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -129,6 +129,8 @@ class MigrateToGroups extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isMigrated(): bool private function isMigrated(): bool
{ {

View File

@ -120,6 +120,8 @@ class MigrateToRules extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -115,6 +115,8 @@ class OtherCurrenciesCorrections extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -99,6 +99,8 @@ class RenameAccountMeta extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -123,6 +123,8 @@ class TransactionIdentifier extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -68,6 +68,9 @@ class TransferCurrenciesCorrections extends Command
* Execute the console command. * Execute the console command.
* *
* @return int * @return int
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function handle(): int public function handle(): int
{ {
@ -134,6 +137,8 @@ class TransferCurrenciesCorrections extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -81,6 +81,8 @@ class UpgradeLiabilities extends Command
/** /**
* @return bool * @return bool
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function isExecuted(): bool private function isExecuted(): bool
{ {

View File

@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands; namespace FireflyIII\Console\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Log; use Log;
use Storage; use Storage;
@ -51,7 +50,7 @@ class VerifySecurityAlerts extends Command
* Execute the console command. * Execute the console command.
* *
* @return int * @return int
* @throws FileNotFoundException * @throws \League\Flysystem\FilesystemException
*/ */
public function handle(): int public function handle(): int
{ {

View File

@ -42,7 +42,6 @@ class Kernel extends ConsoleKernel
{ {
$this->load(__DIR__ . '/Commands'); $this->load(__DIR__ . '/Commands');
/** @noinspection PhpIncludeInspection */
require base_path('routes/console.php'); require base_path('routes/console.php');
} }

View File

@ -30,6 +30,6 @@ namespace FireflyIII\Enums;
class ClauseType class ClauseType
{ {
public const TRANSACTION = 'transaction'; public const TRANSACTION = 'transaction';
public const WHERE = 'where';
public const UPDATE = 'update'; public const UPDATE = 'update';
public const WHERE = 'where';
} }

View File

@ -37,21 +37,16 @@ class AdminRequestedTestMessage extends Event
{ {
use SerializesModels; use SerializesModels;
/** @var string The users IP address */ public User $user;
public $ipAddress;
/** @var User The user */
public $user;
/** /**
* Create a new event instance. * Create a new event instance.
* *
* @param User $user * @param User $user
* @param string $ipAddress
*/ */
public function __construct(User $user, string $ipAddress) public function __construct(User $user)
{ {
Log::debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s) and IP %s!', $user->id, $user->email, $ipAddress)); Log::debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s)', $user->id, $user->email));
$this->user = $user; $this->user = $user;
$this->ipAddress = $ipAddress;
} }
} }

View File

@ -40,10 +40,8 @@ class RequestedReportOnJournals
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
/** @var Collection The transaction groups to report on. */ public Collection $groups;
public $groups; public int $userId;
/** @var int The ID of the user. */
public $userId;
/** /**
* Create a new event instance. * Create a new event instance.

View File

@ -36,14 +36,9 @@ class UserChangedEmail extends Event
{ {
use SerializesModels; use SerializesModels;
/** @var string The user's IP address */ public string $newEmail;
public $ipAddress; public string $oldEmail;
/** @var string The user's new email address */ public User $user;
public $newEmail;
/** @var string The user's old email address */
public $oldEmail;
/** @var User The user itself */
public $user;
/** /**
* UserChangedEmail constructor. * UserChangedEmail constructor.
@ -51,12 +46,10 @@ class UserChangedEmail extends Event
* @param User $user * @param User $user
* @param string $newEmail * @param string $newEmail
* @param string $oldEmail * @param string $oldEmail
* @param string $ipAddress
*/ */
public function __construct(User $user, string $newEmail, string $oldEmail, string $ipAddress) public function __construct(User $user, string $newEmail, string $oldEmail)
{ {
$this->user = $user; $this->user = $user;
$this->ipAddress = $ipAddress;
$this->oldEmail = $oldEmail; $this->oldEmail = $oldEmail;
$this->newEmail = $newEmail; $this->newEmail = $newEmail;
} }

View File

@ -1,8 +1,8 @@
<?php <?php
/* /**
* AttributeHandler.php * DestroyedTransactionGroup.php
* Copyright (c) 2021 james@firefly-iii.org * Copyright (c) 2019 james@firefly-iii.org
* *
* This file is part of Firefly III (https://github.com/firefly-iii). * This file is part of Firefly III (https://github.com/firefly-iii).
* *
@ -22,23 +22,33 @@
declare(strict_types=1); declare(strict_types=1);
namespace FireflyIII\Ldap; namespace FireflyIII\Events;
use FireflyIII\User as DatabaseUser; use FireflyIII\Models\Bill;
use LdapRecord\Models\Entry; use Illuminate\Queue\SerializesModels;
/** /**
* Class AttributeHandler * Class WarnUserAboutBill.
*
* @codeCoverageIgnore
*/ */
class AttributeHandler class WarnUserAboutBill extends Event
{ {
use SerializesModels;
public Bill $bill;
public int $diff;
public string $field;
/** /**
* @param Entry $ldapUser * @param Bill $bill
* @param DatabaseUser $database * @param string $field
* @param int $diff
*/ */
public function handle(Entry $ldapUser, DatabaseUser $database) public function __construct(Bill $bill, string $field, int $diff)
{ {
$database->email = $ldapUser->getFirstAttribute('mail'); $this->bill = $bill;
$database->save(); $this->field = $field;
$this->diff = $diff;
} }
} }

View File

@ -71,9 +71,11 @@ class GracefulNotFoundHandler extends ExceptionHandler
return parent::render($request, $e); return parent::render($request, $e);
case 'accounts.show': case 'accounts.show':
case 'accounts.edit':
case 'accounts.show.all': case 'accounts.show.all':
return $this->handleAccount($request, $e); return $this->handleAccount($request, $e);
case 'transactions.show': case 'transactions.show':
case 'transactions.edit':
return $this->handleGroup($request, $e); return $this->handleGroup($request, $e);
case 'attachments.show': case 'attachments.show':
case 'attachments.edit': case 'attachments.edit':
@ -119,7 +121,6 @@ class GracefulNotFoundHandler extends ExceptionHandler
$request->session()->reflash(); $request->session()->reflash();
return redirect(route('rules.index')); return redirect(route('rules.index'));
case 'transactions.edit':
case 'transactions.mass.edit': case 'transactions.mass.edit':
case 'transactions.mass.delete': case 'transactions.mass.delete':
case 'transactions.bulk.edit': case 'transactions.bulk.edit':

View File

@ -153,6 +153,12 @@ class Handler extends ExceptionHandler
$userData['id'] = auth()->user()->id; $userData['id'] = auth()->user()->id;
$userData['email'] = auth()->user()->email; $userData['email'] = auth()->user()->email;
} }
$headers = [];
if (request()->headers) {
$headers = request()->headers->all();
}
$data = [ $data = [
'class' => get_class($e), 'class' => get_class($e),
'errorMessage' => $e->getMessage(), 'errorMessage' => $e->getMessage(),
@ -165,6 +171,7 @@ class Handler extends ExceptionHandler
'url' => request()->fullUrl(), 'url' => request()->fullUrl(),
'userAgent' => request()->userAgent(), 'userAgent' => request()->userAgent(),
'json' => request()->acceptsJson(), 'json' => request()->acceptsJson(),
'headers' => $headers,
]; ];
// create job that will mail. // create job that will mail.

View File

@ -74,6 +74,7 @@ class AccountFactory
* *
* @return Account * @return Account
* @throws FireflyException * @throws FireflyException
* @throws JsonException
*/ */
public function findOrCreate(string $accountName, string $accountType): Account public function findOrCreate(string $accountName, string $accountType): Account
{ {
@ -182,6 +183,7 @@ class AccountFactory
* @param array $data * @param array $data
* *
* @return Account * @return Account
* @throws FireflyException
* @throws JsonException * @throws JsonException
*/ */
private function createAccount(AccountType $type, array $data): Account private function createAccount(AccountType $type, array $data): Account
@ -361,7 +363,6 @@ class AccountFactory
* @param array $data * @param array $data
* *
* @throws FireflyException * @throws FireflyException
* @throws JsonException
*/ */
private function storeOrder(Account $account, array $data): void private function storeOrder(Account $account, array $data): void
{ {

View File

@ -58,12 +58,10 @@ class AccountMetaFactory
} }
// if $data has field and $entry is not null, update $entry: // if $data has field and $entry is not null, update $entry:
if (null !== $entry) {
$entry->data = $value; $entry->data = $value;
$entry->save(); $entry->save();
Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $value, $account->id, $account->name)); Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $value, $account->id, $account->name));
} }
}
if ('' === $value && null !== $entry) { if ('' === $value && null !== $entry) {
try { try {
$entry->delete(); $entry->delete();

View File

@ -46,6 +46,7 @@ class BillFactory
* *
* @return Bill|null * @return Bill|null
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
*/ */
public function create(array $data): ?Bill public function create(array $data): ?Bill
{ {

View File

@ -43,6 +43,15 @@ class TransactionCurrencyFactory
*/ */
public function create(array $data): TransactionCurrency public function create(array $data): TransactionCurrency
{ {
// if the code already exists (deleted)
// force delete it and then create the transaction:
$count = TransactionCurrency::withTrashed()->whereCode($data['code'])->count();
if (1 === $count) {
$old = TransactionCurrency::withTrashed()->whereCode($data['code'])->first();
$old->forceDelete();
Log::warning(sprintf('Force deleted old currency with ID #%d and code "%s".', $old->id, $data['code']));
}
try { try {
/** @var TransactionCurrency $result */ /** @var TransactionCurrency $result */
$result = TransactionCurrency::create( $result = TransactionCurrency::create(

View File

@ -57,6 +57,7 @@ class TransactionGroupFactory
* @return TransactionGroup * @return TransactionGroup
* @throws DuplicateTransactionException * @throws DuplicateTransactionException
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
*/ */
public function create(array $data): TransactionGroup public function create(array $data): TransactionGroup
{ {

View File

@ -417,6 +417,8 @@ class TransactionJournalFactory
* @param Account $account * @param Account $account
* *
* @return TransactionCurrency * @return TransactionCurrency
* @throws FireflyException
* @throws JsonException
*/ */
private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency
{ {

View File

@ -64,6 +64,16 @@ class MonthReportGenerator implements ReportGeneratorInterface
return $result; return $result;
} }
/**
* Return the preferred period.
*
* @return string
*/
protected function preferredPeriod(): string
{
return 'day';
}
/** /**
* Set accounts. * Set accounts.
* *
@ -155,14 +165,4 @@ class MonthReportGenerator implements ReportGeneratorInterface
{ {
return $this; return $this;
} }
/**
* Return the preferred period.
*
* @return string
*/
protected function preferredPeriod(): string
{
return 'day';
}
} }

View File

@ -49,6 +49,8 @@ class MonthReportGenerator implements ReportGeneratorInterface
* Generates the report. * Generates the report.
* *
* @return string * @return string
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function generate(): string public function generate(): string
@ -90,6 +92,80 @@ class MonthReportGenerator implements ReportGeneratorInterface
return $result; return $result;
} }
/**
* Get the audit report.
*
* @param Account $account
* @param Carbon $date
*
* @return array
* @throws FireflyException
* @throws JsonException
*/
#[ArrayShape(['journals' => "array", 'currency' => "mixed", 'exists' => "bool", 'end' => "string", 'endBalance' => "mixed", 'dayBefore' => "string",
'dayBeforeBalance' => "mixed"])] public function getAuditReport(Account $account, Carbon $date): array
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accountRepository->setUser($account->user);
/** @var JournalRepositoryInterface $journalRepository */
$journalRepository = app(JournalRepositoryInterface::class);
$journalRepository->setUser($account->user);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end)->withAccountInformation()
->withBudgetInformation()->withCategoryInformation()->withBillInformation();
$journals = $collector->getExtractedJournals();
$journals = array_reverse($journals, true);
$dayBeforeBalance = app('steam')->balance($account, $date);
$startBalance = $dayBeforeBalance;
$defaultCurrency = app('amount')->getDefaultCurrencyByUser($account->user);
$currency = $accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
foreach ($journals as $index => $journal) {
$journals[$index]['balance_before'] = $startBalance;
$transactionAmount = $journal['amount'];
// make sure amount is in the right "direction".
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['amount']);
}
if ($currency->id === $journal['foreign_currency_id']) {
$transactionAmount = $journal['foreign_amount'];
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['foreign_amount']);
}
}
$newBalance = bcadd($startBalance, $transactionAmount);
$journals[$index]['balance_after'] = $newBalance;
$startBalance = $newBalance;
// add meta dates for each journal.
$journals[$index]['interest_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'interest_date');
$journals[$index]['book_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'book_date');
$journals[$index]['process_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'process_date');
$journals[$index]['due_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'due_date');
$journals[$index]['payment_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'payment_date');
$journals[$index]['invoice_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'invoice_date');
}
$locale = app('steam')->getLocale();
return [
'journals' => $journals,
'currency' => $currency,
'exists' => !empty($journals),
'end' => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
'endBalance' => app('steam')->balance($account, $this->end),
'dayBefore' => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
'dayBeforeBalance' => $dayBeforeBalance,
];
}
/** /**
* Account collection setter. * Account collection setter.
* *
@ -187,78 +263,4 @@ class MonthReportGenerator implements ReportGeneratorInterface
{ {
return $this; return $this;
} }
/**
* Get the audit report.
*
* @param Account $account
* @param Carbon $date
*
* @return array
* @throws FireflyException
* @throws JsonException
*/
#[ArrayShape(['journals' => "array", 'currency' => "mixed", 'exists' => "bool", 'end' => "string", 'endBalance' => "mixed", 'dayBefore' => "string",
'dayBeforeBalance' => "mixed"])] public function getAuditReport(Account $account, Carbon $date): array
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accountRepository->setUser($account->user);
/** @var JournalRepositoryInterface $journalRepository */
$journalRepository = app(JournalRepositoryInterface::class);
$journalRepository->setUser($account->user);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end)->withAccountInformation()
->withBudgetInformation()->withCategoryInformation()->withBillInformation();
$journals = $collector->getExtractedJournals();
$journals = array_reverse($journals, true);
$dayBeforeBalance = app('steam')->balance($account, $date);
$startBalance = $dayBeforeBalance;
$defaultCurrency = app('amount')->getDefaultCurrencyByUser($account->user);
$currency = $accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
foreach ($journals as $index => $journal) {
$journals[$index]['balance_before'] = $startBalance;
$transactionAmount = $journal['amount'];
// make sure amount is in the right "direction".
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['amount']);
}
if ($currency->id === $journal['foreign_currency_id']) {
$transactionAmount = $journal['foreign_amount'];
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['foreign_amount']);
}
}
$newBalance = bcadd($startBalance, $transactionAmount);
$journals[$index]['balance_after'] = $newBalance;
$startBalance = $newBalance;
// add meta dates for each journal.
$journals[$index]['interest_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'interest_date');
$journals[$index]['book_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'book_date');
$journals[$index]['process_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'process_date');
$journals[$index]['due_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'due_date');
$journals[$index]['payment_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'payment_date');
$journals[$index]['invoice_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'invoice_date');
}
$locale = app('steam')->getLocale();
return [
'journals' => $journals,
'currency' => $currency,
'exists' => !empty($journals),
'end' => $this->end->formatLocalized((string)trans('config.month_and_day', [], $locale)),
'endBalance' => app('steam')->balance($account, $this->end),
'dayBefore' => $date->formatLocalized((string)trans('config.month_and_day', [], $locale)),
'dayBeforeBalance' => $dayBeforeBalance,
];
}
} }

View File

@ -66,38 +66,6 @@ class StandardMessageGenerator implements MessageGeneratorInterface
$this->run(); $this->run();
} }
/**
* @inheritDoc
*/
public function getVersion(): int
{
return $this->version;
}
/**
* @param Collection $objects
*/
public function setObjects(Collection $objects): void
{
$this->objects = $objects;
}
/**
* @param int $trigger
*/
public function setTrigger(int $trigger): void
{
$this->trigger = $trigger;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
/** /**
* @return Collection * @return Collection
*/ */
@ -134,6 +102,8 @@ class StandardMessageGenerator implements MessageGeneratorInterface
/** /**
* @param Webhook $webhook * @param Webhook $webhook
* @param Model $model * @param Model $model
* @throws FireflyException
* @throws \JsonException
*/ */
private function generateMessage(Webhook $webhook, Model $model): void private function generateMessage(Webhook $webhook, Model $model): void
{ {
@ -197,6 +167,14 @@ class StandardMessageGenerator implements MessageGeneratorInterface
$this->storeMessage($webhook, $basicMessage); $this->storeMessage($webhook, $basicMessage);
} }
/**
* @inheritDoc
*/
public function getVersion(): int
{
return $this->version;
}
/** /**
* @param TransactionGroup $transactionGroup * @param TransactionGroup $transactionGroup
* *
@ -220,9 +198,9 @@ class StandardMessageGenerator implements MessageGeneratorInterface
* @param Webhook $webhook * @param Webhook $webhook
* @param array $message * @param array $message
* *
* @return WebhookMessage * @return void
*/ */
private function storeMessage(Webhook $webhook, array $message): WebhookMessage private function storeMessage(Webhook $webhook, array $message): void
{ {
$webhookMessage = new WebhookMessage; $webhookMessage = new WebhookMessage;
$webhookMessage->webhook()->associate($webhook); $webhookMessage->webhook()->associate($webhook);
@ -233,6 +211,29 @@ class StandardMessageGenerator implements MessageGeneratorInterface
$webhookMessage->save(); $webhookMessage->save();
Log::debug(sprintf('Stored new webhook message #%d', $webhookMessage->id)); Log::debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
return $webhookMessage; }
/**
* @param Collection $objects
*/
public function setObjects(Collection $objects): void
{
$this->objects = $objects;
}
/**
* @param int $trigger
*/
public function setTrigger(int $trigger): void
{
$this->trigger = $trigger;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
} }
} }

View File

@ -30,7 +30,6 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
use Laravel\Passport\Events\AccessTokenCreated; use Laravel\Passport\Events\AccessTokenCreated;
use Log; use Log;
use Mail; use Mail;
use Request;
use Session; use Session;
/** /**
@ -59,18 +58,16 @@ class APIEventHandler
$email = config('firefly.site_owner'); $email = config('firefly.site_owner');
} }
$ipAddress = Request::ip();
// see if user has alternative email address: // see if user has alternative email address:
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email'); $pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
if (null !== $pref) { if (null !== $pref) {
$email = (string) (is_array($pref->data) ? $email : $pref->data); $email = (string) (is_array($pref->data) ? $email : $pref->data);
} }
Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s, IP is %s', $email, $ipAddress)); Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s', $email));
try { try {
Log::debug('Trying to send message...'); Log::debug('Trying to send message...');
Mail::to($email)->send(new AccessTokenCreatedMail($email, $ipAddress)); Mail::to($email)->send(new AccessTokenCreatedMail);
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line
Log::debug('Send message failed! :('); Log::debug('Send message failed! :(');

View File

@ -52,7 +52,6 @@ class AdminEventHandler
// is user even admin? // is user even admin?
if ($repository->hasRole($event->user, 'owner')) { if ($repository->hasRole($event->user, 'owner')) {
$email = $event->user->email; $email = $event->user->email;
$ipAddress = $event->ipAddress;
// if user is demo user, send to owner: // if user is demo user, send to owner:
if ($event->user->hasRole('demo')) { if ($event->user->hasRole('demo')) {
@ -65,10 +64,10 @@ class AdminEventHandler
$email = $pref->data; $email = $pref->data;
} }
Log::debug(sprintf('Now in sendTestMessage event handler. Email is %s, IP is %s', $email, $ipAddress)); Log::debug(sprintf('Now in sendTestMessage event handler. Email is %s', $email));
try { try {
Log::debug('Trying to send message...'); Log::debug('Trying to send message...');
Mail::to($email)->send(new AdminTestMail($email, $ipAddress)); Mail::to($email)->send(new AdminTestMail($email));
// Laravel cannot pretend this process failed during testing. // Laravel cannot pretend this process failed during testing.
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line

View File

@ -43,7 +43,6 @@ class AutomationHandler
* @param RequestedReportOnJournals $event * @param RequestedReportOnJournals $event
* *
* @return bool * @return bool
* @throws FireflyException
*/ */
public function reportJournals(RequestedReportOnJournals $event): bool public function reportJournals(RequestedReportOnJournals $event): bool
{ {
@ -58,23 +57,9 @@ class AutomationHandler
$repository = app(UserRepositoryInterface::class); $repository = app(UserRepositoryInterface::class);
$user = $repository->find($event->userId); $user = $repository->find($event->userId);
if (null !== $user && 0 !== $event->groups->count()) { if (null !== $user && 0 !== $event->groups->count()) {
$email = $user->email;
// see if user has alternative email address:
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
if (null !== $pref) {
$email = $pref->data;
}
// if user is demo user, send to owner:
if ($user->hasRole('demo')) {
$email = config('firefly.site_owner');
}
try { try {
Log::debug('Trying to mail...'); Log::debug('Trying to mail...');
Mail::to($user->email)->send(new ReportNewJournalsMail($email, '127.0.0.1', $event->groups)); Mail::to($user->email)->send(new ReportNewJournalsMail($event->groups));
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line
Log::debug('Send message failed! :('); Log::debug('Send message failed! :(');

View File

@ -0,0 +1,64 @@
<?php
/*
* BillEventHandler.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\WarnUserAboutBill;
use FireflyIII\Mail\BillWarningMail;
use Log;
use Mail;
/**
* Class BillEventHandler
*/
class BillEventHandler
{
/**
* @param WarnUserAboutBill $event
* @return void
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function warnAboutBill(WarnUserAboutBill $event): void
{
$bill = $event->bill;
$field = $event->field;
$diff = $event->diff;
$user = $bill->user;
$address = $user->email;
$ipAddress = request()?->ip();
// see if user has alternative email address:
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
if (null !== $pref) {
$address = $pref->data;
}
// send message:
Mail::to($address)->send(new BillWarningMail($bill, $field, $diff, $ipAddress));
Log::debug('warnAboutBill');
}
}

View File

@ -1,57 +0,0 @@
<?php
/*
* LDAPEventHandler.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\User;
use LdapRecord\Laravel\Events\Import\Imported;
use Log;
/**
* Class LDAPEventHandler
*/
class LDAPEventHandler
{
/**
* @param Imported $event
*/
public function importedUser(Imported $event)
{
Log::debug(sprintf('Now in %s', __METHOD__));
/** @var User $user */
$user = $event->eloquent;
$alternative = User::where('email', $user->email)->where('id', '!=', $user->id)->first();
if (null !== $alternative) {
Log::debug(sprintf('User #%d is created but user #%d already exists.', $user->id, $alternative->id));
$alternative->objectguid = $user->objectguid;
$alternative->domain = $user->domain;
$alternative->save();
$user->delete();
auth()->logout();
}
}
}

View File

@ -229,11 +229,10 @@ class UserEventHandler
$newEmail = $event->newEmail; $newEmail = $event->newEmail;
$oldEmail = $event->oldEmail; $oldEmail = $event->oldEmail;
$user = $event->user; $user = $event->user;
$ipAddress = $event->ipAddress;
$token = app('preferences')->getForUser($user, 'email_change_confirm_token', 'invalid'); $token = app('preferences')->getForUser($user, 'email_change_confirm_token', 'invalid');
$uri = route('profile.confirm-email-change', [$token->data]); $uri = route('profile.confirm-email-change', [$token->data]);
try { try {
Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress)); Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri));
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage()); Log::error($e->getMessage());
@ -255,12 +254,11 @@ class UserEventHandler
$newEmail = $event->newEmail; $newEmail = $event->newEmail;
$oldEmail = $event->oldEmail; $oldEmail = $event->oldEmail;
$user = $event->user; $user = $event->user;
$ipAddress = $event->ipAddress;
$token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid'); $token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid');
$hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail)); $hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail));
$uri = route('profile.undo-email-change', [$token->data, $hashed]); $uri = route('profile.undo-email-change', [$token->data, $hashed]);
try { try {
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress)); Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri));
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage()); Log::error($e->getMessage());
@ -334,11 +332,11 @@ class UserEventHandler
/** /**
* @param ActuallyLoggedIn $event * @param ActuallyLoggedIn $event
* @throws FireflyException
*/ */
public function storeUserIPAddress(ActuallyLoggedIn $event): void public function storeUserIPAddress(ActuallyLoggedIn $event): void
{ {
Log::debug('Now in storeUserIPAddress'); Log::debug('Now in storeUserIPAddress');
/** @var User $user */
$user = $event->user; $user = $event->user;
/** @var array $preference */ /** @var array $preference */
try { try {

View File

@ -44,6 +44,8 @@ class VersionCheckEventHandler
* @param RequestedVersionCheckStatus $event * @param RequestedVersionCheckStatus $event
* *
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function checkForUpdates(RequestedVersionCheckStatus $event): void public function checkForUpdates(RequestedVersionCheckStatus $event): void
{ {
@ -90,6 +92,8 @@ class VersionCheckEventHandler
* @param RequestedVersionCheckStatus $event * @param RequestedVersionCheckStatus $event
* *
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
protected function warnToCheckForUpdates(RequestedVersionCheckStatus $event): void protected function warnToCheckForUpdates(RequestedVersionCheckStatus $event): void
{ {

View File

@ -28,7 +28,6 @@ use FireflyIII\Models\Attachment;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\EncryptException; use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -77,15 +76,10 @@ class AttachmentHelper implements AttachmentHelperInterface
*/ */
public function getAttachmentContent(Attachment $attachment): string public function getAttachmentContent(Attachment $attachment): string
{ {
$encryptedData = ''; $encryptedData = (string) $this->uploadDisk->get(sprintf('at-%d.data', $attachment->id));
try {
$encryptedData = $this->uploadDisk->get(sprintf('at-%d.data', $attachment->id));
} catch (FileNotFoundException $e) {
Log::error($e->getMessage());
}
try { try {
$unencryptedData = Crypt::decrypt($encryptedData); // verified $unencryptedData = Crypt::decrypt($encryptedData); // verified
} catch (DecryptException | FileNotFoundException $e) { } catch (DecryptException $e) {
Log::error(sprintf('Could not decrypt data of attachment #%d: %s', $attachment->id, $e->getMessage())); Log::error(sprintf('Could not decrypt data of attachment #%d: %s', $attachment->id, $e->getMessage()));
$unencryptedData = $encryptedData; $unencryptedData = $encryptedData;
} }

View File

@ -86,4 +86,61 @@ trait AmountCollection
return $this; return $this;
} }
/**
* Get transactions with a specific foreign amount.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountIs(string $amount): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($amount) {
$q->whereNotNull('source.foreign_amount');
$q->where('source.foreign_amount', app('steam')->negative($amount));
}
);
return $this;
}
/**
* Get transactions where the amount is less than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountLess(string $amount): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->whereNotNull('destination.foreign_amount');
$q->where('destination.foreign_amount', '<=', app('steam')->positive($amount));
}
);
return $this;
}
/**
* Get transactions where the amount is more than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountMore(string $amount): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->whereNotNull('destination.foreign_amount');
$q->where('destination.foreign_amount', '>=', app('steam')->positive($amount));
}
);
return $this;
}
} }

View File

@ -0,0 +1,306 @@
<?php
/*
* AttachmentCollection.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Helpers\Collector\Extensions;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Log;
/**
* Trait AttachmentCollection
*/
trait AttachmentCollection
{
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameContains(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = str_contains(strtolower($attachment['filename']), strtolower($name)) || str_contains(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface
{
Log::debug('Add filter on attachment ID.');
$this->joinAttachmentTables();
$this->query->whereNotNull('attachments.attachable_id');
return $this;
}
/**
* Join table to get attachment information.
*/
private function joinAttachmentTables(): void
{
if (false === $this->hasJoinedAttTables) {
// join some extra tables:
$this->hasJoinedAttTables = true;
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
->where(
static function (EloquentBuilder $q1) {
$q1->where('attachments.attachable_type', TransactionJournal::class);
//$q1->where('attachments.uploaded', true);
$q1->orWhereNull('attachments.attachable_type');
}
);
}
}
/**
* @inheritDoc
*/
public function withAttachmentInformation(): GroupCollectorInterface
{
$this->fields[] = 'attachments.id as attachment_id';
$this->fields[] = 'attachments.filename as attachment_filename';
$this->fields[] = 'attachments.title as attachment_title';
$this->fields[] = 'attachments.uploaded as attachment_uploaded';
$this->joinAttachmentTables();
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameEnds(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = str_ends_with(strtolower($attachment['filename']), strtolower($name)) || str_ends_with(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameIs(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = $attachment['filename'] === $name || $attachment['title'] === $name;
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameStarts(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = str_starts_with(strtolower($attachment['filename']), strtolower($name)) || str_starts_with(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesAre(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && $notes === $value;
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesContains(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && str_contains(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesEnds(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && str_ends_with(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesStarts(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && str_starts_with(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasNoAttachments(): GroupCollectorInterface
{
Log::debug('Add filter on no attachments.');
$this->joinAttachmentTables();
$this->query->whereNull('attachments.attachable_id');
return $this;
}
}

View File

@ -32,33 +32,20 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
*/ */
trait CollectorProperties trait CollectorProperties
{ {
/** @var array The standard fields to select. */ private array $fields;
private $fields; private bool $hasAccountInfo;
/** @var bool Will be set to true if query result contains account information. (see function withAccountInformation). */ private bool $hasBillInformation;
private $hasAccountInfo; private bool $hasBudgetInformation;
/** @var bool Will be true if query result includes bill information. */ private bool $hasCatInformation;
private $hasBillInformation; private bool $hasJoinedAttTables;
/** @var bool Will be true if query result contains budget info. */
private $hasBudgetInformation;
/** @var bool Will be true if query result contains category info. */
private $hasCatInformation;
/** @var bool Will be true for attachments */
private $hasJoinedAttTables;
private bool $hasJoinedMetaTables; private bool $hasJoinedMetaTables;
/** @var bool Will be true of the query has the tag info tables joined. */ private bool $hasJoinedTagTables;
private $hasJoinedTagTables; private bool $hasNotesInformation;
/** @var bool */ private array $integerFields;
private $hasNotesInformation; private ?int $limit;
/** @var array */ private ?int $page;
private $integerFields; private array $postFilters;
/** @var int The maximum number of results. */ private HasMany $query;
private $limit; private int $total;
/** @var int The page to return. */ private ?User $user;
private $page;
/** @var HasMany The query object. */
private $query;
/** @var int Total number of results. */
private $total;
/** @var User The user object. */
private $user;
} }

View File

@ -38,6 +38,101 @@ use Illuminate\Support\Collection;
*/ */
trait MetaCollection trait MetaCollection
{ {
/**
* @inheritDoc
*/
public function externalIdContains(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* Join table to get tag information.
*/
protected function joinMetaDataTables(): void
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
$this->fields[] = 'journal_meta.name as meta_name';
$this->fields[] = 'journal_meta.data as meta_data';
}
}
/**
* @inheritDoc
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlContains(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', trim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $url));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlEnds(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', ltrim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s', $url));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlStarts(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', rtrim($url, '"'));
//var_dump($url);
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%s%%', $url));
return $this;
}
/** /**
* Where has no tags. * Where has no tags.
@ -52,6 +147,73 @@ trait MetaCollection
return $this; return $this;
} }
/**
* @return GroupCollectorInterface
*/
public function withTagInformation(): GroupCollectorInterface
{
$this->fields[] = 'tags.id as tag_id';
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
$this->joinTagTables();
return $this;
}
/**
* Join table to get tag information.
*/
protected function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
}
}
/**
* @inheritDoc
*/
public function internalReferenceContains(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceEnds(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceStarts(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/** /**
* @param string $value * @param string $value
* *
@ -65,6 +227,29 @@ trait MetaCollection
return $this; return $this;
} }
/**
* @inheritDoc
*/
public function withNotes(): GroupCollectorInterface
{
if (false === $this->hasNotesInformation) {
// join bill table
$this->query->leftJoin(
'notes',
static function (JoinClause $join) {
$join->on('notes.noteable_id', '=', 'transaction_journals.id');
$join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal');
$join->whereNull('notes.deleted_at');
}
);
// add fields
$this->fields[] = 'notes.text as notes';
$this->hasNotesInformation = true;
}
return $this;
}
/** /**
* @param string $value * @param string $value
* *
@ -119,6 +304,25 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/** /**
* Limit the search to a specific set of bills. * Limit the search to a specific set of bills.
* *
@ -149,6 +353,27 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Will include budget ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withBudgetInformation(): GroupCollectorInterface
{
if (false === $this->hasBudgetInformation) {
// join link table
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id');
// add fields
$this->fields[] = 'budgets.id as budget_id';
$this->fields[] = 'budgets.name as budget_name';
$this->hasBudgetInformation = true;
}
return $this;
}
/** /**
* Limit the search to a specific set of budgets. * Limit the search to a specific set of budgets.
* *
@ -183,6 +408,27 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Will include category ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface
{
if (false === $this->hasCatInformation) {
// join link table
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id');
// add fields
$this->fields[] = 'categories.id as category_id';
$this->fields[] = 'categories.name as category_name';
$this->hasCatInformation = true;
}
return $this;
}
/** /**
* Limit the search to a specific category. * Limit the search to a specific category.
* *
@ -203,10 +449,7 @@ trait MetaCollection
*/ */
public function setExternalId(string $externalId): GroupCollectorInterface public function setExternalId(string $externalId): GroupCollectorInterface
{ {
if (false === $this->hasJoinedMetaTables) { $this->joinMetaDataTables();
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_id'); $this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($externalId))); $this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($externalId)));
@ -216,33 +459,11 @@ trait MetaCollection
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function withoutExternalUrl(): GroupCollectorInterface public function setExternalUrl(string $url): GroupCollectorInterface
{ {
if (false === $this->hasJoinedMetaTables) { $this->joinMetaDataTables();
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where(function (Builder $q1) {
$q1->where(function (Builder $q2) {
$q2->where('journal_meta.name', '=', 'external_url');
$q2->whereNull('journal_meta.data');
})->orWhereNull('journal_meta.name');
});
return $this;
}
/**
* @inheritDoc
*/
public function withExternalUrl(): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'external_url'); $this->query->where('journal_meta.name', '=', 'external_url');
$this->query->whereNotNull('journal_meta.data'); $this->query->where('journal_meta.data', '=', json_encode($url));
return $this; return $this;
} }
@ -252,10 +473,7 @@ trait MetaCollection
*/ */
public function setInternalReference(string $internalReference): GroupCollectorInterface public function setInternalReference(string $internalReference): GroupCollectorInterface
{ {
if (false === $this->hasJoinedMetaTables) { $this->joinMetaDataTables();
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where('journal_meta.name', '=', 'internal_reference'); $this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $internalReference)); $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $internalReference));
@ -263,6 +481,18 @@ trait MetaCollection
return $this; return $this;
} }
/**
* @inheritDoc
*/
public function setRecurrenceId(string $recurringId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'recurrence_id');
$this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($recurringId)));
return $this;
}
/** /**
* Limit results to a specific tag. * Limit results to a specific tag.
* *
@ -293,6 +523,35 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Without tags
*
* @param Collection $tags
*
* @return GroupCollectorInterface
*/
public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface
{
$this->withTagInformation();
// this method adds a "postFilter" to the collector.
$list = $tags->pluck('tag')->toArray();
$filter = function (int $index, array $object) use ($list): bool {
foreach ($object['transactions'] as $transaction) {
foreach ($transaction['tags'] as $tag) {
if (in_array($tag['name'], $list)) {
return false;
}
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/** /**
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
@ -305,7 +564,7 @@ trait MetaCollection
} }
/** /**
* Limit results to transactions without a bill.. * Limit results to transactions without a bill.
* *
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
@ -317,25 +576,6 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/** /**
* Limit results to a transactions without a budget.. * Limit results to a transactions without a budget..
* *
@ -349,27 +589,6 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Will include budget ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withBudgetInformation(): GroupCollectorInterface
{
if (false === $this->hasBudgetInformation) {
// join link table
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id');
// add fields
$this->fields[] = 'budgets.id as budget_id';
$this->fields[] = 'budgets.name as budget_name';
$this->hasBudgetInformation = true;
}
return $this;
}
/** /**
* Limit results to a transactions without a category. * Limit results to a transactions without a category.
* *
@ -383,63 +602,14 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Will include category ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface
{
if (false === $this->hasCatInformation) {
// join link table
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id');
// add fields
$this->fields[] = 'categories.id as category_id';
$this->fields[] = 'categories.name as category_name';
$this->hasCatInformation = true;
}
return $this;
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function withNotes(): GroupCollectorInterface public function withExternalUrl(): GroupCollectorInterface
{ {
if (false === $this->hasNotesInformation) { $this->joinMetaDataTables();
// join bill table $this->query->where('journal_meta.name', '=', 'external_url');
$this->query->leftJoin( $this->query->whereNotNull('journal_meta.data');
'notes',
static function (JoinClause $join) {
$join->on('notes.noteable_id', '=', 'transaction_journals.id');
$join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal');
}
);
// add fields
$this->fields[] = 'notes.text as notes';
$this->hasNotesInformation = true;
}
return $this;
}
/**
* @return GroupCollectorInterface
*/
public function withTagInformation(): GroupCollectorInterface
{
$this->fields[] = 'tags.id as tag_id';
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
$this->joinTagTables();
return $this; return $this;
} }
@ -482,6 +652,27 @@ trait MetaCollection
return $this; return $this;
} }
/**
* @inheritDoc
*/
public function withoutExternalUrl(): GroupCollectorInterface
{
$this->joinMetaDataTables();
// TODO not sure if this will work properly.
$this->query->where(function (Builder $q1) {
$q1->where(function (Builder $q2) {
$q2->where('journal_meta.name', '=', 'external_url');
$q2->whereNull('journal_meta.data');
})->orWhere(function (Builder $q3) {
$q3->where('journal_meta.name', '!=', 'external_url');
})->orWhere(function (Builder $q4) {
$q4->whereNull('journal_meta.name');
});
});
return $this;
}
/** /**
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
@ -508,17 +699,4 @@ trait MetaCollection
return $this; return $this;
} }
/**
* Join table to get tag information.
*/
protected function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
}
}
} }

View File

@ -32,6 +32,383 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
*/ */
trait TimeCollection trait TimeCollection
{ {
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayAfter(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '>=', $day);
return $this;
}
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayBefore(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '<=', $day);
return $this;
}
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayIs(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '=', $day);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayAfter(string $day, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $day): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->day >= (int) $day;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @inheritDoc
*/
public function withMetaDate(string $field): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', $field);
$this->query->whereNotNull('journal_meta.data');
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayBefore(string $day, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $day): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->day <= (int) $day;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayIs(string $day, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $day): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return (int) $day === $transaction[$field]->day;
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthAfter(string $month, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $month): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->month >= (int) $month;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthBefore(string $month, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $month): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->month <= (int) $month;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthIs(string $month, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $month): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return (int) $month === $transaction[$field]->month;
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearAfter(string $year, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $year): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->year >= (int) $year;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearBefore(string $year, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $year): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->year <= (int) $year;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearIs(string $year, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $year): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $year === (string) $transaction[$field]->year;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthAfter(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '>=', $month);
return $this;
}
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthBefore(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '<=', $month);
return $this;
}
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthIs(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '=', $month);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayAfter(string $day, string $field): GroupCollectorInterface
{
$this->query->whereDay(sprintf('transaction_journals.%s', $field), '>=', $day);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayBefore(string $day, string $field): GroupCollectorInterface
{
$this->query->whereDay(sprintf('transaction_journals.%s', $field), '<=', $day);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayIs(string $day, string $field): GroupCollectorInterface
{
$this->query->whereDay(sprintf('transaction_journals.%s', $field), '=', $day);
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthAfter(string $month, string $field): GroupCollectorInterface
{
$this->query->whereMonth(sprintf('transaction_journals.%s', $field), '>=', $month);
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthBefore(string $month, string $field): GroupCollectorInterface
{
$this->query->whereMonth(sprintf('transaction_journals.%s', $field), '<=', $month);
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthIs(string $month, string $field): GroupCollectorInterface
{
$this->query->whereMonth(sprintf('transaction_journals.%s', $field), '=', $month);
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearAfter(string $year, string $field): GroupCollectorInterface
{
$this->query->whereYear(sprintf('transaction_journals.%s', $field), '>=', $year);
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearBefore(string $year, string $field): GroupCollectorInterface
{
$this->query->whereYear(sprintf('transaction_journals.%s', $field), '<=', $year);
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearIs(string $year, string $field): GroupCollectorInterface
{
$this->query->whereYear(sprintf('transaction_journals.%s', $field), '=', $year);
return $this;
}
/** /**
* Collect transactions after a specific date. * Collect transactions after a specific date.
@ -80,6 +457,123 @@ trait TimeCollection
return $this; return $this;
} }
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaAfter(Carbon $date, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$date->startOfDay();
$filter = function (int $index, array $object) use ($field, $date): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->gte($date);
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaBefore(Carbon $date, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $date): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->lte($date);
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaDateRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface
{
if ($end < $start) {
[$start, $end] = [$end, $start];
}
$end = clone $end; // this is so weird, but it works if $end and $start secretly point to the same object.
$end->endOfDay();
$start->startOfDay();
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $start, $end): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->gte($start) && $transaction[$field]->lte($end);
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectAfter(Carbon $date, string $field): GroupCollectorInterface
{
$afterStr = $date->format('Y-m-d 00:00:00');
$this->query->where(sprintf('transaction_journals.%s', $field), '>=', $afterStr);
return $this;
}
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectBefore(Carbon $date, string $field): GroupCollectorInterface
{
die('a');
}
/**
* @param Carbon $start
* @param Carbon $end
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface
{
$after = $start->format('Y-m-d 00:00:00');
$before = $end->format('Y-m-d 23:59:59');
$this->query->where(sprintf('transaction_journals.%s', $field), '>=', $after);
$this->query->where(sprintf('transaction_journals.%s', $field), '<=', $before);
return $this;
}
/** /**
* Set the start and end time of the results to return. * Set the start and end time of the results to return.
* *
@ -120,22 +614,9 @@ trait TimeCollection
return $this; return $this;
} }
public function yearIs(string $year): GroupCollectorInterface public function yearAfter(string $year): GroupCollectorInterface
{ {
$this->query->whereYear('transaction_journals.date', '=', $year); $this->query->whereYear('transaction_journals.date', '>=', $year);
return $this;
}
public function monthIs(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '=', $month);
return $this;
}
public function dayIs(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '=', $day);
return $this; return $this;
} }
@ -145,35 +626,10 @@ trait TimeCollection
return $this; return $this;
} }
public function monthBefore(string $month): GroupCollectorInterface public function yearIs(string $year): GroupCollectorInterface
{ {
$this->query->whereMonth('transaction_journals.date', '<=', $month); $this->query->whereYear('transaction_journals.date', '=', $year);
return $this;
}
public function dayBefore(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '<=', $day);
return $this; return $this;
} }
public function yearAfter(string $year): GroupCollectorInterface
{
$this->query->whereYear('transaction_journals.date', '>=', $year);
return $this;
}
public function monthAfter(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '>=', $month);
return $this;
}
public function dayAfter(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '>=', $day);
return $this;
}
} }

View File

@ -25,9 +25,11 @@ namespace FireflyIII\Helpers\Collector;
use Carbon\Carbon; use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException; use Carbon\Exceptions\InvalidDateException;
use Closure;
use Exception; use Exception;
use FireflyIII\Helpers\Collector\Extensions\AccountCollection; use FireflyIII\Helpers\Collector\Extensions\AccountCollection;
use FireflyIII\Helpers\Collector\Extensions\AmountCollection; use FireflyIII\Helpers\Collector\Extensions\AmountCollection;
use FireflyIII\Helpers\Collector\Extensions\AttachmentCollection;
use FireflyIII\Helpers\Collector\Extensions\CollectorProperties; use FireflyIII\Helpers\Collector\Extensions\CollectorProperties;
use FireflyIII\Helpers\Collector\Extensions\MetaCollection; use FireflyIII\Helpers\Collector\Extensions\MetaCollection;
use FireflyIII\Helpers\Collector\Extensions\TimeCollection; use FireflyIII\Helpers\Collector\Extensions\TimeCollection;
@ -48,13 +50,18 @@ use Log;
*/ */
class GroupCollector implements GroupCollectorInterface class GroupCollector implements GroupCollectorInterface
{ {
use CollectorProperties, AccountCollection, AmountCollection, TimeCollection, MetaCollection; use CollectorProperties, AccountCollection, AmountCollection, TimeCollection, MetaCollection, AttachmentCollection;
/** /**
* Group collector constructor. * Group collector constructor.
*/ */
public function __construct() public function __construct()
{ {
$this->postFilters = [];
$this->user = null;
$this->limit = null;
$this->page = null;
$this->hasAccountInfo = false; $this->hasAccountInfo = false;
$this->hasCatInformation = false; $this->hasCatInformation = false;
$this->hasBudgetInformation = false; $this->hasBudgetInformation = false;
@ -198,6 +205,26 @@ class GroupCollector implements GroupCollectorInterface
return $this; return $this;
} }
/**
*
*/
public function dumpQuery(): void
{
echo $this->query->select($this->fields)->toSql();
echo '<pre>';
print_r($this->query->getBindings());
echo '</pre>';
}
/**
*
*/
public function dumpQueryInLogs(): void
{
Log::debug($this->query->select($this->fields)->toSql());
Log::debug('Bindings', $this->query->getBindings());
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@ -241,6 +268,11 @@ class GroupCollector implements GroupCollectorInterface
// now to parse this into an array. // now to parse this into an array.
$collection = $this->parseArray($result); $collection = $this->parseArray($result);
// filter the array using all available post filters:
$collection = $this->postFilterCollection($collection);
// count it and continue:
$this->total = $collection->count(); $this->total = $collection->count();
// now filter the array according to the page and the limit (if necessary) // now filter the array according to the page and the limit (if necessary)
@ -253,6 +285,285 @@ class GroupCollector implements GroupCollectorInterface
return $collection; return $collection;
} }
/**
* @param Collection $collection
*
* @return Collection
*/
private function parseArray(Collection $collection): Collection
{
$groups = [];
/** @var TransactionJournal $augumentedJournal */
foreach ($collection as $augumentedJournal) {
$groupId = $augumentedJournal->transaction_group_id;
if (!array_key_exists($groupId, $groups)) {
// make new array
$parsedGroup = $this->parseAugmentedJournal($augumentedJournal);
$groupArray = [
'id' => (int) $augumentedJournal->transaction_group_id,
'user_id' => (int) $augumentedJournal->user_id,
'title' => $augumentedJournal->transaction_group_title,
'transaction_type' => $parsedGroup['transaction_type_type'],
'count' => 1,
'sums' => [],
'transactions' => [],
];
$journalId = (int) $augumentedJournal->transaction_journal_id;
$groupArray['transactions'][$journalId] = $parsedGroup;
$groups[$groupId] = $groupArray;
continue;
}
// or parse the rest.
$journalId = (int) $augumentedJournal->transaction_journal_id;
if (array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// append data to existing group + journal (for multiple tags or multiple attachments)
$groups[$groupId]['transactions'][$journalId] = $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
$groups[$groupId]['transactions'][$journalId] = $this->mergeAttachments($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
}
if (!array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// create second, third, fourth split:
$groups[$groupId]['count']++;
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
}
}
$groups = $this->parseSums($groups);
return new Collection($groups);
}
/**
* @param TransactionJournal $augumentedJournal
*
* @return array
*/
private function parseAugmentedJournal(TransactionJournal $augumentedJournal): array
{
$result = $augumentedJournal->toArray();
$result['tags'] = [];
$result['attachments'] = [];
$result['interest_date'] = null;
$result['payment_date'] = null;
$result['invoice_date'] = null;
$result['book_date'] = null;
$result['due_date'] = null;
$result['process_date'] = null;
try {
$result['date'] = new Carbon($result['date'], 'UTC');
$result['created_at'] = new Carbon($result['created_at'], 'UTC');
$result['updated_at'] = new Carbon($result['updated_at'], 'UTC');
// this is going to happen a lot:
$result['date']->setTimezone(config('app.timezone'));
$result['created_at']->setTimezone(config('app.timezone'));
$result['updated_at']->setTimezone(config('app.timezone'));
} catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage());
}
// try to process meta date value (if present)
$dates = ['interest_date', 'payment_date', 'invoice_date', 'book_date', 'due_date', 'process_date'];
if (array_key_exists('meta_name', $result) && in_array($result['meta_name'], $dates, true)) {
$name = $result['meta_name'];
if (array_key_exists('meta_data', $result) && '' !== (string) $result['meta_data']) {
$result[$name] = Carbon::createFromFormat('!Y-m-d', substr(json_decode($result['meta_data']), 0, 10));
}
}
// convert values to integers:
$result = $this->convertToInteger($result);
$result['reconciled'] = 1 === (int) $result['reconciled'];
if (array_key_exists('tag_id', $result) && null !== $result['tag_id']) { // assume the other fields are present as well.
$tagId = (int) $augumentedJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($augumentedJournal['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$result['tags'][$tagId] = [
'id' => (int) $result['tag_id'],
'name' => $result['tag_name'],
'date' => $tagDate,
'description' => $result['tag_description'],
];
}
// also merge attachments:
if (array_key_exists('attachment_id', $result)) {
$uploaded = 1 === (int) $result['attachment_uploaded'];
$attachmentId = (int) $augumentedJournal['attachment_id'];
if (0 !== $attachmentId && $uploaded) {
$result['attachments'][$attachmentId] = [
'id' => $attachmentId,
'filename' => $augumentedJournal['attachment_filename'],
'title' => $augumentedJournal['attachment_title'],
];
}
}
// unset various fields:
unset($result['tag_id'], $result['meta_data'], $result['meta_name'],
$result['tag_name'], $result['tag_date'], $result['tag_description'],
$result['tag_latitude'], $result['tag_longitude'], $result['tag_zoom_level'],
$result['attachment_filename'], $result['attachment_id']
);
return $result;
}
/**
* Convert a selected set of fields to arrays.
*
* @param array $array
*
* @return array
*/
private function convertToInteger(array $array): array
{
foreach ($this->integerFields as $field) {
$array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null;
}
return $array;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well.
$tagId = (int) $newJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int) $newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
];
}
return $existingJournal;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('attachment_id', $newArray)) {
$attachmentId = (int) $newJournal['attachment_id'];
$existingJournal['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
return $existingJournal;
}
/**
* @param array $groups
*
* @return array
*/
private function parseSums(array $groups): array
{
/**
* @var int $groudId
* @var array $group
*/
foreach ($groups as $groudId => $group) {
/** @var array $transaction */
foreach ($group['transactions'] as $transaction) {
$currencyId = (int) $transaction['currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount'] ?? '0');
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$currencyId = (int) $transaction['foreign_currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd(
$groups[$groudId]['sums'][$currencyId]['amount'],
$transaction['foreign_amount'] ?? '0'
);
}
}
}
return $groups;
}
/**
* @param Collection $collection
* @return Collection
*/
private function postFilterCollection(Collection $collection): Collection
{
$currentCollection = $collection;
/**
* @var int $i
* @var Closure $function
*/
foreach ($this->postFilters as $function) {
$nextCollection = new Collection;
// loop everything in the current collection
// and save it (or not) in the new collection.
// that new collection is the next current collection
/**
* @var int $index
* @var array $item
*/
foreach ($currentCollection as $ii => $item) {
$result = $function($ii, $item);
if (false === $result) {
// skip other filters, continue to next item.
continue;
}
$nextCollection->push($item);
}
$currentCollection = $nextCollection;
}
return $currentCollection;
}
/** /**
* Same as getGroups but everything is in a paginator. * Same as getGroups but everything is in a paginator.
* *
@ -269,19 +580,21 @@ class GroupCollector implements GroupCollectorInterface
} }
/** /**
* Has attachments * Limit the number of returned entries.
*
* @param int $limit
* *
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
public function hasAttachments(): GroupCollectorInterface public function setLimit(int $limit): GroupCollectorInterface
{ {
Log::debug('Add filter on attachment ID.'); $this->limit = $limit;
$this->joinAttachmentTables(); app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
$this->query->whereNotNull('attachments.attachable_id');
return $this; return $this;
} }
/** /**
* Limit results to a specific currency, either foreign or normal one. * Limit results to a specific currency, either foreign or normal one.
* *
@ -346,21 +659,6 @@ class GroupCollector implements GroupCollectorInterface
return $this; return $this;
} }
/**
* Limit the number of returned entries.
*
* @param int $limit
*
* @return GroupCollectorInterface
*/
public function setLimit(int $limit): GroupCollectorInterface
{
$this->limit = $limit;
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
return $this;
}
/** /**
* Set the page to get. * Set the page to get.
* *
@ -456,56 +754,6 @@ class GroupCollector implements GroupCollectorInterface
return $this; return $this;
} }
/**
* Automatically include all stuff required to make API calls work.
*
* @return GroupCollectorInterface
*/
public function withAPIInformation(): GroupCollectorInterface
{
// include source + destination account name and type.
$this->withAccountInformation()
// include category ID + name (if any)
->withCategoryInformation()
// include budget ID + name (if any)
->withBudgetInformation()
// include bill ID + name (if any)
->withBillInformation();
return $this;
}
/**
* @inheritDoc
*/
public function withAttachmentInformation(): GroupCollectorInterface
{
$this->fields[] = 'attachments.id as attachment_id';
$this->fields[] = 'attachments.uploaded as attachment_uploaded';
$this->joinAttachmentTables();
return $this;
}
/**
* Join table to get attachment information.
*/
private function joinAttachmentTables(): void
{
if (false === $this->hasJoinedAttTables) {
// join some extra tables:
$this->hasJoinedAttTables = true;
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
->where(
static function (EloquentBuilder $q1) {
$q1->where('attachments.attachable_type', TransactionJournal::class);
//$q1->where('attachments.uploaded', true);
$q1->orWhereNull('attachments.attachable_type');
}
);
}
}
/** /**
* Build the query. * Build the query.
*/ */
@ -550,243 +798,21 @@ class GroupCollector implements GroupCollectorInterface
} }
/** /**
* Automatically include all stuff required to make API calls work.
* *
* @return GroupCollectorInterface
*/ */
public function dumpQuery(): void public function withAPIInformation(): GroupCollectorInterface
{ {
echo $this->query->select($this->fields)->toSql(); // include source + destination account name and type.
echo '<pre>'; $this->withAccountInformation()
print_r($this->query->getBindings()); // include category ID + name (if any)
echo '</pre>'; ->withCategoryInformation()
} // include budget ID + name (if any)
->withBudgetInformation()
// include bill ID + name (if any)
->withBillInformation();
/** return $this;
*
*/
public function dumpQueryInLogs(): void
{
Log::debug($this->query->select($this->fields)->toSql());
Log::debug('Bindings', $this->query->getBindings());
}
/**
* Convert a selected set of fields to arrays.
*
* @param array $array
*
* @return array
*/
private function convertToInteger(array $array): array
{
foreach ($this->integerFields as $field) {
$array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null;
}
return $array;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('attachment_id', $newArray)) {
$attachmentId = (int) $newJournal['attachment_id'];
$existingJournal['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
return $existingJournal;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well.
$tagId = (int) $newJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int) $newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
];
}
return $existingJournal;
}
/**
* @param Collection $collection
*
* @return Collection
*/
private function parseArray(Collection $collection): Collection
{
$groups = [];
/** @var TransactionJournal $augumentedJournal */
foreach ($collection as $augumentedJournal) {
$groupId = $augumentedJournal->transaction_group_id;
if (!array_key_exists($groupId, $groups)) {
// make new array
$parsedGroup = $this->parseAugmentedJournal($augumentedJournal);
$groupArray = [
'id' => (int) $augumentedJournal->transaction_group_id,
'user_id' => (int) $augumentedJournal->user_id,
'title' => $augumentedJournal->transaction_group_title,
'transaction_type' => $parsedGroup['transaction_type_type'],
'count' => 1,
'sums' => [],
'transactions' => [],
];
$journalId = (int) $augumentedJournal->transaction_journal_id;
$groupArray['transactions'][$journalId] = $parsedGroup;
$groups[$groupId] = $groupArray;
continue;
}
// or parse the rest.
$journalId = (int) $augumentedJournal->transaction_journal_id;
if (array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// append data to existing group + journal (for multiple tags or multiple attachments)
$groups[$groupId]['transactions'][$journalId] = $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
$groups[$groupId]['transactions'][$journalId] = $this->mergeAttachments($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
}
if (!array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// create second, third, fourth split:
$groups[$groupId]['count']++;
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
}
}
$groups = $this->parseSums($groups);
return new Collection($groups);
}
/**
* @param TransactionJournal $augumentedJournal
*
* @return array
*/
private function parseAugmentedJournal(TransactionJournal $augumentedJournal): array
{
$result = $augumentedJournal->toArray();
$result['tags'] = [];
$result['attachments'] = [];
try {
$result['date'] = new Carbon($result['date'], 'UTC');
$result['created_at'] = new Carbon($result['created_at'], 'UTC');
$result['updated_at'] = new Carbon($result['updated_at'], 'UTC');
// this is going to happen a lot:
$result['date']->setTimezone(config('app.timezone'));
$result['created_at']->setTimezone(config('app.timezone'));
$result['updated_at']->setTimezone(config('app.timezone'));
} catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage());
}
// convert values to integers:
$result = $this->convertToInteger($result);
$result['reconciled'] = 1 === (int) $result['reconciled'];
if (array_key_exists('tag_id', $result) && null !== $result['tag_id']) { // assume the other fields are present as well.
$tagId = (int) $augumentedJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($augumentedJournal['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$result['tags'][$tagId] = [
'id' => (int) $result['tag_id'],
'name' => $result['tag_name'],
'date' => $tagDate,
'description' => $result['tag_description'],
];
}
// also merge attachments:
if (array_key_exists('attachment_id', $result)) {
$uploaded = 1 === (int)$result['attachment_uploaded'];
$attachmentId = (int) $augumentedJournal['attachment_id'];
if (0 !== $attachmentId && $uploaded) {
$result['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
}
return $result;
}
/**
* @param array $groups
*
* @return array
*/
private function parseSums(array $groups): array
{
/**
* @var int $groudId
* @var array $group
*/
foreach ($groups as $groudId => $group) {
/** @var array $transaction */
foreach ($group['transactions'] as $transaction) {
$currencyId = (int) $transaction['currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount'] ?? '0');
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$currencyId = (int) $transaction['foreign_currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd(
$groups[$groudId]['sums'][$currencyId]['amount'],
$transaction['foreign_amount'] ?? '0'
);
}
}
}
return $groups;
} }
} }

View File

@ -58,7 +58,7 @@ interface GroupCollectorInterface
public function amountLess(string $amount): GroupCollectorInterface; public function amountLess(string $amount): GroupCollectorInterface;
/** /**
* Get transactions where the amount is more than. * Get transactions where the foreign amount is more than.
* *
* @param string $amount * @param string $amount
* *
@ -66,6 +66,72 @@ interface GroupCollectorInterface
*/ */
public function amountMore(string $amount): GroupCollectorInterface; public function amountMore(string $amount): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameContains(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameEnds(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameIs(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameStarts(string $name): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesAre(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesContains(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesEnds(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesStarts(string $value): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayAfter(string $day): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayBefore(string $day): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayIs(string $day): GroupCollectorInterface;
/** /**
* End of the description must match: * End of the description must match:
* *
@ -111,6 +177,42 @@ interface GroupCollectorInterface
*/ */
public function excludeSourceAccounts(Collection $accounts): GroupCollectorInterface; public function excludeSourceAccounts(Collection $accounts): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlContains(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlEnds(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlStarts(string $url): GroupCollectorInterface;
/** /**
* Ensure the search will find nothing at all, zero results. * Ensure the search will find nothing at all, zero results.
* *
@ -118,6 +220,33 @@ interface GroupCollectorInterface
*/ */
public function findNothing(): GroupCollectorInterface; public function findNothing(): GroupCollectorInterface;
/**
* Get transactions with a specific foreign amount.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountIs(string $amount): GroupCollectorInterface;
/**
* Get transactions where the amount is less than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountLess(string $amount): GroupCollectorInterface;
/**
* Get transactions where the foreign amount is more than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountMore(string $amount): GroupCollectorInterface;
/** /**
* Return the transaction journals without group information. Is useful in some instances. * Return the transaction journals without group information. Is useful in some instances.
* *
@ -151,6 +280,112 @@ interface GroupCollectorInterface
*/ */
public function hasAttachments(): GroupCollectorInterface; public function hasAttachments(): GroupCollectorInterface;
/**
* Has no attachments
*
* @return GroupCollectorInterface
*/
public function hasNoAttachments(): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayAfter(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayBefore(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayIs(string $day, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthAfter(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthBefore(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthIs(string $month, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearAfter(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearBefore(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearIs(string $year, string $field): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthAfter(string $month): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthBefore(string $month): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthIs(string $month): GroupCollectorInterface;
/** /**
* @param string $value * @param string $value
* *
@ -179,6 +414,69 @@ interface GroupCollectorInterface
*/ */
public function notesStartWith(string $value): GroupCollectorInterface; public function notesStartWith(string $value): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayAfter(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayBefore(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayIs(string $day, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthAfter(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthBefore(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthIs(string $month, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearAfter(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearBefore(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearIs(string $year, string $field): GroupCollectorInterface;
/** /**
* Define which accounts can be part of the source and destination transactions. * Define which accounts can be part of the source and destination transactions.
* *
@ -306,18 +604,10 @@ interface GroupCollectorInterface
public function setExternalId(string $externalId): GroupCollectorInterface; public function setExternalId(string $externalId): GroupCollectorInterface;
/** /**
* Transactions without an external URL * @param string $url
*
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
public function withoutExternalUrl(): GroupCollectorInterface; public function setExternalUrl(string $url): GroupCollectorInterface;
/**
* Transactions with an external URL
*
* @return GroupCollectorInterface
*/
public function withExternalUrl(): GroupCollectorInterface;
/** /**
* Limit results to a specific foreign currency. * Limit results to a specific foreign currency.
@ -364,6 +654,57 @@ interface GroupCollectorInterface
*/ */
public function setLimit(int $limit): GroupCollectorInterface; public function setLimit(int $limit): GroupCollectorInterface;
/**
* Collect transactions after a specific date.
*
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaAfter(Carbon $date, string $field): GroupCollectorInterface;
/**
* Collect transactions before a specific date.
*
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaBefore(Carbon $date, string $field): GroupCollectorInterface;
/**
* Set the start and end time of the results to return, based on meta data.
*
* @param Carbon $start
* @param Carbon $end
* @param string $field
*
* @return GroupCollectorInterface
*/
public function setMetaDateRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface;
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectAfter(Carbon $date, string $field): GroupCollectorInterface;
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectBefore(Carbon $date, string $field): GroupCollectorInterface;
/**
* @param Carbon $start
* @param Carbon $end
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface;
/** /**
* Set the page to get. * Set the page to get.
* *
@ -383,6 +724,15 @@ interface GroupCollectorInterface
*/ */
public function setRange(Carbon $start, Carbon $end): GroupCollectorInterface; public function setRange(Carbon $start, Carbon $end): GroupCollectorInterface;
/**
* Look for specific recurring ID's.
*
* @param string $recurringId
*
* @return GroupCollectorInterface
*/
public function setRecurrenceId(string $recurringId): GroupCollectorInterface;
/** /**
* Search for words in descriptions. * Search for words in descriptions.
* *
@ -455,6 +805,15 @@ interface GroupCollectorInterface
*/ */
public function setUser(User $user): GroupCollectorInterface; public function setUser(User $user): GroupCollectorInterface;
/**
* Only when does not have these tags
*
* @param Collection $tags
*
* @return GroupCollectorInterface
*/
public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface;
/** /**
* Either account can be set, but NOT both. This effectively excludes internal transfers. * Either account can be set, but NOT both. This effectively excludes internal transfers.
* *
@ -534,6 +893,21 @@ interface GroupCollectorInterface
*/ */
public function withCategoryInformation(): GroupCollectorInterface; public function withCategoryInformation(): GroupCollectorInterface;
/**
* Transactions with an external URL
*
* @return GroupCollectorInterface
*/
public function withExternalUrl(): GroupCollectorInterface;
/**
* Transaction must have meta date field X.
*
* @param string $field
* @return GroupCollectorInterface
*/
public function withMetaDate(string $field): GroupCollectorInterface;
/** /**
* Will include notes. * Will include notes.
* *
@ -569,6 +943,13 @@ interface GroupCollectorInterface
*/ */
public function withoutCategory(): GroupCollectorInterface; public function withoutCategory(): GroupCollectorInterface;
/**
* Transactions without an external URL
*
* @return GroupCollectorInterface
*/
public function withoutExternalUrl(): GroupCollectorInterface;
/** /**
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */
@ -579,15 +960,23 @@ interface GroupCollectorInterface
*/ */
public function withoutTags(): GroupCollectorInterface; public function withoutTags(): GroupCollectorInterface;
/**
public function yearIs(string $year): GroupCollectorInterface; * @param string $year
public function monthIs(string $month): GroupCollectorInterface; * @return GroupCollectorInterface
public function dayIs(string $day): GroupCollectorInterface; */
public function yearBefore(string $year): GroupCollectorInterface;
public function monthBefore(string $month): GroupCollectorInterface;
public function dayBefore(string $day): GroupCollectorInterface;
public function yearAfter(string $year): GroupCollectorInterface; public function yearAfter(string $year): GroupCollectorInterface;
public function monthAfter(string $month): GroupCollectorInterface;
public function dayAfter(string $day): GroupCollectorInterface; /**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearBefore(string $year): GroupCollectorInterface;
/**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearIs(string $year): GroupCollectorInterface;
} }

View File

@ -46,6 +46,9 @@ class FiscalHelper implements FiscalHelperInterface
* @param Carbon $date * @param Carbon $date
* *
* @return Carbon date object * @return Carbon date object
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function endOfFiscalYear(Carbon $date): Carbon public function endOfFiscalYear(Carbon $date): Carbon
{ {
@ -69,6 +72,8 @@ class FiscalHelper implements FiscalHelperInterface
* *
* @return Carbon date object * @return Carbon date object
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function startOfFiscalYear(Carbon $date): Carbon public function startOfFiscalYear(Carbon $date): Carbon
{ {

View File

@ -31,7 +31,6 @@ use FireflyIII\Support\CacheProperties;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use JsonException; use JsonException;
use Log;
/** /**
* *
@ -64,6 +63,7 @@ class NetWorth implements NetWorthInterface
* *
* @return array * @return array
* @throws JsonException * @throws JsonException
* @throws \FireflyIII\Exceptions\FireflyException
*/ */
public function getNetWorthByCurrency(Collection $accounts, Carbon $date): array public function getNetWorthByCurrency(Collection $accounts, Carbon $date): array
{ {

View File

@ -141,7 +141,7 @@ class ReportHelper implements ReportHelperInterface
$currentEnd = clone $start; $currentEnd = clone $start;
$currentEnd->endOfMonth(); $currentEnd->endOfMonth();
$months[$year]['months'][] = [ $months[$year]['months'][] = [
'formatted' => $start->formatLocalized('%B %Y'), 'formatted' => $start->isoFormat((string) trans('config.month_js')),
'start' => $start->format('Y-m-d'), 'start' => $start->format('Y-m-d'),
'end' => $currentEnd->format('Y-m-d'), 'end' => $currentEnd->format('Y-m-d'),
'month' => $start->month, 'month' => $start->month,

View File

@ -40,6 +40,8 @@ trait UpdateTrait
* *
* @return array * @return array
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function getLatestRelease(): array public function getLatestRelease(): array
{ {

View File

@ -137,6 +137,8 @@ class CreateController extends Controller
* *
* @return RedirectResponse|Redirector * @return RedirectResponse|Redirector
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function store(AccountFormRequest $request) public function store(AccountFormRequest $request)
{ {

View File

@ -74,6 +74,9 @@ class IndexController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function inactive(Request $request, string $objectType) public function inactive(Request $request, string $objectType)
{ {
@ -128,7 +131,10 @@ class IndexController extends Controller
* @param string $objectType * @param string $objectType
* *
* @return Factory|View * @return Factory|View
* @throws Exception * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function index(Request $request, string $objectType) public function index(Request $request, string $objectType)
{ {

View File

@ -86,7 +86,10 @@ class ReconcileController extends Controller
* @param Carbon|null $end * @param Carbon|null $end
* *
* @return Factory|RedirectResponse|Redirector|View * @return Factory|RedirectResponse|Redirector|View
* @throws Exception * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function reconcile(Account $account, Carbon $start = null, Carbon $end = null) public function reconcile(Account $account, Carbon $start = null, Carbon $end = null)
{ {
@ -237,7 +240,8 @@ class ReconcileController extends Controller
// title: // title:
$description = trans( $description = trans(
'firefly.reconciliation_transaction_title', 'firefly.reconciliation_transaction_title',
['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)] ['from' => $start->isoFormat($this->monthAndDayFormat),
'to' => $end->isoFormat($this->monthAndDayFormat)]
); );
$submission = [ $submission = [
'user' => auth()->user()->id, 'user' => auth()->user()->id,

View File

@ -83,8 +83,10 @@ class ShowController extends Controller
* @param Carbon|null $end * @param Carbon|null $end
* *
* @return RedirectResponse|Redirector|Factory|View * @return RedirectResponse|Redirector|Factory|View
* @throws Exception * @throws \FireflyIII\Exceptions\FireflyException
* * @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null) public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null)
{ {
@ -109,8 +111,8 @@ class ShowController extends Controller
$page = (int) $request->get('page'); $page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data; $pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); $currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$fStart = $start->formatLocalized($this->monthAndDayFormat); $fStart = $start->isoFormat($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat); $fEnd = $end->isoFormat($this->monthAndDayFormat);
$subTitle = (string) trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]); $subTitle = (string) trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$firstTransaction = $this->repository->oldestJournalDate($account) ?? $start; $firstTransaction = $this->repository->oldestJournalDate($account) ?? $start;
@ -164,8 +166,10 @@ class ShowController extends Controller
* @param Account $account * @param Account $account
* *
* @return RedirectResponse|Redirector|Factory|View * @return RedirectResponse|Redirector|Factory|View
* @throws Exception * @throws \FireflyIII\Exceptions\FireflyException
* * @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function showAll(Request $request, Account $account) public function showAll(Request $request, Account $account)
{ {

View File

@ -61,6 +61,8 @@ class ConfigurationController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function index() public function index()
{ {

View File

@ -55,6 +55,8 @@ class HomeController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function index() public function index()
{ {
@ -83,9 +85,8 @@ class HomeController extends Controller
Log::channel('audit')->info('User sends test message.'); Log::channel('audit')->info('User sends test message.');
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$ipAddress = $request->ip(); Log::debug('Now in testMessage() controller.');
Log::debug(sprintf('Now in testMessage() controller. IP is %s', $ipAddress)); event(new AdminRequestedTestMessage($user));
event(new AdminRequestedTestMessage($user, $ipAddress));
session()->flash('info', (string) trans('firefly.send_test_triggered')); session()->flash('info', (string) trans('firefly.send_test_triggered'));
return redirect(route('admin.index')); return redirect(route('admin.index'));

View File

@ -61,6 +61,8 @@ class UpdateController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function index() public function index()
{ {

View File

@ -110,6 +110,8 @@ class ForgotPasswordController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function showLinkRequestForm() public function showLinkRequestForm()
{ {

View File

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth; namespace FireflyIII\Http\Controllers\Auth;
use Adldap;
use Cookie; use Cookie;
use DB; use DB;
use FireflyIII\Events\ActuallyLoggedIn; use FireflyIII\Events\ActuallyLoggedIn;
@ -90,15 +89,6 @@ class LoginController extends Controller
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get($this->username()))); Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get($this->username())));
Log::info('User is trying to login.'); Log::info('User is trying to login.');
$guard = config('auth.defaults.guard');
// if the user logs in using LDAP the field is also changed (per LDAP config)
if ('ldap' === $guard) {
Log::debug('User wishes to login using LDAP.');
$this->username = config('firefly.ldap_auth_field');
}
$this->validateLogin($request); $this->validateLogin($request);
Log::debug('Login data is valid.'); Log::debug('Login data is valid.');
@ -138,6 +128,37 @@ class LoginController extends Controller
$this->sendFailedLoginResponse($request); $this->sendFailedLoginResponse($request);
} }
/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function username()
{
return $this->username;
}
/**
* Get the failed login response instance.
*
* @param Request $request
*
* @return void
*
* @throws ValidationException
*/
protected function sendFailedLoginResponse(Request $request)
{
$exception = ValidationException::withMessages(
[
$this->username() => [trans('auth.failed')],
]
);
$exception->redirectTo = route('login');
throw $exception;
}
/** /**
* Log the user out of the application. * Log the user out of the application.
* *
@ -175,27 +196,6 @@ class LoginController extends Controller
: redirect('/'); : redirect('/');
} }
/**
* Get the failed login response instance.
*
* @param Request $request
*
* @return void
*
* @throws ValidationException
*/
protected function sendFailedLoginResponse(Request $request)
{
$exception = ValidationException::withMessages(
[
$this->username() => [trans('auth.failed')],
]
);
$exception->redirectTo = route('login');
throw $exception;
}
/** /**
* Show the application's login form. * Show the application's login form.
* *
@ -203,6 +203,8 @@ class LoginController extends Controller
* *
* @return Factory|Application|View|Redirector|RedirectResponse * @return Factory|Application|View|Redirector|RedirectResponse
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function showLoginForm(Request $request) public function showLoginForm(Request $request)
{ {
@ -216,17 +218,6 @@ class LoginController extends Controller
return redirect(route('register')); return redirect(route('register'));
} }
// switch to LDAP settings:
if ('ldap' === $guard) {
Log::debug('User wishes to login using LDAP.');
$this->username = config('firefly.ldap_auth_field');
}
// throw warning if still using login_provider
$ldapWarning = false;
if ('ldap' === config('firefly.login_provider')) {
$ldapWarning = true;
}
// is allowed to register, etc. // is allowed to register, etc.
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data; $singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$allowRegistration = true; $allowRegistration = true;
@ -251,16 +242,6 @@ class LoginController extends Controller
} }
$usernameField = $this->username(); $usernameField = $this->username();
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'ldapWarning', 'allowReset', 'title', 'usernameField')); return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title', 'usernameField'));
}
/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function username()
{
return $this->username;
} }
} }

View File

@ -36,6 +36,8 @@ use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\View\View; use Illuminate\View\View;
use Log; use Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/** /**
* Class RegisterController * Class RegisterController
@ -85,18 +87,7 @@ class RegisterController extends Controller
*/ */
public function register(Request $request) public function register(Request $request)
{ {
// is allowed to? $allowRegistration = $this->allowedToRegister();
$allowRegistration = true;
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
$guard = config('auth.defaults.guard');
if (true === $singleUserMode && $userCount > 0 && 'ldap' !== $guard) {
$allowRegistration = false;
}
if ('ldap' === $guard) {
$allowRegistration = false;
}
if (false === $allowRegistration) { if (false === $allowRegistration) {
throw new FireflyException('Registration is currently not available :('); throw new FireflyException('Registration is currently not available :(');
@ -105,7 +96,7 @@ class RegisterController extends Controller
$this->validator($request->all())->validate(); $this->validator($request->all())->validate();
$user = $this->createUser($request->all()); $user = $this->createUser($request->all());
Log::info(sprintf('Registered new user %s', $user->email)); Log::info(sprintf('Registered new user %s', $user->email));
event(new RegisteredUser($user, $request->ip())); event(new RegisteredUser($user));
$this->guard()->login($user); $this->guard()->login($user);
@ -116,34 +107,45 @@ class RegisterController extends Controller
return redirect($this->redirectPath()); return redirect($this->redirectPath());
} }
/**
* @return bool
* @throws FireflyException
*/
protected function allowedToRegister(): bool
{
// is allowed to register?
$allowRegistration = true;
try {
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
$singleUserMode = true;
}
$userCount = User::count();
$guard = config('auth.defaults.guard');
if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) {
$allowRegistration = false;
}
if ('web' !== $guard) {
$allowRegistration = false;
}
return $allowRegistration;
}
/** /**
* Show the application registration form. * Show the application registration form.
* *
* @param Request $request * @param Request $request
* *
* @return Factory|View * @return Factory|View
* @throws ContainerExceptionInterface
* @throws FireflyException * @throws FireflyException
* @throws NotFoundExceptionInterface
*/ */
public function showRegistrationForm(Request $request) public function showRegistrationForm(Request $request)
{ {
$allowRegistration = true;
$isDemoSite = app('fireflyconfig')->get('is_demo_site', config('firefly.configuration.is_demo_site'))->data; $isDemoSite = app('fireflyconfig')->get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
$pageTitle = (string) trans('firefly.register_page_title'); $pageTitle = (string) trans('firefly.register_page_title');
$guard = config('auth.defaults.guard'); $allowRegistration = $this->allowedToRegister();
if (true === $isDemoSite) {
$allowRegistration = false;
}
if (true === $singleUserMode && $userCount > 0 && 'ldap' !== $guard) {
$allowRegistration = false;
}
if ('ldap' === $guard) {
$allowRegistration = false;
}
if (false === $allowRegistration) { if (false === $allowRegistration) {
$message = 'Registration is currently not available.'; $message = 'Registration is currently not available.';
@ -155,5 +157,4 @@ class RegisterController extends Controller
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle')); return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
} }
} }

View File

@ -123,6 +123,8 @@ class ResetPasswordController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function showResetForm(Request $request, $token = null) public function showResetForm(Request $request, $token = null)
{ {

View File

@ -102,6 +102,7 @@ class CreateController extends Controller
public function store(BillStoreRequest $request): RedirectResponse public function store(BillStoreRequest $request): RedirectResponse
{ {
$billData = $request->getBillData(); $billData = $request->getBillData();
$billData['active'] = true; $billData['active'] = true;
try { try {
$bill = $this->repository->store($billData); $bill = $this->repository->store($billData);

View File

@ -98,6 +98,8 @@ class EditController extends Controller
$hasOldInput = null !== $request->old('_token'); $hasOldInput = null !== $request->old('_token');
$preFilled = [ $preFilled = [
'bill_end_date' => $bill->end_date,
'extension_date' => $bill->extension_date,
'notes' => $this->repository->getNoteText($bill), 'notes' => $this->repository->getNoteText($bill),
'transaction_currency_id' => $bill->transaction_currency_id, 'transaction_currency_id' => $bill->transaction_currency_id,
'active' => $hasOldInput ? (bool) $request->old('active') : $bill->active, 'active' => $hasOldInput ? (bool) $request->old('active') : $bill->active,

View File

@ -136,8 +136,9 @@ class IndexController extends Controller
// summarise per currency / per group. // summarise per currency / per group.
$sums = $this->getSums($bills); $sums = $this->getSums($bills);
$totals = $this->getTotals($sums); $totals = $this->getTotals($sums);
$today = now()->startOfDay();
return view('bills.index', compact('bills', 'sums', 'total', 'totals')); return view('bills.index', compact('bills', 'sums', 'total', 'totals', 'today'));
} }
/** /**
@ -145,6 +146,8 @@ class IndexController extends Controller
* *
* @return array * @return array
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
private function getSums(array $bills): array private function getSums(array $bills): array
{ {

View File

@ -123,7 +123,9 @@ class ShowController extends Controller
* @param Bill $bill * @param Bill $bill
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws \FireflyIII\Exceptions\FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function show(Request $request, Bill $bill) public function show(Request $request, Bill $bill)
{ {

View File

@ -171,6 +171,8 @@ class AvailableBudgetController extends Controller
* @param Request $request * @param Request $request
* *
* @return RedirectResponse|Redirector * @return RedirectResponse|Redirector
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function store(Request $request) public function store(Request $request)
{ {

View File

@ -92,6 +92,9 @@ class IndexController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function index(Request $request, Carbon $start = null, Carbon $end = null) public function index(Request $request, Carbon $start = null, Carbon $end = null)
{ {
@ -208,8 +211,8 @@ class IndexController extends Controller
$array['budgeted'][] = [ $array['budgeted'][] = [
'id' => $limit->id, 'id' => $limit->id,
'amount' => number_format((float) $limit->amount, $currency->decimal_places, '.', ''), 'amount' => number_format((float) $limit->amount, $currency->decimal_places, '.', ''),
'start_date' => $limit->start_date->formatLocalized($this->monthAndDayFormat), 'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat),
'end_date' => $limit->end_date->formatLocalized($this->monthAndDayFormat), 'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat),
'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end), 'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),
'currency_id' => $currency->id, 'currency_id' => $currency->id,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,

View File

@ -79,6 +79,9 @@ class ShowController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function noBudget(Request $request, Carbon $start = null, Carbon $end = null) public function noBudget(Request $request, Carbon $start = null, Carbon $end = null)
{ {
@ -88,7 +91,7 @@ class ShowController extends Controller
$end = $end ?? session('end'); $end = $end ?? session('end');
$subTitle = trans( $subTitle = trans(
'firefly.without_budget_between', 'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]
); );
// get first journal ever to set off the budget period overview. // get first journal ever to set off the budget period overview.
@ -115,6 +118,8 @@ class ShowController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function noBudgetAll(Request $request) public function noBudgetAll(Request $request)
{ {
@ -144,6 +149,9 @@ class ShowController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function show(Request $request, Budget $budget) public function show(Request $request, Budget $budget)
{ {
@ -179,6 +187,9 @@ class ShowController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit) public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
{ {
@ -192,8 +203,8 @@ class ShowController extends Controller
'firefly.budget_in_period', 'firefly.budget_in_period',
[ [
'name' => $budget->name, 'name' => $budget->name,
'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat), 'start' => $budgetLimit->start_date->isoFormat($this->monthAndDayFormat),
'end' => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat), 'end' => $budgetLimit->end_date->isoFormat($this->monthAndDayFormat),
'currency' => $budgetLimit->transactionCurrency->name, 'currency' => $budgetLimit->transactionCurrency->name,
] ]
); );

View File

@ -68,6 +68,8 @@ class IndexController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function index(Request $request) public function index(Request $request)
{ {

View File

@ -76,6 +76,9 @@ class NoCategoryController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function show(Request $request, Carbon $start = null, Carbon $end = null) public function show(Request $request, Carbon $start = null, Carbon $end = null)
{ {
@ -88,7 +91,7 @@ class NoCategoryController extends Controller
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data; $pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$subTitle = trans( $subTitle = trans(
'firefly.without_category_between', 'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]
); );
$periods = $this->getNoCategoryPeriodOverview($start); $periods = $this->getNoCategoryPeriodOverview($start);
@ -114,6 +117,8 @@ class NoCategoryController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function showAll(Request $request) public function showAll(Request $request)
{ {

View File

@ -78,6 +78,9 @@ class ShowController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function show(Request $request, Category $category, Carbon $start = null, Carbon $end = null) public function show(Request $request, Category $category, Carbon $start = null, Carbon $end = null)
{ {
@ -94,8 +97,8 @@ class ShowController extends Controller
$path = route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $path = route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$subTitle = trans( $subTitle = trans(
'firefly.journals_in_period_for_category', 'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat), ['name' => $category->name, 'start' => $start->isoFormat($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat),] 'end' => $end->isoFormat($this->monthAndDayFormat),]
); );
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
@ -118,6 +121,8 @@ class ShowController extends Controller
* *
* @return Factory|View * @return Factory|View
* @throws FireflyException * @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function showAll(Request $request, Category $category) public function showAll(Request $request, Category $category)
{ {

View File

@ -50,14 +50,9 @@ class AccountController extends Controller
{ {
use DateCalculation, AugumentData, ChartGeneration; use DateCalculation, AugumentData, ChartGeneration;
/** @var GeneratorInterface Chart generation methods. */ protected GeneratorInterface $generator;
protected $generator; private AccountRepositoryInterface $accountRepository;
private CurrencyRepositoryInterface $currencyRepository;
/** @var AccountRepositoryInterface Account repository. */
private $accountRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepository;
/** /**
* AccountController constructor. * AccountController constructor.
@ -180,6 +175,7 @@ class AccountController extends Controller
* @param Account $account * @param Account $account
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
{ {
@ -197,7 +193,6 @@ class AccountController extends Controller
* @param Carbon $end * @param Carbon $end
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function expenseBudget(Account $account, Carbon $start, Carbon $end): JsonResponse public function expenseBudget(Account $account, Carbon $start, Carbon $end): JsonResponse
{ {
@ -255,6 +250,7 @@ class AccountController extends Controller
* @param Account $account * @param Account $account
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
{ {
@ -272,7 +268,6 @@ class AccountController extends Controller
* @param Carbon $end * @param Carbon $end
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function expenseCategory(Account $account, Carbon $start, Carbon $end): JsonResponse public function expenseCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
{ {
@ -328,6 +323,9 @@ class AccountController extends Controller
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException * @throws FireflyException
* @throws JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/ */
public function frontpage(AccountRepositoryInterface $repository): JsonResponse public function frontpage(AccountRepositoryInterface $repository): JsonResponse
{ {
@ -353,6 +351,7 @@ class AccountController extends Controller
* @param Account $account * @param Account $account
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
{ {
@ -370,7 +369,6 @@ class AccountController extends Controller
* @param Carbon $end * @param Carbon $end
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function incomeCategory(Account $account, Carbon $start, Carbon $end): JsonResponse public function incomeCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
{ {
@ -440,7 +438,7 @@ class AccountController extends Controller
$cache->addProperty($end); $cache->addProperty($end);
$cache->addProperty($account->id); $cache->addProperty($account->id);
if ($cache->has()) { if ($cache->has()) {
return response()->json($cache->get()); //return response()->json($cache->get());
} }
$currencies = $this->accountRepository->getUsedCurrencies($account); $currencies = $this->accountRepository->getUsedCurrencies($account);
@ -487,13 +485,13 @@ class AccountController extends Controller
break; break;
case '1D': case '1D':
// per day the entire period, balance for every day. // per day the entire period, balance for every day.
$format = (string)trans('config.month_and_day', [], $locale); $format = (string) trans('config.month_and_day_js', [], $locale);
$range = app('steam')->balanceInRange($account, $start, $end, $currency); $range = app('steam')->balanceInRange($account, $start, $end, $currency);
$previous = array_values($range)[0]; $previous = array_values($range)[0];
while ($end >= $current) { while ($end >= $current) {
$theDate = $current->format('Y-m-d'); $theDate = $current->format('Y-m-d');
$balance = $range[$theDate] ?? $previous; $balance = $range[$theDate] ?? $previous;
$label = $current->formatLocalized($format); $label = $current->isoFormat($format);
$entries[$label] = (float) $balance; $entries[$label] = (float) $balance;
$previous = $balance; $previous = $balance;
$current->addDay(); $current->addDay();
@ -522,11 +520,13 @@ class AccountController extends Controller
* *
* See reference nr. 55 * See reference nr. 55
* *
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts * @param Collection $accounts
* *
* @param Carbon $start
* @param Carbon $end
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
* @throws JsonException
*/ */
public function report(Collection $accounts, Carbon $start, Carbon $end): JsonResponse public function report(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{ {

View File

@ -58,7 +58,6 @@ class BillController extends Controller
* @param BillRepositoryInterface $repository * @param BillRepositoryInterface $repository
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function frontpage(BillRepositoryInterface $repository): JsonResponse public function frontpage(BillRepositoryInterface $repository): JsonResponse
{ {
@ -104,7 +103,7 @@ class BillController extends Controller
* @param Bill $bill * @param Bill $bill
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException * @throws \FireflyIII\Exceptions\FireflyException
*/ */
public function single(Bill $bill): JsonResponse public function single(Bill $bill): JsonResponse
{ {
@ -145,7 +144,7 @@ class BillController extends Controller
]; ];
foreach ($journals as $journal) { foreach ($journals as $journal) {
$date = $journal['date']->formatLocalized((string)trans('config.month_and_day', [], $locale)); $date = $journal['date']->isoFormat((string) trans('config.month_and_day_js', [], $locale));
$chartData[0]['entries'][$date] = $bill->amount_min; // minimum amount of bill $chartData[0]['entries'][$date] = $bill->amount_min; // minimum amount of bill
$chartData[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill $chartData[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill

View File

@ -85,7 +85,6 @@ class BudgetController extends Controller
* @param Budget $budget * @param Budget $budget
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function budget(Budget $budget): JsonResponse public function budget(Budget $budget): JsonResponse
{ {
@ -154,7 +153,6 @@ class BudgetController extends Controller
* @return JsonResponse * @return JsonResponse
* *
* @throws FireflyException * @throws FireflyException
* @throws JsonException
*/ */
public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit): JsonResponse public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit): JsonResponse
{ {
@ -184,7 +182,7 @@ class BudgetController extends Controller
$expenses = $this->opsRepository->sumExpenses($current, $current, null, $budgetCollection, $currency); $expenses = $this->opsRepository->sumExpenses($current, $current, null, $budgetCollection, $currency);
$spent = $expenses[(int) $currency->id]['sum'] ?? '0'; $spent = $expenses[(int) $currency->id]['sum'] ?? '0';
$amount = bcadd($amount, $spent); $amount = bcadd($amount, $spent);
$format = $start->formatLocalized((string)trans('config.month_and_day', [], $locale)); $format = $start->isoFormat((string) trans('config.month_and_day_js', [], $locale));
$entries[$format] = $amount; $entries[$format] = $amount;
$start->addDay(); $start->addDay();
@ -205,7 +203,6 @@ class BudgetController extends Controller
* @param BudgetLimit|null $budgetLimit * @param BudgetLimit|null $budgetLimit
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
{ {
@ -274,7 +271,6 @@ class BudgetController extends Controller
* @param BudgetLimit|null $budgetLimit * @param BudgetLimit|null $budgetLimit
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
{ {
@ -339,7 +335,6 @@ class BudgetController extends Controller
* @param BudgetLimit|null $budgetLimit * @param BudgetLimit|null $budgetLimit
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
{ {
@ -403,7 +398,6 @@ class BudgetController extends Controller
* Shows a budget list with spent/left/overspent. * Shows a budget list with spent/left/overspent.
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function frontpage(): JsonResponse public function frontpage(): JsonResponse
{ {
@ -441,7 +435,6 @@ class BudgetController extends Controller
* @param Carbon $end * @param Carbon $end
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function period(Budget $budget, TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse public function period(Budget $budget, TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{ {
@ -478,7 +471,7 @@ class BudgetController extends Controller
$currentStart = clone $start; $currentStart = clone $start;
while ($currentStart <= $end) { while ($currentStart <= $end) {
$currentStart = app('navigation')->startOfPeriod($currentStart, $preferredRange); $currentStart = app('navigation')->startOfPeriod($currentStart, $preferredRange);
$title = $currentStart->formatLocalized($titleFormat); $title = $currentStart->isoFormat($titleFormat);
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
// default limit is no limit: // default limit is no limit:
@ -517,7 +510,6 @@ class BudgetController extends Controller
* @param Carbon $end * @param Carbon $end
* *
* @return JsonResponse * @return JsonResponse
* @throws JsonException
*/ */
public function periodNoBudget(TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse public function periodNoBudget(TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{ {
@ -539,7 +531,7 @@ class BudgetController extends Controller
$preferredRange = app('navigation')->preferredRangeFormat($start, $end); $preferredRange = app('navigation')->preferredRangeFormat($start, $end);
while ($currentStart <= $end) { while ($currentStart <= $end) {
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange); $currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
$title = $currentStart->formatLocalized($titleFormat); $title = $currentStart->isoFormat($titleFormat);
$sum = $this->nbRepository->sumExpenses($currentStart, $currentEnd, $accounts, $currency); $sum = $this->nbRepository->sumExpenses($currentStart, $currentEnd, $accounts, $currency);
$amount = app('steam')->positive($sum[$currency->id]['sum'] ?? '0'); $amount = app('steam')->positive($sum[$currency->id]['sum'] ?? '0');
$chartData[$title] = round((float) $amount, $currency->decimal_places); $chartData[$title] = round((float) $amount, $currency->decimal_places);

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