diff --git a/public/admin.js b/public/admin.js index 7484238..cf568bb 100644 --- a/public/admin.js +++ b/public/admin.js @@ -788,6 +788,9 @@ const OFFER_STATUS = { const offerState = { filter: 'open', customers: null, labelToId: new Map(), context: null, offers: new Map() } +// Katalogname wie im Formular ("Hosting-Abo"/"Wartungs-Abo") +const OFFER_PRODUCT_LABEL = { hosting: 'Hosting-Abo', wartung: 'Wartungs-Abo' } + function offerStatusPill(o) { const [cls, label] = OFFER_STATUS[o.status] || ['pill-muted', o.status] const sub = o.status === 'aktiv' && o.appliedAt ? `seit ${fmtDate(o.appliedAt)}` : o.status === 'offen' ? 'wartet auf Abschluss' : '' @@ -873,7 +876,7 @@ async function loadOffers() { ${fmtDateTime(o.createdAt)}
${escapeHtml(o.ref)}${o.createdBy ? ` · ${escapeHtml(o.createdBy.replace(/\s*<.*>$/, ''))}` : ''} ${escapeHtml(o.customerName)}${o.customerEmail ? `
${escapeHtml(o.customerEmail).replace('@', '@')}` : ''}${o.customerCode ? `
Kd.-Nr. ${escapeHtml(o.customerCode)}` : ''} - ${escapeHtml(o.text)}
Ersparnis ${euroFromCents(o.savingsCents)}${o.note ? ` · „${escapeHtml(o.note)}“` : ''} + ${OFFER_PRODUCT_LABEL[o.productKey] || o.productKey}
${escapeHtml(o.text)}
Ersparnis ${euroFromCents(o.savingsCents)}${o.note ? ` · „${escapeHtml(o.note)}“` : ''} ${discount ? `
${escapeHtml(discount)}` : ''} ${o.internalNote ? `
intern: ${escapeHtml(o.internalNote)}` : ''} ${o.scope === 'laufend' ? 'laufendes Abo' : fmtDate(o.validUntil)} @@ -937,6 +940,7 @@ async function ensureOfferCustomers() { function offerFormValues(form) { return { customerId: offerState.labelToId.get(form.querySelector('#offer-customer').value.trim()) || '', + productKey: form.querySelector('#offer-product').value, monthlyPriceCents: Math.round(Number(form.querySelector('#offer-price').value) * 100), months: Number(form.querySelector('#offer-months').value), validUntil: form.querySelector('#offer-valid').value, @@ -956,12 +960,14 @@ function renderOfferPreview(form) { box.textContent = v.customerId ? 'Lade Kunde…' : 'Bitte einen Kunden aus der Liste wählen.' return } - const regular = ctx.regularPriceCents - const running = Boolean(ctx.subscription?.active) + const regular = ctx.products[v.productKey].regularPriceCents + // Sofort anwendbar ist ein Angebot nur, wenn das laufende Abo dasselbe Produkt betrifft (F32) + const running = Boolean(ctx.subscription?.active && ctx.subscription.productKey === v.productKey) + const openOffer = ctx.openOffers[v.productKey] const lines = [] let ok = true - if (ctx.openOffer) { - lines.push(`Es gibt schon ein offenes Angebot (${ctx.openOffer.ref}: ${ctx.openOffer.text}) – bitte zuerst zurückziehen.`) + if (openOffer) { + lines.push(`Es gibt schon ein offenes ${OFFER_PRODUCT_LABEL[v.productKey]}-Angebot (${openOffer.ref}: ${openOffer.text}) – bitte zuerst zurückziehen.`) ok = false } if (!Number.isInteger(v.monthlyPriceCents) || v.monthlyPriceCents < 0 || v.monthlyPriceCents >= regular) { @@ -1016,8 +1022,8 @@ async function saveOffer(form) { errEl.classList.add('hidden') const v = offerFormValues(form) const ctx = offerState.context - const running = Boolean(ctx.subscription?.active) - const text = offerPreviewText(v.monthlyPriceCents, v.months, ctx.regularPriceCents, running) + const running = Boolean(ctx.subscription?.active && ctx.subscription.productKey === v.productKey) + const text = offerPreviewText(v.monthlyPriceCents, v.months, ctx.products[v.productKey].regularPriceCents, running) if (!window.confirm(`Angebot für ${ctx.customer.name || 'den Kunden'} anlegen?\n\n${text}${running ? '' : `\nGültig bis ${fmtYmd(v.validUntil)}`}\n\nDer Coupon wird sofort in Stripe angelegt${running ? ' und auf das laufende Abo angewendet' : ''}.`)) return const save = form.querySelector('#offer-save') save.disabled = true @@ -1065,6 +1071,11 @@ async function openOfferForm(customerId = '') { ${customers.map((c) => ``).join('')} +
+
@@ -1090,6 +1101,7 @@ async function openOfferForm(customerId = '') { if (offerState.labelToId.has(form.querySelector('#offer-customer').value.trim())) loadOfferContext(form) }) form.querySelectorAll('#offer-price, #offer-months, #offer-valid').forEach((el) => el.addEventListener('input', () => renderOfferPreview(form))) + form.querySelector('#offer-product').addEventListener('change', () => renderOfferPreview(form)) form.querySelector('#offer-save').addEventListener('click', () => saveOffer(form)) form.querySelector('#offer-cancel').addEventListener('click', () => form.classList.add('hidden')) if (preselected) loadOfferContext(form) diff --git a/server/mail-templates/K05 Persoenliches Angebot.html b/server/mail-templates/K05 Persoenliches Angebot.html index 718dce9..261dcf8 100644 --- a/server/mail-templates/K05 Persoenliches Angebot.html +++ b/server/mail-templates/K05 Persoenliches Angebot.html @@ -4,16 +4,16 @@ -Ihr persönliches Angebot für das WEBklar Wartungs-Abo +Ihr persönliches Angebot für das WEBklar {{produkt}}