Compare commits

...

77 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
Michael Thomas
daddee7806 feat: support action expression parsing, validation, and evaluation 2024-03-06 17:50:16 -05:00
381 changed files with 4918 additions and 76064 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",

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

@@ -63,13 +63,16 @@ class IndexController extends Controller
public function index(IndexRequest $request): JsonResponse
{
$this->repository->resetAccountOrder();
$types = $request->getAccountTypes();
$accounts = $this->repository->getAccountsByType($types);
$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();
$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()

View File

@@ -27,6 +27,7 @@ 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;
/**
@@ -39,6 +40,14 @@ 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.
@@ -48,13 +57,6 @@ class IndexRequest extends FormRequest
return $this->getCarbonDate('date');
}
public function getAccountTypes(): array
{
$type = (string)$this->get('type', 'default');
return $this->mapAccountTypes($type);
}
/**
* The rules that the incoming request must be matched against.
*/

View File

@@ -29,6 +29,7 @@ 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;
/**
@@ -40,6 +41,7 @@ class InfiniteListRequest extends FormRequest
use AccountFilter;
use ChecksLogin;
use ConvertsDataTypes;
use GetSortInstructions;
use TransactionFilter;
public function buildParams(): string
@@ -97,31 +99,6 @@ class InfiniteListRequest extends FormRequest
return 0 === $page || $page > 65536 ? 1 : $page;
}
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;
}
public function getTransactionTypes(): array
{
$type = (string)$this->get('type', 'default');

View File

@@ -36,7 +36,7 @@ trait CollectorProperties
public const string TEST = 'Test';
/** @var array<int, string> */
public array $sorting;
public array $sorting;
private ?int $endRow;
private bool $expandGroupSearch;
private array $fields;

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

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

@@ -239,16 +239,19 @@ class AccountRepository implements AccountRepositoryInterface
public function getAccountsByType(array $types, ?array $sort = []): Collection
{
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
$query = $this->userGroup->accounts();
$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 (0 !== count($sort)) {
foreach ($sort as $param) {
$query->orderBy($param[0], $param[1]);
if (count($sort) > 0) {
foreach ($sort as $column => $direction) {
if (in_array($column, $sortable, true)) {
$query->orderBy(sprintf('accounts.%s', $column), $direction);
}
}
}

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

@@ -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,9 +72,9 @@ class OperatorQuerySearch implements SearchInterface
private GroupCollectorInterface $collector;
private CurrencyRepositoryInterface $currencyRepository;
private array $excludeTags;
private array $includeAnyTags;
private array $includeAnyTags;
// added to fix #8632
private array $includeTags;
private array $includeTags;
private array $invalidOperators;
private int $limit;
private Collection $operators;

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

@@ -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,6 +105,7 @@
"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"
@@ -124,8 +125,7 @@
"phpunit/phpunit": "^10",
"thecodingmachine/phpstan-strict-rules": "^1.0"
},
"suggest": {
},
"suggest": {},
"autoload": {
"psr-4": {
"FireflyIII\\": "app/",

458
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "58ae8806859163b7e368713d917a12e0",
"content-hash": "47ea9b5a493721b23d1463d9d41b6b33",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -2014,16 +2014,16 @@
},
{
"name": "laravel/framework",
"version": "v10.47.0",
"version": "v10.48.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "fce29b8de62733cdecbe12e3bae801f83fff2ea4"
"reference": "32a8bb151d748b579c3794dea53b56bd40dfbbd3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/fce29b8de62733cdecbe12e3bae801f83fff2ea4",
"reference": "fce29b8de62733cdecbe12e3bae801f83fff2ea4",
"url": "https://api.github.com/repos/laravel/framework/zipball/32a8bb151d748b579c3794dea53b56bd40dfbbd3",
"reference": "32a8bb151d748b579c3794dea53b56bd40dfbbd3",
"shasum": ""
},
"require": {
@@ -2071,6 +2071,7 @@
"conflict": {
"carbonphp/carbon-doctrine-types": ">=3.0",
"doctrine/dbal": ">=4.0",
"mockery/mockery": "1.6.8",
"phpunit/phpunit": ">=11.0.0",
"tightenco/collect": "<5.5.33"
},
@@ -2216,7 +2217,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2024-03-05T15:18:36+00:00"
"time": "2024-03-12T16:35:43+00:00"
},
{
"name": "laravel/passport",
@@ -3076,16 +3077,16 @@
},
{
"name": "league/flysystem",
"version": "3.24.0",
"version": "3.25.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "b25a361508c407563b34fac6f64a8a17a8819675"
"reference": "4c44347133618cccd9b3df1729647a1577b4ad99"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b25a361508c407563b34fac6f64a8a17a8819675",
"reference": "b25a361508c407563b34fac6f64a8a17a8819675",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4c44347133618cccd9b3df1729647a1577b4ad99",
"reference": "4c44347133618cccd9b3df1729647a1577b4ad99",
"shasum": ""
},
"require": {
@@ -3113,7 +3114,7 @@
"friendsofphp/php-cs-fixer": "^3.5",
"google/cloud-storage": "^1.23",
"microsoft/azure-storage-blob": "^1.1",
"phpseclib/phpseclib": "^3.0.34",
"phpseclib/phpseclib": "^3.0.36",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.5.11|^10.0",
"sabre/dav": "^4.6.0"
@@ -3150,7 +3151,7 @@
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/3.24.0"
"source": "https://github.com/thephpleague/flysystem/tree/3.25.0"
},
"funding": [
{
@@ -3162,7 +3163,7 @@
"type": "github"
}
],
"time": "2024-02-04T12:10:17+00:00"
"time": "2024-03-09T17:06:45+00:00"
},
{
"name": "league/flysystem-local",
@@ -5514,33 +5515,33 @@
},
{
"name": "rcrowe/twigbridge",
"version": "v0.14.1",
"version": "v0.14.2",
"source": {
"type": "git",
"url": "https://github.com/rcrowe/TwigBridge.git",
"reference": "639345cb32156ff69845ed471bbf0778c52a28b2"
"reference": "6bf5a8fa48eb5d45de0bd5027936796947acfcbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/639345cb32156ff69845ed471bbf0778c52a28b2",
"reference": "639345cb32156ff69845ed471bbf0778c52a28b2",
"url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/6bf5a8fa48eb5d45de0bd5027936796947acfcbc",
"reference": "6bf5a8fa48eb5d45de0bd5027936796947acfcbc",
"shasum": ""
},
"require": {
"illuminate/support": "^6|^7|^8|^9|^10",
"illuminate/view": "^6|^7|^8|^9|^10",
"php": "^7.2.5 || ^8.0",
"illuminate/support": "^9|^10|^11",
"illuminate/view": "^9|^10|^11",
"php": "^8.1",
"twig/twig": "~3.0"
},
"require-dev": {
"ext-json": "*",
"laravel/framework": "^6|^7|^8|^9|^10",
"laravel/framework": "^9|^10|^11",
"mockery/mockery": "^1.3.1",
"phpunit/phpunit": "^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.7",
"phpunit/phpunit": "^8.5.8 || ^9.3.7",
"squizlabs/php_codesniffer": "^3.6"
},
"suggest": {
"laravelcollective/html": "For bringing back html/form in Laravel 5.x",
"laravelcollective/html": "For bringing back html/form in Laravel",
"twig/extensions": "~1.0"
},
"type": "library",
@@ -5584,9 +5585,9 @@
],
"support": {
"issues": "https://github.com/rcrowe/TwigBridge/issues",
"source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.1"
"source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.2"
},
"time": "2023-02-16T14:03:23+00:00"
"time": "2024-03-09T19:41:32+00:00"
},
{
"name": "spatie/backtrace",
@@ -5804,27 +5805,27 @@
},
{
"name": "spatie/laravel-html",
"version": "3.5.0",
"version": "3.6.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-html.git",
"reference": "ead179a8b6802647027486049f5209bd23b610a9"
"reference": "96ee6fc6b6484150c10c23985b3826b066aafc52"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-html/zipball/ead179a8b6802647027486049f5209bd23b610a9",
"reference": "ead179a8b6802647027486049f5209bd23b610a9",
"url": "https://api.github.com/repos/spatie/laravel-html/zipball/96ee6fc6b6484150c10c23985b3826b066aafc52",
"reference": "96ee6fc6b6484150c10c23985b3826b066aafc52",
"shasum": ""
},
"require": {
"illuminate/http": "^9.0|^8.0|^10.0",
"illuminate/support": "^9.0|^8.0|^10.0",
"php": "^8.0"
"illuminate/http": "^10.0|^11.0",
"illuminate/support": "^10.0|^11.0",
"php": "^8.2"
},
"require-dev": {
"mockery/mockery": "^1.3",
"orchestra/testbench": "^7.0|^6.23|^8.0",
"pestphp/pest": "^1.22"
"orchestra/testbench": "^8.0|^9.0",
"pestphp/pest": "^2.34"
},
"type": "library",
"extra": {
@@ -5870,7 +5871,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-html/tree/3.5.0"
"source": "https://github.com/spatie/laravel-html/tree/3.6.0"
},
"funding": [
{
@@ -5878,7 +5879,7 @@
"type": "custom"
}
],
"time": "2024-02-20T15:17:00+00:00"
"time": "2024-03-08T11:56:06+00:00"
},
{
"name": "spatie/laravel-ignition",
@@ -6026,6 +6027,178 @@
},
"time": "2023-02-20T14:31:09+00:00"
},
{
"name": "symfony/cache",
"version": "v7.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
"reference": "fc822951dd360a593224bb2cef90a087d0dff60f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache/zipball/fc822951dd360a593224bb2cef90a087d0dff60f",
"reference": "fc822951dd360a593224bb2cef90a087d0dff60f",
"shasum": ""
},
"require": {
"php": ">=8.2",
"psr/cache": "^2.0|^3.0",
"psr/log": "^1.1|^2|^3",
"symfony/cache-contracts": "^2.5|^3",
"symfony/service-contracts": "^2.5|^3",
"symfony/var-exporter": "^6.4|^7.0"
},
"conflict": {
"doctrine/dbal": "<3.6",
"symfony/dependency-injection": "<6.4",
"symfony/http-kernel": "<6.4",
"symfony/var-dumper": "<6.4"
},
"provide": {
"psr/cache-implementation": "2.0|3.0",
"psr/simple-cache-implementation": "1.0|2.0|3.0",
"symfony/cache-implementation": "1.1|2.0|3.0"
},
"require-dev": {
"cache/integration-tests": "dev-master",
"doctrine/dbal": "^3.6|^4",
"predis/predis": "^1.1|^2.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"symfony/config": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/filesystem": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Cache\\": ""
},
"classmap": [
"Traits/ValueWrapper.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
"homepage": "https://symfony.com",
"keywords": [
"caching",
"psr6"
],
"support": {
"source": "https://github.com/symfony/cache/tree/v7.0.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-02-22T20:27:20+00:00"
},
{
"name": "symfony/cache-contracts",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache-contracts.git",
"reference": "1d74b127da04ffa87aa940abe15446fa89653778"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache-contracts/zipball/1d74b127da04ffa87aa940abe15446fa89653778",
"reference": "1d74b127da04ffa87aa940abe15446fa89653778",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/cache": "^3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Cache\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to caching",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/cache-contracts/tree/v3.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-09-25T12:52:38+00:00"
},
{
"name": "symfony/console",
"version": "v6.4.4",
@@ -6483,6 +6656,69 @@
],
"time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/expression-language",
"version": "v7.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/expression-language.git",
"reference": "0877c599cb260c9614f9229c0a2090d6919fd621"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/expression-language/zipball/0877c599cb260c9614f9229c0a2090d6919fd621",
"reference": "0877c599cb260c9614f9229c0a2090d6919fd621",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/cache": "^6.4|^7.0",
"symfony/service-contracts": "^2.5|^3"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\ExpressionLanguage\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an engine that can compile and evaluate expressions",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/expression-language/tree/v7.0.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-01-23T15:02:46+00:00"
},
{
"name": "symfony/finder",
"version": "v6.4.0",
@@ -8584,6 +8820,80 @@
],
"time": "2024-02-15T11:23:52+00:00"
},
{
"name": "symfony/var-exporter",
"version": "v7.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
"reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
"reference": "dfb0acb6803eb714f05d97dd4c5abe6d5fa9fe41",
"shasum": ""
},
"require": {
"php": ">=8.2"
},
"require-dev": {
"symfony/var-dumper": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\VarExporter\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Allows exporting any serializable PHP data structure to plain PHP code",
"homepage": "https://symfony.com",
"keywords": [
"clone",
"construct",
"export",
"hydrate",
"instantiate",
"lazy-loading",
"proxy",
"serialize"
],
"support": {
"source": "https://github.com/symfony/var-exporter/tree/v7.0.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-02-26T10:35:24+00:00"
},
{
"name": "therobfonz/laravel-mandrill-driver",
"version": "5.0.0",
@@ -8991,23 +9301,23 @@
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.10.6",
"version": "v3.12.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "1fcb37307ebb32207dce16fa160a92b14d8b671f"
"reference": "43555503052443964ce2c1c1f3b0378e58219eb8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/1fcb37307ebb32207dce16fa160a92b14d8b671f",
"reference": "1fcb37307ebb32207dce16fa160a92b14d8b671f",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/43555503052443964ce2c1c1f3b0378e58219eb8",
"reference": "43555503052443964ce2c1c1f3b0378e58219eb8",
"shasum": ""
},
"require": {
"illuminate/routing": "^9|^10|^11",
"illuminate/session": "^9|^10|^11",
"illuminate/support": "^9|^10|^11",
"maximebf/debugbar": "~1.20.1",
"maximebf/debugbar": "~1.21.0",
"php": "^8.0",
"symfony/finder": "^6|^7"
},
@@ -9059,7 +9369,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.10.6"
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.12.2"
},
"funding": [
{
@@ -9071,7 +9381,7 @@
"type": "github"
}
],
"time": "2024-03-01T14:41:13+00:00"
"time": "2024-03-13T09:50:34+00:00"
},
{
"name": "barryvdh/laravel-ide-helper",
@@ -9294,16 +9604,16 @@
},
{
"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": {
@@ -9345,7 +9655,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": [
{
@@ -9361,7 +9671,7 @@
"type": "tidelift"
}
],
"time": "2023-10-11T07:11:09+00:00"
"time": "2024-03-07T15:38:35+00:00"
},
{
"name": "ergebnis/phpstan-rules",
@@ -9653,16 +9963,16 @@
},
{
"name": "maximebf/debugbar",
"version": "v1.20.2",
"version": "v1.21.3",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "484625c23a4fa4f303617f29fcacd42951c9c01d"
"reference": "0b407703b08ea0cf6ebc61e267cc96ff7000911b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/484625c23a4fa4f303617f29fcacd42951c9c01d",
"reference": "484625c23a4fa4f303617f29fcacd42951c9c01d",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0b407703b08ea0cf6ebc61e267cc96ff7000911b",
"reference": "0b407703b08ea0cf6ebc61e267cc96ff7000911b",
"shasum": ""
},
"require": {
@@ -9682,7 +9992,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.20-dev"
"dev-master": "1.21-dev"
}
},
"autoload": {
@@ -9713,13 +10023,13 @@
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.20.2"
"source": "https://github.com/maximebf/php-debugbar/tree/v1.21.3"
},
"time": "2024-02-15T10:49:09+00:00"
"time": "2024-03-12T14:23:07+00:00"
},
{
"name": "mockery/mockery",
"version": "1.6.7",
"version": "1.6.9",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
@@ -10327,16 +10637,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.59",
"version": "1.10.62",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "e607609388d3a6d418a50a49f7940e8086798281"
"reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281",
"reference": "e607609388d3a6d418a50a49f7940e8086798281",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd5c8a1660ed3540b211407c77abf4af193a6af9",
"reference": "cd5c8a1660ed3540b211407c77abf4af193a6af9",
"shasum": ""
},
"require": {
@@ -10385,7 +10695,7 @@
"type": "tidelift"
}
],
"time": "2024-02-20T13:59:13+00:00"
"time": "2024-03-13T12:27:20+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -10486,16 +10796,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.12",
"version": "10.1.14",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "842f72662d6b9edda84c4b6f13885fd9cd53dc63"
"reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/842f72662d6b9edda84c4b6f13885fd9cd53dc63",
"reference": "842f72662d6b9edda84c4b6f13885fd9cd53dc63",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b",
"reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b",
"shasum": ""
},
"require": {
@@ -10552,7 +10862,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.12"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14"
},
"funding": [
{
@@ -10560,7 +10870,7 @@
"type": "github"
}
],
"time": "2024-03-02T07:22:05+00:00"
"time": "2024-03-12T15:33:41+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -10807,16 +11117,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.11",
"version": "10.5.13",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4"
"reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4",
"reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20a63fc1c6db29b15da3bd02d4b6cf59900088a7",
"reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7",
"shasum": ""
},
"require": {
@@ -10888,7 +11198,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.11"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.13"
},
"funding": [
{
@@ -10904,7 +11214,7 @@
"type": "tidelift"
}
],
"time": "2024-02-25T14:05:00+00:00"
"time": "2024-03-12T15:37:41+00:00"
},
{
"name": "sebastian/cli-parser",

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-07',
'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,7 +922,7 @@ return [
'sorting' => [
'allowed' => [
'transactions' => ['description', 'amount'],
'accounts' => ['name'],
'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.6",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.6.tgz",
"integrity": "sha512-/F7pVR+11r1A0KVw+eY1DcjTFlRQn9arD3p5/2Q4vq0N2WDC/dHpg+Pz7ZMiVQlHE7ZmZmcqTRm1wYTdDLMiEg==",
"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-f5e18451.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

View File

@@ -1 +1 @@
import{a as s}from"./format-money-00d5f4b9.js";import{f as n}from"./vendor-f5e18451.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};
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-00d5f4b9.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-00d5f4b9.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};

View File

@@ -0,0 +1 @@
import{d as l,f as d}from"./format-money-5a1aa122.js";import{f as o,i as n}from"./vendor-291d7a70.js";/* empty css */import{G as f}from"./get-2d864c96.js";const r=window.location.href.split("/"),h=r[r.length-1];let b=function(){return{notifications:{error:{show:!1,text:"",url:""},success:{show:!1,text:"",url:""},wait:{show:!1,text:""}},totalPages:1,page:1,tableColumns:{name:{enabled:!0}},sortingColumn:"",sortDirection:"",accounts:[],sort(a){return this.sortingColumn=a,this.sortDirection=this.sortDirection==="asc"?"desc":"asc",this.loadAccounts(),!1},formatMoney(a,e){return d(a,e)},format(a){return o(a,n.t("config.date_time_fns"))},init(){this.notifications.wait.show=!0,this.notifications.wait.text=n.t("firefly.wait_loading_data"),this.loadAccounts()},loadAccounts(){this.notifications.wait.show=!0,this.notifications.wait.text=n.t("firefly.wait_loading_data"),this.accounts=[];const a=[{column:this.sortingColumn,direction:this.sortDirection}];new f().index({sorting:a,type:h,page:this.page}).then(e=>{for(let i=0;i<e.data.data.length;i++)if(e.data.data.hasOwnProperty(i)){let t=e.data.data[i],u={id:parseInt(t.id),active:t.attributes.active,name:t.attributes.name,type:t.attributes.type,role:t.attributes.account_role,iban:t.attributes.iban===null?"":t.attributes.iban.match(/.{1,4}/g).join(" "),account_number:t.attributes.account_number===null?"":t.attributes.account_number,current_balance:t.attributes.current_balance,currency_code:t.attributes.currency_code,native_current_balance:t.attributes.native_current_balance,native_currency_code:t.attributes.native_currency_code,last_activity:t.attributes.last_activity===null?"":o(new Date(t.attributes.last_activity),"P")};this.accounts.push(u)}this.notifications.wait.show=!1})}}},s={index:b,dates:l};function c(){Object.keys(s).forEach(a=>{console.log(`Loading page component "${a}"`);let e=s[a]();Alpine.data(a,()=>e)}),Alpine.start()}document.addEventListener("firefly-iii-bootstrapped",()=>{console.log("Loaded through event listener."),c()});window.bootstrapped&&(console.log("Loaded through window variable."),c());

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{d as l,f as d}from"./format-money-00d5f4b9.js";import{f as c,i as o}from"./vendor-f5e18451.js";/* empty css */import{G as f}from"./get-5b2cde80.js";const i=window.location.href.split("/"),u=i[i.length-1];let h=function(){return{notifications:{error:{show:!1,text:"",url:""},success:{show:!1,text:"",url:""},wait:{show:!1,text:""}},totalPages:1,page:1,tableColumns:{name:{enabled:!0}},accounts:[],formatMoney(t,a){return d(t,a)},format(t){return c(t,o.t("config.date_time_fns"))},init(){this.notifications.wait.show=!0,this.notifications.wait.text=o.t("firefly.wait_loading_data"),this.loadAccounts()},loadAccounts(){new f().index({type:u,page:this.page}).then(t=>{for(let a=0;a<t.data.data.length;a++)if(t.data.data.hasOwnProperty(a)){let e=t.data.data[a],r={id:parseInt(e.id),name:e.attributes.name};this.accounts.push(r)}this.notifications.wait.show=!1})}}},n={index:h,dates:l};function s(){Object.keys(n).forEach(t=>{console.log(`Loading page component "${t}"`);let a=n[t]();Alpine.data(t,()=>a)}),Alpine.start()}document.addEventListener("firefly-iii-bootstrapped",()=>{console.log("Loaded through event listener."),s()});window.bootstrapped&&(console.log("Loaded through window variable."),s());

View File

@@ -1 +1 @@
import{c as o}from"./create-empty-split-c97f45c5.js";import{f as _}from"./vendor-f5e18451.js";function l(a,r){let n=[];for(let i in a)if(a.hasOwnProperty(i)){let e=a[i],t=o();t.transaction_journal_id=e.transaction_journal_id,t.transaction_group_id=r,t.bill_id=e.bill_id,t.bill_name=e.bill_name,t.budget_id=e.budget_id,t.budget_name=e.budget_name,t.category_name=e.category_name,t.category_id=e.category_id,t.piggy_bank_id=e.piggy_bank_id,t.piggy_bank_name=e.piggy_bank_name,t.book_date=e.book_date,t.due_date=e.due_date,t.interest_date=e.interest_date,t.invoice_date=e.invoice_date,t.payment_date=e.payment_date,t.process_date=e.process_date,t.external_url=e.external_url,t.internal_reference=e.internal_reference,t.notes=e.notes,t.tags=e.tags,t.amount=parseFloat(e.amount).toFixed(e.currency_decimal_places),t.currency_code=e.currency_code,e.foreign_amount!==null&&(t.forein_currency_code=e.foreign_currency_code,t.foreign_amount=parseFloat(e.foreign_amount).toFixed(e.foreign_currency_decimal_places)),t.date=_(new Date(e.date),"yyyy-MM-dd HH:mm"),t.description=e.description,t.destination_account={id:e.destination_id,name:e.destination_name,type:e.destination_type,alpine_name:e.destination_name},t.source_account={id:e.source_id,name:e.source_name,type:e.source_type,alpine_name:e.source_name},e.latitude!==null&&(t.hasLocation=!0,t.latitude=e.latitude,t.longitude=e.longitude,t.zoomLevel=e.zoom_level),n.push(t)}return n}export{l as p};
import{c as o}from"./create-empty-split-70d17748.js";import{f as _}from"./vendor-291d7a70.js";function l(a,r){let n=[];for(let i in a)if(a.hasOwnProperty(i)){let e=a[i],t=o();t.transaction_journal_id=e.transaction_journal_id,t.transaction_group_id=r,t.bill_id=e.bill_id,t.bill_name=e.bill_name,t.budget_id=e.budget_id,t.budget_name=e.budget_name,t.category_name=e.category_name,t.category_id=e.category_id,t.piggy_bank_id=e.piggy_bank_id,t.piggy_bank_name=e.piggy_bank_name,t.book_date=e.book_date,t.due_date=e.due_date,t.interest_date=e.interest_date,t.invoice_date=e.invoice_date,t.payment_date=e.payment_date,t.process_date=e.process_date,t.external_url=e.external_url,t.internal_reference=e.internal_reference,t.notes=e.notes,t.tags=e.tags,t.amount=parseFloat(e.amount).toFixed(e.currency_decimal_places),t.currency_code=e.currency_code,e.foreign_amount!==null&&(t.forein_currency_code=e.foreign_currency_code,t.foreign_amount=parseFloat(e.foreign_amount).toFixed(e.foreign_currency_decimal_places)),t.date=_(new Date(e.date),"yyyy-MM-dd HH:mm"),t.description=e.description,t.destination_account={id:e.destination_id,name:e.destination_name,type:e.destination_type,alpine_name:e.destination_name},t.source_account={id:e.source_id,name:e.source_name,type:e.source_type,alpine_name:e.source_name},e.latitude!==null&&(t.hasLocation=!0,t.latitude=e.latitude,t.longitude=e.longitude,t.zoomLevel=e.zoom_level),n.push(t)}return n}export{l as p};

View File

@@ -1 +1 @@
import{a as p}from"./format-money-00d5f4b9.js";class u{put(t,a){let r="/api/v2/transactions/"+parseInt(a.id);return p.put(r,t)}}export{u as P};
import{a as p}from"./format-money-5a1aa122.js";class u{put(t,a){let r="/api/v2/transactions/"+parseInt(a.id);return p.put(r,t)}}export{u as P};

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,65 +1,57 @@
{
"_create-empty-split-c97f45c5.js": {
"file": "assets/create-empty-split-c97f45c5.js",
"_create-empty-split-70d17748.js": {
"file": "assets/create-empty-split-70d17748.js",
"imports": [
"_vendor-f5e18451.js"
],
"integrity": "sha384-wKgs+6aM6DzW9ZXDeQVeN/TZtIlkASgtnnFzSjaSdQ1oFOyhKBuZBr3j2nGoXWRI"
"_vendor-291d7a70.js"
]
},
"_format-money-00d5f4b9.js": {
"file": "assets/format-money-00d5f4b9.js",
"_format-money-5a1aa122.js": {
"file": "assets/format-money-5a1aa122.js",
"imports": [
"_vendor-f5e18451.js"
],
"integrity": "sha384-NwibVlqlZ8kjqF9U76HNU1p/xODJOtH4zTBi6i4OGG4x9PcqhTEbQtoXC4WnYE2u"
"_vendor-291d7a70.js"
]
},
"_get-0097c30a.js": {
"file": "assets/get-0097c30a.js",
"_get-2d864c96.js": {
"file": "assets/get-2d864c96.js",
"imports": [
"_format-money-00d5f4b9.js"
],
"integrity": "sha384-FLOxTBwEkGf7PbJFJGi1kmcf110PY99rnGSOOmJSlnPd4fnsEZXYu4e7Q0gAxmC8"
"_format-money-5a1aa122.js",
"_vendor-291d7a70.js"
]
},
"_get-59bb27e9.js": {
"file": "assets/get-59bb27e9.js",
"_get-316f0281.js": {
"file": "assets/get-316f0281.js",
"imports": [
"_format-money-00d5f4b9.js"
],
"integrity": "sha384-bmtl6LrtsrNh0rSgcWohzOXmDozXSaQ9BLaXchj8IiJAeqULMGFOx/z6olr9Z6I9"
"_format-money-5a1aa122.js"
]
},
"_get-5b2cde80.js": {
"file": "assets/get-5b2cde80.js",
"_get-a398221d.js": {
"file": "assets/get-a398221d.js",
"imports": [
"_format-money-00d5f4b9.js",
"_vendor-f5e18451.js"
],
"integrity": "sha384-Tr9M0BbSz6qynIKHsBlaiZlTWRqPh5K8oIoFNE9eqXvooEeAW2o0thWfuXBrV5s+"
"_format-money-5a1aa122.js"
]
},
"_parse-downloaded-splits-9e5b1aaf.js": {
"file": "assets/parse-downloaded-splits-9e5b1aaf.js",
"_parse-downloaded-splits-ce04c01c.js": {
"file": "assets/parse-downloaded-splits-ce04c01c.js",
"imports": [
"_create-empty-split-c97f45c5.js",
"_vendor-f5e18451.js"
],
"integrity": "sha384-+u7ce40ZJSnmX7AM9MgiCCgIjXt+TkLmWC/p/A7bocN5A5HLNNEgM5UUkecfGDbU"
"_create-empty-split-70d17748.js",
"_vendor-291d7a70.js"
]
},
"_put-d6ed6223.js": {
"file": "assets/put-d6ed6223.js",
"_put-02a25c9f.js": {
"file": "assets/put-02a25c9f.js",
"imports": [
"_format-money-00d5f4b9.js"
],
"integrity": "sha384-AqFeuR/08blTrxpgzmtTKao9mbSJhBT6N/0clyeAgTtSUyH4w0gKJ2MTvt8lOujk"
"_format-money-5a1aa122.js"
]
},
"_splice-errors-into-transactions-bba1ef64.js": {
"file": "assets/splice-errors-into-transactions-bba1ef64.js",
"_splice-errors-into-transactions-5b51d5fd.js": {
"file": "assets/splice-errors-into-transactions-5b51d5fd.js",
"imports": [
"_format-money-00d5f4b9.js",
"_get-0097c30a.js",
"_vendor-f5e18451.js"
],
"integrity": "sha384-ld/QxFbIFbqpxeHPa3i4Rs58YFPmt265gYQThhJqTDxuXYD0vBS5fiMzKYccFI7g"
"_format-money-5a1aa122.js",
"_get-316f0281.js",
"_vendor-291d7a70.js"
]
},
"_vendor-f5e18451.js": {
"_vendor-291d7a70.js": {
"assets": [
"assets/layers-1dbbe9d0.png",
"assets/layers-2x-066daca8.png",
@@ -68,160 +60,140 @@
"css": [
"assets/vendor-52daf6b6.css"
],
"file": "assets/vendor-f5e18451.js",
"integrity": "sha384-BzNMP5uYsiVrJylPJoLh/L+4WMPug3bTJN6sHZQpTe/UM3l1gwxier4DHUeuKNwq"
"file": "assets/vendor-291d7a70.js"
},
"grid-ff3-theme.css": {
"file": "assets/grid-ff3-theme-badb0a41.css",
"src": "grid-ff3-theme.css",
"integrity": "sha384-YOSw8Sq/038Q+it1zgud/qsZiSuNu8tf099eME8psK3OAg1G3FfV063w7Q+LGQj0"
"src": "grid-ff3-theme.css"
},
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf": {
"file": "assets/fa-brands-400-5656d596.ttf",
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf",
"integrity": "sha384-z6ErMQpm9VO+PSzFwaRYqupTfPdf/8FqehfXiAegd1OsCVEwyxGfXRitsOAlSwL8"
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf"
},
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2": {
"file": "assets/fa-brands-400-3a8924cd.woff2",
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2",
"integrity": "sha384-bkcB7e1rvHdmNnhkhQO4nuNk9I0RiTPMW8QUEuAB43yV0a+pj+4wcq0TqdEjkAG0"
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2"
},
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf": {
"file": "assets/fa-regular-400-5d02dc9b.ttf",
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf",
"integrity": "sha384-U8lQh2H28bJCCd0UDMYkUilXo/MkC+X4bagXkhsKG7nSiMTjR7PXEol7FOqOfiRv"
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf"
},
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2": {
"file": "assets/fa-regular-400-2bccecf0.woff2",
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2",
"integrity": "sha384-2hgsGQjjgVclDGAvWslIa+GwYh3Bn/ztuvakT4ICpV95tadfxcw0WMPHsEKjsPVT"
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2"
},
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf": {
"file": "assets/fa-solid-900-fbbf06d7.ttf",
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf",
"integrity": "sha384-2VoAADNdfW/EPlkrhW8xHXpt8CCvqmFA3HH12T2EfU1VBBJfvTPNbKLxMk84zc5O"
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf"
},
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2": {
"file": "assets/fa-solid-900-9fc85f3a.woff2",
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2",
"integrity": "sha384-YxWlWCDksuL6Ljn1HkJNPH8l+jSRIWPpMpPw3pFa0QnmLXjwV/uPwpDm/b9vn/o1"
"src": "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2"
},
"node_modules/leaflet/dist/images/layers-2x.png": {
"file": "assets/layers-2x-066daca8.png",
"src": "node_modules/leaflet/dist/images/layers-2x.png",
"integrity": "sha384-+F2ZWK/HTpkV9kN2HnMGCQOTM/cnQJLs770FLOeHznwVWRfDESI8z4JwcGYmy2Au"
"src": "node_modules/leaflet/dist/images/layers-2x.png"
},
"node_modules/leaflet/dist/images/layers.png": {
"file": "assets/layers-1dbbe9d0.png",
"src": "node_modules/leaflet/dist/images/layers.png",
"integrity": "sha384-80x85ZS+G189o0xL8E8D7BnfhuNss6EwUPHzG7e+qByRD2xnpxikZ6UQU4Re5nNy"
"src": "node_modules/leaflet/dist/images/layers.png"
},
"node_modules/leaflet/dist/images/marker-icon.png": {
"file": "assets/marker-icon-574c3a5c.png",
"src": "node_modules/leaflet/dist/images/marker-icon.png",
"integrity": "sha384-wg83fCOXjBtqzFAWhTL9Sd9vmLUNhfEEzfmNUX9zwv2igKlz/YQbdapF4ObdxF+R"
"src": "node_modules/leaflet/dist/images/marker-icon.png"
},
"resources/assets/v2/pages/accounts/index.js": {
"css": [
"assets/grid-ff3-theme-badb0a41.css"
],
"file": "assets/index-bcaedc78.js",
"file": "assets/index-6c8a02f2.js",
"imports": [
"_format-money-00d5f4b9.js",
"_vendor-f5e18451.js",
"_get-5b2cde80.js"
"_format-money-5a1aa122.js",
"_vendor-291d7a70.js",
"_get-2d864c96.js"
],
"isEntry": true,
"src": "resources/assets/v2/pages/accounts/index.js",
"integrity": "sha384-76KRUQFORlmJ/leXDqMLlPq4/4wiW8vFbtL5qUk+bE86/oKpY8CIuk17zFU1emoS"
"src": "resources/assets/v2/pages/accounts/index.js"
},
"resources/assets/v2/pages/dashboard/dashboard.js": {
"file": "assets/dashboard-41a4b449.js",
"file": "assets/dashboard-39513f31.js",
"imports": [
"_format-money-00d5f4b9.js",
"_vendor-f5e18451.js",
"_get-5b2cde80.js",
"_get-59bb27e9.js",
"_get-0097c30a.js"
"_format-money-5a1aa122.js",
"_vendor-291d7a70.js",
"_get-2d864c96.js",
"_get-a398221d.js",
"_get-316f0281.js"
],
"isEntry": true,
"src": "resources/assets/v2/pages/dashboard/dashboard.js",
"integrity": "sha384-1kybqwSzdo4Qfy5jIkvNgbYJwALV6jnXqKM/aGUAM1sjIJvfGnxK2DjHX4lZN3IZ"
"src": "resources/assets/v2/pages/dashboard/dashboard.js"
},
"resources/assets/v2/pages/transactions/create.js": {
"file": "assets/create-b721a410.js",
"file": "assets/create-b70c5f7e.js",
"imports": [
"_format-money-00d5f4b9.js",
"_create-empty-split-c97f45c5.js",
"_splice-errors-into-transactions-bba1ef64.js",
"_vendor-f5e18451.js",
"_get-0097c30a.js"
"_format-money-5a1aa122.js",
"_create-empty-split-70d17748.js",
"_splice-errors-into-transactions-5b51d5fd.js",
"_vendor-291d7a70.js",
"_get-316f0281.js"
],
"isEntry": true,
"src": "resources/assets/v2/pages/transactions/create.js",
"integrity": "sha384-cAEOcjbtExGHfptyeQTlY4AINMi2a/DH22mgeTzvjnzY2KbZ0UydbBze9XmXNq9v"
"src": "resources/assets/v2/pages/transactions/create.js"
},
"resources/assets/v2/pages/transactions/edit.js": {
"file": "assets/edit-195027dc.js",
"file": "assets/edit-0146de3c.js",
"imports": [
"_format-money-00d5f4b9.js",
"_get-59bb27e9.js",
"_parse-downloaded-splits-9e5b1aaf.js",
"_splice-errors-into-transactions-bba1ef64.js",
"_vendor-f5e18451.js",
"_create-empty-split-c97f45c5.js",
"_put-d6ed6223.js",
"_get-0097c30a.js"
"_format-money-5a1aa122.js",
"_get-a398221d.js",
"_parse-downloaded-splits-ce04c01c.js",
"_splice-errors-into-transactions-5b51d5fd.js",
"_vendor-291d7a70.js",
"_create-empty-split-70d17748.js",
"_put-02a25c9f.js",
"_get-316f0281.js"
],
"isEntry": true,
"src": "resources/assets/v2/pages/transactions/edit.js",
"integrity": "sha384-yZLBSDqjmV3bx4u+464kapaHom6+82OkAyZfuITONyH8DbtMsQ1HrNPTJbeBZ3qN"
"src": "resources/assets/v2/pages/transactions/edit.js"
},
"resources/assets/v2/pages/transactions/index.js": {
"css": [
"assets/grid-ff3-theme-badb0a41.css"
],
"file": "assets/index-943f66ab.js",
"file": "assets/index-80c224c9.js",
"imports": [
"_format-money-00d5f4b9.js",
"_vendor-f5e18451.js",
"_put-d6ed6223.js",
"_get-59bb27e9.js"
"_format-money-5a1aa122.js",
"_vendor-291d7a70.js",
"_put-02a25c9f.js",
"_get-a398221d.js"
],
"isEntry": true,
"src": "resources/assets/v2/pages/transactions/index.js",
"integrity": "sha384-gaqmZQLTNbfSpNmb87PYF8FD5c7jq3Ng+4ghR3Q3aKxOLhOLyBXoVb7ryCnkpfcF"
"src": "resources/assets/v2/pages/transactions/index.js"
},
"resources/assets/v2/pages/transactions/show.css": {
"file": "assets/show-8b1429e5.css",
"src": "resources/assets/v2/pages/transactions/show.css",
"integrity": "sha384-D0+ADFJ1uVTJKTOYOVIjDXI7vzboM0WuC0AjpA2jhwEXxAVGnZTzqFZPuSYWBWiL"
"src": "resources/assets/v2/pages/transactions/show.css"
},
"resources/assets/v2/pages/transactions/show.js": {
"css": [
"assets/show-8b1429e5.css"
],
"file": "assets/show-63e01089.js",
"file": "assets/show-7b497861.js",
"imports": [
"_format-money-00d5f4b9.js",
"_vendor-f5e18451.js",
"_get-59bb27e9.js",
"_parse-downloaded-splits-9e5b1aaf.js",
"_create-empty-split-c97f45c5.js"
"_format-money-5a1aa122.js",
"_vendor-291d7a70.js",
"_get-a398221d.js",
"_parse-downloaded-splits-ce04c01c.js",
"_create-empty-split-70d17748.js"
],
"isEntry": true,
"src": "resources/assets/v2/pages/transactions/show.js",
"integrity": "sha384-x17vRUDjWuxqceuMDNz/325LQPW85BoFU7068e9G3TPT01C1vfmQAfPcYxlWaYxn"
"src": "resources/assets/v2/pages/transactions/show.js"
},
"resources/assets/v2/sass/app.scss": {
"file": "assets/app-fb7b26ec.css",
"file": "assets/app-f7a62782.css",
"isEntry": true,
"src": "resources/assets/v2/sass/app.scss",
"integrity": "sha384-asG3EmbviAZntc1AzgJpoF+jBChn+oq/7eQfYWrCdJ1Ku/c7rJ82sstr6Eptxqgd"
"src": "resources/assets/v2/sass/app.scss"
},
"vendor.css": {
"file": "assets/vendor-52daf6b6.css",
"src": "vendor.css",
"integrity": "sha384-Fc+BMCTQkokqH7JnKp0yiICeLn8STx2f8o4TbcqhtXweJCOlM/cHwVILvX9LkK9t"
"src": "vendor.css"
}
}

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because it is too large Load Diff

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

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