Shop Review B I-1: Eingangsmail G15 bei SEPA, G09 nimmt die Bestellung ausdruecklich an

G15 "Wir haben Ihre Bestellung erhalten" geht einmal raus, sobald Stripe die Sitzung
abgeschlossen, die Zahlung aber noch nicht gemeldet hat (meldeEingang, Marker mailsEingangAt,
aus dem Webhook und aus pruefeOffene). G09 sagt ausdruecklich, dass der Kaufvertrag mit der
Mail geschlossen ist, trennt Bestell- und Zahlungsdatum und nennt die Telefonnummer des
Anbieters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 09:44:48 +00:00
1 parent 1b7aa9ebe3
commit 77fb8c8f44
11 files changed
+238 -27

No files matched your search

@@ -7,12 +7,12 @@
Betreff: Ihre Bestellung {{bestellnummer}} bei {{betrieb_name}}
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse aus der Bestellung bzw. der Widerrufserklärung) · ohne BCC
Platzhalter: {{bestellnummer}}, {{datum}}, {{positionen}}, {{zwischensumme}}, {{versand}}, {{gesamt}}, {{steuer_hinweis}}, {{lieferart_text}}, {{lieferadresse}}, {{abholort}}, {{lieferzeit}}, {{zahlungsart}}, {{ausschluss_text}}, {{agb}}, {{widerrufsbelehrung}}, {{widerrufsformular}}, {{gewaehrleistung}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Platzhalter: {{bestellnummer}}, {{datum}}, {{zahlungsdatum}}, {{positionen}}, {{zwischensumme}}, {{versand}}, {{gesamt}}, {{steuer_hinweis}}, {{lieferart_text}}, {{lieferadresse}}, {{abholort}}, {{lieferzeit}}, {{zahlungsart}}, {{ausschluss_text}}, {{agb}}, {{widerrufsbelehrung}}, {{widerrufsformular}}, {{gewaehrleistung}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Optionale Blöcke (vom Server gesetzt, versand und abholung schließen sich aus): versand, abholung, ausschluss
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
positionen = eine Zeile je Position „Menge × Name (Variante) – Summe“; lieferadresse mehrzeilig; agb, widerrufsbelehrung, widerrufsformular und gewaehrleistung sind die vollen Texte (Zeilenumbrüche werden zu <br>).
Block versand: Lieferadresse. Block abholung: Abholort. Block ausschluss: Hinweis auf Artikel ohne Widerrufsrecht (ausschluss_text).
Ist die Mail zu lang für die Warteschlange, ersetzt der Server den AGB-Text durch einen Link auf die AGB im Shop.
datum = Bestelldatum (Zeitpunkt der Bestellung), zahlungsdatum = Tag des Zahlungseingangs. Mit dieser Mail nimmt der Händler die Bestellung an (Vertragsschluss). Ist die Mail zu lang für die Warteschlange, wird sie nicht versendet (der Marker an der Bestellung bleibt offen und der Versand wird wiederholt), sie wird nie durch einen Link ersetzt.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
@@ -33,10 +33,11 @@ a{color:#111111;}
<tr><td class="card" style="background:#ffffff;border-radius:0 0 16px 16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px ink" style="padding:36px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:26px;line-height:32px;mso-line-height-rule:exactly;font-weight:bold;letter-spacing:-0.5px;color:#111111;">Vielen Dank für Ihre Bestellung</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag,<br>wir haben Ihre Bestellung bei {{betrieb_name}} erhalten und Ihre Zahlung ist eingegangen. Bitte bewahren Sie diese E-Mail auf: Sie enthält alle Angaben zu Ihrem Vertrag.</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag,<br>wir haben Ihre Bestellung bei {{betrieb_name}} erhalten und Ihre Zahlung ist eingegangen. Mit dieser E-Mail nehmen wir Ihre Bestellung an. Der Kaufvertrag ist damit geschlossen. Bitte bewahren Sie diese E-Mail auf: Sie enthält alle Angaben zu Ihrem Vertrag.</td></tr>
<tr><td class="px" style="padding:24px 40px 0 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;mso-line-height-rule:exactly;">
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</strong></td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Bestelldatum</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{datum}}</td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Zahlungsdatum</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{zahlungsdatum}}</td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Anbieter</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{anbieter_zeile}}</td></tr>
</table></td></tr>
<tr><td class="px ink" style="padding:28px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Ihre Artikel</td></tr>
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<!--
G15 · Eingang der Bestellung (Webshop, SEPA-Lastschrift: Zahlung noch in Bearbeitung, noch keine Vertragsbestätigung)
Betreff: Wir haben Ihre Bestellung {{bestellnummer}} erhalten
Absender: „<Shopname>“ <Portal-Postfach> · Antworten an die E-Mail-Adresse des Händlers
Empfänger: Käufer (Adresse der Stripe-Sitzung) · ohne BCC
Platzhalter: {{bestellnummer}}, {{datum}}, {{positionen}}, {{gesamt}}, {{farbe}}, {{betrieb_name}}, {{anbieter_zeile}}, {{impressum_url}}, {{datenschutz_url}}, {{widerruf_url}}
Sie-Form, keine Anrede-Blöcke. Footer: Anbieter-Zeile mit Name, Anschrift und E-Mail, Impressum, Datenschutz, Link zur Widerrufsfunktion.
Wird einmal gesendet, sobald Stripe die Sitzung abgeschlossen meldet, die Zahlung aber noch aussteht (Lastschrift). Der Kaufvertrag kommt erst mit der Bestellbestätigung (G09) nach Zahlungseingang zustande.
positionen = eine Zeile je Position „Menge × Name (Variante) – Summe“.
-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<title>Wir haben Ihre Bestellung {{bestellnummer}} erhalten</title>
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif!important;}</style><![endif]-->
<style>
a{color:#111111;}
@media (max-width:620px){.w{width:100%!important;}.px{padding-left:24px!important;padding-right:24px!important;}.lbl{display:block!important;width:100%!important;padding-bottom:2px!important;border-bottom:0!important;}.val{display:block!important;width:100%!important;padding-top:0!important;}}
@media (prefers-color-scheme:dark){.bg{background:#161615!important;}.card{background:#1f1f1e!important;}.ink{color:#f2f1ee!important;}.mut{color:#a8a6a0!important;}.box{background:#2a2a28!important;border-color:#3a3a37!important;}.btn{background:#f2f1ee!important;}.btn a{color:#161615!important;}.btn2{border-color:#f2f1ee!important;}.btn2 a{color:#f2f1ee!important;}.rowline{border-color:#3a3a37!important;}.pill{background:#1f1f1e!important;}.pill span{color:#f2f1ee!important;}}
</style>
</head>
<body style="margin:0;padding:0;background:#f1f0ec;">
<table role="presentation" class="bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f1f0ec;">
<tr><td align="center" style="padding:32px 12px;">
<table role="presentation" class="w" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;">
<tr><td class="px" bgcolor="{{farbe}}" style="background:{{farbe}};border-radius:16px 16px 0 0;padding:18px 40px;"><table role="presentation" cellpadding="0" cellspacing="0" border="0"><tr><td class="pill" bgcolor="#ffffff" style="background:#ffffff;border-radius:8px;padding:8px 14px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:22px;font-weight:bold;color:#111111;"><span style="color:#111111;">{{betrieb_name}}</span></td></tr></table></td></tr>
<tr><td class="card" style="background:#ffffff;border-radius:0 0 16px 16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr><td class="px ink" style="padding:36px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:26px;line-height:32px;mso-line-height-rule:exactly;font-weight:bold;letter-spacing:-0.5px;color:#111111;">Wir haben Ihre Bestellung erhalten</td></tr>
<tr><td class="px mut" style="padding:14px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:25px;mso-line-height-rule:exactly;color:#4a4843;">Guten Tag,<br>vielen Dank für Ihre Bestellung bei {{betrieb_name}}. Ihre Zahlung per Lastschrift ist noch in Bearbeitung. Sobald sie bei uns eingegangen ist, erhalten Sie die Bestellbestätigung; erst damit kommt der Kaufvertrag zustande.</td></tr>
<tr><td class="px" style="padding:24px 40px 0 40px;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;mso-line-height-rule:exactly;">
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Bestellnummer</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{bestellnummer}}</strong></td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Bestelldatum</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;">{{datum}}</td></tr>
<tr><td class="lbl mut rowline" width="150" valign="top" style="width:150px;padding:11px 12px 11px 0;border-bottom:1px solid #ecebe6;color:#6b6964;">Gesamt</td><td class="val ink rowline" valign="top" style="padding:11px 0;border-bottom:1px solid #ecebe6;color:#111111;"><strong>{{gesamt}}</strong></td></tr>
</table></td></tr>
<tr><td class="px ink" style="padding:28px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:17px;line-height:24px;font-weight:bold;color:#111111;">Ihre Artikel</td></tr>
<tr><td class="px" style="padding:20px 40px 0 40px;"><table role="presentation" class="box" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f7f6f3;border:1px solid #e8e6e0;border-radius:12px;"><tr><td class="ink" style="padding:16px 20px;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:21px;color:#111111;">{{positionen}}</td></tr></table></td></tr>
<tr><td class="px mut" style="padding:26px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:13px;line-height:20px;mso-line-height-rule:exactly;color:#6b6964;">Bitte bewahren Sie diese E-Mail auf. Sie können Ihren Vertrag jederzeit über die Schaltfläche „Vertrag widerrufen“ am Ende dieser E-Mail widerrufen, sobald er zustande gekommen ist und solange Ihnen ein Widerrufsrecht zusteht.</td></tr>
<tr><td class="px ink" style="padding:28px 40px 40px 40px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:24px;color:#111111;">Viele Grüße<br><strong>{{betrieb_name}}</strong></td></tr>
</table>
</td></tr>
<tr><td class="px mut" align="center" style="padding:24px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><div><strong>{{betrieb_name}}</strong></div><div>{{anbieter_zeile}}</div></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;"><a class="mut" href="{{impressum_url}}" style="color:#4a4843;text-decoration:underline;">Impressum</a> · <a class="mut" href="{{datenschutz_url}}" style="color:#4a4843;text-decoration:underline;">Datenschutz</a> · <a class="mut" href="{{widerruf_url}}" style="color:#4a4843;text-decoration:underline;">Vertrag widerrufen</a></td></tr>
<tr><td class="px mut" align="center" style="padding:12px 40px 0 40px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:19px;color:#6b6964;">Diese E-Mail wurde im Auftrag von {{betrieb_name}} über den Webshop von WEBklar versendet. Antworten gehen direkt an {{betrieb_name}}.</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
+2 -1
View File
@@ -3,7 +3,7 @@ import { config } from '../config.js'
import { getDocument, listDocuments, Query, updateDocument } from '../services/appwriteAdmin.js'
import { withLocks } from '../services/keyedLock.js'
import { connectEreignis, statusAusKonto, statusMitHinweis } from '../services/shop/connect.js'
import { schliesseAb, verwirf } from '../services/shop/bestellungen.js'
import { meldeEingang, schliesseAb, verwirf } from '../services/shop/bestellungen.js'
import { bucheStripeErstattung } from '../services/shop/erstattungen.js'
import { ladeShop, leereCache } from '../services/shop/kontext.js'
@@ -56,6 +56,7 @@ async function verarbeite(event, now) {
switch (event.type) {
case 'checkout.session.completed':
if (objekt.payment_status === 'paid') await schliesseAb({ accountId, session: objekt, now })
else await meldeEingang({ accountId, session: objekt, now })
break
case 'checkout.session.async_payment_succeeded':
await schliesseAb({ accountId, session: objekt, now })
+45 -1
View File
@@ -604,6 +604,46 @@ export async function schliesseAb({ accountId, session, now = new Date() }) {
})
}
/**
* Eingang der Bestellung (G15): Stripe meldet die Sitzung abgeschlossen, die Zahlung steht aber noch aus (SEPA-Lastschrift,
* payment_status unpaid / no_payment_required). Die Bestellung bleibt offen, der Kaeufer bekommt einmal eine Eingangsmail ohne
* Vertragsschluss (der Vertrag kommt mit G09 nach Zahlungseingang zustande). Marker `mailsEingangAt` im Bestell-JSON, Versuche in
* mailsVersuche.eingang (hoechstens MAIL_VERSUCHE_MAX). Aufrufer: Webhook (checkout.session.completed) und pruefeOffene.
* Nie ein Wurf wegen der Mail; falsches Konto, fremde Sitzung oder eine nicht mehr offene Bestellung bleiben wirkungslos.
* @param {{ accountId: string, session: object, now?: Date }} p
* @returns {Promise<{ gesendet: boolean }>}
*/
export async function meldeEingang({ accountId, session, now = new Date() }) {
const orderId = session?.metadata?.orderId
if (!istObjekt(session) || session.metadata?.wkShop !== '1' || typeof orderId !== 'string' || !orderId) return { gesendet: false }
if (session.payment_status !== 'unpaid' && session.payment_status !== 'no_payment_required') return { gesendet: false }
const an = kurz(session.customer_details?.email, 320)
if (!an) return { gesendet: false }
const jetzt = new Date(now)
return withLocks([`shop-order:${orderId}`], async () => {
const order = await bestellungLesen(orderId)
if (!order || order.status !== 'offen') return { gesendet: false }
const shop = await shopDokument(order.shopId)
if (!shop || typeof accountId !== 'string' || shop.stripeAccountId !== accountId) return { gesendet: false }
const daten = order.daten
const versuche = istObjekt(daten.mailsVersuche) ? daten.mailsVersuche : {}
if (daten.mailsEingangAt || (versuche.eingang ?? 0) >= MAIL_VERSUCHE_MAX) return { gesendet: false }
let ok = false
try {
leereCache(shop.siteKey)
const ctx = await shopPerSiteKey(shop.siteKey, { now: jetzt })
if (!ctx) throw new Error('Shop-Kontext nicht gefunden')
ok = (await anKaeufer('G15', ctx, order, { an }, jetzt)).ok
} catch (err) {
console.warn(`[shop] G15 zur Bestellung ${orderId.slice(0, 36)} nicht eingereiht: ${err?.name}: ${String(err?.message).slice(0, 120)}`)
}
const neu = ok ? { ...daten, mailsEingangAt: jetzt.toISOString() } : { ...daten, mailsVersuche: { ...versuche, eingang: (versuche.eingang ?? 0) + 1 } }
await updateDocument(col(), orderId, { bestellung: JSON.stringify(neu) })
return { gesendet: ok }
})
}
/**
* Verwirft eine offene Bestellung (Status storniert, Bestand wird frei). Nur aus offen; sonst keine Wirkung.
* @param {string} orderId
@@ -692,7 +732,11 @@ export async function pruefeOffene(now = new Date()) {
if (session.payment_status === 'paid') {
const erg = await schliesseAb({ accountId: shop.stripeAccountId, session, now })
if (erg.neu) zaehler.bezahlt += 1
} else zaehler.wartend += 1 // SEPA unterwegs: async_payment_succeeded/failed entscheidet
} else {
// SEPA unterwegs: async_payment_succeeded/failed entscheidet; bis dahin hat der Kaeufer die Eingangsmail (G15) zu bekommen
zaehler.wartend += 1
await meldeEingang({ accountId: shop.stripeAccountId, session, now })
}
continue
}
if (session?.status === 'open') {
+12 -7
View File
@@ -13,7 +13,7 @@ import { gewaehrleistungsHinweis } from './rechtstexte.js'
* Mails des Webshops (Spezifikation 2026-10-05-webshop-design.md, Abschnitte 5.3, 5.4 und 8; Plan Task E4).
*
* Kaeufer (G09 Bestellbestaetigung, G10 versendet, G11 abholbereit, G12 Eingang Widerruf, G13 Erstattung, G14 Zahlung
* fehlgeschlagen) laufen durch die Mail-Warteschlange des Terminplaners (termine/outbox.js einreihen): Absendername = Shopname,
* fehlgeschlagen, G15 Eingang der Bestellung) laufen durch die Mail-Warteschlange des Terminplaners (termine/outbox.js einreihen): Absendername = Shopname,
* Absenderadresse bleibt SMTP_FROM, Antworten an die E-Mail des Haendlers, nie BCC. featureId = ID des portalFeatures-Dokuments des
* Shops, bookingId = Bestell-ID. Der Betreff steht in der Vorlage (G-Vorlagen des Shops haben keinen {{betreff}}-Wert). Die
* Empfaenger-Kappe der Terminplaner-Mails (limits.darfEmpfaenger) gilt hier nicht: G09 ist Vertragspflicht.
@@ -36,7 +36,7 @@ import { gewaehrleistungsHinweis } from './rechtstexte.js'
*/
const LEER = '–'
const GAST_ARTEN = Object.freeze(['G09', 'G10', 'G11', 'G12', 'G13', 'G14'])
const GAST_ARTEN = Object.freeze(['G09', 'G10', 'G11', 'G12', 'G13', 'G14', 'G15'])
/** Grenzen des Outbox-Schemas (html 100000, text 50000) mit etwas Luft. */
const MAX_HTML = 95000
const MAX_TEXT = 47000
@@ -87,7 +87,7 @@ function anbieterZeile(a) {
const ort = [text(a.plz), text(a.ort)].filter(Boolean).join(' ')
const land = text(a.land) && a.land !== 'DE' ? (LANDNAMEN[a.land] ?? a.land) : ''
const anschrift = [text(a.strasse), ort, land].filter(Boolean).join(', ')
return [text(a.name), anschrift, text(a.email)].filter(Boolean).join(', ') || LEER
return [text(a.name), anschrift, text(a.email), text(a.telefon) ? `Tel. ${einzeilig(a.telefon)}` : ''].filter(Boolean).join(', ') || LEER
}
const siteKeyVon = (ctx) => ctx?.shop?.siteKey || ctx?.doc?.siteKey || ''
@@ -143,9 +143,13 @@ function eingangText(eingang) {
return `${datumKurz(d)}, ${uhrzeit(d)} Uhr`
}
const datumText = (order, now) => {
const ms = zeitMs(order?.bezahltAt || order?.erstelltAt)
return datumKurz(Number.isFinite(ms) ? new Date(ms) : now)
/** Datum des ersten brauchbaren Zeitpunkts (Berliner Tag), sonst des Ersatzdatums. */
const datumVon = (now, ...zeitpunkte) => {
for (const wert of zeitpunkte) {
const ms = zeitMs(wert)
if (Number.isFinite(ms)) return datumKurz(new Date(ms))
}
return datumKurz(now)
}
// ---------------------------------------------------------------- Werte
@@ -184,7 +188,8 @@ export function kaeuferWerte(ctx, order, now = new Date()) {
impressum_url: text(rechtstexte.impressumUrl) || shop,
datenschutz_url: text(rechtstexte.datenschutzUrl) || shop,
bestellnummer: oderLeer(order?.bestellnummer),
datum: datumText(order, now),
datum: datumVon(now, order?.erstelltAt),
zahlungsdatum: datumVon(now, order?.bezahltAt, order?.erstelltAt),
positionen: positionenText(snap.positionen),
zwischensumme: text(texte.zwischensumme) || cent(summen.zwischensummeCent),
versand: text(texte.versand) || (lieferart === 'abholung' ? 'Abholung – kostenlos' : cent(summen.versandCent)),
+5 -5
View File
@@ -39,7 +39,7 @@ import { pruefeNow, pruefeText, zeitMs } from './util.js'
* pause Anmeldepause laeuft
* budget Plaetze im Fenster >= mailsProStunde
* reserve nachrangige Mails (G05, K20, K21) im Fenster >= max(1, floor(mailsProStunde x (1 - mailsReserve))): so bleibt
* ein Teil der Stunde den Vorrang-Mails (G01, G02, G08, Webshop G09, G12) und den normalen Mails vorbehalten
* ein Teil der Stunde den Vorrang-Mails (G01, G02, G08, Webshop G09, G12, G15) und den normalen Mails vorbehalten
* kontingent Mails dieser Funktion im Fenster >= mailsProFunktion: eine Funktion kann die Stunde nicht allein fuellen
*
* Minutenjob (verarbeite): liest nie alle wartenden Eintraege. Bei Pause oder leerem Gesamtbudget endet er ohne Abfrage.
@@ -73,10 +73,10 @@ const iso = (wertMs) => new Date(wertMs).toISOString()
/**
* Mails, die vor allen anderen rausgehen duerfen (Bestaetigung, Termin, Eingangsbestaetigung des Widerrufs). Dazu die beiden
* Pflichtmails des Webshops: G09 Bestellbestaetigung (dauerhafter Datentraeger) und G12 Eingangsbestaetigung des Widerrufs. Die
* Pflichtmails des Webshops: G09 Bestellbestaetigung (dauerhafter Datentraeger), G12 Eingangsbestaetigung des Widerrufs und G15 Eingang der Bestellung (SEPA, Zahlung noch offen). Die
* uebrigen Shop-Mails G10, G11, G13, G14 sind 'normal'.
*/
export const MAIL_VORRANG = Object.freeze(['G01', 'G02', 'G08', 'G09', 'G12'])
export const MAIL_VORRANG = Object.freeze(['G01', 'G02', 'G08', 'G09', 'G12', 'G15'])
/** Mails, die zuerst warten (Erinnerung, Hinweise an den Betrieb): sie nehmen nie die ganze Stunde. */
export const MAIL_NACHRANGIG = Object.freeze(['G05', 'K20', 'K21'])
@@ -119,7 +119,7 @@ function gesamtBremse(nowMs) {
* 'pause' | 'budget' | 'reserve' | 'kontingent'. Nur lesen: belegt keinen Platz.
* @param {number} nowMs
* @param {string} featureId
* @param {string} art Vorlagen-ID der Mail ('G01' ... 'K21', Shop: 'G09' ... 'G14')
* @param {string} art Vorlagen-ID der Mail ('G01' ... 'K21', Shop: 'G09' ... 'G15')
* @returns {null|'pause'|'budget'|'reserve'|'kontingent'}
*/
export function pruefePlatz(nowMs, featureId, art) {
@@ -442,7 +442,7 @@ async function verarbeiteKlasse(klasse, nowMs, now, lauf) {
}
/**
* Minutenjob: sendet faellige wartende Eintraege, Vorrang-Mails (G01, G02, G08, G09, G12) vor normalen vor nachrangigen (G05, K20, K21),
* Minutenjob: sendet faellige wartende Eintraege, Vorrang-Mails (G01, G02, G08, G09, G12, G15) vor normalen vor nachrangigen (G05, K20, K21),
* in jeder Klasse die faelligsten zuerst, bis Pause oder Stundenbudget bremsen. Er liest begrenzte Listen statt aller Eintraege
* (siehe Kopfkommentar); Eintraege, die Vorrang-Reserve oder Kontingent ihrer Funktion bremsen, bleiben `wartet`, und die uebrigen
* kommen trotzdem dran. Fehler bei einzelnen Eintraegen (z. B. Appwrite) stehen im Log und stoppen den Lauf nicht.