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:
@@ -45,7 +45,8 @@ function placeholdersOf(entry) {
|
||||
/** Übersetzt einen Directus-Auftrag in die Dokumentliste für runGeneration. */
|
||||
function buildDocuments(order, entries) {
|
||||
const font = order.font || 'tilda';
|
||||
const letterFormat = LETTER_FORMAT[order.format?.key] || 'a4';
|
||||
// Eigene Format-Schlüssel (freie Formate) unverändert durchreichen.
|
||||
const letterFormat = LETTER_FORMAT[order.format?.key] || order.format?.key || 'a4';
|
||||
const envFormat = ENVELOPE_FORMAT[order.envelope_format] || 'c6';
|
||||
const wantsEnvelope = !!order.needs_envelope && order.envelope_labeling !== 'keine';
|
||||
const envelopeType = order.envelope_labeling === 'freitext' ? 'custom' : 'recipient';
|
||||
@@ -72,7 +73,7 @@ function buildDocuments(order, entries) {
|
||||
index: nr,
|
||||
type: 'envelope',
|
||||
envelopeType,
|
||||
text: envelopeType === 'recipient' ? addressText(entry) : (order.text_template || ''),
|
||||
text: envelopeType === 'recipient' ? addressText(entry) : (order.envelope_text || order.text_template || ''),
|
||||
format: envFormat,
|
||||
font,
|
||||
placeholders,
|
||||
|
||||
@@ -57,6 +57,7 @@ function applyRenderFormats(rows) {
|
||||
PAGE_FORMATS = pf;
|
||||
FORMAT_LAYOUTS = fl;
|
||||
LINE_LIMITS = ll;
|
||||
signaledMisses.clear();
|
||||
}
|
||||
|
||||
// mm in Pixel umrechnen
|
||||
@@ -64,10 +65,27 @@ function mmToPx(mm) {
|
||||
return mm * PX_PER_MM;
|
||||
}
|
||||
|
||||
// ── Refresh bei unbekanntem Format ──────────────────────────────────────────
|
||||
// Wird ein Format angefragt, das (noch) nicht im Cache liegt, meldet page-layout
|
||||
// das einmalig an einen Handler (den der Server auf refreshRenderFormats setzt).
|
||||
// So werden neu in Directus angelegte Formate sofort nachgeladen, ohne die
|
||||
// stündliche Aktualisierung abzuwarten. Für DIESEN Aufruf greift noch der
|
||||
// Default – der nächste Aufruf hat das Format.
|
||||
let missHandler = null;
|
||||
const signaledMisses = new Set();
|
||||
function setFormatMissHandler(fn) { missHandler = fn; }
|
||||
function signalMiss(key) {
|
||||
const k = String(key || '').toLowerCase();
|
||||
if (!missHandler || !k || signaledMisses.has(k)) return;
|
||||
signaledMisses.add(k);
|
||||
try { missHandler(k); } catch { /* egal */ }
|
||||
}
|
||||
|
||||
// Layout für Format holen
|
||||
function getLayoutForFormat(format) {
|
||||
const normalizedFormat = String(format).toLowerCase();
|
||||
const layoutConfig = FORMAT_LAYOUTS[normalizedFormat];
|
||||
if (!layoutConfig) signalMiss(normalizedFormat);
|
||||
|
||||
if (!layoutConfig) {
|
||||
// Fallback auf a4
|
||||
@@ -89,6 +107,7 @@ function getLayoutForFormat(format) {
|
||||
// Seiten-Dimensionen holen
|
||||
function getPageDimensions(format) {
|
||||
const normalizedFormat = String(format).toLowerCase();
|
||||
if (!PAGE_FORMATS[normalizedFormat]) signalMiss(normalizedFormat);
|
||||
const dimensions = PAGE_FORMATS[normalizedFormat] || PAGE_FORMATS.a4;
|
||||
|
||||
return {
|
||||
@@ -110,6 +129,7 @@ function escapeXml(str) {
|
||||
// Zeilenlimit für Format holen
|
||||
function getLineLimit(format) {
|
||||
const normalizedFormat = String(format).toLowerCase();
|
||||
if (!LINE_LIMITS[normalizedFormat]) signalMiss(normalizedFormat);
|
||||
return LINE_LIMITS[normalizedFormat] || LINE_LIMITS.a4;
|
||||
}
|
||||
|
||||
@@ -122,4 +142,5 @@ module.exports = {
|
||||
getLineLimit,
|
||||
escapeXml,
|
||||
applyRenderFormats,
|
||||
setFormatMissHandler,
|
||||
};
|
||||
|
||||
@@ -9,12 +9,14 @@ const healthRoutes = require('./api/routes/health-routes');
|
||||
const errorHandler = require('./api/middleware/error-handler');
|
||||
const requestLogger = require('./api/middleware/request-logger');
|
||||
|
||||
const { applyRenderFormats } = require('./lib/page-layout');
|
||||
const { applyRenderFormats, setFormatMissHandler } = require('./lib/page-layout');
|
||||
const { fetchRenderFormats } = require('./services/directus-service');
|
||||
|
||||
// Zentrale Format-Definitionen aus Directus in den Cache holen. Läuft beim Start
|
||||
// und stündlich; Fehler sind unkritisch (die Defaults im page-layout greifen).
|
||||
let lastFormatRefresh = 0;
|
||||
async function refreshRenderFormats() {
|
||||
lastFormatRefresh = Date.now();
|
||||
try {
|
||||
const rows = await fetchRenderFormats();
|
||||
applyRenderFormats(rows);
|
||||
@@ -24,6 +26,14 @@ async function refreshRenderFormats() {
|
||||
}
|
||||
}
|
||||
|
||||
// Unbekanntes Format angefragt → sofort nachladen (max. alle 30 s, damit ein
|
||||
// dauerhaft unbekanntes Format kein Dauerfeuer auslöst).
|
||||
setFormatMissHandler((key) => {
|
||||
if (Date.now() - lastFormatRefresh < 30 * 1000) return;
|
||||
console.log(`[Formate] Unbekanntes Format "${key}" angefragt – lade Formate nach.`);
|
||||
refreshRenderFormats();
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
// Middleware
|
||||
|
||||
@@ -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(',');
|
||||
|
||||
Reference in New Issue
Block a user