Produktion: Format-Warteschlangen, Sammeldruck, Webhook, Preis-a/e

Produktions-Board:
- Massenauswahl (+ "Alle auswählen") mit Sammel-Druck und Sammel-Status
- Warteschlange je Maschine nach Format gebündelt; Formatabfrage
  (machines.current_format) statt automatischem Anschluss
- resiliente Ladeabfragen (Board bleibt ohne neue Felder nutzbar)

Agent (skrift-agent):
- druckt strikt ein Dokument nach dem anderen (Idle-Gate) -> behebt
  Verlust von Folgejobs während eines laufenden Drucks
- druckt nur Jobs des eingelegten Formats (current_format), kein Auto-Anschluss
- Heartbeat gedrosselt (nur bei Statuswechsel / alle 60 s)

Neues Modul "Manueller Druck" (skrift-neuauftrag):
- interner Auftrag ohne Zahlung, Vorschau, Platzhalter/Excel-Import,
  Felder bleiben nach dem Absenden stehen

Backend/Endpunkte (skrift-orders):
- jobs tragen format/format_label; /enqueue-bulk für Sammeldruck
- Webhook /inbound/:kind (JetFormBuilder) mit Token + tolerantem Body-Parser
- Kleinmengen-Aufschlag je Produkt über Faktoren a (Deckel) und e (Exponent)
- Fix: norm()-Shadowing in calculatePrice (norm is not a function)

Bootstrap:
- jobs.format/format_label, machines.current_format
- products.price_factor_a/e; Multiplikator-Formel auf a/e umgestellt inkl.
  verhaltensneutraler Migration bestehender Formel/Produkte

skrift-app (Konfigurator):
- Zeilenumbrüche im Textfeld erlaubt; Umschlag-Individualtext als Textfeld
- Platzhalter-Tabelle im Text-Schritt immer verfügbar (auch Sammelversand
  ohne Kuvert) inkl. Excel-Upload nur für Platzhalterwerte
- Danke-Seite: Button zurück zu skrift.de
- z-index-Fix: Platzhalter-Tabelle überlagert die Vorschau nicht mehr

WordPress-Preisrechner: a/e-Faktoren in PHP- und JS-Rechnung
Directus: SKRIFT_WEBHOOK_TOKEN in compose/.env.example

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Lucas Orth
2026-07-26 11:51:24 +02:00
parent f77ddb6a4c
commit 6232cecdb1
17 changed files with 1239 additions and 97 deletions

View File

@@ -41,7 +41,7 @@ async function directusFetch(path, options = {}) {
async function fetchOrder(orderId) {
const fields = [
'id', 'order_number', 'customer_email', 'font', 'status',
'needs_envelope', 'envelope_labeling', 'envelope_format',
'needs_envelope', 'envelope_labeling', 'envelope_text', 'envelope_format',
'text_template', 'motif_mode', 'own_cards', 'entries_count', 'preview_session',
'source_file', 'product.key', 'product.type', 'product.input_mode', 'format.key',
].join(',');