mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Merge pull request #7503 from firefly-iii/feat/adjusted-budget
feat: a budget type that will rollover but also incorporate overspend…
This commit is contained in:
commit
cf7c01e5d0
@ -71,6 +71,8 @@ class TransactionController extends Controller
|
||||
$resource = new Collection($transactions, $transformer, 'transactions');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($groups));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
$array = $manager->createData($resource)->toArray();
|
||||
|
||||
return response()->json($array)->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,10 @@ class CronController extends Controller
|
||||
$return = [];
|
||||
$return['recurring_transactions'] = $this->runRecurring($config['force'], $config['date']);
|
||||
$return['auto_budgets'] = $this->runAutoBudget($config['force'], $config['date']);
|
||||
if (true === config('cer.enabled')) {
|
||||
$return['exchange_rates'] = $this->exchangeRatesCronJob($config['force'], $config['date']);
|
||||
}
|
||||
$return['bill_warnings'] = $this->billWarningCronJob($config['force'], $config['date']);
|
||||
|
||||
return response()->json($return);
|
||||
}
|
||||
|
@ -79,9 +79,9 @@ class StoreRequest extends FormRequest
|
||||
'currency_code' => 'exists:transaction_currencies,code',
|
||||
'notes' => 'nullable|between:1,65536',
|
||||
// auto budget info
|
||||
'auto_budget_type' => 'in:reset,rollover,none',
|
||||
'auto_budget_amount' => 'numeric|min:0|max:1000000000|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover',
|
||||
'auto_budget_type' => 'in:reset,rollover,adjusted,none',
|
||||
'auto_budget_amount' => 'numeric|min:0|max:1000000000|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover|required_if:auto_budget_type,adjusted',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover|required_if:auto_budget_type,adjusted',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,7 @@ class UpdateRequest extends FormRequest
|
||||
'none' => 0,
|
||||
'reset' => 1,
|
||||
'rollover' => 2,
|
||||
'adjusted' => 3,
|
||||
];
|
||||
$allData['auto_budget_type'] = $types[$allData['auto_budget_type']] ?? 0;
|
||||
}
|
||||
@ -88,7 +89,7 @@ class UpdateRequest extends FormRequest
|
||||
'name' => sprintf('between:1,100|uniqueObjectForUser:budgets,name,%d', $budget->id),
|
||||
'active' => [new IsBoolean()],
|
||||
'notes' => 'nullable|between:1,65536',
|
||||
'auto_budget_type' => 'in:reset,rollover,none',
|
||||
'auto_budget_type' => 'in:reset,rollover,adjusted,none',
|
||||
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
|
||||
'auto_budget_currency_code' => 'exists:transaction_currencies,code',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000',
|
||||
|
@ -77,7 +77,7 @@ class Cron extends Command
|
||||
*/
|
||||
if (true === config('cer.enabled')) {
|
||||
try {
|
||||
$this->exchangeRatesCronJob($force, $date);
|
||||
//$this->exchangeRatesCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
@ -89,7 +89,7 @@ class Cron extends Command
|
||||
* Fire recurring transaction cron job.
|
||||
*/
|
||||
try {
|
||||
$this->recurringCronJob($force, $date);
|
||||
//$this->recurringCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
@ -111,7 +111,7 @@ class Cron extends Command
|
||||
* Fire bill warning cron job
|
||||
*/
|
||||
try {
|
||||
$this->billWarningCronJob($force, $date);
|
||||
//$this->billWarningCronJob($force, $date);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
|
@ -31,4 +31,5 @@ enum AutoBudgetType: int
|
||||
{
|
||||
case AUTO_BUDGET_RESET = 1;
|
||||
case AUTO_BUDGET_ROLLOVER = 2;
|
||||
case AUTO_BUDGET_ADJUSTED = 3;
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ class CreateController extends Controller
|
||||
0 => (string)trans('firefly.auto_budget_none'),
|
||||
AutoBudget::AUTO_BUDGET_RESET => (string)trans('firefly.auto_budget_reset'),
|
||||
AutoBudget::AUTO_BUDGET_ROLLOVER => (string)trans('firefly.auto_budget_rollover'),
|
||||
AutoBudget::AUTO_BUDGET_ADJUSTED => (string)trans('firefly.auto_budget_adjusted'),
|
||||
];
|
||||
$autoBudgetPeriods = [
|
||||
'daily' => (string)trans('firefly.auto_budget_period_daily'),
|
||||
|
@ -82,6 +82,7 @@ class EditController extends Controller
|
||||
0 => (string)trans('firefly.auto_budget_none'),
|
||||
AutoBudget::AUTO_BUDGET_RESET => (string)trans('firefly.auto_budget_reset'),
|
||||
AutoBudget::AUTO_BUDGET_ROLLOVER => (string)trans('firefly.auto_budget_rollover'),
|
||||
AutoBudget::AUTO_BUDGET_ADJUSTED => (string)trans('firefly.auto_budget_adjusted'),
|
||||
];
|
||||
$autoBudgetPeriods = [
|
||||
'daily' => (string)trans('firefly.auto_budget_period_daily'),
|
||||
|
@ -65,7 +65,7 @@ class BudgetFormStoreRequest extends FormRequest
|
||||
return [
|
||||
'name' => 'required|between:1,100|uniqueObjectForUser:budgets,name',
|
||||
'active' => 'numeric|between:0,1',
|
||||
'auto_budget_type' => 'numeric|integer|gte:0|lte:2',
|
||||
'auto_budget_type' => 'numeric|integer|gte:0|lte:3',
|
||||
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
|
||||
'auto_budget_amount' => 'min:0|max:1000000000|required_if:auto_budget_type,1|required_if:auto_budget_type,2',
|
||||
'auto_budget_period' => 'in:daily,weekly,monthly,quarterly,half_year,yearly',
|
||||
|
@ -81,6 +81,70 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AutoBudget $autoBudget
|
||||
* @return void
|
||||
*/
|
||||
private function createAdjustedLimit(AutoBudget $autoBudget): void
|
||||
{
|
||||
Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id));
|
||||
// current period:
|
||||
$start = app('navigation')->startOfPeriod($this->date, $autoBudget->period);
|
||||
$end = app('navigation')->endOfPeriod($start, $autoBudget->period);
|
||||
|
||||
// which means previous period:
|
||||
$previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period);
|
||||
$previousEnd = app('navigation')->endOfPeriod($previousStart, $autoBudget->period);
|
||||
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Current period is %s-%s, so previous period is %s-%s',
|
||||
$start->format('Y-m-d'),
|
||||
$end->format('Y-m-d'),
|
||||
$previousStart->format('Y-m-d'),
|
||||
$previousEnd->format('Y-m-d')
|
||||
)
|
||||
);
|
||||
|
||||
// has budget limit in previous period?
|
||||
$budgetLimit = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd);
|
||||
|
||||
if (null === $budgetLimit) {
|
||||
Log::debug('No budget limit exists in previous period, so create one.');
|
||||
// if not, create standard amount and we're done.
|
||||
$this->createBudgetLimit($autoBudget, $start, $end);
|
||||
return;
|
||||
}
|
||||
Log::debug('Budget limit exists for previous period.');
|
||||
|
||||
// if has one, calculate expenses and use that as a base.
|
||||
$repository = app(OperationsRepositoryInterface::class);
|
||||
$repository->setUser($autoBudget->budget->user);
|
||||
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency);
|
||||
$currencyId = (int)$autoBudget->transaction_currency_id;
|
||||
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
|
||||
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
|
||||
// what you spent in previous period PLUS the amount for the current period,
|
||||
// if that is more than zero, that's the amount that will be set.
|
||||
|
||||
$budgetAvailable = bcadd(bcadd($budgetLimit->amount, $autoBudget->amount), $spentAmount);
|
||||
$totalAmount = $autoBudget->amount;
|
||||
Log::debug(sprintf('Total amount available for current budget period is %s', $budgetAvailable));
|
||||
|
||||
if (-1 !== bccomp( $budgetAvailable, $totalAmount)) {
|
||||
Log::info(sprintf('There is no overspending, no need to adjust. Budget limit amount will be %s.', $totalAmount));
|
||||
// create budget limit:
|
||||
$this->createBudgetLimit($autoBudget, $start, $end, $totalAmount);
|
||||
}
|
||||
if (1 !== bccomp($budgetAvailable, $totalAmount)) {
|
||||
Log::info(sprintf('There was overspending, so the new amount will be %s.', $budgetAvailable));
|
||||
// create budget limit:
|
||||
$this->createBudgetLimit($autoBudget, $start, $end, $budgetAvailable);
|
||||
}
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AutoBudget $autoBudget
|
||||
*
|
||||
@ -148,6 +212,13 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
|
||||
return;
|
||||
}
|
||||
if (null === $budgetLimit && AutoBudget::AUTO_BUDGET_ADJUSTED === (int)$autoBudget->auto_budget_type) {
|
||||
// budget limit exists already,
|
||||
$this->createAdjustedLimit($autoBudget);
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ class AutoBudget extends Model
|
||||
|
||||
public const AUTO_BUDGET_RESET = 1;
|
||||
public const AUTO_BUDGET_ROLLOVER = 2;
|
||||
public const AUTO_BUDGET_ADJUSTED = 3;
|
||||
|
||||
protected $fillable = ['budget_id','amount','period'];
|
||||
|
||||
|
@ -829,6 +829,9 @@ class BudgetRepository implements BudgetRepositoryInterface
|
||||
if ('rollover' === $type) {
|
||||
$type = AutoBudget::AUTO_BUDGET_ROLLOVER;
|
||||
}
|
||||
if('adjusted' === $type) {
|
||||
$type = AutoBudget::AUTO_BUDGET_ADJUSTED;
|
||||
}
|
||||
|
||||
$repos = app(CurrencyRepositoryInterface::class);
|
||||
$currency = null;
|
||||
|
@ -78,6 +78,7 @@ class BudgetTransformer extends AbstractTransformer
|
||||
$types = [
|
||||
AutoBudget::AUTO_BUDGET_RESET => 'reset',
|
||||
AutoBudget::AUTO_BUDGET_ROLLOVER => 'rollover',
|
||||
AutoBudget::AUTO_BUDGET_ADJUSTED => 'adjusted',
|
||||
];
|
||||
|
||||
if (null !== $autoBudget) {
|
||||
|
@ -1656,6 +1656,7 @@ return [
|
||||
'auto_budget_none' => 'No auto-budget',
|
||||
'auto_budget_reset' => 'Set a fixed amount every period',
|
||||
'auto_budget_rollover' => 'Add an amount every period',
|
||||
'auto_budget_adjusted' => 'Add an amount every period and correct for overspending',
|
||||
'auto_budget_period_daily' => 'Daily',
|
||||
'auto_budget_period_weekly' => 'Weekly',
|
||||
'auto_budget_period_monthly' => 'Monthly',
|
||||
@ -1665,6 +1666,7 @@ return [
|
||||
'auto_budget_help' => 'You can read more about this feature in the help. Click the top-right (?) icon.',
|
||||
'auto_budget_reset_icon' => 'This budget will be set periodically',
|
||||
'auto_budget_rollover_icon' => 'The budget amount will increase periodically',
|
||||
'auto_budget_adjusted_icon' => 'The budget amount will increase periodically and will correct for overspending',
|
||||
'remove_budgeted_amount' => 'Remove budgeted amount in :currency',
|
||||
|
||||
// bills:
|
||||
|
@ -239,6 +239,9 @@
|
||||
{% if 2 == budget.auto_budget.auto_budget_type %}
|
||||
<span class="fa fa-fw fa-calendar-plus-o" title="{{ 'auto_budget_rollover_icon'|_ }}"></span>
|
||||
{% endif %}
|
||||
{% if 3 == budget.auto_budget.auto_budget_type %}
|
||||
<span class="fa fa-fw fa-calendar-plus-o" title="{{ 'auto_budget_adjusted_icon'|_ }}"></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if budget.attachments.count() > 0 %}
|
||||
<span class="fa fa-paperclip"></span>
|
||||
|
Loading…
Reference in New Issue
Block a user