Compare commits

...

91 Commits

Author SHA1 Message Date
github-actions
9ecb414b02 Auto commit for release 'develop' on 2024-03-14 2024-03-14 01:29:53 +01:00
James Cole
ad4f908c24 CI will stop complaining about code base, bi-weekly release picks this up. 2024-03-13 06:52:16 +01:00
James Cole
025f739442 Reformat some code. 2024-03-13 06:51:31 +01:00
James Cole
6df7354c48 Rebuild frontend cause lazy. 2024-03-13 06:51:22 +01:00
James Cole
3f77c845ca Add last activity column 2024-03-13 06:50:08 +01:00
James Cole
d4771f7a5c Remove old configuration file. 2024-03-13 06:29:39 +01:00
James Cole
ec4e2bfa4f Fix https://github.com/firefly-iii/firefly-iii/issues/8663 2024-03-12 20:36:31 +01:00
github-actions
dfdbfae4b5 Auto commit for release 'develop' on 2024-03-11 2024-03-11 06:17:46 +01:00
James Cole
349d38b956 Merge branch 'main' into develop 2024-03-11 06:12:38 +01:00
James Cole
2267aa3ac4 Fix workflow 2024-03-11 06:12:29 +01:00
James Cole
2323aa454e Add git keep 2024-03-11 06:10:17 +01:00
James Cole
8b3317b665 Merge pull request #8658 from firefly-iii/dependabot/composer/develop/symfony/expression-language-7.0.3 2024-03-11 05:26:48 +01:00
James Cole
15f893c343 Merge pull request #8657 from firefly-iii/dependabot/npm_and_yarn/develop/alpinejs-3.13.7 2024-03-11 05:26:21 +01:00
James Cole
309b3e765e Merge pull request #8656 from firefly-iii/dependabot/npm_and_yarn/develop/i18next-23.10.1 2024-03-11 05:26:12 +01:00
dependabot[bot]
d3fad06e00 Bump symfony/expression-language from 6.4.3 to 7.0.3
Bumps [symfony/expression-language](https://github.com/symfony/expression-language) from 6.4.3 to 7.0.3.
- [Release notes](https://github.com/symfony/expression-language/releases)
- [Changelog](https://github.com/symfony/expression-language/blob/7.0/CHANGELOG.md)
- [Commits](https://github.com/symfony/expression-language/compare/v6.4.3...v7.0.3)

---
updated-dependencies:
- dependency-name: symfony/expression-language
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 03:55:59 +00:00
dependabot[bot]
834f24c99c Bump alpinejs from 3.13.6 to 3.13.7
Bumps [alpinejs](https://github.com/alpinejs/alpine/tree/HEAD/packages/alpinejs) from 3.13.6 to 3.13.7.
- [Release notes](https://github.com/alpinejs/alpine/releases)
- [Commits](https://github.com/alpinejs/alpine/commits/v3.13.7/packages/alpinejs)

---
updated-dependencies:
- dependency-name: alpinejs
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 03:39:56 +00:00
dependabot[bot]
35291e1298 Bump i18next from 23.10.0 to 23.10.1
Bumps [i18next](https://github.com/i18next/i18next) from 23.10.0 to 23.10.1.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.10.0...v23.10.1)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 03:39:44 +00:00
James Cole
ac4e9dcbc5 Code cleanup. 2024-03-10 17:15:38 +01:00
James Cole
d57806f2ba Drop hashes 2024-03-10 16:52:59 +01:00
James Cole
3b005c317d Remove 'strict-dynamic' 2024-03-10 16:49:16 +01:00
James Cole
e91903fed2 Different orderRemove self 2024-03-10 16:47:59 +01:00
James Cole
fee2002b0f Remove self 2024-03-10 16:47:36 +01:00
James Cole
f12e502eb8 Fix header 2024-03-10 16:46:33 +01:00
James Cole
24e62b1cee Fix header 2024-03-10 16:45:19 +01:00
James Cole
f559ec73e0 Add exception catch. 2024-03-10 16:44:41 +01:00
James Cole
530b501fcf Disable engine. [skip ci] 2024-03-10 11:57:54 +01:00
James Cole
d5ea78025e Fix a few small bugs and rearrange code. 2024-03-10 11:57:21 +01:00
James Cole
3413b9b5b5 Refresh notes in various actions. 2024-03-10 08:11:58 +01:00
James Cole
0b45c1aa76 Better validation, can now also use notes in expression. 2024-03-10 08:08:26 +01:00
James Cole
5718d1690a Add debug logging 2024-03-10 08:07:47 +01:00
James Cole
67b16cc070 Overrule "constant" and "enum" actions. 2024-03-10 06:46:38 +01:00
James Cole
5746ac3247 Add feature flag for expression engine and disable it by default. 2024-03-10 06:46:24 +01:00
James Cole
8a2c520b11 Update packages 2024-03-10 06:29:24 +01:00
James Cole
f46c14df8c Validation over GET, take precedence over other routes 2024-03-10 06:29:15 +01:00
James Cole
009fbba491 Drop "failedValidation" method because this is handled by the system already. 2024-03-10 06:28:58 +01:00
James Cole
53d84347c2 sprintf the rules 2024-03-10 06:24:32 +01:00
James Cole
1961487055 Reformat code. 2024-03-10 06:17:31 +01:00
James Cole
c9ce5df74b Merge pull request #8650 from michaelhthomas/feat/expression-engine
[feat] Rules Expression Engine
2024-03-10 06:04:06 +01:00
Michael Thomas
1371b6773e chore: ignore PHPMD unused parameter errors 2024-03-09 14:09:36 -05:00
James Cole
b9f1baf150 Update packages 2024-03-09 19:50:46 +01:00
James Cole
66b322e844 Fix methods and clean up code. 2024-03-09 19:46:16 +01:00
James Cole
487b65b669 Rebuild frontend. 2024-03-09 19:33:43 +01:00
James Cole
9078781d61 New endpoint, fixed logo, better account overview. 2024-03-09 19:31:27 +01:00
Michael Thomas
1ec830521a fix: resolve PHPstan errors 2024-03-09 13:02:04 -05:00
Michael Thomas
c4bf2aae7d fix: migrate action expression validation to separate rule class 2024-03-09 12:57:34 -05:00
Michael Thomas
69ca88d9f8 fix(api): use kebab case route for validate-expression endpoint 2024-03-09 12:07:20 -05:00
Michael Thomas
b38b7b2534 fix: drop unnecessary changes to composer.lock 2024-03-09 12:05:56 -05:00
Michael Thomas
f19bfc3b4b fix(ActionExpression): update list of valid variable names to reflect actual values 2024-03-09 12:03:46 -05:00
Michael Thomas
d22f9c09d7 fix(RuleAction): add return type to getValue 2024-03-09 12:02:47 -05:00
Michael Thomas
fc2da9eb42 fix(ExpressionController): remove unnecessary rule repository 2024-03-09 11:27:21 -05:00
James Cole
f2c9e20aef Fix other pages 2024-03-09 13:35:22 +01:00
James Cole
16b8ca2746 Add some spacing 2024-03-09 13:20:43 +01:00
James Cole
46ea074821 Merge branch 'main' into develop 2024-03-09 13:20:03 +01:00
James Cole
d2c89781e2 Rebuild frontend 2024-03-09 13:19:39 +01:00
James Cole
e54d711891 Improve colors. 2024-03-09 13:17:58 +01:00
James Cole
84d3ad4764 Remove unused local files. 2024-03-09 13:12:33 +01:00
James Cole
b908951a2d Refactor views 2024-03-09 13:08:23 +01:00
James Cole
8b87deea58 Refactor error pages 2024-03-09 13:03:02 +01:00
James Cole
0d7325b3dc Fix CSS and JS (on dashboard) 2024-03-09 12:21:45 +01:00
James Cole
a3fd99a498 Rename (unused) files. 2024-03-09 12:11:18 +01:00
James Cole
0ff405d1e0 Refactor views and CSS 2024-03-09 12:11:06 +01:00
James Cole
46a60af966 Clean up authentication views. 2024-03-09 08:13:53 +01:00
James Cole
591c9e3b39 Move old login screen. 2024-03-09 07:00:35 +01:00
James Cole
c30461b20b Update lock.yml
Signed-off-by: James Cole <james@firefly-iii.org>
2024-03-09 05:20:30 +01:00
James Cole
2c3f86d9bc Update lock.yml
Signed-off-by: James Cole <james@firefly-iii.org>
2024-03-09 05:19:17 +01:00
Michael Thomas
34349e4475 chore: fix typo 2024-03-07 21:37:24 -05:00
Michael Thomas
6acd5be5dc chore: remove accidental changes 2024-03-07 21:10:11 -05:00
Michael Thomas
55a2b4e789 feat: make all transaction journal variables globals
removes redundant reference to the `transaction` object by making all its properties global
2024-03-07 20:58:43 -05:00
Michael Thomas
f41397eb43 refactor: add method on RuleAction to compute action value 2024-03-07 19:02:40 -05:00
Michael Thomas
41fc1e8f82 Merge remote-tracking branch 'upstream/develop' into feat/expression-engine 2024-03-07 13:09:43 -05:00
Michael Thomas
bee219ebf7 refactor: inject ExpressionLanguage singleton using DI 2024-03-07 13:00:57 -05:00
Michael Thomas
438f602961 feat: surface expression validation errors when creating or updating rules 2024-03-07 12:23:32 -05:00
James Cole
429e72e681 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-03-07 06:08:09 +01:00
James Cole
7a134781f2 Fix route name. 2024-03-07 06:01:39 +01:00
Michael Thomas
b572c1dcd3 Merge remote-tracking branch 'upstream/main' into feat/expression-engine 2024-03-06 21:38:40 -05:00
Michael Thomas
95593f847b feat: update all rules to support action value expressions 2024-03-06 20:54:50 -05:00
github-actions
b82fcbd97b Auto commit for release 'develop' on 2024-03-07 2024-03-07 01:29:08 +01:00
Michael Thomas
daddee7806 feat: support action expression parsing, validation, and evaluation 2024-03-06 17:50:16 -05:00
James Cole
930a08ec90 Better index for accounts. 2024-03-06 19:54:09 +01:00
James Cole
fd2edf3b23 Various code cleanup. 2024-03-06 07:16:01 +01:00
James Cole
0597255c08 Fix #8632 2024-03-06 07:01:21 +01:00
James Cole
955ab38a85 Merge pull request #8634 from WardenJakx/develop 2024-03-06 06:10:29 +01:00
WardenJakx
1311a0db8b fix broken link 2024-03-05 22:35:23 -05:00
James Cole
0ce9ee6a6c Ignore phpstan error [skip ci] 2024-03-05 19:39:20 +01:00
James Cole
3a339382d4 Add a button to go back to the v1 layout. 2024-03-05 19:38:45 +01:00
James Cole
a5b15bbc16 Rebuild frontend for basic account list. 2024-03-05 05:54:04 +01:00
James Cole
fbf89fd514 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2024-03-04 20:42:05 +01:00
James Cole
b3223feba2 Add debug log 2024-03-04 20:41:59 +01:00
James Cole
88a9bc379e Expand account list 2024-03-04 20:41:34 +01:00
James Cole
b442b91b7c Merge pull request #8621 from firefly-iii/dependabot/composer/develop/barryvdh/laravel-ide-helper-3.0.0 2024-03-04 06:01:38 +01:00
dependabot[bot]
9fadbbe087 Bump barryvdh/laravel-ide-helper from 2.15.1 to 3.0.0
Bumps [barryvdh/laravel-ide-helper](https://github.com/barryvdh/laravel-ide-helper) from 2.15.1 to 3.0.0.
- [Release notes](https://github.com/barryvdh/laravel-ide-helper/releases)
- [Changelog](https://github.com/barryvdh/laravel-ide-helper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/barryvdh/laravel-ide-helper/compare/v2.15.1...v3.0.0)

---
updated-dependencies:
- dependency-name: barryvdh/laravel-ide-helper
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 03:49:45 +00:00
418 changed files with 5738 additions and 76379 deletions

View File

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

View File

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

View File

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

@@ -0,0 +1,2 @@
# code owners for this Firefly III related repository
* @JC5 @SDx3

View File

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

View File

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

View File

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

View 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,
]);
}
}

View File

@@ -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()],

View File

@@ -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()],

View File

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

View File

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

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

View File

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

View 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',
];
}
}

View File

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

View File

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

View File

@@ -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').

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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:
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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(),
]
);
}
}
}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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']) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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'])

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@
*/
declare(strict_types=1);
use FireflyIII\User;
if ('ldap' === strtolower((string)env('AUTHENTICATION_GUARD'))) {

View File

@@ -21,6 +21,7 @@
*/
declare(strict_types=1);
use Diglactic\Breadcrumbs\Generator;
use Diglactic\Breadcrumbs\Manager;

View File

@@ -21,8 +21,6 @@
declare(strict_types=1);
use Illuminate\Support\Str;
$databaseUrl = getenv('DATABASE_URL');
$host = '';
$username = '';

View File

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

View File

@@ -20,6 +20,7 @@
*/
declare(strict_types=1);
use FireflyIII\User;
return [

View File

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

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

View File

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

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

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

View File

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

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

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

View File

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

View File

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