Add effective-date transaction filtering and bulk selection

This commit is contained in:
2026-06-15 21:38:25 +02:00
parent 1c88d12f0d
commit 238a30ae0c
12 changed files with 668 additions and 28 deletions

189
convex/transactions.test.ts Normal file
View File

@@ -0,0 +1,189 @@
/// <reference types="vite/client" />
import { convexTest } from "convex-test";
import { describe, expect, test } from "vitest";
import { api } from "./_generated/api";
import type { Id } from "./_generated/dataModel";
import schema from "./schema";
const modules = import.meta.glob("./**/*.ts");
delete modules["./transactions.test.ts"];
describe("transactions.list", () => {
test("combines search, date range, account, category, and type filters", async () => {
const t = convexTest(schema, modules);
const seeded = await t.run(async (ctx) => {
const userId = await ctx.db.insert("users", {
name: "Filter User",
email: "filter@example.com",
});
const giroAccountId = await ctx.db.insert("accounts", {
userId,
name: "Girokonto",
type: "checking",
openingBalance: 0,
currency: "EUR",
isArchived: false,
});
const otherAccountId = await ctx.db.insert("accounts", {
userId,
name: "Depot",
type: "investment",
openingBalance: 0,
currency: "EUR",
isArchived: false,
});
const groceryCategoryId = await ctx.db.insert("categories", {
userId,
name: "Lebensmittel & Supermarkt",
kind: "ausgabe",
block: "variabel",
color: "#ef4444",
sortOrder: 1,
isSystem: false,
});
const restaurantCategoryId = await ctx.db.insert("categories", {
userId,
name: "Restaurant",
kind: "ausgabe",
block: "variabel",
color: "#f97316",
sortOrder: 2,
isSystem: false,
});
const matchingId = await ctx.db.insert("transactions", {
userId,
accountId: giroAccountId,
categoryId: groceryCategoryId,
bookingDate: "2026-06-15",
description: "LIDL SAGT DANKE",
amount: -18.37,
isPending: false,
effectiveMonth: "2026-06",
});
await ctx.db.insert("transactions", {
userId,
accountId: giroAccountId,
categoryId: groceryCategoryId,
bookingDate: "2026-05-29",
description: "LIDL OLD MONTH",
amount: -21.92,
isPending: false,
effectiveMonth: "2026-05",
});
await ctx.db.insert("transactions", {
userId,
accountId: otherAccountId,
categoryId: groceryCategoryId,
bookingDate: "2026-06-16",
description: "LIDL OTHER ACCOUNT",
amount: -99,
isPending: false,
effectiveMonth: "2026-06",
});
await ctx.db.insert("transactions", {
userId,
accountId: giroAccountId,
categoryId: restaurantCategoryId,
bookingDate: "2026-06-17",
description: "LIDL RESTAURANT",
amount: -12,
isPending: false,
effectiveMonth: "2026-06",
});
await ctx.db.insert("transactions", {
userId,
accountId: giroAccountId,
categoryId: groceryCategoryId,
bookingDate: "2026-06-18",
description: "LIDL REFUND",
amount: 5,
isPending: false,
effectiveMonth: "2026-06",
});
return { userId, giroAccountId, groceryCategoryId, matchingId };
});
const asUser = t.withIdentity({
subject: `${seeded.userId}|test-session`,
tokenIdentifier: `test:${seeded.userId}`,
});
const result = await asUser.query(api.transactions.list, {
paginationOpts: { cursor: null, numItems: 20 },
search: "LIDL",
from: "2026-06-01",
to: "2026-06-30",
accountId: seeded.giroAccountId as Id<"accounts">,
categoryIds: [seeded.groceryCategoryId as Id<"categories">],
type: "ausgabe",
basis: "booking",
});
expect(result.page.map((tx) => tx._id)).toEqual([seeded.matchingId]);
});
test("filters by effective month when the global basis is assignment month", async () => {
const t = convexTest(schema, modules);
const seeded = await t.run(async (ctx) => {
const userId = await ctx.db.insert("users", {
name: "Basis User",
email: "basis@example.com",
});
const accountId = await ctx.db.insert("accounts", {
userId,
name: "Girokonto",
type: "checking",
openingBalance: 0,
currency: "EUR",
isArchived: false,
});
const shiftedId = await ctx.db.insert("transactions", {
userId,
accountId,
bookingDate: "2026-05-29",
description: "Salary shifted into June",
amount: 2500,
isPending: false,
assignedMonth: "2026-06",
effectiveMonth: "2026-06",
});
await ctx.db.insert("transactions", {
userId,
accountId,
bookingDate: "2026-05-20",
description: "May only",
amount: -15,
isPending: false,
effectiveMonth: "2026-05",
});
return { userId, shiftedId };
});
const asUser = t.withIdentity({
subject: `${seeded.userId}|test-session`,
tokenIdentifier: `test:${seeded.userId}`,
});
const effectiveResult = await asUser.query(api.transactions.list, {
paginationOpts: { cursor: null, numItems: 20 },
from: "2026-06-01",
to: "2026-06-30",
basis: "effective",
});
const bookingResult = await asUser.query(api.transactions.list, {
paginationOpts: { cursor: null, numItems: 20 },
from: "2026-06-01",
to: "2026-06-30",
basis: "booking",
});
expect(effectiveResult.page.map((tx) => tx._id)).toEqual([seeded.shiftedId]);
expect(bookingResult.page).toEqual([]);
});
});