Add WebDAV integration and template improvements
✅ 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
This commit is contained in:
parent
a01423321b
commit
5371d5e479
107
create-angebot-fixed.js
Normal file
107
create-angebot-fixed.js
Normal file
@ -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 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||||
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||||
|
<Default Extension="xml" ContentType="application/xml"/>
|
||||||
|
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
||||||
|
</Types>`;
|
||||||
|
|
||||||
|
// _rels/.rels
|
||||||
|
const rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
||||||
|
</Relationships>`;
|
||||||
|
|
||||||
|
// word/document.xml - Angebot Template mit docxtemplater Syntax
|
||||||
|
const document = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||||
|
<w:body>
|
||||||
|
<w:p>
|
||||||
|
<w:pPr><w:jc w:val="center"/></w:pPr>
|
||||||
|
<w:r><w:rPr><w:b/><w:sz w:val="32"/></w:rPr><w:t>ANGEBOT</w:t></w:r>
|
||||||
|
</w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:tbl>
|
||||||
|
<w:tblPr><w:tblW w:w="0" w:type="auto"/></w:tblPr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Anbieter</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Kunde</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{firma}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{kunde_name}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{strasse}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{kunde_strasse}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{plz} {stadt}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{kunde_plz} {kunde_stadt}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>E-Mail: {email}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>E-Mail: {kunde_email}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
</w:tbl>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Angebotsnummer: {angebotsnummer}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Angebotsdatum: {datum}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Projekt: {projekt}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>Sehr geehrte Damen und Herren,</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>gerne unterbreiten wir Ihnen folgendes Angebot:</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:tbl>
|
||||||
|
<w:tblPr><w:tblW w:w="0" w:type="auto"/></w:tblPr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Pos.</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Leistung</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Menge</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Preis (€)</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{#positionen}{nr}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{leistung}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{menge}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{preis}{/positionen}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
</w:tbl>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Gesamtsumme: {gesamtsumme}€</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>Mit freundlichen Grüßen</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>{ansprechpartner}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
</w:body>
|
||||||
|
</w:document>`;
|
||||||
|
|
||||||
|
// 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();
|
||||||
91
create-brief-fixed.js
Normal file
91
create-brief-fixed.js
Normal file
@ -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 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||||
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||||
|
<Default Extension="xml" ContentType="application/xml"/>
|
||||||
|
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
||||||
|
</Types>`;
|
||||||
|
|
||||||
|
// _rels/.rels
|
||||||
|
const rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
||||||
|
</Relationships>`;
|
||||||
|
|
||||||
|
// word/document.xml - Brief Template
|
||||||
|
const document = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||||
|
<w:body>
|
||||||
|
<w:p>
|
||||||
|
<w:pPr><w:jc w:val="right"/></w:pPr>
|
||||||
|
<w:r><w:t>{firma}</w:t></w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:p>
|
||||||
|
<w:pPr><w:jc w:val="right"/></w:pPr>
|
||||||
|
<w:r><w:t>{strasse}</w:t></w:r>
|
||||||
|
</w:p>
|
||||||
|
<w:p>
|
||||||
|
<w:pPr><w:jc w:val="right"/></w:pPr>
|
||||||
|
<w:r><w:t>{plz} {stadt}</w:t></w:r>
|
||||||
|
</w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t>{empfaenger_name}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>{empfaenger_strasse}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>{empfaenger_plz} {empfaenger_stadt}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p>
|
||||||
|
<w:pPr><w:jc w:val="right"/></w:pPr>
|
||||||
|
<w:r><w:t>{datum}</w:t></w:r>
|
||||||
|
</w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Betreff: {betreff}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t>{anrede},</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t>{nachricht}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t>Mit freundlichen Grüßen</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t>{absender_name}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>{position}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
</w:body>
|
||||||
|
</w:document>`;
|
||||||
|
|
||||||
|
// 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();
|
||||||
67
create-new-table-template.js
Normal file
67
create-new-table-template.js
Normal file
@ -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);
|
||||||
104
create-rechnung-fixed.js
Normal file
104
create-rechnung-fixed.js
Normal file
@ -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 = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||||
|
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
||||||
|
<Default Extension="xml" ContentType="application/xml"/>
|
||||||
|
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
||||||
|
</Types>`;
|
||||||
|
|
||||||
|
// _rels/.rels
|
||||||
|
const rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||||
|
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
||||||
|
</Relationships>`;
|
||||||
|
|
||||||
|
// word/document.xml - Rechnungs Template
|
||||||
|
const document = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
||||||
|
<w:body>
|
||||||
|
<w:p>
|
||||||
|
<w:pPr><w:jc w:val="center"/></w:pPr>
|
||||||
|
<w:r><w:rPr><w:b/><w:sz w:val="36"/></w:rPr><w:t>RECHNUNG</w:t></w:r>
|
||||||
|
</w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:tbl>
|
||||||
|
<w:tblPr><w:tblW w:w="0" w:type="auto"/></w:tblPr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Rechnungssteller</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Kunde</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{firma}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{kunde_name}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{strasse}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{kunde_strasse}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{plz} {stadt}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{kunde_plz} {kunde_stadt}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
</w:tbl>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Rechnungsnummer: {rechnungsnummer}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Rechnungsdatum: {datum}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Leistungszeitraum: {leistungszeitraum}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:tbl>
|
||||||
|
<w:tblPr><w:tblW w:w="0" w:type="auto"/></w:tblPr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Pos.</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Beschreibung</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Menge</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Einzelpreis (€)</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Gesamtpreis (€)</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
<w:tr>
|
||||||
|
<w:tc><w:p><w:r><w:t>{#positionen}{nr}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{beschreibung}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{menge}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{einzelpreis}</w:t></w:r></w:p></w:tc>
|
||||||
|
<w:tc><w:p><w:r><w:t>{gesamtpreis}{/positionen}</w:t></w:r></w:p></w:tc>
|
||||||
|
</w:tr>
|
||||||
|
</w:tbl>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Nettobetrag: {nettobetrag}€</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Mehrwertsteuer (19%): {mwst}€</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:rPr><w:b/><w:sz w:val="24"/></w:rPr><w:t>Gesamtbetrag: {gesamtbetrag}€</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
<w:p><w:r><w:t></w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>Zahlbar bis: {zahlbar_bis}</w:t></w:r></w:p>
|
||||||
|
<w:p><w:r><w:t>Verwendungszweck: {rechnungsnummer}</w:t></w:r></w:p>
|
||||||
|
|
||||||
|
</w:body>
|
||||||
|
</w:document>`;
|
||||||
|
|
||||||
|
// 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();
|
||||||
@ -20,21 +20,21 @@ async function createTestTemplate() {
|
|||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: "Kunde: ++name++",
|
text: "Kunde: {name}",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: "E-Mail: ++email++",
|
text: "E-Mail: {email}",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: "Datum: ++date++",
|
text: "Datum: {datum}",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -56,7 +56,14 @@ async function createTestTemplate() {
|
|||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
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({
|
new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: "Gesamtsumme: ++total++€",
|
text: "Gesamtsumme: {betrag}€",
|
||||||
bold: true,
|
bold: true,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -33,6 +33,23 @@
|
|||||||
margin: 10px 0 0 0;
|
margin: 10px 0 0 0;
|
||||||
opacity: 0.9;
|
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 {
|
.content {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
}
|
}
|
||||||
@ -150,6 +167,22 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<button class="refresh-btn" onclick="loadTemplates()">🔄 Templates neu laden</button>
|
<button class="refresh-btn" onclick="loadTemplates()">🔄 Templates neu laden</button>
|
||||||
|
|
||||||
|
<div class="webdav-info">
|
||||||
|
<h3>🌐 WebDAV Template-Bearbeitung</h3>
|
||||||
|
<p><strong>Template direkt in Word bearbeiten:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Klicken Sie auf <strong>"📝 In Word öffnen"</strong> bei einem Template</li>
|
||||||
|
<li>Word öffnet das Template direkt vom Server</li>
|
||||||
|
<li>Änderungen werden automatisch gespeichert</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>WebDAV-URLs:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Templates: <code>${window.location.origin}/webdav/templates/</code></li>
|
||||||
|
<li>Generierte Dateien: <code>${window.location.origin}/webdav/output/</code></li>
|
||||||
|
</ul>
|
||||||
|
<p><em>💡 Tipp: Sie können diese URLs auch als Netzlaufwerk in Windows einbinden!</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="info-section">
|
<div class="info-section">
|
||||||
<h3>🚀 Neue Features:</h3>
|
<h3>🚀 Neue Features:</h3>
|
||||||
<ul>
|
<ul>
|
||||||
@ -211,7 +244,8 @@
|
|||||||
<div class="template-name">📄 ${template.name}</div>
|
<div class="template-name">📄 ${template.name}</div>
|
||||||
<div class="template-info">
|
<div class="template-info">
|
||||||
📊 Größe: ${sizeKB} KB<br>
|
📊 Größe: ${sizeKB} KB<br>
|
||||||
📅 Geändert: ${modifiedDate}
|
📅 Geändert: ${modifiedDate}<br>
|
||||||
|
🏷️ Tags: ${template.tags} (${template.simpleTags} einfach, ${template.tables} Tabellen)
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button onclick="demoTemplate('${template.name}')" class="btn btn-demo">
|
<button onclick="demoTemplate('${template.name}')" class="btn btn-demo">
|
||||||
@ -223,12 +257,12 @@
|
|||||||
<button onclick="testTemplate('${template.name}')" class="btn btn-test">
|
<button onclick="testTemplate('${template.name}')" class="btn btn-test">
|
||||||
🎲 Testen
|
🎲 Testen
|
||||||
</button>
|
</button>
|
||||||
<a href="${template.msWordUrl}" class="btn btn-word">
|
<a href="/webdav/templates/${template.name}" class="btn btn-webdav">
|
||||||
|
📁 Download
|
||||||
|
</a>
|
||||||
|
<button onclick="openInWord('${template.name}')" class="btn btn-word">
|
||||||
📝 In Word öffnen
|
📝 In Word öffnen
|
||||||
</a>
|
</button>
|
||||||
<a href="${template.webdavUrl}" target="_blank" class="btn btn-webdav">
|
|
||||||
🌐 WebDAV
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -240,41 +274,38 @@
|
|||||||
if (!rowCount) return;
|
if (!rowCount) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/demo-template/${templateName}`, {
|
const response = await fetch(`/demo/${templateName}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'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();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const message = `🚀 One-Click Demo erfolgreich erstellt!
|
const message = `🚀 Demo erfolgreich erstellt!
|
||||||
|
|
||||||
📄 Template: ${result.templateInfo.name}
|
📄 Template: ${templateName}
|
||||||
📊 Analyse-Ergebnis:
|
<EFBFBD> Demo-Dokument erstellt: ${result.file}
|
||||||
• Tags gefunden: ${result.tagAnalysis.totalTags}
|
🎉 ${result.message}
|
||||||
• Einfache Tags: ${result.tagAnalysis.tagBreakdown.simple}
|
|
||||||
• Tabellen: ${result.tagAnalysis.tagBreakdown.tables}
|
|
||||||
|
|
||||||
📝 Demo-Dokument erstellt:
|
📊 Analyse:
|
||||||
• Datei: ${result.demoDocument.filename}
|
• Einfache Tags: ${result.analysis.simpleTags.length}
|
||||||
• Tabellenzeilen: ${rowCount} pro Tabelle
|
• Tabellen: ${result.analysis.tables.length}
|
||||||
|
|
||||||
🔗 Aktionen:
|
🔗 Aktionen:
|
||||||
• Herunterladen: ${window.location.origin}${result.demoDocument.downloadUrl}
|
• Herunterladen: ${window.location.origin}${result.download}
|
||||||
• In Word öffnen: ${result.demoDocument.msWordUrl}
|
|
||||||
• WebDAV: ${window.location.origin}${result.demoDocument.webdavUrl}
|
|
||||||
|
|
||||||
📋 Gefundene Tags: ${result.tagAnalysis.foundTags.join(', ')}`;
|
📋 Gefundene Tags: ${result.analysis.simpleTags.join(', ')}
|
||||||
|
<EFBFBD> Tabellen: ${result.analysis.tables.join(', ')}`;
|
||||||
|
|
||||||
alert(message);
|
alert(message);
|
||||||
|
|
||||||
// Optional: Demo-Dokument direkt öffnen
|
// Optional: Demo-Dokument direkt herunterladen
|
||||||
if (confirm('Demo-Dokument jetzt in Word öffnen?')) {
|
if (confirm('Demo-Dokument jetzt herunterladen?')) {
|
||||||
window.location.href = result.demoDocument.msWordUrl;
|
window.location.href = result.download;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert('❌ Fehler: ' + result.error);
|
alert('❌ Fehler: ' + result.error);
|
||||||
@ -290,12 +321,12 @@
|
|||||||
if (!rowCount) return;
|
if (!rowCount) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/test-template/${templateName}`, {
|
const response = await fetch(`/demo/${templateName}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'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();
|
const result = await response.json();
|
||||||
@ -303,20 +334,19 @@
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
const message = `✅ Test-Dokument erstellt!
|
const message = `✅ Test-Dokument erstellt!
|
||||||
|
|
||||||
📄 Datei: ${result.templateName}
|
📄 Datei: ${result.file}
|
||||||
🏷️ Tags gefunden: ${result.foundTags.length}
|
<EFBFBD> ${result.message}
|
||||||
📊 Tabellen: ${result.tableNames.length}
|
|
||||||
|
|
||||||
🔗 Aktionen:
|
🔗 Aktionen:
|
||||||
• Herunterladen: ${window.location.origin}${result.downloadUrl}
|
• Herunterladen: ${window.location.origin}${result.download}
|
||||||
• In Word öffnen: ${result.msWordUrl}
|
|
||||||
• WebDAV: ${window.location.origin}${result.webdavUrl}`;
|
📊 Analyse: ${result.analysis.simpleTags.length} Tags, ${result.analysis.tables.length} Tabellen`;
|
||||||
|
|
||||||
alert(message);
|
alert(message);
|
||||||
|
|
||||||
// Optional: Test-Dokument direkt öffnen
|
// Optional: Test-Dokument direkt herunterladen
|
||||||
if (confirm('Test-Dokument jetzt in Word öffnen?')) {
|
if (confirm('Test-Dokument jetzt herunterladen?')) {
|
||||||
window.location.href = result.msWordUrl;
|
window.location.href = result.download;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert('❌ Fehler: ' + result.error);
|
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
|
// Templates beim Laden der Seite laden
|
||||||
document.addEventListener('DOMContentLoaded', loadTemplates);
|
document.addEventListener('DOMContentLoaded', loadTemplates);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user