From fdebc97c9a9d46685c1acbec514473ce7caf19b4 Mon Sep 17 00:00:00 2001 From: Munkherdene Date: Sun, 23 Aug 2026 02:01:13 +0800 Subject: [PATCH] feat(web): exclude buying-on-behalf pass-throughs from spending views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consume the new `passThrough` flag: both legs (the person inflow and the same-day purchase it funded) are dropped from the list, income/expense totals, the categorize queue, and the recurring estimate — same treatment as transfers and fees. --- src/api/schemas/transaction.ts | 3 ++- src/features/accounting/CategorizeReview.tsx | 1 + src/features/accounting/TransactionList.tsx | 4 +++- src/features/home/recurring.ts | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/api/schemas/transaction.ts b/src/api/schemas/transaction.ts index 00e2010..2adc403 100644 --- a/src/api/schemas/transaction.ts +++ b/src/api/schemas/transaction.ts @@ -2,5 +2,6 @@ import { z } from "zod"; export const TxnSchema = z.object({ date: z.string(), amount: z.string(), direction: z.string(), category: z.string(), title: z.string(), balanceAfter: z.string().nullish(), accountId: z.number(), transfer: z.boolean().nullish(), txnId: z.number().nullish(), note: z.string().nullish(), matchKey: z.string().nullish(), salary: z.boolean().nullish(), - fee: z.boolean().nullish() }); + fee: z.boolean().nullish(), + passThrough: z.boolean().nullish() }); export type Txn = z.infer; diff --git a/src/features/accounting/CategorizeReview.tsx b/src/features/accounting/CategorizeReview.tsx index 282ab4b..d388f4a 100644 --- a/src/features/accounting/CategorizeReview.tsx +++ b/src/features/accounting/CategorizeReview.tsx @@ -37,6 +37,7 @@ function isReviewableSpend(t: Txn): boolean { if (t.direction === "income") return false; if (t.transfer === true) return false; if (t.fee === true) return false; // bank's own fees are excluded, not categorized + if (t.passThrough === true) return false; // buying-on-behalf leg — excluded return Boolean(t.matchKey || t.title); } diff --git a/src/features/accounting/TransactionList.tsx b/src/features/accounting/TransactionList.tsx index 94af981..04ac88c 100644 --- a/src/features/accounting/TransactionList.tsx +++ b/src/features/accounting/TransactionList.tsx @@ -155,6 +155,7 @@ export function TransactionList() { for (const txn of all) { if (txn.transfer === true) continue; if (txn.fee === true) continue; // bank's own fees are excluded from totals + if (txn.passThrough === true) continue; // buying-on-behalf nets to zero if (txn.direction === "income") income += amountOf(txn); else expense += amountOf(txn); } @@ -171,6 +172,7 @@ export function TransactionList() { txn.salary !== true && txn.transfer !== true && txn.fee !== true && + txn.passThrough !== true && txn.direction !== "income" && (txn.matchKey || txn.title), ), @@ -180,7 +182,7 @@ export function TransactionList() { const visible = useMemo( () => all - .filter((txn) => txn.salary !== true && txn.fee !== true) + .filter((txn) => txn.salary !== true && txn.fee !== true && txn.passThrough !== true) .filter((txn) => selectedCategory === null || txn.category === selectedCategory) .slice() .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()), diff --git a/src/features/home/recurring.ts b/src/features/home/recurring.ts index f334a9f..bcfd419 100644 --- a/src/features/home/recurring.ts +++ b/src/features/home/recurring.ts @@ -26,7 +26,7 @@ export function detectRecurringMerchants( ): DetectedRecurring[] { const groups = new Map(); for (const t of txns) { - if (t.transfer === true || t.fee === true || t.direction === "income") continue; + if (t.transfer === true || t.fee === true || t.passThrough === true || t.direction === "income") continue; const key = t.matchKey || t.title; if (!key || knownMatchKeys.has(key)) continue; const amount = dec(t.amount);