Buchungskalender: Portal-Bereich und Anfragen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d7b5b98187
commit
3ecea05b52
11 files changed
+811
-4
No files matched your search
@@ -0,0 +1,166 @@
|
||||
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 <b>Muster</b>', 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 <b>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)
|
||||
}))
|
||||
@@ -127,7 +127,7 @@ test('Portal: app.js und dashboard.html kennen den Reiter buchungen', () => {
|
||||
const html = lies('public/dashboard.html')
|
||||
assert.match(html, /<section class="kb-view" id="view-buchungen">[\s\S]*?<div id="buchungen-body">[\s\S]*?<\/section>/)
|
||||
assert.equal([...html.matchAll(/id="buchungen-body"/g)].length, 1)
|
||||
assert.match(lies('public/buchungen.js'), /export function initBuchungen\(el\)/)
|
||||
assert.match(lies('public/buchungen.js'), /export function initBuchungen\(root, \{ projectId \} = \{\}\)/)
|
||||
})
|
||||
|
||||
test('Server: index.js hängt Mounts und Jobs ein, die Router-Dateien existieren', () => {
|
||||
|
||||
Reference in new issue
Block a user