Buchungskalender: Preise je Personenzahl (Portal-Editor)
Neuer Haken "Preis hängt von der Personenzahl ab" im Preis-Editor (nur bei Höchstzahl ab 2 Personen). Angeschaltet zeigt jede Staffel k Euro-Felder (1 Person bis k Personen, k = min(Höchstzahl, 10)) statt eines Preises; "Personen im Preis inklusive" und "Aufpreis je weiterer Person" sind dann ausgeblendet, ihre Werte gehen unverändert mit. Speichern sendet personenCent und preisProNachtCent = personenCent[0]. Beim Laden ist der Haken an, wenn personenCent gespeichert sind. Kundenansicht (nur lesen) sperrt Haken und Felder. Felder brechen auf dem Handy um. Tests im Fake-DOM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a4ade5f649
commit
1e0eb9f569
3 files changed
+274
-10
No files matched your search
@@ -868,3 +868,206 @@ test('Preis-Editor: Platzhalter-Feldfehler verschwindet beim Tippen', mit(ukServ
|
||||
await fx.setze(storno, 'Bis zu 14 Tage vor Anreise kostenfrei stornierbar, danach 100 %.')
|
||||
assert.equal(fehlerText(fx, panel, 'stornobedingungen'), '')
|
||||
}))
|
||||
|
||||
// ---- Preis-Editor: Preise je Personenzahl (staffeln[].personenCent), Haken #bk-p-personenmodus
|
||||
const PERSONEN_PREISE = {
|
||||
...PREISE,
|
||||
staffeln: [
|
||||
{ abNaechten: 1, preisProNachtCent: 5500, personenCent: [5500, 7000, 8200] },
|
||||
{ abNaechten: 7, preisProNachtCent: 5000, personenCent: [5000, 6500, 7700] },
|
||||
],
|
||||
}
|
||||
const PUT_PREISE = { 'PUT /api/unterkunft/unterkuenfte/u1/preise': () => ({ body: {} }) }
|
||||
/** Server mit anderen Preisen und/oder anderer Gaestezahl der Unterkunft. */
|
||||
const personenServer = (preise, maxPersonen = 3, o = {}) => ukServer({
|
||||
'GET /api/unterkunft/unterkuenfte': () => ({ body: { unterkuenfte: [{ ...UK, maxPersonen }] } }),
|
||||
'GET /api/unterkunft/unterkuenfte/u1/preise': () => ({ body: { preise } }),
|
||||
...PUT_PREISE,
|
||||
...o,
|
||||
})
|
||||
const zeilen = (fx, panel) => fx.alle(panel, '[data-staffel-zeile]')
|
||||
const spalten = (fx, zeile) => fx.alle(zeile, '[data-staffel="person"]')
|
||||
const putPreise = (fx) => posts(fx).filter((a) => a.url.includes('/preise')).at(-1)
|
||||
const modusBox = (fx, panel) => fx.finde(panel, '#bk-p-personenmodus')
|
||||
const sichtbar = (el) => { for (let e = el; e && e.hidden !== undefined; e = e.parentElement) if (e.hidden) return false; return true }
|
||||
|
||||
test('Preis-Editor Personen-Modus: gespeicherte personenCent laden (Haken an, k Felder je Staffel), Speichern sendet sie unverändert', mit(personenServer(PERSONEN_PREISE), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
assert.equal(modusBox(fx, panel).checked, true)
|
||||
assert.equal(zeilen(fx, panel).length, 2)
|
||||
for (const [i, erwartet] of [['5500', '7000', '8200'], ['5000', '6500', '7700']].entries()) {
|
||||
const felder = spalten(fx, zeilen(fx, panel)[i])
|
||||
assert.equal(felder.length, 3)
|
||||
assert.deepEqual(felder.map((f) => f.value), erwartet.map((c) => `${c.slice(0, -2)},${c.slice(-2)}`))
|
||||
assert.deepEqual(felder.map((f) => f.dataset.personen), ['1', '2', '3'])
|
||||
assert.ok(felder.every(sichtbar))
|
||||
assert.equal(sichtbar(fx.finde(zeilen(fx, panel)[i], '[data-staffel="preis"]')), false, 'der eine Preis ist ausgeblendet')
|
||||
}
|
||||
const beschriftung = fx.text(zeilen(fx, panel)[0])
|
||||
assert.match(beschriftung, /1 Person(?!en)/)
|
||||
assert.match(beschriftung, /2 Personen/)
|
||||
assert.match(beschriftung, /3 Personen/)
|
||||
assert.equal(sichtbar(fx.finde(panel, '[data-hinweis="personenpreise"]')), true)
|
||||
assert.equal(fx.text(fx.finde(panel, '[data-hinweis="personenpreise"]')), 'Preis pro Nacht für die ganze Unterkunft, je nach Anzahl der Gäste.')
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-inklusive')), false)
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-aufpreis')), false)
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
const put = putPreise(fx)
|
||||
assert.deepEqual(put.body.staffeln, PERSONEN_PREISE.staffeln)
|
||||
assert.equal(put.body.personenInklusive, 2, 'ausgeblendete Felder gehen unverändert mit')
|
||||
assert.equal(put.body.aufpreisPersonNachtCent, 1000)
|
||||
assert.equal(put.body.reinigungCent, 6000)
|
||||
}))
|
||||
|
||||
test('Preis-Editor Personen-Modus: preisProNachtCent im Body ist personenCent[0]; Felder lassen sich ändern', mit(personenServer(PERSONEN_PREISE), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
await fx.setze(spalten(fx, zeilen(fx, panel)[1])[0], '48,50')
|
||||
await fx.setze(spalten(fx, zeilen(fx, panel)[1])[2], '75')
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
assert.deepEqual(putPreise(fx).body.staffeln[1], { abNaechten: 7, preisProNachtCent: 4850, personenCent: [4850, 6500, 7500] })
|
||||
}))
|
||||
|
||||
test('Preis-Editor Personen-Modus: Haken schaltet vom alten Modell um (k Felder aus maxPersonen, Hilfsfelder weg) und zurück', mit(personenServer(PREISE, 4), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
const box = modusBox(fx, panel)
|
||||
assert.equal(box.checked, false)
|
||||
assert.equal(sichtbar(box), true)
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-inklusive')), true)
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-aufpreis')), true)
|
||||
assert.equal(sichtbar(fx.finde(panel, '[data-hinweis="personenpreise"]')), false)
|
||||
assert.ok(zeilen(fx, panel).every((z) => sichtbar(fx.finde(z, '[data-staffel="preis"]')) && spalten(fx, z).every((f) => !sichtbar(f))))
|
||||
await fx.klick(box)
|
||||
assert.equal(box.checked, true)
|
||||
for (const z of zeilen(fx, panel)) {
|
||||
assert.equal(spalten(fx, z).length, 4, 'k = maxPersonen')
|
||||
assert.ok(spalten(fx, z).every((f) => sichtbar(f) && f.value === ''), 'fehlende Werte bleiben leer')
|
||||
assert.equal(sichtbar(fx.finde(z, '[data-staffel="preis"]')), false)
|
||||
}
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-inklusive')), false)
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-aufpreis')), false)
|
||||
assert.equal(sichtbar(fx.finde(panel, '[data-hinweis="personenpreise"]')), true)
|
||||
// neue Staffel hat die Felder ebenfalls
|
||||
await fx.klick(aktion(fx, panel, 'staffel-dazu'))
|
||||
assert.equal(spalten(fx, zeilen(fx, panel)[2]).length, 4)
|
||||
assert.ok(spalten(fx, zeilen(fx, panel)[2]).every(sichtbar))
|
||||
await fx.klick(fx.finde(zeilen(fx, panel)[2], '[data-aktion="staffel-weg"]'))
|
||||
// zurück: alter Modus wie bisher, der eine Preis blieb erhalten
|
||||
await fx.klick(box)
|
||||
assert.equal(box.checked, false)
|
||||
assert.equal(fx.finde(zeilen(fx, panel)[0], '[data-staffel="preis"]').value, '55,00')
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-inklusive')), true)
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
assert.deepEqual(putPreise(fx).body.staffeln, PREISE.staffeln)
|
||||
assert.ok(putPreise(fx).body.staffeln.every((s) => !('personenCent' in s)))
|
||||
}))
|
||||
|
||||
test('Preis-Editor Personen-Modus: Umschalten, alle Felder füllen, Speichern', mit(personenServer(PREISE, 3), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
await fx.klick(modusBox(fx, panel))
|
||||
assert.equal(await versuche(fx, panel), 0, 'leere Felder werden nicht gesendet')
|
||||
assert.match(fehlerText(fx, panel, 'staffeln'), /für jede Personenzahl einen Preis/)
|
||||
const werte = [['55', '70', '82'], ['50', '65', '77']]
|
||||
for (const [i, z] of zeilen(fx, panel).entries()) for (const [j, f] of spalten(fx, z).entries()) await fx.setze(f, werte[i][j])
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
assert.deepEqual(putPreise(fx).body.staffeln, [
|
||||
{ abNaechten: 1, preisProNachtCent: 5500, personenCent: [5500, 7000, 8200] },
|
||||
{ abNaechten: 7, preisProNachtCent: 5000, personenCent: [5000, 6500, 7700] },
|
||||
])
|
||||
assert.equal(putPreise(fx).body.personenInklusive, 2)
|
||||
assert.equal(putPreise(fx).body.aufpreisPersonNachtCent, 1000)
|
||||
}))
|
||||
|
||||
test('Preis-Editor Personen-Modus: Client prüft Preisgrenzen und Staffeln wie der Server', mit(personenServer(PERSONEN_PREISE), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
const zeile = (i) => zeilen(fx, panel)[i]
|
||||
await fx.setze(spalten(fx, zeile(0))[1], '0,50')
|
||||
assert.equal(await versuche(fx, panel), 0)
|
||||
assert.match(fehlerText(fx, panel, 'staffeln'), /1,00 € und 1\.000,00 €/)
|
||||
await fx.setze(spalten(fx, zeile(0))[1], '1.000,01')
|
||||
assert.equal(await versuche(fx, panel), 0)
|
||||
await fx.setze(spalten(fx, zeile(0))[1], 'abc')
|
||||
assert.equal(await versuche(fx, panel), 0)
|
||||
assert.match(fehlerText(fx, panel, 'staffeln'), /für jede Personenzahl einen Preis/)
|
||||
await fx.setze(spalten(fx, zeile(0))[1], '70')
|
||||
await fx.setze(fx.finde(zeile(1), '[data-staffel="ab"]'), '1')
|
||||
assert.equal(await versuche(fx, panel), 0)
|
||||
assert.match(fehlerText(fx, panel, 'staffeln'), /größer werden/)
|
||||
await fx.setze(fx.finde(zeile(1), '[data-staffel="ab"]'), '7')
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
}))
|
||||
|
||||
test('Preis-Editor Personen-Modus: weniger gespeicherte Werte als Felder bleiben leer, überzählige werden verworfen', async () => {
|
||||
const gespeichert = { ...PERSONEN_PREISE, staffeln: [{ abNaechten: 1, preisProNachtCent: 5500, personenCent: [5500, 7000, 8200] }] }
|
||||
// Unterkunft jetzt für 4 Gäste: vierte Spalte leer
|
||||
await mit(personenServer(gespeichert, 4), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
assert.deepEqual(spalten(fx, zeilen(fx, panel)[0]).map((f) => f.value), ['55,00', '70,00', '82,00', ''])
|
||||
assert.equal(await versuche(fx, panel), 0)
|
||||
assert.match(fehlerText(fx, panel, 'staffeln'), /für jede Personenzahl einen Preis/)
|
||||
})()
|
||||
// Unterkunft jetzt für 2 Gäste: dritter Wert fällt weg
|
||||
await mit(personenServer(gespeichert, 2), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
assert.deepEqual(spalten(fx, zeilen(fx, panel)[0]).map((f) => f.value), ['55,00', '70,00'])
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
assert.deepEqual(putPreise(fx).body.staffeln, [{ abNaechten: 1, preisProNachtCent: 5500, personenCent: [5500, 7000] }])
|
||||
})()
|
||||
// mehr als 10 Gäste: höchstens 10 Felder
|
||||
await mit(personenServer(gespeichert, 15), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
assert.equal(spalten(fx, zeilen(fx, panel)[0]).length, 10)
|
||||
assert.match(fx.text(zeilen(fx, panel)[0]), /10 Personen/)
|
||||
})()
|
||||
})
|
||||
|
||||
test('Preis-Editor Personen-Modus: bei einer Person Höchstzahl kein Haken, altes Modell, personenCent fällt weg', mit(personenServer(PERSONEN_PREISE, 1), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
const box = modusBox(fx, panel)
|
||||
assert.equal(box.checked, false)
|
||||
assert.equal(sichtbar(box), false)
|
||||
assert.ok(zeilen(fx, panel).every((z) => spalten(fx, z).length === 0 && sichtbar(fx.finde(z, '[data-staffel="preis"]'))))
|
||||
assert.equal(sichtbar(fx.finde(panel, '#bk-p-inklusive')), true)
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
assert.deepEqual(putPreise(fx).body.staffeln, [{ abNaechten: 1, preisProNachtCent: 5500 }, { abNaechten: 7, preisProNachtCent: 5000 }])
|
||||
}))
|
||||
|
||||
test('Preis-Editor Personen-Modus: nur lesen sperrt Haken und alle Felder', async () => {
|
||||
await mit(personenServer(PERSONEN_PREISE), async (fx) => {
|
||||
const { panel } = await zeigeMit(fx, ukTab.render, { nurLesen: true })
|
||||
await fx.klick(aktion(fx, panel, 'bearbeiten'))
|
||||
await fx.ruhe()
|
||||
assert.ok(modusBox(fx, panel).disabled)
|
||||
assert.equal(spalten(fx, zeilen(fx, panel)[0]).length, 3)
|
||||
assert.ok(fx.alle(panel, '.bk-preisform input, .bk-preisform select, .bk-preisform textarea, .bk-preisform button').every((el) => el.disabled), 'alles gesperrt')
|
||||
assert.equal(aktion(fx, panel, 'preise-speichern'), null)
|
||||
// Klick auf den gesperrten Haken ändert nichts
|
||||
await fx.klick(modusBox(fx, panel))
|
||||
assert.equal(modusBox(fx, panel).checked, true)
|
||||
})()
|
||||
// auch im alten Modell sperrt nur lesen den Haken
|
||||
await mit(personenServer(PREISE, 3), async (fx) => {
|
||||
const { panel } = await zeigeMit(fx, ukTab.render, { nurLesen: true })
|
||||
await fx.klick(aktion(fx, panel, 'bearbeiten'))
|
||||
await fx.ruhe()
|
||||
assert.ok(modusBox(fx, panel).disabled)
|
||||
await fx.klick(modusBox(fx, panel))
|
||||
assert.equal(modusBox(fx, panel).checked, false)
|
||||
assert.ok(zeilen(fx, panel).every((z) => spalten(fx, z).every((f) => f.disabled)))
|
||||
})()
|
||||
})
|
||||
|
||||
test('Preis-Editor Personen-Modus: unlesbare Reste in den ausgeblendeten Feldern blockieren das Speichern nicht', mit(personenServer(PERSONEN_PREISE), async (fx) => {
|
||||
const panel = await preisEditor(fx)
|
||||
await fx.setze(fx.finde(panel, '#bk-p-inklusive'), 'xyz')
|
||||
await fx.setze(fx.finde(panel, '#bk-p-aufpreis'), 'abc')
|
||||
assert.equal(await versuche(fx, panel), 1)
|
||||
assert.equal(putPreise(fx).body.personenInklusive, 2)
|
||||
assert.equal(putPreise(fx).body.aufpreisPersonNachtCent, 1000)
|
||||
}))
|
||||
|
||||
test('CSS: Felder je Personenzahl brechen um, ausgeblendete Teile bleiben unsichtbar', () => {
|
||||
const css = readFileSync(new URL('../public/buchungen.css', import.meta.url), 'utf8')
|
||||
assert.match(css, /\.bk-personenpreise\s*\{[^}]*flex-wrap:\s*wrap/)
|
||||
assert.match(css, /\.bk-personenpreise\[hidden\][^{]*\{\s*display:\s*none/)
|
||||
assert.match(css, /\.bk-preis-einzeln\[hidden\]/)
|
||||
})
|
||||
Reference in new issue
Block a user