From e3a71b64f85f38037c13a8d5c9c9436c954655f0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 18 Feb 2023 18:48:55 +0100 Subject: [PATCH] Update budget box and rebuild frontend. --- .../Model/Budget/ShowController.php | 85 +++++++++++ app/Repositories/Budget/BudgetRepository.php | 119 ++++++++++++++- .../Budget/BudgetRepositoryInterface.php | 20 +++ frontend/package.json | 4 +- frontend/src/api/v2/budgets/get.js | 32 ++++ .../src/components/dashboard/BudgetBox.vue | 140 ++++++++++++++++-- frontend/yarn.lock | 16 +- ...endor.4da34a2f.css => vendor.973c97fc.css} | 10 +- ...50a5c89.ttf => fa-brands-400.13e40630.ttf} | Bin 186124 -> 187448 bytes public/v3/fonts/fa-brands-400.3ac6859b.woff2 | Bin 107656 -> 0 bytes public/v3/fonts/fa-brands-400.7be2266f.woff2 | Bin 0 -> 108000 bytes public/v3/fonts/fa-regular-400.14640490.ttf | Bin 0 -> 63728 bytes public/v3/fonts/fa-regular-400.1f377217.woff2 | Bin 25236 -> 0 bytes public/v3/fonts/fa-regular-400.3cd9fb8f.ttf | Bin 62320 -> 0 bytes public/v3/fonts/fa-regular-400.8bedd7cf.woff2 | Bin 0 -> 24840 bytes public/v3/fonts/fa-solid-900.2877d54f.ttf | Bin 0 -> 394832 bytes public/v3/fonts/fa-solid-900.41c0f706.woff2 | Bin 150516 -> 0 bytes public/v3/fonts/fa-solid-900.b1a653db.ttf | Bin 397420 -> 0 bytes public/v3/fonts/fa-solid-900.bdb9e232.woff2 | Bin 0 -> 149908 bytes .../v3/fonts/fa-v4compatibility.6c0a7b77.ttf | Bin 0 -> 10172 bytes .../v3/fonts/fa-v4compatibility.e314b618.ttf | Bin 10140 -> 0 bytes public/v3/index.html | 2 +- .../js/{159.f93b425d.js => 159.849b2e88.js} | 2 +- .../js/{2306.12ac1e37.js => 2306.accc86fe.js} | 2 +- public/v3/js/3922.0d52278f.js | 1 + .../js/{4036.7d57aee0.js => 4036.46dc453b.js} | 2 +- .../js/{4647.0380c870.js => 4647.eb08255c.js} | 2 +- .../js/{5114.dd3d8109.js => 5114.96732a35.js} | 2 +- .../js/{5529.3951591b.js => 5529.dbcd5e10.js} | 2 +- .../js/{6254.b1ed2b7f.js => 6254.16279dd8.js} | 2 +- public/v3/js/7886.1ed9f731.js | 1 - .../js/{9053.f687ee0d.js => 9053.8c7cb7c1.js} | 2 +- .../js/{9158.f0573e6c.js => 9158.887ce7fc.js} | 2 +- .../js/{9173.e7f704e9.js => 9173.44a0bd7d.js} | 2 +- .../js/{9729.cfa2a9c7.js => 9729.a64217d1.js} | 2 +- .../js/{app.89886383.js => app.8c79411c.js} | 2 +- ...n.a335a96a.js => chunk-common.2a30b5d5.js} | 2 +- ...{vendor.57fa50ec.js => vendor.aba07782.js} | 2 +- routes/api.php | 5 +- routes/breadcrumbs.php | 72 ++++----- 40 files changed, 451 insertions(+), 84 deletions(-) create mode 100644 app/Api/V2/Controllers/Model/Budget/ShowController.php create mode 100644 frontend/src/api/v2/budgets/get.js rename public/v3/css/{vendor.4da34a2f.css => vendor.973c97fc.css} (50%) rename public/v3/fonts/{fa-brands-400.450a5c89.ttf => fa-brands-400.13e40630.ttf} (76%) delete mode 100644 public/v3/fonts/fa-brands-400.3ac6859b.woff2 create mode 100644 public/v3/fonts/fa-brands-400.7be2266f.woff2 create mode 100644 public/v3/fonts/fa-regular-400.14640490.ttf delete mode 100644 public/v3/fonts/fa-regular-400.1f377217.woff2 delete mode 100644 public/v3/fonts/fa-regular-400.3cd9fb8f.ttf create mode 100644 public/v3/fonts/fa-regular-400.8bedd7cf.woff2 create mode 100644 public/v3/fonts/fa-solid-900.2877d54f.ttf delete mode 100644 public/v3/fonts/fa-solid-900.41c0f706.woff2 delete mode 100644 public/v3/fonts/fa-solid-900.b1a653db.ttf create mode 100644 public/v3/fonts/fa-solid-900.bdb9e232.woff2 create mode 100644 public/v3/fonts/fa-v4compatibility.6c0a7b77.ttf delete mode 100644 public/v3/fonts/fa-v4compatibility.e314b618.ttf rename public/v3/js/{159.f93b425d.js => 159.849b2e88.js} (98%) rename public/v3/js/{2306.12ac1e37.js => 2306.accc86fe.js} (98%) create mode 100644 public/v3/js/3922.0d52278f.js rename public/v3/js/{4036.7d57aee0.js => 4036.46dc453b.js} (98%) rename public/v3/js/{4647.0380c870.js => 4647.eb08255c.js} (98%) rename public/v3/js/{5114.dd3d8109.js => 5114.96732a35.js} (98%) rename public/v3/js/{5529.3951591b.js => 5529.dbcd5e10.js} (98%) rename public/v3/js/{6254.b1ed2b7f.js => 6254.16279dd8.js} (98%) delete mode 100644 public/v3/js/7886.1ed9f731.js rename public/v3/js/{9053.f687ee0d.js => 9053.8c7cb7c1.js} (97%) rename public/v3/js/{9158.f0573e6c.js => 9158.887ce7fc.js} (98%) rename public/v3/js/{9173.e7f704e9.js => 9173.44a0bd7d.js} (99%) rename public/v3/js/{9729.cfa2a9c7.js => 9729.a64217d1.js} (98%) rename public/v3/js/{app.89886383.js => app.8c79411c.js} (97%) rename public/v3/js/{chunk-common.a335a96a.js => chunk-common.2a30b5d5.js} (99%) rename public/v3/js/{vendor.57fa50ec.js => vendor.aba07782.js} (73%) diff --git a/app/Api/V2/Controllers/Model/Budget/ShowController.php b/app/Api/V2/Controllers/Model/Budget/ShowController.php new file mode 100644 index 0000000000..1cdafb8e6c --- /dev/null +++ b/app/Api/V2/Controllers/Model/Budget/ShowController.php @@ -0,0 +1,85 @@ +. + */ + +namespace FireflyIII\Api\V2\Controllers\Model\Budget; + +use FireflyIII\Api\V2\Controllers\Controller; +use FireflyIII\Api\V2\Request\Generic\DateRequest; +use FireflyIII\Models\Budget; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Support\Http\Api\ConvertsExchangeRates; +use Illuminate\Http\JsonResponse; + +/** + * Class ShowController + */ +class ShowController extends Controller +{ + use ConvertsExchangeRates; + + private BudgetRepositoryInterface $repository; + + /** + * + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(BudgetRepositoryInterface::class); + return $next($request); + } + ); + } + + /** + * This endpoint is documented at: + * TODO add URL + * + * @param DateRequest $request + * @return JsonResponse + */ + public function budgeted(DateRequest $request, Budget $budget): JsonResponse + { + $data = $request->getAll(); + $result = $this->repository->budgetedInPeriodForBudget($budget, $data['start'], $data['end']); + $converted = $this->cerSum(array_values($result)); + + return response()->json($converted); + } + + /** + * This endpoint is documented at: + * TODO add URL + * + * @param DateRequest $request + * @return JsonResponse + */ + public function spent(DateRequest $request, Budget $budget): JsonResponse + { + $data = $request->getAll(); + $result = $this->repository->spentInPeriodForBudget($budget, $data['start'], $data['end']); + $converted = $this->cerSum(array_values($result)); + + return response()->json($converted); + } +} diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index cef6d344a5..4b62b0737a 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -25,7 +25,6 @@ namespace FireflyIII\Repositories\Budget; use Carbon\Carbon; use DB; -use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; @@ -752,4 +751,122 @@ class BudgetRepository implements BudgetRepositoryInterface { return (int)$this->user->budgets()->max('order'); } + + /** + * @inheritDoc + */ + public function spentInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array + { + Log::debug(sprintf('Now in %s', __METHOD__)); + $start->startOfDay(); + $end->endOfDay(); + + // exclude specific liabilities + $repository = app(AccountRepositoryInterface::class); + $repository->setUser($this->user); + $subset = $repository->getAccountsByType(config('firefly.valid_liabilities')); + $selection = new Collection(); + /** @var Account $account */ + foreach ($subset as $account) { + if ('credit' === $repository->getMetaValue($account, 'liability_direction')) { + $selection->push($account); + } + } + + // start collecting: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user) + ->setRange($start, $end) + ->excludeDestinationAccounts($selection) + ->setTypes([TransactionType::WITHDRAWAL]) + ->setBudget($budget); + + $journals = $collector->getExtractedJournals(); + $array = []; + + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; + $array[$currencyId] = $array[$currencyId] ?? [ + 'id' => (string)$currencyId, + 'name' => $journal['currency_name'], + 'symbol' => $journal['currency_symbol'], + 'code' => $journal['currency_code'], + 'decimal_places' => $journal['currency_decimal_places'], + 'sum' => '0', + ]; + $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount'])); + + // also do foreign amount: + $foreignId = (int)$journal['foreign_currency_id']; + if (0 !== $foreignId) { + $array[$foreignId] = $array[$foreignId] ?? [ + 'id' => (string)$foreignId, + 'name' => $journal['foreign_currency_name'], + 'symbol' => $journal['foreign_currency_symbol'], + 'code' => $journal['foreign_currency_code'], + 'decimal_places' => $journal['foreign_currency_decimal_places'], + 'sum' => '0', + ]; + $array[$foreignId]['sum'] = bcadd($array[$foreignId]['sum'], app('steam')->negative($journal['foreign_amount'])); + } + } + + return $array; + } + + /** + * @inheritDoc + */ + public function budgetedInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array + { + Log::debug(sprintf('Now in budgetedInPeriod(#%d, "%s", "%s")', $budget->id, $start->format('Y-m-d'), $end->format('Y-m-d'))); + $return = []; + /** @var BudgetLimitRepository $limitRepository */ + $limitRepository = app(BudgetLimitRepository::class); + $limitRepository->setUser($this->user); + + Log::debug(sprintf('Budget #%d: "%s"', $budget->id, $budget->name)); + $limits = $limitRepository->getBudgetLimits($budget, $start, $end); + /** @var BudgetLimit $limit */ + foreach ($limits as $limit) { + Log::debug(sprintf('Budget limit #%d', $limit->id)); + $currency = $limit->transactionCurrency; + $return[$currency->id] = $return[$currency->id] ?? [ + 'id' => (string)$currency->id, + 'name' => $currency->name, + 'symbol' => $currency->symbol, + 'code' => $currency->code, + 'decimal_places' => $currency->decimal_places, + 'sum' => '0', + ]; + // same period + if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)) { + $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string)$limit->amount); + Log::debug(sprintf('Add full amount [1]: %s', $limit->amount)); + continue; + } + // limit is inside of date range + if ($start->lte($limit->start_date) && $end->gte($limit->end_date)) { + $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string)$limit->amount); + Log::debug(sprintf('Add full amount [2]: %s', $limit->amount)); + continue; + } + $total = $limit->start_date->diffInDays($limit->end_date) + 1; // include the day itself. + $days = $this->daysInOverlap($limit, $start, $end); + $amount = bcmul(bcdiv((string)$limit->amount, (string)$total), (string)$days); + $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $amount); + Log::debug( + sprintf( + 'Amount per day: %s (%s over %d days). Total amount for %d days: %s', + bcdiv((string)$limit->amount, (string)$total), + $limit->amount, + $total, + $days, + $amount + ) + ); + } + return $return; + } } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 9441b3b76a..ab86074dc6 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -60,6 +60,16 @@ interface BudgetRepositoryInterface */ public function budgetedInPeriod(Carbon $start, Carbon $end): array; + /** + * Returns the amount that is budgeted in a period. + * + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * @return array + */ + public function budgetedInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array; + /** * @return bool */ @@ -195,6 +205,16 @@ interface BudgetRepositoryInterface */ public function spentInPeriod(Carbon $start, Carbon $end): array; + /** + * Used in the v2 API to calculate the amount of money spent in a single budget.. + * + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + public function spentInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array; + /** * @param array $data * diff --git a/frontend/package.json b/frontend/package.json index 800dd97e42..4057475e29 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,14 +11,14 @@ }, "dependencies": { "@popperjs/core": "^2.11.2", - "@quasar/extras": "^1.15.10", + "@quasar/extras": "^1.15.11", "apexcharts": "^3.32.1", "axios": "^0.21.1", "axios-cache-adapter": "^2.7.3", "core-js": "^3.6.5", "date-fns": "^2.28.0", "pinia": "^2.0.14", - "quasar": "^2.11.5", + "quasar": "^2.11.6", "vue": "3", "vue-i18n": "^9.0.0", "vue-router": "^4.0.0", diff --git a/frontend/src/api/v2/budgets/get.js b/frontend/src/api/v2/budgets/get.js new file mode 100644 index 0000000000..2f5d1c2aed --- /dev/null +++ b/frontend/src/api/v2/budgets/get.js @@ -0,0 +1,32 @@ +/* + * list.js + * Copyright (c) 2022 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 . + */ + +import {api} from "boot/axios"; +import {format} from "date-fns"; + +export default class Get { + spent(identifier, start, end) { + let url = '/api/v2/budgets/' + identifier + '/spent'; + let startStr = format(start, 'y-MM-dd'); + let endStr = format(end, 'y-MM-dd'); + return api.get(url, {params: {start: startStr, end: endStr}}); + } + +} diff --git a/frontend/src/components/dashboard/BudgetBox.vue b/frontend/src/components/dashboard/BudgetBox.vue index 9be3af1705..beae4361c0 100644 --- a/frontend/src/components/dashboard/BudgetBox.vue +++ b/frontend/src/components/dashboard/BudgetBox.vue @@ -18,6 +18,11 @@ - along with this program. If not, see . --> + +