feat(transactions): manual transaction support and multi-owner query infrastructure
- Add POST /api/transactions to create manual transactions (statement_id=NULL, owner_id set directly) - Queries switch from JOIN to LEFT JOIN statements so manual transactions are visible - COALESCE(t.owner_id, s.owner_id) throughout for owner resolution - Add "Manual" bank filter option in getTransactions - Search extended to include merchant_normalized override - Split data fetched via lateral subquery on every transaction row - getParticipantBalances rewritten as UNION for bidirectional net balances (credits/refunds negate, split from either side of the relationship) - getSharedTransactions: remove my_share_percent from SELECT (fixes GROUP BY error), WHERE rewritten as two distinct cases (owner with others split vs participant on others' txn) - getTransactions: OR EXISTS condition so split participants see shared transactions - add-transaction-modal component for creating manual transactions with splits - 0008_my_share_percent migration adds my_share_percent to transaction_overrides
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getCurrentUser } from "@/lib/auth";
|
||||
import { getTransactions } from "@/lib/queries";
|
||||
import { queryRaw } from "@/lib/db";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const user = await getCurrentUser(req);
|
||||
@@ -23,3 +24,54 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
return NextResponse.json(result);
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const user = await getCurrentUser(req);
|
||||
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
|
||||
|
||||
const body = await req.json() as {
|
||||
date: string;
|
||||
description: string;
|
||||
amount: number;
|
||||
transaction_type?: string;
|
||||
merchant_normalized?: string;
|
||||
category?: string;
|
||||
splits?: { participant_id: number; share_percent: number }[];
|
||||
};
|
||||
|
||||
if (!body.date || !body.description || body.amount == null) {
|
||||
return NextResponse.json({ error: "date, description, amount are required" }, { status: 400 });
|
||||
}
|
||||
|
||||
// Insert manual transaction with no statement (statement_id = NULL, owner_id set directly)
|
||||
const txRows = await queryRaw<{ id: number }>(
|
||||
`INSERT INTO transactions (statement_id, owner_id, transaction_date, description, amount, transaction_type, merchant_normalized, category, row_index)
|
||||
VALUES (NULL, $1, $2, $3, $4, $5, $6, $7, (
|
||||
SELECT COALESCE(MAX(row_index), -1) + 1 FROM transactions WHERE owner_id = $1 AND statement_id IS NULL
|
||||
))
|
||||
RETURNING id`,
|
||||
[
|
||||
user.id,
|
||||
body.date,
|
||||
body.description,
|
||||
body.amount,
|
||||
body.transaction_type || "debit",
|
||||
body.merchant_normalized || null,
|
||||
body.category || null,
|
||||
]
|
||||
);
|
||||
const transactionId = txRows[0].id;
|
||||
|
||||
// Insert splits if provided
|
||||
if (body.splits?.length) {
|
||||
for (const s of body.splits) {
|
||||
await queryRaw(
|
||||
`INSERT INTO transaction_splits (transaction_id, participant_id, share_percent)
|
||||
VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`,
|
||||
[transactionId, s.participant_id, s.share_percent]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ id: transactionId }, { status: 201 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user