From 0141a243ce02002b086e39d8a6a9aec4587c39c0 Mon Sep 17 00:00:00 2001 From: florianfederspiel Date: Sat, 21 Feb 2026 22:21:10 +0100 Subject: [PATCH] Initial for #123 --- backend/db/migrations/meta/_journal.json | 9 ++++- backend/db/schema/accounts.ts | 1 + backend/db/schema/tenants.ts | 1 + backend/src/routes/admin.ts | 1 + backend/src/routes/auth/me.ts | 1 + backend/src/routes/resources/main.ts | 6 ++++ backend/src/routes/resourcesSpecial.ts | 42 ++++++++++++++++++++++-- backend/src/utils/gpt.ts | 13 ++++++-- frontend/pages/settings/tenant.vue | 31 +++++++++++++++-- 9 files changed, 98 insertions(+), 7 deletions(-) diff --git a/backend/db/migrations/meta/_journal.json b/backend/db/migrations/meta/_journal.json index a9dd05f..0d46d0c 100644 --- a/backend/db/migrations/meta/_journal.json +++ b/backend/db/migrations/meta/_journal.json @@ -127,6 +127,13 @@ "when": 1771704862789, "tag": "0017_slow_the_hood", "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1773000900000, + "tag": "0018_account_chart", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/backend/db/schema/accounts.ts b/backend/db/schema/accounts.ts index cc154de..58bf757 100644 --- a/backend/db/schema/accounts.ts +++ b/backend/db/schema/accounts.ts @@ -16,6 +16,7 @@ export const accounts = pgTable("accounts", { number: text("number").notNull(), label: text("label").notNull(), + accountChart: text("accountChart").notNull().default("skr03"), description: text("description"), }) diff --git a/backend/db/schema/tenants.ts b/backend/db/schema/tenants.ts index c73cfbf..82f4ecb 100644 --- a/backend/db/schema/tenants.ts +++ b/backend/db/schema/tenants.ts @@ -94,6 +94,7 @@ export const tenants = pgTable( projects: { prefix: "PRJ-", suffix: "", nextNumber: 1000 }, costcentres: { prefix: "KST-", suffix: "", nextNumber: 1000 }, }), + accountChart: text("accountChart").notNull().default("skr03"), standardEmailForInvoices: text("standardEmailForInvoices"), diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index 9120d18..10c7d53 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -94,6 +94,7 @@ export default async function adminRoutes(server: FastifyInstance) { short: tenants.short, locked: tenants.locked, numberRanges: tenants.numberRanges, + accountChart: tenants.accountChart, extraModules: tenants.extraModules, }) .from(authTenantUsers) diff --git a/backend/src/routes/auth/me.ts b/backend/src/routes/auth/me.ts index fffb793..2db6457 100644 --- a/backend/src/routes/auth/me.ts +++ b/backend/src/routes/auth/me.ts @@ -54,6 +54,7 @@ export default async function meRoutes(server: FastifyInstance) { extraModules: tenants.extraModules, businessInfo: tenants.businessInfo, numberRanges: tenants.numberRanges, + accountChart: tenants.accountChart, dokuboxkey: tenants.dokuboxkey, standardEmailForInvoices: tenants.standardEmailForInvoices, standardPaymentDays: tenants.standardPaymentDays, diff --git a/backend/src/routes/resources/main.ts b/backend/src/routes/resources/main.ts index 2aa351d..d354c4a 100644 --- a/backend/src/routes/resources/main.ts +++ b/backend/src/routes/resources/main.ts @@ -586,6 +586,9 @@ export default async function resourceRoutes(server: FastifyInstance) { try { if (!req.user?.tenant_id) return reply.code(400).send({ error: "No tenant selected" }); const { resource } = req.params as { resource: string }; + if (resource === "accounts") { + return reply.code(403).send({ error: "Accounts are read-only" }) + } const body = req.body as Record; const config = resourceConfig[resource]; const table = config.table; @@ -656,6 +659,9 @@ export default async function resourceRoutes(server: FastifyInstance) { server.put("/resource/:resource/:id", async (req, reply) => { try { const { resource, id } = req.params as { resource: string; id: string } + if (resource === "accounts") { + return reply.code(403).send({ error: "Accounts are read-only" }) + } const body = req.body as Record const tenantId = req.user?.tenant_id const userId = req.user?.user_id diff --git a/backend/src/routes/resourcesSpecial.ts b/backend/src/routes/resourcesSpecial.ts index c191615..8717835 100644 --- a/backend/src/routes/resourcesSpecial.ts +++ b/backend/src/routes/resourcesSpecial.ts @@ -1,9 +1,9 @@ import { FastifyInstance } from "fastify" -import { asc, desc } from "drizzle-orm" +import { asc, desc, eq } from "drizzle-orm" import { sortData } from "../utils/sort" // Schema imports -import { accounts, units,countrys } from "../../db/schema" +import { accounts, units, countrys, tenants } from "../../db/schema" const TABLE_MAP: Record = { accounts, @@ -40,6 +40,44 @@ export default async function resourceRoutesSpecial(server: FastifyInstance) { // Wir geben IMMER alle Spalten zurück → kompatibel zum Frontend // --------------------------------------- + if (resource === "accounts") { + const [tenant] = await server.db + .select({ + accountChart: tenants.accountChart, + }) + .from(tenants) + .where(eq(tenants.id, Number(req.user.tenant_id))) + .limit(1) + + const activeAccountChart = tenant?.accountChart || "skr03" + let data + if (sort && (accounts as any)[sort]) { + const col = (accounts as any)[sort] + data = ascQuery === "true" + ? await server.db + .select() + .from(accounts) + .where(eq(accounts.accountChart, activeAccountChart)) + .orderBy(asc(col)) + : await server.db + .select() + .from(accounts) + .where(eq(accounts.accountChart, activeAccountChart)) + .orderBy(desc(col)) + } else { + data = await server.db + .select() + .from(accounts) + .where(eq(accounts.accountChart, activeAccountChart)) + } + + return sortData( + data, + sort as any, + ascQuery === "true" + ) + } + let query = server.db.select().from(table) // --------------------------------------- diff --git a/backend/src/utils/gpt.ts b/backend/src/utils/gpt.ts index 0d73eeb..c61f386 100644 --- a/backend/src/utils/gpt.ts +++ b/backend/src/utils/gpt.ts @@ -11,7 +11,7 @@ import { s3 } from "./s3"; import { secrets } from "./secrets"; // Drizzle schema -import { vendors, accounts } from "../../db/schema"; +import { vendors, accounts, tenants } from "../../db/schema"; import {eq} from "drizzle-orm"; let openai: OpenAI | null = null; @@ -163,13 +163,22 @@ export const getInvoiceDataFromGPT = async function ( .from(vendors) .where(eq(vendors.tenant,tenantId)); + const [tenant] = await server.db + .select({ accountChart: tenants.accountChart }) + .from(tenants) + .where(eq(tenants.id, tenantId)) + .limit(1) + + const activeAccountChart = tenant?.accountChart || "skr03" + const accountList = await server.db .select({ id: accounts.id, label: accounts.label, number: accounts.number, }) - .from(accounts); + .from(accounts) + .where(eq(accounts.accountChart, activeAccountChart)); // --------------------------------------------------------- // 4) GPT ANALYSIS diff --git a/frontend/pages/settings/tenant.vue b/frontend/pages/settings/tenant.vue index 2295e35..26a4b5d 100644 --- a/frontend/pages/settings/tenant.vue +++ b/frontend/pages/settings/tenant.vue @@ -15,6 +15,11 @@ const setupPage = async () => { const features = ref(auth.activeTenantData.features) const businessInfo = ref(auth.activeTenantData.businessInfo) +const accountChart = ref(auth.activeTenantData.accountChart || "skr03") +const accountChartOptions = [ + { label: "SKR 03", value: "skr03" }, + { label: "Verein", value: "verein" } +] const updateTenant = async (newData) => { @@ -24,6 +29,11 @@ const updateTenant = async (newData) => { data: newData, } }) + + if (res) { + itemInfo.value = res + auth.activeTenantData = res + } } setupPage() @@ -63,8 +73,8 @@ setupPage()
- - + + @@ -90,6 +100,23 @@ setupPage() > Speichern + + + + + Kontenrahmen speichern +