import { test } from 'node:test' import assert from 'node:assert/strict' import { installFakeDom } from './support/fakeDom.js' import { h, request } from '../public/shop-dom.js' import { REITER } from '../public/buchungen.js' import { render, zeitraumText, naechteText } from '../public/buchungen-tab-anfragen.js' import { summeLive } from '../public/buchungen-posten.js' // Task 12: Gerüst und Reiter „Anfragen“ gegen eine Anfrage-Attrappe nach docs/funktionen/unterkunft-api.md. const POSTEN = [ { text: 'Übernachtung', menge: 7, einzelCent: 5500, ustProzent: 7 }, { text: 'Reinigung', menge: 1, einzelCent: 6000, ustProzent: 7 }, { text: 'Direktbuchungsrabatt 10 %', menge: 1, einzelCent: -3850, ustProzent: 7 }, ] const zeile = (nr, extra = {}) => ({ id: `a${nr}`, nummer: `A-${nr}`, von: '2026-11-12', bis: '2026-11-30', gast: { name: `Gast ${nr}` }, status: 'neu', summen: { bruttoCent: 40650 }, ...extra }) const anfrage = (extra = {}) => ({ id: 'a1', nummer: 'A-1', von: '2026-11-12', bis: '2026-11-19', personen: 2, status: 'neu', steuerart: 'regelbesteuert', unterkunftName: 'Haus am See', gast: { name: 'Anna Muster', email: 'anna@example.com', telefon: '0151', nachricht: 'Hund dabei' }, ...extra, }) const vorschlag = { naechte: 7, posten: POSTEN, summen: { bruttoCent: 40650 } } const mit = (route, fn) => async () => { const fx = installFakeDom({ antworten: Array.from({ length: 40 }, () => route) }) try { await fn(fx) } finally { fx.restore() } } function baueCtx() { const ctx = { projectId: 'p1', status: { state: 'active' }, nurLesen: false, meldungen: [], aktuell: () => true, request, zeigeMeldung: (t, a) => ctx.meldungen.push({ text: t, art: a }), neuLaden: async () => ctx.status } return ctx } async function zeige(fx) { const ctx = baueCtx() const panel = h('div') fx.document.body.append(panel) await render(panel, ctx) await fx.ruhe() return { panel, ctx } } const server = (o = {}) => (a) => { const key = `${a.method} ${a.url.split('?')[0]}` if (o[key]) return o[key](a) if (a.method === 'GET' && a.url.startsWith('/api/unterkunft/anfragen?')) return { body: { anfragen: [zeile(1), zeile(2, { status: 'gebucht' })] } } if (a.method === 'GET' && a.url.startsWith('/api/unterkunft/anfragen/')) return { body: { anfrage: anfrage(), vorschlag } } return { body: {} } } const posts = (fx) => fx.fetchLog.filter((a) => a.method !== 'GET') const aktion = (fx, el, n) => fx.finde(el, `[data-aktion="${n}"]`) const oeffne = async (fx, panel, id = 'a1') => { await fx.klick(fx.finde(panel, `[data-anfrage="${id}"]`)); await fx.ruhe() } test('REITER: fünf Reiter mit den Labels', () => { assert.deepEqual(REITER.map((r) => r.key), ['anfragen', 'kalender', 'unterkuenfte', 'rechnungen', 'einstellungen']) assert.deepEqual(REITER.map((r) => r.label), ['Anfragen', 'Kalender', 'Unterkünfte & Preise', 'Rechnungen', 'Einstellungen']) }) test('summeLive spiegelt summiere: 40650 / 37991 / 2659', () => { const s = summeLive(POSTEN, 'regelbesteuert') assert.equal(s.bruttoCent, 40650) assert.equal(s.nettoCent, 37991) assert.equal(s.ust[0].steuerCent, 2659) const k = summeLive(POSTEN, 'kleinunternehmer') assert.deepEqual(k, { bruttoCent: 40650, nettoCent: 40650, ust: [] }) }) test('Zeitraum und Nächte', () => { assert.equal(zeitraumText('2026-11-12', '2026-11-30'), '12.–30.11.2026') assert.equal(zeitraumText('2026-10-28', '2026-11-03'), '28.10.–03.11.2026') assert.equal(zeitraumText('2026-12-28', '2027-01-03'), '28.12.2026–03.01.2027') assert.equal(naechteText('2026-11-12', '2026-11-30'), '18 Nächte') assert.equal(naechteText('2026-11-12', '2026-11-13'), '1 Nacht') }) test('Liste: Nummer, Zeitraum, Gast, Status-Pille, Betrag; Filter offen als Standard', mit(server(), async (fx) => { const { panel } = await zeige(fx) assert.equal(fx.fetchLog[0].url, '/api/unterkunft/anfragen?projectId=p1&filter=offen') const z = fx.alle(panel, '[data-anfrage]') assert.equal(z.length, 2) const t = fx.text(z[0]) for (const re of [/A-1/, /12\.–30\.11\.2026 · 18 Nächte/, /Gast 1/, /Neu/, /406,50\s€/]) assert.match(t, re) assert.match(fx.text(z[1]), /Gebucht/) await fx.setze(fx.finde(panel, '#bk-a-filter'), 'alle') await fx.ruhe() assert.equal(fx.fetchLog.at(-1).url, '/api/unterkunft/anfragen?projectId=p1&filter=alle') })) test('Detail: Vorschlag editierbar, Summe aktualisiert sich, Freigeben sendet posten/fristTage/nachricht und zeigt den Link', mit(server({ 'POST /api/unterkunft/anfragen/a1/freigeben': () => ({ body: { buchungsLink: 'https://x.test/buchen/a1?t=abc' } }), }), async (fx) => { const { panel, ctx } = await zeige(fx) await oeffne(fx, panel) assert.match(fx.text(panel), /Anna Muster<\/b>/) assert.equal(fx.finde(panel, 'b'), null, 'Fremdtext bleibt Text') assert.match(fx.text(fx.finde(panel, '[data-summen]')), /Gesamt 406,50\s€/) const zeilen = fx.alle(panel, '[data-zeile]') assert.equal(zeilen.length, 3) const einzel = fx.finde(zeilen[1], '[data-posten="einzel"]') await fx.setze(einzel, '70,00') assert.match(fx.text(fx.finde(panel, '[data-summen]')), /Gesamt 416,50\s€/) await fx.setze(einzel, '60,00') await fx.setze(fx.finde(panel, '#bk-a-frist'), '5') await fx.setze(fx.finde(panel, '#bk-a-nachricht'), 'Gern!') await fx.klick(aktion(fx, panel, 'freigeben')) await fx.ruhe() const [p] = posts(fx) assert.equal(p.url, '/api/unterkunft/anfragen/a1/freigeben?projectId=p1') assert.deepEqual(p.body, { posten: POSTEN, fristTage: 5, nachricht: 'Gern!' }) assert.equal(fx.finde(panel, '[data-link]').getAttribute('value'), 'https://x.test/buchen/a1?t=abc') assert.ok(aktion(fx, panel, 'link-kopieren')) assert.equal(ctx.meldungen.at(-1).art, 'ok') })) test('Freigeben: 409 zeigt „Der Zeitraum ist inzwischen belegt.“', mit(server({ 'POST /api/unterkunft/anfragen/a1/freigeben': () => ({ status: 409, body: { fehler: 'zeitraum_belegt' } }), }), async (fx) => { const { panel } = await zeige(fx) await oeffne(fx, panel) await fx.klick(aktion(fx, panel, 'freigeben')) await fx.ruhe() assert.match(fx.text(panel), /Der Zeitraum ist inzwischen belegt\./) assert.equal(aktion(fx, panel, 'freigeben').disabled, false) })) test('Ablehnen sendet den Text', mit(server({ 'POST /api/unterkunft/anfragen/a1/ablehnen': () => ({ body: {} }) }), async (fx) => { const { panel } = await zeige(fx) await oeffne(fx, panel) await fx.klick(aktion(fx, panel, 'ablehnen')) await fx.setze(fx.finde(panel, '#bk-a-ablehn'), 'Leider belegt') await fx.klick(aktion(fx, panel, 'ablehnen-senden')) await fx.ruhe() assert.deepEqual(posts(fx)[0].body, { text: 'Leider belegt' }) })) const gebucht = (extra = {}) => ({ anfrage: anfrage({ status: 'gebucht', posten: POSTEN, summen: { bruttoCent: 40650 }, rechnungId: 'r1', rechnungNummer: 'R-2026-001', ...extra }) }) test('Gebucht: Rechnung-Link, „Als bezahlt (vor Ort)“, Storno ohne Erstattungsfeld bei Vor-Ort-Zahlung', mit(server({ 'GET /api/unterkunft/anfragen/a1': () => ({ body: gebucht({ zahlart: 'vorort' }) }), 'POST /api/unterkunft/anfragen/a1/bezahlt': () => ({ body: {} }), 'POST /api/unterkunft/anfragen/a1/stornieren': () => ({ body: {} }), }), async (fx) => { const { panel } = await zeige(fx) await oeffne(fx, panel) assert.equal(aktion(fx, panel, 'rechnung').getAttribute('href'), '/api/unterkunft/rechnungen/r1.pdf?projectId=p1') await fx.klick(aktion(fx, panel, 'bezahlt')) await fx.ruhe() assert.equal(posts(fx)[0].url, '/api/unterkunft/anfragen/a1/bezahlt?projectId=p1') await fx.klick(aktion(fx, panel, 'stornieren')) assert.equal(fx.finde(panel, '#bk-a-erstattung'), null) await fx.setze(fx.finde(panel, '#bk-a-grund'), 'Gast krank') await fx.klick(aktion(fx, panel, 'storno-senden')) await fx.ruhe() assert.deepEqual(posts(fx).at(-1).body, { grund: 'Gast krank', erstattungCent: 0 }) })) test('Storno bei Online-Zahlung: Erstattungsbetrag wird gesendet', mit(server({ 'GET /api/unterkunft/anfragen/a1': () => ({ body: gebucht({ status: 'bezahlt', zahlart: 'online', bezahltVia: 'online' }) }), 'POST /api/unterkunft/anfragen/a1/stornieren': () => ({ body: {} }), }), async (fx) => { const { panel } = await zeige(fx) await oeffne(fx, panel) assert.equal(aktion(fx, panel, 'bezahlt'), null) await fx.klick(aktion(fx, panel, 'stornieren')) assert.equal(fx.finde(panel, '#bk-a-erstattung').value, '406,50') await fx.setze(fx.finde(panel, '#bk-a-erstattung'), '200,00') await fx.klick(aktion(fx, panel, 'storno-senden')) await fx.ruhe() assert.equal(posts(fx)[0].body.erstattungCent, 20000) }))