Muster-/Angebot-Endpunkte: WordPress-Feldnamen als Aliase akzeptieren

vorname→first_name, name→last_name, telefon→phone, zipcode→zip, ort→city,
produkt_interesse→product. App-Feldnamen behalten Vorrang. So kann ein
WordPress-Formular direkt Muster-/Angebotsanfragen anlegen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-09-11 21:28:10 +02:00
parent f4ba242953
commit e86fb476fa

View File

@@ -1423,16 +1423,20 @@ const handler = (router, { services, getSchema, logger }) => {
// ── Angebotsanfrage („Individuelles Angebot") ──────────────────────────── // ── Angebotsanfrage („Individuelles Angebot") ────────────────────────────
router.post('/offer-request', async (req, res) => { router.post('/offer-request', async (req, res) => {
const body = req.body || {}; const body = req.body || {};
const vorname = String(body.first_name || '').trim(); // Feld-Aliase (auch WordPress): vorname→first_name, name→last_name, telefon→phone,
const nachname = String(body.last_name || '').trim(); // produkt_interesse→product. App-Feldnamen haben Vorrang.
const name = [vorname, nachname].filter(Boolean).join(' ') || String(body.name || '').trim(); const vorname = String(body.first_name ?? body.vorname ?? '').trim();
const nachname = String(body.last_name ?? body.name ?? '').trim();
const name = [vorname, nachname].filter(Boolean).join(' ');
const phone = String(body.phone ?? body.telefon ?? '').trim();
const product = String(body.product ?? body.produkt_interesse ?? '').trim();
const email = norm(body.email); const email = norm(body.email);
const message = String(body.message || '').trim(); const message = String(body.message || '').trim();
const errors = []; const errors = [];
if (!name) errors.push('Name fehlt.'); if (!name) errors.push('Name fehlt.');
if (!isEmail(email)) errors.push('Gültige E-Mail-Adresse fehlt.'); if (!isEmail(email)) errors.push('Gültige E-Mail-Adresse fehlt.');
if (!message && !body.product) errors.push('Bitte beschreiben Sie Ihr Vorhaben.'); if (!message && !product) errors.push('Bitte beschreiben Sie Ihr Vorhaben.');
if (message.length > 5000) errors.push('Beschreibung ist zu lang.'); if (message.length > 5000) errors.push('Beschreibung ist zu lang.');
if (errors.length) return res.status(400).json({ errors }); if (errors.length) return res.status(400).json({ errors });
@@ -1454,9 +1458,9 @@ const handler = (router, { services, getSchema, logger }) => {
type: 'angebot', type: 'angebot',
name: clean(name), name: clean(name),
email, email,
phone: clean(String(body.phone || '').trim()) || null, phone: clean(phone) || null,
quantity: Number.isFinite(quantity) && quantity > 0 ? quantity : null, quantity: Number.isFinite(quantity) && quantity > 0 ? quantity : null,
product: body.product ? clean(String(body.product)) : null, product: product ? clean(product) : null,
message: clean(message), message: clean(message),
address: (vorname || nachname) ? { first_name: clean(vorname), last_name: clean(nachname) } : null, address: (vorname || nachname) ? { first_name: clean(vorname), last_name: clean(nachname) } : null,
status: 'neu', status: 'neu',
@@ -1464,7 +1468,7 @@ const handler = (router, { services, getSchema, logger }) => {
}); });
// Schlanke interne Benachrichtigung. // Schlanke interne Benachrichtigung.
const produkt = body.product ? clean(String(body.product)) : null; const produkt = product ? clean(product) : null;
await teamMail(schema, `Neue Angebotsanfrage: ${name}`, await teamMail(schema, `Neue Angebotsanfrage: ${name}`,
`<p>Neue Angebotsanfrage von <strong>${name}</strong> (${email}${body.phone ? `, ${clean(String(body.phone))}` : ''})` `<p>Neue Angebotsanfrage von <strong>${name}</strong> (${email}${body.phone ? `, ${clean(String(body.phone))}` : ''})`
+ `${produkt ? ` · ${produkt}` : ''} – im Board unter „Neu".</p>`); + `${produkt ? ` · ${produkt}` : ''} – im Board unter „Neu".</p>`);
@@ -1480,15 +1484,23 @@ const handler = (router, { services, getSchema, logger }) => {
router.post('/sample-request', async (req, res) => { router.post('/sample-request', async (req, res) => {
const body = req.body || {}; const body = req.body || {};
const email = norm(body.email); const email = norm(body.email);
// Vor-/Nachname getrennt; „name" bleibt als Fallback (Altstand/Kompatibilität). // Feld-Aliase, damit auch das WordPress-Formular direkt anlegen kann:
const vollName = [String(body.first_name || '').trim(), String(body.last_name || '').trim()].filter(Boolean).join(' ') // vorname→first_name, name→last_name, telefon→phone, zipcode→zip, ort→city,
|| String(body.name || '').trim(); // produkt_interesse→product. Die App-Feldnamen haben Vorrang.
const first = String(body.first_name ?? body.vorname ?? '').trim();
const last = String(body.last_name ?? body.name ?? '').trim();
const product = String(body.product ?? body.produkt_interesse ?? '').trim();
const phone = String(body.phone ?? body.telefon ?? '').trim();
const street = String(body.street ?? '').trim();
const zip = String(body.zip ?? body.zipcode ?? '').trim();
const city = String(body.city ?? body.ort ?? '').trim();
const country = String(body.country ?? '').trim();
const vollName = [first, last].filter(Boolean).join(' ');
const errors = []; const errors = [];
if (!body.product) errors.push('Produkt fehlt.'); if (!product) errors.push('Produkt fehlt.');
if (!vollName) errors.push('Name fehlt.'); if (!vollName) errors.push('Name fehlt.');
if (!isEmail(email)) errors.push('Gültige E-Mail-Adresse fehlt.'); if (!isEmail(email)) errors.push('Gültige E-Mail-Adresse fehlt.');
if (!String(body.street || '').trim() || !String(body.zip || '').trim() || !String(body.city || '').trim()) if (!street || !zip || !city) errors.push('Lieferadresse unvollständig.');
errors.push('Lieferadresse unvollständig.');
if (errors.length) return res.status(400).json({ errors }); if (errors.length) return res.status(400).json({ errors });
try { try {
@@ -1505,16 +1517,16 @@ const handler = (router, { services, getSchema, logger }) => {
type: 'muster', type: 'muster',
name: clean(vollName), name: clean(vollName),
email, email,
phone: clean(String(body.phone || '').trim()) || null, phone: clean(phone) || null,
product: clean(String(body.product)), product: clean(product),
message: clean(String(body.message || '').trim().slice(0, 1000)) || null, message: clean(String(body.message || '').trim().slice(0, 1000)) || null,
address: { address: {
first_name: clean(String(body.first_name || '').trim()), first_name: clean(first),
last_name: clean(String(body.last_name || '').trim()), last_name: clean(last),
street: clean(String(body.street || '')), street: clean(street),
zip: clean(String(body.zip || '')), zip: clean(zip),
city: clean(String(body.city || '')), city: clean(city),
country: clean(String(body.country || '')), country: clean(country),
}, },
status: 'neu', status: 'neu',
customer: customer?.id ?? null, customer: customer?.id ?? null,
@@ -1522,8 +1534,8 @@ const handler = (router, { services, getSchema, logger }) => {
const musterNachricht = clean(String(body.message || '').trim().slice(0, 1000)); const musterNachricht = clean(String(body.message || '').trim().slice(0, 1000));
await teamMail(schema, `Neue Musteranfrage: ${clean(vollName)}`, await teamMail(schema, `Neue Musteranfrage: ${clean(vollName)}`,
`<p>Neue Musteranfrage von <strong>${clean(vollName)}</strong> (${email}${body.phone ? `, ${clean(String(body.phone))}` : ''})` `<p>Neue Musteranfrage von <strong>${clean(vollName)}</strong> (${email}${phone ? `, ${clean(phone)}` : ''})`
+ ` · Produkt: ${clean(String(body.product))} – im Board unter „Neu".</p>` + ` · Produkt: ${clean(product)} – im Board unter „Neu".</p>`
+ (musterNachricht ? `<p><strong>Nachricht:</strong><br>${musterNachricht.replace(/\n/g, '<br>')}</p>` : '')); + (musterNachricht ? `<p><strong>Nachricht:</strong><br>${musterNachricht.replace(/\n/g, '<br>')}</p>` : ''));
return res.json({ ok: true }); return res.json({ ok: true });