mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-29 22:47:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			331 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * IndexController.php
 | |
|  * Copyright (c) 2019 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\Http\Controllers\Budget;
 | |
| 
 | |
| use Carbon\Carbon;
 | |
| use FireflyIII\Exceptions\FireflyException;
 | |
| use FireflyIII\Http\Controllers\Controller;
 | |
| use FireflyIII\Models\AvailableBudget;
 | |
| use FireflyIII\Models\Budget;
 | |
| use FireflyIII\Models\BudgetLimit;
 | |
| use FireflyIII\Models\TransactionCurrency;
 | |
| use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
 | |
| use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
 | |
| use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
 | |
| use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
 | |
| use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
 | |
| use FireflyIII\Support\Http\Api\ExchangeRateConverter;
 | |
| use FireflyIII\Support\Http\Controllers\DateCalculation;
 | |
| use Illuminate\Contracts\View\Factory;
 | |
| use Illuminate\Http\JsonResponse;
 | |
| use Illuminate\Http\Request;
 | |
| use Illuminate\Support\Collection;
 | |
| use Illuminate\Support\Facades\Log;
 | |
| use Illuminate\View\View;
 | |
| 
 | |
| /**
 | |
|  * Class IndexController
 | |
|  */
 | |
| class IndexController extends Controller
 | |
| {
 | |
|     use DateCalculation;
 | |
| 
 | |
|     private AvailableBudgetRepositoryInterface $abRepository;
 | |
|     private BudgetLimitRepositoryInterface     $blRepository;
 | |
|     private CurrencyRepositoryInterface        $currencyRepository;
 | |
|     private OperationsRepositoryInterface      $opsRepository;
 | |
|     private BudgetRepositoryInterface          $repository;
 | |
| 
 | |
|     /**
 | |
|      * IndexController constructor.
 | |
|      */
 | |
|     public function __construct()
 | |
|     {
 | |
|         parent::__construct();
 | |
| 
 | |
|         $this->middleware(
 | |
|             function ($request, $next) {
 | |
|                 app('view')->share('title', (string) trans('firefly.budgets'));
 | |
|                 app('view')->share('mainTitleIcon', 'fa-pie-chart');
 | |
|                 $this->repository         = app(BudgetRepositoryInterface::class);
 | |
|                 $this->opsRepository      = app(OperationsRepositoryInterface::class);
 | |
|                 $this->abRepository       = app(AvailableBudgetRepositoryInterface::class);
 | |
|                 $this->currencyRepository = app(CurrencyRepositoryInterface::class);
 | |
|                 $this->blRepository       = app(BudgetLimitRepositoryInterface::class);
 | |
|                 $this->repository->cleanupBudgets();
 | |
| 
 | |
|                 return $next($request);
 | |
|             }
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Show all budgets.
 | |
|      *
 | |
|      * @return Factory|View
 | |
|      *
 | |
|      * @throws FireflyException
 | |
|      *                                              */
 | |
|     public function index(?Carbon $start = null, ?Carbon $end = null)
 | |
|     {
 | |
|         $this->abRepository->cleanup();
 | |
|         Log::debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d')));
 | |
| 
 | |
|         // collect some basic vars:
 | |
|         $range            = app('navigation')->getViewRange(true);
 | |
|         $isCustomRange    = session('is_custom_range', false);
 | |
|         if (false === $isCustomRange) {
 | |
|             $start ??= session('start', today(config('app.timezone'))->startOfMonth());
 | |
|             $end   ??= app('navigation')->endOfPeriod($start, $range);
 | |
|         }
 | |
| 
 | |
|         // overrule start and end if necessary:
 | |
|         if (true === $isCustomRange) {
 | |
|             $start ??= session('start', today(config('app.timezone'))->startOfMonth());
 | |
|             $end   ??= session('end', today(config('app.timezone'))->endOfMonth());
 | |
|         }
 | |
| 
 | |
|         $currencies       = $this->currencyRepository->get();
 | |
|         $budgeted         = '0';
 | |
|         $spent            = '0';
 | |
| 
 | |
|         // new period stuff:
 | |
|         $periodTitle      = app('navigation')->periodShow($start, $range);
 | |
|         $prevLoop         = $this->getPreviousPeriods($start, $range);
 | |
|         $nextLoop         = $this->getNextPeriods($start, $range);
 | |
| 
 | |
|         // get all available budgets:
 | |
|         $availableBudgets = $this->getAllAvailableBudgets($start, $end);
 | |
|         // get all active budgets:
 | |
|         $budgets          = $this->getAllBudgets($start, $end, $currencies, $this->primaryCurrency);
 | |
|         $sums             = $this->getSums($budgets);
 | |
| 
 | |
|         // get budgeted for default currency:
 | |
|         if (0 === count($availableBudgets)) {
 | |
|             $budgeted = $this->blRepository->budgeted($start, $end, $this->primaryCurrency);
 | |
|             $spentArr = $this->opsRepository->sumExpenses($start, $end, null, null, $this->primaryCurrency);
 | |
|             $spent    = $spentArr[$this->primaryCurrency->id]['sum'] ?? '0';
 | |
|             unset($spentArr);
 | |
|         }
 | |
|         // number of days for consistent budgeting.
 | |
|         $activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description.
 | |
|         $activeDaysLeft   = $this->activeDaysLeft($start, $end);   // see method description.
 | |
| 
 | |
|         // get all inactive budgets, and simply list them:
 | |
|         $inactive         = $this->repository->getInactiveBudgets();
 | |
| 
 | |
|         return view(
 | |
|             'budgets.index',
 | |
|             compact(
 | |
|                 'availableBudgets',
 | |
|                 'budgeted',
 | |
|                 'spent',
 | |
|                 'prevLoop',
 | |
|                 'nextLoop',
 | |
|                 'budgets',
 | |
|                 'currencies',
 | |
|                 'periodTitle',
 | |
|                 'activeDaysPassed',
 | |
|                 'activeDaysLeft',
 | |
|                 'inactive',
 | |
|                 'budgets',
 | |
|                 'start',
 | |
|                 'end',
 | |
|                 'sums'
 | |
|             )
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     private function getAllAvailableBudgets(Carbon $start, Carbon $end): array
 | |
|     {
 | |
|         Log::debug(sprintf('Start of getAllAvailableBudgets("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
 | |
|         $converter        = new ExchangeRateConverter();
 | |
|         // get all available budgets.
 | |
|         $ab               = $this->abRepository->get($start, $end);
 | |
|         $availableBudgets = [];
 | |
| 
 | |
|         // for each, complement with spent amount:
 | |
|         /** @var AvailableBudget $entry */
 | |
|         foreach ($ab as $entry) {
 | |
|             $array                = $entry->toArray();
 | |
|             $array['start_date']  = $entry->start_date;
 | |
|             $array['end_date']    = $entry->end_date;
 | |
| 
 | |
|             // spent in period:
 | |
|             $spentArr             = $this->opsRepository->sumExpenses($entry->start_date, $entry->end_date, null, null, $entry->transactionCurrency);
 | |
|             $array['spent']       = $spentArr[$entry->transaction_currency_id]['sum'] ?? '0';
 | |
|             $array['pc_spent']    = $this->convertToPrimary && $entry->transaction_currency_id !== $this->primaryCurrency->id ? $converter->convert($entry->transactionCurrency, $this->primaryCurrency, $entry->start_date, $array['spent']) : null;
 | |
|             // budgeted in period:
 | |
|             $budgeted             = $this->blRepository->budgeted($entry->start_date, $entry->end_date, $entry->transactionCurrency);
 | |
|             $array['budgeted']    = $budgeted;
 | |
|             $array['pc_budgeted'] = $this->convertToPrimary && $entry->transaction_currency_id !== $this->primaryCurrency->id ? $converter->convert($entry->transactionCurrency, $this->primaryCurrency, $entry->start_date, $budgeted) : null;
 | |
|             // this time, because of complex sums, use the currency converter.
 | |
| 
 | |
| 
 | |
|             $availableBudgets[]   = $array;
 | |
|             unset($spentArr);
 | |
|         }
 | |
| 
 | |
|         return $availableBudgets;
 | |
|     }
 | |
| 
 | |
|     private function getAllBudgets(Carbon $start, Carbon $end, Collection $currencies, TransactionCurrency $primaryCurrency): array
 | |
|     {
 | |
|         // get all budgets, and paginate them into $budgets.
 | |
|         $collection = $this->repository->getActiveBudgets();
 | |
|         $budgets    = [];
 | |
|         Log::debug(sprintf('(getAllBudgets) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
 | |
| 
 | |
|         // complement budget with budget limits in range, and expenses in currency X in range.
 | |
|         /** @var Budget $current */
 | |
|         foreach ($collection as $current) {
 | |
|             Log::debug(sprintf('Working on budget #%d ("%s")', $current->id, $current->name));
 | |
|             $array                = $current->toArray();
 | |
|             $array['spent']       = [];
 | |
|             $array['spent_total'] = [];
 | |
|             $array['budgeted']    = [];
 | |
|             $array['attachments'] = $this->repository->getAttachments($current);
 | |
|             $array['auto_budget'] = $this->repository->getAutoBudget($current);
 | |
|             $budgetLimits         = $this->blRepository->getBudgetLimits($current, $start, $end);
 | |
| 
 | |
|             /** @var BudgetLimit $limit */
 | |
|             foreach ($budgetLimits as $limit) {
 | |
|                 Log::debug(sprintf('Working on budget limit #%d', $limit->id));
 | |
|                 $currency            = $limit->transactionCurrency ?? $primaryCurrency;
 | |
|                 $amount              = app('steam')->bcround($limit->amount, $currency->decimal_places);
 | |
|                 $array['budgeted'][] = [
 | |
|                     'id'                      => $limit->id,
 | |
|                     'amount'                  => $amount,
 | |
|                     'notes'                   => $this->blRepository->getNoteText($limit),
 | |
|                     'start_date'              => $limit->start_date->isoFormat($this->monthAndDayFormat),
 | |
|                     'end_date'                => $limit->end_date->isoFormat($this->monthAndDayFormat),
 | |
|                     'in_range'                => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),
 | |
|                     'total_days'              => $limit->start_date->diffInDays($limit->end_date) + 1,
 | |
|                     'currency_id'             => $currency->id,
 | |
|                     'currency_symbol'         => $currency->symbol,
 | |
|                     'currency_name'           => $currency->name,
 | |
|                     'currency_decimal_places' => $currency->decimal_places,
 | |
|                 ];
 | |
|                 Log::debug(sprintf('The amount budgeted for budget limit #%d is %s %s', $limit->id, $currency->code, $amount));
 | |
|             }
 | |
| 
 | |
|             // #10463
 | |
| 
 | |
|             /** @var TransactionCurrency $currency */
 | |
|             foreach ($currencies as $currency) {
 | |
|                 $spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection()->push($current), $currency);
 | |
|                 if (array_key_exists($currency->id, $spentArr) && array_key_exists('sum', $spentArr[$currency->id])) {
 | |
|                     $array['spent'][$currency->id]['spent']                   = $spentArr[$currency->id]['sum'];
 | |
|                     $array['spent'][$currency->id]['currency_id']             = $currency->id;
 | |
|                     $array['spent'][$currency->id]['currency_symbol']         = $currency->symbol;
 | |
|                     $array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places;
 | |
|                 }
 | |
|             }
 | |
|             $budgets[]            = $array;
 | |
|         }
 | |
| 
 | |
|         return $budgets;
 | |
|     }
 | |
| 
 | |
|     private function getSums(array $budgets): array
 | |
|     {
 | |
|         $sums = [
 | |
|             'budgeted' => [],
 | |
|             'spent'    => [],
 | |
|             'left'     => [],
 | |
|         ];
 | |
| 
 | |
|         /** @var array $budget */
 | |
|         foreach ($budgets as $budget) {
 | |
|             Log::debug(sprintf('Now working on budget #%d ("%s")', $budget['id'], $budget['name']));
 | |
| 
 | |
|             /** @var array $spent */
 | |
|             foreach ($budget['spent'] as $spent) {
 | |
|                 $currencyId                           = $spent['currency_id'];
 | |
|                 $sums['spent'][$currencyId]
 | |
|                                                       ??= [
 | |
|                                                           'amount'                  => '0',
 | |
|                                                           'currency_id'             => $spent['currency_id'],
 | |
|                                                           'currency_symbol'         => $spent['currency_symbol'],
 | |
|                                                           'currency_decimal_places' => $spent['currency_decimal_places'],
 | |
|                                                       ];
 | |
|                 $sums['spent'][$currencyId]['amount'] = bcadd($sums['spent'][$currencyId]['amount'], (string) $spent['spent']);
 | |
|             }
 | |
| 
 | |
|             /** @var array $budgeted */
 | |
|             foreach ($budget['budgeted'] as $budgeted) {
 | |
|                 $currencyId                              = $budgeted['currency_id'];
 | |
|                 $sums['budgeted'][$currencyId]
 | |
|                                                          ??= [
 | |
|                                                              'amount'                  => '0',
 | |
|                                                              'currency_id'             => $budgeted['currency_id'],
 | |
|                                                              'currency_symbol'         => $budgeted['currency_symbol'],
 | |
|                                                              'currency_decimal_places' => $budgeted['currency_decimal_places'],
 | |
|                                                          ];
 | |
|                 $sums['budgeted'][$currencyId]['amount'] = bcadd($sums['budgeted'][$currencyId]['amount'], (string) $budgeted['amount']);
 | |
| 
 | |
|                 // also calculate how much left from budgeted:
 | |
|                 $sums['left'][$currencyId]
 | |
|                                                          ??= [
 | |
|                                                              'amount'                  => '0',
 | |
|                                                              'currency_id'             => $budgeted['currency_id'],
 | |
|                                                              'currency_symbol'         => $budgeted['currency_symbol'],
 | |
|                                                              'currency_decimal_places' => $budgeted['currency_decimal_places'],
 | |
|                                                          ];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // final calculation for 'left':
 | |
|         /**
 | |
|          * @var int $currencyId
 | |
|          */
 | |
|         foreach (array_keys($sums['budgeted']) as $currencyId) {
 | |
|             $spent                               = $sums['spent'][$currencyId]['amount'] ?? '0';
 | |
|             $budgeted                            = $sums['budgeted'][$currencyId]['amount'] ?? '0';
 | |
|             $sums['left'][$currencyId]['amount'] = bcadd($spent, $budgeted);
 | |
|         }
 | |
| 
 | |
|         return $sums;
 | |
|     }
 | |
| 
 | |
|     public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse
 | |
|     {
 | |
|         $this->abRepository->cleanup();
 | |
|         $budgetIds = $request->get('budgetIds');
 | |
| 
 | |
|         foreach ($budgetIds as $index => $budgetId) {
 | |
|             $budgetId = (int) $budgetId;
 | |
|             $budget   = $repository->find($budgetId);
 | |
|             if ($budget instanceof Budget) {
 | |
|                 app('log')->debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1));
 | |
|                 $repository->setBudgetOrder($budget, $index + 1);
 | |
|             }
 | |
|         }
 | |
|         app('preferences')->mark();
 | |
| 
 | |
|         return response()->json(['OK']);
 | |
|     }
 | |
| }
 |