Buchungskalender: Abgleich – T22/K34 passen zu den Vorlagen, Config-Werte geprüft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5.5 committed 2026-10-07 14:55:32 +00:00
1 parent fbd07b3d8c
commit c3eef4747d
2 files changed
+32 -12

No files matched your search

+10 -7
View File
@@ -12,14 +12,14 @@ import { ladeEinstellungen, speichereEinstellungen } from './speicher.js'
* portalFeatures-Dokuments): anfrage (JSON), anfrageStatus ('' | offen | angenommen | abgelehnt | zurueckgezogen), anfrageAt,
* anfrageEntschiedenAt, anfrageEntschiedenVon, anfrageBegruendung. Sperre je Projekt: uk-anfrage:<projectId>.
* Fehler an den Kunden tragen status (4xx) und einen deutschen Text (Du-Form), optional err.body.code und err.fehler (je Feld).
* Mails: T22 ans Team (Anfrage eingegangen), K34 an den Kunden (Entscheidung); die Vorlagen kommen aus Task 20.
* Mails: T22 ans Team (Anfrage eingegangen), K34 an den Kunden (Entscheidung).
*/
export const ANFRAGE_OFFEN_TEXT = 'Deine Anfrage für den Buchungskalender ist noch nicht freigegeben.'
export const ARTEN = ['ferienwohnung', 'monteurzimmer', 'pension', 'sonstiges']
export const ART_NAMEN = { ferienwohnung: 'Ferienwohnung oder Ferienhaus', monteurzimmer: 'Monteurwohnung', pension: 'Pension oder Gästezimmer', sonstiges: 'Sonstiges' }
/** Das Portal sendet und liest 'monteur' (ui-erwartungen.md); gespeichert wird 'monteurzimmer'. */
/** Das Portal sendet 'monteurzimmer'; 'monteur' (aeltere Fassung, ui-erwartungen.md) wird weiter als Alias angenommen und gelesen. */
const ART_ALIAS = { monteur: 'monteurzimmer' }
export const PORTALE = ['booking', 'airbnb']
export const PORTAL_NAMEN = { booking: 'Booking.com', airbnb: 'Airbnb', andere: 'Portale (ohne Angabe)' }
@@ -43,7 +43,7 @@ const MELDUNG = {
const stop = (status, text, code) => Object.assign(new Error(text), { status, ...(code ? { body: { code } } : {}) })
const istObjekt = (wert) => wert !== null && typeof wert === 'object' && !Array.isArray(wert)
/** Versand (nur fuer Tests austauschbar: die Vorlagen K34 und T22 liegen auf einem anderen Strang). */
/** Versand (nur fuer Tests austauschbar). */
export const _versand = {
admin: (nachricht) => sendAdminNotice(nachricht),
kunde: (an, nachricht) => sendCustomerMail(an, nachricht),
@@ -193,10 +193,13 @@ async function meldeTeam({ customerId, projectId, project, anfrage, person }) {
kunde_name: kundeName || 'Kunde',
email: person?.email || '',
projekt: project?.projectName || projectId,
art: ART_NAMEN[anfrage.art],
einheiten: String(anfrage.einheiten),
portale: anfrage.portale.length ? anfrage.portale.map((p) => PORTAL_NAMEN[p]).join(', ') : 'keine',
nachricht: anfrage.nachricht || '–',
// die Vorlage T22 kennt nur kunde_name, email, projekt, nachricht, woms_url: die Angaben stehen zusammen in der Nachricht
nachricht: [
`Art: ${ART_NAMEN[anfrage.art]}`,
`Einheiten: ${anfrage.einheiten}`,
`Portale: ${anfrage.portale.length ? anfrage.portale.map((p) => PORTAL_NAMEN[p]).join(', ') : 'keine'}`,
...(anfrage.nachricht ? ['', anfrage.nachricht] : []),
].join('\n'),
woms_url: `${config.womsBaseUrl}/customers/${encodeURIComponent(customerId)}?tab=funktionen`,
},
})
+22 -5
View File
@@ -17,6 +17,7 @@ const { config } = await import('../server/config.js')
const { startTestApp, send, rawGet, post: rawPost } = await import('./support/testApp.js')
const { customerCookie, kundenansichtCookie, seedFixtures, customers } = await import('./support/fixtures.js')
const { deleteDocument } = await import('../server/services/appwriteAdmin.js')
const { renderMail } = await import('../server/services/mailTemplates.js')
const af = await import('../server/services/unterkunft/anfrageFunktion.js')
const { freischaltbar } = await import('../server/services/unterkunft/freischaltung.js')
const { unterkunftTicketDetails } = await import('../server/services/unterkunft/ticketDetails.js')
@@ -43,15 +44,17 @@ const status = (opts = { cookie }) => send('GET', `/api/unterkunft/status?projec
const entscheide = (b, opts = staff) => rawPost(`/api/team/unterkunft/c1/anfrage`, { projectId: P, ...b }, opts)
const einst = () => aw.docs('ukEinstellungen').get(FEATURE) ?? null
// Mails: Vorlagen K34/T22 liegen auf einem anderen Strang, der Test prueft nur den Aufruf
const aufrufe = { admin: [], kunde: [], adminFehler: false, kundeFehler: false }
// Mails: die Aufrufe werden festgehalten und gegen die echten Vorlagen K34/T22 gerendert (problems muss leer bleiben)
const aufrufe = { admin: [], kunde: [], probleme: [], adminFehler: false, kundeFehler: false }
af._versand.admin = async (m) => {
aufrufe.admin.push(m)
aufrufe.probleme.push(...renderMail(m.template, m.values, m.blocks ?? {}).problems.map((x) => `${m.template}: ${x}`))
if (aufrufe.adminFehler) throw new Error('SMTP')
return { sent: true }
}
af._versand.kunde = async (an, m) => {
aufrufe.kunde.push({ an, ...m })
aufrufe.probleme.push(...renderMail(m.template, m.values, m.blocks ?? {}).problems.map((x) => `${m.template}: ${x}`))
if (aufrufe.kundeFehler) throw new Error('SMTP')
}
@@ -60,6 +63,7 @@ beforeEach(async () => {
Object.assign(config.unterkunft, vorher, { mode: 'pilot', pilotEmails: [customers.c1.email] })
resetUnterkunftRoutesForTests()
aufrufe.admin.length = 0
aufrufe.probleme.length = 0
aufrufe.kunde.length = 0
aufrufe.adminFehler = aufrufe.kundeFehler = false
aw.seed('websiteProjects', { $id: P, customerId: 'c1', projectName: 'Donau', archived: false })
@@ -101,9 +105,7 @@ test('Anfrage stellen: gespeichert, T22 ans Team, Status-Block fuer state off',
assert.equal(aufrufe.admin.length, 1)
assert.equal(aufrufe.admin[0].template, 'T22')
assert.equal(aufrufe.admin[0].values.projekt, 'Donau')
assert.equal(aufrufe.admin[0].values.einheiten, '3')
assert.equal(aufrufe.admin[0].values.art, 'Monteurwohnung')
assert.equal(aufrufe.admin[0].values.portale, 'Booking.com, Airbnb')
assert.match(aufrufe.admin[0].values.nachricht, /^Art: Monteurwohnung\nEinheiten: 3\nPortale: Booking\.com, Airbnb/)
assert.equal(aufrufe.admin[0].values.woms_url, `${config.womsBaseUrl}/customers/c1?tab=funktionen`)
assert.equal(aufrufe.kunde.length, 0)
const s = (await status()).body.anfrage
@@ -307,3 +309,18 @@ test('ticketDetails: siteKey, Snippet, Schritte, Verweis, Anfrage; wirft nie', a
const viaConfig = await config.features.unterkunft.ticket.details({ doc: doc(), project: {}, customer: null, feature: { key: 'Buchungskalender' } })
assert.ok(viaConfig.includes('abcdefghijklmnop'))
})
test('T22 und K34 (angenommen und abgelehnt) passen zu den Vorlagen: keine Platzhalter-Probleme', async () => {
assert.equal((await stelle({ nachricht: 'Hallo\nZeile 2' })).status, 200)
assert.equal((await entscheide({ entscheidung: 'ablehnen', begruendung: 'Passt nicht.' })).status, 200)
assert.equal((await stelle()).status, 200)
assert.equal((await entscheide({ entscheidung: 'annehmen' })).status, 200)
assert.deepEqual(aufrufe.admin.map((m) => m.template), ['T22', 'T22'])
assert.deepEqual(aufrufe.kunde.map((m) => m.template), ['K34', 'K34'])
assert.deepEqual(aufrufe.probleme, [])
const t22 = renderMail('T22', aufrufe.admin[0].values, {})
assert.match(t22.text, /Art: Ferienwohnung oder Ferienhaus/)
assert.match(t22.text, /Hallo/)
const k34 = renderMail('K34', aufrufe.kunde[1].values, aufrufe.kunde[1].blocks)
assert.match(k34.text, /199 €/)
})