diff --git a/app/Http/Controllers/Budget/BudgetLimitController.php b/app/Http/Controllers/Budget/BudgetLimitController.php index 750dd1523c..74558b511c 100644 --- a/app/Http/Controllers/Budget/BudgetLimitController.php +++ b/app/Http/Controllers/Budget/BudgetLimitController.php @@ -24,15 +24,23 @@ 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\Controllers\DateCalculation; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Collection; +use Log; /** * @@ -40,6 +48,7 @@ use Illuminate\Http\Request; */ class BudgetLimitController extends Controller { + use DateCalculation; /** @var AvailableBudgetRepositoryInterface */ private $abRepository; @@ -73,6 +82,35 @@ class BudgetLimitController extends Controller ); } + /** + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function create(Budget $budget, Carbon $start, Carbon $end) + { + $collection = $this->currencyRepos->getEnabled(); + $budgetLimits = $this->blRepository->getBudgetLimits($budget, $start, $end); + + // remove already budgeted currencies: + $currencies = $collection->filter( + static function (TransactionCurrency $currency) use ($budgetLimits) { + /** @var AvailableBudget $budget */ + foreach ($budgetLimits as $budget) { + if ($budget->transaction_currency_id === $currency->id) { + return false; + } + } + + return true; + } + ); + + return view('budgets.budget-limits.create', compact('start', 'end', 'currencies', 'budget')); + } + /** * @param Request $request * @param BudgetLimit $budgetLimit @@ -90,21 +128,62 @@ class BudgetLimitController extends Controller /** * @param Request $request * - * @return JsonResponse + * @return JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws FireflyException */ - public function store(Request $request): JsonResponse + public function store(Request $request) { - $limit = $this->blRepository->store( - [ - 'budget_id' => $request->get('budget_id'), - 'transaction_currency_id' => $request->get('transaction_currency_id'), - 'start_date' => $request->get('start'), - 'end_date' => $request->get('end'), - 'amount' => $request->get('amount'), - ] - ); + // first search for existing one and update it if necessary. + $currency = $this->currencyRepos->find((int)$request->get('transaction_currency_id')); + $budget = $this->repository->findNull((int)$request->get('budget_id')); + if (null === $currency || null === $budget) { + throw new FireflyException('No valid currency or budget.'); + } + $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); + $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + $start->startOfDay(); + $end->endOfDay(); - return response()->json($limit->toArray()); + + Log::debug(sprintf('Start: %s, end: %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + + $limit = $this->blRepository->find($budget, $currency, $start, $end); + if (null !== $limit) { + $limit->amount = $request->get('amount'); + $limit->save(); + } + if (null === $limit) { + $limit = $this->blRepository->store( + [ + 'budget_id' => $request->get('budget_id'), + 'transaction_currency_id' => $request->get('transaction_currency_id'), + 'start_date' => $request->get('start'), + 'end_date' => $request->get('end'), + 'amount' => $request->get('amount'), + ] + ); + } + + if ($request->expectsJson()) { + $array = $limit->toArray(); + + + // add some extra meta data: + $spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $currency); + $array['spent'] = $spentArr[$currency->id]['sum'] ?? '0'; + $array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount'])); + $array['amount_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $limit['amount']); + $array['days_left'] = (string)$this->activeDaysLeft($start, $end); + // left per day: + $array['left_per_day'] = bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']); + + // left per day formatted. + $array['left_per_day_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $array['left_per_day']); + + return response()->json($array); + } + + return redirect(route('budgets.index')); } /** diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index 8ce2683702..e2cae5f065 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -149,6 +149,7 @@ class IndexController extends Controller // number of days for consistent budgeting. $activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description. $activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description. + Log::debug(sprintf('Start: %s, end: %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); // get all budgets, and paginate them into $budgets. $collection = $this->repository->getActiveBudgets(); @@ -221,18 +222,14 @@ class IndexController extends Controller public function reorder(Request $request, BudgetRepositoryInterface $repository): JsonResponse { $budgetIds = $request->get('budgetIds'); - $page = (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $currentOrder = (($page - 1) * $pageSize) + 1; - foreach ($budgetIds as $budgetId) { + foreach ($budgetIds as $index => $budgetId) { $budgetId = (int)$budgetId; $budget = $repository->findNull($budgetId); if (null !== $budget) { - Log::debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $currentOrder)); - $repository->setBudgetOrder($budget, $currentOrder); + Log::debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $index + 1)); + $repository->setBudgetOrder($budget, $index + 1); } - $currentOrder++; } return response()->json(['OK']); diff --git a/app/Repositories/Budget/BudgetLimitRepository.php b/app/Repositories/Budget/BudgetLimitRepository.php index 795eb8a688..9c8a1b63fa 100644 --- a/app/Repositories/Budget/BudgetLimitRepository.php +++ b/app/Repositories/Budget/BudgetLimitRepository.php @@ -99,6 +99,22 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface } } + /** + * @param Budget $budget + * @param TransactionCurrency $currency + * @param Carbon $start + * @param Carbon $end + * + * @return BudgetLimit|null + */ + public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit + { + return $budget->budgetlimits() + ->where('transaction_currency_id', $currency->id) + ->where('start_date', $start->format('Y-m-d')) + ->where('end_date', $end->format('Y-m-d'))->first(); + } + /** * @param Carbon $start * @param Carbon $end diff --git a/app/Repositories/Budget/BudgetLimitRepositoryInterface.php b/app/Repositories/Budget/BudgetLimitRepositoryInterface.php index 5db47a2865..a9a795d08c 100644 --- a/app/Repositories/Budget/BudgetLimitRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetLimitRepositoryInterface.php @@ -55,6 +55,16 @@ interface BudgetLimitRepositoryInterface */ public function destroyBudgetLimit(BudgetLimit $budgetLimit): void; + /** + * @param Budget $budget + * @param TransactionCurrency $currency + * @param Carbon $start + * @param Carbon $end + * + * @return BudgetLimit|null + */ + public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit; + /** * TODO this method is not multi-currency aware. * diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index f4e8abeb2e..1f08e9df51 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -28,7 +28,6 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\TransactionCurrency; use FireflyIII\Services\Internal\Destroy\BudgetDestroyService; use FireflyIII\User; use Illuminate\Support\Collection; @@ -55,7 +54,6 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @return bool - * // it's 5. */ public function cleanupBudgets(): bool { @@ -65,23 +63,14 @@ class BudgetRepository implements BudgetRepositoryInterface } catch (Exception $e) { Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage())); } - Budget::where('order', 0)->update(['order' => 100]); - - // do the clean up by hand because Sqlite can be tricky with this. - $budgetLimits = BudgetLimit::orderBy('created_at', 'DESC')->get(['id', 'budget_id', 'start_date', 'end_date']); - $count = []; - /** @var BudgetLimit $budgetLimit */ - foreach ($budgetLimits as $budgetLimit) { - $key = $budgetLimit->budget_id . '-' . $budgetLimit->start_date->format('Y-m-d') . $budgetLimit->end_date->format('Y-m-d'); - if (isset($count[$key])) { - // delete it! - try { - BudgetLimit::find($budgetLimit->id)->delete(); - } catch (Exception $e) { - Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage())); - } - } - $count[$key] = true; + $budgets = $this->getActiveBudgets(); + /** + * @var int $index + * @var Budget $budget + */ + foreach ($budgets as $index => $budget) { + $budget->order = $index + 1; + $budget->save(); } return true; diff --git a/public/v1/js/ff/budgets/index.js b/public/v1/js/ff/budgets/index.js index 52b6ca8da5..cf590a16c4 100644 --- a/public/v1/js/ff/budgets/index.js +++ b/public/v1/js/ff/budgets/index.js @@ -39,6 +39,8 @@ $(function () { $('.create_ab_alt').on('click', createAltAvailableBudget); $('.budget_amount').on('change', updateBudgetedAmount); + $('.create_bl').on('click', createBudgetLimit); + /* When the input changes, update the percentages for the budgeted bar: @@ -49,8 +51,7 @@ $(function () { var selected = $(e.currentTarget); if (selected.find(":selected").val() !== "x") { var newUri = budgetIndexUri.replace("START", selected.find(":selected").data('start')).replace('END', selected.find(":selected").data('end')); - console.log(newUri); - window.location.assign(newUri + "?page=" + page); + window.location.assign(newUri); } }); @@ -88,9 +89,9 @@ function updateBudgetedAmount(e) { var budgetId = parseInt(input.data('id')); var budgetLimitId = parseInt(input.data('limit')); var currencyId = parseInt(input.data('currency')); - console.log(budgetLimitId); + input.prop('disabled', true); if (0 === budgetLimitId) { - $.post(createBudgetLimitUri, { + $.post(storeBudgetLimitUri, { _token: token, budget_id: budgetId, transaction_currency_id: currencyId, @@ -99,12 +100,21 @@ function updateBudgetedAmount(e) { end: periodEnd }).done(function (data) { - alert('done!'); + input.prop('disabled', false); + + // update amount left. + $('.left_span[data-limit="0"][data-id="' + budgetId + '"]').html(data.left_formatted); + if (data.left_per_day > 0) { + $('.left_span[data-limit="0"][data-id="' + budgetId + '"]').html(data.left_formatted + '(' + data.left_per_day_formatted + ')'); + } + console.log(data); + //$('.left_span[data-limit="0"][data-id="' + budgetId + '"]').text('XXXXX'); + }).fail(function () { alert('I failed :('); }); } else { - $.post(updateBudgetLimitUri.replace('REPLACEME', budgetLimitId), { + $.post(updateBudgetLimitUri.replace('REPLACEME', budgetLimitId.toString()), { _token: token, amount: input.val(), }).done(function (data) { @@ -142,14 +152,21 @@ function sortStop(event, ui) { }); var arr = { budgetIds: submit, - page: page, _token: token }; $.post('budgets/reorder', arr); } -function createAltAvailableBudget(e) { +function createBudgetLimit(e) { var button = $(e.currentTarget); + var budgetId = button.data('id'); + $('#defaultModal').empty().load(createBudgetLimitUri.replace('REPLACEME', budgetId.toString()), function () { + $('#defaultModal').modal('show'); + }); + return false; +} + +function createAltAvailableBudget(e) { $('#defaultModal').empty().load(createAltAvailableBudgetUri, function () { $('#defaultModal').modal('show'); }); @@ -180,18 +197,15 @@ function drawBudgetedBars() { var bar = $(v); var budgeted = parseFloat(bar.data('budgeted')); var available = parseFloat(bar.data('available')); - console.log('Budgeted bar for bar ' + bar.data('id')); var budgetedTooMuch = budgeted > available; var pct; if (budgetedTooMuch) { - console.log('over budget'); // budgeted too much. pct = (available / budgeted) * 100; bar.find('.progress-bar-warning').css('width', pct + '%'); bar.find('.progress-bar-danger').css('width', (100 - pct) + '%'); bar.find('.progress-bar-info').css('width', 0); } else { - console.log('under budget'); pct = (budgeted / available) * 100; bar.find('.progress-bar-warning').css('width', 0); bar.find('.progress-bar-danger').css('width', 0); diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 5d827f2d9b..26a530d184 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -700,6 +700,8 @@ return [ 'update_amount' => 'Update amount', 'update_budget' => 'Update budget', 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', + 'set_budget_limit_title' => 'Set budgeted amount for budget :budget between :start and :end', + 'set_budget_limit' => 'Set budgeted amount', 'budget_period_navigator' => 'Period navigator', 'info_on_available_amount' => 'What do I have available?', 'available_amount_indication' => 'Use these amounts to get an indication of what your total budget could be.', diff --git a/resources/views/v1/budgets/available-budgets/create-alternative.twig b/resources/views/v1/budgets/available-budgets/create-alternative.twig index a2273da364..eb76aa2e2b 100644 --- a/resources/views/v1/budgets/available-budgets/create-alternative.twig +++ b/resources/views/v1/budgets/available-budgets/create-alternative.twig @@ -15,7 +15,6 @@ -