From c659d67172ca90e7684746d3fb8c55eff579f20b Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 20 Nov 2020 06:24:08 +0100 Subject: [PATCH] Budget limit now has period. --- .../BudgetLimit/IndexController.php | 92 --------- .../V1/Controllers/BudgetLimitController.php | 1 + .../Upgrade/AppendBudgetLimitPeriods.php | 174 ++++++++++++++++++ app/Jobs/CreateAutoBudgetLimits.php | 8 +- app/Transformers/BudgetLimitTransformer.php | 13 +- .../2020_11_12_070604_changes_for_v550.php | 4 +- routes/api.php | 11 -- 7 files changed, 193 insertions(+), 110 deletions(-) delete mode 100644 app/Api/V1/Controllers/BudgetLimit/IndexController.php create mode 100644 app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php diff --git a/app/Api/V1/Controllers/BudgetLimit/IndexController.php b/app/Api/V1/Controllers/BudgetLimit/IndexController.php deleted file mode 100644 index 5ffae39e56..0000000000 --- a/app/Api/V1/Controllers/BudgetLimit/IndexController.php +++ /dev/null @@ -1,92 +0,0 @@ -. - */ - -namespace FireflyIII\Api\V1\Controllers\BudgetLimit; - - -use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\DateRequest; -use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Transformers\BudgetLimitTransformer; -use FireflyIII\User; -use Illuminate\Http\JsonResponse; -use Illuminate\Pagination\LengthAwarePaginator; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; -use League\Fractal\Resource\Collection as FractalCollection; - -/** - * Class IndexController - */ -class IndexController extends Controller -{ - - private BudgetLimitRepositoryInterface $blRepository; - private BudgetRepositoryInterface $repository; - - /** - * IndexController constructor. - * - * @codeCoverageIgnore - */ - public function __construct() - { - parent::__construct(); - $this->middleware( - function ($request, $next) { - /** @var User $user */ - $user = auth()->user(); - $this->repository = app(BudgetRepositoryInterface::class); - $this->blRepository = app(BudgetLimitRepositoryInterface::class); - $this->repository->setUser($user); - $this->blRepository->setUser($user); - - return $next($request); - } - ); - } - - /** - * Return all budget limits in a range. - * - * @return JsonResponse - */ - public function index(DateRequest $request): JsonResponse - { - $dates = $request->getAll(); - $manager = $this->getManager(); - $manager->parseIncludes('budget'); - $budgetLimits = $this->blRepository->getAllBudgetLimits($dates['start'], $dates['end']); - $budgetLimits = $budgetLimits->slice(0, 5); - /** @var BudgetLimitTransformer $transformer */ - $transformer = app(BudgetLimitTransformer::class); - $transformer->setParameters($this->parameters); - - $paginator = new LengthAwarePaginator($budgetLimits, $budgetLimits->count(), 1000, $this->parameters->get('page')); - $paginator->setPath(route('api.v1.budget_limits.index') . $this->buildParams()); - - $resource = new FractalCollection($budgetLimits, $transformer, 'budget_limits'); - $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); - - - return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); - } -} \ No newline at end of file diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php index 353277b03c..a6dc66e3ff 100644 --- a/app/Api/V1/Controllers/BudgetLimitController.php +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -102,6 +102,7 @@ class BudgetLimitController extends Controller public function index(Request $request): JsonResponse { $manager = $this->getManager(); + $manager->parseIncludes('budget'); $budgetId = (int)($request->get('budget_id') ?? 0); $budget = $this->repository->findNull($budgetId); $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; diff --git a/app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php b/app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php new file mode 100644 index 0000000000..3e6cb6e520 --- /dev/null +++ b/app/Console/Commands/Upgrade/AppendBudgetLimitPeriods.php @@ -0,0 +1,174 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +use FireflyIII\Models\BudgetLimit; +use Illuminate\Console\Command; +use Log; + +class AppendBudgetLimitPeriods extends Command +{ + public const CONFIG_NAME = '550_budget_limit_periods'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Append budget limits with their (estimated) timeframe.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:budget-limit-periods {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + + $this->theresNoLimit(); + + $this->markAsExecuted(); + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Fixed budget limits in %s seconds.', $end)); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * @param BudgetLimit $limit + * + * @return string|null + */ + private function getLimitPeriod(BudgetLimit $limit): ?string + { + // is daily + if ($limit->end_date->isSameDay($limit->start_date)) { + return 'daily'; + } + // is weekly + if ('1' === $limit->start_date->format('N') && '7' === $limit->end_date->format('N') && 6 === $limit->end_date->diffInDays($limit->start_date)) { + return 'weekly'; + } + + // is monthly + if ( + '1' === $limit->start_date->format('j') // first day + && $limit->end_date->format('j') === $limit->end_date->format('t') // last day + && $limit->start_date->isSameMonth($limit->end_date) + ) { + return 'monthly'; + } + + // is quarter + $start = ['1-1', '1-4', '1-7', '1-10']; + $end = ['31-3', '30-6', '30-9', '31-12']; + if ( + in_array($limit->start_date->format('j-n'), $start, true) // start of quarter + && in_array($limit->end_date->format('j-n'), $end, true) // end of quarter + && 2 === $limit->start_date->diffInMonths($limit->end_date) + ) { + return 'quarterly'; + } + // is half year + $start = ['1-1', '1-7']; + $end = ['30-6', '31-12']; + if ( + in_array($limit->start_date->format('j-n'), $start) // start of quarter + && in_array($limit->end_date->format('j-n'), $end) // end of quarter + && 5 === $limit->start_date->diffInMonths($limit->end_date) + ) { + return 'half_year'; + } + // is yearly + if ('1-1' === $limit->start_date->format('j-n') && '31-12' === $limit->end_date->format('j-n')) { + return 'yearly'; + } + + return null; + } + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * + */ + private function theresNoLimit(): void + { + $limits = BudgetLimit::whereNull('period')->get(); + /** @var BudgetLimit $limit */ + foreach ($limits as $limit) { + $this->fixLimit($limit); + } + } + + /** + * @param BudgetLimit $limit + */ + private function fixLimit(BudgetLimit $limit) + { + $period = $this->getLimitPeriod($limit); + + if (null === $period) { + $message = sprintf('Could not guesstimate budget limit #%d (%s - %s) period.', $limit->id, $limit->start_date->format('Y-m-d'), $limit->end_date->format('Y-m-d')); + $this->warn($message); + Log::warning($message); + return; + } + $limit->period = $period; + $limit->save(); + + $msg = sprintf('Budget limit #%d (%s - %s) period is "%s".', $limit->id, $limit->start_date->format('Y-m-d'), $limit->end_date->format('Y-m-d'), $period); + Log::debug($msg); + + } +} diff --git a/app/Jobs/CreateAutoBudgetLimits.php b/app/Jobs/CreateAutoBudgetLimits.php index b2cd983b75..4d216f0e01 100644 --- a/app/Jobs/CreateAutoBudgetLimits.php +++ b/app/Jobs/CreateAutoBudgetLimits.php @@ -45,8 +45,7 @@ class CreateAutoBudgetLimits implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - /** @var Carbon The current date */ - private $date; + private Carbon $date; /** * Create a new job instance. @@ -60,8 +59,8 @@ class CreateAutoBudgetLimits implements ShouldQueue if (null !== $date) { $date->startOfDay(); $this->date = $date; + Log::debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d'))); } - Log::debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d'))); } /** @@ -106,6 +105,8 @@ class CreateAutoBudgetLimits implements ShouldQueue $budgetLimit->start_date = $start; $budgetLimit->end_date = $end; $budgetLimit->amount = $amount ?? $autoBudget->amount; + $budgetLimit->period = $autoBudget->period; + $budgetLimit->generated = true; $budgetLimit->save(); Log::debug(sprintf('Created budget limit #%d.', $budgetLimit->id)); @@ -203,6 +204,7 @@ class CreateAutoBudgetLimits implements ShouldQueue if (null === $autoBudget->budget) { Log::info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id)); $autoBudget->delete(); + return; } if (!$this->isMagicDay($autoBudget)) { diff --git a/app/Transformers/BudgetLimitTransformer.php b/app/Transformers/BudgetLimitTransformer.php index a30444dec5..3183ac1066 100644 --- a/app/Transformers/BudgetLimitTransformer.php +++ b/app/Transformers/BudgetLimitTransformer.php @@ -24,6 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Transformers; use FireflyIII\Models\BudgetLimit; +use FireflyIII\Repositories\Budget\BudgetLimitRepository; +use FireflyIII\Repositories\Budget\OperationsRepository; +use Illuminate\Support\Collection; use League\Fractal\Resource\Item; /** @@ -46,7 +49,7 @@ class BudgetLimitTransformer extends AbstractTransformer */ public function includeBudget(BudgetLimit $limit) { - return $this->item($limit->budget, new BudgetTransformer,'budgets '); + return $this->item($limit->budget, new BudgetTransformer, 'budgets'); } /** @@ -58,6 +61,11 @@ class BudgetLimitTransformer extends AbstractTransformer */ public function transform(BudgetLimit $budgetLimit): array { + $repository = app(OperationsRepository::class); + $repository->setUser($budgetLimit->budget->user); + $expenses = $repository->sumExpenses($budgetLimit->start_date, $budgetLimit->end_date, null, new Collection([$budgetLimit->budget]), $budgetLimit->transactionCurrency); + + $currency = $budgetLimit->transactionCurrency; $amount = $budgetLimit->amount; $currencyDecimalPlaces = 2; @@ -88,8 +96,9 @@ class BudgetLimitTransformer extends AbstractTransformer 'currency_decimal_places' => $currencyName, 'currency_symbol' => $currencySymbol, 'amount' => $amount, - 'repeat_freq' => $budgetLimit->repeat_freq, + 'period' => $budgetLimit->period, 'auto_budget' => $budgetLimit->auto_budget, + 'spent' => $expenses[$currencyId]['sum'] ?? '0', 'links' => [ [ 'rel' => 'self', diff --git a/database/migrations/2020_11_12_070604_changes_for_v550.php b/database/migrations/2020_11_12_070604_changes_for_v550.php index db437bcf1f..096598eb3b 100644 --- a/database/migrations/2020_11_12_070604_changes_for_v550.php +++ b/database/migrations/2020_11_12_070604_changes_for_v550.php @@ -49,7 +49,7 @@ class ChangesForV550 extends Migration Schema::table( 'budget_limits', static function (Blueprint $table) { - $table->dropColumn('repeat_freq'); + $table->dropColumn('period'); $table->dropColumn('generated'); } ); @@ -105,7 +105,7 @@ class ChangesForV550 extends Migration Schema::table( 'budget_limits', static function (Blueprint $table) { - $table->string('repeat_freq', 12)->nullable(); + $table->string('period', 12)->nullable(); $table->boolean('generated')->default(false); } ); diff --git a/routes/api.php b/routes/api.php index 969ca5a72c..b2cd3eb29b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -175,17 +175,6 @@ Route::group( } ); - -Route::group( - ['namespace' => 'FireflyIII\Api\V1\Controllers\BudgetLimit', 'prefix' => 'limits', - 'as' => 'api.v1.limits.',], - static function () { - - // Budget API routes: - Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']); - } -); - Route::group( ['namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'categories', 'as' => 'api.v1.categories.',],