diff --git a/backend/db/migrations/0050_outgoing_document_costcentres.sql b/backend/db/migrations/0050_outgoing_document_costcentres.sql new file mode 100644 index 0000000..ee9bf30 --- /dev/null +++ b/backend/db/migrations/0050_outgoing_document_costcentres.sql @@ -0,0 +1,7 @@ +ALTER TABLE "createddocuments" ADD COLUMN "costcentre" uuid; +--> statement-breakpoint +ALTER TABLE "services" ADD COLUMN "costcentre" uuid; +--> statement-breakpoint +ALTER TABLE "createddocuments" ADD CONSTRAINT "createddocuments_costcentre_costcentres_id_fk" FOREIGN KEY ("costcentre") REFERENCES "public"."costcentres"("id") ON DELETE no action ON UPDATE no action; +--> statement-breakpoint +ALTER TABLE "services" ADD CONSTRAINT "services_costcentre_costcentres_id_fk" FOREIGN KEY ("costcentre") REFERENCES "public"."costcentres"("id") ON DELETE no action ON UPDATE no action; diff --git a/backend/db/migrations/meta/_journal.json b/backend/db/migrations/meta/_journal.json index 7d702c2..c1bfbdf 100644 --- a/backend/db/migrations/meta/_journal.json +++ b/backend/db/migrations/meta/_journal.json @@ -344,6 +344,20 @@ "when": 1780174800000, "tag": "0048_mobile_push_devices", "breakpoints": true + }, + { + "idx": 49, + "version": "7", + "when": 1780178400000, + "tag": "0049_email_cache", + "breakpoints": true + }, + { + "idx": 50, + "version": "7", + "when": 1780261200000, + "tag": "0050_outgoing_document_costcentres", + "breakpoints": true } ] } diff --git a/backend/db/schema/createddocuments.ts b/backend/db/schema/createddocuments.ts index e93359b..40b74fa 100644 --- a/backend/db/schema/createddocuments.ts +++ b/backend/db/schema/createddocuments.ts @@ -20,6 +20,7 @@ import { plants } from "./plants" import { authUsers } from "./auth_users" import {serialExecutions} from "./serialexecutions"; import { outgoingsepamandates } from "./outgoingsepamandates" +import { costcentres } from "./costcentres" export const createddocuments = pgTable("createddocuments", { id: bigint("id", { mode: "number" }) @@ -49,6 +50,8 @@ export const createddocuments = pgTable("createddocuments", { () => projects.id ), + costcentre: uuid("costcentre").references(() => costcentres.id), + documentNumber: text("documentNumber"), documentDate: text("documentDate"), diff --git a/backend/db/schema/services.ts b/backend/db/schema/services.ts index c9491af..708d942 100644 --- a/backend/db/schema/services.ts +++ b/backend/db/schema/services.ts @@ -13,6 +13,7 @@ import { import { tenants } from "./tenants" import { units } from "./units" import { authUsers } from "./auth_users" +import { costcentres } from "./costcentres" export const services = pgTable("services", { id: bigint("id", { mode: "number" }) @@ -35,6 +36,8 @@ export const services = pgTable("services", { unit: bigint("unit", { mode: "number" }).references(() => units.id), + costcentre: uuid("costcentre").references(() => costcentres.id), + serviceNumber: bigint("serviceNumber", { mode: "number" }), tags: jsonb("tags").default([]), diff --git a/backend/src/mcp/tools/accounting.ts b/backend/src/mcp/tools/accounting.ts index 7e1e4f7..91c4d16 100644 --- a/backend/src/mcp/tools/accounting.ts +++ b/backend/src/mcp/tools/accounting.ts @@ -205,6 +205,8 @@ const buildOutgoingDocumentPayload = ( if (args[field] !== undefined) payload[field] = numberArg(args, field) } + if (args.costcentre !== undefined) payload.costcentre = stringArg(args, "costcentre") + for (const field of ["paymentDays"] as const) { if (args[field] !== undefined) payload[field] = numberArg(args, field) } @@ -458,6 +460,7 @@ export const accountingTools: McpTool[] = [ contact: { type: "number" }, contract: { type: "number" }, project: { type: "number" }, + costcentre: { type: "string", description: "Kostenstellen-UUID als Belegvorgabe." }, plant: { type: "number" }, documentDate: { type: "string" }, deliveryDate: { type: "string" }, @@ -512,6 +515,7 @@ export const accountingTools: McpTool[] = [ contact: { type: "number" }, contract: { type: "number" }, project: { type: "number" }, + costcentre: { type: "string", description: "Kostenstellen-UUID als Belegvorgabe." }, plant: { type: "number" }, documentDate: { type: "string" }, deliveryDate: { type: "string" }, diff --git a/backend/src/modules/serialexecution.service.ts b/backend/src/modules/serialexecution.service.ts index df3188a..14e5287 100644 --- a/backend/src/modules/serialexecution.service.ts +++ b/backend/src/modules/serialexecution.service.ts @@ -378,6 +378,7 @@ async function getSaveData(item: any, tenant: any, firstDate: string, lastDate: contract: item.contract, address: item.address, project: item.project, + costcentre: item.costcentre, documentDate: executionDate, deliveryDate: firstDate, deliveryDateEnd: lastDate, diff --git a/backend/src/utils/resource.config.ts b/backend/src/utils/resource.config.ts index 7c59fee..bf7e486 100644 --- a/backend/src/utils/resource.config.ts +++ b/backend/src/utils/resource.config.ts @@ -78,7 +78,7 @@ export const resourceConfig = { table: contracts, searchColumns: ["name", "notes", "contractNumber", "paymentType", "billingInterval", "sepaRef", "bankingName"], numberRangeHolder: "contractNumber", - mtoLoad: ["customer", "contracttype", "outgoingsepamandate"], + mtoLoad: ["customer", "contracttype", "contact", "outgoingsepamandate"], }, outgoingsepamandates: { table: outgoingsepamandates, @@ -230,7 +230,7 @@ export const resourceConfig = { }, createddocuments: { table: createddocuments, - mtoLoad: ["customer", "project", "contact", "contract", "plant","letterhead","createddocument", "outgoingsepamandate"], + mtoLoad: ["customer", "project", "costcentre", "contact", "contract", "plant","letterhead","createddocument", "outgoingsepamandate"], mtmLoad: ["statementallocations","files","createddocuments"], mtmListLoad: ["statementallocations", "files"], }, diff --git a/frontend/components/copyCreatedDocumentModal.vue b/frontend/components/copyCreatedDocumentModal.vue index 613e774..9ce1fba 100644 --- a/frontend/components/copyCreatedDocumentModal.vue +++ b/frontend/components/copyCreatedDocumentModal.vue @@ -54,6 +54,7 @@ const optionsToImport = ref({ contactPerson: true, plant: true, project:true, + costcentre: true, description: true, startText: false, rows: true, @@ -74,6 +75,7 @@ const mappings = ref({ contactPerson: "Ansprechpartner Mitarbeiter", plant: "Objekt", project: "Projekt", + costcentre: "Kostenstelle", description: "Beschreibung", startText: "Einleitung", rows: "Positionen", diff --git a/frontend/components/costcentreDisplay.vue b/frontend/components/costcentreDisplay.vue index 2eb3113..09a4e06 100644 --- a/frontend/components/costcentreDisplay.vue +++ b/frontend/components/costcentreDisplay.vue @@ -10,6 +10,7 @@ const props = defineProps({ const loading = ref(true) const incomingInvoices = ref([]) +const createddocuments = ref([]) const costcentres = ref([]) const selectedYear = ref(String(dayjs().year())) const selectedMonth = ref("all") @@ -98,7 +99,7 @@ const monthItems = [ ] const reportRows = computed(() => { - return incomingInvoices.value.flatMap((invoice) => { + const incomingRows = incomingInvoices.value.flatMap((invoice) => { const invoiceDate = invoice.date ? dayjs(invoice.date) : null if (invoiceDate && invoiceDate.year().toString() !== selectedYear.value) { @@ -120,7 +121,8 @@ const reportRows = computed(() => { const accountCostCentre = costCentreMap.value.get(getCostCentreId(account.costCentre)) return { - id: `${invoice.id}-${index}`, + id: `incoming-${invoice.id}-${index}`, + sourceLabel: "Eingangsbeleg", invoiceId: invoice.id, reference: invoice.reference || "-", date: invoice.date, @@ -135,6 +137,53 @@ const reportRows = computed(() => { } }) }) + + const outgoingRows = createddocuments.value.flatMap((document) => { + const documentDate = document.documentDate ? dayjs(document.documentDate) : null + + if (documentDate && documentDate.year().toString() !== selectedYear.value) { + return [] + } + + if (documentDate && selectedMonth.value !== "all" && documentDate.month() + 1 !== Number(selectedMonth.value)) { + return [] + } + + return (document.rows || []) + .filter((row) => !["pagebreak", "title", "text"].includes(row.mode)) + .map((row, index) => { + const costCentreId = getCostCentreId(row.costCentre || row.costcentre || document.costcentre) + + if (!relevantCostCentreIds.value.has(costCentreId)) { + return null + } + + const amountNet = Number((Number(row.quantity || 0) * Number(row.price || 0) * (1 - Number(row.discountPercent || 0) / 100)).toFixed(2)) + const taxPercent = Number(row.taxPercent || 0) + const amountTax = Number((amountNet * (taxPercent / 100)).toFixed(2)) + const amountGross = Number((amountNet + amountTax).toFixed(2)) + const accountCostCentre = costCentreMap.value.get(costCentreId) + + return { + id: `outgoing-${document.id}-${index}`, + sourceLabel: "Ausgangsbeleg", + invoiceId: document.id, + reference: document.documentNumber || document.title || "-", + date: document.documentDate, + state: document.state || "-", + vendorName: document.customer?.name || "-", + accountLabel: "Umsatz", + costCentreName: accountCostCentre ? `${accountCostCentre.number} - ${accountCostCentre.name}` : "-", + description: row.text || row.description || document.description || "-", + amountNet, + amountTax, + amountGross + } + }) + .filter(Boolean) + }) + + return [...incomingRows, ...outgoingRows] }) const totals = computed(() => { @@ -147,9 +196,10 @@ const totals = computed(() => { }) const columns = [ + { accessorKey: "sourceLabel", header: "Art" }, { accessorKey: "reference", header: "Beleg" }, { accessorKey: "date", header: "Datum" }, - { accessorKey: "vendorName", header: "Lieferant" }, + { accessorKey: "vendorName", header: "Kontakt" }, { accessorKey: "accountLabel", header: "Konto" }, { accessorKey: "costCentreName", header: "Kostenstelle" }, { accessorKey: "description", header: "Beschreibung" }, @@ -163,10 +213,16 @@ const setupPage = async () => { costcentres.value = await useEntities("costcentres").select("*", null, false, true) const invoices = await useEntities("incominginvoices").select("*, vendor(id,name)") + const documents = await useEntities("createddocuments").select("*, customer(id,name)") incomingInvoices.value = invoices.filter((invoice) => (invoice.accounts || []).some((account) => relevantCostCentreIds.value.has(account.costCentre)) ) + createddocuments.value = documents.filter((document) => + ["invoices", "advanceInvoices", "cancellationInvoices"].includes(document.type) + && document.state === "Gebucht" + && (document.rows || []).some((row) => relevantCostCentreIds.value.has(getCostCentreId(row.costCentre || row.costcentre || document.costcentre))) + ) const firstYear = yearItems.value[0]?.value if (firstYear && !yearItems.value.some((item) => item.value === selectedYear.value)) { @@ -264,7 +320,7 @@ setupPage()