From 5371d5e479825074907279ea42ee0c73395bf5ca Mon Sep 17 00:00:00 2001 From: dgsoft Date: Wed, 1 Oct 2025 22:27:29 +0200 Subject: [PATCH] Add WebDAV integration and template improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Frontend-Verbesserungen: - WebDAV-Integration für direktes Template-Bearbeiten - 'In Word öffnen' Button für ms-word: URLs - Verbesserte Template-Karten mit Tag-Informationen - WebDAV-Info-Sektion mit Anleitungen - Korrigierte API-Endpunkte für docxtemplater 🔧 Template-Updates: - Alle Templates auf docxtemplater-Syntax aktualisiert - create-*-fixed.js Scripts für korrekte {tag} Syntax - Entfernt alte ++tag++ und ++INS++ Syntax - Neue create-new-table-template.js für echte Loops 🌐 WebDAV-Features: - Direkter Template-Download über /webdav/templates/ - Template-Bearbeitung in Word möglich - Automatische Speicherung über WebDAV - Fallback auf Download bei Word-Problemen 📊 Template-Syntax-Migration: - {variable} statt ++variable++ - {#array}{field}{/array} statt ++INS array++ - Echte dynamische Tabellen ohne fixe Indizes - Verbesserte Tag-Erkennung und Analyse --- create-angebot-fixed.js | 107 +++++++++++++++++++++++++++ create-brief-fixed.js | 91 +++++++++++++++++++++++ create-new-table-template.js | 67 +++++++++++++++++ create-rechnung-fixed.js | 104 ++++++++++++++++++++++++++ create-simple-template.js | 17 +++-- public/templates.html | 122 ++++++++++++++++++++++--------- templates/angebot-template.docx | Bin 1581 -> 1384 bytes templates/brief-template.docx | Bin 1175 -> 1123 bytes templates/rechnung-template.docx | Bin 1367 -> 1372 bytes templates/simple-template.docx | Bin 7783 -> 7752 bytes templates/tabellen-template.docx | Bin 7962 -> 7957 bytes 11 files changed, 468 insertions(+), 40 deletions(-) create mode 100644 create-angebot-fixed.js create mode 100644 create-brief-fixed.js create mode 100644 create-new-table-template.js create mode 100644 create-rechnung-fixed.js diff --git a/create-angebot-fixed.js b/create-angebot-fixed.js new file mode 100644 index 0000000..97084ed --- /dev/null +++ b/create-angebot-fixed.js @@ -0,0 +1,107 @@ +const fs = require('fs'); +const path = require('path'); +const AdmZip = require('adm-zip'); + +function createAngebotTemplate() { + const zip = new AdmZip(); + + // [Content_Types].xml + const contentTypes = ` + + + + +`; + + // _rels/.rels + const rels = ` + + +`; + + // word/document.xml - Angebot Template mit docxtemplater Syntax + const document = ` + + + + + ANGEBOT + + + + + + + + Anbieter + Kunde + + + {firma} + {kunde_name} + + + {strasse} + {kunde_strasse} + + + {plz} {stadt} + {kunde_plz} {kunde_stadt} + + + E-Mail: {email} + E-Mail: {kunde_email} + + + + + + Angebotsnummer: {angebotsnummer} + Angebotsdatum: {datum} + Projekt: {projekt} + + + Sehr geehrte Damen und Herren, + + gerne unterbreiten wir Ihnen folgendes Angebot: + + + + + + Pos. + Leistung + Menge + Preis (€) + + + {#positionen}{nr} + {leistung} + {menge} + {preis}{/positionen} + + + + + Gesamtsumme: {gesamtsumme}€ + + + Mit freundlichen Grüßen + {ansprechpartner} + + +`; + + // Füge Dateien zum ZIP hinzu + zip.addFile('[Content_Types].xml', Buffer.from(contentTypes, 'utf8')); + zip.addFile('_rels/.rels', Buffer.from(rels, 'utf8')); + zip.addFile('word/document.xml', Buffer.from(document, 'utf8')); + + // Speichere die Datei + const outputPath = path.join(__dirname, 'templates', 'angebot-template.docx'); + zip.writeZip(outputPath); + + console.log('✅ Angebot-Template mit docxtemplater-Syntax erstellt:', outputPath); +} + +createAngebotTemplate(); \ No newline at end of file diff --git a/create-brief-fixed.js b/create-brief-fixed.js new file mode 100644 index 0000000..7871344 --- /dev/null +++ b/create-brief-fixed.js @@ -0,0 +1,91 @@ +const fs = require('fs'); +const path = require('path'); +const AdmZip = require('adm-zip'); + +function createBriefTemplate() { + const zip = new AdmZip(); + + // [Content_Types].xml + const contentTypes = ` + + + + +`; + + // _rels/.rels + const rels = ` + + +`; + + // word/document.xml - Brief Template + const document = ` + + + + + {firma} + + + + {strasse} + + + + {plz} {stadt} + + + + + + {empfaenger_name} + {empfaenger_strasse} + {empfaenger_plz} {empfaenger_stadt} + + + + + + + {datum} + + + + + Betreff: {betreff} + + + + {anrede}, + + + + {nachricht} + + + + Mit freundlichen Grüßen + + + + + {absender_name} + {position} + + +`; + + // Füge Dateien zum ZIP hinzu + zip.addFile('[Content_Types].xml', Buffer.from(contentTypes, 'utf8')); + zip.addFile('_rels/.rels', Buffer.from(rels, 'utf8')); + zip.addFile('word/document.xml', Buffer.from(document, 'utf8')); + + // Speichere die Datei + const outputPath = path.join(__dirname, 'templates', 'brief-template.docx'); + zip.writeZip(outputPath); + + console.log('✅ Brief-Template mit docxtemplater-Syntax erstellt:', outputPath); +} + +createBriefTemplate(); \ No newline at end of file diff --git a/create-new-table-template.js b/create-new-table-template.js new file mode 100644 index 0000000..0c17639 --- /dev/null +++ b/create-new-table-template.js @@ -0,0 +1,67 @@ +const fs = require('fs'); +const path = require('path'); +const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx'); + +async function createTableTemplate() { + const doc = new Document({ + sections: [{ + properties: {}, + children: [ + new Paragraph({ + children: [ + new TextRun({ + text: "DYNAMISCHES TABELLEN-TEMPLATE", + bold: true, + size: 32, + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ children: [new TextRun({ text: "Projekt: {projekt}", bold: true })] }), + new Paragraph({ children: [new TextRun({ text: "Datum: {datum}" })] }), + new Paragraph({ children: [new TextRun({ text: "Ersteller: {ersteller}" })] }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ + children: [new TextRun({ text: "MITARBEITERLISTE:", bold: true, size: 24 })], + }), + new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Name", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Position", bold: true })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }), + ], + }), + new TableRow({ + children: [ + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "{#mitarbeiter}{nr}" })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "{name}" })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "{position}" })] })] }), + new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "{email}{/mitarbeiter}" })] })] }), + ], + }), + ], + }), + new Paragraph({ children: [new TextRun({ text: "" })] }), + new Paragraph({ + children: [ + new TextRun({ + text: "Status: {status}", + bold: true + }) + ] + }), + ], + }], + }); + + const buffer = await Packer.toBuffer(doc); + const outputPath = path.join(__dirname, 'templates', 'tabellen-template.docx'); + fs.writeFileSync(outputPath, buffer); + + console.log('✅ Tabellen-Template mit docxtemplater Loop-Syntax erstellt'); +} + +createTableTemplate().catch(console.error); \ No newline at end of file diff --git a/create-rechnung-fixed.js b/create-rechnung-fixed.js new file mode 100644 index 0000000..7f60d8f --- /dev/null +++ b/create-rechnung-fixed.js @@ -0,0 +1,104 @@ +const fs = require('fs'); +const path = require('path'); +const AdmZip = require('adm-zip'); + +function createRechnungTemplate() { + const zip = new AdmZip(); + + // [Content_Types].xml + const contentTypes = ` + + + + +`; + + // _rels/.rels + const rels = ` + + +`; + + // word/document.xml - Rechnungs Template + const document = ` + + + + + RECHNUNG + + + + + + + + Rechnungssteller + Kunde + + + {firma} + {kunde_name} + + + {strasse} + {kunde_strasse} + + + {plz} {stadt} + {kunde_plz} {kunde_stadt} + + + + + + Rechnungsnummer: {rechnungsnummer} + Rechnungsdatum: {datum} + Leistungszeitraum: {leistungszeitraum} + + + + + + + Pos. + Beschreibung + Menge + Einzelpreis (€) + Gesamtpreis (€) + + + {#positionen}{nr} + {beschreibung} + {menge} + {einzelpreis} + {gesamtpreis}{/positionen} + + + + + + Nettobetrag: {nettobetrag}€ + Mehrwertsteuer (19%): {mwst}€ + Gesamtbetrag: {gesamtbetrag}€ + + + Zahlbar bis: {zahlbar_bis} + Verwendungszweck: {rechnungsnummer} + + +`; + + // Füge Dateien zum ZIP hinzu + zip.addFile('[Content_Types].xml', Buffer.from(contentTypes, 'utf8')); + zip.addFile('_rels/.rels', Buffer.from(rels, 'utf8')); + zip.addFile('word/document.xml', Buffer.from(document, 'utf8')); + + // Speichere die Datei + const outputPath = path.join(__dirname, 'templates', 'rechnung-template.docx'); + zip.writeZip(outputPath); + + console.log('✅ Rechnungs-Template mit docxtemplater-Syntax erstellt:', outputPath); +} + +createRechnungTemplate(); \ No newline at end of file diff --git a/create-simple-template.js b/create-simple-template.js index 041d111..7d0b293 100644 --- a/create-simple-template.js +++ b/create-simple-template.js @@ -20,21 +20,21 @@ async function createTestTemplate() { new Paragraph({ children: [ new TextRun({ - text: "Kunde: ++name++", + text: "Kunde: {name}", }), ], }), new Paragraph({ children: [ new TextRun({ - text: "E-Mail: ++email++", + text: "E-Mail: {email}", }), ], }), new Paragraph({ children: [ new TextRun({ - text: "Datum: ++date++", + text: "Datum: {datum}", }), ], }), @@ -56,7 +56,14 @@ async function createTestTemplate() { new Paragraph({ children: [ new TextRun({ - text: "Artikel 1: ++items[0].product++ (++items[0].quantity++x) - ++items[0].price++€", + text: "Projekt: {projekt}", + }), + ], + }), + new Paragraph({ + children: [ + new TextRun({ + text: "Status: {status}", }), ], }), @@ -70,7 +77,7 @@ async function createTestTemplate() { new Paragraph({ children: [ new TextRun({ - text: "Gesamtsumme: ++total++€", + text: "Gesamtsumme: {betrag}€", bold: true, }), ], diff --git a/public/templates.html b/public/templates.html index ef7075b..d7fcbcd 100644 --- a/public/templates.html +++ b/public/templates.html @@ -33,6 +33,23 @@ margin: 10px 0 0 0; opacity: 0.9; } + .webdav-info { + background: #e3f2fd; + border: 1px solid #1976d2; + border-radius: 5px; + padding: 15px; + margin: 20px 0; + } + .webdav-info h3 { + margin: 0 0 10px 0; + color: #1976d2; + } + .webdav-info code { + background: #fff; + padding: 2px 5px; + border-radius: 3px; + font-family: monospace; + } .content { padding: 30px; } @@ -150,6 +167,22 @@
+
+

🌐 WebDAV Template-Bearbeitung

+

Template direkt in Word bearbeiten:

+
    +
  • Klicken Sie auf "📝 In Word öffnen" bei einem Template
  • +
  • Word öffnet das Template direkt vom Server
  • +
  • Änderungen werden automatisch gespeichert
  • +
+

WebDAV-URLs:

+
    +
  • Templates: ${window.location.origin}/webdav/templates/
  • +
  • Generierte Dateien: ${window.location.origin}/webdav/output/
  • +
+

💡 Tipp: Sie können diese URLs auch als Netzlaufwerk in Windows einbinden!

+
+

🚀 Neue Features:

    @@ -211,7 +244,8 @@
    📄 ${template.name}
    📊 Größe: ${sizeKB} KB
    - 📅 Geändert: ${modifiedDate} + 📅 Geändert: ${modifiedDate}
    + 🏷️ Tags: ${template.tags} (${template.simpleTags} einfach, ${template.tables} Tabellen)
    - + + 📁 Download + +
    `; @@ -240,41 +274,38 @@ if (!rowCount) return; try { - const response = await fetch(`/demo-template/${templateName}`, { + const response = await fetch(`/demo/${templateName}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ rowCount: parseInt(rowCount) }) + body: JSON.stringify({ tables: { mitarbeiter: parseInt(rowCount), positionen: parseInt(rowCount) } }) }); const result = await response.json(); if (result.success) { - const message = `🚀 One-Click Demo erfolgreich erstellt! + const message = `🚀 Demo erfolgreich erstellt! -📄 Template: ${result.templateInfo.name} -📊 Analyse-Ergebnis: - • Tags gefunden: ${result.tagAnalysis.totalTags} - • Einfache Tags: ${result.tagAnalysis.tagBreakdown.simple} - • Tabellen: ${result.tagAnalysis.tagBreakdown.tables} +📄 Template: ${templateName} +� Demo-Dokument erstellt: ${result.file} +🎉 ${result.message} -📝 Demo-Dokument erstellt: - • Datei: ${result.demoDocument.filename} - • Tabellenzeilen: ${rowCount} pro Tabelle +📊 Analyse: + • Einfache Tags: ${result.analysis.simpleTags.length} + • Tabellen: ${result.analysis.tables.length} 🔗 Aktionen: -• Herunterladen: ${window.location.origin}${result.demoDocument.downloadUrl} -• In Word öffnen: ${result.demoDocument.msWordUrl} -• WebDAV: ${window.location.origin}${result.demoDocument.webdavUrl} +• Herunterladen: ${window.location.origin}${result.download} -📋 Gefundene Tags: ${result.tagAnalysis.foundTags.join(', ')}`; +📋 Gefundene Tags: ${result.analysis.simpleTags.join(', ')} +� Tabellen: ${result.analysis.tables.join(', ')}`; alert(message); - // Optional: Demo-Dokument direkt öffnen - if (confirm('Demo-Dokument jetzt in Word öffnen?')) { - window.location.href = result.demoDocument.msWordUrl; + // Optional: Demo-Dokument direkt herunterladen + if (confirm('Demo-Dokument jetzt herunterladen?')) { + window.location.href = result.download; } } else { alert('❌ Fehler: ' + result.error); @@ -290,12 +321,12 @@ if (!rowCount) return; try { - const response = await fetch(`/test-template/${templateName}`, { + const response = await fetch(`/demo/${templateName}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ rowCount: parseInt(rowCount) }) + body: JSON.stringify({ tables: { mitarbeiter: parseInt(rowCount), positionen: parseInt(rowCount) } }) }); const result = await response.json(); @@ -303,20 +334,19 @@ if (result.success) { const message = `✅ Test-Dokument erstellt! -📄 Datei: ${result.templateName} -🏷️ Tags gefunden: ${result.foundTags.length} -📊 Tabellen: ${result.tableNames.length} +📄 Datei: ${result.file} +� ${result.message} 🔗 Aktionen: -• Herunterladen: ${window.location.origin}${result.downloadUrl} -• In Word öffnen: ${result.msWordUrl} -• WebDAV: ${window.location.origin}${result.webdavUrl}`; +• Herunterladen: ${window.location.origin}${result.download} + +📊 Analyse: ${result.analysis.simpleTags.length} Tags, ${result.analysis.tables.length} Tabellen`; alert(message); - // Optional: Test-Dokument direkt öffnen - if (confirm('Test-Dokument jetzt in Word öffnen?')) { - window.location.href = result.msWordUrl; + // Optional: Test-Dokument direkt herunterladen + if (confirm('Test-Dokument jetzt herunterladen?')) { + window.location.href = result.download; } } else { alert('❌ Fehler: ' + result.error); @@ -327,6 +357,28 @@ } } + function openInWord(templateName) { + // WebDAV-URL für direktes Öffnen in Word + const webdavUrl = `${window.location.protocol}//${window.location.host}/webdav/templates/${templateName}`; + const msWordUrl = `ms-word:ofe|u|${webdavUrl}`; + + // Versuche Word zu öffnen + try { + window.location.href = msWordUrl; + + // Fallback-Information für den Benutzer + setTimeout(() => { + if (confirm(`Word konnte nicht automatisch geöffnet werden.\n\nMöchten Sie die Datei stattdessen herunterladen?\n\nWebDAV-URL: ${webdavUrl}`)) { + window.open(webdavUrl, '_blank'); + } + }, 2000); + + } catch (error) { + // Fallback: Datei herunterladen + window.open(webdavUrl, '_blank'); + } + } + // Templates beim Laden der Seite laden document.addEventListener('DOMContentLoaded', loadTemplates); diff --git a/templates/angebot-template.docx b/templates/angebot-template.docx index 17bfa3bc94ae455cf700e019eeb9cb3b338f8f89..4bdfdc76f66d00cb68a857dffff06034ef42e1da 100644 GIT binary patch delta 742 zcmV@6aWAS2mmv(kqp@aGqSPgAOU|fvO!xRise>q0ssJH3jhES z0001YZ*pWWWN%}2ZDnqBE_iKh%~suR(=Zgi?^9Ue1|-&$At4mCnLvz!praB)Tp}kq zO}+UivYnN#>J2Z#3+xegw>RNQIH}V==xCZ2#7%OJea`1EpW{z%pL`XZT!A)B$&UA= z+42a;SS5cvOM1ow|yt!KWVOnPC15JZhsGA_bdlDWvmeI4TK zA||8Yit>&ZJAsw>^J7E?dfZ8@@Q}p1%BWUW{2cQxfnO z43>oE=&o{JzoFDde1#Ea<#G}Kj8sr9R2tK9hN9Bks*;r}Txnb+E{(%A8Y>^U_DM2g zfa?6taW)dy!F-p?1hxUrvK{!_a57-{e@tsJ#qP_3)fY(xY=x-7%~(y1*$(|@c2!Yo z5X^u0^^_n@n%F%_I;1(_DtNooJw57C#se}ze_*^W;RT8#TMcWKY9ul($DEA=&`?$4+JpA$P=FwlU)CZX|%;I$j1vLhl1ONa406APtg8%>k delta 960 zcmV;x13&!e3atzbP)h>@6aWAS2msuxkqp@a+^ezYAOU~et3g{B41feO0{{Ro5C8xX z0001YZ*pWWWN%}2ZDnqBE_iKh)mLqA(=ZVJzF%SGYoIAZLa16ZZDK2v7*sHZ1VWRO zTwXTEj%;UT{WJWLe(N9OoR>D+T9&4r1YZ)LeSYro-RaIgIQ*3|atl%=f)A}%y}m_& z`yxnqG_-%dUYx#sYZ0X>4=58HhSnV@>(Jdhm<$5p=P7Va5P_?~WN5`&X9LGkehev9 zJ&^&&wNS{EY7}MUOoR+FDSS{04W!K3@Auz0DNVTL?h%5ho(S&Dun@9xRF22e#ox70 zCWBkbhL(@EL0XP`;FPON-9l5A8$8E+)wJIBdcS|#Qmt$E=;P?*_|t`%)8$Ga#igy`0mF8MhtV+ek#)VLdzI%^GwI*mft%X8%pj1GwTeg{MKf@KajS`R^9s zPcVOMjnjo>#-_GS3|SiJt*O>%HSt!Mo6|nxDwE*H8I_u2)}lpvf2#9IPO!MJ!Qu#D z#OFA|!cI#IYhW&*K~@8UQ1H#@Rz;+9-D*)=BJhM(JWta)R?V4JbTn(M~G@$pOK!&U+FPjg}^fBxfcb29`D``9ztnM^6u68A)40+)y{uF zX&I-T;bkUB|5#5MfugA{m=!g8DYmQ8n9YmyZFV5mlh4 z3@35z91H3Mq(;>oUX5T5~wtl)4lIF_z zMHhY`r|5ztLMJeWDbj#7wSMQ_Y>j_gzSG!PS2uzOiN>cW@Rr`O?djva&(MTV=QBw@ z&u9KkC~0I;tIV)ve=M8lG?(NOWH_HiNNQbHb)s;dXc9`m-d|<#NX}>Ykl<}S)#6TF zd&x-UQ{0T|r@s3KP)h*<6ay3h00;;G+^a!bARC72vH$=8E&%`l3jhEB005J_1S106 itCQdaKMvfhL0cFMfCMrF001u#lPv``23`dK0001|{JtFk diff --git a/templates/brief-template.docx b/templates/brief-template.docx index 6018df053eb2be0bdc4f1165c40553cb145a0bfb..c9e1ebfe745c5ea9f66b5dafaca2fa124b4daee2 100644 GIT binary patch delta 479 zcmV<50U-XD3F8P1P)h>@6aWAS2mnE{kqp@aL9(&uAOU|tvO!z5b0yqp0RR9R1^@sN z0001YZ*pWWWN%}2ZDnqBE_iKhy;RFi!!QuM_bV))p(z{?ikej704F34a7IqNNsQuk zWV;Qe>bLMC-1=iUF{lqh0g?8US?%o3W;WUJ~*^^2p;m9lOUzBG*0%osSZN>#oriPL{#-0oFc%dLwy`MX2?$m*glKvo$8 z%t1dQliTn9y~1W$mutVP^iFKG+BM}ke*9~noY|@DYm;B8+E0}D5k4XtUxC$-Wg%H4 z9`#^b=e`(f;IKS7PhU{JO928D0~7!N2nYZ{vXj;WHv&PjlMe(x4neX(TeWi~ V+-Ly+02&69Tm&@+lmh?&003p})1d$W delta 531 zcmV+u0_^?c2$u;AP)h>@6aWAS2mtS^kqp@a@2j!rAOU~xt3g}QZRcg20RRAw2mk;P z0001YZ*pWWWN%}2ZDnqBE_iKh#g@TRgD?<=?>+^SE45S)j>8lj4|?{XXD5&a;)D&8 zEUo%7UVM|jm`SnL((334)+$(lu~K8gPNp*!0OndqEE9HjdwqP) z0!thPR~mmIVNYP$MS3(}#zN<94M>5T0d2fYSVh#tQDpN9YHp{x0d#$ZHZ>>LG-b5Z zMl?p}V66kxDmtCb&Z3%2Wa&{5xLGqTo_x4&G+oriHzswDhdfxu3$7AoWLXi5(s|T% zZl!CX>GWAAcyCb}hJ|$bgrR>+UEez|`T+hB?J0kj3}3y#=$uA9hhgA(T+qn0TTrn> z5hy4$jtskUu2|#12j9cC#O?Uc`hH#msSZhfzD==u#S+Xt^1A1MuNgZ754z`vV>gc2 znQqSkfZ6js}GL&XchKAW?y&7E4R; zIEYdS4udM*HZ(56T`a1#Bd?=0?drwfS)l&o#O#@6aWAS2mn#Ckqp@aQL?e;AOU|-vO!ylu+H&e0ssJo3jhES z0001YZ*pWWWN%}2ZDnqBE_iKh)mC3`(=ZT!@29YWkhTY!ZlDROHj_YONU&-`3?yDA zC%L6z{u9~GLRwUlW>XCHZ5UI(>}mU;%qhE9OKK{>8BaP8_!x6oA9z<2Yno-WLupOt?m<+|}cUc7lXx*1*Olg2KU4BRD! z6{7{b)m-S;s2ZPDD4|sP^Vt{Vg5knYqc}-0sx&pSq-7N=hD*VUfwxAZvdgtSK`<7n zh_p7q7^pwF7BfK`?Dwe%VH;o}?XVBy&F*4MlzA(rXnmivx)qeeR)`kdv{6*+?a+_2 zYl?r;f?&d?bAmK!X!ay&krsrT;8p3CDct(jG|z02at^9bW@>$=hVJg*AvGyS?Bc%< z;s=Pe$q}azkQ;Fq8&H#>m0C?8J}sbhmZ0EmD2Ow`?>W+lY0oLFlRo*z$ZYgnB{Ym8_W z^7+iI#O9wWg(EOV`hX=Q!m1)x(j2p7kD7G_W0iq2*gK@4$kEBGr^o1kXWFztt~Ot% zFE*Pmo7~GyL>2K7Upqu|L+dLYGoLEr#~R&Dt#s?x=1QIYXTZ>iu&CY{1ozF~pN<*L z2;1Q1qeTPl{RU7=0Rj{Q6aWAS2mn#Cla>TG0#UM)&jddXQL;f>im=Y{VFCaEg$t7$ M1vLhZ1ONa40Nol_{r~^~ delta 725 zcmV;`0xJF73fBq@P)h>@6aWAS2mr^ckqp@a$E&gDAOU~Jt3g|{yLISX0ssIg3;+NT z0001YZ*pWWWN%}2ZDnqBE_iKh&6eG6(=Zgr@AniY5)vq)>4t>Sve&_Gw`q-`G@0^iakSY_{_6{3O zj{wFZ$uNIyd)HT;7q2~{G{uB6fzb9QpuFSgpfwH?5f>a#6GK21j@#Z)>pTns6%T<^ z)et$L=^F^isWz@mgRziFE=3GVnE;#xN6qG|fYS`U=ztJI?Tch$&CGXR?_PJ$Y@>Eva|OYj zgr%Yd-i=(0-!N47*@`6N&ExgiC!@ur%cR=4liXyL7Fx_@xq&Ma?;PjG(VC2vRj#h7 zlbNK4#Pumhzw@GB>6CubK2&NOBs+(}G@DWth>5<|8`m@K0_)-e)ErW8`vIa5lO!9!6` zkv3a3M8uS|$&{T+t4q;U+;enW@Ao|a|NZZ`p6C0&??2BiS}m|n_R=y70ECFe*;uj^ zyc_AkdzFVC3P&tvc-1nGM`=g{fEAR6(&9;6B*sd~+nR@Fw9OUBvGPp4PC=0|Hbu^f z-RvGfwBZMI;ms^I6;^C=X-lE|F35*dm^%N7oV4*nxo@X9wi z*4ATHB#CtGW6QacRctbo-lQ6ness^SI6j)Z!JwIGu7=K09#XlZy0Y+-wcdncK94zV z{g}onOJoXD2_c43fZAbN(%3!~oti<+-hFg*e3iERfwXp|Nz1+!Hz}s-VLJ66Jq?PoDtjVNJqXo(Z1oh^$-^k!OE|I;vRG)6{(3_lyB*j5=rjH- zH~WgU>r84*W>L4Bc3iL=bt-=ZhdNyJV7h$!w7E52L|bIW_=49~_3ew`70a5J*xaZH z-naGbh?#at1tYkoypP|j5dI+jK?FaeHe1%A>nfqVD!+qq;q^XBbUI^=e9Tf1b=x`* zJJ!@mB0s%gQL&Yf)4afmm^qSlEbfmxK}}>yTy5$Tsnbp=eJj@@X3+8c_|?h9!AnyUn8zt-6|=+z30RIw#0?ZrEi7rjzyG>eK4=Adg$&i zl=;g>nC0PD)A;t~v*t;Ocu3rlP=4gygnPaEd&O601W9yG0?l?j%IxO)cUt_L#>@)a zE@du0+77qm;@%A{dhYL*iB7eB;ZAXOM~^q0EbwaM^_QADrw*0}c=M)ubk=L8-6qWz zzV4_@bUPz(sY@F5uwP_3xBJtVbJV2lvHuCw@NE5#g>mJ?ET+X zwJ7Gu+U|7Lq|_#Nbp(fpAD!jAc(O@1)!#?)xp#=;tH?wRuWDK{*>q}RiBU{-zjp+# z#O=BDd2S5Um2&BZ@N>4_iH#~-b^NA}1+irmZCIYd%|XJ7c&E9Uyjk?ONs@;rsd`9e zND7`Js8vpvhfL9^j>ote3IGmB0e~_*Z0v{tCvF5M$|xX$8@N0+EVPvG6Vhm`mON+I zw)bRN@zqZE?&K}1vaY7PH25XSCM2B`4|&%w25+7W&uN<(91wCz{w;C*k|=on2m2%6m$WjcG~5sKl#S_PB;qFXBWlX_kG7 z{%emM%oymQH~(o-i@TgQSBdMV+|~17_$H#0&HaMqmaH*AlLQ|hn>4KBU~&4Lq5!^f zyq`3EgtYbrM0IK7RK8cTOp$Jw;sTP(;vVPBmRT8|Vei_1oL$T793t068_-Aa9>(W* zte0*p(Zx5nX2WJYehZ83(?}i_c)O za-!TccGDa;7L2kzhXu?RvAGU~Eqy+h2)EW(IO@&@TH23Y`}8K$bq@7&*%@Ao4PI{L zz%^!L0YC+=b4&!cI!qyT{w}656!=Q2K`ai1a4PNw!l5^K5yC1JuOre(rT6pF0F^QX zVpPTR#yx6(&x`M&e|SfQpF!n!qWe|tF%wN@YsKP?bd2PJ4N zMnHiU$P>!_)Co>-NHqX?i;)icf6Ks%|LE4wZr>W{clX~b@y!_k3>D_?RbNc~H2}ja;0@~$5uQnF`0s}F$`7)G#))Jk Sln73e1JM>6%UWq7>VE;LL&drP delta 1997 zcmX?M^W26fz?+$civa|t&YsAl$OI(y)q&*32z5r@sk0rUOa4SoG-hUCu$|n)C{=&D z=$3&0^ZColUn*03dKI4} zomnP*a#M-jU-wHB`~A;7Gbv1q&^mPU054z7^@BVgJ2sp=P+Rxw(yQHlmnOIxJmlfO z!y5MP=1Wg8H<_g(#%}{e;`}A17fhM)Pv?bIWPQPl6r~rt`WlTz)0lYgL}UvxEePn* z3}K(5a8gwMHS-b82agRMB~oJC7QAJz`=_Gf9-+B7h2tjADk1mnnK21zR>xz4R`>?{ zF|-!V>YBEYd&S8P6&HP;+wZ4J*cAkyJj8S{igT0N!=Qk`sbZVX?eU)6omBcsvhhgg z#RD_DI#<-+$?!6sTgW+c@AD%)YFEWr?*9Gu!0FJL5YY{Ne=;&%CNKT;y|Xbs*AX>|)hQEiI{={bgsDX;16@DI0NAZT;?L z--JI)IB)E7Fj>#8eQxg4QpT=@S1RHRo8~IEKalvc-SI7(LjB_e>l<#RWo};^LbtN6 zs+<gqOzNJA2@k6L0d0 zN2QPQuFveeF0_3?T5ryydgDhI4_!MXW~Be_a~8{|q7|Q_{uXU2V4K`{J9GEZ*$Z#g zu!n8C<;63#;RJ_>khV_x4cposGIe{a_wC-K{I#eiuFID(KjQupokI&BSMmhCsXVJN zeQmUdkhkG#*GbD}aIXl?(o74M%Kmxa)zsP_1+C}h=dH`v+i!ZgSkH9kqa&R4Ym!}( zSMB?4&v$0Kw%5dOHqS5Lx;< z|Cy9pzIKSjOI1G~xwjk5J0k^TZiiTN`~G3uTem!a{!SIOZd2jHm8vRLlHy54+)H0x zz4K&>^jT4v0n4V zn3^A%Kl7S&LW%S6cNb;l9hX0Mf6$3#PQBCo>iqLB@~~XS0nBBakFz{x0_L#EYuHse zK{@Ph$;&LO$+y^L>ZeYM&2Iwcum4;xZKphPmtpkTc69YjotbIf<-XNd(!MC?s{eW) z9w2C(Uv_h!|NK4A9SrSHSy?rdI;|F(sNyQ9v216+ZlSf|-4$(0&sRR$a*fNYgsJ^m z=s}-zTUyIk^G4_&a6Py<=A2LGw|lc?l8=8=Sn18Q&}qljups%qvi5r3)cYmu7T%XN zr#;YIKJQVO#O!+=dS2(Mmj2vWs{Vk-nOA5^`3x17`w{$MyW078r8sy!^kbbZ6} ze!njXmrXp4UtPE`|H1mFx82RNbV7D(vaQgvI6af~)z-Dww8BD|^gle<6r12XH@?j` zpY_A@%MHJ0Z{5+_oUuR1)oPKwn8);m=kp!}YRsPRzFTen;_26~GKEIY@=cm_Ofz!g za+}#t9|syq`kUo$wLb2vrLX!gcP`(WaL=oz`~MfNibn}zOU`-VAil}11PtlTf4O-X zK{OBV0T4a;9N$3@HIaWmNHj>`16b5WXc>t6M(8Y9@ote%VBz&*OF_b6;@)8HVu=+X zy}pu$AgV#q97OGu^gt^t`J{~GL6KV>QF(g@69a<{F9QQVxSWL%*C)qI$$>Q2OUW?q znLI~Iq8^?a7#SFvftmziYQgklb_Rx&{N#Y5{DNZrQ=5J^3d5>yqA(J?FQ;wA!pwH4?GVYsOvhWzCH(n{c<0)~{* zB%sI>AVyX5aFPr}$)i7WZ%qbDKLcWrlMrCZWJei!urcQ!K0kB_Xw@8`>rq1>b#lFo zB3R9(@;}>iftssR diff --git a/templates/tabellen-template.docx b/templates/tabellen-template.docx index 99117888de3d3bb3949c0955ee9c3a58dc30f48c..f777d1ffa26e1070eda8a92d2d30ec9fb8dda481 100644 GIT binary patch delta 2095 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