feat(web): exclude buying-on-behalf pass-throughs from spending views
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.
This commit is contained in:
parent
b4ff65deb8
commit
fdebc97c9a
4 changed files with 7 additions and 3 deletions
|
|
@ -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(),
|
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(),
|
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(),
|
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<typeof TxnSchema>;
|
export type Txn = z.infer<typeof TxnSchema>;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ function isReviewableSpend(t: Txn): boolean {
|
||||||
if (t.direction === "income") return false;
|
if (t.direction === "income") return false;
|
||||||
if (t.transfer === true) return false;
|
if (t.transfer === true) return false;
|
||||||
if (t.fee === true) return false; // bank's own fees are excluded, not categorized
|
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);
|
return Boolean(t.matchKey || t.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ export function TransactionList() {
|
||||||
for (const txn of all) {
|
for (const txn of all) {
|
||||||
if (txn.transfer === true) continue;
|
if (txn.transfer === true) continue;
|
||||||
if (txn.fee === true) continue; // bank's own fees are excluded from totals
|
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);
|
if (txn.direction === "income") income += amountOf(txn);
|
||||||
else expense += amountOf(txn);
|
else expense += amountOf(txn);
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +172,7 @@ export function TransactionList() {
|
||||||
txn.salary !== true &&
|
txn.salary !== true &&
|
||||||
txn.transfer !== true &&
|
txn.transfer !== true &&
|
||||||
txn.fee !== true &&
|
txn.fee !== true &&
|
||||||
|
txn.passThrough !== true &&
|
||||||
txn.direction !== "income" &&
|
txn.direction !== "income" &&
|
||||||
(txn.matchKey || txn.title),
|
(txn.matchKey || txn.title),
|
||||||
),
|
),
|
||||||
|
|
@ -180,7 +182,7 @@ export function TransactionList() {
|
||||||
const visible = useMemo(
|
const visible = useMemo(
|
||||||
() =>
|
() =>
|
||||||
all
|
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)
|
.filter((txn) => selectedCategory === null || txn.category === selectedCategory)
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()),
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()),
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export function detectRecurringMerchants(
|
||||||
): DetectedRecurring[] {
|
): DetectedRecurring[] {
|
||||||
const groups = new Map<string, { label: string; count: number; total: number }>();
|
const groups = new Map<string, { label: string; count: number; total: number }>();
|
||||||
for (const t of txns) {
|
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;
|
const key = t.matchKey || t.title;
|
||||||
if (!key || knownMatchKeys.has(key)) continue;
|
if (!key || knownMatchKeys.has(key)) continue;
|
||||||
const amount = dec(t.amount);
|
const amount = dec(t.amount);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue