Add savings chat analysis feature

This commit is contained in:
Matthias
2026-06-15 18:26:25 +02:00
parent d65e7681ac
commit 4869402d45
26 changed files with 2789 additions and 163 deletions

View File

@@ -39,6 +39,7 @@ export const list = query({
from: v.optional(v.string()),
to: v.optional(v.string()),
categoryIds: v.optional(v.array(v.id("categories"))),
withoutCategory: v.optional(v.boolean()),
accountId: v.optional(v.id("accounts")),
type: v.optional(v.union(v.literal("einnahme"), v.literal("ausgabe"))),
pendingOnly: v.optional(v.boolean()),
@@ -50,48 +51,57 @@ export const list = query({
}),
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
let q = ctx.db
.query("transactions")
.withIndex("by_user_booking", (q) => q.eq("userId", userId))
.order("desc");
const result = await q.paginate(args.paginationOpts);
let page = result.page;
if (args.from) {
page = page.filter((tx) => !tx.bookingDate || tx.bookingDate >= args.from!);
let q;
if (args.search) {
q = ctx.db
.query("transactions")
.withSearchIndex("search_description", (sq) =>
sq.search("description", args.search!).eq("userId", userId),
);
} else {
q = ctx.db
.query("transactions")
.withIndex("by_user_booking", (iq) => {
if (args.from && args.to) {
return iq.eq("userId", userId).gte("bookingDate", args.from).lte("bookingDate", args.to);
}
if (args.from) {
return iq.eq("userId", userId).gte("bookingDate", args.from);
}
if (args.to) {
return iq.eq("userId", userId).lte("bookingDate", args.to);
}
return iq.eq("userId", userId);
})
.order("desc");
}
if (args.to) {
page = page.filter((tx) => !tx.bookingDate || tx.bookingDate <= args.to!);
if (args.pendingOnly) {
q = q.filter((f) => f.eq(f.field("isPending"), true));
}
if (args.accountId) {
page = page.filter((tx) => tx.accountId === args.accountId);
}
if (args.pendingOnly) {
page = page.filter((tx) => tx.isPending);
q = q.filter((f) => f.eq(f.field("accountId"), args.accountId));
}
if (args.type === "einnahme") {
page = page.filter((tx) => tx.amount > 0);
q = q.filter((f) => f.gt(f.field("amount"), 0));
}
if (args.type === "ausgabe") {
page = page.filter((tx) => tx.amount < 0);
q = q.filter((f) => f.lt(f.field("amount"), 0));
}
if (args.categoryIds && args.categoryIds.length > 0) {
const set = new Set(args.categoryIds);
page = page.filter((tx) => tx.categoryId && set.has(tx.categoryId));
}
if (args.search) {
const s = args.search.toLowerCase();
page = page.filter(
(tx) =>
tx.description.toLowerCase().includes(s) ||
(tx.counterparty?.toLowerCase().includes(s) ?? false) ||
(tx.rawText?.toLowerCase().includes(s) ?? false),
q = q.filter((f) =>
f.or(...args.categoryIds!.map((id) => f.eq(f.field("categoryId"), id))),
);
}
if (args.withoutCategory) {
q = q.filter((f) => f.eq(f.field("categoryId"), undefined));
}
const result = await q.paginate(args.paginationOpts);
return {
page,
page: result.page,
isDone: result.isDone,
continueCursor: result.continueCursor,
};