Files
scan-receipts/scripts/generate_demo_images.mjs
Timo 84b9987c49 Add full application: receipt scanning, auth, billing, and account deletion
Brings the working codebase (Next.js app, auth system, Stripe billing,
Docker/deploy config, tests, docs) into version control on top of the
placeholder initial commit, and adds account self-deletion (Danger Zone
in Settings, password + typed-email confirmation, cascading DB cleanup,
Stripe cancellation) per GDPR right-to-erasure.

Excludes local build caches, node_modules, and internal agent scratch
files; .gitignore hardened to keep those out going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 20:59:04 +02:00

369 lines
20 KiB
JavaScript

import fs from "fs";
import path from "path";
import sharp from "sharp";
const demoDir = path.join(process.cwd(), "public", "demo");
if (!fs.existsSync(demoDir)) {
fs.mkdirSync(demoDir, { recursive: true });
}
// 1. Aral Tankstelle Receipt SVG
const aralSvg = `
<svg width="600" height="900" xmlns="http://www.w3.org/2000/svg" style="background:#f7f6f2; font-family:'Courier New', monospace;">
<!-- Receipt Paper Background with slight grunge -->
<rect x="20" y="20" width="560" height="860" fill="#fffdfa" stroke="#d5d0c8" stroke-width="2" rx="4" />
<text x="300" y="80" font-size="28" font-weight="bold" text-anchor="middle" fill="#111">ARAL TANKSTELLE</text>
<text x="300" y="110" font-size="16" text-anchor="middle" fill="#333">Station 10492 München</text>
<text x="300" y="130" font-size="14" text-anchor="middle" fill="#555">Hauptstraße 42, 80331 München</text>
<text x="300" y="150" font-size="14" text-anchor="middle" fill="#555">USt-IdNr: DE123456789</text>
<line x1="50" y1="180" x2="550" y2="180" stroke="#888" stroke-dasharray="6,4" stroke-width="2" />
<text x="50" y="210" font-size="14" fill="#333">Datum: 14.08.2026</text>
<text x="400" y="210" font-size="14" fill="#333">Uhrzeit: 08:42</text>
<text x="50" y="235" font-size="14" fill="#333">Beleg-Nr: AR-982341</text>
<text x="400" y="235" font-size="14" fill="#333">Säule: 04</text>
<line x1="50" y1="260" x2="550" y2="260" stroke="#888" stroke-dasharray="6,4" stroke-width="2" />
<!-- Line items header -->
<text x="50" y="290" font-size="15" font-weight="bold" fill="#111">ARTIKEL</text>
<text x="360" y="290" font-size="15" font-weight="bold" fill="#111">MENGE</text>
<text x="480" y="290" font-size="15" font-weight="bold" fill="#111">EUR</text>
<!-- Items -->
<text x="50" y="330" font-size="15" fill="#222">SUPER E10 (1.699 EUR/l)</text>
<text x="370" y="330" font-size="15" fill="#222">38.24 l</text>
<text x="480" y="330" font-size="15" fill="#222">64.95 B</text>
<text x="50" y="365" font-size="15" fill="#222">KAFFEE CREMA GROSS</text>
<text x="370" y="365" font-size="15" fill="#222">1</text>
<text x="480" y="365" font-size="15" fill="#222">3.50 B</text>
<line x1="50" y1="410" x2="550" y2="410" stroke="#222" stroke-width="2" />
<!-- Total -->
<text x="50" y="455" font-size="24" font-weight="bold" fill="#000">SUMME EUR</text>
<text x="430" y="455" font-size="28" font-weight="bold" fill="#000">68,45</text>
<line x1="50" y1="480" x2="550" y2="480" stroke="#222" stroke-width="2" />
<!-- Tax breakdown -->
<text x="50" y="520" font-size="14" font-weight="bold" fill="#333">MwSt-Satz</text>
<text x="220" y="520" font-size="14" font-weight="bold" fill="#333">Netto</text>
<text x="380" y="520" font-size="14" font-weight="bold" fill="#333">MwSt</text>
<text x="480" y="520" font-size="14" font-weight="bold" fill="#333">Brutto</text>
<text x="50" y="550" font-size="14" fill="#333">B = 19%</text>
<text x="220" y="550" font-size="14" fill="#333">57,52</text>
<text x="380" y="550" font-size="14" fill="#333">10,93</text>
<text x="480" y="550" font-size="14" fill="#333">68,45</text>
<!-- Payment -->
<text x="50" y="610" font-size="14" fill="#333">ZAHLUNG: EC-Karte Maestro / Girocard</text>
<text x="50" y="635" font-size="14" fill="#333">TERMINAL: 89012241 • TRACE: 4491</text>
<text x="50" y="660" font-size="14" fill="#333">TSE-Signatur: VALIDATED</text>
<!-- Fake Barcode -->
<rect x="120" y="700" width="360" height="50" fill="#111" />
<line x1="130" y1="700" x2="130" y2="750" stroke="#fff" stroke-width="4" />
<line x1="145" y1="700" x2="145" y2="750" stroke="#fff" stroke-width="2" />
<line x1="160" y1="700" x2="160" y2="750" stroke="#fff" stroke-width="6" />
<line x1="180" y1="700" x2="180" y2="750" stroke="#fff" stroke-width="3" />
<line x1="200" y1="700" x2="200" y2="750" stroke="#fff" stroke-width="5" />
<line x1="220" y1="700" x2="220" y2="750" stroke="#fff" stroke-width="2" />
<line x1="240" y1="700" x2="240" y2="750" stroke="#fff" stroke-width="4" />
<line x1="260" y1="700" x2="260" y2="750" stroke="#fff" stroke-width="2" />
<line x1="285" y1="700" x2="285" y2="750" stroke="#fff" stroke-width="6" />
<line x1="310" y1="700" x2="310" y2="750" stroke="#fff" stroke-width="3" />
<line x1="330" y1="700" x2="330" y2="750" stroke="#fff" stroke-width="5" />
<line x1="350" y1="700" x2="350" y2="750" stroke="#fff" stroke-width="2" />
<line x1="370" y1="700" x2="370" y2="750" stroke="#fff" stroke-width="4" />
<line x1="390" y1="700" x2="390" y2="750" stroke="#fff" stroke-width="2" />
<line x1="410" y1="700" x2="410" y2="750" stroke="#fff" stroke-width="5" />
<line x1="430" y1="700" x2="430" y2="750" stroke="#fff" stroke-width="3" />
<line x1="450" y1="700" x2="450" y2="750" stroke="#fff" stroke-width="6" />
<text x="300" y="800" font-size="14" text-anchor="middle" fill="#555">Vielen Dank für Ihren Besuch!</text>
</svg>
`;
// 2. Trattoria Restaurant Bewirtungsbeleg SVG
const restaurantSvg = `
<svg width="600" height="980" xmlns="http://www.w3.org/2000/svg" style="background:#f5f3ee; font-family:'Courier New', monospace;">
<rect x="20" y="20" width="560" height="940" fill="#fffefb" stroke="#ded8cf" stroke-width="2" rx="4" />
<text x="300" y="80" font-size="26" font-weight="bold" text-anchor="middle" fill="#111">TRATTORIA BELLA VISTA</text>
<text x="300" y="110" font-size="15" text-anchor="middle" fill="#333">Italienische Spezialitäten</text>
<text x="300" y="130" font-size="14" text-anchor="middle" fill="#555">Marktplatz 12, 10115 Berlin</text>
<text x="300" y="150" font-size="14" text-anchor="middle" fill="#555">St.-Nr: 34/815/09123 • USt-Id: DE987654321</text>
<line x1="50" y1="180" x2="550" y2="180" stroke="#888" stroke-dasharray="6,4" stroke-width="2" />
<text x="50" y="210" font-size="14" fill="#333">Tisch: 14</text>
<text x="250" y="210" font-size="14" fill="#333">Bedienung: Marco</text>
<text x="420" y="210" font-size="14" fill="#333">Pax: 2</text>
<text x="50" y="235" font-size="14" fill="#333">Datum: 14.08.2026</text>
<text x="250" y="235" font-size="14" fill="#333">Zeit: 20:15</text>
<text x="420" y="235" font-size="14" fill="#333">Rechn-Nr: TR-44201</text>
<line x1="50" y1="260" x2="550" y2="260" stroke="#888" stroke-dasharray="6,4" stroke-width="2" />
<!-- Header -->
<text x="50" y="295" font-size="15" font-weight="bold" fill="#111">POS</text>
<text x="110" y="295" font-size="15" font-weight="bold" fill="#111">BESCHREIBUNG</text>
<text x="380" y="295" font-size="15" font-weight="bold" fill="#111">STK</text>
<text x="480" y="295" font-size="15" font-weight="bold" fill="#111">GESAMT</text>
<!-- Items -->
<text x="50" y="335" font-size="14" fill="#222">1</text>
<text x="110" y="335" font-size="14" fill="#222">Pasta Tartufo</text>
<text x="380" y="335" font-size="14" fill="#222">2</text>
<text x="480" y="335" font-size="14" fill="#222">44.00 A</text>
<text x="50" y="370" font-size="14" fill="#222">2</text>
<text x="110" y="370" font-size="14" fill="#222">S.Pellegrino 0.75l</text>
<text x="380" y="370" font-size="14" fill="#222">1</text>
<text x="480" y="370" font-size="14" fill="#222">7.50 A</text>
<text x="50" y="405" font-size="14" fill="#222">3</text>
<text x="110" y="405" font-size="14" fill="#222">Tiramisu Hausgemacht</text>
<text x="380" y="405" font-size="14" fill="#222">2</text>
<text x="480" y="405" font-size="14" fill="#222">16.00 B</text>
<text x="50" y="440" font-size="14" fill="#222">4</text>
<text x="110" y="440" font-size="14" fill="#222">Espresso Doppio</text>
<text x="380" y="440" font-size="14" fill="#222">1</text>
<text x="480" y="440" font-size="14" fill="#222">4.50 A</text>
<text x="50" y="475" font-size="14" fill="#222">5</text>
<text x="110" y="475" font-size="14" fill="#222">Trinkgeld / Tip freiwillig</text>
<text x="380" y="475" font-size="14" fill="#222">1</text>
<text x="480" y="475" font-size="14" fill="#222">12.50 -</text>
<line x1="50" y1="515" x2="550" y2="515" stroke="#222" stroke-width="2" />
<!-- Total -->
<text x="50" y="560" font-size="24" font-weight="bold" fill="#000">GESAMTBETRAG</text>
<text x="430" y="560" font-size="28" font-weight="bold" fill="#000">84,50 €</text>
<line x1="50" y1="585" x2="550" y2="585" stroke="#222" stroke-width="2" />
<!-- Steuern -->
<text x="50" y="625" font-size="13" font-weight="bold" fill="#444">MwSt-Satz</text>
<text x="210" y="625" font-size="13" font-weight="bold" fill="#444">Netto</text>
<text x="360" y="625" font-size="13" font-weight="bold" fill="#444">MwSt</text>
<text x="470" y="625" font-size="13" font-weight="bold" fill="#444">Brutto</text>
<text x="50" y="650" font-size="13" fill="#444">A = 19% (Getr./Speis.)</text>
<text x="210" y="650" font-size="13" fill="#444">47,06</text>
<text x="360" y="650" font-size="13" fill="#444">8,94</text>
<text x="470" y="650" font-size="13" fill="#444">56,00</text>
<text x="50" y="675" font-size="13" fill="#444">B = 7% (Dessert to go)</text>
<text x="210" y="675" font-size="13" fill="#444">14,95</text>
<text x="360" y="675" font-size="13" fill="#444">1,05</text>
<text x="470" y="675" font-size="13" fill="#444">16,00</text>
<!-- Bewirtungsbeleg Fields -->
<rect x="45" y="720" width="510" height="150" fill="#faf8f5" stroke="#bbb" stroke-width="1" />
<text x="60" y="745" font-size="12" font-weight="bold" fill="#333">Angaben zur geschäftlichen Bewirtung (§ 4 Abs. 5 EStG):</text>
<text x="60" y="775" font-size="12" fill="#666">Anlass der Bewirtung: ................................................</text>
<text x="60" y="805" font-size="12" fill="#666">Bewirtete Personen: ................................................</text>
<text x="60" y="835" font-size="12" fill="#666">Ort, Datum: Berlin, 14.08.2026 Unterschrift: .......................</text>
<text x="300" y="910" font-size="14" text-anchor="middle" fill="#555">Arrivederci &amp; Grazie Mille!</text>
</svg>
`;
// 3. MediaMarkt Saturn IT Receipt SVG
const mediamarktSvg = `
<svg width="600" height="920" xmlns="http://www.w3.org/2000/svg" style="background:#f4f5f7; font-family:'Courier New', monospace;">
<rect x="20" y="20" width="560" height="880" fill="#ffffff" stroke="#cbd5e1" stroke-width="2" rx="4" />
<text x="300" y="75" font-size="28" font-weight="bold" text-anchor="middle" fill="#df0000">MediaMarkt</text>
<text x="300" y="105" font-size="15" text-anchor="middle" fill="#333">MediaMarkt Saturn Holding GmbH</text>
<text x="300" y="125" font-size="14" text-anchor="middle" fill="#555">Alexanderplatz 3, 10178 Berlin</text>
<text x="300" y="145" font-size="14" text-anchor="middle" fill="#555">USt-IdNr: DE119876543 • Filiale 204</text>
<line x1="50" y1="175" x2="550" y2="175" stroke="#94a3b8" stroke-dasharray="6,4" stroke-width="2" />
<text x="50" y="205" font-size="14" fill="#333">Datum: 14.08.2026</text>
<text x="400" y="205" font-size="14" fill="#333">Zeit: 14:30:12</text>
<text x="50" y="230" font-size="14" fill="#333">Kassenbon-Nr: MM-2026-9811</text>
<text x="400" y="230" font-size="14" fill="#333">Kasse: 02</text>
<line x1="50" y1="255" x2="550" y2="255" stroke="#94a3b8" stroke-dasharray="6,4" stroke-width="2" />
<text x="50" y="290" font-size="15" font-weight="bold" fill="#0f172a">ARTIKEL</text>
<text x="380" y="290" font-size="15" font-weight="bold" fill="#0f172a">MENGE</text>
<text x="480" y="290" font-size="15" font-weight="bold" fill="#0f172a">PREIS</text>
<!-- Items -->
<text x="50" y="330" font-size="14" fill="#1e293b">USB-C DOCK 100W PRO</text>
<text x="50" y="350" font-size="12" fill="#64748b">Art-Nr: 48920194 • 19% MwSt</text>
<text x="390" y="330" font-size="14" fill="#1e293b">1</text>
<text x="480" y="330" font-size="14" fill="#1e293b">89.99</text>
<text x="50" y="390" font-size="14" fill="#1e293b">LOGITECH MX MASTER 3S</text>
<text x="50" y="410" font-size="12" fill="#64748b">Art-Nr: 29014811 • 19% MwSt</text>
<text x="390" y="390" font-size="14" fill="#1e293b">1</text>
<text x="480" y="390" font-size="14" fill="#1e293b">40.00</text>
<line x1="50" y1="455" x2="550" y2="455" stroke="#0f172a" stroke-width="2" />
<!-- Total -->
<text x="50" y="500" font-size="24" font-weight="bold" fill="#0f172a">GESAMTBETRAG</text>
<text x="410" y="500" font-size="28" font-weight="bold" fill="#0f172a">129,99 €</text>
<line x1="50" y1="525" x2="550" y2="525" stroke="#0f172a" stroke-width="2" />
<!-- Tax breakdown -->
<text x="50" y="565" font-size="14" font-weight="bold" fill="#334155">Steuersatz</text>
<text x="220" y="565" font-size="14" font-weight="bold" fill="#334155">Netto</text>
<text x="370" y="565" font-size="14" font-weight="bold" fill="#334155">Steuer</text>
<text x="470" y="565" font-size="14" font-weight="bold" fill="#334155">Brutto</text>
<text x="50" y="595" font-size="14" fill="#334155">19%</text>
<text x="220" y="595" font-size="14" fill="#334155">109,24 €</text>
<text x="370" y="595" font-size="14" fill="#334155">20,75 €</text>
<text x="470" y="595" font-size="14" fill="#334155">129,99 €</text>
<!-- Payment -->
<text x="50" y="655" font-size="14" fill="#334155">Zahlart: Apple Pay / VISA (**** 4819)</text>
<text x="50" y="680" font-size="14" fill="#334155">Autorisierung: 902184 • TSE: OK</text>
<!-- Barcode -->
<rect x="120" y="730" width="360" height="50" fill="#0f172a" />
<line x1="140" y1="730" x2="140" y2="780" stroke="#fff" stroke-width="4" />
<line x1="165" y1="730" x2="165" y2="780" stroke="#fff" stroke-width="3" />
<line x1="190" y1="730" x2="190" y2="780" stroke="#fff" stroke-width="6" />
<line x1="220" y1="730" x2="220" y2="780" stroke="#fff" stroke-width="2" />
<line x1="250" y1="730" x2="250" y2="780" stroke="#fff" stroke-width="5" />
<line x1="280" y1="730" x2="280" y2="780" stroke="#fff" stroke-width="3" />
<line x1="310" y1="730" x2="310" y2="780" stroke="#fff" stroke-width="6" />
<line x1="340" y1="730" x2="340" y2="780" stroke="#fff" stroke-width="2" />
<line x1="370" y1="730" x2="370" y2="780" stroke="#fff" stroke-width="5" />
<line x1="400" y1="730" x2="400" y2="780" stroke="#fff" stroke-width="4" />
<line x1="430" y1="730" x2="430" y2="780" stroke="#fff" stroke-width="2" />
<text x="300" y="830" font-size="14" text-anchor="middle" fill="#64748b">Umtausch innerhalb 14 Tagen mit Kassenbon.</text>
</svg>
`;
// 4. REWE Supermarkt Kassenbon SVG
const reweSvg = `
<svg width="600" height="920" xmlns="http://www.w3.org/2000/svg" style="background:#fbfbf9; font-family:'Courier New', monospace;">
<rect x="20" y="20" width="560" height="880" fill="#ffffff" stroke="#e2e8f0" stroke-width="2" rx="4" />
<text x="300" y="75" font-size="32" font-weight="bold" text-anchor="middle" fill="#c00">REWE</text>
<text x="300" y="105" font-size="15" text-anchor="middle" fill="#333">REWE Markt GmbH • Filiale 1089</text>
<text x="300" y="125" font-size="14" text-anchor="middle" fill="#555">Friedrichstraße 190, 10117 Berlin</text>
<text x="300" y="145" font-size="14" text-anchor="middle" fill="#555">USt-IdNr: DE811122334</text>
<line x1="50" y1="175" x2="550" y2="175" stroke="#94a3b8" stroke-dasharray="6,4" stroke-width="2" />
<text x="50" y="205" font-size="14" fill="#333">Datum: 14.08.2026</text>
<text x="400" y="205" font-size="14" fill="#333">Uhrzeit: 17:45</text>
<text x="50" y="230" font-size="14" fill="#333">Beleg-Nr: RW-77821</text>
<text x="400" y="230" font-size="14" fill="#333">Bon-Kasse: 03</text>
<line x1="50" y1="255" x2="550" y2="255" stroke="#94a3b8" stroke-dasharray="6,4" stroke-width="2" />
<text x="50" y="290" font-size="15" font-weight="bold" fill="#000">ARTIKEL</text>
<text x="380" y="290" font-size="15" font-weight="bold" fill="#000">MENGE</text>
<text x="480" y="290" font-size="15" font-weight="bold" fill="#000">EUR</text>
<!-- Items -->
<text x="50" y="330" font-size="14" fill="#111">REWE Bio Vollmilch 3.8%</text>
<text x="380" y="330" font-size="14" fill="#111">2</text>
<text x="480" y="330" font-size="14" fill="#111">3.18 A</text>
<text x="50" y="365" font-size="14" fill="#111">Bio Bananen (0.85 kg)</text>
<text x="380" y="365" font-size="14" fill="#111">1</text>
<text x="480" y="365" font-size="14" fill="#111">1.89 A</text>
<text x="50" y="400" font-size="14" fill="#111">Bio Espresso Bohnen 1kg</text>
<text x="380" y="400" font-size="14" fill="#111">1</text>
<text x="480" y="400" font-size="14" fill="#111">14.99 A</text>
<text x="50" y="435" font-size="14" fill="#111">Küchenrolle 4er Pack</text>
<text x="380" y="435" font-size="14" fill="#111">1</text>
<text x="480" y="435" font-size="14" fill="#111">4.74 B</text>
<line x1="50" y1="475" x2="550" y2="475" stroke="#000" stroke-width="2" />
<!-- Total -->
<text x="50" y="520" font-size="24" font-weight="bold" fill="#000">SUMME EUR</text>
<text x="420" y="520" font-size="28" font-weight="bold" fill="#000">24,80</text>
<line x1="50" y1="545" x2="550" y2="545" stroke="#000" stroke-width="2" />
<text x="50" y="585" font-size="13" font-weight="bold" fill="#333">MwSt-Satz</text>
<text x="220" y="585" font-size="13" font-weight="bold" fill="#333">Netto</text>
<text x="370" y="585" font-size="13" font-weight="bold" fill="#333">MwSt</text>
<text x="480" y="585" font-size="13" font-weight="bold" fill="#333">Brutto</text>
<text x="50" y="615" font-size="13" fill="#333">A = 7% (Lebensmittel)</text>
<text x="220" y="615" font-size="13" fill="#333">18,75</text>
<text x="370" y="615" font-size="13" fill="#333">1,31</text>
<text x="480" y="615" font-size="13" fill="#333">20,06</text>
<text x="50" y="640" font-size="13" fill="#333">B = 19% (Drogerie)</text>
<text x="220" y="640" font-size="13" fill="#333">3,98</text>
<text x="370" y="640" font-size="13" fill="#333">0,76</text>
<text x="480" y="640" font-size="13" fill="#333">4,74</text>
<!-- Payment -->
<text x="50" y="700" font-size="14" fill="#333">Gegeben: Bar 50,00 EUR • Rückgeld: 25,20 EUR</text>
<text x="50" y="725" font-size="14" fill="#333">TSE-Signatur: VALIDATED</text>
<!-- Barcode -->
<rect x="120" y="760" width="360" height="45" fill="#111" />
<line x1="140" y1="760" x2="140" y2="805" stroke="#fff" stroke-width="4" />
<line x1="170" y1="760" x2="170" y2="805" stroke="#fff" stroke-width="2" />
<line x1="200" y1="760" x2="200" y2="805" stroke="#fff" stroke-width="5" />
<line x1="230" y1="760" x2="230" y2="805" stroke="#fff" stroke-width="3" />
<line x1="260" y1="760" x2="260" y2="805" stroke="#fff" stroke-width="6" />
<line x1="290" y1="760" x2="290" y2="805" stroke="#fff" stroke-width="2" />
<line x1="320" y1="760" x2="320" y2="805" stroke="#fff" stroke-width="5" />
<line x1="350" y1="760" x2="350" y2="805" stroke="#fff" stroke-width="3" />
<line x1="380" y1="760" x2="380" y2="805" stroke="#fff" stroke-width="6" />
<line x1="410" y1="760" x2="410" y2="805" stroke="#fff" stroke-width="2" />
<line x1="440" y1="760" x2="440" y2="805" stroke="#fff" stroke-width="4" />
<text x="300" y="850" font-size="14" text-anchor="middle" fill="#555">Vielen Dank für Ihren Einkauf bei REWE!</text>
</svg>
`;
async function generateDemoImages() {
console.log("Generating Demo Receipt Images in public/demo/...");
const images = [
{ filename: "01_aral_tankbeleg_muenchen.jpg", svg: aralSvg },
{ filename: "02_trattoria_bewirtungsbeleg_berlin.jpg", svg: restaurantSvg },
{ filename: "03_mediamarkt_it_rechnung.jpg", svg: mediamarktSvg },
{ filename: "04_rewe_supermarkt_kassenbon.jpg", svg: reweSvg },
];
for (const img of images) {
const destPath = path.join(demoDir, img.filename);
const svgBuffer = Buffer.from(img.svg);
await sharp(svgBuffer)
.jpeg({ quality: 90, mozjpeg: true })
.toFile(destPath);
console.log(`✓ Created demo receipt: ${img.filename}`);
}
console.log("All demo receipt images created successfully in public/demo/");
}
generateDemoImages().catch((err) => {
console.error("Failed to generate demo images:", err);
process.exit(1);
});