Compare commits
3 Commits
409db82368
...
0141a243ce
| Author | SHA1 | Date | |
|---|---|---|---|
| 0141a243ce | |||
| a0e1b8c0eb | |||
| 45fb45845a |
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -59,6 +59,44 @@ const parseId = (value: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function resourceHistoryRoutes(server: FastifyInstance) {
|
export default async function resourceHistoryRoutes(server: FastifyInstance) {
|
||||||
|
server.get("/history", {
|
||||||
|
schema: {
|
||||||
|
tags: ["History"],
|
||||||
|
summary: "Get all history entries for the active tenant",
|
||||||
|
},
|
||||||
|
}, async (req: any) => {
|
||||||
|
const data = await server.db
|
||||||
|
.select()
|
||||||
|
.from(historyitems)
|
||||||
|
.where(eq(historyitems.tenant, req.user?.tenant_id))
|
||||||
|
.orderBy(asc(historyitems.createdAt));
|
||||||
|
|
||||||
|
const userIds = Array.from(
|
||||||
|
new Set(data.map((item) => item.createdBy).filter(Boolean))
|
||||||
|
) as string[];
|
||||||
|
|
||||||
|
const profiles = userIds.length > 0
|
||||||
|
? await server.db
|
||||||
|
.select()
|
||||||
|
.from(authProfiles)
|
||||||
|
.where(and(
|
||||||
|
eq(authProfiles.tenant_id, req.user?.tenant_id),
|
||||||
|
inArray(authProfiles.user_id, userIds)
|
||||||
|
))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const profileByUserId = new Map(
|
||||||
|
profiles.map((profile) => [profile.user_id, profile])
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.map((historyitem) => ({
|
||||||
|
...historyitem,
|
||||||
|
created_at: historyitem.createdAt,
|
||||||
|
created_by: historyitem.createdBy,
|
||||||
|
created_by_profile: historyitem.createdBy ? profileByUserId.get(historyitem.createdBy) || null : null,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
server.get<{
|
server.get<{
|
||||||
Params: { resource: string; id: string }
|
Params: { resource: string; id: string }
|
||||||
}>("/resource/:resource/:id/history", {
|
}>("/resource/:resource/:id/history", {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import dayjs from "dayjs"
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: false,
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
elementId: {
|
elementId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: false,
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
renderHeadline: {
|
renderHeadline: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -25,13 +27,11 @@ const items = ref([])
|
|||||||
const platform = ref("default")
|
const platform = ref("default")
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
|
|
||||||
|
|
||||||
if(props.type && props.elementId){
|
if(props.type && props.elementId){
|
||||||
items.value = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`)
|
items.value = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`)
|
||||||
} /*else {
|
} else {
|
||||||
|
items.value = await useNuxtApp().$api(`/api/history`)
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
@@ -43,6 +43,10 @@ const addHistoryItemData = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const addHistoryItem = async () => {
|
const addHistoryItem = async () => {
|
||||||
|
if (!props.type || !props.elementId) {
|
||||||
|
toast.add({ title: "Im zentralen Logbuch können keine direkten Einträge erstellt werden." })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const res = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`, {
|
const res = await useNuxtApp().$api(`/api/resource/${props.type}/${props.elementId}/history`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -161,4 +165,4 @@ const renderText = (text) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ const links = computed(() => {
|
|||||||
id: 'historyitems',
|
id: 'historyitems',
|
||||||
label: "Logbuch",
|
label: "Logbuch",
|
||||||
to: "/historyitems",
|
to: "/historyitems",
|
||||||
icon: "i-heroicons-book-open",
|
icon: "i-heroicons-book-open"
|
||||||
disabled: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Organisation",
|
label: "Organisation",
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ defineShortcuts({
|
|||||||
'Enter': {
|
'Enter': {
|
||||||
usingInput: true,
|
usingInput: true,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
router.push(`/incomingInvoices/show/${filteredRows.value[selectedItem.value].id}`)
|
const invoice = filteredRows.value[selectedItem.value]
|
||||||
|
if (invoice) {
|
||||||
|
selectIncomingInvoice(invoice)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'arrowdown': () => {
|
'arrowdown': () => {
|
||||||
@@ -146,13 +149,11 @@ const isPaid = (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectIncomingInvoice = (invoice) => {
|
const selectIncomingInvoice = (invoice) => {
|
||||||
if(invoice.state === "Vorbereitet" ) {
|
if (invoice.state === "Gebucht") {
|
||||||
router.push(`/incomingInvoices/edit/${invoice.id}`)
|
|
||||||
} else {
|
|
||||||
router.push(`/incomingInvoices/show/${invoice.id}`)
|
router.push(`/incomingInvoices/show/${invoice.id}`)
|
||||||
|
} else {
|
||||||
|
router.push(`/incomingInvoices/edit/${invoice.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -291,4 +292,4 @@ const selectIncomingInvoice = (invoice) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user