Files
skrift-backend/src/services/directus-service.js
s4luorth 94751c12ff
All checks were successful
Build & Deploy / deploy (push) Successful in 11s
Batch-Buendelung: gezielte Auswahl per ids (FTP-Upload aus dem Board)
/api/batch/build akzeptiert jetzt ids:[...] und buendelt genau diese
Uebermittlungen (je Zugang, xlsx + Sammel-PDF + FTP). Der Board-Knopf
"Per FTP hochladen" wirkt damit auf die Auswahl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-28 10:23:58 +02:00

133 lines
5.1 KiB
JavaScript

/**
* Directus-Client für das Backend.
*
* Directus ist die Datenquelle für Aufträge; dieses Backend bleibt die reine
* Generierungs-Engine. Zugriff über einen Service-Token (Rolle "Service").
*/
const config = require('../config');
function requireConfig() {
if (!config.directus.url || !config.directus.token) {
const e = new Error('DIRECTUS_URL und DIRECTUS_TOKEN müssen gesetzt sein.');
e.status = 500;
throw e;
}
}
async function directusFetch(path, options = {}) {
requireConfig();
const res = await fetch(config.directus.url.replace(/\/$/, '') + path, {
...options,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${config.directus.token}`,
...(options.headers || {}),
},
});
const text = await res.text();
let data = null;
try { data = text ? JSON.parse(text) : null; } catch { /* ignore */ }
if (!res.ok) {
const msg = data?.errors?.[0]?.message || text || res.statusText;
const e = new Error(`Directus ${options.method || 'GET'} ${path}: ${msg}`);
e.status = res.status === 404 ? 404 : 502;
throw e;
}
return data?.data ?? data;
}
/** Lädt einen Auftrag inkl. Empfängerzeilen, Produkt und Format. */
async function fetchOrder(orderId) {
const fields = [
'id', 'order_number', 'customer_email', 'font', 'status',
'needs_envelope', 'envelope_labeling', 'envelope_text', 'envelope_format', 'envelope_sender', 'envelope_sender_scale',
'text_template', 'motif_mode', 'own_cards', 'entries_count', 'preview_session',
'source_file', 'product.key', 'product.type', 'product.input_mode', 'format.key',
'font_scale_letter', 'font_scale_envelope', 'signatures_manual', 'multi_page', 'realistic',
].join(',');
const order = await directusFetch(`/items/orders/${encodeURIComponent(orderId)}?fields=${fields}`);
if (!order) {
const e = new Error(`Auftrag ${orderId} nicht gefunden.`);
e.status = 404;
throw e;
}
const entries = await directusFetch(
`/items/order_entries?filter[order][_eq]=${encodeURIComponent(orderId)}` +
`&sort=letter_number&limit=-1&fields=*`
);
return { order, entries: entries || [] };
}
/** Schreibt Status und Artefakt-Pfad zurück. */
async function updateOrder(orderId, patch) {
return directusFetch(`/items/orders/${encodeURIComponent(orderId)}`, {
method: 'PATCH',
body: JSON.stringify(patch),
});
}
/** Protokolliert einen Statuswechsel in der Timeline. */
async function addStatusHistory(orderId, status, note) {
return directusFetch('/items/status_history', {
method: 'POST',
body: JSON.stringify({ order: orderId, status, note }),
});
}
/** Lädt mehrere Aufträge (nur die für den Charge-Sammeldruck nötigen Felder),
* sortiert nach Auftragsnummer. */
async function fetchOrdersMinimal(ids) {
const idList = ids.map((v) => encodeURIComponent(v)).join(',');
return directusFetch(
`/items/orders?filter[id][_in]=${idList}&limit=-1&sort=order_number` +
'&fields=id,order_number,source_files,source_file',
);
}
/** Lädt eine Directus-Datei (z. B. hochgeladenes Brief-PDF) als Buffer. */
async function fetchAssetBuffer(fileId) {
requireConfig();
const res = await fetch(config.directus.url.replace(/\/$/, '') + '/assets/' + encodeURIComponent(fileId), {
headers: { Authorization: `Bearer ${config.directus.token}` },
});
if (!res.ok) {
const e = new Error(`Directus asset ${fileId}: ${res.status}`);
e.status = res.status === 404 ? 404 : 502;
throw e;
}
return Buffer.from(await res.arrayBuffer());
}
/** Zentrale Format-Definitionen (Seitengröße, Ränder, Zeilenlimit) aus Directus. */
async function fetchRenderFormats() {
return directusFetch(
'/items/render_formats?filter[active][_eq]=true&limit=-1' +
'&fields=key,kind,page_width_mm,page_height_mm,margin_top_mm,margin_left_mm,margin_right_mm,line_height_factor,line_limit',
);
}
/** Offene (pending) Batch-Übermittlungen MIT Druck-PDF, optional je Zugang.
* Text-only-Datensätze (ohne PDF) werden bewusst NICHT gebündelt. */
async function fetchBatchPending(clientId, ids) {
let filter = 'filter[status][_eq]=pending&filter[pdf_file][_nnull]=true';
if (clientId != null) filter += `&filter[api_client][_eq]=${encodeURIComponent(clientId)}`;
if (Array.isArray(ids) && ids.length) filter += `&filter[id][_in]=${ids.map(encodeURIComponent).join(',')}`;
const fields = 'id,api_client,firma,vorname,nachname,zeile1,zeile2,zeile3,plz,stadt,land,pdf_file';
return (await directusFetch(`/items/batch_submissions?${filter}&sort=date_created&limit=-1&fields=${fields}`)) || [];
}
/** Markiert Datensätze als gebündelt (nach erfolgreichem FTP-Upload). */
async function markBatchBundled(ids, bundleName) {
if (!ids || !ids.length) return;
return directusFetch('/items/batch_submissions', {
method: 'PATCH',
body: JSON.stringify({ keys: ids, data: { status: 'bundled', bundle_name: bundleName, bundled_at: new Date().toISOString() } }),
});
}
module.exports = { fetchOrder, updateOrder, addStatusHistory, fetchRenderFormats, fetchOrdersMinimal, fetchAssetBuffer, fetchBatchPending, markBatchBundled };