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:
parent
4ac60601f0
commit
a01423321b
132
create-angebot-template.js
Normal file
132
create-angebot-template.js
Normal 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
117
create-brief-template.js
Normal 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();
|
||||||
55
create-docxtemplater-template.js
Normal file
55
create-docxtemplater-template.js
Normal 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);
|
||||||
56
create-hybrid-dynamic-template.js
Normal file
56
create-hybrid-dynamic-template.js
Normal 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
270
create-invoice-template.js
Normal 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();
|
||||||
62
create-real-dynamic-template.js
Normal file
62
create-real-dynamic-template.js
Normal 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);
|
||||||
72
create-simple-table-template.js
Normal file
72
create-simple-table-template.js
Normal 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);
|
||||||
@ -2,7 +2,6 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
|
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell } = require('docx');
|
||||||
|
|
||||||
// Erstelle ein Template mit Tabelle für Listen
|
|
||||||
async function createTableTemplate() {
|
async function createTableTemplate() {
|
||||||
const doc = new Document({
|
const doc = new Document({
|
||||||
sections: [{
|
sections: [{
|
||||||
@ -11,123 +10,76 @@ async function createTableTemplate() {
|
|||||||
new Paragraph({
|
new Paragraph({
|
||||||
children: [
|
children: [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: "RECHNUNG",
|
text: "TABELLEN DEMO DOKUMENT",
|
||||||
bold: true,
|
bold: true,
|
||||||
size: 32,
|
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({
|
new Paragraph({
|
||||||
children: [
|
children: [new TextRun({ text: "MITARBEITERLISTE:", bold: true, size: 24 })],
|
||||||
new TextRun({
|
|
||||||
text: "",
|
|
||||||
}),
|
}),
|
||||||
],
|
|
||||||
}),
|
|
||||||
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({
|
new Table({
|
||||||
rows: [
|
rows: [
|
||||||
new TableRow({
|
new TableRow({
|
||||||
children: [
|
children: [
|
||||||
new TableCell({
|
new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Nr.", bold: true })] })] }),
|
||||||
children: [new Paragraph("Produkt")],
|
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({
|
new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "Abteilung", bold: true })] })] }),
|
||||||
children: [new Paragraph("Menge")],
|
new TableCell({ children: [new Paragraph({ children: [new TextRun({ text: "E-Mail", bold: true })] })] }),
|
||||||
}),
|
|
||||||
new TableCell({
|
|
||||||
children: [new Paragraph("Preis")],
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
new TableRow({
|
new TableRow({
|
||||||
children: [
|
children: [
|
||||||
new TableCell({
|
new TableCell({ children: [new Paragraph("++INS mitarbeiterRows++Nr: ++nr++")] }),
|
||||||
children: [new Paragraph("++items[0].product++")],
|
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 TableCell({
|
],
|
||||||
children: [new Paragraph("++items[0].quantity++")],
|
|
||||||
}),
|
|
||||||
new TableCell({
|
|
||||||
children: [new Paragraph("++items[0].price++€")],
|
|
||||||
}),
|
}),
|
||||||
|
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({
|
new TableRow({
|
||||||
children: [
|
children: [
|
||||||
new TableCell({
|
new TableCell({ children: [new Paragraph("++INS aufgabenRows++Nr: ++nr++")] }),
|
||||||
children: [new Paragraph("++items[1].product++")],
|
new TableCell({ children: [new Paragraph("++titel++")] }),
|
||||||
}),
|
new TableCell({ children: [new Paragraph("++status++")] }),
|
||||||
new TableCell({
|
new TableCell({ children: [new Paragraph("++deadline++++/INS++")] }),
|
||||||
children: [new Paragraph("++items[1].quantity++")],
|
|
||||||
}),
|
|
||||||
new TableCell({
|
|
||||||
children: [new Paragraph("++items[1].price++€")],
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
new Paragraph({
|
new Paragraph({ children: [new TextRun({ text: "" })] }),
|
||||||
children: [
|
new Paragraph({ children: [new TextRun({ text: "Zusammenfassung: ++zusammenfassung++" })] }),
|
||||||
new TextRun({
|
new Paragraph({ children: [new TextRun({ text: "Status: ++status++" })] }),
|
||||||
text: "",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
new Paragraph({
|
|
||||||
children: [
|
|
||||||
new TextRun({
|
|
||||||
text: "Gesamtsumme: ++total++€",
|
|
||||||
bold: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
const buffer = await Packer.toBuffer(doc);
|
const buffer = await Packer.toBuffer(doc);
|
||||||
const outputPath = path.join(__dirname, 'templates', 'tabellen-template.docx');
|
const templatePath = path.join(__dirname, 'templates', 'tabellen-template.docx');
|
||||||
fs.writeFileSync(outputPath, buffer);
|
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);
|
||||||
62
create-truly-dynamic-template.js
Normal file
62
create-truly-dynamic-template.js
Normal 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);
|
||||||
72
create-working-table-template.js
Normal file
72
create-working-table-template.js
Normal 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
40
package-lock.json
generated
@ -13,8 +13,11 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"docx": "^9.5.1",
|
"docx": "^9.5.1",
|
||||||
"docx-templates": "^4.10.2",
|
"docx-templates": "^4.10.2",
|
||||||
|
"docxtemplater": "^3.66.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"jszip-utils": "^0.1.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"pizzip": "^3.2.0",
|
||||||
"webdav-server": "^2.6.2"
|
"webdav-server": "^2.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -29,6 +32,14 @@
|
|||||||
"undici-types": "~7.13.0"
|
"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": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
"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": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@ -770,6 +792,11 @@
|
|||||||
"setimmediate": "^1.0.5"
|
"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": {
|
"node_modules/lie": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||||
@ -1044,6 +1071,19 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"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": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
|||||||
@ -13,8 +13,11 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"docx": "^9.5.1",
|
"docx": "^9.5.1",
|
||||||
"docx-templates": "^4.10.2",
|
"docx-templates": "^4.10.2",
|
||||||
|
"docxtemplater": "^3.66.4",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"jszip-utils": "^0.1.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"pizzip": "^3.2.0",
|
||||||
"webdav-server": "^2.6.2"
|
"webdav-server": "^2.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -92,6 +92,14 @@
|
|||||||
.btn-test:hover {
|
.btn-test:hover {
|
||||||
background-color: #218838;
|
background-color: #218838;
|
||||||
}
|
}
|
||||||
|
.btn-demo {
|
||||||
|
background-color: #ff6b35;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.btn-demo:hover {
|
||||||
|
background-color: #e55a2b;
|
||||||
|
}
|
||||||
.btn-word {
|
.btn-word {
|
||||||
background-color: #0066cc;
|
background-color: #0066cc;
|
||||||
color: white;
|
color: white;
|
||||||
@ -145,7 +153,8 @@
|
|||||||
<div class="info-section">
|
<div class="info-section">
|
||||||
<h3>🚀 Neue Features:</h3>
|
<h3>🚀 Neue Features:</h3>
|
||||||
<ul>
|
<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>🎲 Testen:</strong> Test-Dokument mit Zufallsdaten erstellen</li>
|
||||||
<li><strong>📝 MS Word:</strong> Direkt in Word öffnen und bearbeiten</li>
|
<li><strong>📝 MS Word:</strong> Direkt in Word öffnen und bearbeiten</li>
|
||||||
<li><strong>🌐 WebDAV:</strong> Browser-basierte Dateiverwaltung</li>
|
<li><strong>🌐 WebDAV:</strong> Browser-basierte Dateiverwaltung</li>
|
||||||
@ -205,6 +214,9 @@
|
|||||||
📅 Geändert: ${modifiedDate}
|
📅 Geändert: ${modifiedDate}
|
||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<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">
|
<a href="${template.analyzeUrl}" target="_blank" class="btn btn-analyze">
|
||||||
📊 Analysieren
|
📊 Analysieren
|
||||||
</a>
|
</a>
|
||||||
@ -223,6 +235,56 @@
|
|||||||
return card;
|
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) {
|
async function testTemplate(templateName) {
|
||||||
const rowCount = prompt('Anzahl Tabellenzeilen für Test:', '3');
|
const rowCount = prompt('Anzahl Tabellenzeilen für Test:', '3');
|
||||||
if (!rowCount) return;
|
if (!rowCount) return;
|
||||||
|
|||||||
1126
server-old.js
1126
server-old.js
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,52 @@ Gesamtsumme: {{total}}€
|
|||||||
4. Speichern Sie die Datei als "template-beispiel.docx" im templates/ Ordner
|
4. Speichern Sie die Datei als "template-beispiel.docx" im templates/ Ordner
|
||||||
5. Verwenden Sie diese Template-Namen in der Web-Oberfläche
|
5. Verwenden Sie diese Template-Namen in der Web-Oberfläche
|
||||||
|
|
||||||
WICHTIG:
|
📄 DOCX TEMPLATE ANLEITUNG
|
||||||
- Verwenden Sie {{}} für Platzhalter
|
============================
|
||||||
- Für Listen/Tabellen verwenden Sie {{#array}} ... {{/array}}
|
|
||||||
- Innerhalb von Listen können Sie auf Eigenschaften zugreifen: {{property}}
|
🏷️ 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! 🎉
|
||||||
BIN
templates/angebot-template.docx
Normal file
BIN
templates/angebot-template.docx
Normal file
Binary file not shown.
BIN
templates/brief-template.docx
Normal file
BIN
templates/brief-template.docx
Normal file
Binary file not shown.
BIN
templates/dynamic-template.docx
Normal file
BIN
templates/dynamic-template.docx
Normal file
Binary file not shown.
BIN
templates/rechnung-template.docx
Normal file
BIN
templates/rechnung-template.docx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
48
test-docxtemplater.js
Normal file
48
test-docxtemplater.js
Normal 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();
|
||||||
Loading…
x
Reference in New Issue
Block a user