. */ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use Illuminate\Http\JsonResponse; use Illuminate\Support\Collection; /** * Class BudgetController */ class BudgetController extends Controller { private BudgetLimitRepositoryInterface $blRepository; private OperationsRepositoryInterface $opsRepository; private BudgetRepositoryInterface $repository; /** * BudgetController constructor. * * @codeCoverageIgnore */ public function __construct() { parent::__construct(); $this->middleware( function ($request, $next) { //$this->generator = app(GeneratorInterface::class); $this->repository = app(BudgetRepositoryInterface::class); $this->opsRepository = app(OperationsRepositoryInterface::class); $this->blRepository = app(BudgetLimitRepositoryInterface::class); //$this->nbRepository = app(NoBudgetRepositoryInterface::class); return $next($request); } ); } /** * [ * 'label' => 'label for entire set' * 'currency_id' => 0, * 'currency_code' => 'EUR', * 'currency_symbol' => '$', * 'currency_decimal_places' => 2, * 'type' => 'bar', // line, area or bar * 'yAxisID' => 0, // 0, 1, 2 * 'entries' => ['a' => 1, 'b' => 4], * ], * * @param DateRequest $request * * @return JsonResponse */ public function overview(DateRequest $request): JsonResponse { $dates = $request->getAll(); $budgets = $this->repository->getActiveBudgets(); $budgetNames = []; $currencyNames = []; $sets = []; /** @var Budget $budget */ foreach ($budgets as $budget) { $expenses = $this->getExpenses($budget, $dates['start'], $dates['end']); $expenses = $this->filterNulls($expenses); foreach ($expenses as $set) { $budgetNames[] = $set['budget_name']; $currencyNames[] = $set['currency_name']; $sets[] = $set; } } $budgetNames = array_unique($budgetNames); $currencyNames = array_unique($currencyNames); $basic = $this->createSets($budgetNames, $currencyNames); $filled = $this->fillSets($basic, $sets); $keys = array_values($filled); return response()->json($keys); } /** * @param Collection $limits * @param Carbon $start * @param Carbon $end * * @return array */ protected function getExpenses(Budget $budget, Carbon $start, Carbon $end): array { $limits = $this->blRepository->getBudgetLimits($budget, $start, $end); if (0 === $limits->count()) { return $this->getExpenseInRange($budget, $start, $end); } $arr = []; /** @var BudgetLimit $limit */ foreach ($limits as $limit) { $arr[] = $this->getExpensesForLimit($limit); } return $arr; } /** * @param array $budgetNames * @param array $currencyNames * * @return array */ private function createSets(array $budgetNames, array $currencyNames): array { $return = []; foreach ($currencyNames as $currencyName) { $entries = []; foreach ($budgetNames as $budgetName) { $label = sprintf('%s (%s)', $budgetName, $currencyName); $entries[$label] = '0'; } // left $return['left'] = [ 'label' => sprintf('%s (%s)', trans('firefly.left'), $currencyName), 'data_type' => 'left', 'currency_name' => $currencyName, 'type' => 'bar', 'yAxisID' => 0, // 0, 1, 2 'entries' => $entries, ]; // // spent // $return['spent'] = [ // 'label' => sprintf('%s (%s)', trans('firefly.spent'), $currencyName), // 'data_type' => 'spent', // 'currency_name' => $currencyName, // 'type' => 'bar', // 'yAxisID' => 0, // 0, 1, 2 // 'entries' => $entries, // ]; // spent_capped $return['spent_capped'] = [ 'label' => sprintf('%s (%s)', trans('firefly.spent'), $currencyName), 'data_type' => 'spent_capped', 'currency_name' => $currencyName, 'type' => 'bar', 'yAxisID' => 0, // 0, 1, 2 'entries' => $entries, ]; // overspent $return['overspent'] = [ 'label' => sprintf('%s (%s)', trans('firefly.overspent'), $currencyName), 'data_type' => 'overspent', 'currency_name' => $currencyName, 'type' => 'bar', 'yAxisID' => 0, // 0, 1, 2 'entries' => $entries, ]; } return $return; } /** * @param array $basic * @param array $sets * * @return array */ private function fillSets(array $basic, array $sets): array { foreach ($sets as $set) { $label = $set['label']; //$basic['spent']['entries'][$label] = $set['entries']['spent']; $basic['spent_capped']['entries'][$label] = $set['entries']['spent_capped']; $basic['left']['entries'][$label] = $set['entries']['left']; $basic['overspent']['entries'][$label] = $set['entries']['overspent']; } return $basic; } /** * @param array $expenses * * @return array */ private function filterNulls(array $expenses): array { $return = []; /** @var array|null $arr */ foreach ($expenses as $arr) { if (null !== $arr) { $return[] = $arr; } } return $return; } /** * @param Budget $budget * @param Carbon $start * @param Carbon $end * * @return array */ private function getExpenseInRange(Budget $budget, Carbon $start, Carbon $end): array { $spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$budget]), null); $return = []; /** @var array $set */ foreach ($spent as $set) { $current = [ 'label' => sprintf('%s (%s)', $budget->name, $set['currency_name']), 'budget_name' => $budget->name, 'start_date' => $start->format('Y-m-d'), 'end_date' => $end->format('Y-m-d'), 'currency_id' => (int) $set['currency_id'], 'currency_code' => $set['currency_code'], 'currency_name' => $set['currency_name'], 'currency_symbol' => $set['currency_symbol'], 'currency_decimal_places' => (int) $set['currency_decimal_places'], 'type' => 'bar', // line, area or bar, 'entries' => [], ]; $sumSpent = bcmul($set['sum'], '-1'); // spent $current['entries']['spent'] = $sumSpent; $current['entries']['amount'] = '0'; $current['entries']['spent_capped'] = $sumSpent; $current['entries']['left'] = '0'; $current['entries']['overspent'] = '0'; $return[] = $current; } return $return; } /** * @param BudgetLimit $limit * * @return array|null */ private function getExpensesForLimit(BudgetLimit $limit): ?array { $budget = $limit->budget; $spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $limit->transactionCurrency); $currency = $limit->transactionCurrency; // when limited to a currency, the count is always one. Or it's empty. $set = array_shift($spent); if (null === $set) { return null; } $return = [ 'label' => sprintf('%s (%s)', $budget->name, $set['currency_name']), 'budget_name' => $budget->name, 'start_date' => $limit->start_date->format('Y-m-d'), 'end_date' => $limit->end_date->format('Y-m-d'), 'currency_id' => (int) $currency->id, 'currency_code' => $currency->code, 'currency_name' => $currency->name, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => (int) $currency->decimal_places, 'type' => 'bar', // line, area or bar, 'entries' => [], ]; $sumSpent = bcmul($set['sum'], '-1'); // spent $return['entries']['spent'] = $sumSpent; $return['entries']['amount'] = $limit->amount; $return['entries']['spent_capped'] = 1 === bccomp($sumSpent, $limit->amount) ? $limit->amount : $sumSpent; $return['entries']['left'] = 1 === bccomp($limit->amount, $sumSpent) ? bcadd($set['sum'], $limit->amount) : '0'; // left $return['entries']['overspent'] = 1 === bccomp($limit->amount, $sumSpent) ? '0' : bcmul(bcadd($set['sum'], $limit->amount), '-1'); // overspent return $return; } }