mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-19 10:53:37 +00:00
Build administration-compatible budget chart.
This commit is contained in:
60
.ci/php-cs-fixer/composer.lock
generated
60
.ci/php-cs-fixer/composer.lock
generated
@@ -745,16 +745,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/console",
|
"name": "symfony/console",
|
||||||
"version": "v6.3.0",
|
"version": "v6.3.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/console.git",
|
"url": "https://github.com/symfony/console.git",
|
||||||
"reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7"
|
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7",
|
"url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898",
|
||||||
"reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7",
|
"reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -815,7 +815,7 @@
|
|||||||
"terminal"
|
"terminal"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/console/tree/v6.3.0"
|
"source": "https://github.com/symfony/console/tree/v6.3.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -831,7 +831,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-05-29T12:49:39+00:00"
|
"time": "2023-07-19T20:17:28+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
@@ -902,16 +902,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/event-dispatcher",
|
"name": "symfony/event-dispatcher",
|
||||||
"version": "v6.3.0",
|
"version": "v6.3.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||||
"reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa"
|
"reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa",
|
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e",
|
||||||
"reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa",
|
"reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -962,7 +962,7 @@
|
|||||||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0"
|
"source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -978,7 +978,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-04-21T14:41:17+00:00"
|
"time": "2023-07-06T06:56:43+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/event-dispatcher-contracts",
|
"name": "symfony/event-dispatcher-contracts",
|
||||||
@@ -1121,16 +1121,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/finder",
|
"name": "symfony/finder",
|
||||||
"version": "v6.3.0",
|
"version": "v6.3.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/finder.git",
|
"url": "https://github.com/symfony/finder.git",
|
||||||
"reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2"
|
"reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2",
|
"url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e",
|
||||||
"reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2",
|
"reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1165,7 +1165,7 @@
|
|||||||
"description": "Finds files and directories via an intuitive fluent interface",
|
"description": "Finds files and directories via an intuitive fluent interface",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/finder/tree/v6.3.0"
|
"source": "https://github.com/symfony/finder/tree/v6.3.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1181,7 +1181,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-04-02T01:25:41+00:00"
|
"time": "2023-07-31T08:31:44+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/options-resolver",
|
"name": "symfony/options-resolver",
|
||||||
@@ -1744,16 +1744,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/process",
|
"name": "symfony/process",
|
||||||
"version": "v6.3.0",
|
"version": "v6.3.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/process.git",
|
"url": "https://github.com/symfony/process.git",
|
||||||
"reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628"
|
"reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628",
|
"url": "https://api.github.com/repos/symfony/process/zipball/c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d",
|
||||||
"reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628",
|
"reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1785,7 +1785,7 @@
|
|||||||
"description": "Executes commands in sub-processes",
|
"description": "Executes commands in sub-processes",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/process/tree/v6.3.0"
|
"source": "https://github.com/symfony/process/tree/v6.3.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1801,7 +1801,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-05-19T08:06:44+00:00"
|
"time": "2023-07-12T16:00:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/service-contracts",
|
"name": "symfony/service-contracts",
|
||||||
@@ -1949,16 +1949,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/string",
|
"name": "symfony/string",
|
||||||
"version": "v6.3.0",
|
"version": "v6.3.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/string.git",
|
"url": "https://github.com/symfony/string.git",
|
||||||
"reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f"
|
"reference": "53d1a83225002635bca3482fcbf963001313fb68"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f",
|
"url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68",
|
||||||
"reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f",
|
"reference": "53d1a83225002635bca3482fcbf963001313fb68",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2015,7 +2015,7 @@
|
|||||||
"utf8"
|
"utf8"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/string/tree/v6.3.0"
|
"source": "https://github.com/symfony/string/tree/v6.3.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2031,7 +2031,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-03-21T21:06:29+00:00"
|
"time": "2023-07-05T08:41:27+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
308
app/Api/V2/Controllers/Chart/BudgetController.php
Normal file
308
app/Api/V2/Controllers/Chart/BudgetController.php
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/*
|
||||||
|
* BudgetController.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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Api\V2\Controllers\Chart;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Api\V2\Controllers\Controller;
|
||||||
|
use FireflyIII\Api\V2\Request\Generic\DateRequest;
|
||||||
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Models\Budget;
|
||||||
|
use FireflyIII\Models\BudgetLimit;
|
||||||
|
use FireflyIII\Models\TransactionCurrency;
|
||||||
|
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||||
|
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BudgetController
|
||||||
|
*/
|
||||||
|
class BudgetController extends Controller
|
||||||
|
{
|
||||||
|
protected OperationsRepositoryInterface $opsRepository;
|
||||||
|
private BudgetLimitRepositoryInterface $blRepository;
|
||||||
|
private array $currencies = [];
|
||||||
|
private TransactionCurrency $currency;
|
||||||
|
private BudgetRepositoryInterface $repository;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
$this->repository = app(BudgetRepositoryInterface::class);
|
||||||
|
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
|
||||||
|
$this->opsRepository = app(OperationsRepositoryInterface::class);
|
||||||
|
$this->currency = app('amount')->getDefaultCurrency();
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DateRequest $request
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function dashboard(DateRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
// get user.
|
||||||
|
/** @var User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
// group ID
|
||||||
|
$administrationId = $user->getAdministrationId();
|
||||||
|
$this->repository->setAdministrationId($administrationId);
|
||||||
|
$this->opsRepository->setAdministrationId($administrationId);
|
||||||
|
|
||||||
|
$params = $request->getAll();
|
||||||
|
/** @var Carbon $start */
|
||||||
|
$start = $params['start'];
|
||||||
|
/** @var Carbon $end */
|
||||||
|
$end = $params['end'];
|
||||||
|
|
||||||
|
// code from FrontpageChartGenerator, but not in separate class
|
||||||
|
$budgets = $this->repository->getActiveBudgets();
|
||||||
|
$data = [];
|
||||||
|
/** @var Budget $budget */
|
||||||
|
foreach ($budgets as $budget) {
|
||||||
|
// could return multiple arrays, so merge.
|
||||||
|
$data = array_merge($data, $this->processBudget($budget, $start, $end));
|
||||||
|
}
|
||||||
|
return response()->json($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
|
||||||
|
{
|
||||||
|
// get all limits:
|
||||||
|
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
// if no limits
|
||||||
|
if (0 === $limits->count()) {
|
||||||
|
// return as a single item in an array
|
||||||
|
$rows = $this->noBudgetLimits($budget, $start, $end);
|
||||||
|
}
|
||||||
|
if ($limits->count() > 0) {
|
||||||
|
$rows = $this->budgetLimits($budget, $limits);
|
||||||
|
}
|
||||||
|
// is always an array
|
||||||
|
$return = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$current = [
|
||||||
|
'label' => $budget->name,
|
||||||
|
'currency_id' => $row['currency_id'],
|
||||||
|
'currency_code' => $row['currency_code'],
|
||||||
|
'currency_name' => $row['currency_name'],
|
||||||
|
'currency_decimal_places' => $row['currency_decimal_places'],
|
||||||
|
'native_id' => $row['native_id'],
|
||||||
|
'native_code' => $row['native_code'],
|
||||||
|
'native_name' => $row['native_name'],
|
||||||
|
'native_decimal_places' => $row['native_decimal_places'],
|
||||||
|
'period' => null,
|
||||||
|
'start' => $row['start'],
|
||||||
|
'end' => $row['end'],
|
||||||
|
'entries' => [
|
||||||
|
'spent' => $row['spent'],
|
||||||
|
'left' => $row['left'],
|
||||||
|
'overspent' => $row['overspent'],
|
||||||
|
],
|
||||||
|
'native_entries' => [
|
||||||
|
'spent' => $row['native_spent'],
|
||||||
|
'left' => $row['native_left'],
|
||||||
|
'overspent' => $row['native_overspent'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$return[] = $current;
|
||||||
|
}
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When no budget limits are present, the expenses of the whole period are collected and grouped.
|
||||||
|
* This is grouped per currency. Because there is no limit set, "left to spend" and "overspent" are empty.
|
||||||
|
*
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function noBudgetLimits(Budget $budget, Carbon $start, Carbon $end): array
|
||||||
|
{
|
||||||
|
$budgetId = (int)$budget->id;
|
||||||
|
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
|
||||||
|
return $this->processExpenses($budgetId, $spent, $start, $end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared between the "noBudgetLimits" function and "processLimit".
|
||||||
|
*
|
||||||
|
* Will take a single set of expenses and return its info.
|
||||||
|
*
|
||||||
|
* @param int $budgetId
|
||||||
|
* @param array $array
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function processExpenses(int $budgetId, array $array, Carbon $start, Carbon $end): array
|
||||||
|
{
|
||||||
|
$converter = new ExchangeRateConverter();
|
||||||
|
$return = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This array contains the expenses in this budget. Grouped per currency.
|
||||||
|
* The grouping is on the main currency only.
|
||||||
|
*
|
||||||
|
* @var int $currencyId
|
||||||
|
* @var array $block
|
||||||
|
*/
|
||||||
|
foreach ($array as $currencyId => $block) {
|
||||||
|
$this->currencies[$currencyId] = $this->currencies[$currencyId] ?? TransactionCurrency::find($currencyId);
|
||||||
|
$return[$currencyId] = $return[$currencyId] ?? [
|
||||||
|
'currency_id' => $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_id' => (int)$this->currency->id,
|
||||||
|
'native_code' => $this->currency->code,
|
||||||
|
'native_name' => $this->currency->name,
|
||||||
|
'native_symbol' => $this->currency->symbol,
|
||||||
|
'native_decimal_places' => (int)$this->currency->decimal_places,
|
||||||
|
'start' => $start->toAtomString(),
|
||||||
|
'end' => $end->toAtomString(),
|
||||||
|
'spent' => '0',
|
||||||
|
'native_spent' => '0',
|
||||||
|
'left' => '0',
|
||||||
|
'native_left' => '0',
|
||||||
|
'overspent' => '0',
|
||||||
|
'native_overspent' => '0',
|
||||||
|
|
||||||
|
];
|
||||||
|
$currentBudgetArray = $block['budgets'][$budgetId];
|
||||||
|
//var_dump($return);
|
||||||
|
/** @var array $journal */
|
||||||
|
foreach ($currentBudgetArray['transaction_journals'] as $journal) {
|
||||||
|
|
||||||
|
// convert the amount to the native currency.
|
||||||
|
$rate = $converter->getCurrencyRate($this->currencies[$currencyId], $this->currency, $journal['date']);
|
||||||
|
$convertedAmount = bcmul($journal['amount'], $rate);
|
||||||
|
if ($journal['foreign_currency_id'] === $this->currency->id) {
|
||||||
|
$convertedAmount = $journal['foreign_amount'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $journal['amount']);
|
||||||
|
$return[$currencyId]['native_spent'] = bcadd($return[$currencyId]['native_spent'], $convertedAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that processes each budget limit (per budget).
|
||||||
|
*
|
||||||
|
* If you have a budget limit in EUR, only transactions in EUR will be considered.
|
||||||
|
* If you have a budget limit in GBP, only transactions in GBP will be considered.
|
||||||
|
*
|
||||||
|
* If you have a budget limit in EUR, and a transaction in GBP, it will not be considered for the EUR budget limit.
|
||||||
|
*
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param Collection $limits
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function budgetLimits(Budget $budget, Collection $limits): array
|
||||||
|
{
|
||||||
|
app('log')->debug(sprintf('Now in budgetLimits(#%d)', $budget->id));
|
||||||
|
$data = [];
|
||||||
|
/** @var BudgetLimit $limit */
|
||||||
|
foreach ($limits as $limit) {
|
||||||
|
$data = array_merge($data, $this->processLimit($budget, $limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Budget $budget
|
||||||
|
* @param BudgetLimit $limit
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function processLimit(Budget $budget, BudgetLimit $limit): array
|
||||||
|
{
|
||||||
|
$budgetId = (int)$budget->id;
|
||||||
|
$end = clone $limit->end_date;
|
||||||
|
$end->endOfDay();
|
||||||
|
$spent = $this->opsRepository->listExpenses($limit->start_date, $end, null, new Collection([$budget]));
|
||||||
|
$limitCurrencyId = (int)$limit->transaction_currency_id;
|
||||||
|
$limitCurrency = $limit->transactionCurrency;
|
||||||
|
$converter = new ExchangeRateConverter();
|
||||||
|
$filtered = [];
|
||||||
|
$rate = $converter->getCurrencyRate($limitCurrency, $this->currency, $limit->start_date);
|
||||||
|
$convertedLimitAmount = bcmul($limit->amount, $rate);
|
||||||
|
|
||||||
|
|
||||||
|
/** @var array $entry */
|
||||||
|
foreach ($spent as $currencyId => $entry) {
|
||||||
|
// only spent the entry where the entry's currency matches the budget limit's currency
|
||||||
|
// so $filtered will only have 1 or 0 entries
|
||||||
|
if ($entry['currency_id'] === $limitCurrencyId) {
|
||||||
|
$filtered[$currencyId] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$result = $this->processExpenses($budgetId, $filtered, $limit->start_date, $end);
|
||||||
|
if (1 === count($result)) {
|
||||||
|
$compare = bccomp((string)$limit->amount, app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||||
|
if (1 === $compare) {
|
||||||
|
// convert this amount into the native currency:
|
||||||
|
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, $result[$limitCurrencyId]['spent']);
|
||||||
|
$result[$limitCurrencyId]['native_left'] = bcadd($convertedLimitAmount, $result[$limitCurrencyId]['native_spent']);
|
||||||
|
}
|
||||||
|
if ($compare <= 0) {
|
||||||
|
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, $result[$limitCurrencyId]['spent']));
|
||||||
|
$result[$limitCurrencyId]['native_overspent'] = app('steam')->positive(bcadd($convertedLimitAmount, $result[$limitCurrencyId]['native_spent']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/*
|
/*
|
||||||
* BalanceChartRequest.php
|
* BalanceChartRequest.php
|
||||||
* Copyright (c) 2023 james@firefly-iii.org
|
* Copyright (c) 2023 james@firefly-iii.org
|
||||||
|
@@ -47,7 +47,7 @@ class DateRequest extends FormRequest
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'start' => $this->getCarbonDate('start'),
|
'start' => $this->getCarbonDate('start'),
|
||||||
'end' => $this->getCarbonDate('end'),
|
'end' => $this->getCarbonDate('end')->endOfDay(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -81,6 +81,7 @@ class TransactionGroupFactory
|
|||||||
|
|
||||||
$group = new TransactionGroup();
|
$group = new TransactionGroup();
|
||||||
$group->user()->associate($this->user);
|
$group->user()->associate($this->user);
|
||||||
|
$group->userGroup()->associate($this->user->userGroup);
|
||||||
$group->title = $title;
|
$group->title = $title;
|
||||||
$group->save();
|
$group->save();
|
||||||
|
|
||||||
|
@@ -225,6 +225,7 @@ class TransactionJournalFactory
|
|||||||
$journal = TransactionJournal::create(
|
$journal = TransactionJournal::create(
|
||||||
[
|
[
|
||||||
'user_id' => $this->user->id,
|
'user_id' => $this->user->id,
|
||||||
|
'user_group_id' => $this->user->user_group_id,
|
||||||
'transaction_type_id' => $type->id,
|
'transaction_type_id' => $type->id,
|
||||||
'bill_id' => $billId,
|
'bill_id' => $billId,
|
||||||
'transaction_currency_id' => $currency->id,
|
'transaction_currency_id' => $currency->id,
|
||||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace FireflyIII\Helpers\Collector\Extensions;
|
namespace FireflyIII\Helpers\Collector\Extensions;
|
||||||
|
|
||||||
|
use FireflyIII\Models\UserGroup;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
@@ -33,28 +34,29 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
trait CollectorProperties
|
trait CollectorProperties
|
||||||
{
|
{
|
||||||
public const TEST = 'Test';
|
public const TEST = 'Test';
|
||||||
private bool $expandGroupSearch;
|
private bool $expandGroupSearch;
|
||||||
private array $fields;
|
private array $fields;
|
||||||
private bool $hasAccountInfo;
|
private bool $hasAccountInfo;
|
||||||
private bool $hasBillInformation;
|
private bool $hasBillInformation;
|
||||||
private bool $hasBudgetInformation;
|
private bool $hasBudgetInformation;
|
||||||
private bool $hasCatInformation;
|
private bool $hasCatInformation;
|
||||||
private bool $hasJoinedAttTables;
|
private bool $hasJoinedAttTables;
|
||||||
private bool $hasJoinedMetaTables;
|
private bool $hasJoinedMetaTables;
|
||||||
private bool $hasJoinedTagTables;
|
private bool $hasJoinedTagTables;
|
||||||
private bool $hasNotesInformation;
|
private bool $hasNotesInformation;
|
||||||
private array $integerFields;
|
private array $integerFields;
|
||||||
private ?int $limit;
|
private ?int $limit;
|
||||||
private ?int $page;
|
private ?int $page;
|
||||||
private array $postFilters;
|
private array $postFilters;
|
||||||
private HasMany $query;
|
private HasMany $query;
|
||||||
private array $stringFields;
|
private array $stringFields;
|
||||||
/*
|
/*
|
||||||
* This array is used to collect ALL tags the user may search for (using 'setTags').
|
* This array is used to collect ALL tags the user may search for (using 'setTags').
|
||||||
* This way the user can call 'setTags' multiple times and get a joined result.
|
* This way the user can call 'setTags' multiple times and get a joined result.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private array $tags;
|
private array $tags;
|
||||||
private int $total;
|
private int $total;
|
||||||
private ?User $user;
|
private ?User $user;
|
||||||
|
private ?UserGroup $userGroup;
|
||||||
}
|
}
|
||||||
|
@@ -38,6 +38,7 @@ use FireflyIII\Models\TransactionCurrency;
|
|||||||
use FireflyIII\Models\TransactionGroup;
|
use FireflyIII\Models\TransactionGroup;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
use FireflyIII\Models\TransactionType;
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Models\UserGroup;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||||
use Illuminate\Database\Query\JoinClause;
|
use Illuminate\Database\Query\JoinClause;
|
||||||
@@ -67,6 +68,7 @@ class GroupCollector implements GroupCollectorInterface
|
|||||||
$this->postFilters = [];
|
$this->postFilters = [];
|
||||||
$this->tags = [];
|
$this->tags = [];
|
||||||
$this->user = null;
|
$this->user = null;
|
||||||
|
$this->userGroup = null;
|
||||||
$this->limit = null;
|
$this->limit = null;
|
||||||
$this->page = null;
|
$this->page = null;
|
||||||
|
|
||||||
@@ -82,6 +84,7 @@ class GroupCollector implements GroupCollectorInterface
|
|||||||
$this->integerFields = [
|
$this->integerFields = [
|
||||||
'transaction_group_id',
|
'transaction_group_id',
|
||||||
'user_id',
|
'user_id',
|
||||||
|
'user_group_id',
|
||||||
'transaction_journal_id',
|
'transaction_journal_id',
|
||||||
'transaction_type_id',
|
'transaction_type_id',
|
||||||
'order',
|
'order',
|
||||||
@@ -102,6 +105,7 @@ class GroupCollector implements GroupCollectorInterface
|
|||||||
# group
|
# group
|
||||||
'transaction_groups.id as transaction_group_id',
|
'transaction_groups.id as transaction_group_id',
|
||||||
'transaction_groups.user_id as user_id',
|
'transaction_groups.user_id as user_id',
|
||||||
|
'transaction_groups.user_group_id as user_group_id',
|
||||||
'transaction_groups.created_at as created_at',
|
'transaction_groups.created_at as created_at',
|
||||||
'transaction_groups.updated_at as updated_at',
|
'transaction_groups.updated_at as updated_at',
|
||||||
'transaction_groups.title as transaction_group_title',
|
'transaction_groups.title as transaction_group_title',
|
||||||
@@ -300,7 +304,20 @@ class GroupCollector implements GroupCollectorInterface
|
|||||||
*/
|
*/
|
||||||
public function dumpQuery(): void
|
public function dumpQuery(): void
|
||||||
{
|
{
|
||||||
echo $this->query->select($this->fields)->toSql();
|
$query = $this->query->select($this->fields)->toSql();
|
||||||
|
$params = $this->query->getBindings();
|
||||||
|
foreach ($params as $param) {
|
||||||
|
$replace = sprintf('"%s"', $param);
|
||||||
|
if (is_int($param)) {
|
||||||
|
$replace = (string)$param;
|
||||||
|
}
|
||||||
|
$pos = strpos($query, '?');
|
||||||
|
if ($pos !== false) {
|
||||||
|
$query = substr_replace($query, $replace, $pos, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo $query;
|
||||||
|
|
||||||
echo '<pre>';
|
echo '<pre>';
|
||||||
print_r($this->query->getBindings());
|
print_r($this->query->getBindings());
|
||||||
echo '</pre>';
|
echo '</pre>';
|
||||||
@@ -548,6 +565,7 @@ class GroupCollector implements GroupCollectorInterface
|
|||||||
$groupArray = [
|
$groupArray = [
|
||||||
'id' => (int)$augumentedJournal->transaction_group_id,
|
'id' => (int)$augumentedJournal->transaction_group_id,
|
||||||
'user_id' => (int)$augumentedJournal->user_id,
|
'user_id' => (int)$augumentedJournal->user_id,
|
||||||
|
'user_group_id' => (int)$augumentedJournal->user_group_id,
|
||||||
// Field transaction_group_title was added by the query.
|
// Field transaction_group_title was added by the query.
|
||||||
'title' => $augumentedJournal->transaction_group_title, // @phpstan-ignore-line
|
'title' => $augumentedJournal->transaction_group_title, // @phpstan-ignore-line
|
||||||
'transaction_type' => $parsedGroup['transaction_type_type'],
|
'transaction_type' => $parsedGroup['transaction_type_type'],
|
||||||
@@ -1087,6 +1105,64 @@ class GroupCollector implements GroupCollectorInterface
|
|||||||
->orderBy('source.amount', 'DESC');
|
->orderBy('source.amount', 'DESC');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user object and start the query.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
*
|
||||||
|
* @return GroupCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface
|
||||||
|
{
|
||||||
|
if (null === $this->userGroup) {
|
||||||
|
$this->userGroup = $userGroup;
|
||||||
|
$this->startQueryForGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the query.
|
||||||
|
*/
|
||||||
|
private function startQueryForGroup(): void
|
||||||
|
{
|
||||||
|
//app('log')->debug('GroupCollector::startQuery');
|
||||||
|
$this->query = $this->userGroup
|
||||||
|
->transactionJournals()
|
||||||
|
->leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
|
||||||
|
|
||||||
|
// join source transaction.
|
||||||
|
->leftJoin(
|
||||||
|
'transactions as source',
|
||||||
|
function (JoinClause $join) {
|
||||||
|
$join->on('source.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
|
->where('source.amount', '<', 0);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// join destination transaction
|
||||||
|
->leftJoin(
|
||||||
|
'transactions as destination',
|
||||||
|
function (JoinClause $join) {
|
||||||
|
$join->on('destination.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
|
->where('destination.amount', '>', 0);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// left join transaction type.
|
||||||
|
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||||
|
->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id')
|
||||||
|
->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id')
|
||||||
|
->whereNull('transaction_groups.deleted_at')
|
||||||
|
->whereNull('transaction_journals.deleted_at')
|
||||||
|
->whereNull('source.deleted_at')
|
||||||
|
->whereNull('destination.deleted_at')
|
||||||
|
->orderBy('transaction_journals.date', 'DESC')
|
||||||
|
->orderBy('transaction_journals.order', 'ASC')
|
||||||
|
->orderBy('transaction_journals.id', 'DESC')
|
||||||
|
->orderBy('transaction_journals.description', 'DESC')
|
||||||
|
->orderBy('source.amount', 'DESC');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically include all stuff required to make API calls work.
|
* Automatically include all stuff required to make API calls work.
|
||||||
*
|
*
|
||||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Models\Category;
|
|||||||
use FireflyIII\Models\Tag;
|
use FireflyIII\Models\Tag;
|
||||||
use FireflyIII\Models\TransactionCurrency;
|
use FireflyIII\Models\TransactionCurrency;
|
||||||
use FireflyIII\Models\TransactionGroup;
|
use FireflyIII\Models\TransactionGroup;
|
||||||
|
use FireflyIII\Models\UserGroup;
|
||||||
use FireflyIII\User;
|
use FireflyIII\User;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
@@ -1315,6 +1316,15 @@ interface GroupCollectorInterface
|
|||||||
*/
|
*/
|
||||||
public function setUser(User $user): GroupCollectorInterface;
|
public function setUser(User $user): GroupCollectorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user group object and start the query.
|
||||||
|
*
|
||||||
|
* @param UserGroup $userGroup
|
||||||
|
*
|
||||||
|
* @return GroupCollectorInterface
|
||||||
|
*/
|
||||||
|
public function setUserGroup(UserGroup $userGroup): GroupCollectorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only when does not have these tags
|
* Only when does not have these tags
|
||||||
*
|
*
|
||||||
|
@@ -99,7 +99,7 @@ class Budget extends Model
|
|||||||
'encrypted' => 'boolean',
|
'encrypted' => 'boolean',
|
||||||
];
|
];
|
||||||
/** @var array Fields that can be filled */
|
/** @var array Fields that can be filled */
|
||||||
protected $fillable = ['user_id', 'name', 'active', 'order'];
|
protected $fillable = ['user_id', 'name', 'active', 'order', 'user_group_id'];
|
||||||
/** @var array Hidden from view */
|
/** @var array Hidden from view */
|
||||||
protected $hidden = ['encrypted'];
|
protected $hidden = ['encrypted'];
|
||||||
|
|
||||||
|
@@ -130,4 +130,12 @@ class TransactionGroup extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(TransactionJournal::class);
|
return $this->hasMany(TransactionJournal::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function userGroup(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(UserGroup::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -148,6 +148,7 @@ class TransactionJournal extends Model
|
|||||||
protected $fillable
|
protected $fillable
|
||||||
= [
|
= [
|
||||||
'user_id',
|
'user_id',
|
||||||
|
'user_group_id',
|
||||||
'transaction_type_id',
|
'transaction_type_id',
|
||||||
'bill_id',
|
'bill_id',
|
||||||
'tag_count',
|
'tag_count',
|
||||||
|
@@ -67,6 +67,16 @@ class UserGroup extends Model
|
|||||||
return $this->hasMany(Account::class);
|
return $this->hasMany(Account::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to budgets.
|
||||||
|
*
|
||||||
|
* @return HasMany
|
||||||
|
*/
|
||||||
|
public function budgets(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Budget::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @return HasMany
|
* @return HasMany
|
||||||
@@ -75,4 +85,14 @@ class UserGroup extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(GroupMembership::class);
|
return $this->hasMany(GroupMembership::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to transaction journals.
|
||||||
|
*
|
||||||
|
* @return HasMany
|
||||||
|
*/
|
||||||
|
public function transactionJournals(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(TransactionJournal::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,10 +29,14 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepository;
|
|||||||
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepository;
|
use FireflyIII\Repositories\Budget\BudgetRepository;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Administration\Budget\BudgetRepository as AdminBudgetRepository;
|
||||||
|
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface as AdminBudgetRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\NoBudgetRepository;
|
use FireflyIII\Repositories\Budget\NoBudgetRepository;
|
||||||
use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
|
||||||
use FireflyIII\Repositories\Budget\OperationsRepository;
|
use FireflyIII\Repositories\Budget\OperationsRepository;
|
||||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Administration\Budget\OperationsRepository as AdminOperationsRepository;
|
||||||
|
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface as AdminOperationsRepositoryInterface;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
@@ -54,7 +58,6 @@ class BudgetServiceProvider extends ServiceProvider
|
|||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
// reference to auth is not understood by phpstan.
|
// reference to auth is not understood by phpstan.
|
||||||
|
|
||||||
$this->app->bind(
|
$this->app->bind(
|
||||||
BudgetRepositoryInterface::class,
|
BudgetRepositoryInterface::class,
|
||||||
static function (Application $app) {
|
static function (Application $app) {
|
||||||
@@ -68,6 +71,19 @@ class BudgetServiceProvider extends ServiceProvider
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->app->bind(
|
||||||
|
AdminBudgetRepositoryInterface::class,
|
||||||
|
static function (Application $app) {
|
||||||
|
/** @var AdminBudgetRepositoryInterface $repository */
|
||||||
|
$repository = app(AdminBudgetRepository::class);
|
||||||
|
if ($app->auth->check()) { // @phpstan-ignore-line
|
||||||
|
$repository->setUser(auth()->user());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $repository;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// available budget repos
|
// available budget repos
|
||||||
$this->app->bind(
|
$this->app->bind(
|
||||||
AvailableBudgetRepositoryInterface::class,
|
AvailableBudgetRepositoryInterface::class,
|
||||||
@@ -120,6 +136,18 @@ class BudgetServiceProvider extends ServiceProvider
|
|||||||
$repository->setUser(auth()->user());
|
$repository->setUser(auth()->user());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $repository;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$this->app->bind(
|
||||||
|
AdminOperationsRepositoryInterface::class,
|
||||||
|
static function (Application $app) {
|
||||||
|
/** @var AdminOperationsRepositoryInterface $repository */
|
||||||
|
$repository = app(AdminOperationsRepository::class);
|
||||||
|
if ($app->auth->check()) { // @phpstan-ignore-line
|
||||||
|
$repository->setUser(auth()->user());
|
||||||
|
}
|
||||||
|
|
||||||
return $repository;
|
return $repository;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
46
app/Repositories/Administration/Budget/BudgetRepository.php
Normal file
46
app/Repositories/Administration/Budget/BudgetRepository.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/*
|
||||||
|
* BudgetRepository.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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Repositories\Administration\Budget;
|
||||||
|
|
||||||
|
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BudgetRepository
|
||||||
|
*/
|
||||||
|
class BudgetRepository implements BudgetRepositoryInterface
|
||||||
|
{
|
||||||
|
use AdministrationTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getActiveBudgets(): Collection
|
||||||
|
{
|
||||||
|
return $this->userGroup->budgets()->where('active', true)
|
||||||
|
->orderBy('order', 'ASC')
|
||||||
|
->orderBy('name', 'ASC')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/*
|
||||||
|
* BudgetRepositoryInterface.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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Repositories\Administration\Budget;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface BudgetRepositoryInterface
|
||||||
|
*/
|
||||||
|
interface BudgetRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getActiveBudgets(): Collection;
|
||||||
|
}
|
136
app/Repositories/Administration/Budget/OperationsRepository.php
Normal file
136
app/Repositories/Administration/Budget/OperationsRepository.php
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/*
|
||||||
|
* OperationsRepository.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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Repositories\Administration\Budget;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||||
|
use FireflyIII\Models\TransactionType;
|
||||||
|
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class OperationsRepository
|
||||||
|
*/
|
||||||
|
class OperationsRepository implements OperationsRepositoryInterface
|
||||||
|
{
|
||||||
|
use AdministrationTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array
|
||||||
|
{
|
||||||
|
/** @var GroupCollectorInterface $collector */
|
||||||
|
$collector = app(GroupCollectorInterface::class);
|
||||||
|
$collector->setUserGroup($this->userGroup)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
|
||||||
|
if (null !== $accounts && $accounts->count() > 0) {
|
||||||
|
$collector->setAccounts($accounts);
|
||||||
|
}
|
||||||
|
if (null !== $budgets && $budgets->count() > 0) {
|
||||||
|
$collector->setBudgets($budgets);
|
||||||
|
}
|
||||||
|
if (null === $budgets || (0 === $budgets->count())) {
|
||||||
|
$collector->setBudgets($this->getBudgets());
|
||||||
|
}
|
||||||
|
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
|
||||||
|
$journals = $collector->getExtractedJournals();
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
foreach ($journals as $journal) {
|
||||||
|
$currencyId = (int)$journal['currency_id'];
|
||||||
|
$budgetId = (int)$journal['budget_id'];
|
||||||
|
$budgetName = (string)$journal['budget_name'];
|
||||||
|
|
||||||
|
// catch "no budget" entries.
|
||||||
|
if (0 === $budgetId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// info about the currency:
|
||||||
|
$array[$currencyId] = $array[$currencyId] ?? [
|
||||||
|
'budgets' => [],
|
||||||
|
'currency_id' => $currencyId,
|
||||||
|
'currency_name' => $journal['currency_name'],
|
||||||
|
'currency_symbol' => $journal['currency_symbol'],
|
||||||
|
'currency_code' => $journal['currency_code'],
|
||||||
|
'currency_decimal_places' => $journal['currency_decimal_places'],
|
||||||
|
];
|
||||||
|
|
||||||
|
// info about the budgets:
|
||||||
|
$array[$currencyId]['budgets'][$budgetId] = $array[$currencyId]['budgets'][$budgetId] ?? [
|
||||||
|
'id' => $budgetId,
|
||||||
|
'name' => $budgetName,
|
||||||
|
'transaction_journals' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
// add journal to array:
|
||||||
|
// only a subset of the fields.
|
||||||
|
$journalId = (int)$journal['transaction_journal_id'];
|
||||||
|
$final = [
|
||||||
|
'amount' => app('steam')->negative($journal['amount']),
|
||||||
|
'foreign_amount' => null,
|
||||||
|
'foreign_currency_id' => null,
|
||||||
|
'foreign_currency_code' => null,
|
||||||
|
'foreign_currency_symbol' => null,
|
||||||
|
'foreign_currency_name' => null,
|
||||||
|
'foreign_currency_decimal_places' => null,
|
||||||
|
'destination_account_id' => $journal['destination_account_id'],
|
||||||
|
'destination_account_name' => $journal['destination_account_name'],
|
||||||
|
'source_account_id' => $journal['source_account_id'],
|
||||||
|
'source_account_name' => $journal['source_account_name'],
|
||||||
|
'category_name' => $journal['category_name'],
|
||||||
|
'description' => $journal['description'],
|
||||||
|
'transaction_group_id' => $journal['transaction_group_id'],
|
||||||
|
'date' => $journal['date'],
|
||||||
|
];
|
||||||
|
if (null !== $journal['foreign_amount']) {
|
||||||
|
$final['foreign_amount'] = app('steam')->negative($journal['foreign_amount']);
|
||||||
|
$final['foreign_currency_id'] = $journal['foreign_currency_id'];
|
||||||
|
$final['foreign_currency_code'] = $journal['foreign_currency_code'];
|
||||||
|
$final['foreign_currency_symbol'] = $journal['foreign_currency_symbol'];
|
||||||
|
$final['foreign_currency_name'] = $journal['foreign_currency_name'];
|
||||||
|
$final['foreign_currency_decimal_places'] = $journal['foreign_currency_decimal_places'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = $final;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
private function getBudgets(): Collection
|
||||||
|
{
|
||||||
|
/** @var BudgetRepositoryInterface $repos */
|
||||||
|
$repos = app(BudgetRepositoryInterface::class);
|
||||||
|
$repos->setAdministrationId($this->getAdministrationId());
|
||||||
|
|
||||||
|
return $repos->getActiveBudgets();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
/*
|
||||||
|
* OperationsRepositoryInterface.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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Repositories\Administration\Budget;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface OperationsRepositoryInterface
|
||||||
|
*/
|
||||||
|
interface OperationsRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This method returns a list of all the withdrawal transaction journals (as arrays) set in that period
|
||||||
|
* which have the specified budget set to them. It's grouped per currency, with as few details in the array
|
||||||
|
* as possible. Amounts are always negative.
|
||||||
|
*
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
* @param Collection|null $accounts
|
||||||
|
* @param Collection|null $budgets
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array;
|
||||||
|
}
|
@@ -798,10 +798,11 @@ class BudgetRepository implements BudgetRepositoryInterface
|
|||||||
try {
|
try {
|
||||||
$newBudget = Budget::create(
|
$newBudget = Budget::create(
|
||||||
[
|
[
|
||||||
'user_id' => $this->user->id,
|
'user_id' => $this->user->id,
|
||||||
'name' => $data['name'],
|
'user_group_id' => $this->user->user_group_id,
|
||||||
'order' => $order + 1,
|
'name' => $data['name'],
|
||||||
'active' => array_key_exists('active', $data) ? $data['active'] : true,
|
'order' => $order + 1,
|
||||||
|
'active' => array_key_exists('active', $data) ? $data['active'] : true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} catch (QueryException $e) {
|
} catch (QueryException $e) {
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
/*
|
/*
|
||||||
* CleansChartData.php
|
* CleansChartData.php
|
||||||
* Copyright (c) 2023 james@firefly-iii.org
|
* Copyright (c) 2023 james@firefly-iii.org
|
||||||
|
@@ -57,5 +57,4 @@ class ExchangeRateConverter
|
|||||||
return '0' === $rate ? '1' : $rate;
|
return '0' === $rate ? '1' : $rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -89,10 +89,10 @@ class Navigation
|
|||||||
|
|
||||||
if (!array_key_exists($repeatFreq, $functionMap)) {
|
if (!array_key_exists($repeatFreq, $functionMap)) {
|
||||||
Log::error(sprintf(
|
Log::error(sprintf(
|
||||||
'The periodicity %s is unknown. Choose one of available periodicity: %s',
|
'The periodicity %s is unknown. Choose one of available periodicity: %s',
|
||||||
$repeatFreq,
|
$repeatFreq,
|
||||||
join(', ', array_keys($functionMap))
|
join(', ', array_keys($functionMap))
|
||||||
));
|
));
|
||||||
return $theDate;
|
return $theDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -88,6 +88,7 @@ Route::group(
|
|||||||
],
|
],
|
||||||
static function () {
|
static function () {
|
||||||
Route::get('account/dashboard', ['uses' => 'AccountController@dashboard', 'as' => 'account.dashboard']);
|
Route::get('account/dashboard', ['uses' => 'AccountController@dashboard', 'as' => 'account.dashboard']);
|
||||||
|
Route::get('budget/dashboard', ['uses' => 'BudgetController@dashboard', 'as' => 'budget.dashboard']);
|
||||||
Route::get('balance/balance', ['uses' => 'BalanceController@balance', 'as' => 'balance.balance']);
|
Route::get('balance/balance', ['uses' => 'BalanceController@balance', 'as' => 'balance.balance']);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user