feat(categorize): show debtor and creditor names (#454)

## What
Show the debtor and creditor names (when present) on the categorization
page (`/transactions/categorize`).

## Changes
- `TransactionController@categorize`: select `creditor_name` and
`debtor_name` — they were never sent to the frontend.
- `categorizer-card.tsx`: render Creditor/Debtor rows below the account
when present, using the same i18n labels as the transaction columns.
- `TransactionTest`: assert both names are exposed on the categorize
page.

## Testing
- `php artisan test --filter="debtor and creditor"` — passes.
- pint, format, lint clean.
This commit is contained in:
Víctor Falcón 2026-05-30 12:27:59 +02:00 committed by GitHub
parent 4dec0ab7ca
commit 05ee8dc442
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 55 additions and 1 deletions

View File

@ -145,7 +145,7 @@ class TransactionController extends Controller
->with(['account.bank:id,name,logo', 'labels:id,name,color'])
->orderBy('transaction_date', 'desc')
->orderBy('id', 'desc')
->get(['id', 'account_id', 'category_id', 'description', 'description_iv', 'transaction_date', 'amount', 'currency_code', 'notes', 'notes_iv']);
->get(['id', 'account_id', 'category_id', 'description', 'description_iv', 'transaction_date', 'amount', 'currency_code', 'notes', 'notes_iv', 'creditor_name', 'debtor_name']);
return Inertia::render('transactions/categorize', [
'categories' => $categories,

View File

@ -7,6 +7,7 @@ import { cn } from '@/lib/utils';
import { type Category, getCategoryColorClasses } from '@/types/category';
import { type DecryptedTransaction } from '@/types/transaction';
import { formatDateLong } from '@/utils/date';
import { __ } from '@/utils/i18n';
import { CheckCircle2 } from 'lucide-react';
interface CategorizerCardProps {
@ -92,6 +93,36 @@ export function CategorizerCard({
/>
</div>
)}
{(transaction.creditor_name ||
transaction.debtor_name) && (
<dl className="flex flex-col gap-1 text-sm">
{transaction.creditor_name && (
<div className="flex items-center gap-2">
<dt className="text-muted-foreground">
{__('Creditor')}
</dt>
<dd className="text-zinc-700 dark:text-zinc-300">
{
transaction.creditor_name
}
</dd>
</div>
)}
{transaction.debtor_name && (
<div className="flex items-center gap-2">
<dt className="text-muted-foreground">
{__('Debtor')}
</dt>
<dd className="text-zinc-700 dark:text-zinc-300">
{
transaction.debtor_name
}
</dd>
</div>
)}
</dl>
)}
</div>
</div>

View File

@ -720,6 +720,29 @@ test('categorize page only returns uncategorized transactions', function () {
);
});
test('categorize page exposes debtor and creditor names', function () {
$user = User::factory()->onboarded()->create();
$account = Account::factory()->create(['user_id' => $user->id]);
$transaction = Transaction::factory()->create([
'user_id' => $user->id,
'account_id' => $account->id,
'category_id' => null,
'creditor_name' => 'Acme Corp',
'debtor_name' => 'Jane Doe',
]);
$response = actingAs($user)->get(route('transactions.categorize'));
$response->assertSuccessful();
$response->assertInertia(fn ($page) => $page
->component('transactions/categorize')
->where('transactions.0.id', $transaction->id)
->where('transactions.0.creditor_name', 'Acme Corp')
->where('transactions.0.debtor_name', 'Jane Doe')
);
});
test('categorize page does not return transactions from deleted accounts', function () {
$user = User::factory()->onboarded()->create();