Unterschriftenservice als echter Auftrag: signature-order (Seiten-SVGs schreiben + Auftrag anlegen), Kundenmail-Feld + 'Auftrag anlegen'-Button, Output-Mount schreibbar
This commit is contained in:
@@ -245,6 +245,96 @@ const handler = (router, { services, getSchema, logger }) => {
|
||||
// A4-Signatur-SVGs als ZIP (zum Plotten in der lokalen Produktion).
|
||||
router.post('/signature-render', (req, res) => signatureAufruf(req, res, '/render', 'application/zip', 'zip_base64'));
|
||||
|
||||
// Unterschriftenservice als ECHTEN Auftrag anlegen: erzeugt je PDF-Seite eine
|
||||
// A4-SVG (leer, wo kein Marker), schreibt sie als Schriftstück in den Auftrags-
|
||||
// ordner und legt den Auftrag an. Danach läuft alles über die normale Produktion
|
||||
// (Drucken → Plotter). KEIN neuer Job-Typ. Setzt den schreibbaren Output-Mount voraus.
|
||||
router.post('/signature-order', async (req, res) => {
|
||||
const acc = req.accountability;
|
||||
if (!acc || !acc.user || !(acc.admin || acc.app)) return res.status(403).json({ error: 'Kein Zugriff.' });
|
||||
const body = req.body || {};
|
||||
if (!body.order && !body.document_id) return res.status(400).json({ error: 'order oder document_id erforderlich.' });
|
||||
const sigList = Array.isArray(body.signatures) ? body.signatures : [];
|
||||
if (!sigList.length) return res.status(400).json({ error: 'Mindestens eine Unterschrift wählen.' });
|
||||
try {
|
||||
const schema = await getSchema();
|
||||
const svc = (c) => new ItemsService(c, { schema, accountability: null });
|
||||
|
||||
let ord = null;
|
||||
if (body.order) {
|
||||
ord = await svc('orders').readOne(body.order, { fields: ['id', 'source_file', 'source_files'] });
|
||||
if (!ord) return res.status(404).json({ error: 'Auftrag nicht gefunden.' });
|
||||
}
|
||||
const pdfId = body.document_id || (ord ? (ord.source_file || (Array.isArray(ord.source_files) ? ord.source_files[0] : null)) : null);
|
||||
if (!pdfId) return res.status(400).json({ error: 'Kein Dokument (document_id fehlt).' });
|
||||
|
||||
const prodKey = body.product || 'unterschriftenservice';
|
||||
let product = (await svc('products').readByQuery({ filter: { key: { _eq: prodKey } }, limit: 1, fields: ['id', 'key', 'name'] }))?.[0];
|
||||
if (!product) product = (await svc('products').readByQuery({ filter: { input_mode: { _eq: 'datei' } }, limit: 1, fields: ['id', 'key', 'name'] }))?.[0];
|
||||
if (!product) return res.status(400).json({ error: 'Kein Unterschriftenservice-/Datei-Produkt gefunden.' });
|
||||
|
||||
let customerId = null, customerEmail = null;
|
||||
if (body.customer_email && isEmail(norm(body.customer_email))) {
|
||||
const u = (await new UsersService({ schema, accountability: null }).readByQuery({
|
||||
filter: { email: { _eq: norm(body.customer_email) } }, limit: 1, fields: ['id', 'email'],
|
||||
}))?.[0];
|
||||
if (u) { customerId = u.id; customerEmail = u.email; } else customerEmail = norm(body.customer_email);
|
||||
}
|
||||
|
||||
// A4-Signaturen erzeugen (eine je Seite) – JSON-Variante.
|
||||
const { form, jobCount } = await signatureForm(schema, pdfId, sigList);
|
||||
if (!jobCount) return res.status(400).json({ error: 'Keine gültige Signatur (SVG-Datei fehlt).' });
|
||||
let r;
|
||||
try { r = await fetch(`${SIGN_URL}/render-json`, { method: 'POST', body: form }); }
|
||||
catch (e) { return res.status(502).json({ error: 'Signatur-Service nicht erreichbar.', detail: `${SIGN_URL} – ${e.message}` }); }
|
||||
if (!r.ok) {
|
||||
let d = ''; try { d = (await r.json())?.detail || ''; } catch { /* ignore */ }
|
||||
return res.status(502).json({ error: 'Signatur-Service-Fehler (Marker im PDF?).', detail: String(Array.isArray(d) ? d.join('; ') : d).slice(0, 400) });
|
||||
}
|
||||
const pages = (await r.json())?.pages || [];
|
||||
if (!pages.length) return res.status(422).json({ error: 'Keine Seiten erzeugt.' });
|
||||
|
||||
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 () => {
|
||||
const heutige = await svc('orders').readByQuery({ filter: { order_number: { _starts_with: `${praefix}-` } }, limit: -1, fields: ['id'] });
|
||||
return `${praefix}-${String((heutige?.length ?? 0) + 1).padStart(3, '0')}`;
|
||||
};
|
||||
const orderData = {
|
||||
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',
|
||||
source_file: pdfId, source_files: [pdfId],
|
||||
entries_count: pages.length, count_inland: pages.length, count_ausland: 0,
|
||||
};
|
||||
let orderId, orderNumber;
|
||||
for (let v = 0; v < 6; v += 1) {
|
||||
orderNumber = await naechsteNummer();
|
||||
try { orderId = await svc('orders').createOne({ order_number: orderNumber, ...orderData }); break; }
|
||||
catch (e) { if (v === 5 || !/unique|duplicate|bereits|exists/i.test(String(e?.message || ''))) throw e; }
|
||||
}
|
||||
|
||||
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}` });
|
||||
}
|
||||
await svc('status_history').createOne({ order: orderId, status: 'eingegangen', note: 'Unterschriftenservice – manuell angelegt.' }).catch(() => {});
|
||||
|
||||
// Seiten-SVGs als Schriftstück in den Auftragsordner schreiben.
|
||||
const ordner = path.join(OUTPUT_DIR, orderNumber);
|
||||
fs.mkdirSync(ordner, { recursive: true });
|
||||
pages.forEach((p, i) => {
|
||||
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 });
|
||||
} 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) });
|
||||
}
|
||||
});
|
||||
|
||||
// Policy-konformes Zufallspasswort (Kunde meldet sich ohnehin passwortlos an).
|
||||
const zufallsPasswort = () =>
|
||||
`Aa1!${crypto.randomBytes(24).toString('base64url').replace(/[^A-Za-z0-9]/g, '')}`;
|
||||
|
||||
Reference in New Issue
Block a user