Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add budgets to groups #24

Merged
merged 7 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions _ide_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/**
* A helper file for Laravel, to provide autocomplete information to your IDE
* Generated for Laravel 7.17.2 on 2020-07-05 03:21:41.
* Generated for Laravel 7.21.0 on 2020-08-06 04:21:14.
*
* This file should not be included in your code, only analyzed by your IDE!
*
Expand Down Expand Up @@ -868,6 +868,18 @@ public static function getLocale()
return $instance->getLocale();
}

/**
* Get the current application fallback locale.
*
* @return string
* @static
*/
public static function getFallbackLocale()
{
/** @var \Illuminate\Foundation\Application $instance */
return $instance->getFallbackLocale();
}

/**
* Set the current application locale.
*
Expand All @@ -881,6 +893,19 @@ public static function setLocale($locale)
$instance->setLocale($locale);
}

/**
* Set the current application fallback locale.
*
* @param string $fallbackLocale
* @return void
* @static
*/
public static function setFallbackLocale($fallbackLocale)
{
/** @var \Illuminate\Foundation\Application $instance */
$instance->setFallbackLocale($fallbackLocale);
}

/**
* Determine if application locale is the given locale.
*
Expand Down Expand Up @@ -5481,7 +5506,7 @@ public static function assertDispatched($event, $callback = null)
}

/**
* Assert if a event was dispatched a number of times.
* Assert if an event was dispatched a number of times.
*
* @param string $event
* @param int $times
Expand Down Expand Up @@ -6978,7 +7003,7 @@ public static function extend($driver, $callback)
/**
* Unset the given channel instance.
*
* @param string|null $name
* @param string|null $driver
* @return \Illuminate\Log\LogManager
* @static
*/
Expand Down Expand Up @@ -18497,6 +18522,8 @@ public static function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id'
/**
* Remove all existing orders and optionally add a new order.
*
* @param string|null $column
* @param string $direction
* @return \Illuminate\Database\Query\Builder
* @static
*/
Expand Down
198 changes: 198 additions & 0 deletions app/Budget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php

namespace App;

use App\Models\Scopes\SummedTransactionsForBudget;
use App\Models\Transaction;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Rule;
use Kregel\LaravelAbstract\AbstractEloquentModel;
use Kregel\LaravelAbstract\AbstractModelTrait;
use RRule\RRule;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\Filters\FiltersScope;
use Spatie\Tags\HasTags;
use Znck\Eloquent\Traits\BelongsToThrough;

/**
* App\Budget
*
* @property int $id
* @property int $user_id
* @property string $name
* @property float $amount
* @property string $frequency
* @property string $interval
* @property \Illuminate\Support\Carbon $started_at
* @property int|null $count
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Database\Eloquent\Collection|\Spatie\Tags\Tag[] $tags
* @property-read int|null $tags_count
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget q($string)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget query()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget totalSpends()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereAmount($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereCount($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereFrequency($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereInterval($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereStartedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget whereUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget withAllTags($tags, $type = null)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget withAllTagsOfAnyType($tags)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget withAnyTags($tags, $type = null)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Budget withAnyTagsOfAnyType($tags)
* @mixin \Eloquent
*/
class Budget extends Model implements AbstractEloquentModel
{
use AbstractModelTrait, BelongsToThrough, HasTags;

public $guarded = [];

protected $casts = [
'started_at' => 'datetime',
'breached_at' => 'datetime',
];

public static function booted()
{
static::creating(function ($budget) {
if (empty($budget->user_id) && auth()->check()) {
$budget->user_id = auth()->id();
}
});
}

public function scopeTotalSpends(Builder $query)
{
$query->addSelect([
'budgets.*',
\DB::raw("CASE
WHEN frequency='YEARLY' THEN @diff:=(YEAR(now()) - YEAR(started_at))
WHEN frequency='MONTHLY' THEN @diff:=PERIOD_DIFF(DATE_FORMAT(now(), \"%Y%m\"), DATE_FORMAT(started_at, \"%Y%m\"))
WHEN frequency='DAILY' THEN @diff:=DATEDIFF(now(), started_at)
WHEN frequency='WEEKLY' THEN @diff:=ROUND(DATEDIFF(now(), started_at)/7, 0)
END as diff"),
\DB::raw("CASE
WHEN frequency='YEARLY' THEN @periodStart:=if(DATE_ADD(started_at, INTERVAL @diff YEAR) < now(), DATE_ADD(started_at, INTERVAL @diff YEAR), DATE_ADD(started_at, INTERVAL @diff-1 YEAR))
WHEN frequency='MONTHLY' THEN @periodStart:=if(DATE_ADD(started_at, INTERVAL @diff MONTH) < now(), DATE_ADD(started_at, INTERVAL @diff MONTH), DATE_ADD(started_at, INTERVAL @diff-1 MONTH))
WHEN frequency='DAILY' THEN @periodStart:=if(DATE_ADD(started_at, INTERVAL @diff DAY) < now(), DATE_ADD(started_at, INTERVAL @diff DAY), DATE_ADD(started_at, INTERVAL @diff-1 DAY))
WHEN frequency='WEEKLY' THEN @periodStart:=if(DATE_ADD(started_at, INTERVAL @diff WEEK) < now(), DATE_ADD(started_at, INTERVAL @diff WEEK), DATE_ADD(started_at, INTERVAL @diff-1 WEEK))
END as period_started_at"),
\DB::raw("CASE
WHEN frequency='YEARLY' THEN if(DATE_ADD(started_at, INTERVAL @diff YEAR) < now(), DATE_ADD(started_at, INTERVAL @diff+1 YEAR), DATE_ADD(started_at, INTERVAL @diff YEAR))
WHEN frequency='MONTHLY' THEN if(DATE_ADD(started_at, INTERVAL @diff MONTH) < now(), DATE_ADD(started_at, INTERVAL @diff+1 MONTH), DATE_ADD(started_at, INTERVAL @diff MONTH))
WHEN frequency='DAILY' THEN if(DATE_ADD(started_at, INTERVAL @diff DAY) < now(), DATE_ADD(started_at, INTERVAL @diff+1 DAY), DATE_ADD(started_at, INTERVAL @diff DAY))
WHEN frequency='WEEKLY' THEN if(DATE_ADD(started_at, INTERVAL @diff WEEK) < now(), DATE_ADD(started_at, INTERVAL @diff+1 WEEK), DATE_ADD(started_at, INTERVAL @diff WEEK))
END as next_period"),
\DB::raw('@userId:=user_id as user_id'),
\DB::raw('(
select sum(amount)
from transactions
cross join taggables on taggables.tag_id in (
select distinct taggables.tag_id
from taggables
cross join tags tag
on taggables.taggable_type = \'App\\\\Budget\'
cross join budgets b
on b.id = taggables.taggable_id
where tag.user_id = b.user_id
and b.user_id = cast(@userId as UNSIGNED)
)
and taggables.taggable_id = transactions.id
and taggables.taggable_type = \'App\\\\Models\\\\Transaction\'
and transactions.date >= date_format(@periodStart, "%Y-%m-%d")
and transactions.account_id in (
select account_id
from accounts
where access_token_id in (
select id
from access_tokens
where access_tokens.user_id = cast(@userId as UNSIGNED)
)
)
) as total_spend')
]);
}

public function getValidationCreateRules (): array
{
return [
'name' => 'required',
'amount' => 'required',
'frequency' => 'required',
'interval' => 'required',
'started_at' => 'required',
'count' => 'required',
];
}

public function getValidationUpdateRules (): array
{
return [
'name' => 'string',
'amount' => 'numeric',
'frequency' => Rule::in([
'MONTHLY',
'YEARLY',
'DAILY',
'WEEKLY',
]),
'interval' => 'numeric',
'started_at' => 'date',
'count' => 'numeric',
];
}

public function getAbstractAllowedFilters (): array
{
return [
AllowedFilter::scope('totalSpends'),
];
}

public function getAbstractAllowedRelationships (): array
{
return ['tags'];
}

public function getAbstractAllowedSorts (): array
{
return [];
}

public function getAbstractAllowedFields (): array
{
return [];
}

public function getAbstractSearchableFields (): array
{
return [];
}

public function getRule(): RRule
{
return new RRule(array_merge([
'FREQ' => $this->frequency,
'INTERVAL' => $this->interval,
'DTSTART' => $this->started_at,
], $this->count ? [
'COUNT' => $this->count,
] : []));
}

public function user()
{
return $this->belongsTo(User::class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

namespace App\Console\Commands;

use App\Jobs\CheckBudgetsForBreachesOfAmount;
use Illuminate\Console\Command;

class SyncTokens extends Command
class CheckBudgetBreachCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sync:tokens';
protected $signature = 'check:budget-breach';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Sync the accounts from the stored plaid access tokens.';
protected $description = 'Check the budgets to see if they\'re over budget';

/**
* Create a new command instance.
Expand All @@ -30,13 +31,8 @@ public function __construct()
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//
CheckBudgetsForBreachesOfAmount::dispatch();
}
}
20 changes: 5 additions & 15 deletions app/Console/Commands/GenerateChannelsAndAlertsFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\AccountKpi;
use App\Condition;
use App\Events\BudgetBreachedEstablishedAmount;
use App\Events\TransactionCreated;
use App\Events\TransactionGroupedEvent;
use App\Events\TransactionUpdated;
Expand Down Expand Up @@ -76,6 +77,10 @@ public function handle()
'type' => TransactionGroupedEvent::class,
'name' => 'When a transaction is added to a group (this gives you access to the `tag` variable in your title, body and payload.)'
],
[
'type' => BudgetBreachedEstablishedAmount::class,
'name' => 'When a budget\'s total spend amount for a period exceeds the set amount.'
]
]);

$this->writeToDisk('js/condition-parameters.js', [
Expand Down Expand Up @@ -109,21 +114,6 @@ public function handle()
],
]);

$this->writeToDisk('js/alert-events.js', [
[
'type' => TransactionUpdated::class,
'name' => 'When a transaction is updated (moving from pending to not pending, updating amounts, etc...)'
],
[
'type' => TransactionCreated::class,
'name' => 'When a transaction is initially created (only fired once per transaction)',
],
[
'type' => TransactionGroupedEvent::class,
'name' => 'When a transaction is added to a group (this gives you access to the `tag` variable in your title, body and payload.)'
],
]);

$this->writeToDisk('js/condition-comparator.js', array_map(function ($comparator) {
return [
'value' => $comparator,
Expand Down
6 changes: 5 additions & 1 deletion app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('sync:plaid 7')->hourly();
$schedule->command('plaid:sync-institutions')->monthly();
$schedule->command('plaid:sync-categories')->monthly();
$schedule->command('generate:account-kpis')->dailyAt('23:55');
$schedule->command('sync:plaid 1')->hourlyAt(0);
// Small job offset so we don't flood the queue. Not really ever going to be a problem... but meh :shrug:
$schedule->command('check:budget-breach')->hourlyAt(10);
}

/**
Expand Down
25 changes: 25 additions & 0 deletions app/Events/BudgetBreachedEstablishedAmount.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Events;

use App\Budget;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class BudgetBreachedEstablishedAmount
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public $budget;

public function __construct(Budget $budget)
{
$this->budget = $budget;
}

public function getBudget(): Budget
{
return $this->budget;
}
}
Loading