Initial for #123
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 31s
Build and Push Docker Images / build-frontend (push) Successful in 1m10s

This commit is contained in:
2026-02-21 22:21:10 +01:00
parent a0e1b8c0eb
commit 0141a243ce
9 changed files with 98 additions and 7 deletions

View File

@@ -127,6 +127,13 @@
"when": 1771704862789, "when": 1771704862789,
"tag": "0017_slow_the_hood", "tag": "0017_slow_the_hood",
"breakpoints": true "breakpoints": true
},
{
"idx": 18,
"version": "7",
"when": 1773000900000,
"tag": "0018_account_chart",
"breakpoints": true
} }
] ]
} }

View File

@@ -16,6 +16,7 @@ export const accounts = pgTable("accounts", {
number: text("number").notNull(), number: text("number").notNull(),
label: text("label").notNull(), label: text("label").notNull(),
accountChart: text("accountChart").notNull().default("skr03"),
description: text("description"), description: text("description"),
}) })

View File

@@ -94,6 +94,7 @@ export const tenants = pgTable(
projects: { prefix: "PRJ-", suffix: "", nextNumber: 1000 }, projects: { prefix: "PRJ-", suffix: "", nextNumber: 1000 },
costcentres: { prefix: "KST-", suffix: "", nextNumber: 1000 }, costcentres: { prefix: "KST-", suffix: "", nextNumber: 1000 },
}), }),
accountChart: text("accountChart").notNull().default("skr03"),
standardEmailForInvoices: text("standardEmailForInvoices"), standardEmailForInvoices: text("standardEmailForInvoices"),

View File

@@ -94,6 +94,7 @@ export default async function adminRoutes(server: FastifyInstance) {
short: tenants.short, short: tenants.short,
locked: tenants.locked, locked: tenants.locked,
numberRanges: tenants.numberRanges, numberRanges: tenants.numberRanges,
accountChart: tenants.accountChart,
extraModules: tenants.extraModules, extraModules: tenants.extraModules,
}) })
.from(authTenantUsers) .from(authTenantUsers)

View File

@@ -54,6 +54,7 @@ export default async function meRoutes(server: FastifyInstance) {
extraModules: tenants.extraModules, extraModules: tenants.extraModules,
businessInfo: tenants.businessInfo, businessInfo: tenants.businessInfo,
numberRanges: tenants.numberRanges, numberRanges: tenants.numberRanges,
accountChart: tenants.accountChart,
dokuboxkey: tenants.dokuboxkey, dokuboxkey: tenants.dokuboxkey,
standardEmailForInvoices: tenants.standardEmailForInvoices, standardEmailForInvoices: tenants.standardEmailForInvoices,
standardPaymentDays: tenants.standardPaymentDays, standardPaymentDays: tenants.standardPaymentDays,

View File

@@ -586,6 +586,9 @@ export default async function resourceRoutes(server: FastifyInstance) {
try { try {
if (!req.user?.tenant_id) return reply.code(400).send({ error: "No tenant selected" }); if (!req.user?.tenant_id) return reply.code(400).send({ error: "No tenant selected" });
const { resource } = req.params as { resource: string }; 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<string, any>; const body = req.body as Record<string, any>;
const config = resourceConfig[resource]; const config = resourceConfig[resource];
const table = config.table; const table = config.table;
@@ -656,6 +659,9 @@ export default async function resourceRoutes(server: FastifyInstance) {
server.put("/resource/:resource/:id", async (req, reply) => { server.put("/resource/:resource/:id", async (req, reply) => {
try { try {
const { resource, id } = req.params as { resource: string; id: string } 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<string, any> const body = req.body as Record<string, any>
const tenantId = req.user?.tenant_id const tenantId = req.user?.tenant_id
const userId = req.user?.user_id const userId = req.user?.user_id

View File

@@ -1,9 +1,9 @@
import { FastifyInstance } from "fastify" import { FastifyInstance } from "fastify"
import { asc, desc } from "drizzle-orm" import { asc, desc, eq } from "drizzle-orm"
import { sortData } from "../utils/sort" import { sortData } from "../utils/sort"
// Schema imports // Schema imports
import { accounts, units,countrys } from "../../db/schema" import { accounts, units, countrys, tenants } from "../../db/schema"
const TABLE_MAP: Record<string, any> = { const TABLE_MAP: Record<string, any> = {
accounts, accounts,
@@ -40,6 +40,44 @@ export default async function resourceRoutesSpecial(server: FastifyInstance) {
// Wir geben IMMER alle Spalten zurück → kompatibel zum Frontend // 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) let query = server.db.select().from(table)
// --------------------------------------- // ---------------------------------------

View File

@@ -11,7 +11,7 @@ import { s3 } from "./s3";
import { secrets } from "./secrets"; import { secrets } from "./secrets";
// Drizzle schema // Drizzle schema
import { vendors, accounts } from "../../db/schema"; import { vendors, accounts, tenants } from "../../db/schema";
import {eq} from "drizzle-orm"; import {eq} from "drizzle-orm";
let openai: OpenAI | null = null; let openai: OpenAI | null = null;
@@ -163,13 +163,22 @@ export const getInvoiceDataFromGPT = async function (
.from(vendors) .from(vendors)
.where(eq(vendors.tenant,tenantId)); .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 const accountList = await server.db
.select({ .select({
id: accounts.id, id: accounts.id,
label: accounts.label, label: accounts.label,
number: accounts.number, number: accounts.number,
}) })
.from(accounts); .from(accounts)
.where(eq(accounts.accountChart, activeAccountChart));
// --------------------------------------------------------- // ---------------------------------------------------------
// 4) GPT ANALYSIS // 4) GPT ANALYSIS

View File

@@ -15,6 +15,11 @@ const setupPage = async () => {
const features = ref(auth.activeTenantData.features) const features = ref(auth.activeTenantData.features)
const businessInfo = ref(auth.activeTenantData.businessInfo) 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) => { const updateTenant = async (newData) => {
@@ -24,6 +29,11 @@ const updateTenant = async (newData) => {
data: newData, data: newData,
} }
}) })
if (res) {
itemInfo.value = res
auth.activeTenantData = res
}
} }
setupPage() setupPage()
@@ -63,8 +73,8 @@ setupPage()
</div> </div>
<div v-if="item.label === 'Rechnung & Kontakt'"> <div v-if="item.label === 'Rechnung & Kontakt'">
<UCard class="mt-5"> <UCard class="mt-5">
<UForm class="w-1/2"> <UForm class="w-1/2">
<UFormGroup <UFormGroup
label="Firmenname:" label="Firmenname:"
> >
@@ -90,6 +100,23 @@ setupPage()
> >
Speichern Speichern
</UButton> </UButton>
<UFormGroup
label="Kontenrahmen:"
class="mt-6"
>
<USelectMenu
v-model="accountChart"
:options="accountChartOptions"
option-attribute="label"
value-attribute="value"
/>
</UFormGroup>
<UButton
class="mt-3"
@click="updateTenant({accountChart: accountChart})"
>
Kontenrahmen speichern
</UButton>
</UForm> </UForm>
</UCard> </UCard>