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

@@ -47,14 +47,19 @@ export const VORLAGEN_SPALTEN = [
/** Spalten der Freitext-Variante: fünf frei beschreibbare Zeilen. */
export const FREITEXT_SPALTEN = ['Zeile 1', 'Zeile 2', 'Zeile 3', 'Zeile 4', 'Zeile 5'];
/** Erzeugt die Vorlagendatei und stößt den Download an. */
export async function ladeVorlageHerunter(zusatzSpalten: string[] = [], freitext = false) {
/**
* Erzeugt die Vorlagendatei und stößt den Download an.
* `nurZusatz` = nur die Platzhalter-Spalten (ohne Adressspalten) – für den
* Fall, dass nur Platzhalterwerte je Dokument gebraucht werden (Sammelversand
* ohne Kuvert), nicht aber Empfängeradressen.
*/
export async function ladeVorlageHerunter(zusatzSpalten: string[] = [], freitext = false, nurZusatz = false) {
const XLSX = await import('xlsx');
const basis = freitext ? FREITEXT_SPALTEN : VORLAGEN_SPALTEN;
const basis = nurZusatz ? [] : (freitext ? FREITEXT_SPALTEN : VORLAGEN_SPALTEN);
const kopf = [...basis, ...zusatzSpalten];
const beispielBasis = freitext
const beispielBasis = nurZusatz ? [] : (freitext
? ['Erika Mustermann', 'Hauptstraße 1', '10115 Berlin', '', '']
: ['Frau', 'Erika', 'Mustermann', 'Hauptstraße', '1', '10115', 'Berlin', 'DE'];
: ['Frau', 'Erika', 'Mustermann', 'Hauptstraße', '1', '10115', 'Berlin', 'DE']);
const beispiel = [...beispielBasis, ...zusatzSpalten.map(() => 'Beispielwert')];
const blatt = XLSX.utils.aoa_to_sheet([kopf, beispiel]);