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:
jeremy bayse
2026-08-01 08:24:14 +02:00
co-authored by Claude Sonnet 5
parent 5ef655d16f
commit 9114bb2e8d
10 changed files with 414 additions and 19 deletions
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('transactions', function (Blueprint $table) {
// Null = dépense commune, non attribuée à un membre en particulier.
$table->foreignId('paid_by_user_id')->nullable()->after('user_id')->constrained('users')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('transactions', function (Blueprint $table) {
$table->dropConstrainedForeignId('paid_by_user_id');
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('recurring_transactions', function (Blueprint $table) {
$table->foreignId('paid_by_user_id')->nullable()->after('category_id')->constrained('users')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('recurring_transactions', function (Blueprint $table) {
$table->dropConstrainedForeignId('paid_by_user_id');
});
}
};