diff --git a/backend/src/mcp/tools/accounting.ts b/backend/src/mcp/tools/accounting.ts index 273083c..7e1e4f7 100644 --- a/backend/src/mcp/tools/accounting.ts +++ b/backend/src/mcp/tools/accounting.ts @@ -1,4 +1,5 @@ import { and, desc, eq, ilike, or } from "drizzle-orm" +import { randomUUID } from "node:crypto" import { accounts, bankstatements, @@ -102,7 +103,7 @@ const applyOutgoingDocumentTaxType = ( const rows = Array.isArray(payload.rows) ? payload.rows : Array.isArray(existingRows) - ? existingRows + ? normalizeOutgoingDocumentRows(existingRows) : null if (!rows) return @@ -124,6 +125,44 @@ const optionalArrayArg = (args: Record, key: string) => { return Array.isArray(value) ? value : null } +const normalizeOutgoingDocumentRow = (row: unknown, index: number) => { + let normalizedRow = row + + if (typeof normalizedRow === "string") { + try { + normalizedRow = JSON.parse(normalizedRow) + } catch { + throw new Error(`Position ${index + 1} ist kein gültiges JSON-Objekt`) + } + } + + if (!normalizedRow || typeof normalizedRow !== "object" || Array.isArray(normalizedRow)) { + throw new Error(`Position ${index + 1} muss ein Objekt sein`) + } + + const rowPayload = { ...(normalizedRow as Record) } + + rowPayload.id = rowPayload.id || randomUUID() + rowPayload.pos = rowPayload.pos || String(index + 1) + rowPayload.mode = rowPayload.mode || "free" + rowPayload.inputPrice = hasValidNumber(rowPayload.inputPrice) + ? Number(rowPayload.inputPrice) + : hasValidNumber(rowPayload.price) + ? Number(rowPayload.price) + : 0 + rowPayload.price = hasValidNumber(rowPayload.price) ? Number(rowPayload.price) : rowPayload.inputPrice + rowPayload.quantity = hasValidNumber(rowPayload.quantity) ? Number(rowPayload.quantity) : 1 + rowPayload.discountPercent = hasValidNumber(rowPayload.discountPercent) ? Number(rowPayload.discountPercent) : 0 + rowPayload.linkedEntitys = Array.isArray(rowPayload.linkedEntitys) ? rowPayload.linkedEntitys : [] + + return rowPayload +} + +const normalizeOutgoingDocumentRows = (rows: unknown) => { + if (!Array.isArray(rows)) return [] + return rows.map((row, index) => normalizeOutgoingDocumentRow(row, index)) +} + const buildOutgoingDocumentPayload = ( args: Record, userId: string, @@ -142,7 +181,7 @@ const buildOutgoingDocumentPayload = ( payload.archived = false payload.state = stringArg(args, "state") || "Entwurf" payload.type = documentTypeArg(args) - payload.rows = optionalArrayArg(args, "rows") || [] + payload.rows = normalizeOutgoingDocumentRows(optionalArrayArg(args, "rows") || []) } const stringFields = [ @@ -176,7 +215,7 @@ const buildOutgoingDocumentPayload = ( if (args.agriculture !== undefined) payload.agriculture = optionalObjectArg(args, "agriculture") if (args.report !== undefined) payload.report = optionalObjectArg(args, "report") || {} if (args.serialConfig !== undefined) payload.serialConfig = optionalObjectArg(args, "serialConfig") || {} - if (args.rows !== undefined) payload.rows = optionalArrayArg(args, "rows") || [] + if (args.rows !== undefined) payload.rows = normalizeOutgoingDocumentRows(optionalArrayArg(args, "rows") || []) if (args.usedAdvanceInvoices !== undefined) payload.usedAdvanceInvoices = optionalArrayArg(args, "usedAdvanceInvoices") || [] if (typeof args.availableInPortal === "boolean") payload.availableInPortal = args.availableInPortal if (typeof args.advanceInvoiceResolved === "boolean") payload.advanceInvoiceResolved = args.advanceInvoiceResolved diff --git a/frontend/pages/createDocument/edit/[[id]].vue b/frontend/pages/createDocument/edit/[[id]].vue index 9699c45..019bdb3 100644 --- a/frontend/pages/createDocument/edit/[[id]].vue +++ b/frontend/pages/createDocument/edit/[[id]].vue @@ -216,6 +216,38 @@ const normalizeEntityId = (value) => { if (value === null || typeof value === "undefined") return null return typeof value === "object" ? (value.id ?? null) : value } +const normalizeCreatedDocumentRow = (row) => { + let normalizedRow = row + + if (typeof normalizedRow === "string") { + try { + normalizedRow = JSON.parse(normalizedRow) + } catch { + normalizedRow = { + id: uuidv4(), + mode: "text", + text: normalizedRow, + } + } + } + + if (!normalizedRow || typeof normalizedRow !== "object" || Array.isArray(normalizedRow)) { + normalizedRow = { + id: uuidv4(), + mode: "text", + text: String(row ?? ""), + } + } + + return { + ...normalizedRow, + id: normalizedRow.id || uuidv4(), + linkedEntitys: Array.isArray(normalizedRow.linkedEntitys) ? normalizedRow.linkedEntitys : [], + } +} +const normalizeCreatedDocumentRows = (rows) => Array.isArray(rows) + ? rows.map((row) => normalizeCreatedDocumentRow(row)) + : [] const setupPage = async () => { await setupData() @@ -227,6 +259,7 @@ const setupPage = async () => { if (route.params.id) { console.log(route.params) itemInfo.value = await useEntities("createddocuments").selectSingle(route.params.id,'',false) + itemInfo.value.rows = normalizeCreatedDocumentRows(itemInfo.value.rows) itemInfo.value.taxType = normalizeTaxTypeValue(itemInfo.value.taxType) await setContactPersonData() checkCompatibilityWithInputPrice() @@ -235,6 +268,8 @@ const setupPage = async () => { if (!itemInfo.value.deliveryDateType) itemInfo.value.deliveryDateType = "Lieferdatum" + itemInfo.value.rows = normalizeCreatedDocumentRows(itemInfo.value.rows) + if (itemInfo.value.rows.find(i => i.agriculture)) { processDieselPosition() } @@ -284,7 +319,7 @@ const setupPage = async () => { mode: "title", text: `${doc.title} vom ${dayjs(doc.documentDate).format("DD.MM.YYYY")}` }, - ...doc.rows + ...normalizeCreatedDocumentRows(doc.rows) ]) }) @@ -305,7 +340,7 @@ const setupPage = async () => { console.log(linkedDocuments) - if (linkedDocuments.find(i => i.rows.find(x => x.agriculture.dieselUsage))) { + if (linkedDocuments.find(i => normalizeCreatedDocumentRows(i.rows).find(x => x.agriculture?.dieselUsage))) { console.log("has diesel") //Remove Existing Total Diesel Pos @@ -347,7 +382,7 @@ const setupPage = async () => { text: linkedDocument.title, }) - itemInfo.value.rows.push(...linkedDocument.rows) + itemInfo.value.rows.push(...normalizeCreatedDocumentRows(linkedDocument.rows)) } for await (const doc of linkedDocuments.filter(i => quoteLikeDocumentTypes.includes(i.type))) { @@ -359,7 +394,7 @@ const setupPage = async () => { text: linkedDocument.title, }) - itemInfo.value.rows.push(...linkedDocument.rows) + itemInfo.value.rows.push(...normalizeCreatedDocumentRows(linkedDocument.rows)) } itemInfo.value.rows.push({ @@ -428,7 +463,7 @@ const setupPage = async () => { if (optionsToImport.title) itemInfo.value.title = linkedDocument.title if (optionsToImport.description) itemInfo.value.description = linkedDocument.description if (optionsToImport.startText) itemInfo.value.startText = linkedDocument.startText - if (optionsToImport.rows) itemInfo.value.rows = linkedDocument.rows + if (optionsToImport.rows) itemInfo.value.rows = normalizeCreatedDocumentRows(linkedDocument.rows) if (optionsToImport.endText) itemInfo.value.endText = linkedDocument.endText } else { @@ -454,7 +489,7 @@ const setupPage = async () => { itemInfo.value.title = linkedDocument.title itemInfo.value.description = linkedDocument.description itemInfo.value.startText = linkedDocument.startText - itemInfo.value.rows = linkedDocument.rows + itemInfo.value.rows = normalizeCreatedDocumentRows(linkedDocument.rows) itemInfo.value.endText = linkedDocument.endText } @@ -1628,6 +1663,8 @@ const getTextTemplateByType = (type, pos) => { } const checkCompatibilityWithInputPrice = () => { + itemInfo.value.rows = normalizeCreatedDocumentRows(itemInfo.value.rows) + itemInfo.value.rows.forEach(row => { if (!row.inputPrice) { row.inputPrice = row.price