Adds a "payé par" field to transactions and recurring transactions: either a household member or null (shared/common expense). No per- member account balances — this is purely a "who paid" label, the household's overall balance is unchanged and unaffected by any filtering. - paid_by_user_id nullable FK on transactions and recurring_transactions - Suivi: "payé par" selector on both entry forms (desktop dropdown, mobile buttons), a filter above the transaction table, a "payé par" column, and a summary tile showing this month's depenses grouped by payer (including Commun) - Recurring transactions carry paid_by_user_id onto the transaction they generate when confirmed - Running balance is computed from all transactions regardless of the payer filter, so it never gets skewed by the active filter 11 new Pest tests (assignment, common fallback, non-member rejection, filter isolation from the balance calc, per-payer summary, recurring propagation). 69/69 total pass, Pint clean, verified end-to-end on production data (desktop + mobile, test row created and removed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
113 lines
4.3 KiB
PHP
113 lines
4.3 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Recurring\Index;
|
|
use App\Models\RecurringTransaction;
|
|
use App\Models\User;
|
|
use Livewire\Livewire;
|
|
|
|
it('creates a recurring transaction for the current household', function (): void {
|
|
$user = User::factory()->create();
|
|
$category = $user->currentHousehold->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]);
|
|
|
|
Livewire::actingAs($user)
|
|
->test(Index::class)
|
|
->set('name', 'Loyer')
|
|
->set('categoryId', $category->id)
|
|
->set('amount', '1200')
|
|
->set('dayOfMonth', '5')
|
|
->call('add')
|
|
->assertHasNoErrors();
|
|
|
|
$recurring = $user->currentHousehold->recurringTransactions()->first();
|
|
|
|
expect($recurring)->not->toBeNull();
|
|
expect($recurring->name)->toBe('Loyer');
|
|
expect((float) $recurring->amount)->toBe(1200.0);
|
|
expect($recurring->day_of_month)->toBe(5);
|
|
expect($recurring->active)->toBeTrue();
|
|
});
|
|
|
|
it('prevents editing a recurring transaction from another household', function (): void {
|
|
$owner = User::factory()->create();
|
|
$intruder = User::factory()->create();
|
|
|
|
$category = $owner->currentHousehold->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]);
|
|
$recurring = $owner->currentHousehold->recurringTransactions()->create([
|
|
'category_id' => $category->id, 'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5,
|
|
]);
|
|
|
|
Livewire::actingAs($intruder)
|
|
->test(Index::class)
|
|
->call('startEdit', $recurring->id)
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('toggles active state', function (): void {
|
|
$user = User::factory()->create();
|
|
$category = $user->currentHousehold->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]);
|
|
$recurring = $user->currentHousehold->recurringTransactions()->create([
|
|
'category_id' => $category->id, 'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5,
|
|
]);
|
|
|
|
Livewire::actingAs($user)->test(Index::class)->call('toggleActive', $recurring->id);
|
|
|
|
expect($recurring->fresh()->active)->toBeFalse();
|
|
});
|
|
|
|
it('deletes a recurring transaction', function (): void {
|
|
$user = User::factory()->create();
|
|
$category = $user->currentHousehold->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]);
|
|
$recurring = $user->currentHousehold->recurringTransactions()->create([
|
|
'category_id' => $category->id, 'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5,
|
|
]);
|
|
|
|
Livewire::actingAs($user)->test(Index::class)->call('delete', $recurring->id);
|
|
|
|
expect(RecurringTransaction::find($recurring->id))->toBeNull();
|
|
});
|
|
|
|
it('assigns a recurring transaction to a household member', function (): void {
|
|
$owner = User::factory()->create();
|
|
$member = User::factory()->create();
|
|
$household = $owner->currentHousehold;
|
|
$member->households()->attach($household->id, ['role' => 'member']);
|
|
|
|
$category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]);
|
|
|
|
Livewire::actingAs($owner)
|
|
->test(Index::class)
|
|
->set('name', 'Loyer')
|
|
->set('categoryId', $category->id)
|
|
->set('amount', '1200')
|
|
->set('dayOfMonth', '5')
|
|
->set('paidByUserId', (string) $member->id)
|
|
->call('add');
|
|
|
|
$recurring = $household->recurringTransactions()->first();
|
|
|
|
expect($recurring->paid_by_user_id)->toBe($member->id);
|
|
});
|
|
|
|
it('carries the recurring paid-by user onto the generated transaction', function (): void {
|
|
$this->travelTo(now()->setDate(2026, 3, 10));
|
|
|
|
$owner = User::factory()->create();
|
|
$member = User::factory()->create();
|
|
$household = $owner->currentHousehold;
|
|
$member->households()->attach($household->id, ['role' => 'member']);
|
|
|
|
$category = $household->categories()->create(['name' => 'Loyer', 'type' => 'depense', 'position' => 1]);
|
|
$recurring = $household->recurringTransactions()->create([
|
|
'category_id' => $category->id, 'paid_by_user_id' => $member->id,
|
|
'name' => 'Loyer', 'amount' => 1200, 'day_of_month' => 5,
|
|
]);
|
|
|
|
Livewire::actingAs($owner)
|
|
->test(App\Livewire\Suivi\Index::class)
|
|
->call('confirmRecurrence', $recurring->id, '1200');
|
|
|
|
$transaction = $household->transactions()->where('recurring_transaction_id', $recurring->id)->first();
|
|
|
|
expect($transaction->paid_by_user_id)->toBe($member->id);
|
|
});
|