mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Add code for budgets
This commit is contained in:
parent
d007db166a
commit
c916fbbee9
56
app/Api/V2/Controllers/Controller.php
Normal file
56
app/Api/V2/Controllers/Controller.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/*
|
||||
* Controller.php
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V2\Controllers;
|
||||
|
||||
use FireflyIII\Transformers\AbstractTransformer;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Resource\Item;
|
||||
use League\Fractal\Serializer\JsonApiSerializer;
|
||||
|
||||
/**
|
||||
* Class Controller
|
||||
*/
|
||||
class Controller extends BaseController
|
||||
{
|
||||
protected const CONTENT_TYPE = 'application/vnd.api+json';
|
||||
/**
|
||||
* Returns a JSON API object and returns it.
|
||||
*
|
||||
* @param string $key
|
||||
* @param Model $object
|
||||
* @param AbstractTransformer $transformer
|
||||
* @return array
|
||||
*/
|
||||
final protected function jsonApiObject(string $key, Model $object, AbstractTransformer $transformer): array
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager;
|
||||
$baseUrl = request()->getSchemeAndHttpHost() . '/api/v2';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$resource = new Item($object, $transformer, $key);
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
}
|
@ -62,4 +62,17 @@ class SumController extends Controller
|
||||
return response()->json($converted);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateRequest $request
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function spent(DateRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->getAll();
|
||||
$result = $this->repository->spentInPeriod($data['start'], $data['end']);
|
||||
$converted = $this->cerSum(array_values($result));
|
||||
|
||||
return response()->json($converted);
|
||||
}
|
||||
|
||||
}
|
||||
|
46
app/Api/V2/Controllers/System/PreferencesController.php
Normal file
46
app/Api/V2/Controllers/System/PreferencesController.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* PreferencesController.php
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V2\Controllers\System;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Transformers\PreferenceTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* Class PreferencesController
|
||||
*/
|
||||
class PreferencesController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Preference $preference
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function get(Preference $preference): JsonResponse
|
||||
{
|
||||
return response()
|
||||
->json($this->jsonApiObject('preferences', $preference, new PreferenceTransformer))
|
||||
->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
}
|
@ -26,6 +26,8 @@ use Carbon\Carbon;
|
||||
use DB;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\AutoBudget;
|
||||
use FireflyIII\Models\Budget;
|
||||
@ -34,6 +36,8 @@ use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\RecurrenceTransactionMeta;
|
||||
use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\RuleTrigger;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Destroy\BudgetDestroyService;
|
||||
use FireflyIII\User;
|
||||
@ -126,15 +130,64 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
$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));
|
||||
$limit->amount,
|
||||
$total,
|
||||
$days,
|
||||
$amount));
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getActiveBudgets(): Collection
|
||||
{
|
||||
return $this->user->budgets()->where('active', true)
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('name', 'ASC')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many days of this budget limit are between start and end?
|
||||
*
|
||||
* @param BudgetLimit $limit
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @return int
|
||||
*/
|
||||
private function daysInOverlap(BudgetLimit $limit, Carbon $start, Carbon $end): int
|
||||
{
|
||||
// start1 = $start
|
||||
// start2 = $limit->start_date
|
||||
// start1 = $end
|
||||
// start2 = $limit->end_date
|
||||
|
||||
// limit is larger than start and end (inclusive)
|
||||
// |-----------|
|
||||
// |----------------|
|
||||
if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) {
|
||||
return $start->diffInDays($end) + 1; // add one day
|
||||
}
|
||||
// limit starts earlier and limit ends first:
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->lte($start) && $limit->end_date->lte($end)) {
|
||||
// return days in the range $start-$limit_end
|
||||
return $start->diffInDays($limit->end_date) + 1; // add one day, the day itself
|
||||
}
|
||||
// limit starts later and limit ends earlier
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->gte($start) && $limit->end_date->gte($end)) {
|
||||
// return days in the range $limit_start - $end
|
||||
return $limit->start_date->diffInDays($end) + 1; // add one day, the day itself
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@ -161,17 +214,6 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getActiveBudgets(): Collection
|
||||
{
|
||||
return $this->user->budgets()->where('active', true)
|
||||
->orderBy('order', 'ASC')
|
||||
->orderBy('name', 'ASC')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
*
|
||||
@ -385,6 +427,69 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function spentInPeriod(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])
|
||||
->setBudgets($this->getActiveBudgets());
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
@ -652,42 +757,4 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
|
||||
$autoBudget->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many days of this budget limit are between start and end?
|
||||
*
|
||||
* @param BudgetLimit $limit
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @return int
|
||||
*/
|
||||
private function daysInOverlap(BudgetLimit $limit, Carbon $start, Carbon $end): int
|
||||
{
|
||||
// start1 = $start
|
||||
// start2 = $limit->start_date
|
||||
// start1 = $end
|
||||
// start2 = $limit->end_date
|
||||
|
||||
// limit is larger than start and end (inclusive)
|
||||
// |-----------|
|
||||
// |----------------|
|
||||
if ($start->gte($limit->start_date) && $end->lte($limit->end_date)) {
|
||||
return $start->diffInDays($end) + 1; // add one day
|
||||
}
|
||||
// limit starts earlier and limit ends first:
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->lte($start) && $limit->end_date->lte($end)) {
|
||||
// return days in the range $start-$limit_end
|
||||
return $start->diffInDays($limit->end_date) + 1; // add one day, the day itself
|
||||
}
|
||||
// limit starts later and limit ends earlier
|
||||
// |-----------|
|
||||
// |-------|
|
||||
if ($limit->start_date->gte($start) && $limit->end_date->gte($end)) {
|
||||
// return days in the range $limit_start - $end
|
||||
return $limit->start_date->diffInDays($end) + 1; // add one day, the day itself
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -185,6 +185,16 @@ interface BudgetRepositoryInterface
|
||||
*/
|
||||
public function setUser(User $user);
|
||||
|
||||
/**
|
||||
* Used in the v2 API to calculate the amount of money spent in all active budgets.
|
||||
*
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function spentInPeriod(Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
|
@ -288,7 +288,7 @@ class OperationsRepository implements OperationsRepositoryInterface
|
||||
* @param Collection|null $accounts
|
||||
* @param Collection|null $budgets
|
||||
* @param TransactionCurrency|null $currency
|
||||
*
|
||||
* @deprecated
|
||||
* @return array
|
||||
*/
|
||||
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null
|
||||
|
@ -90,6 +90,7 @@ interface OperationsRepositoryInterface
|
||||
public function spentInPeriodMc(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param Collection|null $accounts
|
||||
@ -101,4 +102,5 @@ interface OperationsRepositoryInterface
|
||||
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null
|
||||
): array;
|
||||
|
||||
|
||||
}
|
||||
|
12
frontend/src/api/v2/budgets/sum.js
vendored
12
frontend/src/api/v2/budgets/sum.js
vendored
@ -29,10 +29,10 @@ export default class Sum {
|
||||
return api.get(url, {params: {start: startStr, end: endStr}});
|
||||
}
|
||||
|
||||
// /*paid(start, end) {
|
||||
// let url = 'api/v2/bills/sum/paid';
|
||||
// let startStr = format(start, 'y-MM-dd');
|
||||
// let endStr = format(end, 'y-MM-dd');
|
||||
// return api.get(url, {params: {start: startStr, end: endStr}});
|
||||
// }*/
|
||||
spent(start, end) {
|
||||
let url = 'api/v2/budgets/sum/spent';
|
||||
let startStr = format(start, 'y-MM-dd');
|
||||
let endStr = format(end, 'y-MM-dd');
|
||||
return api.get(url, {params: {start: startStr, end: endStr}});
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
<q-card bordered>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label><strong>Spend</strong></q-item-label>
|
||||
<q-item-label><strong>To spend and left</strong></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator/>
|
||||
@ -48,6 +48,10 @@
|
||||
<span :title="formatAmount(this.currency, this.budgetedAmount)">Budgeted</span>:
|
||||
<span v-for="(budget, index) in budgeted"><span v-if="budget.native">(</span>{{ formatAmount(budget.code, budget.sum) }}<span v-if="budget.native">)</span><span
|
||||
v-if="index+1 !== budgeted.length">, </span></span>
|
||||
<br>
|
||||
<span :title="formatAmount(this.currency, this.spentAmount)">Spent</span>:
|
||||
<span v-for="(budget, index) in spent"><span v-if="budget.native">(</span>{{ formatAmount(budget.code, budget.sum) }}<span v-if="budget.native">)</span><span
|
||||
v-if="index+1 !== budgeted.length">, </span></span>
|
||||
</q-card-section>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
@ -118,7 +122,7 @@ export default {
|
||||
const sum = new Sum;
|
||||
this.currency = this.store.getCurrencyCode;
|
||||
sum.budgeted(start, end).then((response) => this.parseBudgetedResponse(response.data));
|
||||
//sum.paid(start, end).then((response) => this.parsePaidResponse(response.data));
|
||||
sum.spent(start, end).then((response) => this.parseSpentResponse(response.data));
|
||||
}
|
||||
},
|
||||
// TODO this method is recycled a lot.
|
||||
@ -143,6 +147,24 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
parseSpentResponse: function (data) {
|
||||
for (let i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
const current = data[i];
|
||||
const hasNative = current.native_id !== current.id && parseFloat(current.native_sum) !== 0.0;
|
||||
this.spent.push(
|
||||
{
|
||||
sum: current.sum,
|
||||
code: current.code,
|
||||
native: hasNative
|
||||
}
|
||||
);
|
||||
if (hasNative || current.native_id === current.id) {
|
||||
this.spentAmount = this.spentAmount + (parseFloat(current.native_sum) * -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -46,14 +46,14 @@ Route::group(
|
||||
);
|
||||
|
||||
/**
|
||||
* V2 API route for bills.
|
||||
* V2 API route for budgets.
|
||||
*/
|
||||
Route::group(
|
||||
['namespace' => 'FireflyIII\Api\V2\Controllers\Model\Budget', 'prefix' => 'v2/budgets',
|
||||
'as' => 'api.v2.budgets',],
|
||||
static function () {
|
||||
Route::get('sum/budgeted', ['uses' => 'SumController@budgeted', 'as' => 'sum.budgeted']);
|
||||
Route::get('sum/unpaid', ['uses' => 'SumController@unpaid', 'as' => 'sum.unpaid']);
|
||||
Route::get('sum/spent', ['uses' => 'SumController@spent', 'as' => 'sum.spent']);
|
||||
}
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user