Telefonnummer bei Buchung übertragen und in Bestätigungsmail ergänzt
parent
9bfa8b78b0
commit
6f2b239e57
|
|
@ -68,7 +68,9 @@ Der vollständige Vertrag steht in `src/server/swagger.json`.
|
|||
|
||||
POST-Anfragen benötigen `Content-Type: application/json`. Beim Buchen ist
|
||||
zusätzlich ein `Idempotency-Key` als UUID v4 erforderlich. Das Formular sendet
|
||||
`packageId`, gegebenenfalls `variantId`, `name` und `email`.
|
||||
`packageId`, gegebenenfalls `variantId`, `name`, `email` und optional `phone`.
|
||||
Die Telefonnummer darf höchstens 50 Zeichen und keine Steuerzeichen enthalten.
|
||||
Sie wird in der CSV gespeichert und in der Bestätigungsmail ausgegeben.
|
||||
|
||||
Alternativ akzeptiert die API das Swagger-Objekt `customer` mit `firstName`,
|
||||
`lastName`, `email` sowie optional `phone`, `ageGroup` und `notes`. Dabei muss
|
||||
|
|
@ -93,6 +95,7 @@ erhalten bleiben; ältere UUID-Buchungsnummern werden weiterhin unterstützt.
|
|||
|
||||
`bookedPackage` speichert den Bestellstand als JSON, `customer` die optionalen
|
||||
strukturierten Kundendaten und `acceptedTerms` die zugehörige Zustimmung.
|
||||
Die optionale Telefonnummer steht zusätzlich in der Spalte `phone`.
|
||||
CSV-Dateien mit dem früheren Spaltensatz bleiben lesbar und werden beim nächsten
|
||||
Speichern erweitert. Für alte Buchungen ohne Bestellstand liefert die API
|
||||
`bookedPackage: null`.
|
||||
|
|
|
|||
|
|
@ -102,8 +102,7 @@ export function mountBookingPage(
|
|||
</div>
|
||||
<div class="booking__field">
|
||||
<label for="${instanceId}-booking-phone">Telefonnummer (optional)</label>
|
||||
<input id="${instanceId}-booking-phone" name="phone" type="tel" autocomplete="tel" maxlength="50" aria-describedby="${instanceId}-booking-phone-hint ${instanceId}-booking-phone-error" />
|
||||
<p id="${instanceId}-booking-phone-hint">Die Telefonnummer wird derzeit noch nicht an die Buchungsstelle übermittelt.</p>
|
||||
<input id="${instanceId}-booking-phone" name="phone" type="tel" autocomplete="tel" maxlength="50" aria-describedby="${instanceId}-booking-phone-error" />
|
||||
<p class="booking__field-error" id="${instanceId}-booking-phone-error"></p>
|
||||
</div>
|
||||
<div class="booking__actions">
|
||||
|
|
@ -252,7 +251,7 @@ export function mountBookingPage(
|
|||
if (stored.order) renderOrder(stored.order);
|
||||
name.value = stored.payload.name;
|
||||
email.value = stored.payload.email;
|
||||
phone.value = stored.phone || "";
|
||||
phone.value = stored.payload.phone || "";
|
||||
lockFields(true);
|
||||
button.textContent = "Status prüfen / erneut versuchen";
|
||||
if (stored.result) showResult(stored.result, false);
|
||||
|
|
@ -301,8 +300,6 @@ export function mountBookingPage(
|
|||
const nextAttempt = {
|
||||
key: crypto.randomUUID(),
|
||||
order: selected,
|
||||
// Bis zur Backend-Erweiterung nur für die Wiederherstellung.
|
||||
phone: phone.value,
|
||||
payload: {
|
||||
packageId: selected.packageId,
|
||||
...(selected.variantId
|
||||
|
|
@ -310,6 +307,7 @@ export function mountBookingPage(
|
|||
: {}),
|
||||
name: name.value,
|
||||
email: email.value,
|
||||
...(phone.value ? { phone: phone.value } : {}),
|
||||
},
|
||||
};
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -112,6 +112,18 @@ function validateInput(input, key) {
|
|||
) {
|
||||
fields.email = "Bitte eine gültige E-Mail-Adresse eingeben.";
|
||||
}
|
||||
if (!customer && input.phone !== undefined) {
|
||||
if (
|
||||
typeof input.phone !== "string" ||
|
||||
input.phone.length > 50 ||
|
||||
/[\x00-\x1f\x7f]/.test(input.phone)
|
||||
) {
|
||||
fields.phone =
|
||||
"Bitte höchstens 50 Zeichen ohne Zeilenumbrüche eingeben.";
|
||||
} else if (input.phone.trim()) {
|
||||
result.phone = input.phone.trim();
|
||||
}
|
||||
}
|
||||
if (Object.keys(fields).length) {
|
||||
throw new BookingError(
|
||||
400,
|
||||
|
|
@ -305,6 +317,7 @@ export function createBookingService({
|
|||
acceptedTerms: data.acceptedTerms ?? null,
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
phone: data.customer?.phone ?? data.phone ?? "",
|
||||
emailStatus: "pending",
|
||||
idempotencyKey: key,
|
||||
requestHash,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ const legacyColumns = [
|
|||
];
|
||||
|
||||
const orderColumns = [...legacyColumns, "bookedPackage"];
|
||||
export const columns = [...orderColumns, "customer", "acceptedTerms"];
|
||||
const customerColumns = [...orderColumns, "customer", "acceptedTerms"];
|
||||
export const columns = [...customerColumns, "phone"];
|
||||
const jsonColumns = ["bookedPackage", "customer", "acceptedTerms"];
|
||||
|
||||
// Apostroph-Präfix schützt Tabellenprogramme. Ein vorhandenes Apostroph wird
|
||||
|
|
@ -47,6 +48,7 @@ export function createCsvStorage(filePath) {
|
|||
columns(header) {
|
||||
if (
|
||||
header.join(",") !== columns.join(",") &&
|
||||
header.join(",") !== customerColumns.join(",") &&
|
||||
header.join(",") !== legacyColumns.join(",") &&
|
||||
header.join(",") !== orderColumns.join(",")
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export function createEmailService(
|
|||
const from = env.SMTP_FROM || "Tri-Hub <buchung@example.test>";
|
||||
return async function sendConfirmation(booking) {
|
||||
const order = booking.bookedPackage;
|
||||
const phone = booking.phone || booking.customer?.phone;
|
||||
const lines = [
|
||||
`Hallo ${booking.name},`,
|
||||
"",
|
||||
|
|
@ -48,9 +49,7 @@ export function createEmailService(
|
|||
: []),
|
||||
`Kunde: ${booking.name}`,
|
||||
`E-Mail: ${booking.email}`,
|
||||
...(booking.customer?.phone
|
||||
? [`Telefon: ${booking.customer.phone}`]
|
||||
: []),
|
||||
...(phone ? [`Telefon: ${phone}`] : []),
|
||||
...(booking.customer?.notes
|
||||
? [`Deine Anmerkungen: ${booking.customer.notes}`]
|
||||
: []),
|
||||
|
|
|
|||
|
|
@ -550,6 +550,12 @@
|
|||
"maxLength": 254,
|
||||
"example": "thomas.mueller@example.de"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"maxLength": 50,
|
||||
"description": "Optionale Telefonnummer bei Übergabe von name und email, ohne Steuerzeichen. Bei customer gilt customer.phone.",
|
||||
"example": "+49 170 1234567"
|
||||
},
|
||||
"customer": {
|
||||
"type": "object",
|
||||
"required": ["firstName", "lastName", "email"],
|
||||
|
|
@ -595,7 +601,7 @@
|
|||
"required": ["name", "email"]
|
||||
}
|
||||
],
|
||||
"description": "Kundendaten gemäß customer mit acceptedTerms=true. Bestehende Clients können alternativ name und email übergeben."
|
||||
"description": "Kundendaten gemäß customer mit acceptedTerms=true. Bestehende Clients können alternativ name und email sowie optional phone übergeben."
|
||||
},
|
||||
"BookingConfirmation": {
|
||||
"type": "object",
|
||||
|
|
|
|||
Loading…
Reference in New Issue