Skip to content

Commit

Permalink
Merge pull request #324 from jesusantguerrero/fix/calulations
Browse files Browse the repository at this point in the history
Add rollovers at the end of the month
  • Loading branch information
jesusantguerrero authored Dec 2, 2023
2 parents b218088 + 5ca671b commit f1ec069
Show file tree
Hide file tree
Showing 23 changed files with 401 additions and 76 deletions.
16 changes: 13 additions & 3 deletions app/Console/Commands/CheckMonthRollover.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace App\Console\Commands;

use DateTime;
use App\Models\Team;
use Illuminate\Console\Command;

use Illuminate\Console\Command;
use App\Domains\Budget\Services\BudgetRolloverService;

class CheckMonthRollover extends Command
Expand All @@ -30,7 +31,16 @@ public function handle(BudgetRolloverService $rolloverService)
{

$teams = Team::with('timezone')->without(['settings'])->get();
dd($teams->toArray());

$now = now();
foreach ($teams as $team) {
$timezone = $team["timezone"] ?? null;
if (!$timezone) continue;

$now->setTimezone($timezone['value']);
if ($now->format('H:i') === '00:00' && $now->format('Y-m-d')) {
$previousMonth = $now->subMonth()->startOf('month')->format('Y-m');
$rolloverService->startFrom($team->id, $previousMonth);
}
}
}
}
1 change: 0 additions & 1 deletion app/Console/Commands/MonthlyRollover.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ public function handle(BudgetRolloverService $rolloverService)
$month = $this->argument('month');

$rolloverService->rollMonth($teamId, $month."-01");

}
}
13 changes: 7 additions & 6 deletions app/Console/Commands/TeamBudgetRollover.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use App\Domains\AppCore\Models\Category;
use App\Domains\Budget\Data\BudgetReservedNames;
use App\Domains\Budget\Services\BudgetRolloverService;

class TeamBudgetRollover extends Command
Expand All @@ -15,7 +13,7 @@ class TeamBudgetRollover extends Command
*
* @var string
*/
protected $signature = 'app:team-budget-rollover {teamId} ?{date}';
protected $signature = 'app:team-budget-rollover {teamId} {date?}';

/**
* The console command description.
Expand All @@ -33,15 +31,18 @@ public function handle(BudgetRolloverService $rolloverService)
$teamId = $this->argument('teamId');
$date = $this->argument('date');

$monthsWithTransactions = DB::table('transaction_lines')
$monthsWithTransactions = $this->getFirstTransaction($teamId);
$rolloverService->startFrom($teamId, $date ?? $monthsWithTransactions->date);
}

private function getFirstTransaction(int $teamId) {
return DB::table('transaction_lines')
->where([
"team_id" => $teamId
])
->selectRaw("date_format(transaction_lines.date, '%Y-%m') AS date")
->groupBy(DB::raw("date_format(transaction_lines.date, '%Y-%m')"))
->orderBy('date')
->first();

$rolloverService->startFrom($teamId, $date ?? $monthsWithTransactions->date);
}
}
6 changes: 6 additions & 0 deletions app/Domains/Budget/Models/BudgetMonth.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class BudgetMonth extends Model
'payments',
'left_from_last_month',
'overspending_previous_month',
'accounts_balance',
'meta_data'
];

protected $casts = [
'meta_data' => 'array'
];

public function category()
Expand Down
5 changes: 3 additions & 2 deletions app/Domains/Budget/Models/BudgetTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ public static function getNextTargets($teamId, $targetTypes = ['spending'])
'frequency' => 'monthly',
'budget_targets.team_id' => $teamId,
])
->whereRaw("concat(date_format(now(), '%Y-%m'), '-', frequency_month_date) >= now()")
->addSelect(DB::raw("budget_targets.*, concat(date_format(now(), '%Y-%m'), '-', frequency_month_date) as due_date"))
->whereRaw("concat(date_format(now(), '%Y-%m'), '-', LPAD(frequency_month_date, 2, '0')) >= date_format(now(), '%Y-%m-%d')")
->addSelect(DB::raw("budget_targets.*, concat(date_format(now(), '%Y-%m'), '-', LPAD(frequency_month_date, 2, '0')) as due_date"))
->from('budget_targets')
->orderByRaw('due_date')
->get();
}

Expand Down
50 changes: 45 additions & 5 deletions app/Domains/Budget/Services/BudgetRolloverService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

namespace App\Domains\Budget\Services;

use App\Models\Team;
use Brick\Money\Money;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Insane\Journal\Models\Core\Account;
use Insane\Journal\Models\Core\Category;
use App\Domains\Budget\Models\BudgetMonth;
use App\Domains\Budget\Data\BudgetAssignData;
use App\Domains\Budget\Data\BudgetReservedNames;
use Insane\Journal\Models\Core\AccountDetailType;

class BudgetRolloverService {
private Team|null $team = null;
private mixed $accounts = [];

public function __construct(private BudgetCategoryService $budgetCategoryService) {}

public function rollMonth($teamId, $month, $categories = null) {
Expand Down Expand Up @@ -92,8 +98,12 @@ private function moveReadyToAssign($teamId, $month, $overspending = 0, $fundedFr
coalesce(sum(budgeted), 0) as budgeted,
coalesce(sum(activity), 0) as budgetsActivity,
coalesce(sum(payments), 0) as payments,
coalesce(sum(available), 0) as available,
sum(CASE WHEN available < 0 THEN available ELSE 0 END) as overspendingInMonth,
coalesce(sum(funded_spending), 0) as funded_spending
")->groupBy('month')
")
->groupBy('month')
// ->dd();
->first();

$budgetMonth = BudgetMonth::where([
Expand All @@ -105,11 +115,31 @@ private function moveReadyToAssign($teamId, $month, $overspending = 0, $fundedFr

$inflow = (new BudgetCategoryService($readyToAssignCategory))->getCategoryActivity($readyToAssignCategory, $month);
$positiveAmount = $budgetMonth->left_from_last_month + $inflow + $results?->funded_spending;
$available = $positiveAmount - $results?->budgeted ;
$available = $positiveAmount - $results?->budgeted;

$nextMonth = Carbon::createFromFormat("Y-m-d", $month)->addMonthsWithNoOverflow(1)->format('Y-m-d');
$overspending = abs($results?->overspendingInMonth);
$leftover = $inflow - $results?->budgeted;

echo "Leftover: ". $leftover . "overspending: " . $overspending . PHP_EOL;

if ($overspending > 0 && $leftover > 0) {
$overspendingCopy = $overspending;
$overspending = $overspending > $leftover ? $overspending - $leftover : 0;
$leftover = $overspendingCopy >= $leftover ? 0 : $leftover - $overspendingCopy;
}


if ($leftover < 0) {
$overspending = $overspending > abs($leftover);
$leftover = 0;

}

// close current month

$details = $this->team->balanceDetail(Carbon::createFromFormat("Y-m-d", $month)->endOfMonth()->format('Y-m-d'), $this->accounts);

BudgetMonth::updateOrCreate([
'category_id' => $readyToAssignCategory->id,
'team_id' => $readyToAssignCategory->team_id,
Expand All @@ -119,9 +149,11 @@ private function moveReadyToAssign($teamId, $month, $overspending = 0, $fundedFr
'user_id' => $readyToAssignCategory->user_id,
'budgeted' => $results?->budgeted,
'activity' => $inflow,
'available' => $available,
'available' => $results?->available,
'funded_spending' => $results?->funded_spending ?? 0,
'payments' => $results?->payments ?? 0,
"accounts_balance" => collect($details)->sum('balance'),
"meta_data" => $details
]);

// set left over to the next month
Expand All @@ -132,8 +164,8 @@ private function moveReadyToAssign($teamId, $month, $overspending = 0, $fundedFr
'name' => $nextMonth,
], [
'user_id' => $readyToAssignCategory->user_id,
'left_from_last_month' => $inflow - $results?->budgeted,
'overspending_previous_month' => $available < 0 ? $available : $available,
'left_from_last_month' => $leftover,
'overspending_previous_month' => $overspending,
]);
}

Expand All @@ -157,6 +189,9 @@ private function reduceOverspent() {
}

public function startFrom($teamId, $yearMonth) {
$this->team = Team::find($teamId);
$this->accounts = Account::getByDetailTypes($teamId, AccountDetailType::ALL_CASH)->pluck('id');

$categories = Category::where([
'team_id' => $teamId,
])
Expand All @@ -171,6 +206,11 @@ public function startFrom($teamId, $yearMonth) {
->get()
->pluck('date');

$monthsWithTransactions = [
...$monthsWithTransactions,
now()->format('Y-m'),
];

$total = count($monthsWithTransactions);
$count = 0;
foreach ($monthsWithTransactions as $month) {
Expand Down
4 changes: 3 additions & 1 deletion app/Domains/Transaction/Traits/TransactionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function scopeExpenses($query)
->whereNotNull('category_id');
}

public function scopeBalance($query)
public function scopeBalance($query, $uptoDate = null, $accountIds = [])
{
$transactionsTotalSum = "ABS(sum(CASE
WHEN transactions.direction = 'WITHDRAW'
Expand All @@ -65,6 +65,8 @@ public function scopeBalance($query)
return $query->where([
'transactions.status' => 'verified',
])
->when($uptoDate, fn($q) => $q->whereRaw('transactions.date <= ?', [$uptoDate]))
->when(count($accountIds), fn($q) => $q->whereIn('transactions.account_id', [$uptoDate]))
->whereNotNull('category_id')
->selectRaw($transactionsTotalSum);
}
Expand Down
3 changes: 2 additions & 1 deletion app/Listeners/CreateBudgetMovement.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
namespace App\Listeners;

use App\Events\BudgetAssigned;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Domains\Budget\Services\BudgetCategoryService;
use App\Domains\Budget\Services\BudgetRolloverService;

class CreateBudgetMovement
class CreateBudgetMovement implements ShouldQueue
{
protected $formData;

Expand Down
36 changes: 27 additions & 9 deletions app/Models/Team.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@

namespace App\Models;

use App\Domains\AppCore\Models\Category;
use Laravel\Paddle\Billable;
use App\Domains\Meal\Models\Meal;
use App\Domains\Transaction\Models\Transaction;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Insane\Journal\Models\Core\Account;
use Illuminate\Support\Facades\DB;
use Insane\Journal\Models\Core\Payee;
use Insane\Journal\Models\Core\Account;
use App\Domains\AppCore\Models\Category;
use Spatie\Onboard\Concerns\Onboardable;
use Laravel\Jetstream\Events\TeamCreated;
use Laravel\Jetstream\Events\TeamDeleted;
use Laravel\Jetstream\Events\TeamUpdated;
use Laravel\Jetstream\Team as JetstreamTeam;
use Laravel\Paddle\Billable;
use Spatie\Onboard\Concerns\GetsOnboarded;
use Spatie\Onboard\Concerns\Onboardable;
use Laravel\Jetstream\Team as JetstreamTeam;
use App\Domains\Transaction\Models\Transaction;
use App\Domains\Transaction\Models\TransactionLine;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Team extends JetstreamTeam implements Onboardable
{
Expand Down Expand Up @@ -103,12 +105,28 @@ public function meals()
*
* @return string
*/
public function balance()
public function balance($upToDate = null, $accountIds = [])
{
return (float) Transaction::byTeam($this->id)
->verified()
->balance()
->balance($upToDate, $accountIds)
->first()
->total;
}

public function balanceDetail($upToDate = null, $accountIds = [])
{

return DB::table('transaction_lines')->where([
'transactions.status' => Transaction::STATUS_VERIFIED,
'transactions.team_id' => $this->id,
])
->when($upToDate, fn($q) => $q->whereRaw('transaction_lines.date <= ?', [$upToDate]))
->when(count($accountIds), fn($q) => $q->whereIn('transaction_lines.account_id', $accountIds))
->join('transactions', 'transactions.id', 'transaction_lines.transaction_id')
->join('accounts', 'accounts.id', 'transaction_lines.account_id')
->selectRaw("sum(amount * transaction_lines.type) as balance, transaction_lines.account_id, accounts.name")
->groupBy('accounts.id')
->get();
}
}
33 changes: 19 additions & 14 deletions app/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@

namespace App\Providers;

use App\Domains\Transaction\Listeners\UpdateOpenReconciliations;
use App\Events\AutomationEvent;
use App\Events\BudgetAssigned;
use App\Events\AutomationEvent;
use App\Events\Menu\AppCreated;
use App\Events\OccurrenceCreated;
use App\Listeners\Menu\ShowInApp;
use App\Listeners\CheckOccurrence;
use App\Listeners\AcceptInvitation;
use App\Listeners\TrashTeamSettings;
use App\Listeners\AutomationListener;
use App\Listeners\CheckOccurrence;
use App\Listeners\CreateTeamSettings;
use Illuminate\Auth\Events\Registered;
use App\Listeners\CreateBudgetCategory;
use App\Listeners\CreateBudgetMovement;
use App\Listeners\CreateBudgetTransactionMovement;
use App\Listeners\CreateOccurrenceAutomation;
use App\Listeners\CreateStartingBalance;
use App\Listeners\CreateTeamSettings;
use App\Listeners\HandleTransactionCreated;
use App\Listeners\Menu\ShowInApp;
use App\Listeners\TrashTeamSettings;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Insane\Journal\Events\AccountCreated;
use Insane\Journal\Events\AccountUpdated;
use Insane\Journal\Events\TransactionCreated;
use Insane\Journal\Listeners\CreateTeamAccounts;
use Laravel\Jetstream\Events\TeamCreated;
use Laravel\Jetstream\Events\TeamDeleted;
use App\Listeners\HandleTransactionCreated;
use App\Listeners\CreateOccurrenceAutomation;
use Insane\Journal\Events\TransactionCreated;
use Insane\Journal\Listeners\CreateTeamAccounts;
use App\Listeners\CreateBudgetTransactionMovement;
use App\Domains\Transaction\Listeners\UpdateOpenReconciliations;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
Expand All @@ -54,21 +54,26 @@ class EventServiceProvider extends ServiceProvider
AccountCreated::class => [
CreateBudgetCategory::class,
CreateStartingBalance::class,
CreateBudgetMovement::class,
],
AccountUpdated::class => [
CreateBudgetCategory::class,
CreateBudgetMovement::class,
],
TransactionCreated::class => [
CreateBudgetTransactionMovement::class,
HandleTransactionCreated::class,
CheckOccurrence::class,
UpdateOpenReconciliations::class,
CreateBudgetMovement::class,
],
TransactionUpdated::class => [
CreateBudgetTransactionMovement::class,
CreateBudgetMovement::class,
],
TransactionDeleted::class => [
CreateBudgetTransactionMovement::class,
CreateBudgetMovement::class,
],
// App events
AppCreated::class => [
Expand Down
Loading

0 comments on commit f1ec069

Please sign in to comment.