fix(transactions): editable transaction type, fee/interest counted as spend, fees category

- TypeBadge is now clickable — opens inline select to change debit/credit/fee/interest/etc.
- PATCH /api/transactions/[id] now accepts transaction_type, updates transactions table directly
- Analytics monthly query includes fee and interest types as spend (not just debit)
- fee and interest amounts show red in transaction list (same as debit)
- Add fees category to taxonomy
This commit is contained in:
2026-03-10 00:24:42 +11:00
parent 714c5a9b25
commit dd11019fdf
5 changed files with 74 additions and 14 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ export async function GET(req: NextRequest) {
LEFT JOIN transaction_splits ts ON ts.transaction_id = t.id AND ts.participant_id = $1
JOIN statements s ON s.id = t.statement_id
WHERE s.owner_id = $1
AND t.transaction_type = 'debit'
AND t.transaction_type IN ('debit', 'fee', 'interest')
AND COALESCE(o.category_override, t.category) NOT IN ('transfers', 'investment')
AND t.transaction_date >= $2
AND t.transaction_date < $3
+22 -1
View File
@@ -1,6 +1,9 @@
import { NextRequest, NextResponse } from "next/server";
import { getTransactionById } from "@/lib/queries";
import { prisma } from "@/lib/db";
import { queryRaw } from "@/lib/db";
const VALID_TYPES = ["debit", "credit", "payment", "refund", "fee", "interest", "transfer"];
export async function GET(
_req: NextRequest,
@@ -20,12 +23,30 @@ export async function PATCH(
const transactionId = Number(id);
const body = await req.json();
const { category, merchant_normalized, notes } = body as {
const { category, merchant_normalized, notes, transaction_type } = body as {
category?: string;
merchant_normalized?: string;
notes?: string;
transaction_type?: string;
};
// transaction_type is a direct correction on the transactions table
if (transaction_type !== undefined) {
if (!VALID_TYPES.includes(transaction_type)) {
return NextResponse.json({ error: "Invalid transaction_type" }, { status: 400 });
}
await queryRaw(
`UPDATE transactions SET transaction_type = $1 WHERE id = $2`,
[transaction_type, transactionId]
);
}
// category/merchant/notes go through the overrides table
const hasOverride = category !== undefined || merchant_normalized !== undefined || notes !== undefined;
if (!hasOverride) {
return NextResponse.json({ ok: true });
}
const data: Record<string, unknown> = { updated_at: new Date() };
if (category !== undefined) data.category_override = category;
if (merchant_normalized !== undefined) data.merchant_normalized = merchant_normalized;