Buchungskalender: Zustimmung per WhatsApp zusammenführen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
a45f436124
5 files changed
+31
-7
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { Router } from 'express'
|
||||
import { config } from '../../config.js'
|
||||
import { aboMemo } from '../../services/aboStatus.js'
|
||||
import { validateTextform } from '../../services/analyse.js'
|
||||
import { FUNKTION_CHANNELS, validateTextform } from '../../services/analyse.js'
|
||||
import { getDocument, updateDocument } from '../../services/appwriteAdmin.js'
|
||||
import { featureConfig, featureHaken } from '../../services/featureRegistry.js'
|
||||
import { withLocks } from '../../services/keyedLock.js'
|
||||
@@ -139,7 +139,7 @@ async function textformFuerAktivierung({ body, customerId, key, doc }) {
|
||||
if (!textformVerlangt[key](kunde?.email)) return null
|
||||
// nur Texte: validateTextform macht aus jedem Wert einen Text (aus ['chat'] wuerde 'chat')
|
||||
const text = (wert) => (typeof wert === 'string' ? wert : '')
|
||||
return validateTextform({ date: text(body?.textformDate), channel: text(body?.textformChannel) }, { minDate: textformFassung(key) })
|
||||
return validateTextform({ date: text(body?.textformDate), channel: text(body?.textformChannel) }, { minDate: textformFassung(key), channels: FUNKTION_CHANNELS })
|
||||
}
|
||||
|
||||
/** Leistungsbeschreibung und AVV der Funktion als PDF fuer die K16 der Textform-Aktivierung; nicht lesbar => 503, bevor etwas geaendert wird. */
|
||||
|
||||
@@ -19,6 +19,8 @@ const SITE_KEY_RE = /^[a-z0-9]{16,40}$/
|
||||
const COLOR_RE = /^#[0-9a-f]{6}$/
|
||||
const DEFAULT_COLOR = '#111111'
|
||||
const CHANNELS = new Set(['chat', 'email'])
|
||||
/** Wege der Zustimmung bei Funktionen (Terminplaner, Webshop, Buchungskalender): zusaetzlich WhatsApp (eine Nachricht dort ist Textform). */
|
||||
export const FUNKTION_CHANNELS = new Set(['chat', 'email', 'whatsapp'])
|
||||
export const MISSING_PRIVACY_MESSAGE =
|
||||
'Für die Zusatzseite fehlen in Deinen Kundendaten Firma oder Name und eine gültige E-Mail-Adresse. Schreib uns im Chat, wir ergänzen sie.'
|
||||
|
||||
@@ -109,9 +111,9 @@ const berlinToday = (now) => new Intl.DateTimeFormat('sv-SE', { timeZone: 'Europ
|
||||
|
||||
/**
|
||||
* Zustimmung des Kunden in Textform (Mitarbeiter-Weg): Datum JJJJ-MM-TT (echt, nicht nach heute in Berlin,
|
||||
* nicht vor der Fassung) und Weg chat/email. Wirft 400, sonst { date, channel }.
|
||||
* nicht vor der Fassung) und Weg chat/email (channels: erlaubte Wege, Funktionen auch whatsapp). Wirft 400, sonst { date, channel }.
|
||||
*/
|
||||
export function validateTextform(textform, { today = berlinToday(new Date()), minDate = config.analyse.avvVersion } = {}) {
|
||||
export function validateTextform(textform, { today = berlinToday(new Date()), minDate = config.analyse.avvVersion, channels = CHANNELS } = {}) {
|
||||
const date = String(textform?.date ?? '')
|
||||
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date)
|
||||
const real = m && new Date(Date.UTC(+m[1], +m[2] - 1, +m[3])).toISOString().slice(0, 10) === date
|
||||
@@ -119,7 +121,7 @@ export function validateTextform(textform, { today = berlinToday(new Date()), mi
|
||||
if (date > today) throw httpError(400, 'Das Datum der Zustimmung liegt in der Zukunft')
|
||||
if (date < minDate) throw httpError(400, `Das Datum der Zustimmung liegt vor der Fassung ${minDate}`)
|
||||
const channel = String(textform?.channel ?? '')
|
||||
if (!CHANNELS.has(channel)) throw httpError(400, 'Weg der Zustimmung fehlt: Chat oder E-Mail')
|
||||
if (!channels.has(channel)) throw httpError(400, channels.has('whatsapp') ? 'Weg der Zustimmung fehlt: Chat, E-Mail oder WhatsApp' : 'Weg der Zustimmung fehlt: Chat oder E-Mail')
|
||||
return { date, channel }
|
||||
}
|
||||
|
||||
|
||||
@@ -614,7 +614,7 @@ async function assertAboLaeuftFuerAktivierung(customerId) {
|
||||
if (!laeuftJetzt) throw httpError(409, KEIN_ABO_AKTIVIERUNG)
|
||||
}
|
||||
|
||||
const TEXTFORM_WEG = { chat: 'Chat im Kundenportal', email: 'E-Mail' }
|
||||
const TEXTFORM_WEG = { chat: 'Chat im Kundenportal', email: 'E-Mail', whatsapp: 'WhatsApp' }
|
||||
|
||||
/** Funktionen, die das Team mit Zustimmung des Kunden in Textform aktivieren kann (Modus on), und die Fassung der Unterlagen, auf die sie sich bezieht. */
|
||||
export const TEXTFORM_KEYS = ['termine', 'shop', 'unterkunft']
|
||||
@@ -622,7 +622,7 @@ export const textformFassung = (key) => (key === 'shop' ? config.shop.fassung :
|
||||
|
||||
/**
|
||||
* Text des Nachweises, wenn der Kunde dem Terminplaner oder dem Webshop in Textform zugestimmt hat (Aktivierung durch das Team im
|
||||
* Modus on, F13a). textform = { date: 'JJJJ-MM-TT', channel: 'chat' | 'email' } (validateTextform); fassung = textformFassung(key).
|
||||
* Modus on, F13a). textform = { date: 'JJJJ-MM-TT', channel: 'chat' | 'email' | 'whatsapp' } (validateTextform); fassung = textformFassung(key).
|
||||
*/
|
||||
export function textformNachweisText({ date, channel }, fassung, key = 'termine') {
|
||||
const [jahr, monat, tag] = String(date).split('-')
|
||||
|
||||
@@ -326,6 +326,10 @@ test('validateTextform: Datum, Zukunft, Fassung, Weg', () => {
|
||||
bad(undefined)
|
||||
assert.deepEqual(an.validateTextform({ date: '2026-10-02', channel: 'email' }, ctx), { date: '2026-10-02', channel: 'email' })
|
||||
assert.deepEqual(an.validateTextform({ date: '2026-10-05', channel: 'chat' }, ctx), { date: '2026-10-05', channel: 'chat' })
|
||||
// WhatsApp nur, wenn der Aufrufer es erlaubt (Funktionen), nicht bei der Analyse
|
||||
bad({ date: '2026-10-02', channel: 'whatsapp' })
|
||||
assert.deepEqual(an.validateTextform({ date: '2026-10-02', channel: 'whatsapp' }, { ...ctx, channels: an.FUNKTION_CHANNELS }), { date: '2026-10-02', channel: 'whatsapp' })
|
||||
assert.throws(() => an.validateTextform({ date: '2026-10-02', channel: 'telefon' }, { ...ctx, channels: an.FUNKTION_CHANNELS }), { status: 400 })
|
||||
})
|
||||
|
||||
test('Einschalten ohne Firma/Name oder mit ungültiger E-Mail: 409, nichts passiert', async () => {
|
||||
|
||||
@@ -129,6 +129,24 @@ test('unterkunft textform: ohne Angaben 400 ohne Nachweis, mit Angaben 200; Nach
|
||||
for (const a of k16[0].attachments) assert.equal(Buffer.from(a.content, 'base64').subarray(0, 4).toString(), '%PDF')
|
||||
})
|
||||
|
||||
test('unterkunft textform: Weg WhatsApp wird angenommen und im Nachweis genannt; unbekannter Weg 400', async (t) => {
|
||||
t.after(() => ukMit(MODUS.mode, MODUS.pilotEmails))
|
||||
ukMit('pilot', ['whatsapp@example.invalid'])
|
||||
const k = legeKunde({ email: 'whatsapp@example.invalid' })
|
||||
const v = config.unterkunft.fassung
|
||||
const telefon = await aktivieren(k, { textformDate: berlinTag(), textformChannel: 'telefon' })
|
||||
assert.equal(telefon.status, 400)
|
||||
assert.match(telefon.body.error, /Chat, E-Mail oder WhatsApp/)
|
||||
assert.equal(consents(k.projectId).length, 0)
|
||||
const tag = berlinTag()
|
||||
const ok = await aktivieren(k, { textformDate: tag, textformChannel: 'whatsapp' })
|
||||
assert.equal(ok.status, 200, JSON.stringify(ok.body))
|
||||
assert.equal(
|
||||
leseZustimmung(consents(k.projectId)[0]).texte[0].text,
|
||||
`Der Kunde hat am ${ttmmjjjj(tag)} per WhatsApp in Textform erklärt, dass er der Leistungsbeschreibung Buchungskalender und dem Auftragsverarbeitungsvertrag Buchungskalender (Fassung ${v}) zustimmt.`
|
||||
)
|
||||
})
|
||||
|
||||
test('unterkunft textform entfällt: Modus off, schon aktiv, Abschalten; ungültige Datumsangaben 400', async (t) => {
|
||||
t.after(() => ukMit(MODUS.mode, MODUS.pilotEmails))
|
||||
ukMit('off')
|
||||
|
||||
Reference in new issue
Block a user