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 = '') {
+
+
@@ -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}}