diff --git a/app/Livewire/Suivi/Index.php b/app/Livewire/Suivi/Index.php index 9c64f80..8e13c1b 100644 --- a/app/Livewire/Suivi/Index.php +++ b/app/Livewire/Suivi/Index.php @@ -6,6 +6,7 @@ namespace App\Livewire\Suivi; use App\Enums\CategoryType; use App\Models\BudgetLine; +use App\Models\Category; use App\Models\RecurringTransaction; use App\Models\Transaction; use Livewire\Attributes\Layout; @@ -108,6 +109,32 @@ class Index extends Component ]); } + /** + * Budget planifié pour cette catégorie sur le mois en cours, en résolvant + * l'override du mois s'il existe sinon le montant par défaut de l'année. + */ + private function budgetForCurrentMonth(Category $category, int $year, int $month): ?float + { + $lines = $category->budgetLines()->where('year', $year)->get(); + + $override = $lines->firstWhere('month', $month); + if ($override) { + return (float) $override->amount; + } + + $default = $lines->firstWhere('month', null); + + return $default ? (float) $default->amount : null; + } + + private function trackedForCategoryThisMonth(Category $category, int $year, int $month): float + { + return (float) $category->transactions() + ->whereYear('date_effective', $year) + ->whereMonth('date_effective', $month) + ->sum('amount'); + } + public function render() { $household = auth()->user()->currentHousehold; @@ -159,10 +186,27 @@ class Index extends Component ->sortBy('day_of_month') ->values(); + $categories = $household->categories()->orderBy('type')->orderBy('position')->get(); + + $categoryProgress = $categories + ->whereIn('type', [CategoryType::Depense, CategoryType::Epargne]) + ->mapWithKeys(function (Category $category) use ($year, $month) { + $budget = $this->budgetForCurrentMonth($category, $year, $month); + + if ($budget === null || $budget <= 0) { + return [$category->id => null]; + } + + $tracked = $this->trackedForCategoryThisMonth($category, $year, $month); + + return [$category->id => min(100, round($tracked / $budget * 100))]; + }); + return view('livewire.suivi.index', [ 'transactions' => $transactions, 'running' => $running, - 'categories' => $household->categories()->orderBy('type')->orderBy('position')->get(), + 'categories' => $categories, + 'categoryProgress' => $categoryProgress, 'nbrThisYear' => $transactions->filter(fn (Transaction $t) => $t->date->year === $year)->count(), 'nbrTotal' => $transactions->count(), 'lastEntry' => $transactions->first(), diff --git a/resources/views/livewire/suivi/index.blade.php b/resources/views/livewire/suivi/index.blade.php index a90c01d..d52e62b 100644 --- a/resources/views/livewire/suivi/index.blade.php +++ b/resources/views/livewire/suivi/index.blade.php @@ -55,14 +55,30 @@

{{ $meta['label'] }}

@foreach ($typeCategories as $category) + @php + $isActive = (string) $categoryId === (string) $category->id; + $progress = $categoryProgress[$category->id] ?? null; + @endphp @endforeach
diff --git a/tests/Feature/SuiviTest.php b/tests/Feature/SuiviTest.php index 302d5c1..000b44f 100644 --- a/tests/Feature/SuiviTest.php +++ b/tests/Feature/SuiviTest.php @@ -184,6 +184,54 @@ it('shows the recurrence again the following month after being confirmed', funct expect($component->viewData('pendingRecurrences')->pluck('id'))->toContain($recurring->id); }); +it('computes the percentage of budget already tracked for a category this month', function (): void { + $this->travelTo(now()->setDate(2026, 3, 10)); + + $user = User::factory()->create(); + $household = $user->currentHousehold; + $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); + + $category->budgetLines()->create(['household_id' => $household->id, 'year' => 2026, 'month' => null, 'amount' => 1000]); + $household->transactions()->create([ + 'category_id' => $category->id, 'user_id' => $user->id, + 'date' => '2026-03-05', 'date_effective' => '2026-03-05', 'amount' => 600, + ]); + + $component = Livewire::actingAs($user)->test(Index::class); + + expect($component->viewData('categoryProgress')[$category->id])->toBe(60.0); +}); + +it('caps the category progress percentage at 100', function (): void { + $this->travelTo(now()->setDate(2026, 3, 10)); + + $user = User::factory()->create(); + $household = $user->currentHousehold; + $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); + + $category->budgetLines()->create(['household_id' => $household->id, 'year' => 2026, 'month' => null, 'amount' => 1000]); + $household->transactions()->create([ + 'category_id' => $category->id, 'user_id' => $user->id, + 'date' => '2026-03-05', 'date_effective' => '2026-03-05', 'amount' => 1500, + ]); + + $component = Livewire::actingAs($user)->test(Index::class); + + expect($component->viewData('categoryProgress')[$category->id])->toBe(100); +}); + +it('returns no progress for a category without a budget defined', function (): void { + $this->travelTo(now()->setDate(2026, 3, 10)); + + $user = User::factory()->create(); + $household = $user->currentHousehold; + $category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]); + + $component = Livewire::actingAs($user)->test(Index::class); + + expect($component->viewData('categoryProgress')[$category->id])->toBeNull(); +}); + it('does not show another household transactions', function (): void { $ownerA = User::factory()->create(); $ownerB = User::factory()->create();