Track who paid each depense, with a "Commun" (shared) fallback
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
5ef655d16f
commit
9114bb2e8d
@@ -123,6 +123,34 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if ($members->isNotEmpty())
|
||||
<fieldset>
|
||||
<legend class="text-sm font-medium text-text-muted mb-2">Payé par</legend>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
wire:click="$set('paidByUserId', '')"
|
||||
aria-pressed="{{ $paidByUserId === '' ? 'true' : 'false' }}"
|
||||
class="min-h-[44px] px-3 py-2 rounded-lg border text-sm font-medium transition focus:outline-none focus-visible:ring-2 ring-brand
|
||||
{{ $paidByUserId === '' ? 'bg-brand text-white border-brand' : 'bg-surface border-border-strong text-text' }}"
|
||||
>
|
||||
Commun
|
||||
</button>
|
||||
@foreach ($members as $member)
|
||||
<button
|
||||
type="button"
|
||||
wire:click="$set('paidByUserId', '{{ $member->id }}')"
|
||||
aria-pressed="{{ (string) $paidByUserId === (string) $member->id ? 'true' : 'false' }}"
|
||||
class="min-h-[44px] px-3 py-2 rounded-lg border text-sm font-medium transition focus:outline-none focus-visible:ring-2 ring-brand
|
||||
{{ (string) $paidByUserId === (string) $member->id ? 'bg-brand text-white border-brand' : 'bg-surface border-border-strong text-text' }}"
|
||||
>
|
||||
{{ $member->name }}
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</fieldset>
|
||||
@endif
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full py-4 rounded-lg bg-brand text-white text-lg font-semibold focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-offset-2 focus-visible:ring-offset-surface"
|
||||
@@ -168,6 +196,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($paidBySummary->isNotEmpty())
|
||||
<div class="bg-surface shadow rounded-lg p-4">
|
||||
<p class="text-xs text-text-muted mb-2">Dépenses ce mois-ci, par personne</p>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
@foreach ($paidBySummary as $entry)
|
||||
<div>
|
||||
<p class="text-xs text-text-muted">{{ $entry['label'] }}</p>
|
||||
<p class="text-sm font-semibold text-negative">{{ number_format($entry['total'], 2, ',', ' ') }} €</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($pendingRecurrences->isNotEmpty())
|
||||
<div class="bg-surface shadow rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-2 bg-brand">
|
||||
@@ -241,6 +283,15 @@
|
||||
<x-input-label for="dateEffective" value="Date effective" />
|
||||
<x-text-input wire:model="dateEffective" id="dateEffective" type="date" class="block mt-1 w-full text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<x-input-label for="paidByUserId" value="Payé par" />
|
||||
<select wire:model="paidByUserId" id="paidByUserId" class="block mt-1 w-full rounded-md bg-surface border-border-strong text-text text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
|
||||
<option value="">Commun</option>
|
||||
@foreach ($members as $member)
|
||||
<option value="{{ $member->id }}">{{ $member->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-6">
|
||||
<x-primary-button>Ajouter</x-primary-button>
|
||||
</div>
|
||||
@@ -256,6 +307,17 @@
|
||||
$amountPrefix = fn ($t) => $t->category->type->value === 'depense' ? '−' : '+';
|
||||
@endphp
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<label for="filterPaidBy" class="text-xs text-text-muted">Payé par</label>
|
||||
<select wire:model.live="filterPaidBy" id="filterPaidBy" class="rounded-md bg-surface border-border-strong text-text text-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
|
||||
<option value="">Tout le monde</option>
|
||||
<option value="common">Commun</option>
|
||||
@foreach ($members as $member)
|
||||
<option value="{{ $member->id }}">{{ $member->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface shadow rounded-lg overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
@@ -267,6 +329,7 @@
|
||||
<th scope="col" class="text-left px-4 py-2">Catégorie</th>
|
||||
<th scope="col" class="text-right px-4 py-2">Montant</th>
|
||||
<th scope="col" class="text-left px-4 py-2">Détails</th>
|
||||
<th scope="col" class="text-left px-4 py-2">Payé par</th>
|
||||
<th scope="col" class="text-right px-4 py-2">Balance</th>
|
||||
<th scope="col" class="text-left px-4 py-2">Date effective</th>
|
||||
<th scope="col" class="px-4 py-2"><span class="sr-only">Actions</span></th>
|
||||
@@ -282,6 +345,7 @@
|
||||
{{ $amountPrefix($transaction) }}{{ number_format($transaction->amount, 2, ',', ' ') }} €
|
||||
</td>
|
||||
<td class="px-4 py-2 text-text-muted">{{ $transaction->details }}</td>
|
||||
<td class="px-4 py-2 text-text-muted">{{ $transaction->paidBy?->name ?? 'Commun' }}</td>
|
||||
<td class="px-4 py-2 text-right font-semibold {{ ($running[$transaction->id] ?? 0) < 0 ? 'text-negative' : 'text-text' }}">
|
||||
{{ number_format($running[$transaction->id] ?? 0, 2, ',', ' ') }} €
|
||||
</td>
|
||||
@@ -293,7 +357,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="8" class="px-4 py-3 text-sm text-text-muted">Aucun enregistrement.</td></tr>
|
||||
<tr><td colspan="9" class="px-4 py-3 text-sm text-text-muted">Aucun enregistrement.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user