All checks were successful
Build & Deploy / deploy (push) Successful in 13s
Neues render_format-kind 'postkarte'. generatePostcardSVG setzt den Text auf die linke Kartenhälfte, Absender (klein, oben) und Empfänger (Anschriftzone unten) auf die rechte Hälfte; oben rechts bleibt das Briefmarkenfeld frei. Ein SVG je Karte (ein Plotter-Durchlauf), geschrieben als letter_NNN.svg. - page-layout: kind je Format mitführen + getFormatKind(). - svg-generator: generatePostcardSVG (Body/Absender/Empfänger über Marker [[pk_abs]]/[[pk_adr]] getrennt, damit alle Teile scriptalisiert werden). - order-/preview-/directus-controller: type 'postcard' dispatchen, Marker in der Scriptalisierung erhalten, Postkarten-Dokument aus render_format-kind bauen. - directus-service: kind aus render_formats mitladen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
609 lines
22 KiB
JavaScript
609 lines
22 KiB
JavaScript
const fs = require('fs').promises;
|
||
const fsSync = require('fs');
|
||
const path = require('path');
|
||
const config = require('../../config');
|
||
const { scriptalizeBatch } = require('../../services/scriptalizer-service');
|
||
const { generateLetterSVG, generateEnvelopeSVG, generatePostcardSVG, generateLetterPages } = require('../../lib/svg-generator');
|
||
const { replacePlaceholders, generateAllCSVs } = require('../../services/placeholder-service');
|
||
const { TAB_MARKER } = require('../../lib/svg-font-engine');
|
||
|
||
/**
|
||
* POST /api/order/finalize
|
||
* Finalize order by copying cached previews to output directory
|
||
*/
|
||
async function finalizeOrder(req, res, next) {
|
||
try {
|
||
const { sessionId, orderNumber } = req.body;
|
||
|
||
// Validation
|
||
if (!sessionId || !orderNumber) {
|
||
return res.status(400).json({
|
||
error: 'Invalid request',
|
||
message: 'sessionId and orderNumber are required'
|
||
});
|
||
}
|
||
|
||
console.log(`[Order] Finalizing order: ${orderNumber} from session: ${sessionId}`);
|
||
|
||
// Check if session cache exists
|
||
const sessionDir = path.join(config.paths.previews, sessionId);
|
||
|
||
try {
|
||
await fs.access(sessionDir);
|
||
} catch {
|
||
return res.status(404).json({
|
||
error: 'Session not found',
|
||
message: `Preview cache not found for session: ${sessionId}. Please generate previews first.`
|
||
});
|
||
}
|
||
|
||
// Check cache expiration
|
||
const metadataPath = path.join(sessionDir, '.metadata.json');
|
||
try {
|
||
const metadataContent = await fs.readFile(metadataPath, 'utf8');
|
||
const metadata = JSON.parse(metadataContent);
|
||
|
||
const expiresAt = new Date(metadata.expiresAt);
|
||
if (new Date() > expiresAt) {
|
||
return res.status(410).json({
|
||
error: 'Cache expired',
|
||
message: 'Preview cache has expired. Please regenerate previews.',
|
||
expiredAt: metadata.expiresAt
|
||
});
|
||
}
|
||
} catch (err) {
|
||
console.warn(`[Order] Metadata not found for session: ${sessionId}`);
|
||
}
|
||
|
||
// Create output directory and subdirectories
|
||
const outputDir = path.join(config.paths.output, orderNumber);
|
||
const envelopesDir = path.join(outputDir, 'umschlaege');
|
||
await fs.mkdir(outputDir, { recursive: true });
|
||
await fs.mkdir(envelopesDir, { recursive: true });
|
||
|
||
// Copy files from cache to output, separating letters and envelopes
|
||
const files = await fs.readdir(sessionDir);
|
||
const copiedLetters = [];
|
||
const copiedEnvelopes = [];
|
||
const copiedOther = [];
|
||
|
||
for (const file of files) {
|
||
// Skip metadata file
|
||
if (file === '.metadata.json') {
|
||
continue;
|
||
}
|
||
|
||
const sourcePath = path.join(sessionDir, file);
|
||
|
||
// Determine destination based on file type
|
||
if (file.startsWith('envelope_')) {
|
||
// Umschläge in Unterordner 'umschlaege'
|
||
const destPath = path.join(envelopesDir, file);
|
||
await fs.copyFile(sourcePath, destPath);
|
||
copiedEnvelopes.push(file);
|
||
} else if (file.startsWith('letter_')) {
|
||
// Briefe im Hauptordner
|
||
const destPath = path.join(outputDir, file);
|
||
await fs.copyFile(sourcePath, destPath);
|
||
copiedLetters.push(file);
|
||
} else {
|
||
// Andere Dateien (CSV, etc.) im Hauptordner
|
||
const destPath = path.join(outputDir, file);
|
||
await fs.copyFile(sourcePath, destPath);
|
||
copiedOther.push(file);
|
||
}
|
||
}
|
||
|
||
// Create order metadata
|
||
const meta = req.body.metadata || {};
|
||
const orderMetadata = {
|
||
orderNumber,
|
||
sessionId,
|
||
finalizedAt: new Date().toISOString(),
|
||
letterCount: copiedLetters.length,
|
||
envelopeCount: copiedEnvelopes.length,
|
||
letters: copiedLetters,
|
||
envelopes: copiedEnvelopes,
|
||
other: copiedOther,
|
||
letterFormat: meta.letterFormat || null,
|
||
envelopeFormat: meta.envelopeFormat || null,
|
||
envelopeBeschriftung: meta.envelopeBeschriftung || null,
|
||
};
|
||
|
||
const orderMetadataPath = path.join(outputDir, 'order-metadata.json');
|
||
await fs.writeFile(orderMetadataPath, JSON.stringify(orderMetadata, null, 2), 'utf8');
|
||
|
||
const totalFiles = copiedLetters.length + copiedEnvelopes.length + copiedOther.length;
|
||
console.log(`[Order] Finalized order ${orderNumber}: ${copiedLetters.length} letters, ${copiedEnvelopes.length} envelopes, ${copiedOther.length} other files`);
|
||
|
||
// Response
|
||
res.status(200).json({
|
||
orderNumber,
|
||
outputPath: outputDir,
|
||
letters: copiedLetters,
|
||
envelopes: copiedEnvelopes,
|
||
other: copiedOther,
|
||
totalFiles,
|
||
envelopesGenerated: 0,
|
||
timestamp: orderMetadata.finalizedAt
|
||
});
|
||
|
||
} catch (err) {
|
||
console.error('[Order] Error finalizing order:', err);
|
||
next(err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* POST /api/order/generate
|
||
* Generate order from scratch without using cache
|
||
*/
|
||
async function runGeneration({ orderNumber, letters, metadata }) {
|
||
{
|
||
// Validation
|
||
if (!orderNumber) {
|
||
const e = new Error('orderNumber is required');
|
||
e.status = 400;
|
||
throw e;
|
||
}
|
||
|
||
if (!letters || !Array.isArray(letters) || letters.length === 0) {
|
||
const e = new Error('Letters array is required and must not be empty');
|
||
e.status = 400;
|
||
throw e;
|
||
}
|
||
|
||
console.log(`[Order] Generating order: ${orderNumber}`);
|
||
console.log(`[Order] Total documents: ${letters.length}`);
|
||
|
||
// Debug: Zeige welche Dokument-Typen ankommen
|
||
const letterCount = letters.filter(l => l.type !== 'envelope').length;
|
||
const envelopeCount = letters.filter(l => l.type === 'envelope').length;
|
||
console.log(`[Order] Letters: ${letterCount}, Envelopes: ${envelopeCount}`);
|
||
console.log(`[Order] Document types:`, letters.map(l => ({ index: l.index, type: l.type, envelopeType: l.envelopeType })));
|
||
|
||
// Create output directory and subdirectories
|
||
const outputDir = path.join(config.paths.output, orderNumber);
|
||
const envelopesDir = path.join(outputDir, 'umschlaege');
|
||
await fs.mkdir(outputDir, { recursive: true });
|
||
await fs.mkdir(envelopesDir, { recursive: true });
|
||
|
||
// Step 1: Prepare texts and replace placeholders
|
||
const processedLetters = letters.map((letter, loopIndex) => {
|
||
let text = letter.text || '';
|
||
|
||
// Signatur-Platzhalter separat extrahieren – sie werden NICHT als Text ersetzt
|
||
const sigPlaceholders = {};
|
||
const nonSigPlaceholders = {};
|
||
if (letter.placeholders && typeof letter.placeholders === 'object') {
|
||
Object.entries(letter.placeholders).forEach(([k, v]) => {
|
||
if (/^signatur\d+$/i.test(k)) sigPlaceholders[k.toLowerCase()] = v;
|
||
else nonSigPlaceholders[k] = v;
|
||
});
|
||
text = replacePlaceholders(text, nonSigPlaceholders);
|
||
}
|
||
|
||
return {
|
||
...letter,
|
||
index: letter.index !== undefined ? letter.index : loopIndex,
|
||
loopIndex,
|
||
processedText: text,
|
||
// Signaturen aus Platzhaltern UND direkt übergebene (manuelle Aufträge).
|
||
signatures: { ...(letter.signatures || {}), ...sigPlaceholders },
|
||
format: letter.format || 'a4',
|
||
font: letter.font || 'tilda',
|
||
type: letter.type || 'letter',
|
||
envelopeType: letter.envelopeType || 'recipient',
|
||
realisticHandwriting: letter.realisticHandwriting ?? false,
|
||
};
|
||
});
|
||
|
||
// Step 2: Group letters by font for batch scriptalization
|
||
const lettersByFont = {};
|
||
|
||
processedLetters.forEach(letter => {
|
||
const font = letter.font;
|
||
if (!lettersByFont[font]) {
|
||
lettersByFont[font] = [];
|
||
}
|
||
lettersByFont[font].push(letter);
|
||
});
|
||
|
||
// Step 3: Scriptalize texts by font
|
||
const scriptalizedMap = new Map();
|
||
|
||
for (const [font, fontLetters] of Object.entries(lettersByFont)) {
|
||
// Tabs (\t) und Signatur-Marker ([[signaturN]]) vor Scriptalisierung extrahieren,
|
||
// danach mit TAB_MARKER bzw. [[signaturN]] restaurieren.
|
||
const scriptalizerInputs = [];
|
||
const splitMaps = [];
|
||
|
||
for (const letter of fontLetters) {
|
||
const raw = letter.processedText;
|
||
const parts = raw.split(/(\t|\[\[signatur\d+\]\]|\[\[seitenumbruch\]\]|\[\[pk_abs\]\]|\[\[pk_adr\]\]|[&|])/gi);
|
||
const segments = [], markers = [];
|
||
for (let i = 0; i < parts.length; i++) {
|
||
if (i % 2 === 0) segments.push(parts[i]);
|
||
else markers.push(parts[i]);
|
||
}
|
||
// Leere Segmente NICHT an den Scriptalizer senden
|
||
const startIdx = scriptalizerInputs.length;
|
||
const hasContent = segments.map(s => s.trim().length > 0);
|
||
segments.forEach((s, j) => { if (hasContent[j]) scriptalizerInputs.push(s); });
|
||
splitMaps.push({ loopIndex: letter.loopIndex, startIdx, segments, markers, hasContent, fallback: raw });
|
||
}
|
||
|
||
console.log(`[Order] Scriptalizing ${scriptalizerInputs.length} segments with font: ${font}`);
|
||
|
||
try {
|
||
const scriptalizedAll = await scriptalizeBatch(scriptalizerInputs, font, config.scriptalizer.errFrequency);
|
||
|
||
for (const { loopIndex, startIdx, segments, markers, hasContent, fallback } of splitMaps) {
|
||
const nonEmptyCount = hasContent.filter(Boolean).length;
|
||
if (!nonEmptyCount && !markers.length) { scriptalizedMap.set(loopIndex, fallback); continue; }
|
||
let si = startIdx;
|
||
const segs = segments.map((_s, j) => hasContent[j] ? (scriptalizedAll[si++] || '') : '');
|
||
let reconstructed = segs[0] || '';
|
||
for (let i = 0; i < markers.length; i++) {
|
||
reconstructed += (markers[i] === '\t' ? TAB_MARKER : markers[i]);
|
||
reconstructed += (segs[i + 1] || '');
|
||
}
|
||
scriptalizedMap.set(loopIndex, reconstructed);
|
||
}
|
||
} catch (err) {
|
||
console.error(`[Order] Scriptalization failed for font ${font}:`, err.message);
|
||
const e = new Error(`Failed to scriptalize texts with font ${font}: ${err.message}`);
|
||
e.status = 500;
|
||
e.label = 'Scriptalization failed';
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
// Step 4: Generate SVG files - separate letters and envelopes
|
||
const generatedLetters = [];
|
||
const generatedEnvelopes = [];
|
||
// Vorschau-Session (falls vorhanden): bereits gezeigte Dokumente werden 1:1
|
||
// aus dem Vorschau-Cache übernommen, damit der Kunde exakt das gedruckte
|
||
// Ergebnis erhält, das er gesehen hat (gleiche Zufalls-Handschrift).
|
||
const previewSession = (metadata || {}).previewSession;
|
||
const previewsRoot = config.paths.previews;
|
||
|
||
for (let i = 0; i < processedLetters.length; i++) {
|
||
const letter = processedLetters[i];
|
||
const absoluteIndex = letter.index !== undefined ? letter.index : i;
|
||
const isEnv = letter.type === 'envelope';
|
||
const isPostcard = letter.type === 'postcard';
|
||
const pad = String(absoluteIndex).padStart(3, '0');
|
||
|
||
// Mehrseitiger Brief → N Seiten-Dateien letter_NNN_pM.svg (Seite 1..N).
|
||
if (!isEnv && letter.multiPage) {
|
||
const scriptalizedText = scriptalizedMap.get(letter.loopIndex) || letter.processedText;
|
||
let pages;
|
||
try {
|
||
({ pages } = generateLetterPages(scriptalizedText, letter.format, {
|
||
font: letter.font, realisticHandwriting: letter.realisticHandwriting ?? false, fontScale: letter.fontScale,
|
||
}));
|
||
} catch (err) {
|
||
const e = new Error(`Failed to generate SVG for letter ${i}: ${err.message}`);
|
||
e.status = 500; e.label = 'SVG generation failed'; throw e;
|
||
}
|
||
// Entweder ALLE Seiten aus der Vorschau übernehmen oder ALLE neu rendern –
|
||
// nicht mischen, sonst unterschiedliche Handschrift-Variation je Seite.
|
||
let alleInVorschau = false;
|
||
if (previewSession) {
|
||
alleInVorschau = true;
|
||
for (let p = 0; p < pages.length; p++) {
|
||
try { await fs.access(path.join(previewsRoot, previewSession, `letter_${pad}_p${p + 1}.svg`)); }
|
||
catch { alleInVorschau = false; break; }
|
||
}
|
||
}
|
||
for (let p = 0; p < pages.length; p++) {
|
||
const fn = `letter_${pad}_p${p + 1}.svg`;
|
||
const fp = path.join(outputDir, fn);
|
||
if (alleInVorschau) await fs.copyFile(path.join(previewsRoot, previewSession, fn), fp);
|
||
else await fs.writeFile(fp, pages[p], 'utf8');
|
||
generatedLetters.push(fn);
|
||
}
|
||
console.log(`[Order] letter_${pad}: ${pages.length} Seiten${alleInVorschau ? ' (aus Vorschau)' : ''}.`);
|
||
continue;
|
||
}
|
||
|
||
const filename = `${isEnv ? 'envelope' : 'letter'}_${pad}.svg`;
|
||
const filepath = isEnv ? path.join(envelopesDir, filename) : path.join(outputDir, filename);
|
||
|
||
// 1:1 aus der Vorschau übernehmen, wenn für dieses Dokument eine
|
||
// Vorschau-Datei existiert. Ausnahme: Kuvert mit Absender – das wird frisch
|
||
// gerendert, damit der Absender (oben links) sicher drauf ist.
|
||
if (previewSession && !(isEnv && metadata.envelopeSender)) {
|
||
const cached = path.join(previewsRoot, previewSession, filename);
|
||
try {
|
||
await fs.access(cached);
|
||
await fs.copyFile(cached, filepath);
|
||
(isEnv ? generatedEnvelopes : generatedLetters).push(filename);
|
||
console.log(`[Order] ${filename} 1:1 aus der Vorschau übernommen.`);
|
||
continue;
|
||
} catch { /* keine Vorschau-Datei → normal rendern */ }
|
||
}
|
||
|
||
const scriptalizedText = scriptalizedMap.get(letter.loopIndex) || letter.processedText;
|
||
let svgResult;
|
||
try {
|
||
if (isEnv) {
|
||
svgResult = generateEnvelopeSVG(
|
||
scriptalizedText, letter.format, letter.envelopeType,
|
||
{ font: letter.font, realisticHandwriting: letter.realisticHandwriting ?? false, fontScale: letter.fontScale, sender: metadata.envelopeSender || '', senderScale: metadata.envelopeSenderScale || 1 }
|
||
);
|
||
} else if (isPostcard) {
|
||
svgResult = generatePostcardSVG(
|
||
scriptalizedText, letter.format,
|
||
{ font: letter.font, realisticHandwriting: letter.realisticHandwriting ?? false, fontScale: letter.fontScale, senderScale: metadata.envelopeSenderScale || 1 }
|
||
);
|
||
} else {
|
||
svgResult = generateLetterSVG(
|
||
scriptalizedText, letter.format,
|
||
{ font: letter.font, realisticHandwriting: letter.realisticHandwriting ?? false, signatures: letter.signatures || {}, signatureOffsets: letter.signatureOffsets || {}, fontScale: letter.fontScale }
|
||
);
|
||
}
|
||
} catch (err) {
|
||
console.error(`[Order] SVG generation failed for letter ${i}:`, err.message);
|
||
const e = new Error(`Failed to generate SVG for letter ${i}: ${err.message}`);
|
||
e.status = 500;
|
||
e.label = 'SVG generation failed';
|
||
throw e;
|
||
}
|
||
|
||
await fs.writeFile(filepath, svgResult.svg, 'utf8');
|
||
(isEnv ? generatedEnvelopes : generatedLetters).push(filename);
|
||
}
|
||
|
||
console.log(`[Order] Generated ${generatedLetters.length} letters, ${generatedEnvelopes.length} envelopes`);
|
||
|
||
// Step 5: Generate all CSV files if placeholders are present
|
||
const hasPlaceholders = processedLetters.some(l => l.placeholders && Object.keys(l.placeholders).length > 0);
|
||
const csvFiles = [];
|
||
|
||
if (hasPlaceholders) {
|
||
try {
|
||
const csvs = generateAllCSVs(processedLetters);
|
||
|
||
// Brief-Platzhalter CSV
|
||
if (csvs.placeholders) {
|
||
const filename = 'brief_platzhalter.csv';
|
||
await fs.writeFile(path.join(outputDir, filename), csvs.placeholders, 'utf8');
|
||
csvFiles.push(filename);
|
||
console.log(`[Order] Generated brief_platzhalter.csv`);
|
||
}
|
||
|
||
// Empfänger CSV (für recipientData-Modus)
|
||
if (csvs.recipients) {
|
||
const filename = 'empfaenger.csv';
|
||
await fs.writeFile(path.join(outputDir, filename), csvs.recipients, 'utf8');
|
||
csvFiles.push(filename);
|
||
console.log(`[Order] Generated empfaenger.csv`);
|
||
}
|
||
|
||
// Umschlag-Platzhalter CSV (für customText-Modus)
|
||
if (csvs.envelopePlaceholders) {
|
||
const filename = 'umschlag_platzhalter.csv';
|
||
await fs.writeFile(path.join(outputDir, filename), csvs.envelopePlaceholders, 'utf8');
|
||
csvFiles.push(filename);
|
||
console.log(`[Order] Generated umschlag_platzhalter.csv`);
|
||
}
|
||
} catch (err) {
|
||
console.error('[Order] CSV generation failed:', err.message);
|
||
// Don't fail the request, CSV is optional
|
||
}
|
||
}
|
||
|
||
// Step 6: Create order metadata
|
||
const meta = metadata || {};
|
||
const orderMetadata = {
|
||
orderNumber,
|
||
generatedAt: new Date().toISOString(),
|
||
letterCount: generatedLetters.length,
|
||
envelopeCount: generatedEnvelopes.length,
|
||
letters: generatedLetters,
|
||
envelopes: generatedEnvelopes,
|
||
fonts: Object.keys(lettersByFont),
|
||
hasPlaceholders,
|
||
csvFiles,
|
||
letterFormat: meta.letterFormat || null,
|
||
envelopeFormat: meta.envelopeFormat || null,
|
||
envelopeBeschriftung: meta.envelopeBeschriftung || null,
|
||
};
|
||
|
||
const orderMetadataPath = path.join(outputDir, 'order-metadata.json');
|
||
await fs.writeFile(orderMetadataPath, JSON.stringify(orderMetadata, null, 2), 'utf8');
|
||
|
||
// Eingegebenen Text als reine Textdatei mitablegen (falls übergeben).
|
||
if (meta.rawText) {
|
||
try { await fs.writeFile(path.join(outputDir, 'brieftext.txt'), String(meta.rawText), 'utf8'); }
|
||
catch (e) { console.warn('[Order] brieftext.txt nicht geschrieben:', e.message); }
|
||
}
|
||
|
||
console.log(`[Order] Order ${orderNumber} generated successfully`);
|
||
|
||
return {
|
||
orderNumber,
|
||
outputPath: outputDir,
|
||
letters: generatedLetters,
|
||
envelopes: generatedEnvelopes,
|
||
csvFiles,
|
||
totalFiles: generatedLetters.length + generatedEnvelopes.length + csvFiles.length,
|
||
timestamp: orderMetadata.generatedAt
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* POST /api/order/generate
|
||
* HTTP-Wrapper um runGeneration – Verhalten unverändert.
|
||
*/
|
||
async function generateOrder(req, res, next) {
|
||
try {
|
||
const { orderNumber, letters, metadata } = req.body;
|
||
const result = await runGeneration({ orderNumber, letters, metadata });
|
||
res.status(200).json(result);
|
||
} catch (err) {
|
||
if (err.status) {
|
||
return res.status(err.status).json({
|
||
error: err.label || 'Invalid request',
|
||
message: err.message
|
||
});
|
||
}
|
||
console.error('[Order] Error generating order:', err);
|
||
next(err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* POST /api/order/motif
|
||
* Upload a motif image for an order
|
||
*/
|
||
async function uploadMotif(req, res, next) {
|
||
try {
|
||
const { orderNumber } = req.body;
|
||
|
||
// Validation
|
||
if (!orderNumber) {
|
||
return res.status(400).json({
|
||
error: 'Invalid request',
|
||
message: 'orderNumber is required'
|
||
});
|
||
}
|
||
|
||
if (!req.file) {
|
||
return res.status(400).json({
|
||
error: 'Invalid request',
|
||
message: 'No file uploaded'
|
||
});
|
||
}
|
||
|
||
console.log(`[Order] Uploading motif for order: ${orderNumber}`);
|
||
console.log(`[Order] File: ${req.file.originalname}, Size: ${req.file.size} bytes, Type: ${req.file.mimetype}`);
|
||
|
||
// Create output directory if it doesn't exist
|
||
const outputDir = path.join(config.paths.output, orderNumber);
|
||
await fs.mkdir(outputDir, { recursive: true });
|
||
|
||
// Determine file extension
|
||
const originalExt = path.extname(req.file.originalname).toLowerCase() || '.png';
|
||
const filename = `motif${originalExt}`;
|
||
const filepath = path.join(outputDir, filename);
|
||
|
||
// Write file to disk
|
||
await fs.writeFile(filepath, req.file.buffer);
|
||
|
||
console.log(`[Order] Motif saved to: ${filepath}`);
|
||
|
||
// Response
|
||
res.status(200).json({
|
||
success: true,
|
||
orderNumber,
|
||
filename,
|
||
path: filepath,
|
||
size: req.file.size,
|
||
mimetype: req.file.mimetype
|
||
});
|
||
|
||
} catch (err) {
|
||
console.error('[Order] Error uploading motif:', err);
|
||
next(err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* POST /api/order/files
|
||
* Speichert eine bereits vom WordPress-Frontend hochgeladene Datei
|
||
* (Adress-/Platzhalter-Excel, Briefdokument) im Auftragsordner ab,
|
||
* damit sie zusammen mit den generierten Umschlägen vorliegt.
|
||
*/
|
||
async function uploadOrderFile(req, res, next) {
|
||
try {
|
||
const { orderNumber, fileType } = req.body;
|
||
|
||
if (!orderNumber || !fileType) {
|
||
return res.status(400).json({
|
||
error: 'Invalid request',
|
||
message: 'orderNumber and fileType are required'
|
||
});
|
||
}
|
||
|
||
if (!req.file) {
|
||
return res.status(400).json({
|
||
error: 'Invalid request',
|
||
message: 'No file uploaded'
|
||
});
|
||
}
|
||
|
||
const outputDir = path.join(config.paths.output, orderNumber, 'dateien');
|
||
await fs.mkdir(outputDir, { recursive: true });
|
||
|
||
const ext = path.extname(req.file.originalname) || '';
|
||
const filename = `${fileType}${ext}`;
|
||
const filepath = path.join(outputDir, filename);
|
||
|
||
await fs.writeFile(filepath, req.file.buffer);
|
||
|
||
console.log(`[Order] File saved for order ${orderNumber}: ${filename}`);
|
||
|
||
res.status(200).json({
|
||
success: true,
|
||
orderNumber,
|
||
fileType,
|
||
filename,
|
||
path: filepath,
|
||
size: req.file.size,
|
||
});
|
||
|
||
} catch (err) {
|
||
console.error('[Order] Error saving order file:', err);
|
||
next(err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* POST /api/order/register
|
||
* Speichert Metadaten für einen Auftrag ohne SVG-Generierung (z.B. gedruckte Briefe)
|
||
*/
|
||
async function registerOrder(req, res, next) {
|
||
try {
|
||
const { orderNumber, metadata } = req.body;
|
||
|
||
if (!orderNumber) {
|
||
return res.status(400).json({ error: 'Invalid request', message: 'orderNumber is required' });
|
||
}
|
||
|
||
const outputDir = path.join(config.paths.output, orderNumber);
|
||
await fs.mkdir(outputDir, { recursive: true });
|
||
|
||
const orderMetadata = {
|
||
orderNumber,
|
||
registeredAt: new Date().toISOString(),
|
||
...(metadata || {}),
|
||
};
|
||
|
||
await fs.writeFile(
|
||
path.join(outputDir, 'order-metadata.json'),
|
||
JSON.stringify(orderMetadata, null, 2),
|
||
'utf8'
|
||
);
|
||
|
||
console.log(`[Order] Registered order ${orderNumber}`);
|
||
res.status(200).json({ orderNumber, registeredAt: orderMetadata.registeredAt });
|
||
|
||
} catch (err) {
|
||
console.error('[Order] Error registering order:', err);
|
||
next(err);
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
finalizeOrder,
|
||
generateOrder,
|
||
runGeneration,
|
||
uploadMotif,
|
||
uploadOrderFile,
|
||
registerOrder,
|
||
};
|