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>
This commit is contained in:
Timo
2026-08-19 20:59:04 +02:00
parent 650a74da97
commit 84b9987c49
415 changed files with 96619 additions and 0 deletions

View File

@@ -0,0 +1,653 @@
/**
* Tier 4: Real-World German Receipt Workload Scenarios
* Minimum 8 realistic German accounting & tax compliance workload scenarios.
*/
import { describe, test, it, expect } from "./runner";
import { ProcessedReceipt } from "../../src/lib/schema/receipt";
import { validateReceiptMath } from "../../src/lib/ai/mathValidator";
import { generateDualSheetExcel } from "../../src/lib/export/excelGenerator";
import { generateAccountingCsv } from "../../src/lib/export/csvGenerator";
import ExcelJS from "exceljs";
describe("Tier 4: Real-World German Receipt Workload Scenarios", () => {
// Scenario 1: Aral Tankstelle Fuel Receipt with Liters & 19% VAT
test("Scenario 1: Aral Tankstelle Fuel Receipt with Liters & 19% VAT", async () => {
const aralReceipt: ProcessedReceipt = {
id: "scen-1-aral",
merchant: {
name: "Aral Tankstelle Station München",
address: "Landsberger Str. 402, 81241 München",
taxId: "DE129482910",
confidence: 0.99,
},
date: {
isoDate: "2026-08-14",
time: "07:35",
confidence: 0.98,
},
documentType: "TANKBELEG",
receiptNumber: "AR-882910",
currency: "EUR",
totalAmount: {
value: 78.45,
confidence: 0.99,
},
netAmount: 65.92,
taxBreakdown: [
{
ratePercent: 19,
taxAmount: 12.53,
netAmount: 65.92,
},
],
lineItems: [
{
description: "Super E10 (42.50 l x 1.729 €/l)",
quantity: 1,
price: 73.48,
taxRate: 19,
},
{
description: "Kaffee Crema Groß 0.3l",
quantity: 1,
price: 3.5,
taxRate: 19,
},
{
description: "Croissant Natur",
quantity: 1,
price: 1.47,
taxRate: 19,
},
],
suggestedCategory: "Tanken & KFZ",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-aral-scen-1",
originalFileName: "01_aral_tankbeleg_muenchen.jpg",
fileSizeBytes: 245000,
createdAt: "2026-08-14T07:35:00.000Z",
updatedAt: "2026-08-14T07:35:00.000Z",
status: "ready",
};
// 1. Math check
const val = validateReceiptMath(aralReceipt);
expect(val.isMathValid).toBe(true);
expect(val.calculatedGross).toBe(78.45);
expect(val.calculatedItemsSum).toBe(78.45);
// 2. Accounting CSV check
const csv = generateAccountingCsv([aralReceipt]);
expect(csv).toContain('"Aral Tankstelle Station München"');
expect(csv).toContain('"78,45"');
expect(csv).toContain('"12,53"');
expect(csv).toContain('"14.08.2026"');
// 3. Excel check
const buf = await generateDualSheetExcel([aralReceipt]);
const wb = new ExcelJS.Workbook();
await wb.xlsx.load(buf as any);
const s1 = wb.getWorksheet("Belegübersicht");
expect(s1?.getRow(2).getCell(4).value).toBe("Tanken & KFZ");
});
// Scenario 2: REWE Supermarkt Mixed Food (7%) & Non-Food (19%) Basket
test("Scenario 2: REWE Supermarkt Mixed Food (7%) & Non-Food (19%) Basket with Pfand", async () => {
const reweReceipt: ProcessedReceipt = {
id: "scen-2-rewe",
merchant: {
name: "REWE Markt GmbH Filiale 441",
address: "Friedrichstraße 100, 10117 Berlin",
taxId: "DE811122334",
confidence: 0.98,
},
date: {
isoDate: "2026-08-15",
time: "18:20",
confidence: 0.97,
},
documentType: "KASSENBON",
receiptNumber: "RW-2026-8819",
currency: "EUR",
totalAmount: {
value: 34.62,
confidence: 0.99,
},
netAmount: 31.78,
taxBreakdown: [
{
ratePercent: 7,
taxAmount: 1.63,
netAmount: 23.32,
},
{
ratePercent: 19,
taxAmount: 1.21,
netAmount: 8.46,
},
],
lineItems: [
{
description: "REWE Bio Vollmilch 3.8% 1l",
quantity: 2,
price: 3.18,
taxRate: 7,
},
{
description: "Bio Bananen Fairtrade 1.2 kg",
quantity: 1,
price: 2.39,
taxRate: 7,
},
{
description: "Dinkel Sauerteigbrot 500g",
quantity: 1,
price: 3.99,
taxRate: 7,
},
{
description: "Gouda jung Bio 400g",
quantity: 1,
price: 4.49,
taxRate: 7,
},
{
description: "Espresso Bohnen Bio 1kg",
quantity: 1,
price: 10.9,
taxRate: 7,
},
{
description: "Küchenrolle Recycling 4er",
quantity: 1,
price: 4.74,
taxRate: 19,
},
{
description: "Spülmittel Lemon 500ml",
quantity: 1,
price: 2.43,
taxRate: 19,
},
{
description: "Mineralwasser Medium 1.0l",
quantity: 1,
price: 2.5,
taxRate: 19,
},
],
suggestedCategory: "Verpflegungsmehraufwand",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-rewe-scen-2",
originalFileName: "04_rewe_supermarkt_kassenbon.jpg",
fileSizeBytes: 210000,
createdAt: "2026-08-15T18:20:00.000Z",
updatedAt: "2026-08-15T18:20:00.000Z",
status: "ready",
};
const val = validateReceiptMath(reweReceipt);
expect(val.isMathValid).toBe(true);
expect(val.calculatedTaxSum).toBe(2.84); // 1.63 + 1.21
const buf = await generateDualSheetExcel([reweReceipt]);
const wb = new ExcelJS.Workbook();
await wb.xlsx.load(buf as any);
const s2 = wb.getWorksheet("Einzelpositionen Detail");
expect(s2?.rowCount).toBe(9); // 1 header + 8 line items
});
// Scenario 3: Trattoria Bella Vista Bewirtungsbeleg with Tip & Attendees (§4 Abs. 5 EStG)
test("Scenario 3: Trattoria Bella Vista Bewirtungsbeleg with Tip & Attendees (§4 Abs. 5 EStG)", async () => {
const trattoriaReceipt: ProcessedReceipt = {
id: "scen-3-trattoria",
merchant: {
name: "Trattoria Bella Vista Ristorante",
address: "Marktplatz 12, 10115 Berlin",
taxId: "DE987654321",
confidence: 0.96,
},
date: {
isoDate: "2026-08-15",
time: "21:10",
confidence: 0.95,
},
documentType: "BEWIRTUNGSBELEG",
receiptNumber: "TR-2026-9941",
currency: "EUR",
totalAmount: {
value: 125.5,
confidence: 0.97,
},
netAmount: 104.97,
taxBreakdown: [
{
ratePercent: 19,
taxAmount: 16.03,
netAmount: 84.37,
},
{
ratePercent: 7,
taxAmount: 4.5,
netAmount: 20.6,
},
],
lineItems: [
{
description: "2x Tagliolini al Tartufo Nero",
quantity: 2,
price: 52.0,
taxRate: 19,
},
{
description: "1x Filetto di Manzo 250g",
quantity: 1,
price: 36.5,
taxRate: 19,
},
{
description: "1x San Pellegrino 0.75l",
quantity: 1,
price: 7.5,
taxRate: 19,
},
{
description: "1x Chianti Classico DOCG Flasche",
quantity: 1,
price: 28.0,
taxRate: 19,
},
{
description: "Trinkgeld / Tip (steuerfrei)",
quantity: 1,
price: 1.5,
taxRate: null,
},
],
suggestedCategory: "Bewirtung",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-trattoria-scen-3",
originalFileName: "02_trattoria_bewirtungsbeleg_berlin.jpg",
fileSizeBytes: 290000,
createdAt: "2026-08-15T21:10:00.000Z",
updatedAt: "2026-08-15T21:10:00.000Z",
status: "ready",
};
const val = validateReceiptMath(trattoriaReceipt);
expect(val.isMathValid).toBe(true);
const csv = generateAccountingCsv([trattoriaReceipt]);
expect(csv).toContain('"Trattoria Bella Vista Ristorante - Bewirtung"');
expect(csv).toContain('"125,50"');
});
// Scenario 4: MediaMarkt Saturn IT Equipment Invoice
test("Scenario 4: MediaMarkt Saturn IT Equipment Invoice (Hardware, Serial Numbers, 19% VAT)", async () => {
const itReceipt: ProcessedReceipt = {
id: "scen-4-mediamarkt",
merchant: {
name: "MediaMarkt Saturn Holding",
address: "Alexanderplatz 3, 10178 Berlin",
taxId: "DE119876543",
confidence: 0.99,
},
date: {
isoDate: "2026-08-15",
time: "15:45",
confidence: 0.98,
},
documentType: "RECHNUNG",
receiptNumber: "MM-INV-2026-77812",
currency: "EUR",
totalAmount: {
value: 299.98,
confidence: 0.99,
},
netAmount: 252.08,
taxBreakdown: [
{
ratePercent: 19,
taxAmount: 47.9,
netAmount: 252.08,
},
],
lineItems: [
{
description: "Dell USB-C 4K Triple Display Dock 130W",
quantity: 1,
price: 189.99,
taxRate: 19,
},
{
description: "Logitech MX Keys S Tastatur Wireless",
quantity: 1,
price: 109.99,
taxRate: 19,
},
],
suggestedCategory: "Bürobedarf & IT",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-mediamarkt-scen-4",
originalFileName: "03_mediamarkt_it_rechnung.jpg",
fileSizeBytes: 310000,
createdAt: "2026-08-15T15:45:00.000Z",
updatedAt: "2026-08-15T15:45:00.000Z",
status: "ready",
};
const val = validateReceiptMath(itReceipt);
expect(val.isMathValid).toBe(true);
const buf = await generateDualSheetExcel([itReceipt]);
const wb = new ExcelJS.Workbook();
await wb.xlsx.load(buf as any);
const s1 = wb.getWorksheet("Belegübersicht");
expect(s1?.getRow(2).getCell(5).value).toBe("RECHNUNG");
expect(s1?.getRow(2).getCell(10).value).toBe(299.98);
});
// Scenario 5: Hotel Übernachtung + Frühstück Split (7% vs 19% + City Tax 0%)
test("Scenario 5: Hotel Übernachtung + Frühstück Split (Lodging 7% vs Breakfast 19% + City Tax 0%)", async () => {
const hotelReceipt: ProcessedReceipt = {
id: "scen-5-hotel",
merchant: {
name: "Motel One München Sendlinger Tor",
address: "Herzog-Wilhelm-Str. 28, 80331 München",
taxId: "DE263819201",
confidence: 0.98,
},
date: {
isoDate: "2026-08-14",
time: "08:15",
confidence: 0.96,
},
documentType: "RECHNUNG",
receiptNumber: "MO-MUC-88412",
currency: "EUR",
totalAmount: {
value: 129.5,
confidence: 0.99,
},
netAmount: 118.84,
taxBreakdown: [
{
ratePercent: 7,
taxAmount: 7.0,
netAmount: 100.0, // Lodging
},
{
ratePercent: 19,
taxAmount: 3.66,
netAmount: 19.26, // Breakfast & Business Package
},
],
lineItems: [
{
description: "1x Übernachtung Standard Room (13.08.-14.08.)",
quantity: 1,
price: 107.0,
taxRate: 7,
},
{
description: "1x Bio-Frühstücksbuffet & WLAN Business",
quantity: 1,
price: 22.5,
taxRate: 19,
},
],
suggestedCategory: "Reisekosten & Hotel",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-hotel-scen-5",
originalFileName: "hotel_motel_one.jpg",
fileSizeBytes: 260000,
createdAt: "2026-08-14T08:15:00.000Z",
updatedAt: "2026-08-14T08:15:00.000Z",
status: "ready",
};
const val = validateReceiptMath(hotelReceipt);
expect(val.isMathValid).toBe(true);
const csv = generateAccountingCsv([hotelReceipt]);
expect(csv).toContain('"Motel One München Sendlinger Tor"');
expect(csv).toContain('"129,50"');
expect(csv).toContain('"7,00"');
expect(csv).toContain('"3,66"');
});
// Scenario 6: Taxi Deutschland Urban Ride (7% VAT according to §12 Abs. 2 Nr. 10 UStG)
test("Scenario 6: Taxi Deutschland Urban Ride (7% VAT for local transport <= 50km)", async () => {
const taxiReceipt: ProcessedReceipt = {
id: "scen-6-taxi",
merchant: {
name: "Taxi Funk München eG - Wagen 312",
address: "Heimeranstr. 35, 80339 München",
taxId: "DE129554411",
confidence: 0.97,
},
date: {
isoDate: "2026-08-15",
time: "23:45",
confidence: 0.95,
},
documentType: "KASSENBON",
receiptNumber: "TX-9901-26",
currency: "EUR",
totalAmount: {
value: 28.5,
confidence: 0.98,
},
netAmount: 26.64,
taxBreakdown: [
{
ratePercent: 7,
taxAmount: 1.86,
netAmount: 26.64,
},
],
lineItems: [
{
description: "Taxifahrt Stadtgebiet München (8.4 km)",
quantity: 1,
price: 28.5,
taxRate: 7,
},
],
suggestedCategory: "Reisekosten & Hotel",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-taxi-scen-6",
originalFileName: "taxi_muenchen.jpg",
fileSizeBytes: 175000,
createdAt: "2026-08-15T23:45:00.000Z",
updatedAt: "2026-08-15T23:45:00.000Z",
status: "ready",
};
const val = validateReceiptMath(taxiReceipt);
expect(val.isMathValid).toBe(true);
expect(val.calculatedTaxSum).toBe(1.86);
const buf = await generateDualSheetExcel([taxiReceipt]);
const wb = new ExcelJS.Workbook();
await wb.xlsx.load(buf as any);
const s1 = wb.getWorksheet("Belegübersicht");
expect(s1?.getRow(2).getCell(8).value).toBe(1.86);
expect(s1?.getRow(2).getCell(9).value).toBe(0.0);
});
// Scenario 7: APCOA Parkhaus Ticket (19% VAT)
test("Scenario 7: APCOA Parkhaus Ticket (Short-term parking, 19% VAT)", async () => {
const parkingReceipt: ProcessedReceipt = {
id: "scen-7-apcoa",
merchant: {
name: "APCOA Parking Deutschland GmbH",
address: "Parkhaus Marienplatz, 80331 München",
taxId: "DE147852369",
confidence: 0.99,
},
date: {
isoDate: "2026-08-15",
time: "14:10",
confidence: 0.98,
},
documentType: "PARKTICKET",
receiptNumber: "PK-APCOA-4412",
currency: "EUR",
totalAmount: {
value: 14.0,
confidence: 0.99,
},
netAmount: 11.76,
taxBreakdown: [
{
ratePercent: 19,
taxAmount: 2.24,
netAmount: 11.76,
},
],
lineItems: [
{
description: "Parkzeit 3 Std. 15 Min. (Tarif Standard)",
quantity: 1,
price: 14.0,
taxRate: 19,
},
],
suggestedCategory: "Tanken & KFZ",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-apcoa-scen-7",
originalFileName: "apcoa_parking.jpg",
fileSizeBytes: 140000,
createdAt: "2026-08-15T14:10:00.000Z",
updatedAt: "2026-08-15T14:10:00.000Z",
status: "ready",
};
const val = validateReceiptMath(parkingReceipt);
expect(val.isMathValid).toBe(true);
const csv = generateAccountingCsv([parkingReceipt]);
expect(csv).toContain('"APCOA Parking Deutschland GmbH"');
expect(csv).toContain('"14,00"');
expect(csv).toContain('"2,24"');
});
// Scenario 8: Bäckerei / Backstube Small Cash Receipt (7% Baked Goods + 19% Coffee)
test("Scenario 8: Bäckerei Small Cash Receipt (7% take-away bread + 19% on-site cappuccino)", async () => {
const bakeryReceipt: ProcessedReceipt = {
id: "scen-8-bakery",
merchant: {
name: "Bio-Bäckerei Hofpfisterei GmbH",
address: "Viktualienmarkt 8, 80331 München",
taxId: "DE128844332",
confidence: 0.98,
},
date: {
isoDate: "2026-08-15",
time: "08:45",
confidence: 0.97,
},
documentType: "KASSENBON",
receiptNumber: "HPF-2026-1192",
currency: "EUR",
totalAmount: {
value: 8.9,
confidence: 0.99,
},
netAmount: 7.97,
taxBreakdown: [
{
ratePercent: 7,
taxAmount: 0.35,
netAmount: 4.95, // 5.30€ baked goods take-away
},
{
ratePercent: 19,
taxAmount: 0.58,
netAmount: 3.02, // 3.60€ Cappuccino
},
],
lineItems: [
{
description: "Pfister Öko-Landbrot 1kg",
quantity: 1,
price: 5.3,
taxRate: 7,
},
{
description: "Cappuccino Groß (im Haus)",
quantity: 1,
price: 3.6,
taxRate: 19,
},
],
suggestedCategory: "Verpflegungsmehraufwand",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
},
imageHash: "hash-bakery-scen-8",
originalFileName: "hofpfisterei_beleg.jpg",
fileSizeBytes: 155000,
createdAt: "2026-08-15T08:45:00.000Z",
updatedAt: "2026-08-15T08:45:00.000Z",
status: "ready",
};
const val = validateReceiptMath(bakeryReceipt);
expect(val.isMathValid).toBe(true);
const buf = await generateDualSheetExcel([bakeryReceipt]);
const wb = new ExcelJS.Workbook();
await wb.xlsx.load(buf as any);
const s1 = wb.getWorksheet("Belegübersicht");
expect(s1?.getRow(2).getCell(10).value).toBe(8.9);
});
});