fix: Budget period not found on last day of period (#91)
## Summary - Budget periods use `date` columns (`start_date`, `end_date`) but queries compared them against `now()` which includes a time component (e.g. `2026-01-31 15:30:00`). MySQL converts the date to midnight for comparison, so `end_date >= now()` evaluates to `'2026-01-31 00:00:00' >= '2026-01-31 15:30:00'` → **false** — causing "No active period" on the last day of every period. - Replaced `now()` with `today()` (date-only) in all budget period date comparisons across `Budget::getCurrentPeriod()`, `BudgetController::index()`, and `GenerateBudgetPeriods` command. ## Test plan - [x] Added 4 Pest tests covering the edge case (last day at various times, first day at end of day, index/show endpoints) - [x] All existing budget tests pass
This commit is contained in:
parent
b4897ef425
commit
00b2ca7c55
|
|
@ -36,18 +36,18 @@ class GenerateBudgetPeriods extends Command
|
|||
}
|
||||
|
||||
$completedPeriods = BudgetPeriod::where('budget_id', $budget->id)
|
||||
->where('end_date', '<', now()->subDay())
|
||||
->where('end_date', '<', today())
|
||||
->get();
|
||||
|
||||
foreach ($completedPeriods as $period) {
|
||||
if ($period->end_date < now()->subDay()) {
|
||||
if ($period->end_date < today()) {
|
||||
$this->budgetPeriodService->closePeriod($period);
|
||||
$closedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$futurePeriods = $budget->periods()
|
||||
->where('start_date', '>', now())
|
||||
->where('start_date', '>', today())
|
||||
->count();
|
||||
|
||||
if ($futurePeriods < 2) {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ class BudgetController extends Controller
|
|||
$budgets = $user
|
||||
->budgets()
|
||||
->with(['category', 'label', 'periods' => function ($query) {
|
||||
$query->where('start_date', '<=', now())
|
||||
->where('end_date', '>=', now())
|
||||
$query->where('start_date', '<=', today())
|
||||
->where('end_date', '>=', today())
|
||||
->with(['budgetTransactions']);
|
||||
}])
|
||||
->get();
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ class Budget extends Model
|
|||
public function getCurrentPeriod(): ?BudgetPeriod
|
||||
{
|
||||
return $this->periods()
|
||||
->where('start_date', '<=', now())
|
||||
->where('end_date', '>=', now())
|
||||
->where('start_date', '<=', today())
|
||||
->where('end_date', '>=', today())
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\BudgetPeriodType;
|
||||
use App\Models\Budget;
|
||||
use App\Models\BudgetPeriod;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Laravel\Pennant\Feature;
|
||||
|
||||
test('getCurrentPeriod finds period on its last day regardless of time', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-01-31 15:30:00'));
|
||||
|
||||
$user = User::factory()->create(['onboarded_at' => now()]);
|
||||
$budget = Budget::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'period_type' => BudgetPeriodType::Monthly,
|
||||
'period_start_day' => 1,
|
||||
]);
|
||||
|
||||
BudgetPeriod::factory()->create([
|
||||
'budget_id' => $budget->id,
|
||||
'start_date' => '2026-01-01',
|
||||
'end_date' => '2026-01-31',
|
||||
'allocated_amount' => 100000,
|
||||
]);
|
||||
|
||||
$currentPeriod = $budget->getCurrentPeriod();
|
||||
|
||||
expect($currentPeriod)->not->toBeNull();
|
||||
expect($currentPeriod->start_date->toDateString())->toBe('2026-01-01');
|
||||
expect($currentPeriod->end_date->toDateString())->toBe('2026-01-31');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
test('getCurrentPeriod finds period on its first day regardless of time', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-01-01 23:59:59'));
|
||||
|
||||
$user = User::factory()->create(['onboarded_at' => now()]);
|
||||
$budget = Budget::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'period_type' => BudgetPeriodType::Monthly,
|
||||
'period_start_day' => 1,
|
||||
]);
|
||||
|
||||
BudgetPeriod::factory()->create([
|
||||
'budget_id' => $budget->id,
|
||||
'start_date' => '2026-01-01',
|
||||
'end_date' => '2026-01-31',
|
||||
'allocated_amount' => 100000,
|
||||
]);
|
||||
|
||||
$currentPeriod = $budget->getCurrentPeriod();
|
||||
|
||||
expect($currentPeriod)->not->toBeNull();
|
||||
expect($currentPeriod->start_date->toDateString())->toBe('2026-01-01');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
test('budget index loads current period on last day of period', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-01-31 18:00:00'));
|
||||
|
||||
$user = User::factory()->create(['onboarded_at' => now()]);
|
||||
Feature::for($user)->activate('budgets');
|
||||
|
||||
$budget = Budget::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'period_type' => BudgetPeriodType::Monthly,
|
||||
'period_start_day' => 1,
|
||||
]);
|
||||
|
||||
BudgetPeriod::factory()->create([
|
||||
'budget_id' => $budget->id,
|
||||
'start_date' => '2026-01-01',
|
||||
'end_date' => '2026-01-31',
|
||||
'allocated_amount' => 100000,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($user)->get('/budgets');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn ($page) => $page
|
||||
->component('budgets/index')
|
||||
->has('budgets', 1)
|
||||
->where('budgets.0.periods', fn ($periods) => count($periods) === 1)
|
||||
);
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
test('budget show finds current period on last day of period', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-01-31 20:00:00'));
|
||||
|
||||
$user = User::factory()->create(['onboarded_at' => now()]);
|
||||
Feature::for($user)->activate('budgets');
|
||||
|
||||
$budget = Budget::factory()->create([
|
||||
'user_id' => $user->id,
|
||||
'period_type' => BudgetPeriodType::Monthly,
|
||||
'period_start_day' => 1,
|
||||
]);
|
||||
|
||||
BudgetPeriod::factory()->create([
|
||||
'budget_id' => $budget->id,
|
||||
'start_date' => '2026-01-01',
|
||||
'end_date' => '2026-01-31',
|
||||
'allocated_amount' => 100000,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($user)->get("/budgets/{$budget->id}");
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertInertia(fn ($page) => $page
|
||||
->component('budgets/show')
|
||||
->has('currentPeriod')
|
||||
->where('currentPeriod.start_date', '2026-01-01T00:00:00.000000Z')
|
||||
);
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
Loading…
Reference in New Issue