fix(shared): tag filter SQL precedence, balance cards filter by tag

This commit is contained in:
2026-03-14 21:30:33 +11:00
parent 02ac136e19
commit d53d3106f2
4 changed files with 27 additions and 14 deletions
+4 -3
View File
@@ -198,11 +198,12 @@ export function useParticipants() {
});
}
export function useParticipantBalances() {
export function useParticipantBalances(tagIds?: string[]) {
return useQuery<{ id: number; name: string; total_owed: number; unsettled_count: number }[]>({
queryKey: ["participant-balances"],
queryKey: ["participant-balances", tagIds],
queryFn: async () => {
const res = await fetch("/api/participants/balances");
const params = tagIds?.length ? `?tag_ids=${tagIds.join(",")}` : "";
const res = await fetch(`/api/participants/balances${params}`);
return res.json();
},
});
+19 -9
View File
@@ -273,7 +273,14 @@ export interface ParticipantBalance {
unsettled_count: number;
}
export async function getParticipantBalances(ownerId: number) {
export async function getParticipantBalances(ownerId: number, tagIds?: number[]) {
const params: unknown[] = [ownerId];
let tagFilter = "";
if (tagIds?.length) {
params.push(tagIds);
tagFilter = `AND EXISTS (SELECT 1 FROM transaction_tags tt WHERE tt.transaction_id = t.id AND tt.tag_id = ANY($2::int[]))`;
}
return queryRaw<ParticipantBalance>(`
SELECT p.id, p.name,
COALESCE(SUM(splits.signed_amount), 0)::numeric(12,2)
@@ -281,7 +288,6 @@ export async function getParticipantBalances(ownerId: number) {
COALESCE(SUM(splits.split_count), 0)::int AS unsettled_count
FROM participants p
-- All split obligations (settled flag ignored — payments are the source of truth)
LEFT JOIN (
-- They owe me: their splits on transactions I own
SELECT ts.participant_id AS pid,
@@ -291,6 +297,7 @@ export async function getParticipantBalances(ownerId: number) {
JOIN transactions t ON t.id = ts.transaction_id
LEFT JOIN statements s ON s.id = t.statement_id
WHERE COALESCE(t.owner_id, s.owner_id) = $1 AND ts.participant_id != $1
${tagFilter}
UNION ALL
@@ -302,9 +309,10 @@ export async function getParticipantBalances(ownerId: number) {
JOIN transactions t ON t.id = ts.transaction_id
LEFT JOIN statements s ON s.id = t.statement_id
WHERE ts.participant_id = $1 AND COALESCE(t.owner_id, s.owner_id) != $1
${tagFilter}
) splits ON splits.pid = p.id
-- Net payments: positive = they paid me, negative = I paid them
-- Net payments always unfiltered (payments are against total debt, not per-tag)
LEFT JOIN (
SELECT
CASE WHEN sp.from_participant_id != $1 THEN sp.from_participant_id ELSE sp.to_participant_id END AS pid,
@@ -317,7 +325,7 @@ export async function getParticipantBalances(ownerId: number) {
WHERE p.id != $1
GROUP BY p.id, p.name, payments.net_paid
ORDER BY p.name
`, [ownerId]);
`, params);
}
export interface SharedTransactionRow extends TransactionRow {
@@ -365,11 +373,13 @@ export async function getSharedTransactions(ownerId: number, tagIds?: number[])
LEFT JOIN statements s ON s.id = t.statement_id
LEFT JOIN participants p_owner ON p_owner.id = COALESCE(t.owner_id, s.owner_id)
WHERE (
COALESCE(t.owner_id, s.owner_id) = $1
AND EXISTS (SELECT 1 FROM transaction_splits ts2 WHERE ts2.transaction_id = t.id AND ts2.participant_id != $1)
) OR (
COALESCE(t.owner_id, s.owner_id) != $1
AND EXISTS (SELECT 1 FROM transaction_splits ts_me WHERE ts_me.transaction_id = t.id AND ts_me.participant_id = $1)
(
COALESCE(t.owner_id, s.owner_id) = $1
AND EXISTS (SELECT 1 FROM transaction_splits ts2 WHERE ts2.transaction_id = t.id AND ts2.participant_id != $1)
) OR (
COALESCE(t.owner_id, s.owner_id) != $1
AND EXISTS (SELECT 1 FROM transaction_splits ts_me WHERE ts_me.transaction_id = t.id AND ts_me.participant_id = $1)
)
)
${tagClause}
GROUP BY t.id, o.category_override, o.merchant_normalized, o.notes, s.bank_name, s.owner_id, p_owner.name