mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-17 11:16:39 +00:00
Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
90109917df | ||
|
0acd54c2b7 | ||
|
c96226b9b4 | ||
|
6d143f1624 | ||
|
93324d1154 | ||
|
a39f0e1891 | ||
|
822f609a22 | ||
|
cd7ddd1c61 | ||
|
0b63ba26bb | ||
|
94d70cdb62 | ||
|
acb3831c8b | ||
|
c9d9ecede4 | ||
|
4eb5873353 | ||
|
7ca39fdb21 | ||
|
b8d1d7a8c0 | ||
|
1af79eab30 | ||
|
03be2704ce | ||
|
34baea66a7 | ||
|
0638d109d0 | ||
|
561e228a2d | ||
|
cb5d856769 | ||
|
04fe5d1fc4 | ||
|
45e9d4f8de | ||
|
73fdbb6202 | ||
|
e49dbefddd | ||
|
fc5143337a | ||
|
4b3eb6dace | ||
|
c741b2a819 | ||
|
cebfaa32bf | ||
|
d356d39d43 | ||
|
7d9f22d3f4 | ||
|
c6c8f282e2 | ||
|
6a64420721 | ||
|
fcde4e2488 | ||
|
aa5c4c20e9 | ||
|
794e31e487 | ||
|
16bf186312 | ||
|
45c722e786 | ||
|
36d9e5c3fe | ||
|
8d614de67f | ||
|
d17da670ab | ||
|
5bf4df9ad8 | ||
|
07db6b59ce | ||
|
e16f1cf4ee | ||
|
4c80d929ca | ||
|
16364d9859 | ||
|
c24f6acb2c | ||
|
4bd19e0627 | ||
|
69d839997a | ||
|
c02c027f4f | ||
|
b14606625e | ||
|
222d7b56c7 | ||
|
b7f7bf42b2 | ||
|
0cbd64d31a | ||
|
60bdae47c4 | ||
|
ca4b38d905 | ||
|
3674465f53 | ||
|
b951d4130c | ||
|
7992b810fd | ||
|
c1c0afa40b | ||
|
56c9026299 | ||
|
021ddfc36b | ||
|
5bd72f6428 | ||
|
feabfe54f0 | ||
|
565409b486 | ||
|
f57366da5f | ||
|
064217ccb0 | ||
|
fa3ccbda33 |
@@ -19,6 +19,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
|
||||
|
||||
$current = __DIR__;
|
||||
|
||||
$paths = [
|
||||
@@ -35,6 +37,7 @@ $finder = PhpCsFixer\Finder::create()
|
||||
|
||||
|
||||
$config = new PhpCsFixer\Config();
|
||||
$config->setParallelConfig(ParallelConfigFactory::detect());
|
||||
return $config->setRules(
|
||||
[
|
||||
// rule sets
|
||||
|
881
.ci/php-cs-fixer/composer.lock
generated
881
.ci/php-cs-fixer/composer.lock
generated
File diff suppressed because it is too large
Load Diff
21
.env.example
21
.env.example
@@ -299,27 +299,6 @@ DKR_BUILD_LOCALE=false
|
||||
# Won't significantly speed up things.
|
||||
DKR_CHECK_SQLITE=true
|
||||
|
||||
# Run database creation and migration commands. Disable this only if you're 100% sure the DB exists
|
||||
# and is up to date.
|
||||
DKR_RUN_MIGRATION=true
|
||||
|
||||
# Run database upgrade commands. Disable this only when you're 100% sure your DB is up-to-date
|
||||
# with the latest fixes (outside of migrations!)
|
||||
DKR_RUN_UPGRADE=true
|
||||
|
||||
# Verify database integrity. Includes all data checks and verifications.
|
||||
# Disabling this makes Firefly III assume your DB is intact.
|
||||
DKR_RUN_VERIFY=true
|
||||
|
||||
# Run database reporting commands. When disabled, Firefly III won't go over your data to report current state.
|
||||
# Disabling this should have no impact on data integrity or safety but it won't warn you of possible issues.
|
||||
DKR_RUN_REPORT=true
|
||||
|
||||
# Generate OAuth2 keys.
|
||||
# When disabled, Firefly III won't attempt to generate OAuth2 Passport keys. This won't be an issue, IFF (if and only if)
|
||||
# you had previously generated keys already and they're stored in your database for restoration.
|
||||
DKR_RUN_PASSPORT_INSTALL=true
|
||||
|
||||
# Leave the following configuration vars as is.
|
||||
# Unless you like to tinker and know what you're doing.
|
||||
APP_NAME=FireflyIII
|
||||
|
2
.github/workflows/close-duplicates.yml
vendored
2
.github/workflows/close-duplicates.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
close_duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: github/command@v1.1.1
|
||||
- uses: github/command@v1.2.0
|
||||
id: command
|
||||
with:
|
||||
allowed_contexts: "issue"
|
||||
|
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -67,6 +67,15 @@ jobs:
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ${{ secrets.CHANGELOG_TOKEN }}
|
||||
- name: "Create THANKS.md"
|
||||
id: thank-you
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
with:
|
||||
action: 'ff3:thank-you'
|
||||
output: ''
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ''
|
||||
- name: Extract changelog
|
||||
id: extract-changelog
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
|
193
THANKS.md
Executable file
193
THANKS.md
Executable file
@@ -0,0 +1,193 @@
|
||||
# Thank you! :tada: :heart: :tada:
|
||||
|
||||
Over time, many people have contributed to Firefly III. Their efforts are not always visible, but always remembered and appreciated.
|
||||
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
|
||||
|
||||
## 2024
|
||||
- imlonghao
|
||||
- Rahman Yusuf
|
||||
- Michael Thomas
|
||||
- WardenJakx
|
||||
- kuilin
|
||||
- Stevie Robinson
|
||||
- luzpaz
|
||||
- Lemuel Roberto Bonifácio
|
||||
- maureenferreira
|
||||
|
||||
## 2023
|
||||
- tieu1991
|
||||
- Maxco10
|
||||
- zqye
|
||||
- Mateus Pereira
|
||||
- josephbadow
|
||||
- Christian Desktop
|
||||
- Edgars
|
||||
- Hannah K
|
||||
- noxonad
|
||||
- Kaijia Feng
|
||||
- Marc Ordinas i Llopis
|
||||
- Kuba Turek
|
||||
- Julien Stébenne
|
||||
|
||||
## 2022
|
||||
- Johannes Zellner
|
||||
- Janne Heß
|
||||
- charlesteets
|
||||
- Nathan PERIER
|
||||
- Jan Willhaus
|
||||
- canoine
|
||||
- Rick Cuddy
|
||||
- James
|
||||
- Hugo Meyronneinc
|
||||
- naveen
|
||||
- neilnaveen
|
||||
- naveensrinivasan
|
||||
- Federico Micelli
|
||||
- George Hahn
|
||||
|
||||
## 2021
|
||||
- StillLoading
|
||||
- Igor Rzegocki
|
||||
- Lorenzo Breda
|
||||
- Hosh
|
||||
- Flightkick
|
||||
- alex6480
|
||||
- VREEdom
|
||||
- Hamza FADIL
|
||||
- Kasper Læssø Sørensen
|
||||
- Alex
|
||||
- Jeroen De Meerleer
|
||||
- Ruben van Erk
|
||||
- Fabian Zimmermann
|
||||
- Mirko Berger
|
||||
- KaihatsuOnline
|
||||
- MihataBG
|
||||
|
||||
## 2020
|
||||
- Hannes Körber
|
||||
- Julien Cassagne
|
||||
- bu4ak
|
||||
- Viktor Yakovlev
|
||||
- Oliver Kaufmann
|
||||
- Arvind Chembarpu
|
||||
- GrayStrider
|
||||
- psychowood
|
||||
- Hosh Sadiq
|
||||
- emansih
|
||||
- Aniruddha Maru
|
||||
- johnny
|
||||
- sephrat
|
||||
- bpatath
|
||||
- Florian Dupret
|
||||
- Maxim Kurbatov
|
||||
- Lucas Guima
|
||||
- Sandro
|
||||
- Ruben Verhoef
|
||||
- Daniel Idzerda
|
||||
- Calum Smith
|
||||
- Agraphie
|
||||
- Tomer Shvueli
|
||||
- Tomer S
|
||||
|
||||
## 2019
|
||||
- Pascal Jungblut
|
||||
- Justyn Shull
|
||||
- Timendum
|
||||
- Nicolas Lœuillet
|
||||
- Dominic Guhl
|
||||
- Melroy van den Berg
|
||||
- Henning Stein
|
||||
- Jan Klepek
|
||||
- Jonathan
|
||||
- Geoffrey “Frogeye” Preud'homme
|
||||
- Michael Fix
|
||||
- Juraj Mlich
|
||||
- Eddybrando Vásquez
|
||||
- hulloanson
|
||||
- Will Rouesnel
|
||||
- lastlink
|
||||
- Mr. Funk
|
||||
- Simon Taddiken
|
||||
- Joris
|
||||
- Bastiaan Nijkamp
|
||||
|
||||
## 2018
|
||||
- a1ex4
|
||||
- Daniel Quah
|
||||
- Marco Lourenço
|
||||
- Dennis Enderink
|
||||
- Luca Bognolo
|
||||
- Mike Conway
|
||||
- Ben
|
||||
- Mathieu Post
|
||||
- George Hertz
|
||||
- HamuZ HamuZ
|
||||
- David Meiseles
|
||||
- Erik Gelderblom
|
||||
- Luca Vallerini
|
||||
- Clemens Wijnekus
|
||||
- Jacob Weisz
|
||||
- Mateusz Gozdek
|
||||
- anmol26s
|
||||
- Kevin Hellemun
|
||||
- Shashank M Chakravarthy
|
||||
- Nico Schreiner
|
||||
- Paul Sohier
|
||||
- Brenden Conte
|
||||
- Ben Yanke
|
||||
- Andrew Prokhorenkov
|
||||
- devlearner
|
||||
- Kelvin
|
||||
- J'informatique
|
||||
|
||||
## 2017
|
||||
- Victor Mosin
|
||||
- Justin
|
||||
- Hugo van Duijn
|
||||
- Lukas Winkler
|
||||
- Marcin Szymanski
|
||||
- Jens Kat
|
||||
- koziolek
|
||||
- jleeong
|
||||
- Simon Hanna
|
||||
- richard & xeli.eu
|
||||
- Sergey Besedin
|
||||
- Welbert Serra
|
||||
- Joris de Vries
|
||||
- Patrick Kostjens
|
||||
- Enrico Lamperti
|
||||
- Christian Musa
|
||||
- Enno Lohmeier
|
||||
|
||||
## 2016
|
||||
- Sander
|
||||
- Toon Schoenmakers
|
||||
- Telyn
|
||||
- Sander Kleykens
|
||||
- Tom van der Werf
|
||||
- Matthew Peck
|
||||
- Sander Mulders
|
||||
- Bonno Nachtegaal-Karels
|
||||
- Niek Haarman
|
||||
- Edwin
|
||||
- Thijs Alkemade
|
||||
- zjean
|
||||
- Graham Miller
|
||||
- Robert Horlings
|
||||
- leander091
|
||||
|
||||
## 2015
|
||||
- Antonio Spinelli
|
||||
- Colin O'Dell
|
||||
- RonaldvanMeer
|
||||
- Richard Ebbers
|
||||
- Balazs Varkonyi
|
||||
- Niek van der Kooy
|
||||
- Ilya Kil
|
||||
|
||||
## 2014
|
||||
- Stewart Malik
|
||||
- Graham Campbell
|
||||
|
||||
|
||||
Thank you for all your support!
|
@@ -32,6 +32,7 @@ use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Transformers\PreferenceTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use League\Fractal\Resource\Collection as FractalCollection;
|
||||
use League\Fractal\Resource\Item;
|
||||
@@ -97,6 +98,32 @@ class PreferencesController extends Controller
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO This endpoint is not documented.
|
||||
*
|
||||
* Return a single preference by name.
|
||||
*/
|
||||
public function showList(Collection $collection): JsonResponse
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
$count = $collection->count();
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$preferences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($preferences, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.preferences.show-list').$this->buildParams());
|
||||
|
||||
/** @var PreferenceTransformer $transformer */
|
||||
$transformer = app(PreferenceTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($preferences, $transformer, self::RESOURCE_KEY);
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/preferences/storePreference
|
||||
|
@@ -63,7 +63,7 @@ class StoreRequest extends FormRequest
|
||||
'order' => $this->convertInteger('order'),
|
||||
'currency_code' => $this->convertString('currency_code'),
|
||||
'virtual_balance' => $this->convertString('virtual_balance'),
|
||||
'iban' => $this->convertString('iban'),
|
||||
'iban' => $this->convertIban('iban'),
|
||||
'BIC' => $this->convertString('bic'),
|
||||
'account_number' => $this->convertString('account_number'),
|
||||
'account_role' => $this->convertString('account_role'),
|
||||
|
@@ -51,7 +51,7 @@ class UpdateRequest extends FormRequest
|
||||
'include_net_worth' => ['include_net_worth', 'boolean'],
|
||||
'account_type_name' => ['type', 'convertString'],
|
||||
'virtual_balance' => ['virtual_balance', 'convertString'],
|
||||
'iban' => ['iban', 'convertString'],
|
||||
'iban' => ['iban', 'convertIban'],
|
||||
'BIC' => ['bic', 'convertString'],
|
||||
'account_number' => ['account_number', 'convertString'],
|
||||
'account_role' => ['account_role', 'convertString'],
|
||||
|
@@ -103,14 +103,14 @@ class StoreRequest extends FormRequest
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string)$object['source_id']),
|
||||
'source_name' => $this->clearString((string)$object['source_name']),
|
||||
'source_iban' => $this->clearString((string)$object['source_iban']),
|
||||
'source_iban' => $this->clearIban((string)$object['source_iban']),
|
||||
'source_number' => $this->clearString((string)$object['source_number']),
|
||||
'source_bic' => $this->clearString((string)$object['source_bic']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
|
||||
'destination_name' => $this->clearString((string)$object['destination_name']),
|
||||
'destination_iban' => $this->clearString((string)$object['destination_iban']),
|
||||
'destination_iban' => $this->clearIban((string)$object['destination_iban']),
|
||||
'destination_number' => $this->clearString((string)$object['destination_number']),
|
||||
'destination_bic' => $this->clearString((string)$object['destination_bic']),
|
||||
|
||||
|
@@ -28,22 +28,21 @@ use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Autocomplete\AutocompleteRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Models\AccountBalance;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AccountController
|
||||
*/
|
||||
class AccountController extends Controller
|
||||
{
|
||||
use AccountFilter;
|
||||
|
||||
private AdminAccountRepositoryInterface $adminRepository;
|
||||
private array $balanceTypes;
|
||||
private AccountRepositoryInterface $repository;
|
||||
private AccountRepositoryInterface $repository;
|
||||
private TransactionCurrency $default;
|
||||
private ExchangeRateConverter $converter;
|
||||
|
||||
/**
|
||||
* AccountController constructor.
|
||||
@@ -53,83 +52,88 @@ class AccountController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->adminRepository = app(AdminAccountRepositoryInterface::class);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->adminRepository->setUserGroup($userGroup);
|
||||
}
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->default = app('amount')->getDefaultCurrency();
|
||||
$this->converter = app(ExchangeRateConverter::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
$this->balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation for this endpoint:
|
||||
* TODO list of checks
|
||||
* 1. use dates from ParameterBag
|
||||
* 2. Request validates dates
|
||||
* 3. Request includes user_group_id
|
||||
* 4. Endpoint is documented.
|
||||
* 5. Collector uses user_group_id
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @throws FireflyException
|
||||
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getAccountsAC
|
||||
*/
|
||||
public function accounts(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->getData();
|
||||
$types = $data['types'];
|
||||
$query = $data['query'];
|
||||
$date = $this->parameters->get('date') ?? today(config('app.timezone'));
|
||||
$result = $this->adminRepository->searchAccount((string)$query, $types, $data['limit']);
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$groupedResult = [];
|
||||
$allItems = [];
|
||||
$queryParameters = $request->getParameters();
|
||||
$result = $this->repository->searchAccount($queryParameters['query'], $queryParameters['account_types'], $queryParameters['size']);
|
||||
$return = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
$nameWithBalance = $account->name;
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? $defaultCurrency;
|
||||
|
||||
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
|
||||
$balance = app('steam')->balance($account, $date);
|
||||
$nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false));
|
||||
}
|
||||
$type = (string)trans(sprintf('firefly.%s', $account->accountType->type));
|
||||
$groupedResult[$type] ??= [
|
||||
'group ' => $type,
|
||||
'items' => [],
|
||||
];
|
||||
$allItems[] = [
|
||||
'id' => (string)$account->id,
|
||||
'value' => (string)$account->id,
|
||||
'name' => $account->name,
|
||||
'name_with_balance' => $nameWithBalance,
|
||||
'label' => $nameWithBalance,
|
||||
'type' => $account->accountType->type,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
];
|
||||
$return[] = $this->parseAccount($account);
|
||||
}
|
||||
|
||||
usort(
|
||||
$allItems,
|
||||
static function (array $left, array $right): int {
|
||||
$order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
|
||||
$posLeft = (int)array_search($left['type'], $order, true);
|
||||
$posRight = (int)array_search($right['type'], $order, true);
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
return $posLeft - $posRight;
|
||||
private function parseAccount(Account $account): array
|
||||
{
|
||||
$currency = $this->repository->getAccountCurrency($account);
|
||||
|
||||
return [
|
||||
'id' => (string) $account->id,
|
||||
'title' => $account->name,
|
||||
'meta' => [
|
||||
'type' => $account->accountType->type,
|
||||
'currency_id' => null === $currency ? null : (string) $currency->id,
|
||||
'currency_code' => $currency?->code,
|
||||
'currency_symbol' => $currency?->symbol,
|
||||
'currency_decimal_places' => $currency?->decimal_places,
|
||||
'account_balances' => $this->getAccountBalances($account),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getAccountBalances(Account $account): array
|
||||
{
|
||||
$return = [];
|
||||
$balances = $this->repository->getAccountBalances($account);
|
||||
|
||||
/** @var AccountBalance $balance */
|
||||
foreach ($balances as $balance) {
|
||||
try {
|
||||
$return[] = $this->parseAccountBalance($balance);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error(sprintf('Could not parse convert account balance: %s', $e->getMessage()));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return response()->json($allItems);
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function parseAccountBalance(AccountBalance $balance): array
|
||||
{
|
||||
$currency = $balance->transactionCurrency;
|
||||
|
||||
return [
|
||||
'title' => $balance->title,
|
||||
'native_amount' => $this->converter->convert($currency, $this->default, today(), $balance->balance),
|
||||
'amount' => app('steam')->bcround($balance->balance, $currency->decimal_places),
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'native_currency_id' => (string) $this->default->id,
|
||||
'native_currency_code' => $this->default->code,
|
||||
'native_currency_symbol' => $this->default->symbol,
|
||||
'native_currency_decimal_places' => $this->default->decimal_places,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -45,11 +45,7 @@ class CategoryController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(CategoryRepositoryInterface::class);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -57,23 +53,18 @@ class CategoryController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation for this endpoint:
|
||||
* TODO list of checks
|
||||
* 1. use dates from ParameterBag
|
||||
* 2. Request validates dates
|
||||
* 3. Request includes user_group_id
|
||||
* 4. Endpoint is documented.
|
||||
* 5. Collector uses user_group_id
|
||||
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getCategoriesAC
|
||||
*/
|
||||
public function categories(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->getData();
|
||||
$result = $this->repository->searchCategory($data['query'], $this->parameters->get('limit'));
|
||||
$filtered = $result->map(
|
||||
$queryParameters = $request->getParameters();
|
||||
$result = $this->repository->searchCategory($queryParameters['query'], $queryParameters['size']);
|
||||
$filtered = $result->map(
|
||||
static function (Category $item) {
|
||||
return [
|
||||
'id' => (string)$item->id,
|
||||
'name' => $item->name,
|
||||
'id' => (string)$item->id,
|
||||
'title' => $item->name,
|
||||
'meta' => [],
|
||||
];
|
||||
}
|
||||
);
|
||||
|
@@ -45,11 +45,7 @@ class TagController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(TagRepositoryInterface::class);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -57,25 +53,20 @@ class TagController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation for this endpoint:
|
||||
* TODO list of checks
|
||||
* 1. use dates from ParameterBag
|
||||
* 2. Request validates dates
|
||||
* 3. Request includes user_group_id
|
||||
* 4. Endpoint is documented.
|
||||
* 5. Collector uses user_group_id
|
||||
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getTagsAC
|
||||
*/
|
||||
public function tags(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->getData();
|
||||
$result = $this->repository->searchTag($data['query'], $data['limit']);
|
||||
$filtered = $result->map(
|
||||
$queryParameters = $request->getParameters();
|
||||
$result = $this->repository->searchTag($queryParameters['query'], $queryParameters['size']);
|
||||
$filtered = $result->map(
|
||||
static function (Tag $item) {
|
||||
return [
|
||||
'id' => (string)$item->id,
|
||||
'name' => $item->tag,
|
||||
'value' => (string)$item->id,
|
||||
'id' => (string) $item->id,
|
||||
'title' => $item->tag,
|
||||
'value' => (string) $item->id,
|
||||
'label' => $item->tag,
|
||||
'meta' => [],
|
||||
];
|
||||
}
|
||||
);
|
||||
|
@@ -45,11 +45,7 @@ class TransactionController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(JournalRepositoryInterface::class);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -57,30 +53,25 @@ class TransactionController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Documentation for this endpoint:
|
||||
* TODO list of checks
|
||||
* 1. use dates from ParameterBag
|
||||
* 2. Request validates dates
|
||||
* 3. Request includes user_group_id
|
||||
* 4. Endpoint is documented.
|
||||
* 5. Collector uses user_group_id
|
||||
* Documentation: https://api-docs.firefly-iii.org/?urls.primaryName=2.1.0%20(v2)#/autocomplete/getTransactionsAC
|
||||
*/
|
||||
public function transactionDescriptions(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->getData();
|
||||
$result = $this->repository->searchJournalDescriptions($data['query'], $data['limit']);
|
||||
$queryParameters = $request->getParameters();
|
||||
$result = $this->repository->searchJournalDescriptions($queryParameters['query'], $queryParameters['size']);
|
||||
|
||||
// limit and unique
|
||||
$filtered = $result->unique('description');
|
||||
$array = [];
|
||||
$filtered = $result->unique('description');
|
||||
$array = [];
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($filtered as $journal) {
|
||||
$array[] = [
|
||||
'id' => (string)$journal->id,
|
||||
'transaction_group_id' => (string)$journal->transaction_group_id,
|
||||
'name' => $journal->description,
|
||||
'description' => $journal->description,
|
||||
'id' => (string) $journal->id,
|
||||
'title' => $journal->description,
|
||||
'meta' => [
|
||||
'transaction_group_id' => (string) $journal->transaction_group_id,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,6 @@ namespace FireflyIII\Api\V2\Controllers\Chart;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Chart\DashboardChartRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
@@ -47,7 +46,6 @@ class AccountController extends Controller
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
private AccountRepositoryInterface $repository;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -55,8 +53,7 @@ class AccountController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -75,8 +72,6 @@ class AccountController extends Controller
|
||||
* TODO validate and set user_group_id from request
|
||||
*
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function dashboard(DashboardChartRequest $request): JsonResponse
|
||||
{
|
||||
|
@@ -64,12 +64,9 @@ class BudgetController extends Controller
|
||||
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
|
||||
$this->opsRepository = app(OperationsRepositoryInterface::class);
|
||||
$this->currency = app('amount')->getDefaultCurrency();
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->opsRepository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
$this->opsRepository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -124,11 +121,11 @@ class BudgetController extends Controller
|
||||
foreach ($rows as $row) {
|
||||
$current = [
|
||||
'label' => $budget->name,
|
||||
'currency_id' => (string)$row['currency_id'],
|
||||
'currency_id' => (string) $row['currency_id'],
|
||||
'currency_code' => $row['currency_code'],
|
||||
'currency_name' => $row['currency_name'],
|
||||
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||
'native_currency_id' => (string)$row['native_currency_id'],
|
||||
'native_currency_id' => (string) $row['native_currency_id'],
|
||||
'native_currency_code' => $row['native_currency_code'],
|
||||
'native_currency_name' => $row['native_currency_name'],
|
||||
'native_currency_decimal_places' => $row['native_currency_decimal_places'],
|
||||
@@ -189,12 +186,12 @@ class BudgetController extends Controller
|
||||
foreach ($array as $currencyId => $block) {
|
||||
$this->currencies[$currencyId] ??= TransactionCurrency::find($currencyId);
|
||||
$return[$currencyId] ??= [
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_id' => (string) $currencyId,
|
||||
'currency_code' => $block['currency_code'],
|
||||
'currency_name' => $block['currency_name'],
|
||||
'currency_symbol' => $block['currency_symbol'],
|
||||
'currency_decimal_places' => (int)$block['currency_decimal_places'],
|
||||
'native_currency_id' => (string)$this->currency->id,
|
||||
'currency_decimal_places' => (int) $block['currency_decimal_places'],
|
||||
'native_currency_id' => (string) $this->currency->id,
|
||||
'native_currency_code' => $this->currency->code,
|
||||
'native_currency_name' => $this->currency->name,
|
||||
'native_currency_symbol' => $this->currency->symbol,
|
||||
|
@@ -57,10 +57,7 @@ class CategoryController extends Controller
|
||||
function ($request, $next) {
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->accountRepos->setUserGroup($userGroup);
|
||||
}
|
||||
$this->accountRepos->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -100,25 +97,25 @@ class CategoryController extends Controller
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
|
||||
$currencies[$currencyId] = $currency;
|
||||
$categoryName = null === $journal['category_name'] ? (string)trans('firefly.no_category') : $journal['category_name'];
|
||||
$categoryName = null === $journal['category_name'] ? (string) trans('firefly.no_category') : $journal['category_name'];
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
$nativeAmount = $converter->convert($default, $currency, $journal['date'], $amount);
|
||||
$key = sprintf('%s-%s', $categoryName, $currency->code);
|
||||
if ((int)$journal['foreign_currency_id'] === $default->id) {
|
||||
if ((int) $journal['foreign_currency_id'] === $default->id) {
|
||||
$nativeAmount = app('steam')->positive($journal['foreign_amount']);
|
||||
}
|
||||
// create arrays
|
||||
$return[$key] ??= [
|
||||
'label' => $categoryName,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'native_currency_id' => (string)$default->id,
|
||||
'native_currency_id' => (string) $default->id,
|
||||
'native_currency_code' => $default->code,
|
||||
'native_currency_name' => $default->name,
|
||||
'native_currency_symbol' => $default->symbol,
|
||||
@@ -138,7 +135,7 @@ class CategoryController extends Controller
|
||||
|
||||
// order by native amount
|
||||
usort($return, static function (array $a, array $b) {
|
||||
return (float)$a['native_amount'] < (float)$b['native_amount'] ? 1 : -1;
|
||||
return (float) $a['native_amount'] < (float) $b['native_amount'] ? 1 : -1;
|
||||
});
|
||||
$converter->summarize();
|
||||
|
||||
|
@@ -157,6 +157,9 @@ class Controller extends BaseController
|
||||
{
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v2';
|
||||
|
||||
// TODO add stuff to path?
|
||||
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$objects = $paginator->getCollection();
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\AccountTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
@@ -57,21 +58,35 @@ class IndexController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO the sort instructions need proper repeatable documentation.
|
||||
* TODO see autocomplete/account controller for list.
|
||||
*/
|
||||
public function index(IndexRequest $request): JsonResponse
|
||||
{
|
||||
$this->repository->resetAccountOrder();
|
||||
$types = $request->getAccountTypes();
|
||||
$instructions = $request->getSortInstructions('accounts');
|
||||
$accounts = $this->repository->getAccountsByType($types, $instructions);
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$count = $accounts->count();
|
||||
$accounts = $accounts->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
||||
$transformer = new AccountTransformer();
|
||||
$types = $request->getAccountTypes();
|
||||
$sorting = $request->getSortInstructions('accounts');
|
||||
$filters = $request->getFilterInstructions('accounts');
|
||||
$accounts = $this->repository->getAccountsByType($types, $sorting, $filters);
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$count = $accounts->count();
|
||||
|
||||
$this->parameters->set('sort', $instructions);
|
||||
// depending on the sort parameters, this list must not be split, because the
|
||||
// order is calculated in the account transformer and by that time it's too late.
|
||||
$first = array_key_first($sorting);
|
||||
$disablePagination = in_array($first, ['last_activity', 'balance', 'balance_difference'], true);
|
||||
Log::debug(sprintf('Will disable pagination in account index v2? %s', var_export($disablePagination, true)));
|
||||
if (!$disablePagination) {
|
||||
$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('disablePagination', $disablePagination);
|
||||
$this->parameters->set('pageSize', $pageSize);
|
||||
$this->parameters->set('sort', $sorting);
|
||||
|
||||
$this->parameters->set('filters', $filters);
|
||||
$transformer->setParameters($this->parameters); // give params to transformer
|
||||
|
||||
return response()
|
||||
|
@@ -45,11 +45,7 @@ class UpdateController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@@ -46,12 +46,7 @@ class IndexController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@@ -46,12 +46,7 @@ class ShowController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@@ -45,11 +45,7 @@ class SumController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@@ -46,11 +46,7 @@ class IndexController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(PiggyBankRepositoryInterface::class);
|
||||
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->repository->setUserGroup($this->validateUserGroup($request));
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@@ -52,10 +52,8 @@ class NetWorthController extends Controller
|
||||
$this->repository = app(AccountRepositoryInterface::class);
|
||||
// new way of user group validation
|
||||
$userGroup = $this->validateUserGroup($request);
|
||||
if (null !== $userGroup) {
|
||||
$this->netWorth->setUserGroup($userGroup);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
$this->netWorth->setUserGroup($userGroup);
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@@ -23,47 +23,68 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Request\Autocomplete;
|
||||
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\JsonApi\Rules\IsValidFilter;
|
||||
use FireflyIII\JsonApi\Rules\IsValidPage;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use LaravelJsonApi\Core\Query\QueryParameters;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
/**
|
||||
* Class AutocompleteRequest
|
||||
*/
|
||||
class AutocompleteRequest extends FormRequest
|
||||
{
|
||||
use AccountFilter;
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use ParsesQueryFilters;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::MANAGE_TRANSACTIONS];
|
||||
|
||||
public function getData(): array
|
||||
/**
|
||||
* Loops over all possible query parameters (these are shared over ALL auto complete requests)
|
||||
* and returns a validated array of parameters.
|
||||
*
|
||||
* The advantage is a single class. But you may also submit "account types" to an endpoint that doesn't use these.
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
$types = $this->convertString('types');
|
||||
$array = [];
|
||||
if ('' !== $types) {
|
||||
$array = explode(',', $types);
|
||||
}
|
||||
$limit = $this->convertInteger('limit');
|
||||
$limit = 0 === $limit ? 10 : $limit;
|
||||
|
||||
// remove 'initial balance' and another from allowed types. its internal
|
||||
$array = array_diff($array, [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION]);
|
||||
$queryParameters = QueryParameters::cast($this->all());
|
||||
|
||||
return [
|
||||
'types' => $array,
|
||||
'query' => $this->convertString('query'),
|
||||
'date' => $this->getCarbonDate('date'),
|
||||
'limit' => $limit,
|
||||
'date' => $this->dateOrToday($queryParameters, 'date'),
|
||||
'query' => $this->arrayOfStrings($queryParameters, 'query'),
|
||||
'size' => $this->integerFromQueryParams($queryParameters, 'size', 50),
|
||||
'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
|
||||
];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'limit' => 'min:0|max:1337',
|
||||
'fields' => JsonApiRule::notSupported(),
|
||||
'filter' => ['nullable', 'array', new IsValidFilter(['query', 'date', 'account_types'])],
|
||||
'include' => JsonApiRule::notSupported(),
|
||||
'page' => ['nullable', 'array', new IsValidPage(['size'])],
|
||||
'sort' => JsonApiRule::notSupported(),
|
||||
];
|
||||
}
|
||||
|
||||
private function getAccountTypeParameter(mixed $types): array
|
||||
{
|
||||
if (is_string($types) && str_contains($types, ',')) {
|
||||
$types = explode(',', $types);
|
||||
}
|
||||
if (!is_iterable($types)) {
|
||||
$types = [$types];
|
||||
}
|
||||
$return = [];
|
||||
foreach ($types as $type) {
|
||||
$return = array_merge($return, $this->mapAccountTypes($type));
|
||||
}
|
||||
|
||||
return array_unique($return);
|
||||
}
|
||||
}
|
||||
|
112
app/Api/V2/Request/Chart/ChartRequest.php
Normal file
112
app/Api/V2/Request/Chart/ChartRequest.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/*
|
||||
* DashboardChartRequest.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Request\Chart;
|
||||
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\JsonApi\Rules\IsValidFilter;
|
||||
use FireflyIII\Support\Http\Api\ParsesQueryFilters;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Validator;
|
||||
use LaravelJsonApi\Core\Query\QueryParameters;
|
||||
use LaravelJsonApi\Validation\Rule as JsonApiRule;
|
||||
|
||||
/**
|
||||
* Class ChartRequest
|
||||
*/
|
||||
class ChartRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use ParsesQueryFilters;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
$queryParameters = QueryParameters::cast($this->all());
|
||||
|
||||
return [
|
||||
'start' => $this->dateOrToday($queryParameters, 'start'),
|
||||
'end' => $this->dateOrToday($queryParameters, 'end'),
|
||||
|
||||
// preselected heeft maar een paar toegestane waardes.
|
||||
|
||||
// 'query' => $this->arrayOfStrings($queryParameters, 'query'),
|
||||
// 'size' => $this->integerFromQueryParams($queryParameters,'size', 50),
|
||||
// 'account_types' => $this->getAccountTypeParameter($this->arrayOfStrings($queryParameters, 'account_types')),
|
||||
];
|
||||
}
|
||||
|
||||
// return [
|
||||
// 'accounts' => $this->getAccountList(),
|
||||
// 'preselected' => $this->convertString('preselected'),
|
||||
// ];
|
||||
// }
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'fields' => JsonApiRule::notSupported(),
|
||||
'filter' => ['nullable', 'array', new IsValidFilter(['start', 'end', 'preselected', 'accounts'])],
|
||||
'include' => JsonApiRule::notSupported(),
|
||||
'page' => JsonApiRule::notSupported(),
|
||||
'sort' => JsonApiRule::notSupported(),
|
||||
];
|
||||
|
||||
// return [
|
||||
// 'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
// 'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
// 'preselected' => sprintf('in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
// 'accounts.*' => 'exists:accounts,id',
|
||||
// ];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
static function (Validator $validator): void {
|
||||
// validate transaction query data.
|
||||
$data = $validator->getData();
|
||||
if (!array_key_exists('accounts', $data)) {
|
||||
// $validator->errors()->add('accounts', trans('validation.filled', ['attribute' => 'accounts']));
|
||||
return;
|
||||
}
|
||||
if (!is_array($data['accounts'])) {
|
||||
$validator->errors()->add('accounts', trans('validation.filled', ['attribute' => 'accounts']));
|
||||
}
|
||||
}
|
||||
);
|
||||
if ($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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\GetFilterInstructions;
|
||||
use FireflyIII\Support\Request\GetSortInstructions;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
@@ -40,6 +41,7 @@ class IndexRequest extends FormRequest
|
||||
use AccountFilter;
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use GetFilterInstructions;
|
||||
use GetSortInstructions;
|
||||
|
||||
public function getAccountTypes(): array
|
||||
|
32
app/Console/Commands/Correction/CorrectAccountBalance.php
Normal file
32
app/Console/Commands/Correction/CorrectAccountBalance.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Class CorrectionSkeleton
|
||||
*/
|
||||
class CorrectAccountBalance extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
protected $description = 'Recalculate all account balance amounts';
|
||||
|
||||
protected $signature = 'firefly-iii:correct-account-balance';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->correctBalanceAmounts();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function correctBalanceAmounts(): void
|
||||
{
|
||||
AccountBalanceCalculator::recalculateAll();
|
||||
}
|
||||
}
|
@@ -74,6 +74,7 @@ class CorrectDatabase extends Command
|
||||
'firefly-iii:unify-group-accounts',
|
||||
'firefly-iii:trigger-credit-recalculation',
|
||||
'firefly-iii:migrate-preferences',
|
||||
'firefly-iii:correct-account-balance',
|
||||
];
|
||||
foreach ($commands as $command) {
|
||||
$this->friendlyLine(sprintf('Now executing command "%s"', $command));
|
||||
|
@@ -60,15 +60,13 @@ class FixIbans extends Command
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$iban = $account->iban;
|
||||
if (str_contains($iban, ' ')) {
|
||||
$iban = app('steam')->filterSpaces((string)$account->iban);
|
||||
if ('' !== $iban) {
|
||||
$account->iban = $iban;
|
||||
$account->save();
|
||||
$this->friendlyInfo(sprintf('Removed spaces from IBAN of account #%d', $account->id));
|
||||
++$this->count;
|
||||
}
|
||||
$iban = (string) $account->iban;
|
||||
$newIban = app('steam')->filterSpaces($iban);
|
||||
if ('' !== $iban && $iban !== $newIban) {
|
||||
$account->iban = $newIban;
|
||||
$account->save();
|
||||
$this->friendlyInfo(sprintf('Removed spaces from IBAN of account #%d', $account->id));
|
||||
++$this->count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +79,7 @@ class FixIbans extends Command
|
||||
foreach ($accounts as $account) {
|
||||
$userId = $account->user_id;
|
||||
$set[$userId] ??= [];
|
||||
$iban = (string)$account->iban;
|
||||
$iban = (string) $account->iban;
|
||||
if ('' === $iban) {
|
||||
continue;
|
||||
}
|
||||
|
49
app/Entities/AccountBalance.php
Normal file
49
app/Entities/AccountBalance.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountBalance.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\Entities;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
|
||||
class AccountBalance
|
||||
{
|
||||
public string $id;
|
||||
public string $amount;
|
||||
public string $currencyId;
|
||||
|
||||
public static function fromArray(): self
|
||||
{
|
||||
$balance = new self();
|
||||
$balance->id = (string) random_int(1, 1000);
|
||||
$balance->name = (string) random_int(1, 1000);
|
||||
$balance->amount = (string) random_int(1, 1000);
|
||||
$balance->currencyId = '1';
|
||||
|
||||
return $balance;
|
||||
}
|
||||
|
||||
public function getAccount(): Account
|
||||
{
|
||||
return Account::inRandomOrder()->first();
|
||||
}
|
||||
}
|
@@ -36,6 +36,7 @@ use Illuminate\Session\TokenMismatchException;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\ValidationException as LaravelValidationException;
|
||||
use Laravel\Passport\Exceptions\OAuthServerException as LaravelOAuthException;
|
||||
use LaravelJsonApi\Core\Exceptions\JsonApiException;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -63,6 +64,7 @@ class Handler extends ExceptionHandler
|
||||
HttpException::class,
|
||||
SuspiciousOperationException::class,
|
||||
BadHttpHeaderException::class,
|
||||
JsonApiException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Handlers\Observer;
|
||||
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
|
||||
/**
|
||||
* Class TransactionObserver
|
||||
@@ -35,4 +36,16 @@ class TransactionObserver
|
||||
app('log')->debug('Observe "deleting" of a transaction.');
|
||||
$transaction?->transactionJournal?->delete();
|
||||
}
|
||||
|
||||
public function updated(Transaction $transaction): void
|
||||
{
|
||||
app('log')->debug('Observe "updated" of a transaction.');
|
||||
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
|
||||
}
|
||||
|
||||
public function created(Transaction $transaction): void
|
||||
{
|
||||
app('log')->debug('Observe "created" of a transaction.');
|
||||
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Api\V3\Controllers;
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\JsonApi\V3\AccountBalances\AccountBalanceSchema;
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Contracts\Support\Responsable;
|
||||
use LaravelJsonApi\Core\Facades\JsonApi;
|
||||
use LaravelJsonApi\Core\Responses\DataResponse;
|
||||
use LaravelJsonApi\Laravel\Http\Controllers\Actions;
|
||||
use LaravelJsonApi\Laravel\Http\Requests\AnonymousQuery;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
use Actions\AttachRelationship;
|
||||
use Actions\Destroy;
|
||||
use Actions\DetachRelationship;
|
||||
use Actions\FetchMany;
|
||||
use Actions\FetchOne;
|
||||
use Actions\FetchRelated;
|
||||
use Actions\FetchRelationship;
|
||||
use Actions\Store;
|
||||
use Actions\Update;
|
||||
use Actions\UpdateRelationship;
|
||||
|
||||
public function readAccountBalances(AnonymousQuery $query, AccountBalanceSchema $schema, Account $account): Responsable
|
||||
{
|
||||
$schema = JsonApi::server()->schemas()->schemaFor('account-balances');
|
||||
|
||||
$models = $schema
|
||||
->repository()
|
||||
->queryAll()
|
||||
->withRequest($query)
|
||||
->withAccount($account)
|
||||
->get()
|
||||
;
|
||||
|
||||
return DataResponse::make($models);
|
||||
}
|
||||
}
|
@@ -86,14 +86,15 @@ class DebugController extends Controller
|
||||
{
|
||||
app('preferences')->mark();
|
||||
$request->session()->forget(['start', 'end', '_previous', 'viewRange', 'range', 'is_custom_range', 'temp-mfa-secret', 'temp-mfa-codes']);
|
||||
app('log')->debug('Call cache:clear...');
|
||||
|
||||
Artisan::call('cache:clear');
|
||||
app('log')->debug('Call config:clear...');
|
||||
Artisan::call('config:clear');
|
||||
app('log')->debug('Call route:clear...');
|
||||
Artisan::call('route:clear');
|
||||
app('log')->debug('Call twig:clean...');
|
||||
Artisan::call('view:clear');
|
||||
|
||||
// also do some recalculations.
|
||||
Artisan::call('firefly-iii:correct-account-balance');
|
||||
Artisan::call('firefly-iii:trigger-credit-recalculation');
|
||||
|
||||
try {
|
||||
Artisan::call('twig:clean');
|
||||
@@ -101,7 +102,6 @@ class DebugController extends Controller
|
||||
throw new FireflyException($e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
app('log')->debug('Call view:clear...');
|
||||
Artisan::call('view:clear');
|
||||
|
||||
return redirect(route('index'));
|
||||
|
@@ -151,8 +151,9 @@ class HomeController extends Controller
|
||||
}
|
||||
|
||||
/** @var Carbon $start */
|
||||
/** @var Carbon $end */
|
||||
$start = session('start', today(config('app.timezone'))->startOfMonth());
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$accounts = $repository->getAccountsById($frontpageArray);
|
||||
$today = today(config('app.timezone'));
|
||||
|
@@ -57,7 +57,7 @@ class AccountFormRequest extends FormRequest
|
||||
'account_type_name' => $this->convertString('objectType'),
|
||||
'currency_id' => $this->convertInteger('currency_id'),
|
||||
'virtual_balance' => $this->convertString('virtual_balance'),
|
||||
'iban' => $this->convertString('iban'),
|
||||
'iban' => $this->convertIban('iban'),
|
||||
'BIC' => $this->convertString('BIC'),
|
||||
'account_number' => $this->convertString('account_number'),
|
||||
'account_role' => $this->convertString('account_role'),
|
||||
|
@@ -29,6 +29,7 @@ use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepositoryInterface;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Bus\Queueable;
|
||||
@@ -100,7 +101,7 @@ class DownloadExchangeRates implements ShouldQueue
|
||||
|
||||
try {
|
||||
$res = $client->get($url);
|
||||
} catch (RequestException $e) {
|
||||
} catch (ConnectException|RequestException $e) {
|
||||
app('log')->warning(sprintf('Trying to grab "%s" resulted in error "%d".', $url, $e->getMessage()));
|
||||
|
||||
return;
|
||||
|
@@ -130,12 +130,12 @@ class MailError extends Job implements ShouldQueue
|
||||
}
|
||||
if (file_exists($file)) {
|
||||
Log::debug(sprintf('Read file in "%s"', $file));
|
||||
$limits = json_decode(file_get_contents($file), true);
|
||||
$limits = json_decode((string)file_get_contents($file), true);
|
||||
}
|
||||
// limit reached?
|
||||
foreach ($types as $type => $info) {
|
||||
Log::debug(sprintf('Now checking limit "%s"', $type), $info);
|
||||
if (!isset($limits[$type])) {
|
||||
if (!array_key_exists($type, $limits)) {
|
||||
Log::debug(sprintf('Limit "%s" reset to zero, did not exist yet.', $type));
|
||||
$limits[$type] = [
|
||||
'time' => time(),
|
||||
|
53
app/JsonApi/Rules/IsValidFilter.php
Normal file
53
app/JsonApi/Rules/IsValidFilter.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* IsValidFilter.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\JsonApi\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class IsValidFilter implements ValidationRule
|
||||
{
|
||||
private array $allowed;
|
||||
|
||||
public function __construct(array $keys)
|
||||
{
|
||||
$this->allowed = $keys;
|
||||
$this->allowed[] = 'user_group_id';
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
if ('filter' !== $attribute) {
|
||||
$fail('validation.bad_api_filter')->translate();
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
foreach ($value as $key => $val) {
|
||||
if (!in_array($key, $this->allowed, true)) {
|
||||
$fail('validation.bad_api_filter')->translate(['filter' => $key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
app/JsonApi/Rules/IsValidPage.php
Normal file
52
app/JsonApi/Rules/IsValidPage.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/*
|
||||
* IsValidFilter.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\JsonApi\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class IsValidPage implements ValidationRule
|
||||
{
|
||||
private array $allowed;
|
||||
|
||||
public function __construct(array $keys)
|
||||
{
|
||||
$this->allowed = $keys;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
if ('page' !== $attribute) {
|
||||
$fail('validation.bad_api_filter')->translate();
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
foreach ($value as $key => $val) {
|
||||
if (!in_array($key, $this->allowed, true)) {
|
||||
$fail('validation.bad_api_page')->translate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
app/JsonApi/V3/AccountBalances/AccountBalanceRepository.php
Normal file
45
app/JsonApi/V3/AccountBalances/AccountBalanceRepository.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountBalanceRepository.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\JsonApi\V3\AccountBalances;
|
||||
|
||||
use FireflyIII\Entities\AccountBalance;
|
||||
use LaravelJsonApi\Contracts\Store\QueriesAll;
|
||||
use LaravelJsonApi\NonEloquent\AbstractRepository;
|
||||
|
||||
class AccountBalanceRepository extends AbstractRepository implements QueriesAll
|
||||
{
|
||||
#[\Override]
|
||||
public function find(string $resourceId): ?object
|
||||
{
|
||||
return AccountBalance::fromArray();
|
||||
}
|
||||
|
||||
public function queryAll(): Capabilities\AccountBalanceQuery
|
||||
{
|
||||
return Capabilities\AccountBalanceQuery::make()
|
||||
->withServer($this->server)
|
||||
->withSchema($this->schema)
|
||||
;
|
||||
}
|
||||
}
|
44
app/JsonApi/V3/AccountBalances/AccountBalanceResource.php
Normal file
44
app/JsonApi/V3/AccountBalances/AccountBalanceResource.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\AccountBalances;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use LaravelJsonApi\Core\Resources\JsonApiResource;
|
||||
|
||||
class AccountBalanceResource extends JsonApiResource
|
||||
{
|
||||
/**
|
||||
* Get the resource id.
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return $this->resource->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource's attributes.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function attributes($request): iterable
|
||||
{
|
||||
return [
|
||||
'name' => $this->resource->amount,
|
||||
'amount' => $this->resource->amount,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource's relationships.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function relationships($request): iterable
|
||||
{
|
||||
return [
|
||||
$this->relation('account')->withData($this->resource->getAccount()),
|
||||
];
|
||||
}
|
||||
}
|
50
app/JsonApi/V3/AccountBalances/AccountBalanceSchema.php
Normal file
50
app/JsonApi/V3/AccountBalances/AccountBalanceSchema.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\AccountBalances;
|
||||
|
||||
use FireflyIII\Entities\AccountBalance;
|
||||
use LaravelJsonApi\Core\Schema\Schema;
|
||||
use LaravelJsonApi\Eloquent\Fields\Relations\HasOne;
|
||||
use LaravelJsonApi\NonEloquent\Fields\Attribute;
|
||||
use LaravelJsonApi\NonEloquent\Fields\ID;
|
||||
|
||||
class AccountBalanceSchema extends Schema
|
||||
{
|
||||
/**
|
||||
* The model the schema corresponds to.
|
||||
*/
|
||||
public static string $model = AccountBalance::class;
|
||||
|
||||
/**
|
||||
* Get the resource fields.
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
ID::make(),
|
||||
Attribute::make('name'),
|
||||
Attribute::make('amount'),
|
||||
HasOne::make('account'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource filters.
|
||||
*/
|
||||
public function filters(): array
|
||||
{
|
||||
return [
|
||||
// Filter::make('id'),
|
||||
];
|
||||
}
|
||||
|
||||
public function repository(): AccountBalanceRepository
|
||||
{
|
||||
return AccountBalanceRepository::make()
|
||||
->withServer($this->server)
|
||||
->withSchema($this)
|
||||
;
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountBalanceQuery.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\JsonApi\V3\AccountBalances\Capabilities;
|
||||
|
||||
use FireflyIII\Entities\AccountBalance;
|
||||
use FireflyIII\Models\Account;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
|
||||
|
||||
class AccountBalanceQuery extends QueryAll
|
||||
{
|
||||
private Account $account;
|
||||
|
||||
/**
|
||||
* QuerySites constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function get(): iterable
|
||||
{
|
||||
return [
|
||||
AccountBalance::fromArray(),
|
||||
AccountBalance::fromArray(),
|
||||
AccountBalance::fromArray(),
|
||||
AccountBalance::fromArray(),
|
||||
];
|
||||
}
|
||||
|
||||
public function withAccount(Account $account): self
|
||||
{
|
||||
$this->account = $account;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
53
app/JsonApi/V3/Accounts/AccountRepository.php
Normal file
53
app/JsonApi/V3/Accounts/AccountRepository.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountRepository.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\JsonApi\V3\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use LaravelJsonApi\Contracts\Store\QueriesAll;
|
||||
use LaravelJsonApi\NonEloquent\AbstractRepository;
|
||||
|
||||
class AccountRepository extends AbstractRepository implements QueriesAll
|
||||
{
|
||||
use UsergroupAware;
|
||||
|
||||
/**
|
||||
* SiteRepository constructor.
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
public function find(string $resourceId): ?object
|
||||
{
|
||||
return Account::find((int) $resourceId);
|
||||
}
|
||||
|
||||
public function queryAll(): Capabilities\AccountQuery
|
||||
{
|
||||
return Capabilities\AccountQuery::make()
|
||||
->withUserGroup($this->userGroup)
|
||||
->withServer($this->server)
|
||||
->withSchema($this->schema)
|
||||
;
|
||||
}
|
||||
}
|
129
app/JsonApi/V3/Accounts/AccountResource.php
Normal file
129
app/JsonApi/V3/Accounts/AccountResource.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Http\Request;
|
||||
use LaravelJsonApi\Core\Resources\JsonApiResource;
|
||||
|
||||
/**
|
||||
* @property Account $resource
|
||||
*/
|
||||
class AccountResource extends JsonApiResource
|
||||
{
|
||||
/**
|
||||
* Get the resource's attributes.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function attributes($request): iterable
|
||||
{
|
||||
return [
|
||||
'created_at' => $this->resource->created_at,
|
||||
'updated_at' => $this->resource->updated_at,
|
||||
'name' => $this->resource->name,
|
||||
'iban' => '' === $this->resource->iban ? null : $this->resource->iban,
|
||||
'active' => $this->resource->active,
|
||||
'last_activity' => $this->resource->last_activity,
|
||||
'type' => $this->resource->type,
|
||||
'account_role' => $this->resource->account_role,
|
||||
|
||||
// 'virtual_balance' => $this->resource->virtual_balance,
|
||||
// 'native_balance' => $this->resource->native_balance,
|
||||
// 'user' => $this->resource->user_array,
|
||||
// 'balances' => []
|
||||
//
|
||||
// currency
|
||||
// 'currency_id' => $this->resource->currency_id,
|
||||
// 'currency_code' => $this->resource->currency_code,
|
||||
// 'currency_symbol' => $this->resource->currency_symbol,
|
||||
// 'currency_decimal_places' => $this->resource->currency_decimal_places,
|
||||
|
||||
// balance (in currency, on date)
|
||||
// 'current_balance' => $this->resource->current_balance,
|
||||
|
||||
// 'current_balance' => app('steam')->bcround(app('steam')->balance($account, $date), $decimalPlaces),
|
||||
// 'current_balance_date' => $date->toAtomString(),
|
||||
// 'notes' => $this->repository->getNoteText($account),
|
||||
// 'monthly_payment_date' => $monthlyPaymentDate,
|
||||
// 'credit_card_type' => $creditCardType,
|
||||
// 'account_number' => $this->repository->getMetaValue($account, 'account_number'),
|
||||
// 'bic' => $this->repository->getMetaValue($account, 'BIC'),
|
||||
// 'opening_balance' => $openingBalance,
|
||||
// 'opening_balance_date' => $openingBalanceDate,
|
||||
// 'liability_type' => $liabilityType,
|
||||
// 'liability_direction' => $liabilityDirection,
|
||||
// 'interest' => $interest,
|
||||
// 'interest_period' => $interestPeriod,
|
||||
// 'current_debt' => $this->repository->getMetaValue($account, 'current_debt'),
|
||||
// 'include_net_worth' => $includeNetWorth,
|
||||
// 'longitude' => $longitude,
|
||||
// 'latitude' => $latitude,
|
||||
// 'zoom_level' => $zoomLevel,
|
||||
|
||||
// 'order' => $order,
|
||||
|
||||
// 'currency_id' => (string) $currency->id,
|
||||
// 'currency_code' => $currency->code,
|
||||
// 'currency_symbol' => $currency->symbol,
|
||||
// 'currency_decimal_places' => $currency->decimal_places,
|
||||
//
|
||||
// 'native_currency_id' => (string) $this->default->id,
|
||||
// 'native_currency_code' => $this->default->code,
|
||||
// 'native_currency_symbol' => $this->default->symbol,
|
||||
// 'native_currency_decimal_places' => $this->default->decimal_places,
|
||||
//
|
||||
// // balance:
|
||||
// 'current_balance' => $balance,
|
||||
// 'native_current_balance' => $nativeBalance,
|
||||
// 'current_balance_date' => $this->getDate()->endOfDay()->toAtomString(),
|
||||
//
|
||||
// // balance difference
|
||||
// 'balance_difference' => $balanceDiff,
|
||||
// 'native_balance_difference' => $nativeBalanceDiff,
|
||||
// 'balance_difference_start' => $diffStart,
|
||||
// 'balance_difference_end' => $diffEnd,
|
||||
//
|
||||
// // more meta
|
||||
// 'last_activity' => array_key_exists($id, $this->lastActivity) ? $this->lastActivity[$id]->toAtomString() : null,
|
||||
//
|
||||
// // liability stuff
|
||||
// 'liability_type' => $liabilityType,
|
||||
// 'liability_direction' => $liabilityDirection,
|
||||
// 'interest' => $interest,
|
||||
// 'interest_period' => $interestPeriod,
|
||||
// 'current_debt' => $currentDebt,
|
||||
//
|
||||
// // object group
|
||||
// 'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null,
|
||||
// 'object_group_order' => $objectGroupOrder,
|
||||
// 'object_group_title' => $objectGroupTitle,
|
||||
// 'notes' => $this->repository->getNoteText($account),
|
||||
// 'monthly_payment_date' => $monthlyPaymentDate,
|
||||
// 'credit_card_type' => $creditCardType,
|
||||
// 'bic' => $this->repository->getMetaValue($account, 'BIC'),
|
||||
// 'virtual_balance' => number_format((float) $account->virtual_balance, $decimalPlaces, '.', ''),
|
||||
// 'opening_balance' => $openingBalance,
|
||||
// 'opening_balance_date' => $openingBalanceDate,
|
||||
// 'include_net_worth' => $includeNetWorth,
|
||||
// 'longitude' => $longitude,
|
||||
// 'latitude' => $latitude,
|
||||
// 'zoom_level' => $zoomLevel,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource's relationships.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function relationships($request): iterable
|
||||
{
|
||||
return [
|
||||
$this->relation('user')->withData($this->resource->user),
|
||||
$this->relation('account_balances')->withData($this->resource->balances),
|
||||
];
|
||||
}
|
||||
}
|
64
app/JsonApi/V3/Accounts/AccountSchema.php
Normal file
64
app/JsonApi/V3/Accounts/AccountSchema.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Accounts;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use LaravelJsonApi\Eloquent\Contracts\Paginator;
|
||||
use LaravelJsonApi\Eloquent\Fields\Boolean;
|
||||
use LaravelJsonApi\Eloquent\Fields\DateTime;
|
||||
use LaravelJsonApi\Eloquent\Fields\ID;
|
||||
use LaravelJsonApi\Eloquent\Fields\Number;
|
||||
use LaravelJsonApi\Eloquent\Fields\Relations\HasMany;
|
||||
use LaravelJsonApi\Eloquent\Fields\Relations\HasOne;
|
||||
use LaravelJsonApi\Eloquent\Fields\Str;
|
||||
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
|
||||
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
|
||||
use LaravelJsonApi\Eloquent\Schema;
|
||||
|
||||
class AccountSchema extends Schema
|
||||
{
|
||||
/**
|
||||
* The model the schema corresponds to.
|
||||
*/
|
||||
public static string $model = Account::class;
|
||||
|
||||
/**
|
||||
* Get the resource fields.
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
ID::make(),
|
||||
DateTime::make('created_at')->sortable()->readOnly(),
|
||||
DateTime::make('updated_at')->sortable()->readOnly(),
|
||||
Str::make('name')->sortable(),
|
||||
Str::make('account_type'),
|
||||
Str::make('virtual_balance'),
|
||||
Str::make('iban'),
|
||||
Boolean::make('active'),
|
||||
Number::make('order'),
|
||||
HasOne::make('user'),
|
||||
HasMany::make('account_balances'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource filters.
|
||||
*/
|
||||
public function filters(): array
|
||||
{
|
||||
return [
|
||||
WhereIdIn::make($this),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource paginator.
|
||||
*/
|
||||
public function pagination(): ?Paginator
|
||||
{
|
||||
return PagePagination::make();
|
||||
}
|
||||
}
|
73
app/JsonApi/V3/Accounts/Capabilities/AccountQuery.php
Normal file
73
app/JsonApi/V3/Accounts/Capabilities/AccountQuery.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountQuery.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\JsonApi\V3\Accounts\Capabilities;
|
||||
|
||||
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\ExpandsQuery;
|
||||
use FireflyIII\Support\JsonApi\FiltersPagination;
|
||||
use FireflyIII\Support\JsonApi\SortsCollection;
|
||||
use FireflyIII\Support\JsonApi\ValidateSortParameters;
|
||||
use LaravelJsonApi\Contracts\Store\HasPagination;
|
||||
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
|
||||
use LaravelJsonApi\NonEloquent\Concerns\PaginatesEnumerables;
|
||||
|
||||
class AccountQuery extends QueryAll implements HasPagination
|
||||
{
|
||||
use ExpandsQuery;
|
||||
use FiltersPagination;
|
||||
use PaginatesEnumerables;
|
||||
use SortsCollection;
|
||||
use UsergroupAware;
|
||||
use ValidateSortParameters;
|
||||
|
||||
#[\Override]
|
||||
public function get(): iterable
|
||||
{
|
||||
$filters = $this->queryParameters->filter();
|
||||
$sort = $this->queryParameters->sortFields();
|
||||
$pagination = $this->filtersPagination($this->queryParameters->page());
|
||||
$needsAll = $this->validateParams('account', $sort);
|
||||
$query = $this->userGroup->accounts();
|
||||
|
||||
if (!$needsAll) {
|
||||
$query = $this->addPagination($query, $pagination);
|
||||
}
|
||||
$query = $this->addSortParams($query, $sort);
|
||||
$query = $this->addFilterParams('account', $query, $filters);
|
||||
|
||||
$collection = $query->get(['accounts.*']);
|
||||
|
||||
// enrich data
|
||||
$enrichment = new AccountEnrichment();
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
// add filters after the query
|
||||
|
||||
// add sort after the query
|
||||
return $this->sortCollection($collection, $sort);
|
||||
// var_dump($filters->value('name'));
|
||||
// exit;
|
||||
}
|
||||
}
|
38
app/JsonApi/V3/Server.php
Normal file
38
app/JsonApi/V3/Server.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3;
|
||||
|
||||
use FireflyIII\JsonApi\V3\Accounts\AccountSchema;
|
||||
use FireflyIII\JsonApi\V3\AccountBalances\AccountBalanceSchema;
|
||||
use FireflyIII\JsonApi\V3\Users\UserSchema;
|
||||
use LaravelJsonApi\Core\Server\Server as BaseServer;
|
||||
|
||||
class Server extends BaseServer
|
||||
{
|
||||
/**
|
||||
* The base URI namespace for this server.
|
||||
*/
|
||||
protected string $baseUri = '/api/v3';
|
||||
|
||||
/**
|
||||
* Bootstrap the server when it is handling an HTTP request.
|
||||
*/
|
||||
public function serving(): void
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server's list of schemas.
|
||||
*/
|
||||
protected function allSchemas(): array
|
||||
{
|
||||
return [
|
||||
AccountSchema::class,
|
||||
UserSchema::class,
|
||||
AccountBalanceSchema::class,
|
||||
];
|
||||
}
|
||||
}
|
41
app/JsonApi/V3/Users/UserResource.php
Normal file
41
app/JsonApi/V3/Users/UserResource.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Users;
|
||||
|
||||
use FireflyIII\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use LaravelJsonApi\Core\Resources\JsonApiResource;
|
||||
|
||||
/**
|
||||
* @property User $resource
|
||||
*/
|
||||
class UserResource extends JsonApiResource
|
||||
{
|
||||
/**
|
||||
* Get the resource's attributes.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function attributes($request): iterable
|
||||
{
|
||||
return [
|
||||
'created_at' => $this->resource->created_at,
|
||||
'updated_at' => $this->resource->updated_at,
|
||||
'email' => $this->resource->email,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource's relationships.
|
||||
*
|
||||
* @param null|Request $request
|
||||
*/
|
||||
public function relationships($request): iterable
|
||||
{
|
||||
return [
|
||||
// @TODO
|
||||
];
|
||||
}
|
||||
}
|
55
app/JsonApi/V3/Users/UserSchema.php
Normal file
55
app/JsonApi/V3/Users/UserSchema.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\JsonApi\V3\Users;
|
||||
|
||||
use FireflyIII\User;
|
||||
use LaravelJsonApi\Eloquent\Contracts\Paginator;
|
||||
use LaravelJsonApi\Eloquent\Fields\DateTime;
|
||||
use LaravelJsonApi\Eloquent\Fields\ID;
|
||||
use LaravelJsonApi\Eloquent\Fields\Relations\HasMany;
|
||||
use LaravelJsonApi\Eloquent\Fields\Str;
|
||||
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
|
||||
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
|
||||
use LaravelJsonApi\Eloquent\Schema;
|
||||
|
||||
class UserSchema extends Schema
|
||||
{
|
||||
/**
|
||||
* The model the schema corresponds to.
|
||||
*/
|
||||
public static string $model = User::class;
|
||||
|
||||
/**
|
||||
* Get the resource fields.
|
||||
*/
|
||||
public function fields(): array
|
||||
{
|
||||
return [
|
||||
ID::make(),
|
||||
DateTime::make('created_at')->sortable()->readOnly(),
|
||||
DateTime::make('updated_at')->sortable()->readOnly(),
|
||||
Str::make('email'),
|
||||
HasMany::make('accounts'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource filters.
|
||||
*/
|
||||
public function filters(): array
|
||||
{
|
||||
return [
|
||||
WhereIdIn::make($this),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource paginator.
|
||||
*/
|
||||
public function pagination(): ?Paginator
|
||||
{
|
||||
return PagePagination::make();
|
||||
}
|
||||
}
|
@@ -193,6 +193,11 @@ class Account extends Model
|
||||
return $this->hasMany(AccountMeta::class);
|
||||
}
|
||||
|
||||
public function accountBalances(): HasMany
|
||||
{
|
||||
return $this->hasMany(AccountBalance::class);
|
||||
}
|
||||
|
||||
public function getEditNameAttribute(): string
|
||||
{
|
||||
$name = $this->name;
|
||||
|
25
app/Models/AccountBalance.php
Normal file
25
app/Models/AccountBalance.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class AccountBalance extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $fillable = ['account_id', 'title', 'transaction_currency_id', 'balance'];
|
||||
|
||||
public function account(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function transactionCurrency(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(TransactionCurrency::class);
|
||||
}
|
||||
}
|
48
app/Policies/AccountBalancePolicy.php
Normal file
48
app/Policies/AccountBalancePolicy.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountPolicy.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\Policies;
|
||||
|
||||
use FireflyIII\Entities\AccountBalance;
|
||||
use FireflyIII\User;
|
||||
|
||||
class AccountBalancePolicy
|
||||
{
|
||||
/**
|
||||
* TODO needs better authentication.
|
||||
*/
|
||||
public function view(User $user, AccountBalance $accountBalance): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
67
app/Policies/AccountPolicy.php
Normal file
67
app/Policies/AccountPolicy.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountPolicy.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\Policies;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\User;
|
||||
|
||||
class AccountPolicy
|
||||
{
|
||||
/**
|
||||
* TODO needs better authentication.
|
||||
*/
|
||||
public function view(User $user, Account $account): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
return auth()->check() && $user->id === $account->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function viewUser(User $user, Account $account): bool
|
||||
{
|
||||
return $this->view($user, $account);
|
||||
}
|
||||
|
||||
public function viewAccountBalances(User $user, Account $account): bool
|
||||
{
|
||||
return $this->view($user, $account);
|
||||
}
|
||||
}
|
48
app/Policies/BalancePolicy.php
Normal file
48
app/Policies/BalancePolicy.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/*
|
||||
* BalancePolicy.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\Policies;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\User;
|
||||
|
||||
class BalancePolicy
|
||||
{
|
||||
/**
|
||||
* TODO needs better authentication.
|
||||
*/
|
||||
public function view(User $user, Account $account): bool
|
||||
{
|
||||
return auth()->check() && $user->id === $account->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
return auth()->check();
|
||||
}
|
||||
}
|
58
app/Policies/UserPolicy.php
Normal file
58
app/Policies/UserPolicy.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* UserPolicy.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\Policies;
|
||||
|
||||
use FireflyIII\User;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
/**
|
||||
* TODO needs better authentication.
|
||||
*/
|
||||
public function view(User $user, User $user1): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
return auth()->check() && $user->id === $account->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everybody can do this, but selection should limit to user.
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function viewAny(): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
public function viewAccounts(User $user): bool
|
||||
{
|
||||
return true;
|
||||
|
||||
return auth()->check();
|
||||
}
|
||||
}
|
@@ -37,6 +37,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
|
||||
use FireflyIII\Services\Internal\Update\AccountUpdateService;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
@@ -123,6 +124,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
|
||||
public function findByIbanNull(string $iban, array $types): ?Account
|
||||
{
|
||||
$iban = Steam::filterSpaces($iban);
|
||||
$query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban');
|
||||
|
||||
if (0 !== count($types)) {
|
||||
|
@@ -27,9 +27,11 @@ namespace FireflyIII\Repositories\UserGroups\Account;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Services\Internal\Update\AccountUpdateService;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -79,6 +81,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
|
||||
public function findByIbanNull(string $iban, array $types): ?Account
|
||||
{
|
||||
$iban = Steam::filterSpaces($iban);
|
||||
$query = $this->userGroup->accounts()->where('iban', '!=', '')->whereNotNull('iban');
|
||||
|
||||
if (0 !== count($types)) {
|
||||
@@ -240,7 +243,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection
|
||||
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection
|
||||
{
|
||||
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
|
||||
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
|
||||
@@ -249,6 +252,22 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
$query->accountTypeIn($types);
|
||||
}
|
||||
|
||||
// process filters
|
||||
// TODO this should be repeatable, it feels like a hack when you do it here.
|
||||
// TODO some fields cannot be filtered using the query, and a second filter must be applied on the collection.
|
||||
foreach ($filters as $column => $value) {
|
||||
// filter on NULL values
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
if ('active' === $column) {
|
||||
$query->where('accounts.active', $value);
|
||||
}
|
||||
if ('name' === $column) {
|
||||
$query->where('accounts.name', 'LIKE', sprintf('%%%s%%', $value));
|
||||
}
|
||||
}
|
||||
|
||||
// add sort parameters. At this point they're filtered to allowed fields to sort by:
|
||||
$hasActiveColumn = array_key_exists('active', $sort);
|
||||
if (count($sort) > 0) {
|
||||
@@ -273,7 +292,7 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
return $query->get(['accounts.*']);
|
||||
}
|
||||
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection
|
||||
public function searchAccount(array $query, array $types, int $limit): Collection
|
||||
{
|
||||
// search by group, not by user
|
||||
$dbQuery = $this->userGroup->accounts()
|
||||
@@ -283,13 +302,17 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
->orderBy('accounts.name', 'ASC')
|
||||
->with(['accountType'])
|
||||
;
|
||||
if ('' !== $query) {
|
||||
if (count($query) > 0) {
|
||||
// split query on spaces just in case:
|
||||
$parts = explode(' ', $query);
|
||||
foreach ($parts as $part) {
|
||||
$search = sprintf('%%%s%%', $part);
|
||||
$dbQuery->where('name', 'LIKE', $search);
|
||||
}
|
||||
$dbQuery->where(function (EloquentBuilder $q) use ($query): void {
|
||||
foreach ($query as $line) {
|
||||
$parts = explode(' ', $line);
|
||||
foreach ($parts as $part) {
|
||||
$search = sprintf('%%%s%%', $part);
|
||||
$q->orWhere('name', 'LIKE', $search);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (0 !== count($types)) {
|
||||
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
|
||||
@@ -337,4 +360,40 @@ class AccountRepository implements AccountRepositoryInterface
|
||||
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line
|
||||
;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getObjectGroups(Collection $accounts): array
|
||||
{
|
||||
$groupIds = [];
|
||||
$return = [];
|
||||
$set = DB::table('object_groupables')->where('object_groupable_type', Account::class)
|
||||
->whereIn('object_groupable_id', $accounts->pluck('id')->toArray())->get()
|
||||
;
|
||||
|
||||
/** @var \stdClass $row */
|
||||
foreach ($set as $row) {
|
||||
$groupIds[] = $row->object_group_id;
|
||||
}
|
||||
$groupIds = array_unique($groupIds);
|
||||
$groups = ObjectGroup::whereIn('id', $groupIds)->get();
|
||||
|
||||
/** @var \stdClass $row */
|
||||
foreach ($set as $row) {
|
||||
if (!array_key_exists($row->object_groupable_id, $return)) {
|
||||
/** @var null|ObjectGroup $group */
|
||||
$group = $groups->firstWhere('id', '=', $row->object_group_id);
|
||||
if (null !== $group) {
|
||||
$return[$row->object_groupable_id] = ['title' => $group->title, 'order' => $group->order, 'id' => $group->id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getAccountBalances(Account $account): Collection
|
||||
{
|
||||
return $account->accountBalances;
|
||||
}
|
||||
}
|
||||
|
@@ -53,9 +53,11 @@ interface AccountRepositoryInterface
|
||||
|
||||
public function getAccountCurrency(Account $account): ?TransactionCurrency;
|
||||
|
||||
public function getAccountBalances(Account $account): Collection;
|
||||
|
||||
public function getAccountsById(array $accountIds): Collection;
|
||||
|
||||
public function getAccountsByType(array $types, ?array $sort = []): Collection;
|
||||
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection;
|
||||
|
||||
/**
|
||||
* Used in the infinite accounts list.
|
||||
@@ -69,6 +71,8 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function getMetaValue(Account $account, string $field): ?string;
|
||||
|
||||
public function getObjectGroups(Collection $accounts): array;
|
||||
|
||||
public function getUserGroup(): UserGroup;
|
||||
|
||||
/**
|
||||
@@ -76,7 +80,7 @@ interface AccountRepositoryInterface
|
||||
*/
|
||||
public function resetAccountOrder(): void;
|
||||
|
||||
public function searchAccount(string $query, array $types, int $limit): Collection;
|
||||
public function searchAccount(array $query, array $types, int $limit): Collection;
|
||||
|
||||
public function setUser(User $user): void;
|
||||
|
||||
|
@@ -24,17 +24,27 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\UserGroups\Category;
|
||||
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CategoryRepository implements CategoryRepositoryInterface
|
||||
{
|
||||
use UserGroupTrait;
|
||||
|
||||
public function searchCategory(string $query, int $limit): Collection
|
||||
public function searchCategory(array $query, int $limit): Collection
|
||||
{
|
||||
$search = $this->userGroup->categories();
|
||||
if ('' !== $query) {
|
||||
$search->where('name', 'LIKE', sprintf('%%%s%%', $query));
|
||||
if (count($query) > 0) {
|
||||
// split query on spaces just in case:
|
||||
$search->where(function (EloquentBuilder $q) use ($query): void {
|
||||
foreach ($query as $line) {
|
||||
$parts = explode(' ', $line);
|
||||
foreach ($parts as $part) {
|
||||
$search = sprintf('%%%s%%', $part);
|
||||
$q->orWhere('name', 'LIKE', $search);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $search->take($limit)->get();
|
||||
|
@@ -30,5 +30,5 @@ interface CategoryRepositoryInterface
|
||||
/**
|
||||
* Search for a category using wild cards. Uses the database, so case sensitive.
|
||||
*/
|
||||
public function searchCategory(string $query, int $limit): Collection;
|
||||
public function searchCategory(array $query, int $limit): Collection;
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\UserGroups\Journal;
|
||||
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
@@ -33,15 +34,24 @@ class JournalRepository implements JournalRepositoryInterface
|
||||
{
|
||||
use UserGroupTrait;
|
||||
|
||||
public function searchJournalDescriptions(string $search, int $limit): Collection
|
||||
public function searchJournalDescriptions(array $query, int $limit): Collection
|
||||
{
|
||||
$query = $this->userGroup->transactionJournals()
|
||||
$search = $this->userGroup->transactionJournals()
|
||||
->orderBy('date', 'DESC')
|
||||
;
|
||||
if ('' !== $search) {
|
||||
$query->where('description', 'LIKE', sprintf('%%%s%%', $search));
|
||||
if (count($query) > 0) {
|
||||
// split query on spaces just in case:
|
||||
$search->where(function (EloquentBuilder $q) use ($query): void {
|
||||
foreach ($query as $line) {
|
||||
$parts = explode(' ', $line);
|
||||
foreach ($parts as $part) {
|
||||
$search = sprintf('%%%s%%', $part);
|
||||
$q->orWhere('description', 'LIKE', $search);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $query->take($limit)->get();
|
||||
return $search->take($limit)->get();
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ interface JournalRepositoryInterface
|
||||
/**
|
||||
* Search in journal descriptions.
|
||||
*/
|
||||
public function searchJournalDescriptions(string $search, int $limit): Collection;
|
||||
public function searchJournalDescriptions(array $query, int $limit): Collection;
|
||||
|
||||
public function setUser(User $user): void;
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\UserGroups\Tag;
|
||||
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
@@ -33,11 +34,20 @@ class TagRepository implements TagRepositoryInterface
|
||||
{
|
||||
use UserGroupTrait;
|
||||
|
||||
public function searchTag(string $query, int $limit): Collection
|
||||
public function searchTag(array $query, int $limit): Collection
|
||||
{
|
||||
$search = $this->user->tags();
|
||||
if ('' !== $query) {
|
||||
$search->where('tag', 'LIKE', sprintf('%%%s%%', $query));
|
||||
$search = $this->userGroup->tags();
|
||||
if (count($query) > 0) {
|
||||
// split query on spaces just in case:
|
||||
$search->where(function (EloquentBuilder $q) use ($query): void {
|
||||
foreach ($query as $line) {
|
||||
$parts = explode(' ', $line);
|
||||
foreach ($parts as $part) {
|
||||
$search = sprintf('%%%s%%', $part);
|
||||
$q->orWhere('tag', 'LIKE', $search);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $search->take($limit)->get(['tags.*']);
|
||||
|
@@ -30,5 +30,5 @@ interface TagRepositoryInterface
|
||||
/**
|
||||
* Find one or more tags based on the query.
|
||||
*/
|
||||
public function searchTag(string $query, int $limit): Collection;
|
||||
public function searchTag(array $query, int $limit): Collection;
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ namespace FireflyIII\Rules;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
/**
|
||||
@@ -95,6 +96,9 @@ class UniqueIban implements ValidationRule
|
||||
$maxCounts = $this->getMaxOccurrences();
|
||||
|
||||
foreach ($maxCounts as $type => $max) {
|
||||
// make sure to trim the value of $value so all spaces are removed.
|
||||
$value = Steam::filterSpaces($value);
|
||||
|
||||
$count = $this->countHits($type, $value);
|
||||
app('log')->debug(sprintf('Count for "%s" and IBAN "%s" is %d', $type, $value, $count));
|
||||
if ($count > $max) {
|
||||
|
57
app/Support/Http/Api/ParsesQueryFilters.php
Normal file
57
app/Support/Http/Api/ParsesQueryFilters.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/*
|
||||
* ParsesQueryFilters.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\Http\Api;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use LaravelJsonApi\Core\Query\QueryParameters;
|
||||
|
||||
trait ParsesQueryFilters
|
||||
{
|
||||
private function dateOrToday(QueryParameters $parameters, string $field): Carbon
|
||||
{
|
||||
$date = today();
|
||||
|
||||
try {
|
||||
$date = Carbon::createFromFormat('Y-m-d', $parameters->filter()?->value($field, date('Y-m-d')), config('app.timezone'));
|
||||
} catch (InvalidFormatException $e) {
|
||||
Log::debug(sprintf('Invalid date format in request. Using today: %s', $e->getMessage()));
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
private function arrayOfStrings(QueryParameters $parameters, string $field): array
|
||||
{
|
||||
$array = $parameters->filter()?->value($field, []) ?? [];
|
||||
|
||||
return is_string($array) ? [$array] : $array;
|
||||
}
|
||||
|
||||
private function integerFromQueryParams(QueryParameters $parameters, string $field, int $default): int
|
||||
{
|
||||
return (int) ($parameters->page()[$field] ?? $default);
|
||||
}
|
||||
}
|
@@ -38,6 +38,10 @@ use Illuminate\Support\Facades\Log;
|
||||
trait ValidatesUserGroupTrait
|
||||
{
|
||||
/**
|
||||
* An "undocumented" filter
|
||||
*
|
||||
* TODO add this filter to the API docs.
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
@@ -81,7 +85,7 @@ trait ValidatesUserGroupTrait
|
||||
throw new AuthorizationException((string) trans('validation.belongs_user_or_user_group'));
|
||||
}
|
||||
Log::debug(sprintf('validateUserGroup: validate access of user to group #%d ("%s").', $groupId, $group->title));
|
||||
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : [];
|
||||
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : []; // @phpstan-ignore-line
|
||||
if (0 === count($roles)) {
|
||||
Log::debug('validateUserGroup: no roles defined, so no access.');
|
||||
|
||||
|
48
app/Support/JsonApi/Concerns/UsergroupAware.php
Normal file
48
app/Support/JsonApi/Concerns/UsergroupAware.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/*
|
||||
* UsergroupAware.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\JsonApi\Concerns;
|
||||
|
||||
use FireflyIII\Models\UserGroup;
|
||||
|
||||
trait UsergroupAware
|
||||
{
|
||||
protected UserGroup $userGroup;
|
||||
|
||||
public function getUserGroup(): UserGroup
|
||||
{
|
||||
return $this->userGroup;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
public function withUserGroup(UserGroup $userGroup): self
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
149
app/Support/JsonApi/Enrichments/AccountEnrichment.php
Normal file
149
app/Support/JsonApi/Enrichments/AccountEnrichment.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountEnricher.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\JsonApi\Enrichments;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AccountEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private array $currencies;
|
||||
|
||||
#[\Override]
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->currencies = [];
|
||||
// do everything here:
|
||||
$this->getLastActivity();
|
||||
// $this->getMetaBalances();
|
||||
$this->collectAccountTypes();
|
||||
$this->collectMetaData();
|
||||
|
||||
$this->collection->transform(function (Account $account) {
|
||||
$account->user_array = ['id' => 1, 'bla bla' => 'bla'];
|
||||
$account->balances = collect([
|
||||
['balance_id' => 1, 'balance' => 5],
|
||||
['balance_id' => 2, 'balance' => 5],
|
||||
['balance_id' => 3, 'balance' => 5],
|
||||
]);
|
||||
|
||||
return $account;
|
||||
});
|
||||
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this method refers to a single-use method inside Steam that could be moved here.
|
||||
*/
|
||||
private function getLastActivity(): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$lastActivity = $accountRepository->getLastActivity($this->collection);
|
||||
foreach ($lastActivity as $row) {
|
||||
$this->collection->where('id', $row['account_id'])->first()->last_activity = Carbon::parse($row['date_max'], config('app.timezone'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this method refers to a single-use method inside Steam that could be moved here.
|
||||
*/
|
||||
private function getMetaBalances(): void
|
||||
{
|
||||
try {
|
||||
$array = app('steam')->balancesByAccountsConverted($this->collection, today());
|
||||
} catch (FireflyException $e) {
|
||||
Log::error(sprintf('Could not load balances: %s', $e->getMessage()));
|
||||
|
||||
return;
|
||||
}
|
||||
foreach ($array as $accountId => $row) {
|
||||
$this->collection->where('id', $accountId)->first()->balance = $row['balance'];
|
||||
$this->collection->where('id', $accountId)->first()->native_balance = $row['native_balance'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO this method refers to a single-use method inside Steam that could be moved here.
|
||||
*/
|
||||
private function collectAccountTypes(): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$accountTypes = $accountRepository->getAccountTypes($this->collection);
|
||||
$types = [];
|
||||
|
||||
/** @var AccountType $row */
|
||||
foreach ($accountTypes as $row) {
|
||||
$types[$row->id] = $row->type;
|
||||
}
|
||||
$this->collection->transform(function (Account $account) use ($types) {
|
||||
$account->type = $types[$account->id];
|
||||
|
||||
return $account;
|
||||
});
|
||||
}
|
||||
|
||||
private function collectMetaData(): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
|
||||
$metaFields = $accountRepository->getMetaValues($this->collection, ['currency_id', 'account_role', 'account_number', 'liability_direction', 'interest', 'interest_period', 'current_debt']);
|
||||
$currencyIds = $metaFields->where('name', 'currency_id')->pluck('data')->toArray();
|
||||
|
||||
$currencies = [];
|
||||
foreach ($repository->getByIds($currencyIds) as $currency) {
|
||||
$id = $currency->id;
|
||||
$currencies[$id] = $currency;
|
||||
}
|
||||
|
||||
$this->collection->transform(function (Account $account) use ($metaFields, $currencies) {
|
||||
$set = $metaFields->where('account_id', $account->id);
|
||||
foreach ($set as $entry) {
|
||||
$account->{$entry->name} = $entry->data;
|
||||
if ('currency_id' === $entry->name) {
|
||||
$id = (int) $entry->data;
|
||||
$account->currency_code = $currencies[$id]?->code;
|
||||
$account->currency_symbol = $currencies[$id]?->symbol;
|
||||
$account->currency_decimal_places = $currencies[$id]?->decimal_places;
|
||||
}
|
||||
}
|
||||
|
||||
return $account;
|
||||
});
|
||||
}
|
||||
}
|
31
app/Support/JsonApi/Enrichments/EnrichmentInterface.php
Normal file
31
app/Support/JsonApi/Enrichments/EnrichmentInterface.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/*
|
||||
* EnricherInterface.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\JsonApi\Enrichments;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface EnrichmentInterface
|
||||
{
|
||||
public function enrich(Collection $collection): Collection;
|
||||
}
|
85
app/Support/JsonApi/ExpandsQuery.php
Normal file
85
app/Support/JsonApi/ExpandsQuery.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/*
|
||||
* ExpandsQuery.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\JsonApi;
|
||||
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use LaravelJsonApi\Core\Query\FilterParameters;
|
||||
use LaravelJsonApi\Core\Query\SortFields;
|
||||
|
||||
trait ExpandsQuery
|
||||
{
|
||||
use AccountFilter;
|
||||
|
||||
final protected function addPagination(Builder $query, array $pagination): Builder
|
||||
{
|
||||
$skip = ($pagination['number'] - 1) * $pagination['size'];
|
||||
|
||||
return $query->skip($skip)->take($pagination['size']);
|
||||
}
|
||||
|
||||
final protected function addSortParams(Builder $query, ?SortFields $sort): Builder
|
||||
{
|
||||
if (null === $sort) {
|
||||
return $query;
|
||||
}
|
||||
foreach ($sort->all() as $sortField) {
|
||||
$query->orderBy($sortField->name(), $sortField->isAscending() ? 'ASC' : 'DESC');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
final protected function addFilterParams(string $class, Builder $query, ?FilterParameters $filters): Builder
|
||||
{
|
||||
if (null === $filters) {
|
||||
return $query;
|
||||
}
|
||||
$config = config(sprintf('firefly.valid_query_filters.%s', $class)) ?? [];
|
||||
if (0 === count($filters->all())) {
|
||||
return $query;
|
||||
}
|
||||
$query->where(function (Builder $q) use ($config, $filters): void {
|
||||
foreach ($filters->all() as $filter) {
|
||||
if (in_array($filter->key(), $config, true)) {
|
||||
foreach ($filter->value() as $value) {
|
||||
$q->where($filter->key(), 'LIKE', sprintf('%%%s%%', $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// some filters are special, i.e. the account type filter.
|
||||
$typeFilters = $filters->value('type', false);
|
||||
if (false !== $typeFilters && count($typeFilters) > 0) {
|
||||
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
|
||||
foreach ($typeFilters as $typeFilter) {
|
||||
$types = $this->mapAccountTypes($typeFilter);
|
||||
$query->whereIn('account_types.type', $types);
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
55
app/Support/JsonApi/FiltersPagination.php
Normal file
55
app/Support/JsonApi/FiltersPagination.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/*
|
||||
* FiltersPagination.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\JsonApi;
|
||||
|
||||
trait FiltersPagination
|
||||
{
|
||||
protected function filtersPagination(?array $pagination): array
|
||||
{
|
||||
if (null === $pagination) {
|
||||
return [
|
||||
'number' => 1,
|
||||
'size' => $this->getPageSize(),
|
||||
];
|
||||
}
|
||||
// cleanup page number
|
||||
$pagination['number'] = (int) ($pagination['number'] ?? 1);
|
||||
$pagination['number'] = min(65536, max($pagination['number'], 1));
|
||||
|
||||
// clean up page size
|
||||
$pagination['size'] = (int) ($pagination['size'] ?? $this->getPageSize());
|
||||
$pagination['size'] = min(1337, max($pagination['size'], 1));
|
||||
|
||||
return $pagination;
|
||||
}
|
||||
|
||||
private function getPageSize(): int
|
||||
{
|
||||
if (auth()->check()) {
|
||||
return (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
}
|
||||
|
||||
return 50;
|
||||
}
|
||||
}
|
42
app/Support/JsonApi/SortsCollection.php
Normal file
42
app/Support/JsonApi/SortsCollection.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/*
|
||||
* SortsCollection.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\JsonApi;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use LaravelJsonApi\Core\Query\SortFields;
|
||||
|
||||
trait SortsCollection
|
||||
{
|
||||
protected function sortCollection(Collection $collection, ?SortFields $sortFields): Collection
|
||||
{
|
||||
if (null === $sortFields) {
|
||||
return $collection;
|
||||
}
|
||||
foreach ($sortFields->all() as $sortField) {
|
||||
$collection = $sortField->isAscending() ? $collection->sortBy($sortField->name()) : $collection->sortByDesc($sortField->name());
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
46
app/Support/JsonApi/ValidateSortParameters.php
Normal file
46
app/Support/JsonApi/ValidateSortParameters.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* ValidateSortParameters.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\JsonApi;
|
||||
|
||||
use LaravelJsonApi\Core\Query\SortFields;
|
||||
|
||||
trait ValidateSortParameters
|
||||
{
|
||||
public function validateParams(string $class, ?SortFields $params): bool
|
||||
{
|
||||
if (null === $params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$config = config(sprintf('firefly.full_data_set.%s', $class)) ?? [];
|
||||
|
||||
foreach ($params->all() as $field) {
|
||||
if (in_array($field->name(), $config, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
216
app/Support/Models/AccountBalanceCalculator.php
Normal file
216
app/Support/Models/AccountBalanceCalculator.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/*
|
||||
* AccountBalanceCalculator.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\Models;
|
||||
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountBalance;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AccountBalanceCalculator
|
||||
{
|
||||
private function __construct()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate all balances for a given account.
|
||||
*
|
||||
* Je moet toch altijd wel alles doen want je weet niet waar een transaction journal invloed op heeft.
|
||||
* Dus dit aantikken per transaction journal is zinloos, beide accounts moeten gedaan worden.
|
||||
*/
|
||||
public static function recalculateAll(): void
|
||||
{
|
||||
$object = new self();
|
||||
$object->recalculateLatest(null);
|
||||
// $object->recalculateJournals(null, null);
|
||||
}
|
||||
|
||||
public static function recalculateForJournal(TransactionJournal $transactionJournal): void
|
||||
{
|
||||
$object = new self();
|
||||
foreach ($transactionJournal->transactions as $transaction) {
|
||||
$object->recalculateLatest($transaction->account);
|
||||
// $object->recalculateJournals($transaction->account, $transactionJournal);
|
||||
}
|
||||
}
|
||||
|
||||
private function getAccountBalanceByAccount(int $account, int $currency): AccountBalance
|
||||
{
|
||||
$query = AccountBalance::where('title', 'balance')->where('account_id', $account)->where('transaction_currency_id', $currency);
|
||||
|
||||
$entry = $query->first();
|
||||
if (null !== $entry) {
|
||||
// Log::debug(sprintf('Found account balance "balance" for account #%d and currency #%d: %s', $account, $currency, $entry->balance));
|
||||
|
||||
return $entry;
|
||||
}
|
||||
$entry = new AccountBalance();
|
||||
$entry->title = 'balance';
|
||||
$entry->account_id = $account;
|
||||
$entry->transaction_currency_id = $currency;
|
||||
$entry->balance = '0';
|
||||
$entry->save();
|
||||
// Log::debug(sprintf('Created new account balance for account #%d and currency #%d: %s', $account, $currency, $entry->balance));
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
private function getAccountBalanceByJournal(string $title, int $account, int $journal, int $currency): AccountBalance
|
||||
{
|
||||
$query = AccountBalance::where('title', $title)->where('account_id', $account)->where('transaction_journal_id', $journal)->where('transaction_currency_id', $currency);
|
||||
|
||||
$entry = $query->first();
|
||||
if (null !== $entry) {
|
||||
return $entry;
|
||||
}
|
||||
$entry = new AccountBalance();
|
||||
$entry->title = $title;
|
||||
$entry->account_id = $account;
|
||||
$entry->transaction_journal_id = $journal;
|
||||
$entry->transaction_currency_id = $currency;
|
||||
$entry->balance = '0';
|
||||
$entry->save();
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
private function recalculateLatest(?Account $account): void
|
||||
{
|
||||
$query = Transaction::groupBy(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
|
||||
|
||||
if (null !== $account) {
|
||||
$query->where('transactions.account_id', $account->id);
|
||||
}
|
||||
$result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
|
||||
|
||||
// reset account balances:
|
||||
$this->resetAccountBalancesByAccount('balance', $account);
|
||||
|
||||
/** @var \stdClass $row */
|
||||
foreach ($result as $row) {
|
||||
$account = (int) $row->account_id;
|
||||
$transactionCurrency = (int) $row->transaction_currency_id;
|
||||
$foreignCurrency = (int) $row->foreign_currency_id;
|
||||
$sumAmount = $row->sum_amount;
|
||||
$sumForeignAmount = $row->sum_foreign_amount;
|
||||
|
||||
// first create for normal currency:
|
||||
$entry = $this->getAccountBalanceByAccount($account, $transactionCurrency);
|
||||
$entry->balance = bcadd($entry->balance, $sumAmount);
|
||||
$entry->save();
|
||||
|
||||
// then do foreign amount, if present:
|
||||
if ($foreignCurrency > 0) {
|
||||
$entry = $this->getAccountBalanceByAccount($account, $foreignCurrency);
|
||||
$entry->balance = bcadd($entry->balance, $sumForeignAmount);
|
||||
$entry->save();
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d account balance(s)', $result->count()));
|
||||
}
|
||||
|
||||
private function resetAccountBalancesByAccount(string $title, ?Account $account): void
|
||||
{
|
||||
if (null === $account) {
|
||||
$count = AccountBalance::whereNotNull('updated_at')->where('title', $title)->update(['balance' => '0']);
|
||||
Log::debug(sprintf('Set %d account balance(s) to zero.', $count));
|
||||
|
||||
return;
|
||||
}
|
||||
$count = AccountBalance::where('account_id', $account->id)->where('title', $title)->update(['balance' => '0']);
|
||||
Log::debug(sprintf('Set %d account balance(s) of account #%d to zero.', $count, $account->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Als je alles opnieuw doet, verzamel je alle transactions en het bedrag en zet je dat neer als "balance after journal".
|
||||
* Dat betekent, netjes op volgorde van datum en doorrekenen.
|
||||
*
|
||||
* Zodra je een transaction journal verplaatst (datum) moet je dat journal en alle latere journals opnieuw doen.
|
||||
* Maar dan moet je van de account wel een beginnetje hebben, namelijk de balance tot en met dat moment.
|
||||
*
|
||||
* 1. Dus dan search je eerst naar die SUM, som alle transactions eerder dan (niet inclusief) de journal.
|
||||
* 2. En vanaf daar pak je alle journals op of na de journal (dus ook de journal zelf) en begin je door te tellen.
|
||||
* 3. Elke voorbij gaande journal entry "balance_after_journal" geef je een update of voeg je toe.
|
||||
*/
|
||||
private function recalculateJournals(?Account $account, ?TransactionJournal $transactionJournal): void
|
||||
{
|
||||
$query = Transaction::groupBy(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
|
||||
$query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
|
||||
$query->orderBy('transaction_journals.date', 'asc');
|
||||
$amounts = [];
|
||||
if (null !== $account) {
|
||||
$query->where('transactions.account_id', $account->id);
|
||||
}
|
||||
if (null !== $account && null !== $transactionJournal) {
|
||||
$query->where('transaction_journals.date', '>=', $transactionJournal->date);
|
||||
$amounts = $this->getStartAmounts($account, $transactionJournal);
|
||||
}
|
||||
$result = $query->get(['transactions.account_id', 'transaction_journals.id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
|
||||
|
||||
/** @var \stdClass $row */
|
||||
foreach ($result as $row) {
|
||||
$account = (int) $row->account_id;
|
||||
$transactionCurrency = (int) $row->transaction_currency_id;
|
||||
$foreignCurrency = (int) $row->foreign_currency_id;
|
||||
$sumAmount = $row->sum_amount;
|
||||
$sumForeignAmount = $row->sum_foreign_amount;
|
||||
$journalId = (int) $row->id;
|
||||
|
||||
// new amounts:
|
||||
$amounts[$account][$transactionCurrency] = bcadd($amounts[$account][$transactionCurrency] ?? '0', $sumAmount ?? '0');
|
||||
$amounts[$account][$foreignCurrency] = bcadd($amounts[$account][$foreignCurrency] ?? '0', $sumForeignAmount ?? '0');
|
||||
|
||||
// first create for normal currency:
|
||||
$entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $transactionCurrency);
|
||||
$entry->balance = $amounts[$account][$transactionCurrency];
|
||||
$entry->save();
|
||||
|
||||
// then do foreign amount, if present:
|
||||
if ($foreignCurrency > 0) {
|
||||
$entry = self::getAccountBalanceByJournal('balance_after_journal', $account, $journalId, $foreignCurrency);
|
||||
$entry->balance = $amounts[$account][$foreignCurrency];
|
||||
$entry->save();
|
||||
}
|
||||
}
|
||||
|
||||
// select transactions.account_id, transaction_journals.id, transactions.transaction_currency_id, transactions.foreign_currency_id, sum(transactions.amount), sum(transactions.foreign_amount)
|
||||
//
|
||||
// from transactions
|
||||
//
|
||||
// left join transaction_journals ON transaction_journals.id = transactions.transaction_journal_id
|
||||
//
|
||||
// group by account_id, transaction_journals.id, transaction_currency_id, foreign_currency_id
|
||||
// order by transaction_journals.date desc
|
||||
}
|
||||
|
||||
private function getStartAmounts(Account $account, TransactionJournal $journal): array
|
||||
{
|
||||
exit('here we are');
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ use Carbon\Carbon;
|
||||
use Carbon\Exceptions\InvalidDateException;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
@@ -114,6 +115,11 @@ trait ConvertsDataTypes
|
||||
return (string)$this->clearString((string)$entry);
|
||||
}
|
||||
|
||||
public function convertIban(string $field): string
|
||||
{
|
||||
return Steam::filterSpaces($this->convertString($field));
|
||||
}
|
||||
|
||||
public function clearString(?string $string): ?string
|
||||
{
|
||||
$string = $this->clearStringKeepNewlines($string);
|
||||
@@ -131,6 +137,13 @@ trait ConvertsDataTypes
|
||||
return trim($string);
|
||||
}
|
||||
|
||||
public function clearIban(?string $string): ?string
|
||||
{
|
||||
$string = $this->clearString($string);
|
||||
|
||||
return Steam::filterSpaces($string);
|
||||
}
|
||||
|
||||
public function clearStringKeepNewlines(?string $string): ?string
|
||||
{
|
||||
if (null === $string) {
|
||||
@@ -159,9 +172,7 @@ trait ConvertsDataTypes
|
||||
|
||||
if (method_exists($this, 'validateUserGroup')) { // @phpstan-ignore-line
|
||||
$userGroup = $this->validateUserGroup($this);
|
||||
if (null !== $userGroup) {
|
||||
$repository->setUserGroup($userGroup);
|
||||
}
|
||||
$repository->setUserGroup($userGroup);
|
||||
}
|
||||
|
||||
// set administration ID
|
||||
|
77
app/Support/Request/GetFilterInstructions.php
Normal file
77
app/Support/Request/GetFilterInstructions.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/*
|
||||
* GetFilterInstructions.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 GetFilterInstructions
|
||||
{
|
||||
private const string INVALID_FILTER = '%INVALID_JAMES_%';
|
||||
|
||||
final public function getFilterInstructions(string $key): array
|
||||
{
|
||||
$config = config(sprintf('firefly.filters.allowed.%s', $key));
|
||||
$allowed = array_keys($config);
|
||||
$set = $this->get('filters', []);
|
||||
$result = [];
|
||||
if (0 === count($set)) {
|
||||
return [];
|
||||
}
|
||||
foreach ($set as $info) {
|
||||
$column = $info['column'] ?? 'NOPE';
|
||||
$filterValue = (string) ($info['filter'] ?? self::INVALID_FILTER);
|
||||
if (false === in_array($column, $allowed, true)) {
|
||||
// skip invalid column
|
||||
continue;
|
||||
}
|
||||
$filterType = $config[$column] ?? false;
|
||||
|
||||
switch ($filterType) {
|
||||
default:
|
||||
exit(sprintf('Do not support filter type "%s"', $filterType));
|
||||
|
||||
case 'boolean':
|
||||
$filterValue = $this->booleanInstruction($filterValue);
|
||||
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
break;
|
||||
}
|
||||
$result[$column] = $filterValue;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function booleanInstruction(string $filterValue): ?bool
|
||||
{
|
||||
if ('true' === $filterValue) {
|
||||
return true;
|
||||
}
|
||||
if ('false' === $filterValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -681,11 +681,13 @@ class Steam
|
||||
"\u{202F}", // narrow no-break space
|
||||
"\u{3000}", // ideographic space
|
||||
"\u{FEFF}", // zero width no -break space
|
||||
"\x20", // plain old normal space
|
||||
"\x20", // plain old normal space,
|
||||
' ',
|
||||
];
|
||||
|
||||
// clear zalgo text
|
||||
$string = preg_replace('/(\pM{2})\pM+/u', '\1', $string);
|
||||
$string = preg_replace('/\s+/', '', $string);
|
||||
|
||||
return str_replace($search, '', $string);
|
||||
}
|
||||
|
@@ -41,11 +41,13 @@ class AccountTransformer extends AbstractTransformer
|
||||
{
|
||||
private array $accountMeta;
|
||||
private array $accountTypes;
|
||||
private array $fullTypes;
|
||||
private array $balanceDifferences;
|
||||
private array $convertedBalances;
|
||||
private array $currencies;
|
||||
private TransactionCurrency $default;
|
||||
private array $lastActivity;
|
||||
private array $objectGroups;
|
||||
|
||||
/**
|
||||
* This method collects meta-data for one or all accounts in the transformer's collection.
|
||||
@@ -55,10 +57,18 @@ class AccountTransformer extends AbstractTransformer
|
||||
$this->currencies = [];
|
||||
$this->accountMeta = [];
|
||||
$this->accountTypes = [];
|
||||
$this->fullTypes = [];
|
||||
$this->lastActivity = [];
|
||||
$this->objectGroups = [];
|
||||
$this->convertedBalances = [];
|
||||
$this->balanceDifferences = [];
|
||||
|
||||
Log::debug(sprintf('collectMetaData on %d object(s)', $objects->count()));
|
||||
|
||||
// first collect all the "heavy" stuff that relies on ALL data to be present.
|
||||
// get last activity:
|
||||
$this->getLastActivity($objects);
|
||||
|
||||
// get balances of all accounts
|
||||
$this->getMetaBalances($objects);
|
||||
|
||||
@@ -71,15 +81,25 @@ class AccountTransformer extends AbstractTransformer
|
||||
// get account types:
|
||||
$this->collectAccountTypes($objects);
|
||||
|
||||
// get last activity:
|
||||
$this->getLastActivity($objects);
|
||||
|
||||
// TODO add balance difference
|
||||
// add balance difference
|
||||
if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) {
|
||||
$this->getBalanceDifference($objects, $this->parameters->get('start'), $this->parameters->get('end'));
|
||||
}
|
||||
|
||||
return $this->sortAccounts($objects);
|
||||
// get object groups
|
||||
$this->getObjectGroups($objects);
|
||||
|
||||
// sort:
|
||||
$objects = $this->sortAccounts($objects);
|
||||
|
||||
// if pagination is disabled, do it now:
|
||||
if (true === $this->parameters->get('disablePagination')) {
|
||||
$page = (int) $this->parameters->get('page');
|
||||
$size = (int) $this->parameters->get('pageSize');
|
||||
$objects = $objects->slice(($page - 1) * $size, $size);
|
||||
}
|
||||
|
||||
return $objects;
|
||||
}
|
||||
|
||||
private function getDate(): Carbon
|
||||
@@ -97,32 +117,44 @@ class AccountTransformer extends AbstractTransformer
|
||||
*/
|
||||
public function transform(Account $account): array
|
||||
{
|
||||
$id = $account->id;
|
||||
$id = $account->id;
|
||||
|
||||
// various meta
|
||||
$accountRole = $this->accountMeta[$id]['account_role'] ?? null;
|
||||
$accountType = $this->accountTypes[$id];
|
||||
$order = $account->order;
|
||||
$accountRole = $this->accountMeta[$id]['account_role'] ?? null;
|
||||
$accountType = $this->accountTypes[$id];
|
||||
$order = $account->order;
|
||||
|
||||
// liability type
|
||||
$liabilityType = 'liabilities' === $accountType ? $this->fullTypes[$id] : null;
|
||||
$liabilityDirection = $this->accountMeta[$id]['liability_direction'] ?? null;
|
||||
$interest = $this->accountMeta[$id]['interest'] ?? null;
|
||||
$interestPeriod = $this->accountMeta[$id]['interest_period'] ?? null;
|
||||
$currentDebt = $this->accountMeta[$id]['current_debt'] ?? null;
|
||||
|
||||
// no currency? use default
|
||||
$currency = $this->default;
|
||||
$currency = $this->default;
|
||||
if (array_key_exists($id, $this->accountMeta) && 0 !== (int) ($this->accountMeta[$id]['currency_id'] ?? 0)) {
|
||||
$currency = $this->currencies[(int) $this->accountMeta[$id]['currency_id']];
|
||||
}
|
||||
// amounts and calculation.
|
||||
$balance = $this->balances[$id]['balance'] ?? null;
|
||||
$nativeBalance = $this->convertedBalances[$id]['native_balance'] ?? null;
|
||||
$balance = $this->balances[$id]['balance'] ?? null;
|
||||
$nativeBalance = $this->convertedBalances[$id]['native_balance'] ?? null;
|
||||
|
||||
// no order for some accounts:
|
||||
if (!in_array(strtolower($accountType), ['liability', 'liabilities', 'asset'], true)) {
|
||||
$order = null;
|
||||
}
|
||||
|
||||
// object group
|
||||
$objectGroupId = $this->objectGroups[$id]['id'] ?? null;
|
||||
$objectGroupOrder = $this->objectGroups[$id]['order'] ?? null;
|
||||
$objectGroupTitle = $this->objectGroups[$id]['title'] ?? null;
|
||||
|
||||
// balance difference
|
||||
$diffStart = null;
|
||||
$diffEnd = null;
|
||||
$balanceDiff = null;
|
||||
$nativeBalanceDiff = null;
|
||||
$diffStart = null;
|
||||
$diffEnd = null;
|
||||
$balanceDiff = null;
|
||||
$nativeBalanceDiff = null;
|
||||
if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) {
|
||||
$diffStart = $this->parameters->get('start')->toAtomString();
|
||||
$diffEnd = $this->parameters->get('end')->toAtomString();
|
||||
@@ -165,19 +197,25 @@ class AccountTransformer extends AbstractTransformer
|
||||
// more meta
|
||||
'last_activity' => array_key_exists($id, $this->lastActivity) ? $this->lastActivity[$id]->toAtomString() : null,
|
||||
|
||||
// liability stuff
|
||||
'liability_type' => $liabilityType,
|
||||
'liability_direction' => $liabilityDirection,
|
||||
'interest' => $interest,
|
||||
'interest_period' => $interestPeriod,
|
||||
'current_debt' => $currentDebt,
|
||||
|
||||
// object group
|
||||
'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null,
|
||||
'object_group_order' => $objectGroupOrder,
|
||||
'object_group_title' => $objectGroupTitle,
|
||||
|
||||
// 'notes' => $this->repository->getNoteText($account),
|
||||
// 'monthly_payment_date' => $monthlyPaymentDate,
|
||||
// 'credit_card_type' => $creditCardType,
|
||||
// 'account_number' => $this->repository->getMetaValue($account, 'account_number'),
|
||||
// 'bic' => $this->repository->getMetaValue($account, 'BIC'),
|
||||
// 'virtual_balance' => number_format((float) $account->virtual_balance, $decimalPlaces, '.', ''),
|
||||
// 'opening_balance' => $openingBalance,
|
||||
// 'opening_balance_date' => $openingBalanceDate,
|
||||
// 'liability_type' => $liabilityType,
|
||||
// 'liability_direction' => $liabilityDirection,
|
||||
// 'interest' => $interest,
|
||||
// 'interest_period' => $interestPeriod,
|
||||
// 'current_debt' => $this->repository->getMetaValue($account, 'current_debt'),
|
||||
// 'include_net_worth' => $includeNetWorth,
|
||||
// 'longitude' => $longitude,
|
||||
// 'latitude' => $latitude,
|
||||
@@ -212,7 +250,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$metaFields = $accountRepository->getMetaValues($accounts, ['currency_id', 'account_role', 'account_number']);
|
||||
$metaFields = $accountRepository->getMetaValues($accounts, ['currency_id', 'account_role', 'account_number', 'liability_direction', 'interest', 'interest_period', 'current_debt']);
|
||||
$currencyIds = $metaFields->where('name', 'currency_id')->pluck('data')->toArray();
|
||||
|
||||
$currencies = $repository->getByIds($currencyIds);
|
||||
@@ -235,6 +273,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
/** @var AccountType $row */
|
||||
foreach ($accountTypes as $row) {
|
||||
$this->accountTypes[$row->id] = (string) config(sprintf('firefly.shortNamesByFullName.%s', $row->type));
|
||||
$this->fullTypes[$row->id] = $row->type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +314,9 @@ class AccountTransformer extends AbstractTransformer
|
||||
if ('balance_difference' === $column) {
|
||||
$accounts = $this->sortByBalanceDifference($accounts, $direction);
|
||||
}
|
||||
if ('current_debt' === $column) {
|
||||
$accounts = $this->sortByCurrentDebt($accounts, $direction);
|
||||
}
|
||||
}
|
||||
|
||||
return $accounts;
|
||||
@@ -365,4 +407,26 @@ class AccountTransformer extends AbstractTransformer
|
||||
return $rightBalance <=> $leftBalance;
|
||||
});
|
||||
}
|
||||
|
||||
private function sortByCurrentDebt(Collection $accounts, string $direction): Collection
|
||||
{
|
||||
$amounts = $this->accountMeta;
|
||||
|
||||
return $accounts->sort(function (Account $left, Account $right) use ($amounts, $direction) {
|
||||
$leftCurrent = (float) ($amounts[$left->id]['current_debt'] ?? 0);
|
||||
$rightCurrent = (float) ($amounts[$right->id]['current_debt'] ?? 0);
|
||||
if ('asc' === $direction) {
|
||||
return $leftCurrent <=> $rightCurrent;
|
||||
}
|
||||
|
||||
return $rightCurrent <=> $leftCurrent;
|
||||
});
|
||||
}
|
||||
|
||||
private function getObjectGroups(Collection $accounts): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = app(AccountRepositoryInterface::class);
|
||||
$this->objectGroups = $accountRepository->getObjectGroups($accounts);
|
||||
}
|
||||
}
|
||||
|
@@ -170,6 +170,7 @@ class FireflyValidator extends Validator
|
||||
"\u{202F}", // narrow no-break space
|
||||
"\u{3000}", // ideographic space
|
||||
"\u{FEFF}", // zero width no -break space
|
||||
' ',
|
||||
'-',
|
||||
'?',
|
||||
];
|
||||
|
22
changelog.md
22
changelog.md
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 6.1.16 - 2024-05-20
|
||||
|
||||
### Added
|
||||
|
||||
- Added [THANKS.md] to give credit to all developers who help with the development of Firefly III.
|
||||
|
||||
### Changed
|
||||
|
||||
- New data model for "account balance" makes it easier to calculate and use multi-currency accounts. Not yet in use.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 8840](https://github.com/firefly-iii/firefly-iii/issues/8840) (Budget page crash) reported by @JcMinarro
|
||||
- [Issue 8863](https://github.com/firefly-iii/firefly-iii/issues/8863) (Empty webhooks page) reported by @mrahmadt
|
||||
- [Issue 8867](https://github.com/firefly-iii/firefly-iii/issues/8867) (SQL Integrity constraint violation when inserting into budget_limits) reported by @HedgehogRidingAnOwl
|
||||
- [Issue 8858](https://github.com/firefly-iii/firefly-iii/issues/8858) (A single Account constantly loses its Account NUmber / IBAN ) reported by @ypsilonkah
|
||||
|
||||
### API
|
||||
|
||||
- New filters for the v2 autocomplete endpoints.
|
||||
- Various attempts to make a better v2 accounts endpoint.
|
||||
|
||||
## 6.1.15 - 2024-04-24
|
||||
|
||||
### Fixed
|
||||
|
@@ -87,6 +87,8 @@
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"jc5/google2fa-laravel": "^2.0",
|
||||
"jc5/recovery": "^2",
|
||||
"laravel-json-api/laravel": "^4.0",
|
||||
"laravel-json-api/non-eloquent": "^4.0",
|
||||
"laravel/framework": "^11",
|
||||
"laravel/passport": "^12",
|
||||
"laravel/sanctum": "^4",
|
||||
@@ -101,13 +103,13 @@
|
||||
"psr/log": "<4",
|
||||
"ramsey/uuid": "^4.7",
|
||||
"rcrowe/twigbridge": "^0.14",
|
||||
"twig/twig": "3.8.0",
|
||||
"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"
|
||||
"symfony/mailgun-mailer": "^7.0",
|
||||
"twig/twig": "3.8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.9",
|
||||
@@ -116,6 +118,7 @@
|
||||
"fakerphp/faker": "1.*",
|
||||
"filp/whoops": "2.*",
|
||||
"larastan/larastan": "^2",
|
||||
"laravel-json-api/testing": "^3.0",
|
||||
"mockery/mockery": "1.*",
|
||||
"phpstan/extension-installer": "^1.3",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
|
1386
composer.lock
generated
1386
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'),
|
||||
'editor' => env('DEBUGBAR_EDITOR') ?? env('IGNITION_EDITOR', 'phpstorm'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@@ -117,8 +117,8 @@ return [
|
||||
'expression_engine' => false,
|
||||
// see cer.php for exchange rates feature flag.
|
||||
],
|
||||
'version' => '6.1.15',
|
||||
'api_version' => '2.0.14',
|
||||
'version' => '6.1.16',
|
||||
'api_version' => '2.1.0',
|
||||
'db_version' => 24,
|
||||
|
||||
// generic settings
|
||||
@@ -920,11 +920,31 @@ return [
|
||||
// preselected account lists possibilities:
|
||||
'preselected_accounts' => ['all', 'assets', 'liabilities'],
|
||||
|
||||
// allowed sort columns for API's
|
||||
// allowed filters (search) for APIs
|
||||
'filters' => [
|
||||
'allowed' => [
|
||||
'accounts' => [
|
||||
'name' => 'string',
|
||||
'active' => 'boolean',
|
||||
'iban' => 'iban',
|
||||
'balance' => 'numeric',
|
||||
'last_activity' => 'date',
|
||||
'balance_difference' => 'numeric',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// allowed sort columns for APIs
|
||||
'sorting' => [
|
||||
'allowed' => [
|
||||
'transactions' => ['description', 'amount'],
|
||||
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference'],
|
||||
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference', 'current_debt'],
|
||||
],
|
||||
],
|
||||
'full_data_set' => [
|
||||
'account' => ['last_activity', 'balance_difference', 'current_balance', 'current_debt'],
|
||||
],
|
||||
'valid_query_filters' => [
|
||||
'account' => ['name', 'iban', 'active'],
|
||||
],
|
||||
];
|
||||
|
35
config/jsonapi.php
Normal file
35
config/jsonapi.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use FireflyIII\JsonApi\V3\Server;
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Root Namespace
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The root JSON:API namespace, within your application's namespace.
|
||||
| This is used when generating any class that does not sit *within*
|
||||
| a server's namespace. For example, new servers and filters.
|
||||
|
|
||||
| By default this is set to `JsonApi` which means the root namespace
|
||||
| will be `\App\JsonApi`, if your application's namespace is `App`.
|
||||
*/
|
||||
'namespace' => 'JsonApi',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Servers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| A list of the JSON:API compliant APIs in your application, referred to
|
||||
| as "servers". They must be listed below, with the array key being the
|
||||
| unique name for each server, and the value being the fully-qualified
|
||||
| class name of the server class.
|
||||
*/
|
||||
'servers' => [
|
||||
'v3' => Server::class,
|
||||
],
|
||||
];
|
@@ -52,6 +52,15 @@ return [
|
||||
'bad_type_destination',
|
||||
],
|
||||
'firefly' => [
|
||||
'liability_direction_debit_short',
|
||||
'liability_direction_credit_short',
|
||||
'interest_calc_yearly',
|
||||
'interest_calc_',
|
||||
'interest_calc_daily',
|
||||
'interest_calc_monthly',
|
||||
'interest_calc_weekly',
|
||||
'interest_calc_half-year',
|
||||
'interest_calc_quarterly',
|
||||
'spent',
|
||||
'administration_owner',
|
||||
'administration_you',
|
||||
|
@@ -284,7 +284,7 @@ class CreateMainTables extends Migration
|
||||
$table->integer('budget_id', false, true);
|
||||
$table->date('startdate');
|
||||
$table->decimal('amount', 32, 12);
|
||||
$table->string('repeat_freq', 30);
|
||||
$table->string('repeat_freq', 30)->nullable();
|
||||
$table->boolean('repeats')->default(0);
|
||||
$table->foreign('budget_id')->references('id')->on('budgets')->onDelete('cascade');
|
||||
}
|
||||
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (!Schema::hasTable('account_balances')) {
|
||||
Schema::create('account_balances', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->timestamps();
|
||||
$table->string('title', 100)->nullable();
|
||||
$table->integer('account_id', false, true);
|
||||
$table->integer('transaction_currency_id', false, true);
|
||||
$table->date('date')->nullable();
|
||||
$table->integer('transaction_journal_id', false, true)->nullable();
|
||||
$table->decimal('balance', 32, 12);
|
||||
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
$table->foreign('transaction_journal_id')->references('id')->on('transaction_journals')->onDelete('cascade');
|
||||
$table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
|
||||
|
||||
$table->unique(['account_id', 'transaction_currency_id', 'transaction_journal_id', 'date', 'title'], 'unique_account_currency');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('account_balances');
|
||||
}
|
||||
};
|
671
package-lock.json
generated
671
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,15 @@
|
||||
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
|
||||
},
|
||||
"firefly": {
|
||||
"liability_direction_debit_short": "\u0414\u044a\u043b\u0436\u0430 \u0434\u044a\u043b\u0433",
|
||||
"liability_direction_credit_short": "\u0414\u044a\u043b\u0436\u044a\u0442 \u043c\u0438 \u0434\u044a\u043b\u0433",
|
||||
"interest_calc_yearly": "\u0413\u043e\u0434\u0438\u0448\u043d\u043e",
|
||||
"interest_calc_": "\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430",
|
||||
"interest_calc_daily": "\u041d\u0430 \u0434\u0435\u043d",
|
||||
"interest_calc_monthly": "\u041d\u0430 \u043c\u0435\u0441\u0435\u0446",
|
||||
"interest_calc_weekly": "\u0421\u0435\u0434\u043c\u0438\u0447\u043d\u043e",
|
||||
"interest_calc_half-year": "\u0417\u0430 \u043f\u043e\u043b\u043e\u0432\u0438\u043d \u0433\u043e\u0434\u0438\u043d\u0430",
|
||||
"interest_calc_quarterly": "\u0417\u0430 \u0442\u0440\u0438\u043c\u0435\u0441\u0435\u0447\u0438\u0435",
|
||||
"spent": "\u041f\u043e\u0445\u0430\u0440\u0447\u0435\u043d\u0438",
|
||||
"administration_owner": "Administration owner: {{email}}",
|
||||
"administration_you": "Your role: {{role}}",
|
||||
|
@@ -27,6 +27,15 @@
|
||||
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
|
||||
},
|
||||
"firefly": {
|
||||
"liability_direction_debit_short": "\u0414\u044a\u043b\u0436\u0430 \u0434\u044a\u043b\u0433",
|
||||
"liability_direction_credit_short": "\u0414\u044a\u043b\u0436\u044a\u0442 \u043c\u0438 \u0434\u044a\u043b\u0433",
|
||||
"interest_calc_yearly": "\u0413\u043e\u0434\u0438\u0448\u043d\u043e",
|
||||
"interest_calc_": "\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430",
|
||||
"interest_calc_daily": "\u041d\u0430 \u0434\u0435\u043d",
|
||||
"interest_calc_monthly": "\u041d\u0430 \u043c\u0435\u0441\u0435\u0446",
|
||||
"interest_calc_weekly": "\u0421\u0435\u0434\u043c\u0438\u0447\u043d\u043e",
|
||||
"interest_calc_half-year": "\u0417\u0430 \u043f\u043e\u043b\u043e\u0432\u0438\u043d \u0433\u043e\u0434\u0438\u043d\u0430",
|
||||
"interest_calc_quarterly": "\u0417\u0430 \u0442\u0440\u0438\u043c\u0435\u0441\u0435\u0447\u0438\u0435",
|
||||
"spent": "\u041f\u043e\u0445\u0430\u0440\u0447\u0435\u043d\u0438",
|
||||
"administration_owner": "Administration owner: {{email}}",
|
||||
"administration_you": "Your role: {{role}}",
|
||||
|
@@ -10,23 +10,32 @@
|
||||
"title": "T\u00edtol"
|
||||
},
|
||||
"list": {
|
||||
"drag_and_drop": "Drag and drop",
|
||||
"drag_and_drop": "Arrossega i deixa anar",
|
||||
"active": "Est\u00e0 actiu?",
|
||||
"name": "Nom",
|
||||
"type": "Tipus",
|
||||
"number": "Account number",
|
||||
"number": "N\u00famero de compte",
|
||||
"liability_type": "Tipus de passiu",
|
||||
"current_balance": "Current balance",
|
||||
"last_activity": "Last activity",
|
||||
"amount_due": "Amount due",
|
||||
"balance_difference": "Balance difference",
|
||||
"menu": "Menu"
|
||||
"current_balance": "Balan\u00e7 actual",
|
||||
"last_activity": "Darrera activitat",
|
||||
"amount_due": "Import pendent",
|
||||
"balance_difference": "Difer\u00e8ncia de saldo",
|
||||
"menu": "Men\u00fa"
|
||||
},
|
||||
"validation": {
|
||||
"bad_type_source": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte font.",
|
||||
"bad_type_destination": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte de dest\u00ed."
|
||||
},
|
||||
"firefly": {
|
||||
"liability_direction_debit_short": "Ho dec",
|
||||
"liability_direction_credit_short": "Se'm deu",
|
||||
"interest_calc_yearly": "Per any",
|
||||
"interest_calc_": "desconegut",
|
||||
"interest_calc_daily": "Per dia",
|
||||
"interest_calc_monthly": "Per mes",
|
||||
"interest_calc_weekly": "Per setmana",
|
||||
"interest_calc_half-year": "Cada mig any",
|
||||
"interest_calc_quarterly": "Per quadrimestre",
|
||||
"spent": "Gastat",
|
||||
"administration_owner": "Propietari de l'administraci\u00f3: {{email}}",
|
||||
"administration_you": "El teu rol: {{role}}",
|
||||
|
@@ -10,23 +10,32 @@
|
||||
"title": "T\u00edtol"
|
||||
},
|
||||
"list": {
|
||||
"drag_and_drop": "Drag and drop",
|
||||
"drag_and_drop": "Arrossega i deixa anar",
|
||||
"active": "Est\u00e0 actiu?",
|
||||
"name": "Nom",
|
||||
"type": "Tipus",
|
||||
"number": "Account number",
|
||||
"number": "N\u00famero de compte",
|
||||
"liability_type": "Tipus de passiu",
|
||||
"current_balance": "Current balance",
|
||||
"last_activity": "Last activity",
|
||||
"amount_due": "Amount due",
|
||||
"balance_difference": "Balance difference",
|
||||
"menu": "Menu"
|
||||
"current_balance": "Balan\u00e7 actual",
|
||||
"last_activity": "Darrera activitat",
|
||||
"amount_due": "Import pendent",
|
||||
"balance_difference": "Difer\u00e8ncia de saldo",
|
||||
"menu": "Men\u00fa"
|
||||
},
|
||||
"validation": {
|
||||
"bad_type_source": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte font.",
|
||||
"bad_type_destination": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte de dest\u00ed."
|
||||
},
|
||||
"firefly": {
|
||||
"liability_direction_debit_short": "Ho dec",
|
||||
"liability_direction_credit_short": "Se'm deu",
|
||||
"interest_calc_yearly": "Per any",
|
||||
"interest_calc_": "desconegut",
|
||||
"interest_calc_daily": "Per dia",
|
||||
"interest_calc_monthly": "Per mes",
|
||||
"interest_calc_weekly": "Per setmana",
|
||||
"interest_calc_half-year": "Cada mig any",
|
||||
"interest_calc_quarterly": "Per quadrimestre",
|
||||
"spent": "Gastat",
|
||||
"administration_owner": "Propietari de l'administraci\u00f3: {{email}}",
|
||||
"administration_you": "El teu rol: {{role}}",
|
||||
|
@@ -27,6 +27,15 @@
|
||||
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
|
||||
},
|
||||
"firefly": {
|
||||
"liability_direction_debit_short": "Owe this debt",
|
||||
"liability_direction_credit_short": "Owed this debt",
|
||||
"interest_calc_yearly": "Za rok",
|
||||
"interest_calc_": "nezn\u00e1m\u00e9",
|
||||
"interest_calc_daily": "Za den",
|
||||
"interest_calc_monthly": "Za m\u011bs\u00edc",
|
||||
"interest_calc_weekly": "Per week",
|
||||
"interest_calc_half-year": "Per half year",
|
||||
"interest_calc_quarterly": "Per quarter",
|
||||
"spent": "Utraceno",
|
||||
"administration_owner": "Administration owner: {{email}}",
|
||||
"administration_you": "Your role: {{role}}",
|
||||
|
@@ -27,6 +27,15 @@
|
||||
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
|
||||
},
|
||||
"firefly": {
|
||||
"liability_direction_debit_short": "Owe this debt",
|
||||
"liability_direction_credit_short": "Owed this debt",
|
||||
"interest_calc_yearly": "Za rok",
|
||||
"interest_calc_": "nezn\u00e1m\u00e9",
|
||||
"interest_calc_daily": "Za den",
|
||||
"interest_calc_monthly": "Za m\u011bs\u00edc",
|
||||
"interest_calc_weekly": "Per week",
|
||||
"interest_calc_half-year": "Per half year",
|
||||
"interest_calc_quarterly": "Per quarter",
|
||||
"spent": "Utraceno",
|
||||
"administration_owner": "Administration owner: {{email}}",
|
||||
"administration_you": "Your role: {{role}}",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user