feat: Add built dist files with EU compliance routes
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
- Include compiled TypeScript with new /impressum, /privacy, /terms routes - Temporary commit of dist files for Docker deployment
This commit is contained in:
parent
5ef8f34133
commit
1ef8f5743c
21 changed files with 2179 additions and 0 deletions
163
dist/services/templates.js
vendored
Normal file
163
dist/services/templates.js
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
export const templates = {
|
||||
invoice: {
|
||||
name: "Invoice",
|
||||
description: "Professional invoice with line items, taxes, and payment details",
|
||||
fields: [
|
||||
{ name: "invoiceNumber", type: "string", required: true, description: "Invoice number" },
|
||||
{ name: "date", type: "string", required: true, description: "Invoice date (YYYY-MM-DD)" },
|
||||
{ name: "dueDate", type: "string", required: false, description: "Due date" },
|
||||
{ name: "from", type: "object", required: true, description: "Sender: {name, address?, email?, phone?, vatId?}" },
|
||||
{ name: "to", type: "object", required: true, description: "Recipient: {name, address?, email?, vatId?}" },
|
||||
{ name: "items", type: "array", required: true, description: "Line items: [{description, quantity, unitPrice, taxRate?}]" },
|
||||
{ name: "currency", type: "string", required: false, description: "Currency symbol (default: €)" },
|
||||
{ name: "notes", type: "string", required: false, description: "Additional notes" },
|
||||
{ name: "paymentDetails", type: "string", required: false, description: "Bank/payment info" },
|
||||
],
|
||||
render: renderInvoice,
|
||||
},
|
||||
receipt: {
|
||||
name: "Receipt",
|
||||
description: "Simple receipt for payments received",
|
||||
fields: [
|
||||
{ name: "receiptNumber", type: "string", required: true, description: "Receipt number" },
|
||||
{ name: "date", type: "string", required: true, description: "Date" },
|
||||
{ name: "from", type: "object", required: true, description: "Business: {name, address?}" },
|
||||
{ name: "to", type: "object", required: false, description: "Customer: {name, email?}" },
|
||||
{ name: "items", type: "array", required: true, description: "Items: [{description, amount}]" },
|
||||
{ name: "currency", type: "string", required: false, description: "Currency symbol" },
|
||||
{ name: "paymentMethod", type: "string", required: false, description: "Payment method" },
|
||||
],
|
||||
render: renderReceipt,
|
||||
},
|
||||
};
|
||||
function esc(s) {
|
||||
return String(s || "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
function renderInvoice(d) {
|
||||
const cur = esc(d.currency || "€");
|
||||
const items = d.items || [];
|
||||
let subtotal = 0;
|
||||
let totalTax = 0;
|
||||
const rows = items
|
||||
.map((item) => {
|
||||
const qty = Number(item.quantity) || 1;
|
||||
const price = Number(item.unitPrice) || 0;
|
||||
const taxRate = Number(item.taxRate) || 0;
|
||||
const lineTotal = qty * price;
|
||||
const lineTax = lineTotal * (taxRate / 100);
|
||||
subtotal += lineTotal;
|
||||
totalTax += lineTax;
|
||||
return `<tr>
|
||||
<td>${esc(item.description)}</td>
|
||||
<td style="text-align:right">${qty}</td>
|
||||
<td style="text-align:right">${cur}${price.toFixed(2)}</td>
|
||||
<td style="text-align:right">${taxRate}%</td>
|
||||
<td style="text-align:right">${cur}${lineTotal.toFixed(2)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
const total = subtotal + totalTax;
|
||||
const from = d.from || {};
|
||||
const to = d.to || {};
|
||||
return `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 13px; color: #222; padding: 40px; }
|
||||
.header { display: flex; justify-content: space-between; margin-bottom: 40px; }
|
||||
.header h1 { font-size: 28px; color: #1a1a1a; }
|
||||
.meta { text-align: right; }
|
||||
.meta div { margin-bottom: 4px; }
|
||||
.parties { display: flex; justify-content: space-between; margin-bottom: 30px; }
|
||||
.party { width: 45%; }
|
||||
.party h3 { font-size: 11px; text-transform: uppercase; color: #888; margin-bottom: 8px; }
|
||||
.party p { margin-bottom: 2px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
|
||||
th { background: #f8f8f8; text-align: left; padding: 10px; font-size: 11px; text-transform: uppercase; color: #666; border-bottom: 2px solid #ddd; }
|
||||
td { padding: 10px; border-bottom: 1px solid #eee; }
|
||||
.totals { text-align: right; margin-bottom: 30px; }
|
||||
.totals div { margin-bottom: 4px; }
|
||||
.totals .total { font-size: 18px; font-weight: 700; color: #1a1a1a; }
|
||||
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; font-size: 12px; color: #666; }
|
||||
</style></head><body>
|
||||
<div class="header">
|
||||
<h1>INVOICE</h1>
|
||||
<div class="meta">
|
||||
<div><strong>#${esc(d.invoiceNumber)}</strong></div>
|
||||
<div>Date: ${esc(d.date)}</div>
|
||||
${d.dueDate ? `<div>Due: ${esc(d.dueDate)}</div>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="parties">
|
||||
<div class="party">
|
||||
<h3>From</h3>
|
||||
<p><strong>${esc(from.name)}</strong></p>
|
||||
${from.address ? `<p>${esc(from.address).replace(/\n/g, "<br>")}</p>` : ""}
|
||||
${from.email ? `<p>${esc(from.email)}</p>` : ""}
|
||||
${from.vatId ? `<p>VAT: ${esc(from.vatId)}</p>` : ""}
|
||||
</div>
|
||||
<div class="party">
|
||||
<h3>To</h3>
|
||||
<p><strong>${esc(to.name)}</strong></p>
|
||||
${to.address ? `<p>${esc(to.address).replace(/\n/g, "<br>")}</p>` : ""}
|
||||
${to.email ? `<p>${esc(to.email)}</p>` : ""}
|
||||
${to.vatId ? `<p>VAT: ${esc(to.vatId)}</p>` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>Description</th><th style="text-align:right">Qty</th><th style="text-align:right">Price</th><th style="text-align:right">Tax</th><th style="text-align:right">Total</th></tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
<div class="totals">
|
||||
<div>Subtotal: ${cur}${subtotal.toFixed(2)}</div>
|
||||
<div>Tax: ${cur}${totalTax.toFixed(2)}</div>
|
||||
<div class="total">Total: ${cur}${total.toFixed(2)}</div>
|
||||
</div>
|
||||
${d.paymentDetails ? `<div class="footer"><strong>Payment Details</strong><br>${esc(d.paymentDetails).replace(/\n/g, "<br>")}</div>` : ""}
|
||||
${d.notes ? `<div class="footer"><strong>Notes</strong><br>${esc(d.notes)}</div>` : ""}
|
||||
</body></html>`;
|
||||
}
|
||||
function renderReceipt(d) {
|
||||
const cur = esc(d.currency || "€");
|
||||
const items = d.items || [];
|
||||
let total = 0;
|
||||
const rows = items
|
||||
.map((item) => {
|
||||
const amount = Number(item.amount) || 0;
|
||||
total += amount;
|
||||
return `<tr><td>${esc(item.description)}</td><td style="text-align:right">${cur}${amount.toFixed(2)}</td></tr>`;
|
||||
})
|
||||
.join("");
|
||||
const from = d.from || {};
|
||||
const to = d.to || {};
|
||||
return `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
|
||||
body { font-family: 'Courier New', monospace; font-size: 13px; max-width: 320px; margin: 0 auto; padding: 30px 20px; }
|
||||
h1 { text-align: center; font-size: 18px; margin-bottom: 4px; }
|
||||
.center { text-align: center; margin-bottom: 16px; }
|
||||
hr { border: none; border-top: 1px dashed #999; margin: 12px 0; }
|
||||
table { width: 100%; }
|
||||
td { padding: 3px 0; }
|
||||
.total { font-weight: bold; font-size: 16px; }
|
||||
</style></head><body>
|
||||
<h1>${esc(from.name)}</h1>
|
||||
${from.address ? `<div class="center">${esc(from.address)}</div>` : ""}
|
||||
<hr>
|
||||
<div>Receipt #${esc(d.receiptNumber)}</div>
|
||||
<div>Date: ${esc(d.date)}</div>
|
||||
${to?.name ? `<div>Customer: ${esc(to.name)}</div>` : ""}
|
||||
<hr>
|
||||
<table>${rows}</table>
|
||||
<hr>
|
||||
<table><tr><td class="total">TOTAL</td><td class="total" style="text-align:right">${cur}${total.toFixed(2)}</td></tr></table>
|
||||
${d.paymentMethod ? `<hr><div>Paid via: ${esc(d.paymentMethod)}</div>` : ""}
|
||||
<hr><div class="center">Thank you!</div>
|
||||
</body></html>`;
|
||||
}
|
||||
export function renderTemplate(id, data) {
|
||||
const template = templates[id];
|
||||
if (!template)
|
||||
throw new Error(`Template '${id}' not found`);
|
||||
return template.render(data);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue