Complete migration from docx-templates to docxtemplater

 Migration completed:
- server.js: Complete rewrite using docxtemplater + pizzip
- server-old.js: Backup of original docx-templates implementation
- package.json: Added docxtemplater and pizzip dependencies
- Removed docx-templates dependency

🚀 New features:
- True dynamic tables with {#array}{field}{/array} syntax
- Unlimited table rows (no more fixed array indices)
- New API endpoints: /analyze, /generate, /demo
- Simplified template creation scripts

🎯 Template improvements:
- Updated template syntax examples
- Added dynamic-template.docx with loop syntax
- Enhanced template analysis functionality

📊 Benefits:
- Real dynamic table generation
- Better template flexibility
- Cleaner API design
- Preserved SSL and WebDAV functionality
This commit is contained in:
dgsoft 2025-10-01 22:14:19 +02:00
parent 4ac60601f0
commit a01423321b
24 changed files with 2451 additions and 1092 deletions

132
create-angebot-template.js Normal file
View File

@ -0,0 +1,132 @@
const fs = require('fs');
const path = require('path');
const AdmZip = require('adm-zip');
function createOfferTemplate() {
try {
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
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>Interessent:</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>++straße++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++kunde_straße++</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>Ansprechpartner: ++ansprechpartner++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>Email: ++kunde_email++</w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:t>Email: ++email++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>Tel: ++kunde_telefon++</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>Gültig bis: ++gültig_bis++</w:t></w:r></w:p>
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Projekt: ++projekt_name++</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>Beschreibung</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Aufwand</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>++leistungen[0].nr++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++leistungen[0].titel++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++leistungen[0].beschreibung++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++leistungen[0].aufwand++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++leistungen[0].preis++</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>Gesamtpreis: ++gesamtpreis++</w:t></w:r></w:p>
<w:p><w:r><w:t>Alle Preise verstehen sich zzgl. der gesetzlichen Mehrwertsteuer.</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>Projektdauer:</w:t></w:r></w:p>
<w:p><w:r><w:t>Geplanter Projektstart: ++projektstart++</w:t></w:r></w:p>
<w:p><w:r><w:t>Voraussichtliche Fertigstellung: ++projektende++</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>Zahlungskonditionen:</w:t></w:r></w:p>
<w:p><w:r><w:t>++zahlungskonditionen++</w:t></w:r></w:p>
<w:p><w:r><w:t></w:t></w:r></w:p>
<w:p><w:r><w:t>Für Rückfragen stehen wir Ihnen gerne zur Verfügung.</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>`;
// Dateien zum ZIP hinzufügen
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'));
// DOCX speichern
const templatePath = path.join(__dirname, 'templates', 'angebot-template.docx');
fs.writeFileSync(templatePath, zip.toBuffer());
console.log('✅ Angebot-Template erstellt:', templatePath);
} catch (error) {
console.error('❌ Fehler beim Erstellen des Angebot-Templates:', error);
}
}
createOfferTemplate();

117
create-brief-template.js Normal file
View File

@ -0,0 +1,117 @@
const fs = require('fs');
const path = require('path');
const AdmZip = require('adm-zip');
function createLetterTemplate() {
try {
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:rPr><w:b/></w:rPr><w:t>++firma++</w:t></w:r>
</w:p>
<w:p>
<w:pPr><w:jc w:val="right"/></w:pPr>
<w:r><w:t>++straße++</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:pPr><w:jc w:val="right"/></w:pPr>
<w:r><w:t>Tel: ++telefon++</w:t></w:r>
</w:p>
<w:p>
<w:pPr><w:jc w:val="right"/></w:pPr>
<w:r><w:t>Email: ++email++</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></w:t></w:r></w:p>
<w:p><w:r><w:t>++empfänger_name++</w:t></w:r></w:p>
<w:p><w:r><w:t>++empfänger_straße++</w:t></w:r></w:p>
<w:p><w:r><w:t>++empfänger_plz++ ++empfänger_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>++ort++, ++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++</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>++einleitungstext++</w:t></w:r></w:p>
<w:p><w:r><w:t></w:t></w:r></w:p>
<w:p><w:r><w:t>++haupttext++</w:t></w:r></w:p>
<w:p><w:r><w:t></w:t></w:r></w:p>
<w:p><w:r><w:t>++schlusstext++</w:t></w:r></w:p>
<w:p><w:r><w:t></w:t></w:r></w:p>
<w:p><w:r><w:t>++grußformel++</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:p><w:r><w:t></w:t></w:r></w:p>
<w:p><w:r><w:rPr><w:i/></w:rPr><w:t>Anlagen: ++anlagen++</w:t></w:r></w:p>
</w:body>
</w:document>`;
// Dateien zum ZIP hinzufügen
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'));
// DOCX speichern
const templatePath = path.join(__dirname, 'templates', 'brief-template.docx');
fs.writeFileSync(templatePath, zip.toBuffer());
console.log('✅ Brief-Template erstellt:', templatePath);
} catch (error) {
console.error('❌ Fehler beim Erstellen des Brief-Templates:', error);
}
}
createLetterTemplate();

View File

@ -0,0 +1,55 @@
const fs = require('fs');
const path = require('path');
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
async function createDocxtemplaterTemplate() {
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
children: [new TextRun({ text: "ECHTE DYNAMISCHE TABELLEN", bold: true, size: 32 })],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Projekt: {projekt}" })] }),
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: "MITARBEITER:", bold: true, size: 24 })] }),
new Table({
rows: [
// Header Row
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 })] })] }),
],
}),
// ECHTE DYNAMISCHE Zeile mit docxtemplater Loop-Syntax
new TableRow({
children: [
new TableCell({ children: [new Paragraph("{#mitarbeiter}{nr}")] }),
new TableCell({ children: [new Paragraph("{name}")] }),
new TableCell({ children: [new Paragraph("{position}")] }),
new TableCell({ children: [new Paragraph("{email}{/mitarbeiter}")] }),
],
}),
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Status: {status}" })] }),
],
}],
});
const buffer = await Packer.toBuffer(doc);
const templatePath = path.join(__dirname, 'templates', 'dynamic-template.docx');
fs.writeFileSync(templatePath, buffer);
console.log('✅ Docxtemplater-Template erstellt: dynamic-template.docx');
console.log('📋 Loop-Syntax: {#mitarbeiter}{feldname}{/mitarbeiter}');
}
createDocxtemplaterTemplate().catch(console.error);

View File

@ -0,0 +1,56 @@
const fs = require('fs');
const path = require('path');
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
async function createHybridDynamicTemplate() {
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
children: [new TextRun({ text: "HYBRID DYNAMISCHE TABELLEN", bold: true, size: 32 })],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }),
new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }),
new Table({
rows: [
// Header Row
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 })] })] }),
],
}),
// Bis zu 10 dynamische Zeilen (werden server-seitig gefüllt/leer gelassen)
...Array.from({length: 10}, (_, i) =>
new TableRow({
children: [
new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].nr++`)] }),
new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].name++`)] }),
new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].position++`)] }),
new TableCell({ children: [new Paragraph(`++mitarbeiter[${i}].email++`)] }),
],
})
)
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }),
],
}],
});
const buffer = await Packer.toBuffer(doc);
const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx');
fs.writeFileSync(templatePath, buffer);
console.log('✅ Hybrid-dynamisches Template erstellt (bis zu 10 Zeilen)');
console.log('📋 Server füllt nur gewünschte Anzahl, Rest bleibt leer');
}
createHybridDynamicTemplate().catch(console.error);

270
create-invoice-template.js Normal file
View File

@ -0,0 +1,270 @@
const fs = require('fs');
const path = require('path');
const { createReport } = require('docx-templates');
// Rechnungsvorlage erstellen
const invoiceTemplate = `
<?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>RECHNUNG</w:t></w:r>
</w:p>
<w:p><w:r><w:t></w:t></w:r></w:p>
<w:tbl>
<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:p><w:r><w:t>++straße++</w:t></w:r></w:p>
<w:p><w:r><w:t>++plz++ ++stadt++</w:t></w:r></w:p>
<w:p><w:r><w:t>Tel: ++telefon++</w:t></w:r></w:p>
<w:p><w:r><w:t>Email: ++email++</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:p><w:r><w:t>++kunde_straße++</w:t></w:r></w:p>
<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:tbl>
<w:tr>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Rechnungsnummer:</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++rechnungsnummer++</w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Rechnungsdatum:</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++datum++</w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Fälligkeitsdatum:</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++fälligkeitsdatum++</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>Rechnungsposten:</w:t></w:r></w:p>
<w:tbl>
<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>Artikel/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>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[0].nr++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].artikel++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].menge++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].einzelpreis++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].gesamtpreis++</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:tbl>
<w:tr>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Nettobetrag:</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++nettobetrag++</w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>MwSt. (19%):</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++mwst++</w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Gesamtbetrag:</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++gesamtbetrag++</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:t>Vielen Dank für Ihr Vertrauen!</w:t></w:r></w:p>
<w:p><w:r><w:t>Bei Fragen erreichen Sie uns unter: ++email++</w:t></w:r></w:p>
</w:body>
</w:document>`;
// Minimales DOCX erstellen
const minimalDocx = Buffer.from([
'UEsDBAoAAAAAAHJZMlUAAAAAAAAAAAAAAQAAAFsQ=',
'b250ZW50X1R5cGVzXS54bWyLycgsyk1VKMsvyk1VKE5OzStJTVEozk8p0S8pzUlNTi0qSsxLTi2y0lEDAEVKB',
'A9QSwECCgAAAAAAclkyVQAAAAAAAAAAAAABAAoAABgAAAAAAAAAAAA==',
'[Content_Types].xml',
'UEsFBgAAAAEAAQAwAAAAOgAAAAA='
].join(''), 'base64');
// Einfaches Template erstellen mit createReport
async function createInvoiceTemplate() {
try {
const templatePath = path.join(__dirname, 'templates', 'rechnung-template.docx');
// Einfaches Template als Basis verwenden und dann überschreiben
const baseTemplate = fs.readFileSync(path.join(__dirname, 'templates', 'simple-template.docx'));
// Rechnung-Template mit erweiterten Tags erstellen
const invoiceContent = await createReport({
template: baseTemplate,
data: {
// Dummy-Daten um Template-Struktur zu erstellen
firma: '++firma++',
straße: '++straße++',
plz: '++plz++',
stadt: '++stadt++',
telefon: '++telefon++',
email: '++email++',
kunde_name: '++kunde_name++',
kunde_straße: '++kunde_straße++',
kunde_plz: '++kunde_plz++',
kunde_stadt: '++kunde_stadt++',
rechnungsnummer: '++rechnungsnummer++',
datum: '++datum++',
fälligkeitsdatum: '++fälligkeitsdatum++',
positionen: [
{
nr: '++positionen[0].nr++',
artikel: '++positionen[0].artikel++',
menge: '++positionen[0].menge++',
einzelpreis: '++positionen[0].einzelpreis++',
gesamtpreis: '++positionen[0].gesamtpreis++'
}
],
nettobetrag: '++nettobetrag++',
mwst: '++mwst++',
gesamtbetrag: '++gesamtbetrag++'
}
});
fs.writeFileSync(templatePath, invoiceContent);
console.log('✅ Rechnungs-Template erstellt:', templatePath);
} catch (error) {
console.error('❌ Fehler beim Erstellen des Rechnungs-Templates:', error);
}
}
// Da wir das base template brauchen, erstellen wir das Template manuell
const fs2 = require('fs');
const AdmZip = require('adm-zip');
function createInvoiceTemplateManual() {
try {
// Neues ZIP für DOCX erstellen
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 - Rechnung 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="32"/></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>Firma: ++firma++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Kunde: ++kunde_name++</w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:t>++straße++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++kunde_straße++</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>Tel: ++telefon++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t></w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:t>Email: ++email++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t></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>Datum: ++datum++</w:t></w:r></w:p>
<w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Fällig: ++fälligkeitsdatum++</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>Rechnungsposten:</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>Artikel</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:tc><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>Gesamt</w:t></w:r></w:p></w:tc>
</w:tr>
<w:tr>
<w:tc><w:p><w:r><w:t>++positionen[0].nr++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].artikel++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].menge++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].einzelpreis++</w:t></w:r></w:p></w:tc>
<w:tc><w:p><w:r><w:t>++positionen[0].gesamtpreis++</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>MwSt. (19%): ++mwst++</w:t></w:r></w:p>
<w:p><w:r><w:rPr><w:b/></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>Vielen Dank für Ihr Vertrauen!</w:t></w:r></w:p>
</w:body>
</w:document>`;
// Dateien zum ZIP hinzufügen
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'));
// DOCX speichern
const templatePath = path.join(__dirname, 'templates', 'rechnung-template.docx');
fs2.writeFileSync(templatePath, zip.toBuffer());
console.log('✅ Rechnungs-Template erstellt:', templatePath);
} catch (error) {
console.error('❌ Fehler beim Erstellen des Rechnungs-Templates:', error);
}
}
createInvoiceTemplateManual();

View File

@ -0,0 +1,62 @@
const fs = require('fs');
const path = require('path');
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
async function createRealDynamicTemplate() {
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
children: [new TextRun({ text: "ECHTE DYNAMISCHE TABELLEN", bold: true, size: 32 })],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }),
new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }),
new Table({
rows: [
// Header Row
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 })] })] }),
],
}),
// Template Row - mit w:tr repeat
new TableRow({
children: [
new TableCell({
children: [new Paragraph("++FOR mitarbeiter++Nr: ++nr++")]
}),
new TableCell({
children: [new Paragraph("++name++")]
}),
new TableCell({
children: [new Paragraph("++position++")]
}),
new TableCell({
children: [new Paragraph("++email+++/FOR++")]
}),
],
}),
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }),
],
}],
});
const buffer = await Packer.toBuffer(doc);
const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx');
fs.writeFileSync(templatePath, buffer);
console.log('✅ Template mit FOR-Loop Syntax erstellt');
console.log('📋 Syntax: ++FOR mitarbeiter++...++/FOR++');
}
createRealDynamicTemplate().catch(console.error);

View File

@ -0,0 +1,72 @@
const fs = require('fs');
const path = require('path');
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
async function createWorkingTableTemplate() {
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
children: [new TextRun({ text: "DYNAMISCHE TABELLEN DEMO", bold: true, size: 32 })],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }),
new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }),
new Table({
rows: [
// Header
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 })] })] }),
],
}),
// Dynamische Zeilen - Row 0
new TableRow({
children: [
new TableCell({ children: [new Paragraph("++mitarbeiter[0].nr++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[0].name++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[0].position++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[0].email++")] }),
],
}),
// Dynamische Zeilen - Row 1
new TableRow({
children: [
new TableCell({ children: [new Paragraph("++mitarbeiter[1].nr++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[1].name++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[1].position++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[1].email++")] }),
],
}),
// Dynamische Zeilen - Row 2
new TableRow({
children: [
new TableCell({ children: [new Paragraph("++mitarbeiter[2].nr++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[2].name++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[2].position++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[2].email++")] }),
],
}),
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }),
],
}],
});
const buffer = await Packer.toBuffer(doc);
const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx');
fs.writeFileSync(templatePath, buffer);
console.log('✅ Funktionierendes Tabellen-Template mit Array-Indizes erstellt');
console.log('📋 Syntax: ++mitarbeiter[0].feldname++, ++mitarbeiter[1].feldname++, etc.');
}
createWorkingTableTemplate().catch(console.error);

View File

@ -2,7 +2,6 @@ const fs = require('fs');
const path = require('path');
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
// Erstelle ein Template mit Tabelle für Listen
async function createTableTemplate() {
const doc = new Document({
sections: [{
@ -11,123 +10,76 @@ async function createTableTemplate() {
new Paragraph({
children: [
new TextRun({
text: "RECHNUNG",
text: "TABELLEN DEMO DOKUMENT",
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: "",
}),
],
children: [new TextRun({ text: "MITARBEITERLISTE:", bold: true, size: 24 })],
}),
new Paragraph({
children: [
new TextRun({
text: "Kunde: ++name++",
}),
],
}),
new Paragraph({
children: [
new TextRun({
text: "E-Mail: ++email++",
}),
],
}),
new Paragraph({
children: [
new TextRun({
text: "Datum: ++date++",
}),
],
}),
new Paragraph({
children: [
new TextRun({
text: "",
}),
],
}),
new Paragraph({
children: [
new TextRun({
text: "ARTIKEL",
bold: true,
}),
],
}),
// Einfache Tabelle für Artikel
new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: [new Paragraph("Produkt")],
}),
new TableCell({
children: [new Paragraph("Menge")],
}),
new TableCell({
children: [new Paragraph("Preis")],
}),
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: "Abteilung", bold: true })] })] }),
new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("++items[0].product++")],
}),
new TableCell({
children: [new Paragraph("++items[0].quantity++")],
}),
new TableCell({
children: [new Paragraph("++items[0].price++€")],
}),
new TableCell({ children: [new Paragraph("++INS mitarbeiterRows++Nr: ++nr++")] }),
new TableCell({ children: [new Paragraph("++name++")] }),
new TableCell({ children: [new Paragraph("++position++")] }),
new TableCell({ children: [new Paragraph("++abteilung++")] }),
new TableCell({ children: [new Paragraph("++email++++/INS++")] }),
],
}),
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "PROJEKTAUFGABEN:", 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: "Aufgabe", bold: true })] })] }),
new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Status", bold: true })] })] }),
new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Deadline", bold: true })] })] }),
],
}),
new TableRow({
children: [
new TableCell({
children: [new Paragraph("++items[1].product++")],
}),
new TableCell({
children: [new Paragraph("++items[1].quantity++")],
}),
new TableCell({
children: [new Paragraph("++items[1].price++€")],
}),
new TableCell({ children: [new Paragraph("++INS aufgabenRows++Nr: ++nr++")] }),
new TableCell({ children: [new Paragraph("++titel++")] }),
new TableCell({ children: [new Paragraph("++status++")] }),
new TableCell({ children: [new Paragraph("++deadline++++/INS++")] }),
],
}),
],
}),
new Paragraph({
children: [
new TextRun({
text: "",
}),
],
}),
new Paragraph({
children: [
new TextRun({
text: "Gesamtsumme: ++total++€",
bold: true,
}),
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Zusammenfassung: ++zusammenfassung++" })] }),
new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }),
],
}],
});
const buffer = await Packer.toBuffer(doc);
const outputPath = path.join(__dirname, 'templates', 'tabellen-template.docx');
fs.writeFileSync(outputPath, buffer);
const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx');
fs.writeFileSync(templatePath, buffer);
console.log('✅ Tabellen-Template erstellt:', outputPath);
console.log('✅ Tabellen-Template mit LOOP-Syntax erstellt');
}
createTableTemplate().catch(console.error);
createTableTemplate().catch(console.error);

View File

@ -0,0 +1,62 @@
const fs = require('fs');
const path = require('path');
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
async function createTrulyDynamicTemplate() {
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
children: [new TextRun({ text: "ECHTE DYNAMISCHE TABELLEN", bold: true, size: 32 })],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }),
new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }),
new Table({
rows: [
// Header Row (statisch)
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 })] })] }),
],
}),
// DYNAMISCHE Zeile mit w:tr Loop-Tag
new TableRow({
children: [
new TableCell({
children: [new Paragraph("++#mitarbeiter ++nr++")]
}),
new TableCell({
children: [new Paragraph("++name++")]
}),
new TableCell({
children: [new Paragraph("++position++")]
}),
new TableCell({
children: [new Paragraph("++email++ ++/mitarbeiter++")]
}),
],
}),
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }),
],
}],
});
const buffer = await Packer.toBuffer(doc);
const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx');
fs.writeFileSync(templatePath, buffer);
console.log('✅ Echte dynamische Tabelle mit Loop-Syntax erstellt');
console.log('📋 Syntax: ++#mitarbeiter ++feldname++ ++/mitarbeiter++');
}
createTrulyDynamicTemplate().catch(console.error);

View File

@ -0,0 +1,72 @@
const fs = require('fs');
const path = require('path');
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
async function createWorkingTableTemplate() {
const doc = new Document({
sections: [{
properties: {},
children: [
new Paragraph({
children: [new TextRun({ text: "DYNAMISCHE TABELLEN DEMO", bold: true, size: 32 })],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Projekt: ++projekt++" })] }),
new Paragraph({ children: [new TextRun({ text: "Datum: ++datum++" })] }),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "MITARBEITER:", bold: true, size: 24 })] }),
new Table({
rows: [
// Header
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 })] })] }),
],
}),
// Dynamische Zeilen - Row 0
new TableRow({
children: [
new TableCell({ children: [new Paragraph("++mitarbeiter[0].nr++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[0].name++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[0].position++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[0].email++")] }),
],
}),
// Dynamische Zeilen - Row 1
new TableRow({
children: [
new TableCell({ children: [new Paragraph("++mitarbeiter[1].nr++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[1].name++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[1].position++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[1].email++")] }),
],
}),
// Dynamische Zeilen - Row 2
new TableRow({
children: [
new TableCell({ children: [new Paragraph("++mitarbeiter[2].nr++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[2].name++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[2].position++")] }),
new TableCell({ children: [new Paragraph("++mitarbeiter[2].email++")] }),
],
}),
],
}),
new Paragraph({ children: [new TextRun({ text: "" })] }),
new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }),
],
}],
});
const buffer = await Packer.toBuffer(doc);
const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx');
fs.writeFileSync(templatePath, buffer);
console.log('✅ Funktionierendes Tabellen-Template mit Array-Indizes erstellt');
console.log('📋 Syntax: ++mitarbeiter[0].feldname++, ++mitarbeiter[1].feldname++, etc.');
}
createWorkingTableTemplate().catch(console.error);

40
package-lock.json generated
View File

@ -13,8 +13,11 @@
"cors": "^2.8.5",
"docx": "^9.5.1",
"docx-templates": "^4.10.2",
"docxtemplater": "^3.66.4",
"express": "^4.18.2",
"jszip-utils": "^0.1.0",
"multer": "^1.4.5-lts.1",
"pizzip": "^3.2.0",
"webdav-server": "^2.6.2"
},
"devDependencies": {
@ -29,6 +32,14 @@
"undici-types": "~7.13.0"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.9.8",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz",
"integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==",
"engines": {
"node": ">=14.6"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -367,6 +378,17 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
},
"node_modules/docxtemplater": {
"version": "3.66.4",
"resolved": "https://registry.npmjs.org/docxtemplater/-/docxtemplater-3.66.4.tgz",
"integrity": "sha512-TzkSgWiZpisMvMyfT1wGDLVR4YsMxqg7jxdfKo7Cs76+XfwcyOgFKz7HOO0NyYVVZG2MOoTvhr6o7u6YnpPn1w==",
"dependencies": {
"@xmldom/xmldom": "^0.9.8"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -770,6 +792,11 @@
"setimmediate": "^1.0.5"
}
},
"node_modules/jszip-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/jszip-utils/-/jszip-utils-0.1.0.tgz",
"integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg=="
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@ -1044,6 +1071,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pizzip": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/pizzip/-/pizzip-3.2.0.tgz",
"integrity": "sha512-X4NPNICxCfIK8VYhF6wbksn81vTiziyLbvKuORVAmolvnUzl1A1xmz9DAWKxPRq9lZg84pJOOAMq3OE61bD8IQ==",
"dependencies": {
"pako": "^2.1.0"
}
},
"node_modules/pizzip/node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

View File

@ -13,8 +13,11 @@
"cors": "^2.8.5",
"docx": "^9.5.1",
"docx-templates": "^4.10.2",
"docxtemplater": "^3.66.4",
"express": "^4.18.2",
"jszip-utils": "^0.1.0",
"multer": "^1.4.5-lts.1",
"pizzip": "^3.2.0",
"webdav-server": "^2.6.2"
},
"devDependencies": {

View File

@ -92,6 +92,14 @@
.btn-test:hover {
background-color: #218838;
}
.btn-demo {
background-color: #ff6b35;
color: white;
font-weight: bold;
}
.btn-demo:hover {
background-color: #e55a2b;
}
.btn-word {
background-color: #0066cc;
color: white;
@ -145,7 +153,8 @@
<div class="info-section">
<h3>🚀 Neue Features:</h3>
<ul>
<li><strong>📊 Analysieren:</strong> Alle Template-Tags automatisch erkennen</li>
<li><strong><EFBFBD> One-Click Demo:</strong> Analyse + Demo-Dokument in einem Schritt</li>
<li><strong><EFBFBD>📊 Analysieren:</strong> Alle Template-Tags automatisch erkennen</li>
<li><strong>🎲 Testen:</strong> Test-Dokument mit Zufallsdaten erstellen</li>
<li><strong>📝 MS Word:</strong> Direkt in Word öffnen und bearbeiten</li>
<li><strong>🌐 WebDAV:</strong> Browser-basierte Dateiverwaltung</li>
@ -205,6 +214,9 @@
📅 Geändert: ${modifiedDate}
</div>
<div class="action-buttons">
<button onclick="demoTemplate('${template.name}')" class="btn btn-demo">
🚀 Demo
</button>
<a href="${template.analyzeUrl}" target="_blank" class="btn btn-analyze">
📊 Analysieren
</a>
@ -223,6 +235,56 @@
return card;
}
async function demoTemplate(templateName) {
const rowCount = prompt('Anzahl Tabellenzeilen für Demo:', '3');
if (!rowCount) return;
try {
const response = await fetch(`/demo-template/${templateName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ rowCount: parseInt(rowCount) })
});
const result = await response.json();
if (result.success) {
const message = `🚀 One-Click 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}
📝 Demo-Dokument erstellt:
• Datei: ${result.demoDocument.filename}
• Tabellenzeilen: ${rowCount} pro Tabelle
🔗 Aktionen:
• Herunterladen: ${window.location.origin}${result.demoDocument.downloadUrl}
• In Word öffnen: ${result.demoDocument.msWordUrl}
• WebDAV: ${window.location.origin}${result.demoDocument.webdavUrl}
📋 Gefundene Tags: ${result.tagAnalysis.foundTags.join(', ')}`;
alert(message);
// Optional: Demo-Dokument direkt öffnen
if (confirm('Demo-Dokument jetzt in Word öffnen?')) {
window.location.href = result.demoDocument.msWordUrl;
}
} else {
alert('❌ Fehler: ' + result.error);
}
} catch (error) {
alert('❌ Fehler beim Erstellen der Demo: ' + error.message);
}
}
async function testTemplate(templateName) {
const rowCount = prompt('Anzahl Tabellenzeilen für Test:', '3');
if (!rowCount) return;

File diff suppressed because it is too large Load Diff

1161
server.js

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,52 @@ Gesamtsumme: {{total}}€
4. Speichern Sie die Datei als "template-beispiel.docx" im templates/ Ordner
5. Verwenden Sie diese Template-Namen in der Web-Oberfläche
WICHTIG:
- Verwenden Sie {{}} für Platzhalter
- Für Listen/Tabellen verwenden Sie {{#array}} ... {{/array}}
- Innerhalb von Listen können Sie auf Eigenschaften zugreifen: {{property}}
📄 DOCX TEMPLATE ANLEITUNG
============================
🏷️ VERFÜGBARE TEMPLATES:
1. simple-template.docx
- Einfaches Template mit Name, Email, Datum, Tabelle
- Perfekt für erste Tests
2. tabellen-template.docx
- Erweiterte Tabellen-Funktionalität
- Mehrere Tabellenzeilen-Beispiele
3. test-template.docx
- Umfassendes Test-Template
- Verschiedene Tag-Typen zum Experimentieren
4. rechnung-template.docx ⭐ NEU
- Professionelle Rechnungsvorlage
- Firma, Kunde, Rechnungsposten, MwSt
- Tags: ++firma++, ++kunde_name++, ++positionen[0].artikel++
5. angebot-template.docx ⭐ NEU
- Angebots-/Kostenvoranschlag-Template
- Projektinformationen, Leistungen, Preise
- Tags: ++projekt_name++, ++leistungen[0].titel++
6. brief-template.docx ⭐ NEU
- Geschäftsbrief-Template
- Absender, Empfänger, Betreff, Brieftext
- Tags: ++betreff++, ++anrede++, ++haupttext++
🎯 TEMPLATE-SYNTAX:
Verwende ++tagname++ für Platzhalter
Verwende ++tabelle[0].spalte++ für Tabellen
🚀 NEUE FUNKTIONEN:
- 📊 Analysieren: /analyze-template/templatename.docx
- 🎲 Testen: POST /test-template/templatename.docx
- 🚀 Demo: POST /demo-template/templatename.docx (Analyse + Demo in einem!)
💡 BEISPIEL-NUTZUNG:
curl -X POST -d '{"rowCount": 5}' \
http://localhost:3000/demo-template/rechnung-template.docx
📱 WEB-INTERFACE:
http://localhost:3000/templates.html
Das war's! Viel Spaß beim Template-Erstellen! 🎉

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

48
test-docxtemplater.js Normal file
View File

@ -0,0 +1,48 @@
const fs = require('fs');
const Docxtemplater = require('docxtemplater');
const PizZip = require('pizzip');
// Test mit docxtemplater
function testDocxtemplater() {
// Beispiel-Template-Inhalt (normalerweise aus .docx gelesen)
const content = fs.readFileSync('./templates/simple-template.docx', 'binary');
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
// Echte dynamische Daten
const data = {
projekt: "Dynamisches Projekt",
datum: new Date().toLocaleDateString('de-DE'),
mitarbeiter: [
{ nr: 1, name: "Max Mustermann", position: "Developer", email: "max@example.com" },
{ nr: 2, name: "Anna Schmidt", position: "Designer", email: "anna@example.com" },
{ nr: 3, name: "Tom Weber", position: "Manager", email: "tom@example.com" },
{ nr: 4, name: "Lisa König", position: "Analyst", email: "lisa@example.com" },
{ nr: 5, name: "Jan Peters", position: "Tester", email: "jan@example.com" }
],
status: "Aktiv"
};
console.log('📊 Teste mit docxtemplater und', data.mitarbeiter.length, 'Mitarbeitern');
try {
doc.render(data);
const buf = doc.getZip().generate({
type: 'nodebuffer',
compression: 'DEFLATE',
});
fs.writeFileSync('./output/docxtemplater_test.docx', buf);
console.log('✅ docxtemplater Test erfolgreich!');
} catch (error) {
console.log('❌ docxtemplater Fehler:', error.message);
}
}
testDocxtemplater();