From 6a14b0c1ca0f14170a6efdf634efc7c50de2cf16 Mon Sep 17 00:00:00 2001 From: flfeders Date: Wed, 15 Jul 2026 13:11:57 +0200 Subject: [PATCH] Extend document title levels --- backend/src/utils/pdf.ts | 68 +++++++++++------- frontend/pages/createDocument/edit/[[id]].vue | 70 ++++++++++++++----- 2 files changed, 92 insertions(+), 46 deletions(-) diff --git a/backend/src/utils/pdf.ts b/backend/src/utils/pdf.ts index 3ba9248..76ef318 100644 --- a/backend/src/utils/pdf.ts +++ b/backend/src/utils/pdf.ts @@ -399,6 +399,12 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi let pageIndex = 0 + const getTitleKey = (row) => `${row.pos} - ${row.text}` + const getTransferSumText = (row) => { + const transferSum = invoiceData.total.titleSumsTransfer[getTitleKey(row)] + + return transferSum ? `Übertrag: ${transferSum}` : null + } invoiceData.rows.forEach((row, index) => { @@ -579,18 +585,20 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi console.log(invoiceData.rows[index + 1]) - if (invoiceData.rows[index + 1].mode === 'title') { - let transferSumText = `Übertrag: ${invoiceData.total.titleSumsTransfer[Object.keys(invoiceData.total.titleSums)[invoiceData.rows[index + 1].pos - 2]]}` - pages[pageCounter - 1].drawText(transferSumText, { - y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, - x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), - size: 10, - color: rgb(0, 0, 0), - lineHeight: 10, - opacity: 1, - maxWidth: 240, - font: fontBold - }) + if (invoiceData.rows[index + 1]?.mode === 'title') { + const transferSumText = getTransferSumText(invoiceData.rows[index + 1]) + if (transferSumText) { + pages[pageCounter - 1].drawText(transferSumText, { + y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, + x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), + size: 10, + color: rgb(0, 0, 0), + lineHeight: 10, + opacity: 1, + maxWidth: 240, + font: fontBold + }) + } } @@ -713,17 +721,19 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi if (index === 0 || pageIndex === 0) { rowHeight += 3 } else { - let transferSumText = `Übertrag: ${invoiceData.total.titleSumsTransfer[Object.keys(invoiceData.total.titleSums)[row.pos - 2]]}` - pages[pageCounter - 1].drawText(transferSumText, { - y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, - x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), - size: 10, - color: rgb(0, 0, 0), - lineHeight: 10, - opacity: 1, - maxWidth: 240, - font: fontBold - }) + const transferSumText = getTransferSumText(row) + if (transferSumText) { + pages[pageCounter - 1].drawText(transferSumText, { + y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y, + x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10), + size: 10, + color: rgb(0, 0, 0), + lineHeight: 10, + opacity: 1, + maxWidth: 240, + font: fontBold + }) + } pages[pageCounter - 1].drawLine({ start: getCoordinatesForPDFLib(20, rowHeight, page1), @@ -735,6 +745,10 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi rowHeight += 5 } + const titleLevel = Math.min(Math.max(Number(row.titleLevel) || 1, 1), 3) + const titleTextSize = titleLevel === 1 ? 12 : titleLevel === 2 ? 11 : 10 + const titleTextX = 35 + ((titleLevel - 1) * 7) + pages[pageCounter - 1].drawText(String(row.pos), { ...getCoordinatesForPDFLib(21, rowHeight, page1), size: 10, @@ -746,16 +760,16 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi }) pages[pageCounter - 1].drawText(splitStringBySpace(row.text, 60).join("\n"), { - ...getCoordinatesForPDFLib(35, rowHeight, page1), - size: 12, + ...getCoordinatesForPDFLib(titleTextX, rowHeight, page1), + size: titleTextSize, color: rgb(0, 0, 0), - lineHeight: 12, + lineHeight: titleTextSize, opacity: 1, maxWidth: 500, font: fontBold }) - rowHeight += splitStringBySpace(row.text, 60).length * 4.5 + rowHeight += splitStringBySpace(row.text, 60).length * (titleLevel === 1 ? 4.5 : 4) } else if (row.mode === 'text') { if (index === 0 || pageIndex === 0) { rowHeight += 3 diff --git a/frontend/pages/createDocument/edit/[[id]].vue b/frontend/pages/createDocument/edit/[[id]].vue index 767ab24..e24aa43 100644 --- a/frontend/pages/createDocument/edit/[[id]].vue +++ b/frontend/pages/createDocument/edit/[[id]].vue @@ -108,6 +108,11 @@ const taxTypeItems = [ { key: '19 UStG', label: '19 UStG Kleinunternehmer' }, { key: '12.3 UStG', label: '12.3 UStG' } ] +const titleLevelItems = [ + { label: 'Titel Ebene 1', value: 1 }, + { label: 'Untertitel Ebene 2', value: 2 }, + { label: 'Untertitel Ebene 3', value: 3 } +] const deliveryDateTypeItems = ['Lieferdatum', 'Lieferzeitraum', 'Leistungsdatum', 'Leistungszeitraum', 'Kein Lieferdatum anzeigen'] const paymentTypeItems = [ { key: 'transfer', label: 'Überweisung' }, @@ -832,6 +837,7 @@ const positionAddOptions = [ { mode: 'free', label: 'Freie Position', icon: 'i-heroicons-pencil-square' }, { mode: 'pagebreak', label: 'Seitenumbruch', icon: 'i-heroicons-document-minus' }, { mode: 'title', label: 'Titel', icon: 'i-heroicons-bars-3-bottom-left' }, + { mode: 'subtitle', label: 'Untertitel', icon: 'i-heroicons-list-bullet' }, { mode: 'text', label: 'Text', icon: 'i-heroicons-document-text' } ] @@ -895,10 +901,11 @@ const createPositionRow = (mode, taxPercentage) => { } } - if (mode === "title") { + if (mode === "title" || mode === "subtitle") { return { id: uuidv4(), mode: "title", + titleLevel: mode === "subtitle" ? 2 : 1, linkedEntitys: [] } } @@ -1152,12 +1159,10 @@ const documentTotal = computed(() => { if (row.mode === 'title') { let title = `${row.pos} - ${row.text}` titleSums[title] = 0 + if (lastTitle) { + titleSumsTransfer[title] = transferCounter + } lastTitle = title - - //Übertrag berechnen - titleSumsTransfer[Object.keys(titleSums)[row.pos - 2]] = transferCounter - - } else if (!['pagebreak', 'text'].includes(row.mode) && lastTitle !== "" && !row.optional && !row.alternative) { titleSums[lastTitle] = Number(titleSums[lastTitle]) + Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)) transferCounter += Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) / 100)) @@ -1610,16 +1615,33 @@ const onChangeTab = (index) => { } const setPosNumbers = () => { - let mainIndex = 1 - let subIndex = 1 + let titleCounters = [0, 0, 0] + let positionIndex = 1 + let currentTitlePath = [] + const hasTitles = itemInfo.value.rows.some(row => row.mode === "title") + let rows = itemInfo.value.rows.map(row => { if (row.mode === 'title') { - row.pos = mainIndex - mainIndex += 1 - subIndex = 1 + const titleLevel = Math.min(Math.max(Number(row.titleLevel) || 1, 1), titleCounters.length) + row.titleLevel = titleLevel + + for (let index = 0; index < titleLevel - 1; index += 1) { + if (titleCounters[index] === 0) { + titleCounters[index] = 1 + } + } + + titleCounters[titleLevel - 1] += 1 + for (let index = titleLevel; index < titleCounters.length; index += 1) { + titleCounters[index] = 0 + } + + currentTitlePath = titleCounters.slice(0, titleLevel) + row.pos = currentTitlePath.join(".") + positionIndex = 1 } else if (!['pagebreak', 'title', 'text'].includes(row.mode)) { - row.pos = itemInfo.value.rows.filter(i => i.mode === "title").length === 0 ? `${subIndex}` : `${mainIndex - 1}.${subIndex}` - subIndex += 1 + row.pos = hasTitles && currentTitlePath.length > 0 ? `${currentTitlePath.join(".")}.${positionIndex}` : `${positionIndex}` + positionIndex += 1 } if (!row.id) { @@ -3471,12 +3493,22 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = { v-if="row.mode === 'title'" :colspan="getTitlePositionColspan()" > - + + + +