Public-API, mehrseitige Schriftstücke, manuelle Aufträge & Mailer

Public-API (neu, directus-extension-skrift-api):
- Versionierter /v1-Namespace: POST /v1/orders, GET /v1/orders/:nr (Status
  pending/processing/shipped/cancelled), POST /v1/files (nur PDF, ≤80 MB).
- Auth über X-Api-Key (Authorization ist von Directus reserviert), SHA-256-Hash.
- Produkt-gebunden; Kuvert-only-Produkttyp; Kuvertformat automatisch (A4→DIN Lang, sonst C6);
  kein Platzhalter, kein Kunden-multipage/font_scale; Versandart englisch (single/bulk).
- Selbst gehostete Swagger-UI-Doku (/docs) + OpenAPI (/v1/openapi.json), englisch/technisch.
- Key-Hook (directus-extension-skrift-apikey): erzeugt Key automatisch, zeigt ihn
  einmalig in key_plain.

Mehrseitige Schriftstücke:
- generateLetterPages (Auto-Fluss, [[seitenumbruch]], Leerseiten-Filter).
- Preview/Order-Generierung schreiben letter_NNN_pM.svg; Seiten-Übersicht im Neuauftrag.
- Agent (skrift-agent): Seiten je Brief absteigend an den Plotter (Stapel-Reihenfolge).
- countLetterPages + /api/order/pagecount für die Seiten-Vorschau.

Manuelle Aufträge:
- Schriftgröße (Brief/Kuvert), Signatur auf dem Brief mit X/Y/Größe-Feinjustierung,
  realistische Handschrift bis in den finalen Druck durchgereicht.
- Spalten-Einfügen in der Empfängertabelle (Excel-Spalte je Zelle).

Mailer: Text-Alternative + Reply-To (Zustellbarkeit).

App: Freitext in Musteranfrage, Konfigurator-Reset, Lightbox-Seitenverhältnis +
Bild-Download, Sticky-Vorschau-Fix, Tab-Eingabe + Rechtsbündigkeit, Leerzeilen-Trim.

Bootstrap: neue Felder (font_scale_*, signatures_manual, multi_page, page_count,
realistic, shipping_day, api_client) + Collections api_clients, Produkttyp envelope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lucas Orth
2026-08-18 10:01:53 +02:00
parent db419bf8eb
commit 1d5c1a14d4
24 changed files with 1327 additions and 121 deletions

View File

@@ -157,7 +157,7 @@ const collections = [
f('key', 'string', { interface: 'input', unique: true, required: true }),
f('name', 'string', { interface: 'input', required: true }),
f('description', 'text', { interface: 'input-multiline' }),
dropdown('type', ['letter', 'postcard', 'sample', 'contact'], { required: true }),
dropdown('type', ['letter', 'postcard', 'envelope', 'sample', 'contact'], { required: true }),
dropdown('visibility', ['alle', 'privat', 'unternehmen'], { default: 'alle', note: 'Für wen im Konfigurator sichtbar.' }),
dropdown('pricing_mode', ['formel', 'auf_anfrage'], { default: 'formel' }),
f('base_price', 'float', { interface: 'input', note: 'Basispreis (netto). Leer bei auf_anfrage.' }),
@@ -249,7 +249,15 @@ const collections = [
m2o('format', 'formats'),
dropdown('person_type', ['privat', 'unternehmen'], { default: 'privat' }),
dropdown('font', ['tilda', 'alva', 'ellie']),
dropdown('source', ['configurator', 'portal', 'operator', 'extern'], { default: 'configurator', note: '„extern" = Bestellung über eine andere Plattform (z. B. Etsy), ohne Preis/Zahlung.' }),
f('realistic', 'boolean', { interface: 'boolean', default: true, note: 'Natürliche Handschrift-Variation (Jitter/Neigung).' }),
m2o('api_client', 'api_clients', { onDelete: 'SET NULL', note: 'Gesetzt bei Aufträgen über die Public-API.' }),
dropdown('shipping_day', ['montag', 'dienstag', 'mittwoch', 'donnerstag', 'freitag', 'samstag', 'sonntag'], { note: 'Vom Kunden gewünschter Versandtag (optional, v. a. Public-API).' }),
f('font_scale_letter', 'float', { interface: 'input', default: 1, note: 'Relative Schriftgröße Brief (0.8–1.5, 1 = Standard).' }),
f('font_scale_envelope', 'float', { interface: 'input', default: 1, note: 'Relative Schriftgröße Kuvert (0.8–1.5, 1 = Standard).' }),
f('signatures_manual', 'json', { interface: 'input-code', note: 'Signatur(en) auf dem Brief bei manuellen Aufträgen: { signatures:{signatur1:svg}, offsets:{signatur1:{x,y,scale}} }.' }),
f('multi_page', 'boolean', { interface: 'boolean', default: false, note: 'Mehrseitiges Schriftstück (Auto-Fluss über mehrere A4-Seiten).' }),
f('page_count', 'integer', { interface: 'input', note: 'Seiten je Schriftstück (bei mehrseitig; wird bei der Generierung gesetzt).' }),
dropdown('source', ['configurator', 'portal', 'operator', 'extern', 'api'], { default: 'configurator', note: '„extern" = andere Plattform (z. B. Etsy); „api" = Public-API-Zugang.' }),
dropdown('status', ['entwurf', 'vorbereitet', 'wartet_auf_zahlung', 'bezahlt', 'material_erwartet',
'material_eingegangen', 'in_queue', 'in_produktion', 'gedruckt', 'versendet', 'abgeschlossen', 'storniert'],
{ default: 'entwurf' }),
@@ -438,6 +446,24 @@ const collections = [
{ field: 'timestamp', type: 'timestamp', meta: { interface: 'datetime', readonly: true, special: ['date-created'] }, schema: {} },
] },
// API-Zugänge für Kunden (Public-API). Keys werden nur als SHA-256-Hash gespeichert.
{ collection: 'api_clients', meta: { icon: 'vpn_key', note: 'API-Zugänge für Kunden (Public-API).' }, fields: [
f('name', 'string', { interface: 'input', required: true, note: 'Bezeichnung, z. B. Firmenname.' }),
f('key_plain', 'string', { interface: 'input', note: 'Wird beim Anlegen automatisch erzeugt und EINMALIG angezeigt – jetzt kopieren! Verschwindet bei der nächsten Änderung. Nur der Hash bleibt gespeichert.' }),
f('key_prefix', 'string', { interface: 'input', note: 'Erkennungspräfix des Keys (nur Anzeige).' }),
f('key_hash', 'string', { interface: 'input', note: 'SHA-256 des API-Keys – automatisch gesetzt. Nicht manuell ändern.' }),
m2o('customer', 'directus_users', { note: 'Verknüpftes Kundenkonto (optional).' }),
m2o('product', 'products', { nullable: false, note: 'Einziges Produkt, das dieser Zugang bestellen darf.' }),
f('active', 'boolean', { interface: 'boolean', default: true }),
f('webhook_url', 'string', { interface: 'input', note: 'Optionaler Webhook für Statusänderungen (derzeit ungenutzt – Kunde pollt).' }),
f('config', 'json', { interface: 'input-code', note: 'Kundenspezifische Konfiguration (optional).' }),
f('last_used', 'timestamp', { interface: 'datetime' }),
// AGB/Vertrag – einmalige Zustimmung beim Onboarding festhalten (Nachweis).
f('terms_version', 'string', { interface: 'input', note: 'Akzeptierte AGB-Version, z. B. „2026-01".' }),
f('terms_accepted_at', 'timestamp', { interface: 'datetime', note: 'Zeitpunkt der AGB-/Vertragszustimmung.' }),
f('contract_ref', 'string', { interface: 'input', note: 'Referenz zum unterschriebenen Vertrag (Aktenzeichen/Link).' }),
] },
// Unterschriften je Kunde (SVG-Datei). Wird am Kundenkonto als o2m gezeigt.
{ collection: 'signatures', meta: { icon: 'draw', note: 'Unterschriften je Kunde.' }, fields: [
m2o('customer', 'directus_users', { onDelete: 'CASCADE' }),