Statistik-Tab, Unterschriftenservice-Kuverts, Auftragsverknüpfung & Notizen

- Produktion: Statistik-Tab (versendete Briefe je Kunde/Monat), verknüpfter
  Auftrag je Karte anzeigen/ändern/entfernen, Datei-Aufträge werden beim
  Übernehmen wieder in den Signatur-Tab überführt.
- Unterschriftenservice (neuauftrag): optionale Kuverts (je Empfänger ein
  Umschlag, Absender möglich) und Verknüpfung mit vorhandenem Auftrag.
- orders: signature-order erzeugt Kuverts über die Standard-Pipeline
  (Datei-Produkt -> nur Umschläge, Seiten bleiben erhalten) und speichert
  linked_order; nachträgliches Verknüpfen im Board.
- bootstrap: orders.linked_order (Self-Relation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-09-07 08:27:44 +02:00
parent 2b0c0028ed
commit ae7ef2422e
4 changed files with 188 additions and 12 deletions

View File

@@ -310,6 +310,20 @@ const handler = (router, { services, getSchema, logger }) => {
const pages = (await r.json())?.pages || [];
if (!pages.length) return res.status(422).json({ error: 'Keine Seiten erzeugt.' });
// Optionale Kuverts (wie manueller Druck): je Empfänger ein Umschlag. Empfänger
// als Blöcke (durch Leerzeile getrennt) im Freitext. Absender optional.
const kuvertsAn = !!body.kuverts;
const empf = kuvertsAn
? String(body.recipients_text || '')
.split(/\n\s*\n/).map((s) => s.trim()).filter(Boolean)
.map((free_text) => ({ free_text }))
: [];
if (kuvertsAn && !empf.length) return res.status(400).json({ error: 'Kuverts gewählt, aber keine Empfänger angegeben.' });
const envFormat = ['c6', 'dinlang'].includes(body.envelope_format) ? body.envelope_format : 'dinlang';
const absender = String(body.envelope_sender || '').trim();
const absenderScale = Math.max(0.5, Math.min(2, Number(body.envelope_sender_scale) || 1));
const countAusland = empf.filter((e) => !zielInland(e)).length;
const jt = new Date();
const praefix = `${String(jt.getDate()).padStart(2, '0')}-${String(jt.getMonth() + 1).padStart(2, '0')}-${String(jt.getFullYear()).slice(2)}`;
const naechsteNummer = async () => {
@@ -320,9 +334,14 @@ const handler = (router, { services, getSchema, logger }) => {
customer: customerId, customer_email: customerEmail, product: product.id,
person_type: 'privat', source: 'operator', status: 'in_queue', production_status: 'eingegangen',
payment_status: 'bezahlt', payment_method: 'rechnung', shipping_type: 'sammel',
needs_envelope: false, envelope_labeling: 'keine',
needs_envelope: kuvertsAn, envelope_labeling: kuvertsAn ? 'empfaenger' : 'keine',
envelope_format: kuvertsAn ? envFormat : null,
envelope_sender: kuvertsAn && absender ? absender : null,
envelope_sender_scale: absenderScale,
source_file: pdfId, source_files: [pdfId],
entries_count: pages.length, count_inland: pages.length, count_ausland: 0,
entries_count: kuvertsAn ? empf.length : pages.length,
count_inland: kuvertsAn ? (empf.length - countAusland) : pages.length,
count_ausland: kuvertsAn ? countAusland : 0,
};
let orderId, orderNumber;
for (let v = 0; v < 6; v += 1) {
@@ -332,11 +351,28 @@ const handler = (router, { services, getSchema, logger }) => {
}
const entriesSvc = svc('order_entries');
for (let i = 0; i < pages.length; i += 1) {
await entriesSvc.createOne({ order: orderId, letter_number: i + 1, free_text: `Seite ${i + 1}` });
if (kuvertsAn) {
// Empfänger als Zeilen – daraus erzeugt die Pipeline je einen Umschlag.
for (let i = 0; i < empf.length; i += 1) {
await entriesSvc.createOne({ order: orderId, letter_number: i + 1, free_text: empf[i].free_text });
}
} else {
for (let i = 0; i < pages.length; i += 1) {
await entriesSvc.createOne({ order: orderId, letter_number: i + 1, free_text: `Seite ${i + 1}` });
}
}
await svc('status_history').createOne({ order: orderId, status: 'eingegangen', note: 'Unterschriftenservice – manuell angelegt.' }).catch(() => {});
// Verknüpfung mit vorhandenem Auftrag (dessen Kuverts genutzt werden) – optional.
if (body.link_order_number) {
try {
const linked = (await svc('orders').readByQuery({
filter: { order_number: { _eq: String(body.link_order_number).trim() } }, limit: 1, fields: ['id'],
}))?.[0];
if (linked) await svc('orders').updateOne(orderId, { linked_order: linked.id });
} catch (e) { logger.warn(`[skrift-orders] signature-order Verknüpfung: ${e.message}`); }
}
// Seiten-SVGs als Schriftstück in den Auftragsordner schreiben.
const ordner = path.join(OUTPUT_DIR, orderNumber);
fs.mkdirSync(ordner, { recursive: true });
@@ -344,7 +380,16 @@ const handler = (router, { services, getSchema, logger }) => {
fs.writeFileSync(path.join(ordner, `letter_${String(i + 1).padStart(3, '0')}.svg`), p.svg, 'utf8');
});
return res.json({ ok: true, order_number: orderNumber, id: orderId, pages: pages.length });
// Kuverts über die Standard-Pipeline erzeugen (Datei-Produkt → NUR Umschläge;
// die Seiten-SVGs bleiben erhalten, da runGeneration den Ordner nicht leert).
if (kuvertsAn) {
fetch(`${BACKEND_URL}/api/order/from-directus`, {
method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Token': BACKEND_TOKEN },
body: JSON.stringify({ orderId }),
}).catch((e) => logger.warn(`[skrift-orders] signature-order Kuverts nicht gestartet: ${e.message}`));
}
return res.json({ ok: true, order_number: orderNumber, id: orderId, pages: pages.length, kuverts: empf.length });
} catch (err) {
logger.error(`[skrift-orders] signature-order: ${err.stack || err.message}`);
return res.status(500).json({ error: 'Auftrag konnte nicht angelegt werden.', detail: String(err.message || err).slice(0, 400) });