mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 02:36:28 +00: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:
		| @@ -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> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user