managarten/docs/plans/invoices-module.md
Till JS dc1a0a65fb docs(invoices): mark all milestones done, list Phase-2/3 open items
Quick status sync after M8. M1–M8 all landed; what's left are the
Phase-2/3 items (multi-device number collision, structured address
schema, finance cross-link, camt bank reconciliation) and the
Spaces-SSR-unblock-then-dogfood step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:23:02 +02:00

22 KiB
Raw Blame History

Invoices — Module Plan

Status (2026-04-20)

Alle Milestones DONE — Modul ist solo-produktiv.

Milestone Commit Notes
M1 Skelett 2cf89ce26 Modul registriert, Dexie v27, Encryption-Registry, leere ListView
M2 CRUD 8d00ee069 Store, Totals, Status-Maschine, ListView mit Stats-Cards, Routes
M3 Settings + Logo 76060b063 SenderProfileForm, Logo-Upload via mana-media, im PDF-Header embedded
M4 PDF Basic 2dc298a79 pdf-lib, pagination, preview + download
M5 QR-Rechnung 5af23d30b swissqrbill/svg + PNG-Overlay, SCOR auto, address-parser-Heuristik
M6 Versand 08b7ac16b SendModal mit mailto + PDF-Download + Confirm → status sent
M7 Dashboard 2ee3a1a93 InvoicesOpenWidget mit Offen/Überfällig + Top-3 älteste überfällig
M8 AI-Tools a4bc7d2ee create_invoice, mark_paid, list_invoices, get_invoice_stats

Tests (59+ green): totals math, SCOR + address-parser (swissqrbill isSCORReferenceValid roundtrip), mail template + mailto encoding, shared-ai function-schema round-trip, mana-ai planner drift-guard.

Offene Phase-2/3-Punkte (nicht blockierend für Solo-Einsatz):

  • Nummernkreis-Multi-Device-Kollision: dokumentiert als gap (pragmatisch, solo-MVP-tauglich)
  • Strukturierte Adressen: Heuristik-Parser deckt Standard-CH/DE ab; komplexere Adressen fehlen → User sieht in Warning, was fehlt
  • Bezahlte Rechnung → finance-Transaktion: Cross-Modul-Hook, Phase 3
  • camt.053 Bankabgleich: Phase 2
  • Browser-Smoke-Test: SSR im Repo während Spaces-Refactor broken für alle Module — sobald das geheilt ist, erste echte Rechnung stellen.

Ziel

Ein Modul, mit dem der Nutzer Rechnungen an Kunden stellt: Rechnung anlegen → PDF generieren (inkl. Schweizer QR-Rechnung) → per Mail versenden → Zahlungs-Status nachhalten. Kernfrage: "Hat der Kunde bezahlt — und wenn nein, wann muss ich mahnen?"

Solo-Zielgruppe: Freelancer/Selbstständige in CH (+DE). Später trivial auf Vereine erweiterbar (Empfänger = Mitgliedergruppe → Serienrechnungslauf für Mitgliederbeiträge).

Abgrenzung

  • Kein Ersatz für finance — das bleibt das private Einnahmen/Ausgaben-Tracker. invoices ist die Outbound-Seite: Rechnungen stellen statt Transaktionen erfassen. Ein bezahlter Rechnungseingang kann später automatisch eine finance-Einnahme erzeugen (Cross-Modul-Hook, Phase 3).
  • Keine vollständige Buchhaltung — keine Kontenrahmen (SKR03/SKR42), keine doppelte Buchführung, keine MwSt-Voranmeldung. Das ist ClubDesk-Niveau Paket B-2 und explizit nicht MVP.
  • Kein Bankabgleich (camt.053) — Phase 2/3, separater Plan.
  • Kein Payment-Provider — keine Stripe/PayPal-Zahlungslinks im MVP. Zahlungseingang wird manuell markiert oder (später) per camt abgeglichen.
  • Kein CRM — Kundendaten kommen aus dem bestehenden contacts-Modul, nicht aus einer eigenen Kunden-Tabelle.

Entscheidung: eigenes Modul, nicht Finance-Erweiterung

Zwei Optionen wurden erwogen:

  1. In finance integrieren — Rechnung wäre ein Transaktions-Typ.
  2. Eigenes Modul invoices (gewählt).

Begründung für (2):

  • Rechnungen haben komplexeres Schema (Positionen, PDF-Assets, QR-Zahldaten, Status-Maschine Entwurf/Versendet/Bezahlt/Überfällig) — das verunreinigt das schlanke Finance-Schema.
  • Unterschiedliche Zielgruppe: finance ist Alltags-Tracking (jeder), invoices ist Selbstständigen-Workflow (Teilmenge).
  • Separater Launcher-Eintrag, eigene Route, eigene AI-Tools.
  • Finance bleibt encryption-minimal, invoices hat mehr sensitive Felder.

Cross-Link: Bezahlte Rechnung → optional auto-erzeugte finance-Einnahme (Phase 3).

Modul-Struktur

apps/mana/apps/web/src/lib/modules/invoices/
├── types.ts                       # LocalInvoice, Invoice, InvoiceLine, InvoiceStatus, Client
├── collections.ts                 # invoices + invoiceClients tables + guest-seed
├── queries.ts                     # useAllInvoices, useInvoicesByStatus, useInvoice(id), useStats, useOverdue
├── stores/
│   ├── invoices.svelte.ts         # create, update, markSent, markPaid, markOverdue, void, duplicate, delete
│   ├── clients.svelte.ts          # createClient, updateClient, deleteClient (optional — kann auch in contacts)
│   └── settings.svelte.ts         # Absender-Profil, Logo, Fußzeile, IBAN, MwSt-Nummer, Nummernkreis
├── components/
│   ├── InvoiceCard.svelte         # Listeneintrag: Nummer + Kunde + Betrag + Status-Badge + Fälligkeit
│   ├── InvoiceForm.svelte         # Create/Edit inkl. Positionen-Editor
│   ├── LinesEditor.svelte         # Positionen: Titel, Menge, Einheit, Einzelpreis, MwSt-Satz
│   ├── StatusBadge.svelte         # draft / sent / paid / overdue / void
│   ├── StatusFilter.svelte        # Filter-Chips
│   ├── ClientPicker.svelte        # Auswahl aus contacts ODER invoiceClients
│   ├── SenderProfileForm.svelte   # einmalige Einrichtung (Name, IBAN, MwSt, Logo)
│   └── InvoicePreview.svelte      # Live-Preview des PDFs (iframe oder react-pdf react-free Variante)
├── pdf/
│   ├── renderer.ts                # erzeugt Blob via pdf-lib (Header, Tabelle, Footer, QR-Slip)
│   ├── qr-bill.ts                 # swissqrbill-Integration (SPC-kompatibel, CHF/EUR)
│   └── templates/
│       └── default.ts             # Layout-Konstanten (Margins, Fonts, Farben)
├── views/
│   ├── ListView.svelte            # komponiert Filter + Grid/Liste
│   ├── DetailView.svelte          # Rechnungsdetail + PDF-Preview + Aktionen
│   └── SettingsView.svelte        # Absender-Profil + Nummernkreis-Editor
├── tools.ts                       # AI-Tools — siehe §AI-Integration
├── constants.ts                   # STATUS_LABELS, VAT_RATES_CH, VAT_RATES_DE, DEFAULT_DUE_DAYS
├── module.config.ts               # { appId: 'invoices', tables: [{ name: 'invoices' }, { name: 'invoiceClients' }, { name: 'invoiceSettings' }] }
└── index.ts                       # Re-Exports

Daten-Schema

LocalInvoice (Dexie)

export type InvoiceStatus = 'draft' | 'sent' | 'paid' | 'overdue' | 'void';
export type Currency = 'CHF' | 'EUR' | 'USD';

export interface LocalInvoiceLine {
  id: string;                       // client-generated, stabil für Drag-Reorder
  title: string;                    // encrypted
  description?: string | null;      // encrypted
  quantity: number;                 // plaintext
  unit?: string | null;             // plaintext — "Std", "Stk", "Tag"
  unitPrice: number;                // plaintext — in kleinster Währungseinheit (Rappen/Cent), Integer
  vatRate: number;                  // plaintext — 0, 2.6, 3.8, 8.1 (CH) oder 0, 7, 19 (DE)
  discount?: number | null;         // plaintext — Prozent (0..100)
}

export interface LocalInvoice extends BaseRecord {
  number: string;                   // plaintext — "2026-0042", aus Nummernkreis generiert
  status: InvoiceStatus;            // plaintext — filterbar
  clientId: string | null;          // plaintext — FK entweder contacts.id ODER invoiceClients.id
  clientSource: 'contact' | 'invoice-client'; // plaintext — welche Tabelle
  clientSnapshot: {                 // encrypted — Kunden-Daten zum Zeitpunkt des Versands eingefroren
    name: string;
    address?: string;
    email?: string;
    vatNumber?: string;
  };
  currency: Currency;               // plaintext
  issueDate: string;                // plaintext — YYYY-MM-DD
  dueDate: string;                  // plaintext — YYYY-MM-DD
  sentAt?: string | null;           // plaintext — ISO timestamp
  paidAt?: string | null;           // plaintext — ISO timestamp
  lines: LocalInvoiceLine[];        // teils encrypted (Titel/Description), teils plaintext (Zahlen)
  subject?: string | null;          // encrypted — "Beratungsleistung April 2026"
  notes?: string | null;            // encrypted — Freitext unterhalb der Positionen
  terms?: string | null;            // encrypted — AGB/Zahlungsbedingungen
  referenceNumber?: string | null;  // plaintext — QR-Referenz (CH) oder Kundenreferenz
  pdfBlobKey?: string | null;       // plaintext — uload/media-ID des gerenderten PDFs (Cache)
  // Berechnete Totals — denormalisiert, bei jedem update neu
  totals: {
    net: number;                    // plaintext
    vat: number;                    // plaintext
    gross: number;                  // plaintext
    vatBreakdown: Array<{ rate: number; base: number; tax: number }>;
  };
}

LocalInvoiceClient (Dexie) — optional, Phase 1.5

Separate Tabelle für Rechnungs-spezifische Kundendaten, falls contacts nicht reicht. Gleiche Felder wie clientSnapshot, aber zentral editierbar. MVP startet ohne — bestehende contacts reichen für solo-User.

export interface LocalInvoiceClient extends BaseRecord {
  name: string;                     // encrypted
  address?: string | null;          // encrypted
  email?: string | null;            // encrypted
  vatNumber?: string | null;        // encrypted
  iban?: string | null;             // encrypted — für spätere SEPA-Lastschrift
  defaultCurrency: Currency;        // plaintext
  defaultDueDays: number;           // plaintext — 14, 30, 60
  notes?: string | null;            // encrypted
}

LocalInvoiceSettings (Dexie, Singleton pro User)

export interface LocalInvoiceSettings extends BaseRecord {
  // Absender
  senderName: string;               // encrypted
  senderAddress: string;            // encrypted
  senderEmail: string;              // encrypted
  senderVatNumber?: string | null;  // encrypted — "CHE-123.456.789 MWST"
  senderIban: string;               // encrypted — CH-IBAN für QR-Rechnung
  senderBic?: string | null;        // encrypted — für SEPA

  // Branding
  logoMediaId?: string | null;      // plaintext — uload-ID
  accentColor: string;              // plaintext — Hex
  footer?: string | null;           // encrypted — Fußzeile unter jedem PDF

  // Nummernkreis
  numberPrefix: string;             // plaintext — "2026-"
  numberPadding: number;            // plaintext — 4 → "0042"
  nextNumber: number;               // plaintext — Zähler, wird atomisch inkrementiert

  // Defaults
  defaultCurrency: Currency;
  defaultVatRate: number;           // plaintext — 8.1 (CH) oder 19 (DE)
  defaultDueDays: number;
  defaultTerms?: string | null;     // encrypted
}

Encryption-Registry

apps/mana/apps/web/src/lib/data/crypto/registry.ts:

invoices: {
  fields: ['clientSnapshot', 'subject', 'notes', 'terms', 'lines'],
  version: 1,
},
invoiceClients: {
  fields: ['name', 'address', 'email', 'vatNumber', 'iban', 'notes'],
  version: 1,
},
invoiceSettings: {
  fields: ['senderName', 'senderAddress', 'senderEmail', 'senderVatNumber', 'senderIban', 'senderBic', 'footer', 'defaultTerms'],
  version: 1,
},

Wichtig zu lines: Die komplette lines-Array wird encrypted serialisiert. Die Zahlenanteile wären zwar plaintext-fähig, aber die Trennung von encrypted Titel + plaintext Zahlen pro Line ist teuer in Dexie-Queries. Pragmatisch: ganze lines-Array encrypten, totals daneben plaintext halten für Filter/Summen.

Routing

apps/mana/apps/web/src/routes/(app)/invoices/
├── +page.svelte                   # ListView mit Status-Filter
├── new/+page.svelte               # InvoiceForm im Create-Mode
├── [id]/+page.svelte              # DetailView
├── [id]/edit/+page.svelte         # InvoiceForm im Edit-Mode
└── settings/+page.svelte          # SettingsView (Absender, Nummernkreis)

UI-Konzept

Landing (/invoices)

  • Top-Leiste: Status-Chips (Alle | Entwurf | Versendet | Bezahlt | Überfällig als rote Count-Badge), Zeitraum-Filter (Dieses Jahr | Letztes Jahr | Alle), Search
  • Kennzahlen-Karten: Offene Summe, Überfällig, YTD fakturiert, YTD bezahlt
  • Liste: InvoiceCard mit Nummer, Kunde, Fälligkeit, Bruttobetrag, Status-Badge; Click → DetailView
  • FAB: "+" öffnet new-Route (oder Modal, konsistent mit anderen Modulen)
  • Scene-Scope: Invoices ignorieren Scene-Scope per Default (kein Tag-System — Kunden/Nummer identifizieren, nicht Tags)

InvoiceForm

Links Eingabe, rechts Live-Preview des PDFs (auf Desktop). Mobile: Tab-Switch.

Sections:

  1. Kunde — ClientPicker (sucht in Contacts + InvoiceClients)
  2. Metadaten — Nummer (auto-generiert, editierbar), Issue-Date, Due-Date, Währung, Betreff
  3. PositionenLinesEditor: Tabelle mit Add/Remove/Reorder. Spalten: Titel, Menge, Einheit, Einzelpreis, MwSt%, Total (berechnet)
  4. Totals — Auto-berechnet: Netto pro MwSt-Satz, MwSt, Brutto
  5. Notizen & AGB — Markdown-Felder für zusätzlichen Text

Save-Actions: "Als Entwurf speichern" (status: draft) oder "Speichern & Versenden" (status: sent, öffnet Mail-Flow).

DetailView

  • Header: Nummer, Status-Badge, Aktionen-Menu (Bearbeiten, Duplizieren, Als bezahlt markieren, Stornieren, PDF herunterladen, Per Mail senden)
  • Linke Spalte: Rechnungs-Metadaten + Positionen als Read-Only-Tabelle
  • Rechte Spalte: PDF-Preview (iframe)
  • Footer: Activity-Log (erstellt, versendet, bezahlt, gemahnt) — aus _activity gefiltert

SettingsView

  • Absender-Profil-Form (einmalig auszufüllen, Onboarding-Block wenn leer)
  • Logo-Upload via bestehende uload-Infrastruktur
  • Nummernkreis-Konfigurator mit Preview ("Nächste Rechnung: 2026-0042")
  • Default-Werte (Währung, MwSt, Zahlungsfrist)

PDF-Rendering

Library-Wahl

  • pdf-lib (npm, ~130 KB gzipped, WebAssembly-frei) — reicht für Tabellen + Text + Logo.
  • swissqrbill (npm, CH-spezifisch, SPC-kompatibel) — generiert den Zahlteil als PDF-Seite oder PNG-Overlay.

Alternativen verworfen:

  • @react-pdf/renderer — React-only, Mana ist Svelte.
  • jsPDF — großer Footprint, weniger flexibel bei komplexen Layouts.
  • Server-Side-Rendering via Puppeteer in apps/api — wäre besser aber Phase 2. MVP bleibt client-side (local-first-Prinzip + keine neuen Service-Dependencies).

Render-Pipeline

invoice → renderer.ts
         ├── Layout-Engine: Header (Absender + Logo) → Empfänger-Block → Rechnungsmeta → Positionen-Tabelle → Totals → Notes → Footer
         ├── qr-bill.ts: swissqrbill.generate({ amount, iban, creditor, debtor, reference }) → PDF-Seite appendieren (nur wenn Currency=CHF)
         └── → Blob (application/pdf)

Rendering dauert ~100-300ms für typische Rechnungen (akzeptabel für Live-Preview mit debounce 500ms).

QR-Rechnung (CH) — Kern-USP

  • Pflichtfelder: IBAN, Creditor (Name + Adresse), Amount, Currency=CHF, Reference (QRR oder SCOR)
  • referenceNumber aus Invoice-Schema generiert via QRR-Algorithmus (27 Ziffern, Mod-10-Prüfziffer) ODER SCOR (ISO 11649 Creditor Reference)
  • swissqrbill kümmert sich um Swiss Cross + Perforation + Zahlteil-Layout

Registrierung (Checklist)

  1. apps/mana/apps/web/src/lib/modules/invoices/module.config.ts anlegen
  2. Config in apps/mana/apps/web/src/lib/data/module-registry.ts importieren + in MODULE_CONFIGS aufnehmen
  3. Dexie-Schema-Migration: db.version(N+1).stores({ invoices: 'id, number, status, userId, clientId, issueDate, dueDate', invoiceClients: 'id, userId', invoiceSettings: 'id, userId' }) hinzufügen — NICHT bestehende Versionen ändern
  4. Encryption-Registry-Einträge (siehe oben)
  5. Routes unter (app)/invoices/ anlegen
  6. App-Eintrag in packages/shared-branding/src/mana-apps.ts:
    { id: 'invoices', name: 'Rechnungen', description: {...}, icon: APP_ICONS.invoices, color: '#059669', status: 'development', requiredTier: 'alpha' }
    
  7. Icon in packages/shared-branding/src/app-icons.ts
  8. docs/MODULE_REGISTRY.md unter "Arbeit & Finanzen" ergänzen
  9. Guest-Seed in collections.ts (eine Demo-Rechnung im Status draft an einen seed-contact)
  10. Vitest-Tests:
    • Store-Mutationen (create, updateLines → totals neu berechnet, markPaid → paidAt gesetzt)
    • Encryption-Roundtrip
    • Nummer-Generator (atomisch, kein Duplikat bei gleichzeitigem Create)
    • QRR-Prüfziffer
    • PDF-Rendering smoke-test (Blob > 0 bytes)

AI-Integration (Phase 2)

Nachdem MVP steht, Tools in tools.ts registrieren und in @mana/shared-ai/src/tools/schemas.ts + AI_PROPOSABLE_TOOL_NAMES aufnehmen:

Tool Policy Beschreibung
create_invoice propose Rechnung als Entwurf anlegen — Kunde, Positionen, Fälligkeit aus Chat extrahieren
mark_invoice_paid propose Rechnung als bezahlt markieren (mit Datum)
list_invoices auto Status-Filter + Zeitraum für Planner-Kontext
get_invoice_stats auto Offene Summe, YTD-Umsatz, überfällige Anzahl
suggest_dunning propose Mahntext für überfällige Rechnung vorschlagen (nutzt Kundensnapshot + Fälligkeit + Betrag)
draft_invoice_from_timesheet propose Phase 3: verbindet times-Modul — "letzte 4 Wochen für Kunde X" → Rechnung

UX-Integration: <AiProposalInbox module="invoices" /> auf ListView einbetten. Mission-Beispiel: "Erinnere mich freitags an überfällige Rechnungen und schlag Mahntexte vor."

Kommunikation / Versand

MVP (Phase 1)

  • Button "Per Mail senden" → öffnet bestehende mail-Compose mit pre-filled Empfänger (aus clientSnapshot.email), Betreff (Rechnung {number}), Body (Vorlage mit Platzhaltern), Anhang = gerendertes PDF
  • User sendet manuell ab → Store setzt status='sent' + sentAt=now
  • Optional: nach Versand automatisches Dexie-_activity-Event "Rechnung versendet"

Phase 2 — Automatisiert

  • Eigener Endpoint in apps/api: POST /api/v1/invoices/:id/send → serverseitig Mail verschicken via Stalwart
  • E-Mail-Tracking (Opens) — analog zu Newsletter-Modul später
  • Zahlungs-Reminder (cron im mana-ai): "Rechnung seit 5 Tagen überfällig"

Milestones

Milestone Umfang Geschätzter Aufwand
M1 Skelett Modul registriert, Dexie-Tables, Encryption-Registry, leere ListView, Guest-Seed, App-Entry + Icon 0.5 Tag
M2 CRUD InvoiceForm + LinesEditor + DetailView, Totals-Berechnung, Status-Maschine, Nummer-Generator 3 Tage
M3 Settings SettingsView + Singleton-Store, Logo-Upload via uload, Nummernkreis-Editor 1 Tag
M4 PDF Basic pdf-lib-Renderer mit Header/Tabelle/Totals/Footer, Preview-iframe in DetailView, Download-Button 2 Tage
M5 QR-Rechnung swissqrbill-Integration, QRR-Referenz-Generator, IBAN-Validation, CHF-only Gating 1.5 Tage
M6 Versand "Per Mail senden"-Flow über bestehendes mail-Modul, status-Transition sent+sentAt 0.5 Tag
M7 Dashboard & Stats Kennzahlen-Karten, Overdue-Highlighting, useStats() + Dashboard-Widget 1 Tag
M8 AI-Tools 4 Tools (create_invoice, mark_paid, list, stats) + Proposal-Inbox 1.5 Tage

Summe MVP (M1M7): ~9.5 Arbeitstage → realistisch ~2.5 Wochen mit Testing/Polishing.

Phase 2 (nach MVP)

  • camt.053/054-Bankabgleich (separater Plan, eigenes Backend-Feature in mana-api)
  • SEPA-Lastschrift (pain.008-XML-Export)
  • Mahnwesen (3-Stufen-Mahnlauf)
  • Wiederkehrende Rechnungen (via rituals)
  • PDF-Rendering server-side via Puppeteer (bessere Typografie, custom fonts)
  • Auto-Sync bezahlter Rechnungen → finance-Transaktion

Phase 3 (Vereinsvariante)

  • Empfänger = Gruppe (aus contacts-Gruppen) → Serienrechnungslauf
  • Integration Mitglieder-Entität (aus Paket A)
  • Beitragsmodelle (Jahresbeitrag, Halbjahresbeitrag, gestaffelt nach Kategorie)
  • QR-Rechnung mit Vereins-Referenz pro Mitglied

Offene Fragen

  1. Nummernkreis-Atomicität: Bei schnellem doppeltem Create-Klick ohne Sync darf keine doppelte Nummer entstehen. Dexie-Transaktion um nextNumber-Read + nextNumber++ wrappen. Bei Sync-Konflikt (Offline Create auf zwei Geräten): LWW löst durch __fieldTimestamps, aber Nummern wären doppelt. Lösung: Beim Sync-Merge Kollision erkennen → älterer Eintrag behält Nummer, neuerer bekommt nextNumber neu zugewiesen. Design nötig vor M2.
  2. Kundensnapshot-Strategie: Beim Versenden wird der Kunden-Stand eingefroren (clientSnapshot). Was passiert bei Edit der Rechnung im Status sent? Option A: Edit nur im Status draft erlauben. Option B: Jede Edit-Operation neuen Snapshot nehmen. Empfehlung A — einfacher, entspricht Realität (versendete Rechnung ist rechtsverbindlich).
  3. Logo-Upload-Pfad: uload liefert Media-ID, aber PDF-Renderer braucht Bytes. Zwei Optionen: (a) Logo beim Render aus uload ziehen (async), oder (b) Base64-inline in invoiceSettings speichern. Empfehlung (a) weil Logos typischerweise >20 KB und wir encryption sparen wollen.
  4. Currency-Scope: MVP CHF + EUR reicht für 95% der Zielgruppe. USD/GBP später.
  5. Mehrsprachigkeit: Sollen Rechnungen sprachlich anpassbar sein (DE/EN/FR auf dem PDF)? Phase 2 — MVP: Sprache = UI-Sprache.
  6. Zero-Knowledge-Kompatibilität: Der clientSnapshot ist encrypted. Wenn ZK aktiv ist, kann der Server kein PDF renderen/versenden (Phase 2). Für MVP (client-side-Render) kein Problem.

Abhängigkeiten

  • contacts-Modul: vorhanden, wird für ClientPicker genutzt.
  • mail-Modul: vorhanden (beta), wird für Versand genutzt.
  • uload-Modul: vorhanden, wird für Logo + PDF-Cache genutzt.
  • npm: pdf-lib@^1, swissqrbill@^4 — beide MIT-lizenziert, aktiv gewartet.
  • Keine neue Backend-Service nötig für MVP.

Erfolgs-Kriterien

  • Tag 1 nach Launch: Till kann für einen realen Kunden eine QR-Rechnung stellen, als PDF herunterladen und manuell versenden.
  • Woche 2: Rechnung wird aus Mana heraus per Mail versendet, PDF landet als Anhang.
  • Monat 1: 35 externe Nutzer (Freelancer) testen das Modul und haben bezahlte Rechnungen generiert.
  • Monat 3: Mission "Erinnere mich an überfällige Rechnungen" läuft und schlägt Mahnungen vor.