From b8cd1b0f89efa1606d8162b8f07c7a3ca0dc3198 Mon Sep 17 00:00:00 2001 From: siddharthd Date: Mon, 11 May 2026 19:15:41 +1000 Subject: [PATCH] fix(reconcile): prevent split/tag double-counting on reconciled transactions Move splits, tags and overrides from manual to statement side on reconcile (delete from manual after copying) instead of just copying. Add read-time filter to exclude reconciled manual transactions from balance and shared transaction queries. Also adds participant filter to shared expenses page. --- src/app/api/shared-transactions/route.ts | 4 +++- src/app/api/transactions/reconcile/route.ts | 7 +++++-- src/app/shared/page.tsx | 17 +++++++++++++++-- src/lib/hooks.ts | 11 +++++++---- src/lib/queries.ts | 12 +++++++++++- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/app/api/shared-transactions/route.ts b/src/app/api/shared-transactions/route.ts index 493ae81..5a951b2 100644 --- a/src/app/api/shared-transactions/route.ts +++ b/src/app/api/shared-transactions/route.ts @@ -10,6 +10,8 @@ export async function GET(req: NextRequest) { const rawIds = tagParam ? tagParam.split(",").filter(Boolean) : []; const noTags = rawIds.includes("untagged"); const tagIds = rawIds.filter((id) => id !== "untagged").map(Number).filter((n) => !isNaN(n)); - const transactions = await getSharedTransactions(user.id, tagIds.length ? tagIds : undefined, noTags); + const participantParam = req.nextUrl.searchParams.get("participant_id"); + const participantId = participantParam ? Number(participantParam) : undefined; + const transactions = await getSharedTransactions(user.id, tagIds.length ? tagIds : undefined, noTags, participantId); return NextResponse.json(transactions); } diff --git a/src/app/api/transactions/reconcile/route.ts b/src/app/api/transactions/reconcile/route.ts index 529c35c..e920ae1 100644 --- a/src/app/api/transactions/reconcile/route.ts +++ b/src/app/api/transactions/reconcile/route.ts @@ -50,18 +50,20 @@ export async function POST(req: NextRequest) { my_share_percent: override.my_share_percent, }, }); + await tx.transaction_overrides.deleteMany({ where: { transaction_id: manual_id } }); } - // Copy tags: manual → statement tx + // Move tags: manual → statement tx const tags = await tx.transaction_tags.findMany({ where: { transaction_id: manual_id } }); if (tags.length) { await tx.transaction_tags.createMany({ data: tags.map((t) => ({ transaction_id: statement_tx_id, tag_id: t.tag_id })), skipDuplicates: true, }); + await tx.transaction_tags.deleteMany({ where: { transaction_id: manual_id } }); } - // Copy splits: manual → statement tx + // Move splits: manual → statement tx const splits = await tx.transaction_splits.findMany({ where: { transaction_id: manual_id } }); if (splits.length) { await tx.transaction_splits.createMany({ @@ -72,6 +74,7 @@ export async function POST(req: NextRequest) { })), skipDuplicates: true, }); + await tx.transaction_splits.deleteMany({ where: { transaction_id: manual_id } }); } // Mark manual tx as reconciled (link to statement tx) diff --git a/src/app/shared/page.tsx b/src/app/shared/page.tsx index 2c6eb4b..891894a 100644 --- a/src/app/shared/page.tsx +++ b/src/app/shared/page.tsx @@ -4,6 +4,7 @@ import { useState, useRef, useEffect } from "react"; import { useSharedTransactions, useParticipantBalances, + useParticipants, useCreateParticipant, useRecordPayment, usePaymentHistory, @@ -274,10 +275,12 @@ type SortCol = "transaction_date" | "created_at" | "amount"; export default function SharedPage() { const [tagIds, setTagIds] = useState([]); + const [participantId, setParticipantId] = useState(undefined); const [sortCol, setSortCol] = useState("transaction_date"); const [sortDir, setSortDir] = useState<"asc" | "desc">("desc"); const realTagIds = tagIds.filter((id) => id !== "untagged"); - const { data: rawTransactions = [], isLoading: txLoading } = useSharedTransactions(tagIds); + const { data: participants = [] } = useParticipants(); + const { data: rawTransactions = [], isLoading: txLoading } = useSharedTransactions(tagIds, participantId); const transactions = [...rawTransactions].sort((a, b) => { const av = sortCol === "amount" ? Number(a.amount) : new Date(a[sortCol]).getTime(); @@ -305,7 +308,17 @@ export default function SharedPage() {

Shared Expenses

-
+
+ {!addingParticipant && (