Files
Skrift-Kofnigurator/skrift-configurator/assets/js/configurator-backend-integration.js
2026-02-07 13:04:04 +01:00

585 lines
19 KiB
JavaScript

/**
* Backend Integration für Skrift Konfigurator
* Erweitert handleOrderSubmit um Backend-API Calls
*/
import SkriftBackendAPI from './configurator-api.js';
import { preparePlaceholdersForIndex } from './configurator-utils.js';
/**
* Bereitet Letter-Daten für Backend vor
*/
function prepareLettersForBackend(state) {
const letters = [];
const quantity = parseInt(state.answers?.quantity) || 1;
// Haupttext
const mainText = state.answers?.letterText || state.answers?.text || state.answers?.briefText || '';
const font = state.answers?.font || 'tilda';
const format = state.answers?.format || 'A4';
// Für jede Kopie einen Letter-Eintrag erstellen mit individuellen Platzhaltern
for (let i = 0; i < quantity; i++) {
const placeholders = preparePlaceholdersForIndex(state, i);
letters.push({
index: i,
text: mainText,
font: mapFontToBackend(font),
format: mapFormatToBackend(format),
placeholders: placeholders,
type: 'letter',
});
}
return letters;
}
/**
* Bereitet Envelope-Daten für Backend vor
*/
function prepareEnvelopesForBackend(state) {
const envelopes = [];
// envelope ist ein boolean (true/false), nicht 'yes'/'no'
const hasEnvelope = state.answers?.envelope === true;
console.log('[Backend Integration] prepareEnvelopesForBackend:', {
envelope: state.answers?.envelope,
hasEnvelope,
envelopeMode: state.answers?.envelopeMode,
});
if (!hasEnvelope) {
return envelopes;
}
const quantity = parseInt(state.answers?.quantity) || 1;
const envelopeMode = state.answers?.envelopeMode || 'recipientData';
const format = state.answers?.format || 'A4';
const font = state.answers?.envelopeFont || state.answers?.font || 'tilda';
// Envelope Format bestimmen
const envelopeFormat = format === 'a4' ? 'DIN_LANG' : 'C6';
if (envelopeMode === 'recipientData') {
// Empfängeradresse-Modus: Ein Envelope pro Brief mit individuellen Empfängerdaten
for (let i = 0; i < quantity; i++) {
const placeholders = preparePlaceholdersForIndex(state, i);
const recipient = state.recipientRows?.[i] || {};
// Umschlagtext aus Empfängerdaten zusammenbauen
const lines = [];
const fullName = `${recipient.firstName || ''} ${recipient.lastName || ''}`.trim();
if (fullName) lines.push(fullName);
const streetLine = `${recipient.street || ''} ${recipient.houseNumber || ''}`.trim();
if (streetLine) lines.push(streetLine);
const location = `${recipient.zip || ''} ${recipient.city || ''}`.trim();
if (location) lines.push(location);
if (recipient.country && recipient.country !== 'Deutschland') {
lines.push(recipient.country);
}
envelopes.push({
index: i,
text: lines.join('\n'),
font: mapFontToBackend(font),
format: envelopeFormat,
placeholders: placeholders,
type: 'envelope',
envelopeType: 'recipient',
});
}
} else if (envelopeMode === 'customText') {
// Custom Text Modus mit Platzhaltern
const customText = state.answers?.envelopeCustomText || '';
for (let i = 0; i < quantity; i++) {
const placeholders = preparePlaceholdersForIndex(state, i);
envelopes.push({
index: i,
text: customText,
font: mapFontToBackend(font),
format: envelopeFormat,
placeholders: placeholders,
type: 'envelope',
envelopeType: 'custom',
});
}
}
return envelopes;
}
// Hinweis: preparePlaceholdersForIndex ist jetzt in configurator-utils.js
/**
* Mapped Frontend-Font zu Backend-Font
*/
function mapFontToBackend(frontendFont) {
const fontMap = {
'tilda': 'tilda',
'alva': 'alva',
'ellie': 'ellie',
// Füge weitere Mappings hinzu falls nötig
};
return fontMap[frontendFont] || 'tilda';
}
/**
* Mapped Frontend-Format zu Backend-Format
*/
function mapFormatToBackend(frontendFormat) {
const formatMap = {
'a4': 'A4',
'a6p': 'A6_PORTRAIT',
'a6l': 'A6_LANDSCAPE',
'A4': 'A4',
'A6_PORTRAIT': 'A6_PORTRAIT',
'A6_LANDSCAPE': 'A6_LANDSCAPE',
};
return formatMap[frontendFormat] || 'A4';
}
/**
* Ermittelt das Umschlag-Format basierend auf Brief-Format
*/
function getEnvelopeFormat(letterFormat) {
const format = String(letterFormat).toLowerCase();
if (format === 'a4') return 'DIN_LANG';
if (format === 'a6p' || format === 'a6l') return 'C6';
return 'DIN_LANG';
}
/**
* Formatiert Format für lesbare Ausgabe
*/
function formatFormatLabel(format) {
const labels = {
'a4': 'A4 Hochformat',
'a6p': 'A6 Hochformat',
'a6l': 'A6 Querformat',
};
return labels[format] || format;
}
/**
* Formatiert Font für lesbare Ausgabe
*/
function formatFontLabel(font) {
const labels = {
'tilda': 'Tilda',
'alva': 'Alva',
'ellie': 'Ellie',
};
return labels[font] || font;
}
/**
* Baut das komplette Webhook-Datenobjekt zusammen
* Enthält ALLE relevanten Felder für Bestellbestätigung und n8n Workflow
*/
function buildWebhookData(state, backendResult) {
const answers = state.answers || {};
const order = state.order || {};
const quote = state.quote || {};
const ctx = state.ctx || {};
// Gutschein-Informationen
const voucherCode = order.voucherStatus?.valid ? order.voucherCode : null;
const voucherDiscount = order.voucherStatus?.valid ? (order.voucherStatus.discount || 0) : 0;
// Umschlag-Format ermitteln
const envelopeFormat = answers.envelope ? getEnvelopeFormat(answers.format) : null;
// Inland/Ausland zählen
let domesticCount = 0;
let internationalCount = 0;
const addressMode = state.addressMode || 'classic';
const rows = addressMode === 'free' ? (state.freeAddressRows || []) : (state.recipientRows || []);
for (const row of rows) {
if (!row) continue;
const country = addressMode === 'free' ? (row.line5 || '') : (row.country || '');
const countryLower = country.toLowerCase().trim();
const isDomestic = !countryLower ||
countryLower === 'deutschland' ||
countryLower === 'germany' ||
countryLower === 'de';
if (isDomestic) {
domesticCount++;
} else {
internationalCount++;
}
}
return {
// === BESTELLNUMMER & ZEITSTEMPEL ===
orderNumber: backendResult?.orderNumber || null,
timestamp: new Date().toISOString(),
// === KUNDE ===
customerType: answers.customerType || 'private',
customerTypeLabel: answers.customerType === 'business' ? 'Geschäftskunde' : 'Privatkunde',
// === PRODUKT ===
product: ctx.product?.key || null,
productLabel: ctx.product?.label || null,
productCategory: ctx.product?.category || null,
// === MENGE ===
quantity: parseInt(answers.quantity) || 0,
domesticCount: domesticCount,
internationalCount: internationalCount,
// === FORMAT & SCHRIFT ===
format: answers.format || null,
formatLabel: formatFormatLabel(answers.format),
font: answers.font || 'tilda',
fontLabel: formatFontLabel(answers.font || 'tilda'),
// === VERSAND ===
shippingMode: answers.shippingMode || null,
shippingModeLabel: answers.shippingMode === 'direct' ? 'Einzelversand durch Skrift' : 'Sammellieferung',
// === UMSCHLAG ===
envelopeIncluded: answers.envelope === true,
envelopeFormat: envelopeFormat,
envelopeFormatLabel: envelopeFormat === 'DIN_LANG' ? 'DIN Lang' : (envelopeFormat === 'C6' ? 'C6' : null),
envelopeMode: answers.envelopeMode || null,
envelopeModeLabel: answers.envelopeMode === 'recipientData' ? 'Empfängeradresse' :
(answers.envelopeMode === 'customText' ? 'Individueller Text' : null),
envelopeFont: answers.envelopeFont || answers.font || 'tilda',
envelopeFontLabel: formatFontLabel(answers.envelopeFont || answers.font || 'tilda'),
envelopeCustomText: answers.envelopeCustomText || null,
// === INHALT ===
contentCreateMode: answers.contentCreateMode || null,
contentCreateModeLabel: answers.contentCreateMode === 'self' ? 'Selbst erstellt' :
(answers.contentCreateMode === 'textservice' ? 'Textservice' : null),
letterText: answers.letterText || null,
// === MOTIV ===
motifNeeded: answers.motifNeed === true,
motifSource: answers.motifSource || null,
motifSourceLabel: answers.motifSource === 'upload' ? 'Eigenes Motiv hochgeladen' :
(answers.motifSource === 'printed' ? 'Bedruckte Karten verwenden' :
(answers.motifSource === 'design' ? 'Designservice' : null)),
motifFileName: answers.motifFileName || null,
motifFileMeta: answers.motifFileMeta || null,
// === SERVICES ===
serviceText: answers.serviceText === true,
serviceDesign: answers.serviceDesign === true,
serviceApi: answers.serviceApi === true,
// === FOLLOW-UP DETAILS (nur bei Follow-ups) ===
followupYearlyVolume: ctx.product?.isFollowUp ? (answers.followupYearlyVolume || null) : null,
followupCreateMode: ctx.product?.isFollowUp ? (answers.followupCreateMode || null) : null,
followupCreateModeLabel: ctx.product?.isFollowUp ? (
answers.followupCreateMode === 'auto' ? 'Automatisch (API)' :
(answers.followupCreateMode === 'manual' ? 'Manuell' : null)
) : null,
followupSourceSystem: ctx.product?.isFollowUp ? (answers.followupSourceSystem || null) : null,
followupTriggerDescription: ctx.product?.isFollowUp ? (answers.followupTriggerDescription || null) : null,
followupCheckCycle: ctx.product?.isFollowUp ? (answers.followupCheckCycle || null) : null,
followupCheckCycleLabel: ctx.product?.isFollowUp ? (
answers.followupCheckCycle === 'weekly' ? 'Wöchentlich' :
(answers.followupCheckCycle === 'monthly' ? 'Monatlich' :
(answers.followupCheckCycle === 'quarterly' ? 'Quartalsweise' : null))
) : null,
// === GUTSCHEIN ===
voucherCode: voucherCode,
voucherDiscount: voucherDiscount,
// === PREISE ===
currency: quote.currency || 'EUR',
subtotalNet: quote.subtotalNet || 0,
vatRate: quote.vatRate || 0.19,
vatAmount: quote.vatAmount || 0,
totalGross: quote.totalGross || 0,
priceLines: quote.lines || [],
// === KUNDENDATEN (Rechnungsadresse) ===
billingFirstName: order.billing?.firstName || '',
billingLastName: order.billing?.lastName || '',
billingCompany: order.billing?.company || '',
billingEmail: order.billing?.email || '',
billingPhone: order.billing?.phone || '',
billingStreet: order.billing?.street || '',
billingHouseNumber: order.billing?.houseNumber || '',
billingZip: order.billing?.zip || '',
billingCity: order.billing?.city || '',
billingCountry: order.billing?.country || 'Deutschland',
// === LIEFERADRESSE (falls abweichend) ===
shippingDifferent: order.shippingDifferent || false,
shippingFirstName: order.shippingDifferent ? (order.shipping?.firstName || '') : null,
shippingLastName: order.shippingDifferent ? (order.shipping?.lastName || '') : null,
shippingCompany: order.shippingDifferent ? (order.shipping?.company || '') : null,
shippingStreet: order.shippingDifferent ? (order.shipping?.street || '') : null,
shippingHouseNumber: order.shippingDifferent ? (order.shipping?.houseNumber || '') : null,
shippingZip: order.shippingDifferent ? (order.shipping?.zip || '') : null,
shippingCity: order.shippingDifferent ? (order.shipping?.city || '') : null,
shippingCountry: order.shippingDifferent ? (order.shipping?.country || 'Deutschland') : null,
// === EMPFÄNGERLISTE ===
addressMode: addressMode,
addressModeLabel: addressMode === 'free' ? 'Freie Adresszeilen' : 'Klassische Adresse',
recipients: addressMode === 'classic' ? (state.recipientRows || []).map((r, i) => ({
index: i,
firstName: r?.firstName || '',
lastName: r?.lastName || '',
street: r?.street || '',
houseNumber: r?.houseNumber || '',
zip: r?.zip || '',
city: r?.city || '',
country: r?.country || 'Deutschland',
})) : null,
recipientsFree: addressMode === 'free' ? (state.freeAddressRows || []).map((r, i) => ({
index: i,
line1: r?.line1 || '',
line2: r?.line2 || '',
line3: r?.line3 || '',
line4: r?.line4 || '',
line5: r?.line5 || '',
})) : null,
// === PLATZHALTER ===
placeholdersEnvelope: state.placeholders?.envelope || [],
placeholdersLetter: state.placeholders?.letter || [],
placeholderValues: state.placeholderValues || {},
// === BACKEND RESULT (falls vorhanden) ===
backendPath: backendResult?.path || null,
backendFiles: backendResult?.files || [],
backendSummary: backendResult?.summary || null,
};
}
/**
* Bereitet Metadaten für Backend vor
*/
function prepareOrderMetadata(state) {
return {
customer: {
type: state.answers?.customerType || 'private',
firstName: state.order?.firstName || '',
lastName: state.order?.lastName || '',
company: state.order?.company || '',
email: state.order?.email || '',
phone: state.order?.phone || '',
street: state.order?.street || '',
zip: state.order?.zip || '',
city: state.order?.city || '',
},
orderDate: new Date().toISOString(),
product: state.ctx?.product?.key || '',
quantity: state.answers?.quantity || 1,
format: state.answers?.format || 'A4',
shippingMode: state.answers?.shippingMode || 'direct',
quote: state.quote || {},
voucherCode: state.order?.voucherCode || null,
};
}
/**
* Erweiterte Order-Submit Funktion mit Backend-Integration
*/
export async function handleOrderSubmitWithBackend(state) {
const isB2B = state.answers?.customerType === "business";
const backend = window.SkriftConfigurator?.settings?.backend_connection || {};
const webhookUrl = backend.order_webhook_url;
const redirectUrlBusiness = backend.redirect_url_business;
const redirectUrlPrivate = backend.redirect_url_private;
const api = window.SkriftBackendAPI;
// Prüfe ob Backend konfiguriert ist
if (!backend.api_url) {
console.warn('[Backend Integration] Backend API URL nicht konfiguriert');
// Fallback zur alten Logik
return handleOrderSubmitLegacy(state);
}
try {
// 1. Backend Health Check
const isHealthy = await api.healthCheck();
if (!isHealthy) {
throw new Error('Backend ist nicht erreichbar');
}
// 2. Bestellnummer generieren (fortlaufend vom WP-Backend)
const orderNumber = await api.generateOrderNumber();
// 3. Daten vorbereiten
const letters = prepareLettersForBackend(state);
const envelopes = prepareEnvelopesForBackend(state);
const metadata = prepareOrderMetadata(state);
console.log('[Backend Integration] Generating order:', {
orderNumber,
letters,
envelopes,
metadata,
});
// 4. Order im Backend generieren
const result = await api.generateOrder(
orderNumber,
letters,
envelopes,
metadata
);
if (!result.success) {
throw new Error(result.error || 'Order generation failed');
}
console.log('[Backend Integration] Order generated successfully:', result);
// 5. Gutschein als verwendet markieren (falls vorhanden)
const voucherCode = state.order?.voucherStatus?.valid
? state.order.voucherCode
: null;
if (voucherCode) {
try {
const restUrl = window.SkriftConfigurator?.restUrl || "/wp-json/";
await fetch(restUrl + "skrift/v1/voucher/use", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code: voucherCode }),
});
} catch (error) {
console.warn('[Backend Integration] Fehler beim Markieren des Gutscheins:', error);
}
}
// 6. Webhook aufrufen (wenn konfiguriert)
if (webhookUrl) {
try {
const webhookData = buildWebhookData(state, result);
const response = await fetch(webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(webhookData),
});
if (!response.ok) {
console.warn('[Backend Integration] Webhook call failed:', response.statusText);
}
} catch (error) {
console.warn('[Backend Integration] Webhook error:', error);
}
}
// 7. Weiterleitung
if (isB2B) {
if (redirectUrlBusiness) {
// Bestellnummer als Query-Parameter anhängen
const redirectUrl = new URL(redirectUrlBusiness);
redirectUrl.searchParams.set('orderNumber', result.orderNumber);
window.location.href = redirectUrl.toString();
} else {
alert(
`Vielen Dank für Ihre Bestellung!\n\nBestellnummer: ${result.orderNumber}\n\nSie erhalten in Kürze eine Bestätigungs-E-Mail mit allen Details.`
);
}
} else {
// Privatkunde: Zu PayPal weiterleiten
if (redirectUrlPrivate) {
const redirectUrl = new URL(redirectUrlPrivate);
redirectUrl.searchParams.set('orderNumber', result.orderNumber);
window.location.href = redirectUrl.toString();
} else {
alert(
`Bestellung erfolgreich erstellt!\n\nBestellnummer: ${result.orderNumber}\n\nWeiterleitung zu PayPal folgt...`
);
}
}
} catch (error) {
console.error('[Backend Integration] Order submission failed:', error);
alert(
`Fehler bei der Bestellverarbeitung:\n\n${error.message}\n\nBitte versuchen Sie es erneut oder kontaktieren Sie uns.`
);
}
}
/**
* Legacy Order-Submit (Fallback ohne Backend)
*/
async function handleOrderSubmitLegacy(state) {
const isB2B = state.answers?.customerType === "business";
const backend = window.SkriftConfigurator?.settings?.backend_connection || {};
const webhookUrl = backend.order_webhook_url;
const redirectUrlBusiness = backend.redirect_url_business;
const redirectUrlPrivate = backend.redirect_url_private;
// Gutschein als verwendet markieren
const voucherCode = state.order?.voucherStatus?.valid
? state.order.voucherCode
: null;
if (voucherCode) {
try {
const restUrl = window.SkriftConfigurator?.restUrl || "/wp-json/";
await fetch(restUrl + "skrift/v1/voucher/use", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code: voucherCode }),
});
} catch (error) {
console.warn('Fehler beim Markieren des Gutscheins:', error);
}
}
// Webhook aufrufen
if (isB2B && webhookUrl) {
try {
const webhookData = buildWebhookData(state, null);
await fetch(webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(webhookData),
});
} catch (error) {
console.warn('Webhook error:', error);
}
}
// Weiterleitung
if (isB2B && redirectUrlBusiness) {
window.location.href = redirectUrlBusiness;
} else if (!isB2B && redirectUrlPrivate) {
window.location.href = redirectUrlPrivate;
} else {
alert("Vielen Dank für Ihre Bestellung!");
}
}
export default {
handleOrderSubmitWithBackend,
prepareLettersForBackend,
prepareEnvelopesForBackend,
mapFontToBackend,
mapFormatToBackend,
buildWebhookData,
};