Der eingehende Watcher nutzt denselben Input-Ordner für alle Dateitypen, schreibt INVRPT-Konvertierungen nun aber in einen konfigurierbaren separaten Output-Ordner. Bleibt das Feld leer, wird der Standard- Output-Ordner als Fallback verwendet (abwärtskompatibel). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
351 lines
14 KiB
JavaScript
351 lines
14 KiB
JavaScript
/**
|
||
* Watcher Bridge – Renderer-side logic for folder watcher
|
||
* Communicates with Electron main process via window.electronAPI
|
||
*
|
||
* Flow:
|
||
* 1. Main process detects new file via chokidar
|
||
* 2. Main sends file content to renderer via 'watcher-file-detected'
|
||
* 3. This bridge converts using the EDIBridge converters
|
||
* 4. Calls back to main to write output file
|
||
* 5. Calls back to main to move source file to archiv/
|
||
*/
|
||
window.EDIBridge = window.EDIBridge || {};
|
||
|
||
class WatcherBridge {
|
||
constructor() {
|
||
this.status = 'stopped'; // 'stopped', 'running', 'paused'
|
||
this.log = [];
|
||
this.maxLogEntries = 200;
|
||
this.onStatusChange = null;
|
||
this.onLogEntry = null;
|
||
this.config = null;
|
||
|
||
this._setupListeners();
|
||
}
|
||
|
||
_setupListeners() {
|
||
if (!window.electronAPI) return;
|
||
|
||
window.electronAPI.onFileDetected((data) => {
|
||
this._processDetectedFile(data);
|
||
});
|
||
|
||
window.electronAPI.onWatcherError((data) => {
|
||
this._addLog('error', data.filePath || '', data.error || 'Unbekannter Fehler');
|
||
});
|
||
|
||
window.electronAPI.onWatcherReady((data) => {
|
||
this._addLog('info', '', `Watcher bereit. Überwache: ${data.inputDir}`);
|
||
});
|
||
}
|
||
|
||
async _processDetectedFile(data) {
|
||
const { filePath, fileName, content, outputDir, invrptOutputDir } = data;
|
||
this._addLog('info', fileName, 'Datei erkannt, starte Konvertierung...');
|
||
|
||
let konvertierungsmodus = 'Unbekannt';
|
||
let quellformat = null;
|
||
let zielformat = null;
|
||
let convertedContent = null;
|
||
let pError = null;
|
||
let outFileName = null;
|
||
|
||
try {
|
||
// Try to use a global config if this.config is not set (e.g. from app.js)
|
||
if (!this.config && window.app) {
|
||
this.config = window.app.config;
|
||
}
|
||
|
||
const result = this._convertContent(content, fileName);
|
||
|
||
if (result.error) {
|
||
this._addLog('error', fileName, result.error);
|
||
pError = result.error;
|
||
} else {
|
||
konvertierungsmodus = result.type;
|
||
quellformat = result.format === 'vda' ? 'EDIFACT DELFOR' : 'VDA 4913';
|
||
zielformat = result.targetFormat || (result.format === 'vda' ? 'VDA 4905' : (result.format === 'xml' ? 'IFM DELVRY03' : 'Bosch DESADV'));
|
||
convertedContent = result.output;
|
||
|
||
// Build output filename
|
||
const baseName = fileName.replace(/\.[^.]+$/, '');
|
||
const outExt = result.format === 'vda' ? '.vda' : (result.format === 'xml' ? '.xml' : '.edi');
|
||
outFileName = baseName + '_converted' + outExt;
|
||
const isInvrpt = result.type && result.type.includes('INVRPT');
|
||
const effectiveOutputDir = (isInvrpt && invrptOutputDir) ? invrptOutputDir : outputDir;
|
||
const outPath = effectiveOutputDir + '\\' + outFileName;
|
||
|
||
// Write converted file
|
||
const writeResult = await window.electronAPI.writeFile(outPath, result.output);
|
||
|
||
if (writeResult.error) {
|
||
this._addLog('error', fileName, `Schreibfehler: ${writeResult.error}`);
|
||
pError = writeResult.error;
|
||
} else {
|
||
this._addLog('success', fileName, `Konvertiert → ${outFileName} (${result.type})`);
|
||
|
||
// Move source file to archiv
|
||
const archiveResult = await window.electronAPI.moveToArchive(filePath);
|
||
|
||
if (archiveResult.error) {
|
||
this._addLog('error', fileName, `Archiv-Fehler: ${archiveResult.error}`);
|
||
} else {
|
||
this._addLog('info', fileName, 'Quelldatei nach archiv/ verschoben.');
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
this._addLog('error', fileName, `Fehler: ${e.message}`);
|
||
pError = e.message;
|
||
}
|
||
|
||
// Log to database
|
||
if (window.app && typeof window.app._logConversion === 'function') {
|
||
await window.app._logConversion({
|
||
dateiname: fileName,
|
||
ausgangsdateiname: outFileName,
|
||
quellformat: quellformat,
|
||
zielformat: zielformat,
|
||
konvertierungsmodus: konvertierungsmodus,
|
||
eingang_daten: content,
|
||
ausgang_daten: convertedContent,
|
||
status: pError ? 'FEHLERHAFT' : 'ERFOLGREICH',
|
||
fehlermeldung: pError,
|
||
quelle: 'WATCHER' // Auto tag
|
||
});
|
||
}
|
||
}
|
||
|
||
_convertContent(content, fileName) {
|
||
const trimmed = content.trim();
|
||
const upper = trimmed.toUpperCase();
|
||
|
||
// Detect file type and choose converter
|
||
if (upper.startsWith('UNA') || upper.startsWith('UNB')) {
|
||
// EDIFACT inbound → VDA 4905
|
||
return this._convertInbound(content, upper);
|
||
} else if (/^[0-9]{3}/.test(trimmed) || /^[57][0-9]{2}/.test(trimmed)) {
|
||
// VDA record (starts with 3-digit record type, e.g. 511, 711) → EDIFACT outbound
|
||
return this._convertOutbound(content);
|
||
} else {
|
||
return { error: `Dateiformat nicht erkannt (weder EDIFACT noch VDA). Erste Zeichen: "${trimmed.substring(0, 20)}..."` };
|
||
}
|
||
}
|
||
|
||
_convertInbound(content, upper) {
|
||
try {
|
||
const ConfigParser = window.EDIBridge.ConfigParser;
|
||
const DelforParser = window.EDIBridge.DelforParser;
|
||
let preferredMode = null;
|
||
|
||
if (ConfigParser && DelforParser) {
|
||
const customerId = DelforParser.getCustomerId(content);
|
||
console.log("Watcher detected Inbound Customer ID:", customerId);
|
||
if (customerId) {
|
||
preferredMode = ConfigParser.lookupCustomerMode(this.config, customerId);
|
||
console.log("Watcher mapped Inbound mode for ID", customerId, ":", preferredMode);
|
||
}
|
||
}
|
||
|
||
// 1. Prioritize mapped mode
|
||
if (preferredMode === 'inbound-ifm') {
|
||
const converter = window.EDIBridge.DelforToVDA4905Converter;
|
||
if (converter) {
|
||
const result = converter.convert(content);
|
||
return { output: result, type: 'IFM DELFOR → VDA 4905', format: 'vda' };
|
||
}
|
||
} else if (preferredMode === 'inbound-bosch') {
|
||
const parser = window.EDIBridge.DelforParser;
|
||
const generator = window.EDIBridge.VDA4905Generator;
|
||
if (parser && generator) {
|
||
const parsed = parser.parse(content);
|
||
const vda4905 = generator.generate(parsed);
|
||
return { output: vda4905, type: 'DELFOR → VDA 4905', format: 'vda' };
|
||
}
|
||
} else if (preferredMode === 'inbound-invrpt') {
|
||
const converter = window.EDIBridge.InvrptToVDA4913;
|
||
if (converter) {
|
||
const vda4913 = converter.convert(content);
|
||
return { output: vda4913, type: 'INVRPT → VDA 4913', format: 'vda' };
|
||
}
|
||
}
|
||
|
||
// 2. Fallback to hardcoded logic if no mapping
|
||
if (upper.includes('INVRPT')) {
|
||
const converter = window.EDIBridge.InvrptToVDA4913;
|
||
if (converter) {
|
||
const vda4913 = converter.convert(content);
|
||
return { output: vda4913, type: 'INVRPT → VDA 4913', format: 'vda' };
|
||
}
|
||
}
|
||
|
||
if (upper.includes('DELFOR') && upper.includes('D:04A')) {
|
||
const converter = window.EDIBridge.DelforToVDA4905Converter;
|
||
if (converter) {
|
||
const result = converter.convert(content);
|
||
return { output: result, type: 'IFM DELFOR D:04A → VDA 4905', format: 'vda' };
|
||
}
|
||
}
|
||
|
||
const parser = window.EDIBridge.DelforParser;
|
||
const generator = window.EDIBridge.VDA4905Generator;
|
||
if (parser && generator) {
|
||
const parsed = parser.parse(content);
|
||
const vda4905 = generator.generate(parsed);
|
||
return { output: vda4905, type: 'DELFOR → VDA 4905', format: 'vda' };
|
||
}
|
||
|
||
return { error: 'Passendes Inbound-Modul nicht geladen oder Typ unbekannt.' };
|
||
} catch (e) {
|
||
return { error: `Inbound-Konvertierung fehlgeschlagen: ${e.message}` };
|
||
}
|
||
}
|
||
|
||
_convertOutbound(content) {
|
||
try {
|
||
const VDAParser = window.EDIBridge.VDAParser;
|
||
if (!VDAParser) return { error: 'VDA Parser nicht geladen.' };
|
||
|
||
const vda = VDAParser.parse(content);
|
||
if (!vda.interchanges || vda.interchanges.length === 0) {
|
||
return { error: 'Keine gültigen VDA 4913 Daten gefunden.' };
|
||
}
|
||
|
||
// Check for customer mapping in config
|
||
const ConfigParser = window.EDIBridge.ConfigParser;
|
||
const customerId = VDAParser.getCustomerId(content);
|
||
console.log("Watcher detected Customer ID:", customerId);
|
||
let preferredMode = null;
|
||
|
||
if (ConfigParser && customerId) {
|
||
preferredMode = ConfigParser.lookupCustomerMode(this.config, customerId);
|
||
console.log("Watcher mapped mode for ID", customerId, ":", preferredMode);
|
||
}
|
||
|
||
if (preferredMode === 'outbound-ifm') {
|
||
const delvry = window.EDIBridge.VDA4913ToDELVRY03;
|
||
if (delvry) {
|
||
const idocs = delvry.convert(vda, this.config || {});
|
||
const output = delvry.toXML(idocs);
|
||
return { output, type: 'VDA 4913 → IFM DELVRY03', format: 'xml' };
|
||
}
|
||
} else if (preferredMode === 'outbound-zf') {
|
||
const desadvZf = window.EDIBridge.VDA4913ToDESADVZF;
|
||
if (desadvZf) {
|
||
const output = desadvZf.convert(content, {}, { linWeights: {}, pacWeights: {}, pacDims: {} }, this.config);
|
||
return { output, type: 'VDA 4913 → ZF DESADV', format: 'zf-edi', targetFormat: 'ZF DESADV' };
|
||
}
|
||
} else if (preferredMode === 'outbound-bosch') {
|
||
const desadv = window.EDIBridge.VDA4913ToDESADV;
|
||
if (desadv) {
|
||
const output = desadv.convert(content, {}, { linWeights: {}, pacWeights: {}, pacDims: {} }, this.config);
|
||
return { output, type: 'VDA 4913 → Bosch DESADV', format: 'edi' };
|
||
}
|
||
}
|
||
|
||
// Fallback to original logic if no mapping or mapped converter missing
|
||
// Try IFM DELVRY03 first
|
||
const delvry = window.EDIBridge.VDA4913ToDELVRY03;
|
||
if (delvry) {
|
||
const idocs = delvry.convert(vda, this.config || {});
|
||
const output = delvry.toXML(idocs);
|
||
return { output, type: 'VDA 4913 → IFM DELVRY03', format: 'xml' };
|
||
}
|
||
|
||
// Fallback to Bosch DESADV
|
||
const desadv = window.EDIBridge.VDA4913ToDESADV;
|
||
if (desadv) {
|
||
const output = desadv.convert(content, {}, { linWeights: {}, pacWeights: {}, pacDims: {} }, this.config);
|
||
return { output, type: 'VDA 4913 → Bosch DESADV', format: 'edi' };
|
||
}
|
||
|
||
return { error: 'Kein Outbound-Converter geladen.' };
|
||
} catch (e) {
|
||
return { error: `Outbound-Konvertierung fehlgeschlagen: ${e.message}` };
|
||
}
|
||
}
|
||
|
||
_addLog(level, fileName, message) {
|
||
const entry = {
|
||
time: new Date().toLocaleTimeString('de-DE'),
|
||
level,
|
||
fileName,
|
||
message
|
||
};
|
||
this.log.unshift(entry);
|
||
if (this.log.length > this.maxLogEntries) this.log.pop();
|
||
if (this.onLogEntry) this.onLogEntry(entry);
|
||
}
|
||
|
||
async start(inputDir, outputDir, invrptOutputDir) {
|
||
if (!window.electronAPI) {
|
||
return { error: 'Electron API nicht verfügbar. App läuft im Browser-Modus.' };
|
||
}
|
||
const result = await window.electronAPI.startWatcher({ inputDir, outputDir, invrptOutputDir });
|
||
if (result.success) {
|
||
this.status = 'running';
|
||
this._addLog('info', '', `Watcher gestartet. Überwache: ${inputDir}`);
|
||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||
} else if (result.error) {
|
||
this._addLog('error', '', result.error);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
async stop() {
|
||
if (!window.electronAPI) return;
|
||
const result = await window.electronAPI.stopWatcher();
|
||
if (result.success) {
|
||
this.status = 'stopped';
|
||
this._addLog('info', '', 'Watcher gestoppt.');
|
||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
async pause() {
|
||
if (!window.electronAPI) return;
|
||
const result = await window.electronAPI.pauseWatcher();
|
||
if (result.success) {
|
||
this.status = 'paused';
|
||
this._addLog('info', '', 'Watcher pausiert.');
|
||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
async resume() {
|
||
if (!window.electronAPI) return;
|
||
const result = await window.electronAPI.resumeWatcher();
|
||
if (result.success) {
|
||
this.status = 'running';
|
||
this._addLog('info', '', 'Watcher fortgesetzt.');
|
||
if (this.onStatusChange) this.onStatusChange(this.status);
|
||
} else if (result.error) {
|
||
this._addLog('error', '', result.error);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
async loadSettings() {
|
||
if (!window.electronAPI) return { inputDir: '', outputDir: '', mode: 'auto' };
|
||
return await window.electronAPI.loadSettings();
|
||
}
|
||
|
||
async saveSettings(settings) {
|
||
if (!window.electronAPI) return;
|
||
return await window.electronAPI.saveSettings(settings);
|
||
}
|
||
|
||
async selectFolder() {
|
||
if (!window.electronAPI) return null;
|
||
return await window.electronAPI.selectFolder();
|
||
}
|
||
|
||
isElectron() {
|
||
return !!window.electronAPI;
|
||
}
|
||
}
|
||
|
||
window.EDIBridge.WatcherBridge = WatcherBridge;
|