mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-16 10:54:39 +00:00
Compare commits
91 Commits
develop-20
...
develop-20
Author | SHA1 | Date | |
---|---|---|---|
|
9ecb414b02 | ||
|
ad4f908c24 | ||
|
025f739442 | ||
|
6df7354c48 | ||
|
3f77c845ca | ||
|
d4771f7a5c | ||
|
ec4e2bfa4f | ||
|
dfdbfae4b5 | ||
|
349d38b956 | ||
|
2267aa3ac4 | ||
|
2323aa454e | ||
|
8b3317b665 | ||
|
15f893c343 | ||
|
309b3e765e | ||
|
d3fad06e00 | ||
|
834f24c99c | ||
|
35291e1298 | ||
|
ac4e9dcbc5 | ||
|
d57806f2ba | ||
|
3b005c317d | ||
|
e91903fed2 | ||
|
fee2002b0f | ||
|
f12e502eb8 | ||
|
24e62b1cee | ||
|
f559ec73e0 | ||
|
530b501fcf | ||
|
d5ea78025e | ||
|
3413b9b5b5 | ||
|
0b45c1aa76 | ||
|
5718d1690a | ||
|
67b16cc070 | ||
|
5746ac3247 | ||
|
8a2c520b11 | ||
|
f46c14df8c | ||
|
009fbba491 | ||
|
53d84347c2 | ||
|
1961487055 | ||
|
c9ce5df74b | ||
|
1371b6773e | ||
|
b9f1baf150 | ||
|
66b322e844 | ||
|
487b65b669 | ||
|
9078781d61 | ||
|
1ec830521a | ||
|
c4bf2aae7d | ||
|
69ca88d9f8 | ||
|
b38b7b2534 | ||
|
f19bfc3b4b | ||
|
d22f9c09d7 | ||
|
fc2da9eb42 | ||
|
f2c9e20aef | ||
|
16b8ca2746 | ||
|
46ea074821 | ||
|
d2c89781e2 | ||
|
e54d711891 | ||
|
84d3ad4764 | ||
|
b908951a2d | ||
|
8b87deea58 | ||
|
0d7325b3dc | ||
|
a3fd99a498 | ||
|
0ff405d1e0 | ||
|
46a60af966 | ||
|
591c9e3b39 | ||
|
c30461b20b | ||
|
2c3f86d9bc | ||
|
34349e4475 | ||
|
6acd5be5dc | ||
|
55a2b4e789 | ||
|
f41397eb43 | ||
|
41fc1e8f82 | ||
|
bee219ebf7 | ||
|
438f602961 | ||
|
429e72e681 | ||
|
7a134781f2 | ||
|
b572c1dcd3 | ||
|
95593f847b | ||
|
b82fcbd97b | ||
|
daddee7806 | ||
|
930a08ec90 | ||
|
fd2edf3b23 | ||
|
0597255c08 | ||
|
955ab38a85 | ||
|
1311a0db8b | ||
|
0ce9ee6a6c | ||
|
3a339382d4 | ||
|
a5b15bbc16 | ||
|
fbf89fd514 | ||
|
b3223feba2 | ||
|
88a9bc379e | ||
|
b442b91b7c | ||
|
9fadbbe087 |
@@ -22,6 +22,9 @@
|
||||
|
||||
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
echo "Running PHP CS Fixer"
|
||||
$SCRIPT_DIR/phpcs.sh
|
||||
echo "Running PHPStan"
|
||||
$SCRIPT_DIR/phpstan.sh
|
||||
echo "Running PHPMD"
|
||||
$SCRIPT_DIR/phpmd.sh
|
||||
|
12
.ci/php-cs-fixer/composer.lock
generated
12
.ci/php-cs-fixer/composer.lock
generated
@@ -8,16 +8,16 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9"
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -59,7 +59,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.1"
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -75,7 +75,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-10-11T07:11:09+00:00"
|
||||
"time": "2024-03-07T15:38:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
|
12
.ci/phpmd/composer.lock
generated
12
.ci/phpmd/composer.lock
generated
@@ -9,16 +9,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.1.1",
|
||||
"version": "3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9"
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -60,7 +60,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.1"
|
||||
"source": "https://github.com/composer/pcre/tree/3.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -76,7 +76,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-10-11T07:11:09+00:00"
|
||||
"time": "2024-03-07T15:38:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
|
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# code owners for this Firefly III related repository
|
||||
* @JC5 @SDx3
|
11
.github/workflows/lock.yml
vendored
11
.github/workflows/lock.yml
vendored
@@ -5,6 +5,14 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
concurrency:
|
||||
group: lock-threads
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
permissions:
|
||||
@@ -12,8 +20,9 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: JC5/lock-threads@main
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: 7
|
||||
pr-inactive-days: 7
|
||||
log-output: true
|
||||
|
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -115,7 +115,8 @@ jobs:
|
||||
GH_TOKEN: ''
|
||||
- name: Build new JS
|
||||
run: |
|
||||
npm upgrade
|
||||
pwd
|
||||
npm install
|
||||
npm run build
|
||||
- name: Build old JS
|
||||
id: old-js
|
||||
|
9
.github/workflows/sonarcloud.yml
vendored
9
.github/workflows/sonarcloud.yml
vendored
@@ -45,15 +45,6 @@ jobs:
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --prefer-dist --no-interaction --no-progress --no-scripts
|
||||
|
||||
- name: PHPStan
|
||||
run: .ci/phpstan.sh
|
||||
|
||||
- name: PHPMD
|
||||
run: .ci/phpmd.sh
|
||||
|
||||
- name: PHP CS Fixer
|
||||
run: .ci/phpcs.sh
|
||||
|
||||
- name: "Create database file"
|
||||
run: touch storage/database/database.sqlite
|
||||
|
||||
|
47
app/Api/V1/Controllers/Models/Rule/ExpressionController.php
Normal file
47
app/Api/V1/Controllers/Models/Rule/ExpressionController.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/*
|
||||
* ExpressionController.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* 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\Api\V1\Controllers\Models\Rule;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Rule\ValidateExpressionRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* Class ExpressionController
|
||||
*/
|
||||
class ExpressionController extends Controller
|
||||
{
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/rules/validateExpression
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function validateExpression(ValidateExpressionRequest $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'valid' => true,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Requests\Models\Rule;
|
||||
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetRuleConfiguration;
|
||||
@@ -57,7 +58,6 @@ class StoreRequest extends FormRequest
|
||||
'active' => ['active', 'boolean'],
|
||||
];
|
||||
$data = $this->getAllData($fields);
|
||||
|
||||
$data['triggers'] = $this->getRuleTriggers();
|
||||
$data['actions'] = $this->getRuleActions();
|
||||
|
||||
@@ -123,7 +123,7 @@ class StoreRequest extends FormRequest
|
||||
'triggers.*.stop_processing' => [new IsBoolean()],
|
||||
'triggers.*.active' => [new IsBoolean()],
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
'actions.*.value' => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue',
|
||||
'actions.*.value' => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
|
||||
'actions.*.stop_processing' => [new IsBoolean()],
|
||||
'actions.*.active' => [new IsBoolean()],
|
||||
'strict' => [new IsBoolean()],
|
||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Rule;
|
||||
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetRuleConfiguration;
|
||||
@@ -140,7 +141,7 @@ class UpdateRequest extends FormRequest
|
||||
'triggers.*.stop_processing' => [new IsBoolean()],
|
||||
'triggers.*.active' => [new IsBoolean()],
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
'actions.*.value' => 'required_if:actions.*.type,'.$contextActions.'|ruleActionValue',
|
||||
'actions.*.value' => [sprintf('required_if:actions.*.type,%s', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
|
||||
'actions.*.stop_processing' => [new IsBoolean()],
|
||||
'actions.*.active' => [new IsBoolean()],
|
||||
'strict' => [new IsBoolean()],
|
||||
|
@@ -1,6 +1,8 @@
|
||||
/*!
|
||||
* app.scss
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ValidateExpressionRequest.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -18,13 +20,23 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* TODO REMOVE ME */
|
||||
// Variables
|
||||
//@import "variables";
|
||||
declare(strict_types=1);
|
||||
|
||||
// Bootstrap
|
||||
//@import "~bootstrap-sass/assets/stylesheets/bootstrap";
|
||||
namespace FireflyIII\Api\V1\Requests\Models\Rule;
|
||||
|
||||
// Font awesome
|
||||
//@import "~font-awesome/css/font-awesome";
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Class ValidateExpressionRequest
|
||||
*/
|
||||
class ValidateExpressionRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return ['expression' => ['required', new IsValidActionExpression()]];
|
||||
}
|
||||
}
|
@@ -158,7 +158,8 @@ class Controller extends BaseController
|
||||
|
||||
// the transformer, at this point, needs to collect information that ALL items in the collection
|
||||
// require, like meta-data and stuff like that, and save it for later.
|
||||
$transformer->collectMetaData($objects);
|
||||
$objects = $transformer->collectMetaData($objects);
|
||||
$paginator->setCollection($objects);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
104
app/Api/V2/Controllers/Model/Account/IndexController.php
Normal file
104
app/Api/V2/Controllers/Model/Account/IndexController.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/*
|
||||
* IndexController.php
|
||||
* Copyright (c) 2024 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\Api\V2\Controllers\Model\Account;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Account\IndexRequest;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\InfiniteListRequest;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
public const string RESOURCE_KEY = 'accounts';
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO see autocomplete/accountcontroller for list.
|
||||
*/
|
||||
public function index(IndexRequest $request): JsonResponse
|
||||
{
|
||||
$this->repository->resetAccountOrder();
|
||||
$types = $request->getAccountTypes();
|
||||
$instructions = $request->getSortInstructions('accounts');
|
||||
$accounts = $this->repository->getAccountsByType($types, $instructions);
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$count = $accounts->count();
|
||||
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
||||
$transformer = new AccountTransformer();
|
||||
|
||||
$this->parameters->set('sort', $instructions);
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList('accounts', $paginator, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function infiniteList(InfiniteListRequest $request): JsonResponse
|
||||
{
|
||||
$this->repository->resetAccountOrder();
|
||||
|
||||
// get accounts of the specified type, and return.
|
||||
$types = $request->getAccountTypes();
|
||||
|
||||
// get from repository
|
||||
$accounts = $this->repository->getAccountsInOrder($types, $request->getSortInstructions('accounts'), $request->getStartRow(), $request->getEndRow());
|
||||
$total = $this->repository->countAccounts($types);
|
||||
$count = $request->getEndRow() - $request->getStartRow();
|
||||
$paginator = new LengthAwarePaginator($accounts, $total, $count, $this->parameters->get('page'));
|
||||
$transformer = new AccountTransformer();
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
}
|
@@ -35,6 +35,47 @@ use Illuminate\Http\JsonResponse;
|
||||
*/
|
||||
class TransactionController extends Controller
|
||||
{
|
||||
public function infiniteList(InfiniteListRequest $request): JsonResponse
|
||||
{
|
||||
// get sort instructions
|
||||
$instructions = $request->getSortInstructions('transactions');
|
||||
|
||||
// collect transactions:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUserGroup(auth()->user()->userGroup)
|
||||
->withAPIInformation()
|
||||
->setStartRow($request->getStartRow())
|
||||
->setEndRow($request->getEndRow())
|
||||
->setTypes($request->getTransactionTypes())
|
||||
->setSorting($instructions)
|
||||
;
|
||||
|
||||
$start = $this->parameters->get('start');
|
||||
$end = $this->parameters->get('end');
|
||||
if (null !== $start) {
|
||||
$collector->setStart($start);
|
||||
}
|
||||
if (null !== $end) {
|
||||
$collector->setEnd($end);
|
||||
}
|
||||
|
||||
$paginator = $collector->getPaginatedGroups();
|
||||
$params = $request->buildParams();
|
||||
$paginator->setPath(
|
||||
sprintf(
|
||||
'%s?%s',
|
||||
route('api.v2.infinite.transactions.list'),
|
||||
$params
|
||||
)
|
||||
);
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer()))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function list(ListRequest $request): JsonResponse
|
||||
{
|
||||
// collect transactions:
|
||||
@@ -75,45 +116,4 @@ class TransactionController extends Controller
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
|
||||
public function infiniteList(InfiniteListRequest $request): JsonResponse
|
||||
{
|
||||
// get sort instructions
|
||||
$instructions = $request->getSortInstructions();
|
||||
|
||||
// collect transactions:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUserGroup(auth()->user()->userGroup)
|
||||
->withAPIInformation()
|
||||
->setStartRow($request->getStartRow())
|
||||
->setEndRow($request->getEndRow())
|
||||
->setTypes($request->getTransactionTypes())
|
||||
->setSorting($instructions)
|
||||
;
|
||||
|
||||
$start = $this->parameters->get('start');
|
||||
$end = $this->parameters->get('end');
|
||||
if (null !== $start) {
|
||||
$collector->setStart($start);
|
||||
}
|
||||
if (null !== $end) {
|
||||
$collector->setEnd($end);
|
||||
}
|
||||
|
||||
$paginator = $collector->getPaginatedGroups();
|
||||
$params = $request->buildParams();
|
||||
$paginator->setPath(
|
||||
sprintf(
|
||||
'%s?%s',
|
||||
route('api.v2.infinite.transactions.list'),
|
||||
$params
|
||||
)
|
||||
);
|
||||
|
||||
return response()
|
||||
->json($this->jsonApiList('transactions', $paginator, new TransactionGroupTransformer()))
|
||||
->header('Content-Type', self::CONTENT_TYPE)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
69
app/Api/V2/Request/Model/Account/IndexRequest.php
Normal file
69
app/Api/V2/Request/Model/Account/IndexRequest.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/*
|
||||
* IndexRequest.php
|
||||
* Copyright (c) 2024 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\Api\V2\Request\Model\Account;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetSortInstructions;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
* Class IndexRequest
|
||||
*
|
||||
* Lots of code stolen from the SingleDateRequest.
|
||||
*/
|
||||
class IndexRequest extends FormRequest
|
||||
{
|
||||
use AccountFilter;
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use GetSortInstructions;
|
||||
|
||||
public function getAccountTypes(): array
|
||||
{
|
||||
$type = (string)$this->get('type', 'default');
|
||||
|
||||
return $this->mapAccountTypes($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data from the request.
|
||||
*/
|
||||
public function getDate(): Carbon
|
||||
{
|
||||
return $this->getCarbonDate('date');
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
];
|
||||
}
|
||||
}
|
@@ -25,9 +25,11 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V2\Request\Model\Transaction;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetSortInstructions;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
/**
|
||||
@@ -36,8 +38,10 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
*/
|
||||
class InfiniteListRequest extends FormRequest
|
||||
{
|
||||
use AccountFilter;
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use GetSortInstructions;
|
||||
use TransactionFilter;
|
||||
|
||||
public function buildParams(): string
|
||||
@@ -81,6 +85,13 @@ class InfiniteListRequest extends FormRequest
|
||||
return $this->getCarbonDate('end');
|
||||
}
|
||||
|
||||
public function getAccountTypes(): array
|
||||
{
|
||||
$type = (string)$this->get('type', 'default');
|
||||
|
||||
return $this->mapAccountTypes($type);
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
$page = $this->convertInteger('page');
|
||||
@@ -88,31 +99,6 @@ class InfiniteListRequest extends FormRequest
|
||||
return 0 === $page || $page > 65536 ? 1 : $page;
|
||||
}
|
||||
|
||||
public function getSortInstructions(): array
|
||||
{
|
||||
$allowed = config('firefly.sorting.allowed.transactions');
|
||||
$set = $this->get('sorting', []);
|
||||
$result = [];
|
||||
if (0 === count($set)) {
|
||||
return [];
|
||||
}
|
||||
foreach ($set as $info) {
|
||||
$column = $info['column'] ?? 'NOPE';
|
||||
$direction = $info['direction'] ?? 'NOPE';
|
||||
if ('asc' !== $direction && 'desc' !== $direction) {
|
||||
// skip invalid direction
|
||||
continue;
|
||||
}
|
||||
if (false === in_array($column, $allowed, true)) {
|
||||
// skip invalid column
|
||||
continue;
|
||||
}
|
||||
$result[$column] = $direction;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getTransactionTypes(): array
|
||||
{
|
||||
$type = (string)$this->get('type', 'default');
|
||||
|
@@ -72,31 +72,6 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$this->run();
|
||||
}
|
||||
|
||||
public function getVersion(): int
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function setObjects(Collection $objects): void
|
||||
{
|
||||
$this->objects = $objects;
|
||||
}
|
||||
|
||||
public function setTrigger(int $trigger): void
|
||||
{
|
||||
$this->trigger = $trigger;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setWebhooks(Collection $webhooks): void
|
||||
{
|
||||
$this->webhooks = $webhooks;
|
||||
}
|
||||
|
||||
private function getWebhooks(): Collection
|
||||
{
|
||||
return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']);
|
||||
@@ -206,6 +181,11 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$this->storeMessage($webhook, $basicMessage);
|
||||
}
|
||||
|
||||
public function getVersion(): int
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
private function collectAccounts(TransactionGroup $transactionGroup): Collection
|
||||
{
|
||||
$accounts = new Collection();
|
||||
@@ -232,4 +212,24 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$webhookMessage->save();
|
||||
app('log')->debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
|
||||
}
|
||||
|
||||
public function setObjects(Collection $objects): void
|
||||
{
|
||||
$this->objects = $objects;
|
||||
}
|
||||
|
||||
public function setTrigger(int $trigger): void
|
||||
{
|
||||
$this->trigger = $trigger;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setWebhooks(Collection $webhooks): void
|
||||
{
|
||||
$this->webhooks = $webhooks;
|
||||
}
|
||||
}
|
||||
|
@@ -33,10 +33,11 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
*/
|
||||
trait CollectorProperties
|
||||
{
|
||||
/** @var array<int, string> */
|
||||
public array $sorting;
|
||||
public const string TEST = 'Test';
|
||||
private ?int $endRow;
|
||||
|
||||
/** @var array<int, string> */
|
||||
public array $sorting;
|
||||
private ?int $endRow;
|
||||
private bool $expandGroupSearch;
|
||||
private array $fields;
|
||||
private bool $hasAccountInfo;
|
||||
@@ -52,7 +53,7 @@ trait CollectorProperties
|
||||
private ?int $page;
|
||||
private array $postFilters;
|
||||
private HasMany $query;
|
||||
private ?int $startRow;
|
||||
private ?int $startRow;
|
||||
private array $stringFields;
|
||||
/*
|
||||
* This array is used to collect ALL tags the user may search for (using 'setTags').
|
||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Helpers\Collector;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\Extensions\AccountCollection;
|
||||
use FireflyIII\Helpers\Collector\Extensions\AmountCollection;
|
||||
@@ -782,6 +783,35 @@ class GroupCollector implements GroupCollectorInterface
|
||||
return $currentCollection;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function sortCollection(Collection $collection): Collection
|
||||
{
|
||||
/**
|
||||
* @var string $field
|
||||
* @var string $direction
|
||||
*/
|
||||
foreach ($this->sorting as $field => $direction) {
|
||||
$func = 'ASC' === $direction ? 'sortBy' : 'sortByDesc';
|
||||
$collection = $collection->{$func}(function (array $product, int $key) use ($field) { // @phpstan-ignore-line
|
||||
// depends on $field:
|
||||
if ('description' === $field) {
|
||||
if (1 === count($product['transactions'])) {
|
||||
return array_values($product['transactions'])[0][$field];
|
||||
}
|
||||
if (count($product['transactions']) > 1) {
|
||||
return $product['title'];
|
||||
}
|
||||
|
||||
return 'zzz';
|
||||
}
|
||||
|
||||
exit('here we are');
|
||||
});
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as getGroups but everything is in a paginator.
|
||||
*/
|
||||
@@ -792,6 +822,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$this->setLimit(50);
|
||||
}
|
||||
if (null !== $this->startRow && null !== $this->endRow) {
|
||||
/** @var int $total */
|
||||
$total = $this->endRow - $this->startRow;
|
||||
|
||||
return new LengthAwarePaginator($set, $this->total, $total, 1);
|
||||
@@ -931,6 +962,14 @@ class GroupCollector implements GroupCollectorInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function setSorting(array $instructions): GroupCollectorInterface
|
||||
{
|
||||
$this->sorting = $instructions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStartRow(int $startRow): self
|
||||
{
|
||||
$this->startRow = $startRow;
|
||||
@@ -1091,41 +1130,4 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function sortCollection(Collection $collection): Collection
|
||||
{
|
||||
/**
|
||||
* @var string $field
|
||||
* @var string $direction
|
||||
*/
|
||||
foreach ($this->sorting as $field => $direction) {
|
||||
$func = 'ASC' === $direction ? 'sortBy' : 'sortByDesc';
|
||||
$collection = $collection->{$func}(function (array $product, int $key) use ($field) { // @phpstan-ignore-line
|
||||
// depends on $field:
|
||||
if ('description' === $field) {
|
||||
if (1 === count($product['transactions'])) {
|
||||
return array_values($product['transactions'])[0][$field];
|
||||
}
|
||||
if (count($product['transactions']) > 1) {
|
||||
return $product['title'];
|
||||
}
|
||||
|
||||
return 'zzz';
|
||||
}
|
||||
|
||||
exit('here we are');
|
||||
});
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function setSorting(array $instructions): GroupCollectorInterface
|
||||
{
|
||||
$this->sorting = $instructions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -285,13 +285,6 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function getPaginatedGroups(): LengthAwarePaginator;
|
||||
|
||||
public function setSorting(array $instructions): self;
|
||||
|
||||
/**
|
||||
* Sort the collection on a column.
|
||||
*/
|
||||
public function sortCollection(Collection $collection): Collection;
|
||||
|
||||
public function hasAnyTag(): self;
|
||||
|
||||
/**
|
||||
@@ -560,6 +553,8 @@ interface GroupCollectorInterface
|
||||
|
||||
public function setSepaCT(string $sepaCT): self;
|
||||
|
||||
public function setSorting(array $instructions): self;
|
||||
|
||||
/**
|
||||
* Set source accounts.
|
||||
*/
|
||||
@@ -620,6 +615,11 @@ interface GroupCollectorInterface
|
||||
*/
|
||||
public function setXorAccounts(Collection $accounts): self;
|
||||
|
||||
/**
|
||||
* Sort the collection on a column.
|
||||
*/
|
||||
public function sortCollection(Collection $collection): Collection;
|
||||
|
||||
/**
|
||||
* Automatically include all stuff required to make API calls work.
|
||||
*/
|
||||
|
@@ -51,7 +51,7 @@ class NetWorth implements NetWorthInterface
|
||||
|
||||
private CurrencyRepositoryInterface $currencyRepos;
|
||||
private User $user;
|
||||
private ?UserGroup $userGroup;
|
||||
private ?UserGroup $userGroup;
|
||||
|
||||
/**
|
||||
* This method collects the user's net worth in ALL the user's currencies
|
||||
|
@@ -80,7 +80,18 @@ class LoginController extends Controller
|
||||
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get($this->username())));
|
||||
app('log')->debug('User is trying to login.');
|
||||
|
||||
$this->validateLogin($request);
|
||||
try {
|
||||
$this->validateLogin($request);
|
||||
} catch (ValidationException $e) {
|
||||
return redirect(route('login'))
|
||||
->withErrors(
|
||||
[
|
||||
$this->username => trans('auth.failed'),
|
||||
]
|
||||
)
|
||||
->onlyInput($this->username)
|
||||
;
|
||||
}
|
||||
app('log')->debug('Login data is present.');
|
||||
|
||||
// Copied directly from AuthenticatesUsers, but with logging added:
|
||||
@@ -91,7 +102,6 @@ class LoginController extends Controller
|
||||
Log::channel('audit')->warning(sprintf('Login for user "%s" was locked out.', $request->get($this->username())));
|
||||
app('log')->error(sprintf('Login for user "%s" was locked out.', $request->get($this->username())));
|
||||
$this->fireLockoutEvent($request);
|
||||
|
||||
$this->sendLockoutResponse($request);
|
||||
}
|
||||
// Copied directly from AuthenticatesUsers, but with logging added:
|
||||
|
@@ -143,7 +143,7 @@ class ReportController extends Controller
|
||||
$cache->addProperty($accounts);
|
||||
$cache->addProperty($end);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
// return response()->json($cache->get());
|
||||
}
|
||||
|
||||
app('log')->debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
|
||||
@@ -220,11 +220,14 @@ class ReportController extends Controller
|
||||
$currentEnd = app('navigation')->endOfPeriod($currentEnd, $preferredRange);
|
||||
}
|
||||
while ($currentStart <= $currentEnd) {
|
||||
$key = $currentStart->format($format);
|
||||
$title = $currentStart->isoFormat($titleFormat);
|
||||
$income['entries'][$title] = app('steam')->bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
|
||||
$expense['entries'][$title] = app('steam')->bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
|
||||
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
|
||||
$key = $currentStart->format($format);
|
||||
$title = $currentStart->isoFormat($titleFormat);
|
||||
// #8663 make sure the period exists in the data previously collected.
|
||||
if (array_key_exists($key, $currency)) {
|
||||
$income['entries'][$title] = app('steam')->bcround($currency[$key]['earned'] ?? '0', $currency['currency_decimal_places']);
|
||||
$expense['entries'][$title] = app('steam')->bcround($currency[$key]['spent'] ?? '0', $currency['currency_decimal_places']);
|
||||
}
|
||||
$currentStart = app('navigation')->addPeriod($currentStart, $preferredRange, 0);
|
||||
}
|
||||
|
||||
$chartData[] = $income;
|
||||
|
@@ -69,6 +69,11 @@ abstract class Controller extends BaseController
|
||||
$authGuard = config('firefly.authentication_guard');
|
||||
$logoutUrl = config('firefly.custom_logout_url');
|
||||
|
||||
// overrule v2 layout back to v1.
|
||||
if ('true' === request()->get('force_default_layout') && 'v2' === config('firefly.layout')) {
|
||||
app('view')->getFinder()->setPaths([realpath(base_path('resources/views'))]); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
app('view')->share('authGuard', $authGuard);
|
||||
app('view')->share('logoutUrl', $logoutUrl);
|
||||
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Models\AccountType;
|
||||
|
@@ -34,6 +34,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class ReconcileController
|
||||
@@ -73,7 +74,6 @@ class ReconcileController extends Controller
|
||||
$accountCurrency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
|
||||
$amount = '0';
|
||||
$clearedAmount = '0';
|
||||
$route = '';
|
||||
|
||||
if (null === $start && null === $end) {
|
||||
throw new FireflyException('Invalid dates submitted.');
|
||||
@@ -103,14 +103,11 @@ class ReconcileController extends Controller
|
||||
$clearedJournals = $collector->getExtractedJournals();
|
||||
}
|
||||
|
||||
app('log')->debug('Start transaction loop');
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$amount = $this->processJournal($account, $accountCurrency, $journal, $amount);
|
||||
}
|
||||
app('log')->debug(sprintf('Final amount is %s', $amount));
|
||||
app('log')->debug('End transaction loop');
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($clearedJournals as $journal) {
|
||||
@@ -118,31 +115,17 @@ class ReconcileController extends Controller
|
||||
$clearedAmount = $this->processJournal($account, $accountCurrency, $journal, $clearedAmount);
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Start balance: "%s"', $startBalance));
|
||||
Log::debug(sprintf('End balance: "%s"', $endBalance));
|
||||
Log::debug(sprintf('Cleared amount: "%s"', $clearedAmount));
|
||||
Log::debug(sprintf('Amount: "%s"', $amount));
|
||||
$difference = bcadd(bcadd(bcsub($startBalance, $endBalance), $clearedAmount), $amount);
|
||||
$diffCompare = bccomp($difference, '0');
|
||||
$countCleared = count($clearedJournals);
|
||||
|
||||
$reconSum = bcadd(bcadd($startBalance, $amount), $clearedAmount);
|
||||
|
||||
try {
|
||||
$view = view(
|
||||
'accounts.reconcile.overview',
|
||||
compact(
|
||||
'account',
|
||||
'start',
|
||||
'diffCompare',
|
||||
'difference',
|
||||
'end',
|
||||
'clearedAmount',
|
||||
'startBalance',
|
||||
'endBalance',
|
||||
'amount',
|
||||
'route',
|
||||
'countCleared',
|
||||
'reconSum',
|
||||
'selectedIds'
|
||||
)
|
||||
)->render();
|
||||
$view = view('accounts.reconcile.overview', compact('account', 'start', 'diffCompare', 'difference', 'end', 'clearedAmount', 'startBalance', 'endBalance', 'amount', 'route', 'countCleared', 'reconSum', 'selectedIds'))->render();
|
||||
} catch (\Throwable $e) {
|
||||
app('log')->debug(sprintf('View error: %s', $e->getMessage()));
|
||||
app('log')->error($e->getTraceAsString());
|
||||
@@ -151,14 +134,42 @@ class ReconcileController extends Controller
|
||||
throw new FireflyException($view, 0, $e);
|
||||
}
|
||||
|
||||
$return = [
|
||||
'post_url' => $route,
|
||||
'html' => $view,
|
||||
];
|
||||
$return = ['post_url' => $route, 'html' => $view];
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
private function processJournal(Account $account, TransactionCurrency $currency, array $journal, string $amount): string
|
||||
{
|
||||
$toAdd = '0';
|
||||
app('log')->debug(sprintf('User submitted %s #%d: "%s"', $journal['transaction_type_type'], $journal['transaction_journal_id'], $journal['description']));
|
||||
|
||||
// not much magic below we need to cover using tests.
|
||||
|
||||
if ($account->id === $journal['source_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = $journal['amount'];
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = $journal['foreign_amount'];
|
||||
}
|
||||
}
|
||||
if ($account->id === $journal['destination_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = bcmul($journal['amount'], '-1');
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = bcmul($journal['foreign_amount'], '-1');
|
||||
}
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Going to add %s to %s', $toAdd, $amount));
|
||||
$amount = bcadd($amount, $toAdd);
|
||||
app('log')->debug(sprintf('Result is %s', $amount));
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of transactions in a modal.
|
||||
*
|
||||
@@ -215,37 +226,6 @@ class ReconcileController extends Controller
|
||||
return response()->json(['html' => $html, 'startBalance' => $startBalance, 'endBalance' => $endBalance]);
|
||||
}
|
||||
|
||||
private function processJournal(Account $account, TransactionCurrency $currency, array $journal, string $amount): string
|
||||
{
|
||||
$toAdd = '0';
|
||||
app('log')->debug(sprintf('User submitted %s #%d: "%s"', $journal['transaction_type_type'], $journal['transaction_journal_id'], $journal['description']));
|
||||
|
||||
// not much magic below we need to cover using tests.
|
||||
|
||||
if ($account->id === $journal['source_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = $journal['amount'];
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = $journal['foreign_amount'];
|
||||
}
|
||||
}
|
||||
if ($account->id === $journal['destination_account_id']) {
|
||||
if ($currency->id === $journal['currency_id']) {
|
||||
$toAdd = bcmul($journal['amount'], '-1');
|
||||
}
|
||||
if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) {
|
||||
$toAdd = bcmul($journal['foreign_amount'], '-1');
|
||||
}
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Going to add %s to %s', $toAdd, $amount));
|
||||
$amount = bcadd($amount, $toAdd);
|
||||
app('log')->debug(sprintf('Result is %s', $amount));
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* "fix" amounts to make it easier on the reconciliation overview:
|
||||
*/
|
||||
|
@@ -75,52 +75,45 @@ class IndexController extends Controller
|
||||
$objectType = 'transfer';
|
||||
}
|
||||
|
||||
// add a split for the (future) v2 release.
|
||||
$periods = [];
|
||||
$groups = [];
|
||||
$subTitle = 'TODO page subtitle in v2';
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.'.$objectType);
|
||||
$types = config('firefly.transactionTypesByType.'.$objectType);
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.'.$objectType);
|
||||
$types = config('firefly.transactionTypesByType.'.$objectType);
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
|
||||
if ('v2' !== (string)config('firefly.layout')) {
|
||||
if (null === $start) {
|
||||
$start = session('start');
|
||||
$end = session('end');
|
||||
}
|
||||
if (null === $end) {
|
||||
// get last transaction ever?
|
||||
$last = $this->repository->getLast();
|
||||
$end = null !== $last ? $last->date : session('end');
|
||||
}
|
||||
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
$startStr = $start->isoFormat($this->monthAndDayFormat);
|
||||
$endStr = $end->isoFormat($this->monthAndDayFormat);
|
||||
$subTitle = (string)trans(sprintf('firefly.title_%s_between', $objectType), ['start' => $startStr, 'end' => $endStr]);
|
||||
$path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$firstJournal = $this->repository->firstNull();
|
||||
$startPeriod = null === $firstJournal ? new Carbon() : $firstJournal->date;
|
||||
$endPeriod = clone $end;
|
||||
$periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod);
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
|
||||
$collector->setRange($start, $end)
|
||||
->setTypes($types)
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withBudgetInformation()
|
||||
->withCategoryInformation()
|
||||
->withAccountInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
if (null === $start) {
|
||||
$start = session('start');
|
||||
$end = session('end');
|
||||
}
|
||||
if (null === $end) {
|
||||
// get last transaction ever?
|
||||
$last = $this->repository->getLast();
|
||||
$end = null !== $last ? $last->date : session('end');
|
||||
}
|
||||
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
$startStr = $start->isoFormat($this->monthAndDayFormat);
|
||||
$endStr = $end->isoFormat($this->monthAndDayFormat);
|
||||
$subTitle = (string)trans(sprintf('firefly.title_%s_between', $objectType), ['start' => $startStr, 'end' => $endStr]);
|
||||
$path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$firstJournal = $this->repository->firstNull();
|
||||
$startPeriod = null === $firstJournal ? new Carbon() : $firstJournal->date;
|
||||
$endPeriod = clone $end;
|
||||
$periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod);
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
|
||||
$collector->setRange($start, $end)
|
||||
->setTypes($types)
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withBudgetInformation()
|
||||
->withCategoryInformation()
|
||||
->withAccountInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
|
||||
return view('transactions.index', compact('subTitle', 'objectType', 'subTitleIcon', 'groups', 'periods', 'start', 'end'));
|
||||
}
|
||||
|
@@ -50,12 +50,12 @@ class SecureHeaders
|
||||
$csp = [
|
||||
"default-src 'none'",
|
||||
"object-src 'none'",
|
||||
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'self' 'unsafe-inline' 'nonce-%1s' %2s", $nonce, $trackingScriptSrc),
|
||||
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'nonce-%1s'", $nonce),
|
||||
"style-src 'unsafe-inline' 'self'",
|
||||
"base-uri 'self'",
|
||||
"font-src 'self' data:",
|
||||
sprintf("connect-src 'self' %s", $trackingScriptSrc),
|
||||
sprintf("img-src data: 'strict-dynamic' 'self' *.tile.openstreetmap.org %s", $trackingScriptSrc),
|
||||
sprintf("img-src 'self' 'nonce-%1s'", $nonce),
|
||||
"manifest-src 'self'",
|
||||
];
|
||||
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Rules\IsValidActionExpression;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Support\Request\GetRuleConfiguration;
|
||||
@@ -147,7 +148,7 @@ class RuleFormRequest extends FormRequest
|
||||
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
|
||||
'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|max:1024|min:1|ruleTriggerValue', $contextTriggers),
|
||||
'actions.*.type' => 'required|in:'.implode(',', $validActions),
|
||||
'actions.*.value' => sprintf('required_if:actions.*.type,%s|min:0|max:1024|ruleActionValue', $contextActions),
|
||||
'actions.*.value' => [sprintf('required_if:actions.*.type,%s|min:0|max:1024', $contextActions), new IsValidActionExpression(), 'ruleActionValue'],
|
||||
'strict' => 'in:0,1',
|
||||
];
|
||||
|
||||
|
@@ -274,6 +274,13 @@ class Account extends Model
|
||||
);
|
||||
}
|
||||
|
||||
protected function iban(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => null === $value ? null : trim(str_replace(' ', '', (string)$value)),
|
||||
);
|
||||
}
|
||||
|
||||
protected function order(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
@@ -26,10 +26,13 @@ namespace FireflyIII\Models;
|
||||
use Carbon\Carbon;
|
||||
use Eloquent;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\TransactionRules\Expressions\ActionExpression;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\ExpressionLanguage\SyntaxError;
|
||||
|
||||
/**
|
||||
* FireflyIII\Models\RuleAction
|
||||
@@ -75,6 +78,26 @@ class RuleAction extends Model
|
||||
|
||||
protected $fillable = ['rule_id', 'action_type', 'action_value', 'order', 'active', 'stop_processing'];
|
||||
|
||||
public function getValue(array $journal): string
|
||||
{
|
||||
if (false === config('firefly.feature_flags.expression_engine')) {
|
||||
Log::debug('Expression engine is disabled, returning action value as string.');
|
||||
|
||||
return (string)$this->action_value;
|
||||
}
|
||||
$expr = new ActionExpression($this->action_value);
|
||||
|
||||
try {
|
||||
$result = $expr->evaluate($journal);
|
||||
} catch (SyntaxError $e) {
|
||||
Log::error(sprintf('Expression engine failed to evaluate expression "%s" with error "%s".', $this->action_value, $e->getMessage()));
|
||||
$result = (string)$this->action_value;
|
||||
}
|
||||
Log::debug(sprintf('Expression engine is enabled, result of expression "%s" is "%s".', $this->action_value, $result));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function rule(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Rule::class);
|
||||
|
@@ -69,9 +69,11 @@ use FireflyIII\Support\Preferences;
|
||||
use FireflyIII\Support\Steam;
|
||||
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
|
||||
use FireflyIII\TransactionRules\Engine\SearchRuleEngine;
|
||||
use FireflyIII\TransactionRules\Expressions\ActionExpressionLanguageProvider;
|
||||
use FireflyIII\Validation\FireflyValidator;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
/**
|
||||
* Class FireflyServiceProvider.
|
||||
@@ -200,6 +202,17 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
}
|
||||
);
|
||||
|
||||
// rule expression language
|
||||
$this->app->singleton(
|
||||
ExpressionLanguage::class,
|
||||
static function () {
|
||||
$expressionLanguage = new ExpressionLanguage();
|
||||
$expressionLanguage->registerProvider(new ActionExpressionLanguageProvider());
|
||||
|
||||
return $expressionLanguage;
|
||||
}
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
RuleEngineInterface::class,
|
||||
static function (Application $app) {
|
||||
|
@@ -202,7 +202,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
|
||||
$availableBudget->user()->associate($this->user);
|
||||
$availableBudget->transactionCurrency()->associate($currency);
|
||||
$availableBudget->start_date = $start->startOfDay()->format('Y-m-d'); // @phpstan-ignore-line
|
||||
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
|
||||
$availableBudget->end_date = $end->endOfDay()->format('Y-m-d'); // @phpstan-ignore-line
|
||||
}
|
||||
$availableBudget->amount = $amount;
|
||||
$availableBudget->save();
|
||||
|
@@ -39,6 +39,17 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
{
|
||||
use UserGroupTrait;
|
||||
|
||||
#[\Override]
|
||||
public function countAccounts(array $types): int
|
||||
{
|
||||
$query = $this->userGroup->accounts();
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
|
||||
public function findByAccountNumber(string $number, array $types): ?Account
|
||||
{
|
||||
$dbQuery = $this->userGroup
|
||||
@@ -161,25 +172,25 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection
|
||||
#[\Override]
|
||||
public function getAccountsInOrder(array $types, array $sort, int $startRow, int $endRow): Collection
|
||||
{
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
$query = $this->userGroup->accounts();
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
$query->skip($startRow);
|
||||
$query->take($endRow - $startRow);
|
||||
|
||||
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
||||
if (0 !== count($sort)) {
|
||||
foreach ($sort as $param) {
|
||||
$query->orderBy($param[0], $param[1]);
|
||||
foreach ($sort as $label => $direction) {
|
||||
$query->orderBy(sprintf('accounts.%s', $label), $direction);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === count($sort)) {
|
||||
if (0 !== count($res)) {
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
}
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
$query->orderBy('accounts.active', 'DESC');
|
||||
$query->orderBy('accounts.name', 'ASC');
|
||||
}
|
||||
@@ -201,6 +212,60 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function resetAccountOrder(): void
|
||||
{
|
||||
$sets = [
|
||||
[AccountType::DEFAULT, AccountType::ASSET],
|
||||
[AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE],
|
||||
];
|
||||
foreach ($sets as $set) {
|
||||
$list = $this->getAccountsByType($set);
|
||||
$index = 1;
|
||||
foreach ($list as $account) {
|
||||
if (false === $account->active) {
|
||||
$account->order = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
if ($index !== (int)$account->order) {
|
||||
app('log')->debug(sprintf('Account #%d ("%s"): order should %d be but is %d.', $account->id, $account->name, $index, $account->order));
|
||||
$account->order = $index;
|
||||
$account->save();
|
||||
}
|
||||
++$index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection
|
||||
{
|
||||
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
$query = $this->userGroup->accounts();
|
||||
if (0 !== count($types)) {
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
|
||||
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
||||
if (count($sort) > 0) {
|
||||
foreach ($sort as $column => $direction) {
|
||||
if (in_array($column, $sortable, true)) {
|
||||
$query->orderBy(sprintf('accounts.%s', $column), $direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === count($sort)) {
|
||||
if (0 !== count($res)) {
|
||||
$query->orderBy('accounts.order', 'ASC');
|
||||
}
|
||||
$query->orderBy('accounts.active', 'DESC');
|
||||
$query->orderBy('accounts.name', 'ASC');
|
||||
}
|
||||
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection
|
||||
{
|
||||
// search by group, not by user
|
||||
|
@@ -35,6 +35,8 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
interface AccountRepositoryInterface
|
||||
{
|
||||
public function countAccounts(array $types): int;
|
||||
|
||||
public function find(int $accountId): ?Account;
|
||||
|
||||
public function findByAccountNumber(string $number, array $types): ?Account;
|
||||
@@ -49,6 +51,11 @@ interface AccountRepositoryInterface
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection;
|
||||
|
||||
/**
|
||||
* Used in the infinite accounts list.
|
||||
*/
|
||||
public function getAccountsInOrder(array $types, array $sort, int $startRow, int $endRow): Collection;
|
||||
|
||||
public function getActiveAccountsByType(array $types): Collection;
|
||||
|
||||
/**
|
||||
@@ -56,6 +63,11 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function getMetaValue(Account $account, string $field): ?string;
|
||||
|
||||
/**
|
||||
* Reset order types of the mentioned accounts.
|
||||
*/
|
||||
public function resetAccountOrder(): void;
|
||||
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection;
|
||||
|
||||
public function setUser(User $user): void;
|
||||
|
57
app/Rules/IsValidActionExpression.php
Normal file
57
app/Rules/IsValidActionExpression.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* IsValidActionExpression.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* 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\Rules;
|
||||
|
||||
use FireflyIII\TransactionRules\Expressions\ActionExpression;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Translation\PotentiallyTranslatedString;
|
||||
|
||||
class IsValidActionExpression implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Check that the given action expression is syntactically valid.
|
||||
*
|
||||
* @param \Closure(string): PotentiallyTranslatedString $fail
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
if (false === config('firefly.feature_flags.expression_engine')) {
|
||||
return;
|
||||
}
|
||||
$value ??= '';
|
||||
$expr = new ActionExpression($value);
|
||||
|
||||
if (!$expr->isValid()) {
|
||||
$fail('validation.rule_action_expression')->translate(
|
||||
[
|
||||
'error' => $expr->getValidationError()->getMessage(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -39,7 +39,7 @@ class RemoteUserGuard implements Guard
|
||||
{
|
||||
protected Application $application;
|
||||
protected UserProvider $provider;
|
||||
protected ?User $user;
|
||||
protected ?User $user;
|
||||
|
||||
/**
|
||||
* Create a new authentication guard.
|
||||
|
@@ -79,13 +79,14 @@ trait CalculateXOccurrencesSince
|
||||
while ($total < $count) {
|
||||
$domCorrected = min($dayOfMonth, $mutator->daysInMonth);
|
||||
$mutator->day = $domCorrected;
|
||||
app('log')->debug(sprintf('Mutator is now %s', $mutator->format('Y-m-d')));
|
||||
if (0 === $attempts % $skipMod && $mutator->gte($afterDate)) {
|
||||
app('log')->debug('Is added to the list.');
|
||||
$return[] = clone $mutator;
|
||||
++$total;
|
||||
}
|
||||
++$attempts;
|
||||
$mutator = $mutator->endOfMonth()->addDay();
|
||||
app('log')->debug(sprintf('Mutator is now %s', $mutator->format('Y-m-d')));
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
52
app/Support/Request/GetSortInstructions.php
Normal file
52
app/Support/Request/GetSortInstructions.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/*
|
||||
* GetSortInstructions.php
|
||||
* Copyright (c) 2024 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\Support\Request;
|
||||
|
||||
trait GetSortInstructions
|
||||
{
|
||||
final public function getSortInstructions(string $key): array
|
||||
{
|
||||
$allowed = config(sprintf('firefly.sorting.allowed.%s', $key));
|
||||
$set = $this->get('sorting', []);
|
||||
$result = [];
|
||||
if (0 === count($set)) {
|
||||
return [];
|
||||
}
|
||||
foreach ($set as $info) {
|
||||
$column = $info['column'] ?? 'NOPE';
|
||||
$direction = $info['direction'] ?? 'NOPE';
|
||||
if ('asc' !== $direction && 'desc' !== $direction) {
|
||||
// skip invalid direction
|
||||
continue;
|
||||
}
|
||||
if (false === in_array($column, $allowed, true)) {
|
||||
// skip invalid column
|
||||
continue;
|
||||
}
|
||||
$result[$column] = $direction;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@@ -72,16 +72,18 @@ class OperatorQuerySearch implements SearchInterface
|
||||
private GroupCollectorInterface $collector;
|
||||
private CurrencyRepositoryInterface $currencyRepository;
|
||||
private array $excludeTags;
|
||||
private array $includeTags;
|
||||
private array $invalidOperators;
|
||||
private int $limit;
|
||||
private Collection $operators;
|
||||
private int $page;
|
||||
private array $prohibitedWords;
|
||||
private float $startTime;
|
||||
private TagRepositoryInterface $tagRepository;
|
||||
private array $validOperators;
|
||||
private array $words;
|
||||
private array $includeAnyTags;
|
||||
// added to fix #8632
|
||||
private array $includeTags;
|
||||
private array $invalidOperators;
|
||||
private int $limit;
|
||||
private Collection $operators;
|
||||
private int $page;
|
||||
private array $prohibitedWords;
|
||||
private float $startTime;
|
||||
private TagRepositoryInterface $tagRepository;
|
||||
private array $validOperators;
|
||||
private array $words;
|
||||
|
||||
/**
|
||||
* OperatorQuerySearch constructor.
|
||||
@@ -93,6 +95,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
$this->page = 1;
|
||||
$this->words = [];
|
||||
$this->excludeTags = [];
|
||||
$this->includeAnyTags = [];
|
||||
$this->includeTags = [];
|
||||
$this->prohibitedWords = [];
|
||||
$this->invalidOperators = [];
|
||||
@@ -1112,8 +1115,9 @@ class OperatorQuerySearch implements SearchInterface
|
||||
$this->collector->findNothing();
|
||||
}
|
||||
if ($tags->count() > 0) {
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$this->includeTags = array_unique(array_merge($this->includeTags, $ids));
|
||||
// changed from includeTags to includeAnyTags for #8632
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$this->includeAnyTags = array_unique(array_merge($this->includeAnyTags, $ids));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1125,8 +1129,9 @@ class OperatorQuerySearch implements SearchInterface
|
||||
$this->collector->findNothing();
|
||||
}
|
||||
if ($tags->count() > 0) {
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$this->includeTags = array_unique(array_merge($this->includeTags, $ids));
|
||||
// changed from includeTags to includeAnyTags for #8632
|
||||
$ids = array_values($tags->pluck('id')->toArray());
|
||||
$this->includeAnyTags = array_unique(array_merge($this->includeAnyTags, $ids));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -2725,6 +2730,19 @@ class OperatorQuerySearch implements SearchInterface
|
||||
}
|
||||
$this->collector->setAllTags($collection);
|
||||
}
|
||||
// if include ANY tags, include them: (see #8632)
|
||||
if (count($this->includeAnyTags) > 0) {
|
||||
app('log')->debug(sprintf('%d include ANY tag(s)', count($this->includeAnyTags)));
|
||||
$collection = new Collection();
|
||||
foreach ($this->includeAnyTags as $tagId) {
|
||||
$tag = $this->tagRepository->find($tagId);
|
||||
if (null !== $tag) {
|
||||
app('log')->debug(sprintf('Include ANY tag "%s"', $tag->tag));
|
||||
$collection->push($tag);
|
||||
}
|
||||
}
|
||||
$this->collector->setTags($collection);
|
||||
}
|
||||
}
|
||||
|
||||
public function getWords(): array
|
||||
|
@@ -54,11 +54,12 @@ class AddTag implements ActionInterface
|
||||
/** @var User $user */
|
||||
$user = User::find($journal['user_id']);
|
||||
$factory->setUser($user);
|
||||
$tag = $factory->findOrCreate($this->action->action_value);
|
||||
$tagName = $this->action->getValue($journal);
|
||||
$tag = $factory->findOrCreate($tagName);
|
||||
|
||||
if (null === $tag) {
|
||||
// could not find, could not create tag.
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.find_or_create_tag_failed', ['tag' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.find_or_create_tag_failed', ['tag' => $tagName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -84,7 +85,7 @@ class AddTag implements ActionInterface
|
||||
app('log')->debug(
|
||||
sprintf('RuleAction AddTag fired but tag %d ("%s") was already added to journal %d.', $tag->id, $tag->tag, $journal['transaction_journal_id'])
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.tag_already_added', ['tag' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.tag_already_added', ['tag' => $tagName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@@ -26,12 +26,15 @@ namespace FireflyIII\TransactionRules\Actions;
|
||||
use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendDescription.
|
||||
* TODO Can be replaced (and migrated) to action "set description" with a prefilled expression
|
||||
*/
|
||||
class AppendDescription implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -44,7 +47,9 @@ class AppendDescription implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$description = sprintf('%s %s', $journal['description'], $this->action->action_value);
|
||||
$this->refreshNotes($journal);
|
||||
$append = $this->action->getValue($journal);
|
||||
$description = sprintf('%s %s', $journal['description'], $append);
|
||||
\DB::table('transaction_journals')->where('id', $journal['transaction_journal_id'])->limit(1)->update(['description' => $description]);
|
||||
|
||||
// event for audit log entry
|
||||
|
@@ -29,12 +29,15 @@ use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendDescriptionToNotes
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class AppendDescriptionToNotes implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -47,6 +50,8 @@ class AppendDescriptionToNotes implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$this->refreshNotes($journal);
|
||||
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
if (null === $object) {
|
||||
|
@@ -27,12 +27,15 @@ use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendNotes.
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class AppendNotes implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -45,6 +48,7 @@ class AppendNotes implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$this->refreshNotes($journal);
|
||||
$dbNote = Note::where('noteable_id', (int)$journal['transaction_journal_id'])
|
||||
->where('noteable_type', TransactionJournal::class)
|
||||
->first(['notes.*'])
|
||||
@@ -55,15 +59,16 @@ class AppendNotes implements ActionInterface
|
||||
$dbNote->noteable_type = TransactionJournal::class;
|
||||
$dbNote->text = '';
|
||||
}
|
||||
app('log')->debug(sprintf('RuleAction AppendNotes appended "%s" to "%s".', $this->action->action_value, $dbNote->text));
|
||||
$before = $dbNote->text;
|
||||
$text = sprintf('%s%s', $dbNote->text, $this->action->action_value);
|
||||
$append = $this->action->getValue($journal);
|
||||
$text = sprintf('%s%s', $dbNote->text, $append);
|
||||
$dbNote->text = $text;
|
||||
$dbNote->save();
|
||||
|
||||
/** @var TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
|
||||
app('log')->debug(sprintf('RuleAction AppendNotes appended "%s" to "%s".', $append, $before));
|
||||
event(new TriggeredAuditLog($this->action->rule, $object, 'update_notes', $before, $text));
|
||||
|
||||
return true;
|
||||
|
@@ -30,14 +30,16 @@ use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class AppendNotesToDescription
|
||||
* TODO Can be replaced (and migrated) to action "set description" with a prefilled expression
|
||||
*/
|
||||
class AppendNotesToDescription implements ActionInterface
|
||||
{
|
||||
use ConvertsDataTypes;
|
||||
|
||||
use RefreshNotesTrait;
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -51,6 +53,7 @@ class AppendNotesToDescription implements ActionInterface
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
app('log')->debug('Now in AppendNotesToDescription');
|
||||
$this->refreshNotes($journal);
|
||||
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
|
@@ -52,16 +52,18 @@ class ConvertToDeposit implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$actionValue = $this->action->getValue($journal);
|
||||
|
||||
// make object from array (so the data is fresh).
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
if (null === $object) {
|
||||
app('log')->error(sprintf('Cannot find journal #%d, cannot convert to deposit.', $journal['transaction_journal_id']));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.journal_not_found')));
|
||||
|
||||
return false;
|
||||
}
|
||||
$groupCount = TransactionJournal::where('transaction_group_id', $journal['transaction_group_id'])->count();
|
||||
$groupCount = TransactionJournal::where('transaction_group_id', $journal['transaction_group_id'])->count();
|
||||
if ($groupCount > 1) {
|
||||
app('log')->error(sprintf('Group #%d has more than one transaction in it, cannot convert to deposit.', $journal['transaction_group_id']));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.split_group')));
|
||||
@@ -70,7 +72,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('Convert journal #%d to deposit.', $journal['transaction_journal_id']));
|
||||
$type = $object->transactionType->type;
|
||||
$type = $object->transactionType->type;
|
||||
if (TransactionType::DEPOSIT === $type) {
|
||||
app('log')->error(sprintf('Journal #%d is already a deposit (rule #%d).', $journal['transaction_journal_id'], $this->action->rule_id));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.is_already_deposit')));
|
||||
@@ -82,7 +84,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
app('log')->debug('Going to transform a withdrawal to a deposit.');
|
||||
|
||||
try {
|
||||
$res = $this->convertWithdrawalArray($object);
|
||||
$res = $this->convertWithdrawalArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert withdrawal to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -99,7 +101,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
app('log')->debug('Going to transform a transfer to a deposit.');
|
||||
|
||||
try {
|
||||
$res = $this->convertTransferArray($object);
|
||||
$res = $this->convertTransferArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert transfer to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -122,7 +124,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertWithdrawalArray(TransactionJournal $journal): bool
|
||||
private function convertWithdrawalArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
$user = $journal->user;
|
||||
|
||||
@@ -139,7 +141,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
|
||||
// get the action value, or use the original destination name in case the action value is empty:
|
||||
// this becomes a new or existing (revenue) account, which is the source of the new deposit.
|
||||
$opposingName = '' === $this->action->action_value ? $destAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $destAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.source.Deposit');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -147,7 +149,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::REVENUE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", new opposing name is "%s"', $this->action->action_value, $opposingAccount->name));
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", new opposing name is "%s"', $actionValue, $opposingAccount->name));
|
||||
|
||||
// update the source transaction and put in the new revenue ID.
|
||||
\DB::table('transactions')
|
||||
@@ -211,7 +213,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertTransferArray(TransactionJournal $journal): bool
|
||||
private function convertTransferArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
$user = $journal->user;
|
||||
|
||||
@@ -227,7 +229,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
|
||||
// get the action value, or use the original source name in case the action value is empty:
|
||||
// this becomes a new or existing (revenue) account, which is the source of the new deposit.
|
||||
$opposingName = '' === $this->action->action_value ? $sourceAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $sourceAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.source.Deposit');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -235,7 +237,7 @@ class ConvertToDeposit implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::REVENUE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", revenue name is "%s"', $this->action->action_value, $opposingAccount->name));
|
||||
app('log')->debug(sprintf('ConvertToDeposit. Action value is "%s", revenue name is "%s"', $actionValue, $opposingAccount->name));
|
||||
|
||||
// update source transaction(s) to be revenue account
|
||||
\DB::table('transactions')
|
||||
|
@@ -55,6 +55,8 @@ class ConvertToTransfer implements ActionInterface
|
||||
*/
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$accountName = $this->action->getValue($journal);
|
||||
|
||||
// make object from array (so the data is fresh).
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
@@ -102,7 +104,7 @@ class ConvertToTransfer implements ActionInterface
|
||||
$expectedType = $this->getDestinationType($journalId);
|
||||
// Deposit? Replace source with account with same type as destination.
|
||||
}
|
||||
$opposing = $repository->findByName($this->action->action_value, [$expectedType]);
|
||||
$opposing = $repository->findByName($accountName, [$expectedType]);
|
||||
|
||||
if (null === $opposing) {
|
||||
app('log')->error(
|
||||
@@ -110,11 +112,11 @@ class ConvertToTransfer implements ActionInterface
|
||||
'Journal #%d cannot be converted because no valid %s account with name "%s" exists (rule #%d).',
|
||||
$expectedType,
|
||||
$journalId,
|
||||
$this->action->action_value,
|
||||
$accountName,
|
||||
$this->action->rule_id
|
||||
)
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_valid_opposing', ['name' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_valid_opposing', ['name' => $accountName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@@ -52,16 +52,18 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$actionValue = $this->action->getValue($journal);
|
||||
|
||||
// make object from array (so the data is fresh).
|
||||
/** @var null|TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
if (null === $object) {
|
||||
app('log')->error(sprintf('Cannot find journal #%d, cannot convert to withdrawal.', $journal['transaction_journal_id']));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.journal_not_found')));
|
||||
|
||||
return false;
|
||||
}
|
||||
$groupCount = TransactionJournal::where('transaction_group_id', $journal['transaction_group_id'])->count();
|
||||
$groupCount = TransactionJournal::where('transaction_group_id', $journal['transaction_group_id'])->count();
|
||||
if ($groupCount > 1) {
|
||||
app('log')->error(sprintf('Group #%d has more than one transaction in it, cannot convert to withdrawal.', $journal['transaction_group_id']));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.split_group')));
|
||||
@@ -69,7 +71,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = $object->transactionType->type;
|
||||
$type = $object->transactionType->type;
|
||||
if (TransactionType::WITHDRAWAL === $type) {
|
||||
app('log')->error(sprintf('Journal #%d is already a withdrawal (rule #%d).', $journal['transaction_journal_id'], $this->action->rule_id));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.is_already_withdrawal')));
|
||||
@@ -85,7 +87,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
app('log')->debug('Going to transform a deposit to a withdrawal.');
|
||||
|
||||
try {
|
||||
$res = $this->convertDepositArray($object);
|
||||
$res = $this->convertDepositArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert transfer to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -101,7 +103,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
app('log')->debug('Going to transform a transfer to a withdrawal.');
|
||||
|
||||
try {
|
||||
$res = $this->convertTransferArray($object);
|
||||
$res = $this->convertTransferArray($object, $actionValue);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->debug('Could not convert transfer to deposit.');
|
||||
app('log')->error($e->getMessage());
|
||||
@@ -117,7 +119,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertDepositArray(TransactionJournal $journal): bool
|
||||
private function convertDepositArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
$user = $journal->user;
|
||||
|
||||
@@ -133,7 +135,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
|
||||
// get the action value, or use the original source name in case the action value is empty:
|
||||
// this becomes a new or existing (expense) account, which is the destination of the new withdrawal.
|
||||
$opposingName = '' === $this->action->action_value ? $sourceAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $sourceAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.destination.Withdrawal');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -141,7 +143,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::EXPENSE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", expense name is "%s"', $this->action->action_value, $opposingName));
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", expense name is "%s"', $actionValue, $opposingName));
|
||||
|
||||
// update source transaction(s) to be the original destination account
|
||||
\DB::table('transactions')
|
||||
@@ -203,7 +205,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function convertTransferArray(TransactionJournal $journal): bool
|
||||
private function convertTransferArray(TransactionJournal $journal, string $actionValue = ''): bool
|
||||
{
|
||||
// find or create expense account.
|
||||
$user = $journal->user;
|
||||
@@ -219,7 +221,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
|
||||
// get the action value, or use the original source name in case the action value is empty:
|
||||
// this becomes a new or existing (expense) account, which is the destination of the new withdrawal.
|
||||
$opposingName = '' === $this->action->action_value ? $destAccount->name : $this->action->action_value;
|
||||
$opposingName = '' === $actionValue ? $destAccount->name : $actionValue;
|
||||
// we check all possible source account types if one exists:
|
||||
$validTypes = config('firefly.expected_source_types.destination.Withdrawal');
|
||||
$opposingAccount = $repository->findByName($opposingName, $validTypes);
|
||||
@@ -227,7 +229,7 @@ class ConvertToWithdrawal implements ActionInterface
|
||||
$opposingAccount = $factory->findOrCreate($opposingName, AccountType::EXPENSE);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", destination name is "%s"', $this->action->action_value, $opposingName));
|
||||
app('log')->debug(sprintf('ConvertToWithdrawal. Action value is "%s", destination name is "%s"', $actionValue, $opposingName));
|
||||
|
||||
// update destination transaction(s) to be new expense account.
|
||||
\DB::table('transactions')
|
||||
|
@@ -54,7 +54,7 @@ class LinkToBill implements ActionInterface
|
||||
/** @var BillRepositoryInterface $repository */
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->setUser($user);
|
||||
$billName = (string)$this->action->action_value;
|
||||
$billName = $this->action->getValue($journal);
|
||||
$bill = $repository->findByName($billName);
|
||||
|
||||
if (null !== $bill && TransactionType::WITHDRAWAL === $journal['transaction_type_type']) {
|
||||
|
@@ -32,6 +32,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
|
||||
/**
|
||||
* Class MoveDescriptionToNotes
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class MoveDescriptionToNotes implements ActionInterface
|
||||
{
|
||||
|
@@ -36,6 +36,7 @@ use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
|
||||
/**
|
||||
* Class MoveNotesToDescription
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class MoveNotesToDescription implements ActionInterface
|
||||
{
|
||||
|
@@ -29,6 +29,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
|
||||
/**
|
||||
* Class PrependDescription.
|
||||
* TODO Can be replaced (and migrated) to action "set description" with a prefilled expression
|
||||
*/
|
||||
class PrependDescription implements ActionInterface
|
||||
{
|
||||
@@ -45,7 +46,7 @@ class PrependDescription implements ActionInterface
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$before = $journal['description'];
|
||||
$after = sprintf('%s%s', $this->action->action_value, $journal['description']);
|
||||
$after = sprintf('%s%s', $this->action->getValue($journal), $journal['description']);
|
||||
\DB::table('transaction_journals')->where('id', $journal['transaction_journal_id'])->limit(1)->update(['description' => $after]);
|
||||
|
||||
// journal
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
|
||||
/**
|
||||
* Class PrependNotes.
|
||||
* TODO Can be replaced (and migrated) to action "set notes" with a prefilled expression
|
||||
*/
|
||||
class PrependNotes implements ActionInterface
|
||||
{
|
||||
@@ -56,8 +57,9 @@ class PrependNotes implements ActionInterface
|
||||
$dbNote->text = '';
|
||||
}
|
||||
$before = $dbNote->text;
|
||||
app('log')->debug(sprintf('RuleAction PrependNotes prepended "%s" to "%s".', $this->action->action_value, $dbNote->text));
|
||||
$text = sprintf('%s%s', $this->action->action_value, $dbNote->text);
|
||||
$after = $this->action->getValue($journal);
|
||||
app('log')->debug(sprintf('RuleAction PrependNotes prepended "%s" to "%s".', $after, $dbNote->text));
|
||||
$text = sprintf('%s%s', $after, $dbNote->text);
|
||||
$dbNote->text = $text;
|
||||
$dbNote->save();
|
||||
|
||||
|
@@ -46,13 +46,13 @@ class RemoveTag implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
// if tag does not exist, no need to continue:
|
||||
$name = $this->action->action_value;
|
||||
$name = $this->action->getValue($journal);
|
||||
|
||||
/** @var User $user */
|
||||
$user = User::find($journal['user_id']);
|
||||
$tag = $user->tags()->where('tag', $name)->first();
|
||||
|
||||
// if tag does not exist, no need to continue:
|
||||
if (null === $tag) {
|
||||
app('log')->debug(
|
||||
sprintf('RuleAction RemoveTag tried to remove tag "%s" from journal #%d but no such tag exists.', $name, $journal['transaction_journal_id'])
|
||||
|
124
app/TransactionRules/Actions/SetAmount.php
Normal file
124
app/TransactionRules/Actions/SetAmount.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/*
|
||||
* SetAmount.php
|
||||
* Copyright (c) 2024 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\TransactionRules\Actions;
|
||||
|
||||
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
|
||||
use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
class SetAmount implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
* TriggerInterface constructor.
|
||||
*/
|
||||
public function __construct(RuleAction $action)
|
||||
{
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$this->refreshNotes($journal);
|
||||
|
||||
// not on slpit transactions
|
||||
$groupCount = TransactionJournal::where('transaction_group_id', $journal['transaction_group_id'])->count();
|
||||
if ($groupCount > 1) {
|
||||
app('log')->error(sprintf('Group #%d has more than one transaction in it, cannot convert to transfer.', $journal['transaction_group_id']));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.split_group')));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $this->action->getValue($journal);
|
||||
|
||||
if (!is_numeric($value) || 0 === bccomp($value, '0')) {
|
||||
app('log')->debug(sprintf('RuleAction SetAmount, amount "%s" is not a number or is zero, will not continue.', $value));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.journal_invalid_amount', ['amount' => $value])));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
|
||||
$positive = app('steam')->positive($value);
|
||||
$negative = app('steam')->negative($value);
|
||||
|
||||
$this->updatePositive($object, $positive);
|
||||
$this->updateNegative($object, $negative);
|
||||
$object->transactionGroup->touch();
|
||||
|
||||
// event for audit log entry
|
||||
event(new TriggeredAuditLog(
|
||||
$this->action->rule,
|
||||
$object,
|
||||
'update_amount',
|
||||
[
|
||||
'currency_symbol' => $object->transactionCurrency->symbol,
|
||||
'decimal_places' => $object->transactionCurrency->decimal_places,
|
||||
'amount' => $journal['amount'],
|
||||
],
|
||||
[
|
||||
'currency_symbol' => $object->transactionCurrency->symbol,
|
||||
'decimal_places' => $object->transactionCurrency->decimal_places,
|
||||
'amount' => $value,
|
||||
]
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function updatePositive(TransactionJournal $object, string $amount): void
|
||||
{
|
||||
/** @var null|Transaction $transaction */
|
||||
$transaction = $object->transactions()->where('amount', '>', 0)->first();
|
||||
if (null === $transaction) {
|
||||
return;
|
||||
}
|
||||
$this->updateAmount($transaction, $amount);
|
||||
}
|
||||
|
||||
private function updateNegative(TransactionJournal $object, string $amount): void
|
||||
{
|
||||
/** @var null|Transaction $transaction */
|
||||
$transaction = $object->transactions()->where('amount', '<', 0)->first();
|
||||
if (null === $transaction) {
|
||||
return;
|
||||
}
|
||||
$this->updateAmount($transaction, $amount);
|
||||
}
|
||||
|
||||
private function updateAmount(Transaction $transaction, string $amount): void
|
||||
{
|
||||
$transaction->amount = $amount;
|
||||
$transaction->save();
|
||||
$transaction->transactionJournal->touch();
|
||||
}
|
||||
}
|
@@ -49,7 +49,7 @@ class SetBudget implements ActionInterface
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = User::find($journal['user_id']);
|
||||
$search = $this->action->action_value;
|
||||
$search = $this->action->getValue($journal);
|
||||
|
||||
$budget = $user->budgets()->where('name', $search)->first();
|
||||
if (null === $budget) {
|
||||
|
@@ -49,7 +49,7 @@ class SetCategory implements ActionInterface
|
||||
{
|
||||
/** @var null|User $user */
|
||||
$user = User::find($journal['user_id']);
|
||||
$search = $this->action->action_value;
|
||||
$search = $this->action->getValue($journal);
|
||||
if (null === $user) {
|
||||
app('log')->error(sprintf('Journal has no valid user ID so action SetCategory("%s") cannot be applied', $search), $journal);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_such_journal')));
|
||||
|
@@ -26,12 +26,14 @@ namespace FireflyIII\TransactionRules\Actions;
|
||||
use FireflyIII\Events\TriggeredAuditLog;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
|
||||
/**
|
||||
* Class SetDescription.
|
||||
*/
|
||||
class SetDescription implements ActionInterface
|
||||
{
|
||||
use RefreshNotesTrait;
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
@@ -44,13 +46,16 @@ class SetDescription implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$this->refreshNotes($journal);
|
||||
|
||||
/** @var TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
$before = $object->description;
|
||||
$after = $this->action->getValue($journal);
|
||||
|
||||
\DB::table('transaction_journals')
|
||||
->where('id', '=', $journal['transaction_journal_id'])
|
||||
->update(['description' => $this->action->action_value])
|
||||
->update(['description' => $after])
|
||||
;
|
||||
|
||||
app('log')->debug(
|
||||
@@ -58,11 +63,11 @@ class SetDescription implements ActionInterface
|
||||
'RuleAction SetDescription changed the description of journal #%d from "%s" to "%s".',
|
||||
$journal['transaction_journal_id'],
|
||||
$journal['description'],
|
||||
$this->action->action_value
|
||||
$after
|
||||
)
|
||||
);
|
||||
$object->refresh();
|
||||
event(new TriggeredAuditLog($this->action->rule, $object, 'update_description', $before, $this->action->action_value));
|
||||
event(new TriggeredAuditLog($this->action->rule, $object, 'update_description', $before, $after));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -51,6 +51,8 @@ class SetDestinationAccount implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$accountName = $this->action->getValue($journal);
|
||||
|
||||
/** @var User $user */
|
||||
$user = User::find($journal['user_id']);
|
||||
|
||||
@@ -68,16 +70,16 @@ class SetDestinationAccount implements ActionInterface
|
||||
$this->repository->setUser($user);
|
||||
|
||||
// if this is a transfer or a deposit, the new destination account must be an asset account or a default account, and it MUST exist:
|
||||
$newAccount = $this->findAssetAccount($type);
|
||||
$newAccount = $this->findAssetAccount($type, $accountName);
|
||||
if ((TransactionType::DEPOSIT === $type || TransactionType::TRANSFER === $type) && null === $newAccount) {
|
||||
app('log')->error(
|
||||
sprintf(
|
||||
'Cant change destination account of journal #%d because no asset account with name "%s" exists.',
|
||||
$object->id,
|
||||
$this->action->action_value
|
||||
$accountName
|
||||
)
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_asset', ['name' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_asset', ['name' => $accountName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -115,7 +117,7 @@ class SetDestinationAccount implements ActionInterface
|
||||
// if this is a withdrawal, the new destination account must be a expense account and may be created:
|
||||
// or it is a liability, in which case it must be returned.
|
||||
if (TransactionType::WITHDRAWAL === $type) {
|
||||
$newAccount = $this->findWithdrawalDestinationAccount();
|
||||
$newAccount = $this->findWithdrawalDestinationAccount($accountName);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('New destination account is #%d ("%s").', $newAccount->id, $newAccount->name));
|
||||
@@ -134,23 +136,23 @@ class SetDestinationAccount implements ActionInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
private function findAssetAccount(string $type): ?Account
|
||||
private function findAssetAccount(string $type, string $accountName): ?Account
|
||||
{
|
||||
// switch on type:
|
||||
$allowed = config(sprintf('firefly.expected_source_types.destination.%s', $type));
|
||||
$allowed = is_array($allowed) ? $allowed : [];
|
||||
app('log')->debug(sprintf('Check config for expected_source_types.destination.%s, result is', $type), $allowed);
|
||||
|
||||
return $this->repository->findByName($this->action->action_value, $allowed);
|
||||
return $this->repository->findByName($accountName, $allowed);
|
||||
}
|
||||
|
||||
private function findWithdrawalDestinationAccount(): Account
|
||||
private function findWithdrawalDestinationAccount(string $accountName): Account
|
||||
{
|
||||
$allowed = config('firefly.expected_source_types.destination.Withdrawal');
|
||||
$account = $this->repository->findByName($this->action->action_value, $allowed);
|
||||
$account = $this->repository->findByName($accountName, $allowed);
|
||||
if (null === $account) {
|
||||
$data = [
|
||||
'name' => $this->action->action_value,
|
||||
'name' => $accountName,
|
||||
'account_type_name' => 'expense',
|
||||
'account_type_id' => null,
|
||||
'virtual_balance' => 0,
|
||||
|
@@ -55,7 +55,8 @@ class SetNotes implements ActionInterface
|
||||
$dbNote->text = '';
|
||||
}
|
||||
$oldNotes = $dbNote->text;
|
||||
$dbNote->text = $this->action->action_value;
|
||||
$newNotes = $this->action->getValue($journal);
|
||||
$dbNote->text = $newNotes;
|
||||
$dbNote->save();
|
||||
|
||||
app('log')->debug(
|
||||
@@ -63,14 +64,14 @@ class SetNotes implements ActionInterface
|
||||
'RuleAction SetNotes changed the notes of journal #%d from "%s" to "%s".',
|
||||
$journal['transaction_journal_id'],
|
||||
$oldNotes,
|
||||
$this->action->action_value
|
||||
$newNotes
|
||||
)
|
||||
);
|
||||
|
||||
/** @var TransactionJournal $object */
|
||||
$object = TransactionJournal::where('user_id', $journal['user_id'])->find($journal['transaction_journal_id']);
|
||||
|
||||
event(new TriggeredAuditLog($this->action->rule, $object, 'update_notes', $oldNotes, $this->action->action_value));
|
||||
event(new TriggeredAuditLog($this->action->rule, $object, 'update_notes', $oldNotes, $newNotes));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -51,6 +51,8 @@ class SetSourceAccount implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$accountName = $this->action->getValue($journal);
|
||||
|
||||
/** @var User $user */
|
||||
$user = User::find($journal['user_id']);
|
||||
|
||||
@@ -67,12 +69,12 @@ class SetSourceAccount implements ActionInterface
|
||||
$this->repository->setUser($user);
|
||||
|
||||
// if this is a transfer or a withdrawal, the new source account must be an asset account or a default account, and it MUST exist:
|
||||
$newAccount = $this->findAssetAccount($type);
|
||||
$newAccount = $this->findAssetAccount($type, $accountName);
|
||||
if ((TransactionType::WITHDRAWAL === $type || TransactionType::TRANSFER === $type) && null === $newAccount) {
|
||||
app('log')->error(
|
||||
sprintf('Cant change source account of journal #%d because no asset account with name "%s" exists.', $object->id, $this->action->action_value)
|
||||
sprintf('Cant change source account of journal #%d because no asset account with name "%s" exists.', $object->id, $accountName)
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_asset', ['name' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_asset', ['name' => $accountName])));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -109,7 +111,7 @@ class SetSourceAccount implements ActionInterface
|
||||
// if this is a deposit, the new source account must be a revenue account and may be created:
|
||||
// or it's a liability
|
||||
if (TransactionType::DEPOSIT === $type) {
|
||||
$newAccount = $this->findDepositSourceAccount();
|
||||
$newAccount = $this->findDepositSourceAccount($accountName);
|
||||
}
|
||||
|
||||
app('log')->debug(sprintf('New source account is #%d ("%s").', $newAccount->id, $newAccount->name));
|
||||
@@ -128,24 +130,24 @@ class SetSourceAccount implements ActionInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
private function findAssetAccount(string $type): ?Account
|
||||
private function findAssetAccount(string $type, string $accountName): ?Account
|
||||
{
|
||||
// switch on type:
|
||||
$allowed = config(sprintf('firefly.expected_source_types.source.%s', $type));
|
||||
$allowed = is_array($allowed) ? $allowed : [];
|
||||
app('log')->debug(sprintf('Check config for expected_source_types.source.%s, result is', $type), $allowed);
|
||||
|
||||
return $this->repository->findByName($this->action->action_value, $allowed);
|
||||
return $this->repository->findByName($accountName, $allowed);
|
||||
}
|
||||
|
||||
private function findDepositSourceAccount(): Account
|
||||
private function findDepositSourceAccount(string $accountName): Account
|
||||
{
|
||||
$allowed = config('firefly.expected_source_types.source.Deposit');
|
||||
$account = $this->repository->findByName($this->action->action_value, $allowed);
|
||||
$account = $this->repository->findByName($accountName, $allowed);
|
||||
if (null === $account) {
|
||||
// create new revenue account with this name:
|
||||
$data = [
|
||||
'name' => $this->action->action_value,
|
||||
'name' => $accountName,
|
||||
'account_type_name' => 'revenue',
|
||||
'account_type_id' => null,
|
||||
'virtual_balance' => 0,
|
||||
|
@@ -50,6 +50,8 @@ class UpdatePiggybank implements ActionInterface
|
||||
|
||||
public function actOnArray(array $journal): bool
|
||||
{
|
||||
$actionValue = $this->action->getValue($journal);
|
||||
|
||||
app('log')->debug(sprintf('Triggered rule action UpdatePiggybank on journal #%d', $journal['transaction_journal_id']));
|
||||
|
||||
// refresh the transaction type.
|
||||
@@ -59,12 +61,12 @@ class UpdatePiggybank implements ActionInterface
|
||||
/** @var TransactionJournal $journalObj */
|
||||
$journalObj = $user->transactionJournals()->find($journal['transaction_journal_id']);
|
||||
|
||||
$piggyBank = $this->findPiggyBank($user);
|
||||
$piggyBank = $this->findPiggyBank($user, $actionValue);
|
||||
if (null === $piggyBank) {
|
||||
app('log')->info(
|
||||
sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $this->action->action_value, $this->action->id, $this->action->rule_id)
|
||||
sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id)
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $actionValue])));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -126,14 +128,14 @@ class UpdatePiggybank implements ActionInterface
|
||||
$destination->account_id
|
||||
)
|
||||
);
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_link_piggy', ['name' => $this->action->action_value])));
|
||||
event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.no_link_piggy', ['name' => $actionValue])));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function findPiggyBank(User $user): ?PiggyBank
|
||||
private function findPiggyBank(User $user, string $name): ?PiggyBank
|
||||
{
|
||||
return $user->piggyBanks()->where('piggy_banks.name', $this->action->action_value)->first();
|
||||
return $user->piggyBanks()->where('piggy_banks.name', $name)->first();
|
||||
}
|
||||
|
||||
private function removeAmount(PiggyBank $piggyBank, TransactionJournal $journal, string $amount): void
|
||||
|
@@ -26,15 +26,18 @@ namespace FireflyIII\TransactionRules\Engine;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\RuleTrigger;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Support\Search\SearchInterface;
|
||||
use FireflyIII\TransactionRules\Factory\ActionFactory;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class SearchRuleEngine
|
||||
@@ -79,8 +82,10 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
}
|
||||
$collection = $collection->merge($found);
|
||||
}
|
||||
$result = $collection->unique();
|
||||
app('log')->debug(sprintf('SearchRuleEngine::find() returns %d unique transactions.', $result->count()));
|
||||
|
||||
return $collection->unique();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,6 +437,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
private function processRuleAction(RuleAction $ruleAction, array $transaction): bool
|
||||
{
|
||||
app('log')->debug(sprintf('Executing rule action "%s" with value "%s"', $ruleAction->action_type, $ruleAction->action_value));
|
||||
$transaction = $this->addNotes($transaction);
|
||||
$actionClass = ActionFactory::getAction($ruleAction);
|
||||
$result = $actionClass->actOnArray($transaction);
|
||||
$journalId = $transaction['transaction_journal_id'] ?? 0;
|
||||
@@ -532,4 +538,16 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function addNotes(array $transaction): array
|
||||
{
|
||||
$transaction['notes'] = '';
|
||||
$dbNote = Note::where('noteable_id', (int)$transaction['transaction_journal_id'])->where('noteable_type', TransactionJournal::class)->first(['notes.*']);
|
||||
if (null !== $dbNote) {
|
||||
$transaction['notes'] = $dbNote->text;
|
||||
}
|
||||
Log::debug(sprintf('Notes of journal #%d filled in.', $transaction['transaction_journal_id']));
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
}
|
||||
|
158
app/TransactionRules/Expressions/ActionExpression.php
Normal file
158
app/TransactionRules/Expressions/ActionExpression.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ActionExpression.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* 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\TransactionRules\Expressions;
|
||||
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Symfony\Component\ExpressionLanguage\SyntaxError;
|
||||
|
||||
class ActionExpression
|
||||
{
|
||||
private static array $NAMES = [
|
||||
// 'transaction_group_id',
|
||||
// 'user_id',
|
||||
// 'user_group_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'transaction_group_title',
|
||||
'group_created_at',
|
||||
'group_updated_at',
|
||||
// 'transaction_journal_id',
|
||||
// 'transaction_type_id',
|
||||
'description',
|
||||
'date',
|
||||
// 'order',
|
||||
'transaction_type_type',
|
||||
// 'source_transaction_id',
|
||||
'source_account_id',
|
||||
// 'reconciled',
|
||||
'amount',
|
||||
// 'currency_id',
|
||||
'currency_code',
|
||||
'currency_name',
|
||||
'currency_symbol',
|
||||
'currency_decimal_places',
|
||||
'foreign_amount',
|
||||
// 'foreign_currency_id',
|
||||
'foreign_currency_code',
|
||||
'foreign_currency_name',
|
||||
'foreign_currency_symbol',
|
||||
'foreign_currency_decimal_places',
|
||||
'destination_account_id',
|
||||
'source_account_name',
|
||||
'source_account_iban',
|
||||
'source_account_type',
|
||||
'destination_account_name',
|
||||
'destination_account_iban',
|
||||
'destination_account_type',
|
||||
'category_id',
|
||||
'category_name',
|
||||
'budget_id',
|
||||
'budget_name',
|
||||
'tags',
|
||||
// 'attachments',
|
||||
'interest_date',
|
||||
'payment_date',
|
||||
'invoice_date',
|
||||
'book_date',
|
||||
'due_date',
|
||||
'process_date',
|
||||
// 'destination_transaction_id',
|
||||
'notes',
|
||||
];
|
||||
|
||||
private ExpressionLanguage $expressionLanguage;
|
||||
private string $expr;
|
||||
private bool $isExpression;
|
||||
private ?SyntaxError $validationError;
|
||||
|
||||
public function __construct(string $expr)
|
||||
{
|
||||
$this->expressionLanguage = app(ExpressionLanguage::class);
|
||||
$this->expr = $expr;
|
||||
|
||||
$this->isExpression = self::isExpression($expr);
|
||||
$this->validationError = $this->validate();
|
||||
}
|
||||
|
||||
private static function isExpression(string $expr): bool
|
||||
{
|
||||
return str_starts_with($expr, '=') && strlen($expr) > 1;
|
||||
}
|
||||
|
||||
private function validate(): ?SyntaxError
|
||||
{
|
||||
if (!$this->isExpression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->lint();
|
||||
|
||||
return null;
|
||||
} catch (SyntaxError $e) {
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function lintExpression(string $expr): void
|
||||
{
|
||||
$this->expressionLanguage->lint($expr, self::$NAMES);
|
||||
}
|
||||
|
||||
private function lint(): void
|
||||
{
|
||||
if (!$this->isExpression) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->lintExpression(substr($this->expr, 1));
|
||||
}
|
||||
|
||||
public function isValid(): bool
|
||||
{
|
||||
return null === $this->validationError;
|
||||
}
|
||||
|
||||
public function getValidationError(): ?SyntaxError
|
||||
{
|
||||
return $this->validationError;
|
||||
}
|
||||
|
||||
private function evaluateExpression(string $expr, array $journal): string
|
||||
{
|
||||
$result = $this->expressionLanguage->evaluate($expr, $journal);
|
||||
|
||||
return (string) $result;
|
||||
}
|
||||
|
||||
public function evaluate(array $journal): string
|
||||
{
|
||||
if (!$this->isExpression) {
|
||||
return $this->expr;
|
||||
}
|
||||
|
||||
return $this->evaluateExpression(substr($this->expr, 1), $journal);
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ActionExpressionLanguageProvider.php
|
||||
* Copyright (c) 2024 Michael Thomas
|
||||
*
|
||||
* 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\TransactionRules\Expressions;
|
||||
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
class ActionExpressionLanguageProvider implements ExpressionFunctionProviderInterface
|
||||
{
|
||||
public function getFunctions(): array
|
||||
{
|
||||
$function = function ($arguments, $str): string {
|
||||
if (!is_string($str)) {
|
||||
return (string) $str;
|
||||
}
|
||||
|
||||
return strtolower($str.'!');
|
||||
};
|
||||
|
||||
return [
|
||||
new ExpressionFunction(
|
||||
'constant2',
|
||||
static function ($str): string {
|
||||
return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str.'!');
|
||||
},
|
||||
$function
|
||||
),
|
||||
new ExpressionFunction(
|
||||
'constant',
|
||||
static function ($str): string {
|
||||
return sprintf('(is_string(%1$s) ? strtolower(%1$s) : %1$s)', $str.'!');
|
||||
},
|
||||
$function
|
||||
),
|
||||
|
||||
ExpressionFunction::fromPhp('substr'),
|
||||
ExpressionFunction::fromPhp('strlen'),
|
||||
];
|
||||
}
|
||||
}
|
43
app/TransactionRules/Traits/RefreshNotesTrait.php
Normal file
43
app/TransactionRules/Traits/RefreshNotesTrait.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/*
|
||||
* RefreshNotesTrait.php
|
||||
* Copyright (c) 2024 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\TransactionRules\Traits;
|
||||
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
trait RefreshNotesTrait
|
||||
{
|
||||
final protected function refreshNotes(array $transaction): array
|
||||
{
|
||||
$transaction['notes'] = '';
|
||||
$dbNote = Note::where('noteable_id', (int)$transaction['transaction_journal_id'])->where('noteable_type', TransactionJournal::class)->first(['notes.*']);
|
||||
if (null !== $dbNote) {
|
||||
$transaction['notes'] = $dbNote->text;
|
||||
}
|
||||
Log::debug(sprintf('Notes of journal #%d refreshed.', $transaction['transaction_journal_id']));
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
}
|
@@ -38,7 +38,7 @@ abstract class AbstractTransformer extends TransformerAbstract
|
||||
/**
|
||||
* This method is called exactly ONCE from FireflyIII\Api\V2\Controllers\Controller::jsonApiList
|
||||
*/
|
||||
abstract public function collectMetaData(Collection $objects): void;
|
||||
abstract public function collectMetaData(Collection $objects): Collection;
|
||||
|
||||
final public function getParameters(): ParameterBag
|
||||
{
|
||||
|
@@ -29,9 +29,11 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Class AccountTransformer
|
||||
@@ -39,6 +41,7 @@ use Illuminate\Support\Collection;
|
||||
class AccountTransformer extends AbstractTransformer
|
||||
{
|
||||
private array $accountMeta;
|
||||
private array $lastActivity;
|
||||
private array $accountTypes;
|
||||
private array $balances;
|
||||
private array $convertedBalances;
|
||||
@@ -48,11 +51,13 @@ class AccountTransformer extends AbstractTransformer
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
// TODO separate methods
|
||||
$this->currencies = [];
|
||||
$this->accountMeta = [];
|
||||
$this->accountTypes = [];
|
||||
$this->lastActivity = [];
|
||||
$this->balances = app('steam')->balancesByAccounts($objects, $this->getDate());
|
||||
$this->convertedBalances = app('steam')->balancesByAccountsConverted($objects, $this->getDate());
|
||||
|
||||
@@ -62,11 +67,12 @@ class AccountTransformer extends AbstractTransformer
|
||||
|
||||
// get currencies:
|
||||
$accountIds = $objects->pluck('id')->toArray();
|
||||
// TODO move query to repository
|
||||
$meta = AccountMeta::whereIn('account_id', $accountIds)
|
||||
->where('name', 'currency_id')
|
||||
->whereIn('name', ['currency_id', 'account_role', 'account_number'])
|
||||
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])
|
||||
;
|
||||
$currencyIds = $meta->pluck('data')->toArray();
|
||||
$currencyIds = $meta->where('name', 'currency_id')->pluck('data')->toArray();
|
||||
|
||||
$currencies = $repository->getByIds($currencyIds);
|
||||
foreach ($currencies as $currency) {
|
||||
@@ -79,6 +85,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
// get account types:
|
||||
// select accounts.id, account_types.type from account_types left join accounts on accounts.account_type_id = account_types.id;
|
||||
// TODO move query to repository
|
||||
$accountTypes = AccountType::leftJoin('accounts', 'accounts.account_type_id', '=', 'account_types.id')
|
||||
->whereIn('accounts.id', $accountIds)
|
||||
->get(['accounts.id', 'account_types.type'])
|
||||
@@ -88,6 +95,53 @@ class AccountTransformer extends AbstractTransformer
|
||||
foreach ($accountTypes as $row) {
|
||||
$this->accountTypes[$row->id] = (string)config(sprintf('firefly.shortNamesByFullName.%s', $row->type));
|
||||
}
|
||||
|
||||
// get last activity
|
||||
// TODO move query to repository
|
||||
$array = Transaction::whereIn('account_id', $accountIds)
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
|
||||
->groupBy('transactions.account_id')
|
||||
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray()
|
||||
;
|
||||
foreach ($array as $row) {
|
||||
$this->lastActivity[(int)$row['account_id']] = Carbon::parse($row['date_max'], config('app.timezone'));
|
||||
}
|
||||
|
||||
// TODO needs separate method.
|
||||
/** @var null|array $sort */
|
||||
$sort = $this->parameters->get('sort');
|
||||
if (null !== $sort && count($sort) > 0) {
|
||||
foreach ($sort as $column => $direction) {
|
||||
// account_number + iban
|
||||
if ('iban' === $column) {
|
||||
$meta = $this->accountMeta;
|
||||
$objects = $objects->sort(function (Account $left, Account $right) use ($meta, $direction) {
|
||||
$leftIban = trim(sprintf('%s%s', $left->iban, $meta[$left->id]['account_number'] ?? ''));
|
||||
$rightIban = trim(sprintf('%s%s', $right->iban, $meta[$right->id]['account_number'] ?? ''));
|
||||
if ('asc' === $direction) {
|
||||
return strcasecmp($leftIban, $rightIban);
|
||||
}
|
||||
|
||||
return strcasecmp($rightIban, $leftIban);
|
||||
});
|
||||
}
|
||||
if ('balance' === $column) {
|
||||
$balances = $this->convertedBalances;
|
||||
$objects = $objects->sort(function (Account $left, Account $right) use ($balances, $direction) {
|
||||
$leftBalance = (float)($balances[$left->id]['native_balance'] ?? 0);
|
||||
$rightBalance = (float)($balances[$right->id]['native_balance'] ?? 0);
|
||||
if ('asc' === $direction) {
|
||||
return $leftBalance <=> $rightBalance;
|
||||
}
|
||||
|
||||
return $rightBalance <=> $leftBalance;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// $objects = $objects->sortByDesc('name');
|
||||
return $objects;
|
||||
}
|
||||
|
||||
private function getDate(): Carbon
|
||||
@@ -114,7 +168,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
|
||||
// no currency? use default
|
||||
$currency = $this->default;
|
||||
if (0 !== (int)$this->accountMeta[$id]['currency_id']) {
|
||||
if (array_key_exists($id, $this->accountMeta) && 0 !== (int)$this->accountMeta[$id]['currency_id']) {
|
||||
$currency = $this->currencies[(int)$this->accountMeta[$id]['currency_id']];
|
||||
}
|
||||
// amounts and calculation.
|
||||
@@ -133,7 +187,8 @@ class AccountTransformer extends AbstractTransformer
|
||||
'active' => $account->active,
|
||||
'order' => $order,
|
||||
'name' => $account->name,
|
||||
'iban' => '' === $account->iban ? null : $account->iban,
|
||||
'iban' => '' === (string)$account->iban ? null : $account->iban,
|
||||
'account_number' => $this->accountMeta[$id]['account_number'] ?? null,
|
||||
'type' => strtolower($accountType),
|
||||
'account_role' => $accountRole,
|
||||
'currency_id' => (string)$currency->id,
|
||||
@@ -152,6 +207,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'current_balance_date' => $this->getDate()->endOfDay()->toAtomString(),
|
||||
|
||||
// more meta
|
||||
'last_activity' => array_key_exists($id, $this->lastActivity) ? $this->lastActivity[$id]->toAtomString() : null,
|
||||
|
||||
// 'notes' => $this->repository->getNoteText($account),
|
||||
// 'monthly_payment_date' => $monthlyPaymentDate,
|
||||
|
@@ -54,7 +54,7 @@ class BillTransformer extends AbstractTransformer
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
*/
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
$currencies = [];
|
||||
$bills = [];
|
||||
@@ -175,6 +175,8 @@ class BillTransformer extends AbstractTransformer
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -37,9 +37,10 @@ class BudgetLimitTransformer extends AbstractTransformer
|
||||
'budget',
|
||||
];
|
||||
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
// TODO: Implement collectMetaData() method.
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -45,9 +45,10 @@ class BudgetTransformer extends AbstractTransformer
|
||||
$this->parameters = new ParameterBag();
|
||||
}
|
||||
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
// TODO: Implement collectMetaData() method.
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -31,7 +31,10 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
class CurrencyTransformer extends AbstractTransformer
|
||||
{
|
||||
public function collectMetaData(Collection $objects): void {}
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the currency.
|
||||
|
@@ -69,7 +69,7 @@ class PiggyBankTransformer extends AbstractTransformer
|
||||
// $this->piggyRepos = app(PiggyBankRepositoryInterface::class);
|
||||
}
|
||||
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
// TODO move to repository (does not exist yet)
|
||||
$piggyBanks = $objects->pluck('id')->toArray();
|
||||
@@ -135,6 +135,8 @@ class PiggyBankTransformer extends AbstractTransformer
|
||||
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
|
||||
$this->default = app('amount')->getDefaultCurrencyByUserGroup(auth()->user()->userGroup);
|
||||
$this->converter = new ExchangeRateConverter();
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -31,9 +31,10 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
class PreferenceTransformer extends AbstractTransformer
|
||||
{
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
// TODO: Implement collectMetaData() method.
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -47,7 +47,7 @@ use Illuminate\Support\Facades\DB;
|
||||
class TransactionGroupTransformer extends AbstractTransformer
|
||||
{
|
||||
private array $accountTypes = []; // account types collection.
|
||||
private ExchangeRateConverter $converter; // collection of all journals and some important meta-data.
|
||||
private ExchangeRateConverter $converter; // collection of all journals and some important meta-data.
|
||||
private array $currencies = [];
|
||||
private TransactionCurrency $default; // collection of all currencies for this transformer.
|
||||
private array $journals = [];
|
||||
@@ -64,7 +64,7 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
// private array $journalCurrencies = [];
|
||||
// private array $foreignCurrencies = [];
|
||||
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
$collectForObjects = false;
|
||||
|
||||
@@ -94,6 +94,8 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
// source accounts
|
||||
// destination accounts
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
private function collectForArray(array $object): void
|
||||
|
@@ -42,7 +42,7 @@ class UserGroupTransformer extends AbstractTransformer
|
||||
$this->memberships = [];
|
||||
}
|
||||
|
||||
public function collectMetaData(Collection $objects): void
|
||||
public function collectMetaData(Collection $objects): Collection
|
||||
{
|
||||
if (auth()->check()) {
|
||||
// collect memberships so they can be listed in the group.
|
||||
@@ -67,6 +67,8 @@ class UserGroupTransformer extends AbstractTransformer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -268,6 +268,11 @@ class FireflyValidator extends Validator
|
||||
return false;
|
||||
}
|
||||
|
||||
// if value is an expression, assume valid
|
||||
if (true === config('firefly.feature_flags.expression_engine') && str_starts_with($value, '=') && strlen($value) > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it's set_budget, verify the budget name:
|
||||
if ('set_budget' === $actionType) {
|
||||
/** @var BudgetRepositoryInterface $repository */
|
||||
|
@@ -36,12 +36,12 @@ bcscale(12);
|
||||
|
||||
if (!function_exists('envNonEmpty')) {
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string|int|bool|null $default
|
||||
* @param string $key
|
||||
* @param string|int|bool|null $default
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
function envNonEmpty(string $key, string|int|bool|null $default = null)
|
||||
function envNonEmpty(string $key, string | int | bool | null $default = null)
|
||||
{
|
||||
$result = env($key, $default);
|
||||
if ('' === $result) {
|
||||
|
@@ -105,13 +105,14 @@
|
||||
"spatie/laravel-html": "^3.2",
|
||||
"spatie/laravel-ignition": "^2",
|
||||
"spatie/period": "^2.4",
|
||||
"symfony/expression-language": "^7.0",
|
||||
"symfony/http-client": "^7.0",
|
||||
"symfony/mailgun-mailer": "^7.0",
|
||||
"therobfonz/laravel-mandrill-driver": "^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.9",
|
||||
"barryvdh/laravel-ide-helper": "2.*",
|
||||
"barryvdh/laravel-ide-helper": "3.*",
|
||||
"ergebnis/phpstan-rules": "^2.1",
|
||||
"fakerphp/faker": "1.*",
|
||||
"filp/whoops": "2.*",
|
||||
@@ -124,8 +125,7 @@
|
||||
"phpunit/phpunit": "^10",
|
||||
"thecodingmachine/phpstan-strict-rules": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
},
|
||||
"suggest": {},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FireflyIII\\": "app/",
|
||||
|
570
composer.lock
generated
570
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use FireflyIII\User;
|
||||
|
||||
if ('ldap' === strtolower((string)env('AUTHENTICATION_GUARD'))) {
|
||||
|
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Diglactic\Breadcrumbs\Generator;
|
||||
use Diglactic\Breadcrumbs\Manager;
|
||||
|
||||
|
@@ -21,8 +21,6 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
$databaseUrl = getenv('DATABASE_URL');
|
||||
$host = '';
|
||||
$username = '';
|
||||
|
@@ -83,6 +83,7 @@ use FireflyIII\TransactionRules\Actions\PrependDescription;
|
||||
use FireflyIII\TransactionRules\Actions\PrependNotes;
|
||||
use FireflyIII\TransactionRules\Actions\RemoveAllTags;
|
||||
use FireflyIII\TransactionRules\Actions\RemoveTag;
|
||||
use FireflyIII\TransactionRules\Actions\SetAmount;
|
||||
use FireflyIII\TransactionRules\Actions\SetBudget;
|
||||
use FireflyIII\TransactionRules\Actions\SetCategory;
|
||||
use FireflyIII\TransactionRules\Actions\SetDescription;
|
||||
@@ -109,13 +110,14 @@ return [
|
||||
],
|
||||
// some feature flags:
|
||||
'feature_flags' => [
|
||||
'export' => true,
|
||||
'telemetry' => false,
|
||||
'webhooks' => true,
|
||||
'handle_debts' => true,
|
||||
'export' => true,
|
||||
'telemetry' => false,
|
||||
'webhooks' => true,
|
||||
'handle_debts' => true,
|
||||
'expression_engine' => false,
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => 'develop/2024-03-04',
|
||||
'version' => 'develop/2024-03-14',
|
||||
'api_version' => '2.0.12',
|
||||
'db_version' => 23,
|
||||
|
||||
@@ -520,6 +522,9 @@ return [
|
||||
'move_notes_to_descr' => MoveNotesToDescription::class,
|
||||
'set_source_to_cash' => SetSourceToCashAccount::class,
|
||||
'set_destination_to_cash' => SetDestinationToCashAccount::class,
|
||||
'set_amount' => SetAmount::class,
|
||||
// 'set_foreign_amount' => SetForeignAmount::class,
|
||||
// 'set_foreign_currency' => SetForeignCurrency::class,
|
||||
],
|
||||
'context-rule-actions' => [
|
||||
'set_category',
|
||||
@@ -917,6 +922,7 @@ return [
|
||||
'sorting' => [
|
||||
'allowed' => [
|
||||
'transactions' => ['description', 'amount'],
|
||||
'accounts' => ['name', 'active', 'iban', 'balance'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use FireflyIII\User;
|
||||
|
||||
return [
|
||||
|
@@ -1,7 +1,6 @@
|
||||
"api_token_env": CROWDIN_TOKEN
|
||||
"api_key_env": CROWDIN_API_KEY
|
||||
"project_identifier_env": CROWDIN_PROJECT_ID
|
||||
"project_id_env": CROWDIN_PROJECT_NR
|
||||
"api_token_env": CROWDIN_TOKEN
|
||||
|
||||
"base_path_env": CROWDIN_BASE_PATH
|
||||
"preserve_hierarchy": false
|
||||
files: [
|
||||
|
735
package-lock.json
generated
735
package-lock.json
generated
@@ -4,6 +4,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@ag-grid-community/client-side-row-model": "^31.0.3",
|
||||
"@ag-grid-community/core": "^31.0.3",
|
||||
@@ -11,7 +12,8 @@
|
||||
"@ag-grid-community/styles": "^31.0.3",
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"alpinejs": "^3.13.3",
|
||||
"admin-lte": "^4.0.0-alpha3",
|
||||
"alpinejs": "^3.13.7",
|
||||
"bootstrap": "^5.3.0",
|
||||
"bootstrap5-autocomplete": "^1.1.22",
|
||||
"bootstrap5-tags": "^1.6.15",
|
||||
@@ -19,7 +21,7 @@
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-chart-sankey": "^0.12.0",
|
||||
"date-fns": "^3.2.0",
|
||||
"i18next": "^23.7.18",
|
||||
"i18next": "^23.10.1",
|
||||
"i18next-chained-backend": "^4.6.2",
|
||||
"i18next-http-backend": "^2.4.2",
|
||||
"i18next-localstorage-backend": "^4.2.0",
|
||||
@@ -29,6 +31,7 @@
|
||||
"devDependencies": {
|
||||
"axios": "^1.6.3",
|
||||
"laravel-vite-plugin": "^0.8.1",
|
||||
"patch-package": "^8.0.0",
|
||||
"sass": "^1.69.6",
|
||||
"vite": "^4.5.2",
|
||||
"vite-plugin-manifest-sri": "^0.1.0"
|
||||
@@ -459,14 +462,40 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||
},
|
||||
"node_modules/@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/admin-lte": {
|
||||
"version": "4.0.0-alpha3",
|
||||
"resolved": "https://registry.npmjs.org/admin-lte/-/admin-lte-4.0.0-alpha3.tgz",
|
||||
"integrity": "sha512-AG2gQ0amzsdNR9+hebBGBJtOth4/GPWNiFPnNqrBLLH/jLQsbTEUnKsGj0Kg4a8aqmxJfpvjdNaK58FCQ16O1g=="
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.13.5",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz",
|
||||
"integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==",
|
||||
"version": "3.13.7",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.7.tgz",
|
||||
"integrity": "sha512-rcTyjTANbsePq1hb7eSekt3qjI94HLGeO6JaRjCssCVbIIc+qBrc7pO5S/+2JB6oojIibjM6FA+xRI3zhGPZIg==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
@@ -486,6 +515,15 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
|
||||
@@ -497,6 +535,12 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -534,6 +578,16 @@
|
||||
"resolved": "https://registry.npmjs.org/bootstrap5-tags/-/bootstrap5-tags-1.7.1.tgz",
|
||||
"integrity": "sha512-qBgtw8E4b+zNfNGPNZAaiC4oR2QSI2OWQYx523S03+ZFXPFUU7fJyMA4ynTYG2CE6hpy3e8HB5Lx9nWn1jrlNA=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
@@ -546,6 +600,41 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz",
|
||||
@@ -598,6 +687,39 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
|
||||
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/sibiraj-s"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -610,6 +732,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||
@@ -618,6 +746,20 @@
|
||||
"node-fetch": "^2.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
|
||||
@@ -627,6 +769,23 @@
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -636,6 +795,27 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||
@@ -685,6 +865,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-yarn-workspace-root": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
@@ -719,6 +908,27 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -733,6 +943,54 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
@@ -745,10 +1003,85 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "23.10.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.0.tgz",
|
||||
"integrity": "sha512-/TgHOqsa7/9abUKJjdPeydoyDc0oTi/7u9F8lMSj6ufg4cbC1Oj3f/Jja7zj7WRIhEQKB7Q4eN6y68I9RDxxGQ==",
|
||||
"version": "23.10.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.1.tgz",
|
||||
"integrity": "sha512-NDiIzFbcs3O9PXpfhkjyf7WdqFn5Vq6mhzhtkXzj51aOcNuPNcTwuYNuXCpHsanZGHlHKL35G7huoFeVic1hng==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -797,6 +1130,22 @@
|
||||
"integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -809,6 +1158,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -839,6 +1203,78 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json-stable-stringify": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz",
|
||||
"integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.5",
|
||||
"isarray": "^2.0.5",
|
||||
"jsonify": "^0.0.1",
|
||||
"object-keys": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonify": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
|
||||
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/klaw-sync": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.1.tgz",
|
||||
@@ -860,6 +1296,31 @@
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@@ -881,6 +1342,27 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
@@ -927,6 +1409,97 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-keys": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0",
|
||||
"is-wsl": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
|
||||
"integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"chalk": "^4.1.2",
|
||||
"ci-info": "^3.7.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"json-stable-stringify": "^1.0.2",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.6",
|
||||
"open": "^7.4.2",
|
||||
"rimraf": "^2.6.3",
|
||||
"semver": "^7.5.3",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.0.33",
|
||||
"yaml": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"patch-package": "index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">5"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
@@ -996,6 +1569,18 @@
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.29.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||
@@ -1029,6 +1614,68 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
@@ -1046,6 +1693,30 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -1063,6 +1734,15 @@
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
@@ -1147,6 +1827,45 @@
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
|
||||
"integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,11 +3,13 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
"build": "vite build",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.6.3",
|
||||
"laravel-vite-plugin": "^0.8.1",
|
||||
"patch-package": "^8.0.0",
|
||||
"sass": "^1.69.6",
|
||||
"vite": "^4.5.2",
|
||||
"vite-plugin-manifest-sri": "^0.1.0"
|
||||
@@ -19,7 +21,8 @@
|
||||
"@ag-grid-community/styles": "^31.0.3",
|
||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"alpinejs": "^3.13.3",
|
||||
"admin-lte": "^4.0.0-alpha3",
|
||||
"alpinejs": "^3.13.7",
|
||||
"bootstrap": "^5.3.0",
|
||||
"bootstrap5-autocomplete": "^1.1.22",
|
||||
"bootstrap5-tags": "^1.6.15",
|
||||
@@ -27,7 +30,7 @@
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-chart-sankey": "^0.12.0",
|
||||
"date-fns": "^3.2.0",
|
||||
"i18next": "^23.7.18",
|
||||
"i18next": "^23.10.1",
|
||||
"i18next-chained-backend": "^4.6.2",
|
||||
"i18next-http-backend": "^2.4.2",
|
||||
"i18next-localstorage-backend": "^4.2.0",
|
||||
|
12
patches/admin-lte+4.0.0-alpha3.patch
Normal file
12
patches/admin-lte+4.0.0-alpha3.patch
Normal file
@@ -0,0 +1,12 @@
|
||||
diff --git a/node_modules/admin-lte/src/scss/_app-sidebar.scss b/node_modules/admin-lte/src/scss/_app-sidebar.scss
|
||||
index 437946b..040bf5d 100644
|
||||
--- a/node_modules/admin-lte/src/scss/_app-sidebar.scss
|
||||
+++ b/node_modules/admin-lte/src/scss/_app-sidebar.scss
|
||||
@@ -582,7 +582,6 @@ body:not(.app-loaded) {
|
||||
|
||||
@if $enable-dark-mode {
|
||||
@include color-mode(dark) {
|
||||
- &.app-sidebar,
|
||||
.app-sidebar {
|
||||
--#{$lte-prefix}sidebar-hover-bg: #{$lte-sidebar-hover-bg-dark};
|
||||
--#{$lte-prefix}sidebar-color: #{$lte-sidebar-color-dark};
|
46
public/build/assets/app-f7a62782.css
Normal file
46
public/build/assets/app-f7a62782.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{f as n}from"./vendor-50e42c6c.js";function e(){return{id:"",name:"",alpine_name:""}}function o(){return{description:[],amount:[],currency_code:[],foreign_amount:[],foreign_currency_code:[],source_account:[],destination_account:[],budget_id:[],category_name:[],piggy_bank_id:[],bill_id:[],tags:[],notes:[],internal_reference:[],external_url:[],latitude:[],longitude:[],zoom_level:[],date:[],interest_date:[],book_date:[],process_date:[],due_date:[],payment_date:[],invoice_date:[]}}function d(){let t=n(new Date,"yyyy-MM-dd HH:mm");return{description:"",amount:"",currency_code:"EUR",foreign_amount:"",foreign_currency_code:"",source_account:e(),destination_account:e(),budget_id:null,category_name:"",piggy_bank_id:null,bill_id:null,tags:[],notes:"",internal_reference:"",external_url:"",hasLocation:!1,latitude:null,longitude:null,zoomLevel:null,date:t,interest_date:"",book_date:"",process_date:"",due_date:"",payment_date:"",invoice_date:"",errors:o()}}export{d as c,o as d};
|
||||
import{f as n}from"./vendor-291d7a70.js";function e(){return{id:"",name:"",alpine_name:""}}function o(){return{description:[],amount:[],currency_code:[],foreign_amount:[],foreign_currency_code:[],source_account:[],destination_account:[],budget_id:[],category_name:[],piggy_bank_id:[],bill_id:[],tags:[],notes:[],internal_reference:[],external_url:[],latitude:[],longitude:[],zoom_level:[],date:[],interest_date:[],book_date:[],process_date:[],due_date:[],payment_date:[],invoice_date:[]}}function d(){let t=n(new Date,"yyyy-MM-dd HH:mm");return{description:"",amount:"",currency_code:"EUR",foreign_amount:"",foreign_currency_code:"",source_account:e(),destination_account:e(),budget_id:null,category_name:"",piggy_bank_id:null,bill_id:null,tags:[],notes:"",internal_reference:"",external_url:"",hasLocation:!1,latitude:null,longitude:null,zoomLevel:null,date:t,interest_date:"",book_date:"",process_date:"",due_date:"",payment_date:"",invoice_date:"",errors:o()}}export{d as c,o as d};
|
1
public/build/assets/dashboard-39513f31.js
Normal file
1
public/build/assets/dashboard-39513f31.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
public/build/assets/get-2d864c96.js
Normal file
1
public/build/assets/get-2d864c96.js
Normal file
@@ -0,0 +1 @@
|
||||
import{a as s}from"./format-money-5a1aa122.js";import{f as n}from"./vendor-291d7a70.js";class c{show(a,t){return s.get("/api/v2/accounts/"+a,{params:t})}index(a){return s.get("/api/v2/accounts",{params:a})}transactions(a,t){const r={page:t.page??1};return t.hasOwnProperty("start")&&(r.start=n(t.start,"y-MM-dd")),t.hasOwnProperty("end")&&(r.end=n(t.end,"y-MM-dd")),s.get("/api/v2/accounts/"+a+"/transactions",{params:r})}}export{c as G};
|
@@ -1 +1 @@
|
||||
import{a as s}from"./format-money-2e5851ad.js";let t=class{list(a){return s.get("/api/v2/subscriptions",{params:a})}paid(a){return s.get("/api/v2/subscriptions/sum/paid",{params:a})}unpaid(a){return s.get("/api/v2/subscriptions/sum/unpaid",{params:a})}};class e{list(a){return s.get("/api/v2/piggy-banks",{params:a})}}export{t as G,e as a};
|
||||
import{a as s}from"./format-money-5a1aa122.js";let t=class{list(a){return s.get("/api/v2/subscriptions",{params:a})}paid(a){return s.get("/api/v2/subscriptions/sum/paid",{params:a})}unpaid(a){return s.get("/api/v2/subscriptions/sum/unpaid",{params:a})}};class e{list(a){return s.get("/api/v2/piggy-banks",{params:a})}}export{t as G,e as a};
|
@@ -1 +1 @@
|
||||
import{a as t}from"./format-money-2e5851ad.js";class n{list(a){return t.get("/api/v2/transactions",{params:a})}infiniteList(a){return t.get("/api/v2/infinite/transactions",{params:a})}show(a,i){return t.get("/api/v2/transactions/"+a,{params:i})}}export{n as G};
|
||||
import{a as t}from"./format-money-5a1aa122.js";class n{list(a){return t.get("/api/v2/transactions",{params:a})}infiniteList(a){return t.get("/api/v2/infinite/transactions",{params:a})}show(a,i){return t.get("/api/v2/transactions/"+a,{params:i})}}export{n as G};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user