mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Merge branch 'release/5.7.0'
This commit is contained in:
commit
e6854b9265
43
.env.example
43
.env.example
@ -142,7 +142,6 @@ MAIL_ENCRYPTION=null
|
||||
MAILGUN_DOMAIN=
|
||||
MAILGUN_SECRET=
|
||||
|
||||
|
||||
# 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
|
||||
MAILGUN_ENDPOINT=api.mailgun.net
|
||||
@ -176,42 +175,14 @@ MAP_DEFAULT_ZOOM=6
|
||||
#
|
||||
# Firefly III supports a few authentication methods:
|
||||
# - 'web' (default, uses built in DB)
|
||||
# - 'ldap'
|
||||
# - 'remote_user_guard' for Authelia etc
|
||||
# Read more about these settings in the documentation.
|
||||
# https://docs.firefly-iii.org/advanced-installation/authentication
|
||||
#
|
||||
# LDAP is no longer supported :(
|
||||
#
|
||||
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
|
||||
#
|
||||
@ -263,6 +234,13 @@ STATIC_CRON_TOKEN=
|
||||
# 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 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.
|
||||
# Won't significantly speed up things.
|
||||
DKR_CHECK_SQLITE=true
|
||||
@ -291,7 +269,6 @@ DKR_RUN_PASSPORT_INSTALL=true
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
ADLDAP_CONNECTION=default
|
||||
BROADCAST_DRIVER=log
|
||||
QUEUE_DRIVER=sync
|
||||
CACHE_PREFIX=firefly
|
||||
|
@ -70,8 +70,9 @@ class AccountController extends Controller
|
||||
* @param AutocompleteRequest $request
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @throws FireflyException
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function accounts(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
|
@ -35,6 +35,9 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ApiSupport;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use JsonException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class AccountController
|
||||
@ -77,6 +80,9 @@ class AccountController extends Controller
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function overview(DateRequest $request): JsonResponse
|
||||
{
|
||||
|
@ -34,6 +34,8 @@ use Illuminate\Routing\Controller as BaseController;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Serializer\JsonApiSerializer;
|
||||
use Log;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
|
||||
/**
|
||||
@ -74,6 +76,8 @@ abstract class Controller extends BaseController
|
||||
* Method to grab all parameters from the URI.
|
||||
*
|
||||
* @return ParameterBag
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function getParameters(): ParameterBag
|
||||
{
|
||||
@ -94,7 +98,7 @@ abstract class Controller extends BaseController
|
||||
$obj = Carbon::parse($date);
|
||||
} catch (InvalidDateException | InvalidFormatException $e) {
|
||||
// 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);
|
||||
|
@ -209,8 +209,6 @@ class ListController extends Controller
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @codeCoverageIgnore
|
||||
|
@ -33,6 +33,7 @@ use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Transformers\CurrencyTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use JsonException;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use League\Fractal\Resource\Collection as FractalCollection;
|
||||
use League\Fractal\Resource\Item;
|
||||
@ -72,6 +73,7 @@ class ShowController extends Controller
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
@ -106,6 +108,8 @@ class ShowController extends Controller
|
||||
* @param TransactionCurrency $currency
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function show(TransactionCurrency $currency): JsonResponse
|
||||
@ -130,6 +134,8 @@ class ShowController extends Controller
|
||||
* Show a currency.
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function showDefault(): JsonResponse
|
||||
|
@ -33,6 +33,7 @@ use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Transformers\CurrencyTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use JsonException;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
/**
|
||||
@ -74,6 +75,7 @@ class StoreController extends Controller
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function store(StoreRequest $request): JsonResponse
|
||||
{
|
||||
|
@ -75,6 +75,8 @@ class UpdateController extends Controller
|
||||
* @param TransactionCurrency $currency
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function disable(TransactionCurrency $currency): JsonResponse
|
||||
@ -108,6 +110,8 @@ class UpdateController extends Controller
|
||||
* @param TransactionCurrency $currency
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function enable(TransactionCurrency $currency): JsonResponse
|
||||
|
@ -113,7 +113,7 @@ class BasicController extends Controller
|
||||
// give new keys
|
||||
$return = [];
|
||||
foreach ($total as $entry) {
|
||||
if (null === $code || (null !== $code && $code === $entry['currency_code'])) {
|
||||
if (null === $code || ($code === $entry['currency_code'])) {
|
||||
$return[$entry['key']] = $entry;
|
||||
}
|
||||
}
|
||||
@ -150,7 +150,9 @@ class BasicController extends Controller
|
||||
foreach ($set as $transactionJournal) {
|
||||
$currencyId = (int) $transactionJournal['currency_id'];
|
||||
$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] = bcadd($sums[$currencyId], bcmul($transactionJournal['amount'], '-1'));
|
||||
}
|
||||
@ -362,7 +364,7 @@ class BasicController extends Controller
|
||||
function (Account $account) {
|
||||
$includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth');
|
||||
|
||||
return null === $includeNetWorth ? true : '1' === $includeNetWorth;
|
||||
return null === $includeNetWorth || '1' === $includeNetWorth;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -30,6 +30,8 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use FireflyIII\Support\Binder\EitherConfigKey;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Log;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class ConfigurationController
|
||||
@ -94,6 +96,8 @@ class ConfigurationController extends Controller
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
private function getDynamicConfiguration(): array
|
||||
{
|
||||
|
@ -81,7 +81,7 @@ class UserController extends Controller
|
||||
return response()->json([], 500);
|
||||
}
|
||||
|
||||
if ($admin->id !== $user->id && $this->repository->hasRole($admin, 'owner')) {
|
||||
if ($this->repository->hasRole($admin, 'owner')) {
|
||||
$this->repository->destroy($user);
|
||||
|
||||
return response()->json([], 204);
|
||||
|
@ -74,10 +74,24 @@ class MoveTransactionsRequest extends FormRequest
|
||||
// validate start before end only if both are there.
|
||||
$data = $validator->getData();
|
||||
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->setUser(auth()->user());
|
||||
$original = $repository->find((int) $data['original_account']);
|
||||
$destination = $repository->find((int) $data['destination_account']);
|
||||
|
||||
// not the same type:
|
||||
if ($original->accountType->type !== $destination->accountType->type) {
|
||||
$validator->errors()->add('title', (string) trans('validation.same_account_type'));
|
||||
|
||||
@ -86,6 +100,8 @@ class MoveTransactionsRequest extends FormRequest
|
||||
// get currency pref:
|
||||
$originalCurrency = $repository->getAccountCurrency($original);
|
||||
$destinationCurrency = $repository->getAccountCurrency($destination);
|
||||
|
||||
// check different scenario's.
|
||||
if (null === $originalCurrency xor null === $destinationCurrency) {
|
||||
$validator->errors()->add('title', (string) trans('validation.same_account_currency'));
|
||||
|
||||
@ -100,6 +116,3 @@ class MoveTransactionsRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ class GenericRequest extends FormRequest
|
||||
foreach ($array as $billId) {
|
||||
$billId = (int) $billId;
|
||||
$bill = $repository->find($billId);
|
||||
if (null !== $billId) {
|
||||
if (null !== $bill) {
|
||||
$this->bills->push($bill);
|
||||
}
|
||||
}
|
||||
@ -160,7 +160,7 @@ class GenericRequest extends FormRequest
|
||||
foreach ($array as $budgetId) {
|
||||
$budgetId = (int) $budgetId;
|
||||
$budget = $repository->find($budgetId);
|
||||
if (null !== $budgetId) {
|
||||
if (null !== $budget) {
|
||||
$this->budgets->push($budget);
|
||||
}
|
||||
}
|
||||
@ -192,7 +192,7 @@ class GenericRequest extends FormRequest
|
||||
foreach ($array as $categoryId) {
|
||||
$categoryId = (int) $categoryId;
|
||||
$category = $repository->find($categoryId);
|
||||
if (null !== $categoryId) {
|
||||
if (null !== $category) {
|
||||
$this->categories->push($category);
|
||||
}
|
||||
}
|
||||
@ -282,7 +282,7 @@ class GenericRequest extends FormRequest
|
||||
foreach ($array as $tagId) {
|
||||
$tagId = (int) $tagId;
|
||||
$tag = $repository->find($tagId);
|
||||
if (null !== $tagId) {
|
||||
if (null !== $tag) {
|
||||
$this->tags->push($tag);
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ class StoreRequest extends FormRequest
|
||||
'name' => ['name', 'string'],
|
||||
'active' => ['active', 'boolean'],
|
||||
'order' => ['active', 'integer'],
|
||||
'notes' => ['notes', 'string'],
|
||||
|
||||
// auto budget currency:
|
||||
'currency_id' => ['auto_budget_currency_id', 'integer'],
|
||||
@ -74,6 +75,7 @@ class StoreRequest extends FormRequest
|
||||
'active' => [new IsBoolean],
|
||||
'currency_id' => 'exists:transaction_currencies,id',
|
||||
'currency_code' => 'exists:transaction_currencies,code',
|
||||
'notes' => 'nullable|between:1,65536',
|
||||
// auto budget info
|
||||
'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',
|
||||
|
@ -51,6 +51,7 @@ class UpdateRequest extends FormRequest
|
||||
'name' => ['name', 'string'],
|
||||
'active' => ['active', 'boolean'],
|
||||
'order' => ['order', 'integer'],
|
||||
'notes' => ['notes', 'string'],
|
||||
'currency_id' => ['auto_budget_currency_id', 'integer'],
|
||||
'currency_code' => ['auto_budget_currency_code', 'string'],
|
||||
'auto_budget_type' => ['auto_budget_type', 'string'],
|
||||
@ -82,6 +83,7 @@ class UpdateRequest extends FormRequest
|
||||
return [
|
||||
'name' => sprintf('between:1,100|uniqueObjectForUser:budgets,name,%d', $budget->id),
|
||||
'active' => [new IsBoolean],
|
||||
'notes' => 'nullable|between:1,65536',
|
||||
'auto_budget_type' => 'in:reset,rollover,none',
|
||||
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
|
||||
'auto_budget_currency_code' => 'exists:transaction_currencies,code',
|
||||
|
@ -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.
|
||||
*
|
||||
@ -231,19 +246,4 @@ class StoreRequest extends FormRequest
|
||||
$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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ class CorrectOpeningBalanceCurrencies extends Command
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
@ -136,6 +135,8 @@ class CorrectOpeningBalanceCurrencies extends Command
|
||||
* @param Account $account
|
||||
*
|
||||
* @return TransactionCurrency
|
||||
* @throws JsonException
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
private function getCurrency(Account $account): TransactionCurrency
|
||||
{
|
||||
|
@ -1,4 +1,25 @@
|
||||
<?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);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
@ -109,6 +109,8 @@ class DecryptDatabase extends Command
|
||||
* @param string $table
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isDecrypted(string $table): bool
|
||||
{
|
||||
|
@ -27,7 +27,6 @@ use Crypt;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Log;
|
||||
use Storage;
|
||||
|
||||
@ -62,10 +61,9 @@ class ScanAttachments extends Command
|
||||
/** @var Attachment $attachment */
|
||||
foreach ($attachments as $attachment) {
|
||||
$fileName = $attachment->fileName();
|
||||
try {
|
||||
$encryptedContent = $disk->get($fileName);
|
||||
} catch (FileNotFoundException $e) {
|
||||
$this->error(sprintf('Could not find data for attachment #%d: %s', $attachment->id, $e->getMessage()));
|
||||
if (null === $encryptedContent) {
|
||||
Log::error(sprintf('No content for attachment #%d under filename "%s"', $attachment->id, $fileName));
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
|
@ -27,6 +27,7 @@ namespace FireflyIII\Console\Commands\Tools;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Support\Cronjobs\AutoBudgetCronjob;
|
||||
use FireflyIII\Support\Cronjobs\BillWarningCronjob;
|
||||
use FireflyIII\Support\Cronjobs\RecurringCronjob;
|
||||
use Illuminate\Console\Command;
|
||||
use InvalidArgumentException;
|
||||
@ -90,6 +91,17 @@ class Cron extends Command
|
||||
$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.');
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -109,6 +109,8 @@ class AccountCurrencies extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -72,6 +72,8 @@ class AppendBudgetLimitPeriods extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -86,6 +86,8 @@ class BackToJournals extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isMigrated(): bool
|
||||
{
|
||||
@ -97,6 +99,8 @@ class BackToJournals extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -99,6 +99,8 @@ class BudgetLimitCurrency extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -96,6 +96,8 @@ class CCLiabilities extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -77,6 +77,8 @@ class CreateGroupMemberships extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -108,6 +108,8 @@ class MigrateAttachments extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -106,6 +106,8 @@ class MigrateJournalNotes extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -84,6 +84,8 @@ class MigrateRecurrenceMeta extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -77,6 +77,8 @@ class MigrateRecurrenceType extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -75,6 +75,8 @@ class MigrateTagLocations extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -129,6 +129,8 @@ class MigrateToGroups extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isMigrated(): bool
|
||||
{
|
||||
|
@ -120,6 +120,8 @@ class MigrateToRules extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -115,6 +115,8 @@ class OtherCurrenciesCorrections extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -99,6 +99,8 @@ class RenameAccountMeta extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -123,6 +123,8 @@ class TransactionIdentifier extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -68,6 +68,9 @@ class TransferCurrenciesCorrections extends Command
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
@ -134,6 +137,8 @@ class TransferCurrenciesCorrections extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -81,6 +81,8 @@ class UpgradeLiabilities extends Command
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
|
@ -25,7 +25,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Log;
|
||||
use Storage;
|
||||
|
||||
@ -51,7 +50,7 @@ class VerifySecurityAlerts extends Command
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
* @throws FileNotFoundException
|
||||
* @throws \League\Flysystem\FilesystemException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
|
@ -42,7 +42,6 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,6 @@ namespace FireflyIII\Enums;
|
||||
class ClauseType
|
||||
{
|
||||
public const TRANSACTION = 'transaction';
|
||||
public const WHERE = 'where';
|
||||
public const UPDATE = 'update';
|
||||
public const WHERE = 'where';
|
||||
}
|
||||
|
@ -37,21 +37,16 @@ class AdminRequestedTestMessage extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/** @var string The users IP address */
|
||||
public $ipAddress;
|
||||
/** @var User The user */
|
||||
public $user;
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @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->ipAddress = $ipAddress;
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,8 @@ class RequestedReportOnJournals
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/** @var Collection The transaction groups to report on. */
|
||||
public $groups;
|
||||
/** @var int The ID of the user. */
|
||||
public $userId;
|
||||
public Collection $groups;
|
||||
public int $userId;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
|
@ -36,14 +36,9 @@ class UserChangedEmail extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/** @var string The user's IP address */
|
||||
public $ipAddress;
|
||||
/** @var string The user's new email address */
|
||||
public $newEmail;
|
||||
/** @var string The user's old email address */
|
||||
public $oldEmail;
|
||||
/** @var User The user itself */
|
||||
public $user;
|
||||
public string $newEmail;
|
||||
public string $oldEmail;
|
||||
public User $user;
|
||||
|
||||
/**
|
||||
* UserChangedEmail constructor.
|
||||
@ -51,12 +46,10 @@ class UserChangedEmail extends Event
|
||||
* @param User $user
|
||||
* @param string $newEmail
|
||||
* @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->ipAddress = $ipAddress;
|
||||
$this->oldEmail = $oldEmail;
|
||||
$this->newEmail = $newEmail;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AttributeHandler.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
/**
|
||||
* DestroyedTransactionGroup.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@ -22,23 +22,33 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Ldap;
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\User as DatabaseUser;
|
||||
use LdapRecord\Models\Entry;
|
||||
use FireflyIII\Models\Bill;
|
||||
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 DatabaseUser $database
|
||||
* @param Bill $bill
|
||||
* @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');
|
||||
$database->save();
|
||||
$this->bill = $bill;
|
||||
$this->field = $field;
|
||||
$this->diff = $diff;
|
||||
}
|
||||
}
|
@ -71,9 +71,11 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
|
||||
return parent::render($request, $e);
|
||||
case 'accounts.show':
|
||||
case 'accounts.edit':
|
||||
case 'accounts.show.all':
|
||||
return $this->handleAccount($request, $e);
|
||||
case 'transactions.show':
|
||||
case 'transactions.edit':
|
||||
return $this->handleGroup($request, $e);
|
||||
case 'attachments.show':
|
||||
case 'attachments.edit':
|
||||
@ -119,7 +121,6 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
$request->session()->reflash();
|
||||
|
||||
return redirect(route('rules.index'));
|
||||
case 'transactions.edit':
|
||||
case 'transactions.mass.edit':
|
||||
case 'transactions.mass.delete':
|
||||
case 'transactions.bulk.edit':
|
||||
|
@ -153,6 +153,12 @@ class Handler extends ExceptionHandler
|
||||
$userData['id'] = auth()->user()->id;
|
||||
$userData['email'] = auth()->user()->email;
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
if (request()->headers) {
|
||||
$headers = request()->headers->all();
|
||||
}
|
||||
|
||||
$data = [
|
||||
'class' => get_class($e),
|
||||
'errorMessage' => $e->getMessage(),
|
||||
@ -165,6 +171,7 @@ class Handler extends ExceptionHandler
|
||||
'url' => request()->fullUrl(),
|
||||
'userAgent' => request()->userAgent(),
|
||||
'json' => request()->acceptsJson(),
|
||||
'headers' => $headers,
|
||||
];
|
||||
|
||||
// create job that will mail.
|
||||
|
@ -74,6 +74,7 @@ class AccountFactory
|
||||
*
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function findOrCreate(string $accountName, string $accountType): Account
|
||||
{
|
||||
@ -182,6 +183,7 @@ class AccountFactory
|
||||
* @param array $data
|
||||
*
|
||||
* @return Account
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function createAccount(AccountType $type, array $data): Account
|
||||
@ -361,7 +363,6 @@ class AccountFactory
|
||||
* @param array $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function storeOrder(Account $account, array $data): void
|
||||
{
|
||||
|
@ -58,12 +58,10 @@ class AccountMetaFactory
|
||||
}
|
||||
|
||||
// if $data has field and $entry is not null, update $entry:
|
||||
if (null !== $entry) {
|
||||
$entry->data = $value;
|
||||
$entry->save();
|
||||
Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $value, $account->id, $account->name));
|
||||
}
|
||||
}
|
||||
if ('' === $value && null !== $entry) {
|
||||
try {
|
||||
$entry->delete();
|
||||
|
@ -46,6 +46,7 @@ class BillFactory
|
||||
*
|
||||
* @return Bill|null
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function create(array $data): ?Bill
|
||||
{
|
||||
|
@ -43,6 +43,15 @@ class TransactionCurrencyFactory
|
||||
*/
|
||||
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 {
|
||||
/** @var TransactionCurrency $result */
|
||||
$result = TransactionCurrency::create(
|
||||
|
@ -57,6 +57,7 @@ class TransactionGroupFactory
|
||||
* @return TransactionGroup
|
||||
* @throws DuplicateTransactionException
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function create(array $data): TransactionGroup
|
||||
{
|
||||
|
@ -417,6 +417,8 @@ class TransactionJournalFactory
|
||||
* @param Account $account
|
||||
*
|
||||
* @return TransactionCurrency
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
*/
|
||||
private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency
|
||||
{
|
||||
|
@ -64,6 +64,16 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the preferred period.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function preferredPeriod(): string
|
||||
{
|
||||
return 'day';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set accounts.
|
||||
*
|
||||
@ -155,14 +165,4 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the preferred period.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function preferredPeriod(): string
|
||||
{
|
||||
return 'day';
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
* Generates the report.
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function generate(): string
|
||||
@ -90,6 +92,80 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
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.
|
||||
*
|
||||
@ -187,78 +263,4 @@ class MonthReportGenerator implements ReportGeneratorInterface
|
||||
{
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -66,38 +66,6 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$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
|
||||
*/
|
||||
@ -134,6 +102,8 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
/**
|
||||
* @param Webhook $webhook
|
||||
* @param Model $model
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
*/
|
||||
private function generateMessage(Webhook $webhook, Model $model): void
|
||||
{
|
||||
@ -197,6 +167,14 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$this->storeMessage($webhook, $basicMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getVersion(): int
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionGroup $transactionGroup
|
||||
*
|
||||
@ -220,9 +198,9 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
* @param Webhook $webhook
|
||||
* @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->webhook()->associate($webhook);
|
||||
@ -233,6 +211,29 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$webhookMessage->save();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use Laravel\Passport\Events\AccessTokenCreated;
|
||||
use Log;
|
||||
use Mail;
|
||||
use Request;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
@ -59,18 +58,16 @@ class APIEventHandler
|
||||
$email = config('firefly.site_owner');
|
||||
}
|
||||
|
||||
$ipAddress = Request::ip();
|
||||
|
||||
// see if user has alternative email address:
|
||||
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
|
||||
if (null !== $pref) {
|
||||
$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 {
|
||||
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
|
||||
Log::debug('Send message failed! :(');
|
||||
|
@ -52,7 +52,6 @@ class AdminEventHandler
|
||||
// is user even admin?
|
||||
if ($repository->hasRole($event->user, 'owner')) {
|
||||
$email = $event->user->email;
|
||||
$ipAddress = $event->ipAddress;
|
||||
|
||||
// if user is demo user, send to owner:
|
||||
if ($event->user->hasRole('demo')) {
|
||||
@ -65,10 +64,10 @@ class AdminEventHandler
|
||||
$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 {
|
||||
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.
|
||||
} catch (Exception $e) { // @phpstan-ignore-line
|
||||
|
@ -43,7 +43,6 @@ class AutomationHandler
|
||||
* @param RequestedReportOnJournals $event
|
||||
*
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function reportJournals(RequestedReportOnJournals $event): bool
|
||||
{
|
||||
@ -58,23 +57,9 @@ class AutomationHandler
|
||||
$repository = app(UserRepositoryInterface::class);
|
||||
$user = $repository->find($event->userId);
|
||||
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 {
|
||||
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
|
||||
Log::debug('Send message failed! :(');
|
||||
|
64
app/Handlers/Events/BillEventHandler.php
Normal file
64
app/Handlers/Events/BillEventHandler.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -229,11 +229,10 @@ class UserEventHandler
|
||||
$newEmail = $event->newEmail;
|
||||
$oldEmail = $event->oldEmail;
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$token = app('preferences')->getForUser($user, 'email_change_confirm_token', 'invalid');
|
||||
$uri = route('profile.confirm-email-change', [$token->data]);
|
||||
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
|
||||
Log::error($e->getMessage());
|
||||
@ -255,12 +254,11 @@ class UserEventHandler
|
||||
$newEmail = $event->newEmail;
|
||||
$oldEmail = $event->oldEmail;
|
||||
$user = $event->user;
|
||||
$ipAddress = $event->ipAddress;
|
||||
$token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid');
|
||||
$hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail));
|
||||
$uri = route('profile.undo-email-change', [$token->data, $hashed]);
|
||||
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
|
||||
Log::error($e->getMessage());
|
||||
@ -334,11 +332,11 @@ class UserEventHandler
|
||||
|
||||
/**
|
||||
* @param ActuallyLoggedIn $event
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function storeUserIPAddress(ActuallyLoggedIn $event): void
|
||||
{
|
||||
Log::debug('Now in storeUserIPAddress');
|
||||
/** @var User $user */
|
||||
$user = $event->user;
|
||||
/** @var array $preference */
|
||||
try {
|
||||
|
@ -44,6 +44,8 @@ class VersionCheckEventHandler
|
||||
* @param RequestedVersionCheckStatus $event
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function checkForUpdates(RequestedVersionCheckStatus $event): void
|
||||
{
|
||||
@ -90,6 +92,8 @@ class VersionCheckEventHandler
|
||||
* @param RequestedVersionCheckStatus $event
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
protected function warnToCheckForUpdates(RequestedVersionCheckStatus $event): void
|
||||
{
|
||||
|
@ -28,7 +28,6 @@ use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Contracts\Encryption\EncryptException;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -77,15 +76,10 @@ class AttachmentHelper implements AttachmentHelperInterface
|
||||
*/
|
||||
public function getAttachmentContent(Attachment $attachment): string
|
||||
{
|
||||
$encryptedData = '';
|
||||
try {
|
||||
$encryptedData = $this->uploadDisk->get(sprintf('at-%d.data', $attachment->id));
|
||||
} catch (FileNotFoundException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
$encryptedData = (string) $this->uploadDisk->get(sprintf('at-%d.data', $attachment->id));
|
||||
try {
|
||||
$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()));
|
||||
$unencryptedData = $encryptedData;
|
||||
}
|
||||
|
@ -86,4 +86,61 @@ trait AmountCollection
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
306
app/Helpers/Collector/Extensions/AttachmentCollection.php
Normal file
306
app/Helpers/Collector/Extensions/AttachmentCollection.php
Normal 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;
|
||||
}
|
||||
}
|
@ -32,33 +32,20 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
*/
|
||||
trait CollectorProperties
|
||||
{
|
||||
/** @var array The standard fields to select. */
|
||||
private $fields;
|
||||
/** @var bool Will be set to true if query result contains account information. (see function withAccountInformation). */
|
||||
private $hasAccountInfo;
|
||||
/** @var bool Will be true if query result includes bill information. */
|
||||
private $hasBillInformation;
|
||||
/** @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 array $fields;
|
||||
private bool $hasAccountInfo;
|
||||
private bool $hasBillInformation;
|
||||
private bool $hasBudgetInformation;
|
||||
private bool $hasCatInformation;
|
||||
private bool $hasJoinedAttTables;
|
||||
private bool $hasJoinedMetaTables;
|
||||
/** @var bool Will be true of the query has the tag info tables joined. */
|
||||
private $hasJoinedTagTables;
|
||||
/** @var bool */
|
||||
private $hasNotesInformation;
|
||||
/** @var array */
|
||||
private $integerFields;
|
||||
/** @var int The maximum number of results. */
|
||||
private $limit;
|
||||
/** @var int The page to return. */
|
||||
private $page;
|
||||
/** @var HasMany The query object. */
|
||||
private $query;
|
||||
/** @var int Total number of results. */
|
||||
private $total;
|
||||
/** @var User The user object. */
|
||||
private $user;
|
||||
private bool $hasJoinedTagTables;
|
||||
private bool $hasNotesInformation;
|
||||
private array $integerFields;
|
||||
private ?int $limit;
|
||||
private ?int $page;
|
||||
private array $postFilters;
|
||||
private HasMany $query;
|
||||
private int $total;
|
||||
private ?User $user;
|
||||
}
|
||||
|
@ -38,6 +38,101 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
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.
|
||||
@ -52,6 +147,73 @@ trait MetaCollection
|
||||
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
|
||||
*
|
||||
@ -65,6 +227,29 @@ trait MetaCollection
|
||||
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
|
||||
*
|
||||
@ -119,6 +304,25 @@ trait MetaCollection
|
||||
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.
|
||||
*
|
||||
@ -149,6 +353,27 @@ trait MetaCollection
|
||||
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.
|
||||
*
|
||||
@ -183,6 +408,27 @@ trait MetaCollection
|
||||
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.
|
||||
*
|
||||
@ -203,10 +449,7 @@ trait MetaCollection
|
||||
*/
|
||||
public function setExternalId(string $externalId): GroupCollectorInterface
|
||||
{
|
||||
if (false === $this->hasJoinedMetaTables) {
|
||||
$this->hasJoinedMetaTables = true;
|
||||
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
|
||||
}
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_id');
|
||||
$this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($externalId)));
|
||||
|
||||
@ -216,33 +459,11 @@ trait MetaCollection
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function withoutExternalUrl(): GroupCollectorInterface
|
||||
public function setExternalUrl(string $url): GroupCollectorInterface
|
||||
{
|
||||
if (false === $this->hasJoinedMetaTables) {
|
||||
$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->joinMetaDataTables();
|
||||
$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;
|
||||
}
|
||||
@ -252,10 +473,7 @@ trait MetaCollection
|
||||
*/
|
||||
public function setInternalReference(string $internalReference): GroupCollectorInterface
|
||||
{
|
||||
if (false === $this->hasJoinedMetaTables) {
|
||||
$this->hasJoinedMetaTables = true;
|
||||
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
|
||||
}
|
||||
$this->joinMetaDataTables();
|
||||
|
||||
$this->query->where('journal_meta.name', '=', 'internal_reference');
|
||||
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $internalReference));
|
||||
@ -263,6 +481,18 @@ trait MetaCollection
|
||||
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.
|
||||
*
|
||||
@ -293,6 +523,35 @@ trait MetaCollection
|
||||
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
|
||||
*/
|
||||
@ -305,7 +564,7 @@ trait MetaCollection
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit results to transactions without a bill..
|
||||
* Limit results to transactions without a bill.
|
||||
*
|
||||
* @return GroupCollectorInterface
|
||||
*/
|
||||
@ -317,25 +576,6 @@ trait MetaCollection
|
||||
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..
|
||||
*
|
||||
@ -349,27 +589,6 @@ trait MetaCollection
|
||||
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.
|
||||
*
|
||||
@ -383,63 +602,14 @@ trait MetaCollection
|
||||
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
|
||||
*/
|
||||
public function withNotes(): GroupCollectorInterface
|
||||
public function withExternalUrl(): 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');
|
||||
}
|
||||
);
|
||||
// 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();
|
||||
$this->joinMetaDataTables();
|
||||
$this->query->where('journal_meta.name', '=', 'external_url');
|
||||
$this->query->whereNotNull('journal_meta.data');
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -482,6 +652,27 @@ trait MetaCollection
|
||||
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
|
||||
*/
|
||||
@ -508,17 +699,4 @@ trait MetaCollection
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,383 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
*/
|
||||
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.
|
||||
@ -80,6 +457,123 @@ trait TimeCollection
|
||||
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.
|
||||
*
|
||||
@ -120,22 +614,9 @@ trait TimeCollection
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function yearIs(string $year): GroupCollectorInterface
|
||||
public function yearAfter(string $year): GroupCollectorInterface
|
||||
{
|
||||
$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);
|
||||
$this->query->whereYear('transaction_journals.date', '>=', $year);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -145,35 +626,10 @@ trait TimeCollection
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function monthBefore(string $month): GroupCollectorInterface
|
||||
public function yearIs(string $year): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereMonth('transaction_journals.date', '<=', $month);
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function dayBefore(string $day): GroupCollectorInterface
|
||||
{
|
||||
$this->query->whereDay('transaction_journals.date', '<=', $day);
|
||||
$this->query->whereYear('transaction_journals.date', '=', $year);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,11 @@ namespace FireflyIII\Helpers\Collector;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidDateException;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use FireflyIII\Helpers\Collector\Extensions\AccountCollection;
|
||||
use FireflyIII\Helpers\Collector\Extensions\AmountCollection;
|
||||
use FireflyIII\Helpers\Collector\Extensions\AttachmentCollection;
|
||||
use FireflyIII\Helpers\Collector\Extensions\CollectorProperties;
|
||||
use FireflyIII\Helpers\Collector\Extensions\MetaCollection;
|
||||
use FireflyIII\Helpers\Collector\Extensions\TimeCollection;
|
||||
@ -48,13 +50,18 @@ use Log;
|
||||
*/
|
||||
class GroupCollector implements GroupCollectorInterface
|
||||
{
|
||||
use CollectorProperties, AccountCollection, AmountCollection, TimeCollection, MetaCollection;
|
||||
use CollectorProperties, AccountCollection, AmountCollection, TimeCollection, MetaCollection, AttachmentCollection;
|
||||
|
||||
/**
|
||||
* Group collector constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->postFilters = [];
|
||||
$this->user = null;
|
||||
$this->limit = null;
|
||||
$this->page = null;
|
||||
|
||||
$this->hasAccountInfo = false;
|
||||
$this->hasCatInformation = false;
|
||||
$this->hasBudgetInformation = false;
|
||||
@ -198,6 +205,26 @@ class GroupCollector implements GroupCollectorInterface
|
||||
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
|
||||
*/
|
||||
@ -241,6 +268,11 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
// now to parse this into an array.
|
||||
$collection = $this->parseArray($result);
|
||||
|
||||
// filter the array using all available post filters:
|
||||
$collection = $this->postFilterCollection($collection);
|
||||
|
||||
// count it and continue:
|
||||
$this->total = $collection->count();
|
||||
|
||||
// now filter the array according to the page and the limit (if necessary)
|
||||
@ -253,6 +285,285 @@ class GroupCollector implements GroupCollectorInterface
|
||||
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.
|
||||
*
|
||||
@ -269,19 +580,21 @@ class GroupCollector implements GroupCollectorInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Has attachments
|
||||
* Limit the number of returned entries.
|
||||
*
|
||||
* @param int $limit
|
||||
*
|
||||
* @return GroupCollectorInterface
|
||||
*/
|
||||
public function hasAttachments(): GroupCollectorInterface
|
||||
public function setLimit(int $limit): GroupCollectorInterface
|
||||
{
|
||||
Log::debug('Add filter on attachment ID.');
|
||||
$this->joinAttachmentTables();
|
||||
$this->query->whereNotNull('attachments.attachable_id');
|
||||
$this->limit = $limit;
|
||||
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Limit results to a specific currency, either foreign or normal one.
|
||||
*
|
||||
@ -346,21 +659,6 @@ class GroupCollector implements GroupCollectorInterface
|
||||
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.
|
||||
*
|
||||
@ -456,56 +754,6 @@ class GroupCollector implements GroupCollectorInterface
|
||||
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.
|
||||
*/
|
||||
@ -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();
|
||||
echo '<pre>';
|
||||
print_r($this->query->getBindings());
|
||||
echo '</pre>';
|
||||
}
|
||||
// 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();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ interface 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
|
||||
*
|
||||
@ -66,6 +66,72 @@ interface 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:
|
||||
*
|
||||
@ -111,6 +177,42 @@ interface 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.
|
||||
*
|
||||
@ -118,6 +220,33 @@ interface 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.
|
||||
*
|
||||
@ -151,6 +280,112 @@ interface 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
|
||||
*
|
||||
@ -179,6 +414,69 @@ interface 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.
|
||||
*
|
||||
@ -306,18 +604,10 @@ interface GroupCollectorInterface
|
||||
public function setExternalId(string $externalId): GroupCollectorInterface;
|
||||
|
||||
/**
|
||||
* Transactions without an external URL
|
||||
*
|
||||
* @param string $url
|
||||
* @return GroupCollectorInterface
|
||||
*/
|
||||
public function withoutExternalUrl(): GroupCollectorInterface;
|
||||
|
||||
/**
|
||||
* Transactions with an external URL
|
||||
*
|
||||
* @return GroupCollectorInterface
|
||||
*/
|
||||
public function withExternalUrl(): GroupCollectorInterface;
|
||||
public function setExternalUrl(string $url): GroupCollectorInterface;
|
||||
|
||||
/**
|
||||
* Limit results to a specific foreign currency.
|
||||
@ -364,6 +654,57 @@ interface 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.
|
||||
*
|
||||
@ -383,6 +724,15 @@ interface 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.
|
||||
*
|
||||
@ -455,6 +805,15 @@ interface 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.
|
||||
*
|
||||
@ -534,6 +893,21 @@ interface 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.
|
||||
*
|
||||
@ -569,6 +943,13 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function withoutCategory(): GroupCollectorInterface;
|
||||
|
||||
/**
|
||||
* Transactions without an external URL
|
||||
*
|
||||
* @return GroupCollectorInterface
|
||||
*/
|
||||
public function withoutExternalUrl(): GroupCollectorInterface;
|
||||
|
||||
/**
|
||||
* @return GroupCollectorInterface
|
||||
*/
|
||||
@ -579,15 +960,23 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function withoutTags(): GroupCollectorInterface;
|
||||
|
||||
|
||||
public function yearIs(string $year): GroupCollectorInterface;
|
||||
public function monthIs(string $month): 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;
|
||||
/**
|
||||
* @param string $year
|
||||
* @return 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;
|
||||
|
||||
|
||||
}
|
||||
|
@ -46,6 +46,9 @@ class FiscalHelper implements FiscalHelperInterface
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return Carbon date object
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function endOfFiscalYear(Carbon $date): Carbon
|
||||
{
|
||||
@ -69,6 +72,8 @@ class FiscalHelper implements FiscalHelperInterface
|
||||
*
|
||||
* @return Carbon date object
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function startOfFiscalYear(Carbon $date): Carbon
|
||||
{
|
||||
|
@ -31,7 +31,6 @@ use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use JsonException;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -64,6 +63,7 @@ class NetWorth implements NetWorthInterface
|
||||
*
|
||||
* @return array
|
||||
* @throws JsonException
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
public function getNetWorthByCurrency(Collection $accounts, Carbon $date): array
|
||||
{
|
||||
|
@ -141,7 +141,7 @@ class ReportHelper implements ReportHelperInterface
|
||||
$currentEnd = clone $start;
|
||||
$currentEnd->endOfMonth();
|
||||
$months[$year]['months'][] = [
|
||||
'formatted' => $start->formatLocalized('%B %Y'),
|
||||
'formatted' => $start->isoFormat((string) trans('config.month_js')),
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $currentEnd->format('Y-m-d'),
|
||||
'month' => $start->month,
|
||||
|
@ -40,6 +40,8 @@ trait UpdateTrait
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function getLatestRelease(): array
|
||||
{
|
||||
|
@ -137,6 +137,8 @@ class CreateController extends Controller
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function store(AccountFormRequest $request)
|
||||
{
|
||||
|
@ -74,6 +74,9 @@ class IndexController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function inactive(Request $request, string $objectType)
|
||||
{
|
||||
@ -128,7 +131,10 @@ class IndexController extends Controller
|
||||
* @param string $objectType
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws Exception
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function index(Request $request, string $objectType)
|
||||
{
|
||||
|
@ -86,7 +86,10 @@ class ReconcileController extends Controller
|
||||
* @param Carbon|null $end
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
@ -237,7 +240,8 @@ class ReconcileController extends Controller
|
||||
// title:
|
||||
$description = trans(
|
||||
'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 = [
|
||||
'user' => auth()->user()->id,
|
||||
|
@ -83,8 +83,10 @@ class ShowController extends Controller
|
||||
* @param Carbon|null $end
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
@ -109,8 +111,8 @@ class ShowController extends Controller
|
||||
$page = (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
|
||||
$fStart = $start->formatLocalized($this->monthAndDayFormat);
|
||||
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
|
||||
$fStart = $start->isoFormat($this->monthAndDayFormat);
|
||||
$fEnd = $end->isoFormat($this->monthAndDayFormat);
|
||||
$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')]);
|
||||
$firstTransaction = $this->repository->oldestJournalDate($account) ?? $start;
|
||||
@ -164,8 +166,10 @@ class ShowController extends Controller
|
||||
* @param Account $account
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
|
@ -61,6 +61,8 @@ class ConfigurationController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
@ -55,6 +55,8 @@ class HomeController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
@ -83,9 +85,8 @@ class HomeController extends Controller
|
||||
Log::channel('audit')->info('User sends test message.');
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$ipAddress = $request->ip();
|
||||
Log::debug(sprintf('Now in testMessage() controller. IP is %s', $ipAddress));
|
||||
event(new AdminRequestedTestMessage($user, $ipAddress));
|
||||
Log::debug('Now in testMessage() controller.');
|
||||
event(new AdminRequestedTestMessage($user));
|
||||
session()->flash('info', (string) trans('firefly.send_test_triggered'));
|
||||
|
||||
return redirect(route('admin.index'));
|
||||
|
@ -61,6 +61,8 @@ class UpdateController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
@ -110,6 +110,8 @@ class ForgotPasswordController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function showLinkRequestForm()
|
||||
{
|
||||
|
@ -22,7 +22,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Auth;
|
||||
|
||||
use Adldap;
|
||||
use Cookie;
|
||||
use DB;
|
||||
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::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);
|
||||
Log::debug('Login data is valid.');
|
||||
|
||||
@ -138,6 +128,37 @@ class LoginController extends Controller
|
||||
$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.
|
||||
*
|
||||
@ -175,27 +196,6 @@ class LoginController extends Controller
|
||||
: 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.
|
||||
*
|
||||
@ -203,6 +203,8 @@ class LoginController extends Controller
|
||||
*
|
||||
* @return Factory|Application|View|Redirector|RedirectResponse
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function showLoginForm(Request $request)
|
||||
{
|
||||
@ -216,17 +218,6 @@ class LoginController extends Controller
|
||||
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.
|
||||
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
|
||||
$allowRegistration = true;
|
||||
@ -251,16 +242,6 @@ class LoginController extends Controller
|
||||
}
|
||||
$usernameField = $this->username();
|
||||
|
||||
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'ldapWarning', 'allowReset', 'title', 'usernameField'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login username to be used by the controller.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function username()
|
||||
{
|
||||
return $this->username;
|
||||
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title', 'usernameField'));
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ use Illuminate\Routing\Redirector;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
use Log;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
/**
|
||||
* Class RegisterController
|
||||
@ -85,18 +87,7 @@ class RegisterController extends Controller
|
||||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
// is allowed to?
|
||||
$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;
|
||||
}
|
||||
$allowRegistration = $this->allowedToRegister();
|
||||
|
||||
if (false === $allowRegistration) {
|
||||
throw new FireflyException('Registration is currently not available :(');
|
||||
@ -105,7 +96,7 @@ class RegisterController extends Controller
|
||||
$this->validator($request->all())->validate();
|
||||
$user = $this->createUser($request->all());
|
||||
Log::info(sprintf('Registered new user %s', $user->email));
|
||||
event(new RegisteredUser($user, $request->ip()));
|
||||
event(new RegisteredUser($user));
|
||||
|
||||
$this->guard()->login($user);
|
||||
|
||||
@ -116,34 +107,45 @@ class RegisterController extends Controller
|
||||
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.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws FireflyException
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function showRegistrationForm(Request $request)
|
||||
{
|
||||
$allowRegistration = true;
|
||||
$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');
|
||||
$guard = config('auth.defaults.guard');
|
||||
|
||||
if (true === $isDemoSite) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
if (true === $singleUserMode && $userCount > 0 && 'ldap' !== $guard) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
|
||||
if ('ldap' === $guard) {
|
||||
$allowRegistration = false;
|
||||
}
|
||||
$allowRegistration = $this->allowedToRegister();
|
||||
|
||||
if (false === $allowRegistration) {
|
||||
$message = 'Registration is currently not available.';
|
||||
@ -155,5 +157,4 @@ class RegisterController extends Controller
|
||||
|
||||
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -123,6 +123,8 @@ class ResetPasswordController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
|
@ -102,6 +102,7 @@ class CreateController extends Controller
|
||||
public function store(BillStoreRequest $request): RedirectResponse
|
||||
{
|
||||
$billData = $request->getBillData();
|
||||
|
||||
$billData['active'] = true;
|
||||
try {
|
||||
$bill = $this->repository->store($billData);
|
||||
|
@ -98,6 +98,8 @@ class EditController extends Controller
|
||||
$hasOldInput = null !== $request->old('_token');
|
||||
|
||||
$preFilled = [
|
||||
'bill_end_date' => $bill->end_date,
|
||||
'extension_date' => $bill->extension_date,
|
||||
'notes' => $this->repository->getNoteText($bill),
|
||||
'transaction_currency_id' => $bill->transaction_currency_id,
|
||||
'active' => $hasOldInput ? (bool) $request->old('active') : $bill->active,
|
||||
|
@ -136,8 +136,9 @@ class IndexController extends Controller
|
||||
// summarise per currency / per group.
|
||||
$sums = $this->getSums($bills);
|
||||
$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
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
private function getSums(array $bills): array
|
||||
{
|
||||
|
@ -123,7 +123,9 @@ class ShowController extends Controller
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function show(Request $request, Bill $bill)
|
||||
{
|
||||
|
@ -171,6 +171,8 @@ class AvailableBudgetController extends Controller
|
||||
* @param Request $request
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
|
@ -92,6 +92,9 @@ class IndexController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function index(Request $request, Carbon $start = null, Carbon $end = null)
|
||||
{
|
||||
@ -208,8 +211,8 @@ class IndexController extends Controller
|
||||
$array['budgeted'][] = [
|
||||
'id' => $limit->id,
|
||||
'amount' => number_format((float) $limit->amount, $currency->decimal_places, '.', ''),
|
||||
'start_date' => $limit->start_date->formatLocalized($this->monthAndDayFormat),
|
||||
'end_date' => $limit->end_date->formatLocalized($this->monthAndDayFormat),
|
||||
'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat),
|
||||
'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat),
|
||||
'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),
|
||||
'currency_id' => $currency->id,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
|
@ -79,6 +79,9 @@ class ShowController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function noBudget(Request $request, Carbon $start = null, Carbon $end = null)
|
||||
{
|
||||
@ -88,7 +91,7 @@ class ShowController extends Controller
|
||||
$end = $end ?? session('end');
|
||||
$subTitle = trans(
|
||||
'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.
|
||||
@ -115,6 +118,8 @@ class ShowController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function noBudgetAll(Request $request)
|
||||
{
|
||||
@ -144,6 +149,9 @@ class ShowController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function show(Request $request, Budget $budget)
|
||||
{
|
||||
@ -179,6 +187,9 @@ class ShowController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
|
||||
{
|
||||
@ -192,8 +203,8 @@ class ShowController extends Controller
|
||||
'firefly.budget_in_period',
|
||||
[
|
||||
'name' => $budget->name,
|
||||
'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat),
|
||||
'start' => $budgetLimit->start_date->isoFormat($this->monthAndDayFormat),
|
||||
'end' => $budgetLimit->end_date->isoFormat($this->monthAndDayFormat),
|
||||
'currency' => $budgetLimit->transactionCurrency->name,
|
||||
]
|
||||
);
|
||||
|
@ -68,6 +68,8 @@ class IndexController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
@ -76,6 +76,9 @@ class NoCategoryController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
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;
|
||||
$subTitle = trans(
|
||||
'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);
|
||||
|
||||
@ -114,6 +117,8 @@ class NoCategoryController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function showAll(Request $request)
|
||||
{
|
||||
|
@ -78,6 +78,9 @@ class ShowController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @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)
|
||||
{
|
||||
@ -94,8 +97,8 @@ class ShowController extends Controller
|
||||
$path = route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_category',
|
||||
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
|
||||
'end' => $end->formatLocalized($this->monthAndDayFormat),]
|
||||
['name' => $category->name, 'start' => $start->isoFormat($this->monthAndDayFormat),
|
||||
'end' => $end->isoFormat($this->monthAndDayFormat),]
|
||||
);
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
@ -118,6 +121,8 @@ class ShowController extends Controller
|
||||
*
|
||||
* @return Factory|View
|
||||
* @throws FireflyException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function showAll(Request $request, Category $category)
|
||||
{
|
||||
|
@ -50,14 +50,9 @@ class AccountController extends Controller
|
||||
{
|
||||
use DateCalculation, AugumentData, ChartGeneration;
|
||||
|
||||
/** @var GeneratorInterface Chart generation methods. */
|
||||
protected $generator;
|
||||
|
||||
/** @var AccountRepositoryInterface Account repository. */
|
||||
private $accountRepository;
|
||||
|
||||
/** @var CurrencyRepositoryInterface */
|
||||
private $currencyRepository;
|
||||
protected GeneratorInterface $generator;
|
||||
private AccountRepositoryInterface $accountRepository;
|
||||
private CurrencyRepositoryInterface $currencyRepository;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
@ -180,6 +175,7 @@ class AccountController extends Controller
|
||||
* @param Account $account
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
|
||||
{
|
||||
@ -197,7 +193,6 @@ class AccountController extends Controller
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function expenseBudget(Account $account, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
@ -255,6 +250,7 @@ class AccountController extends Controller
|
||||
* @param Account $account
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
|
||||
{
|
||||
@ -272,7 +268,6 @@ class AccountController extends Controller
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function expenseCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
@ -328,6 +323,9 @@ class AccountController extends Controller
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function frontpage(AccountRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
@ -353,6 +351,7 @@ class AccountController extends Controller
|
||||
* @param Account $account
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
|
||||
{
|
||||
@ -370,7 +369,6 @@ class AccountController extends Controller
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function incomeCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
@ -440,7 +438,7 @@ class AccountController extends Controller
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($account->id);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
//return response()->json($cache->get());
|
||||
}
|
||||
$currencies = $this->accountRepository->getUsedCurrencies($account);
|
||||
|
||||
@ -487,13 +485,13 @@ class AccountController extends Controller
|
||||
break;
|
||||
case '1D':
|
||||
// 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);
|
||||
$previous = array_values($range)[0];
|
||||
while ($end >= $current) {
|
||||
$theDate = $current->format('Y-m-d');
|
||||
$balance = $range[$theDate] ?? $previous;
|
||||
$label = $current->formatLocalized($format);
|
||||
$label = $current->isoFormat($format);
|
||||
$entries[$label] = (float) $balance;
|
||||
$previous = $balance;
|
||||
$current->addDay();
|
||||
@ -522,11 +520,13 @@ class AccountController extends Controller
|
||||
*
|
||||
* See reference nr. 55
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection $accounts
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @return JsonResponse
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function report(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
|
||||
{
|
||||
|
@ -58,7 +58,6 @@ class BillController extends Controller
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function frontpage(BillRepositoryInterface $repository): JsonResponse
|
||||
{
|
||||
@ -104,7 +103,7 @@ class BillController extends Controller
|
||||
* @param Bill $bill
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
public function single(Bill $bill): JsonResponse
|
||||
{
|
||||
@ -145,7 +144,7 @@ class BillController extends Controller
|
||||
];
|
||||
|
||||
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[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill
|
||||
|
||||
|
@ -85,7 +85,6 @@ class BudgetController extends Controller
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function budget(Budget $budget): JsonResponse
|
||||
{
|
||||
@ -154,7 +153,6 @@ class BudgetController extends Controller
|
||||
* @return JsonResponse
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws JsonException
|
||||
*/
|
||||
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);
|
||||
$spent = $expenses[(int) $currency->id]['sum'] ?? '0';
|
||||
$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;
|
||||
|
||||
$start->addDay();
|
||||
@ -205,7 +203,6 @@ class BudgetController extends Controller
|
||||
* @param BudgetLimit|null $budgetLimit
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
|
||||
{
|
||||
@ -274,7 +271,6 @@ class BudgetController extends Controller
|
||||
* @param BudgetLimit|null $budgetLimit
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
|
||||
{
|
||||
@ -339,7 +335,6 @@ class BudgetController extends Controller
|
||||
* @param BudgetLimit|null $budgetLimit
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function frontpage(): JsonResponse
|
||||
{
|
||||
@ -441,7 +435,6 @@ class BudgetController extends Controller
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
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;
|
||||
while ($currentStart <= $end) {
|
||||
$currentStart = app('navigation')->startOfPeriod($currentStart, $preferredRange);
|
||||
$title = $currentStart->formatLocalized($titleFormat);
|
||||
$title = $currentStart->isoFormat($titleFormat);
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
|
||||
// default limit is no limit:
|
||||
@ -517,7 +510,6 @@ class BudgetController extends Controller
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return JsonResponse
|
||||
* @throws JsonException
|
||||
*/
|
||||
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);
|
||||
while ($currentStart <= $end) {
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
|
||||
$title = $currentStart->formatLocalized($titleFormat);
|
||||
$title = $currentStart->isoFormat($titleFormat);
|
||||
$sum = $this->nbRepository->sumExpenses($currentStart, $currentEnd, $accounts, $currency);
|
||||
$amount = app('steam')->positive($sum[$currency->id]['sum'] ?? '0');
|
||||
$chartData[$title] = round((float) $amount, $currency->decimal_places);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user