Widerrufsseite: Vertragsart Projektauftrag mit freiwilliger Nummer der Auftragsbestätigung
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
73c5446cf8
commit
60a51c0f68
4 files changed
+176
-2
No files matched your search
@@ -0,0 +1,56 @@
|
||||
/* Widerrufsseite: Vertragsart „Projektauftrag“ mit freiwilliger Nummer der Auftragsbestätigung (Feld abNummer).
|
||||
Rechtsfunktion: dieses Modul darf die anderen Vertragsarten nie stören. Fällt es aus, funktioniert der Widerruf wie bisher.
|
||||
Das Feld ist nur bei „Projektauftrag“ sichtbar; eine eingegebene, aber unpassende Nummer wird gemeldet und stoppt das Absenden,
|
||||
ein leeres Feld lässt den Widerruf durch (der Widerruf bleibt formfrei). Die Meldung steht in auftrag-ansicht.js. */
|
||||
import { abNummerNormalisiert, texte } from './auftrag-ansicht.js'
|
||||
|
||||
export function initProjektWiderruf(form) {
|
||||
const wrap = form?.querySelector('#ab-nummer-wrap')
|
||||
const feld = form?.querySelector('#abNummer')
|
||||
const fehler = form?.querySelector('#form-error')
|
||||
if (!form || !wrap || !feld || !fehler) return
|
||||
const gewaehlt = () => form.querySelector('[name="order"]:checked')?.value
|
||||
let eigeneMeldung = false
|
||||
|
||||
const meldungWeg = () => {
|
||||
if (!eigeneMeldung) return
|
||||
eigeneMeldung = false
|
||||
fehler.textContent = ''
|
||||
fehler.classList.add('hidden')
|
||||
}
|
||||
const zeige = () => {
|
||||
const projekt = gewaehlt() === 'projekt'
|
||||
wrap.classList.toggle('hidden', !projekt)
|
||||
if (!projekt) {
|
||||
feld.value = ''
|
||||
meldungWeg()
|
||||
}
|
||||
}
|
||||
|
||||
form.addEventListener('change', zeige)
|
||||
form.addEventListener('click', (ev) => {
|
||||
if (!ev.target?.closest?.('button[type="submit"], input[type="submit"]') || gewaehlt() !== 'projekt') return
|
||||
const eingabe = feld.value.trim()
|
||||
if (!eingabe) {
|
||||
feld.value = ''
|
||||
meldungWeg()
|
||||
return
|
||||
}
|
||||
const nummer = abNummerNormalisiert(eingabe)
|
||||
if (!nummer) {
|
||||
ev.preventDefault()
|
||||
fehler.textContent = texte().abNummerUngueltig
|
||||
fehler.classList.remove('hidden')
|
||||
eigeneMeldung = true
|
||||
feld.focus()
|
||||
return
|
||||
}
|
||||
feld.value = nummer
|
||||
meldungWeg()
|
||||
})
|
||||
zeige()
|
||||
}
|
||||
|
||||
if (typeof document !== 'undefined' && document.getElementById('decl-form') && document.getElementById('ab-nummer-wrap')) {
|
||||
initProjektWiderruf(document.getElementById('decl-form'))
|
||||
}
|
||||
@@ -24,8 +24,15 @@
|
||||
<label class="radio"><input type="radio" name="order" value="termine" data-text="Terminplaner (Einrichtung)"> Terminplaner (Einrichtung)</label>
|
||||
<label class="radio"><input type="radio" name="order" value="shop" data-text="Webshop (Einrichtung)"> Webshop (Einrichtung)</label>
|
||||
<label class="radio"><input type="radio" name="order" value="zeiten" data-text="Öffnungszeiten live"> Öffnungszeiten live</label>
|
||||
<label class="radio"><input type="radio" name="order" value="projekt" data-text="Projektauftrag (Auftragsbestätigung)"> Projektauftrag (Auftragsbestätigung)</label>
|
||||
</fieldset>
|
||||
|
||||
<!-- Nur bei „Projektauftrag“ und freiwillig (auftrag-widerruf.js blendet das Feld sonst aus und leert es); ohne das Skript bleibt es sichtbar -->
|
||||
<div id="ab-nummer-wrap">
|
||||
<label for="abNummer" data-label-for="abNummer" data-label="Nummer der Auftragsbestätigung">Nummer der Auftragsbestätigung <small>(falls zur Hand, zum Beispiel AB-2026-0001)</small></label>
|
||||
<input id="abNummer" name="abNummer" maxlength="20" autocomplete="off" autocapitalize="characters" spellcheck="false" placeholder="AB-2026-0001">
|
||||
</div>
|
||||
|
||||
<label for="name" data-label-for="name" data-label="Name">Name</label>
|
||||
<input id="name" name="name" autocomplete="name" required maxlength="200">
|
||||
|
||||
@@ -49,7 +56,8 @@
|
||||
</main>
|
||||
<script src="/legal-form.js"></script>
|
||||
<script>
|
||||
initLegalForm({ endpoint: '/api/abo/public/withdraw', fields: ['order', 'name', 'customerNumber', 'email'] })
|
||||
initLegalForm({ endpoint: '/api/abo/public/withdraw', fields: ['order', 'abNummer', 'name', 'customerNumber', 'email'] })
|
||||
</script>
|
||||
<script type="module" src="/auftrag-widerruf.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,110 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { installFakeDom } from './support/fakeDom.js'
|
||||
import { h } from '../public/auftrag-dom.js'
|
||||
import { initProjektWiderruf } from '../public/auftrag-widerruf.js'
|
||||
import { pruefeQuelltext } from './auftraegeKundenportalDaten.js'
|
||||
|
||||
const html = readFileSync(new URL('../public/widerrufen.html', import.meta.url), 'utf8')
|
||||
const MELDUNG = 'Die Nummer sieht anders aus als AB-2026-0001. Bitte prüfen Sie die Nummer oder lassen Sie das Feld leer.'
|
||||
|
||||
test('widerrufen.html: Vertragsart „Projektauftrag“ hinter „Öffnungszeiten live“, freiwilliges Feld abNummer vor dem Namen, Skripte', () => {
|
||||
assert.match(html, /name="order" value="zeiten"[^>]*> Öffnungszeiten live<\/label>\s*<label class="radio"><input type="radio" name="order" value="projekt" data-text="Projektauftrag \(Auftragsbestätigung\)"> Projektauftrag \(Auftragsbestätigung\)<\/label>\s*<\/fieldset>/)
|
||||
assert.match(html, /<div id="ab-nummer-wrap">\s*<label for="abNummer" data-label-for="abNummer" data-label="Nummer der Auftragsbestätigung">Nummer der Auftragsbestätigung <small>\(falls zur Hand, zum Beispiel AB-2026-0001\)<\/small><\/label>\s*<input id="abNummer" name="abNummer" maxlength="20" autocomplete="off" autocapitalize="characters" spellcheck="false" placeholder="AB-2026-0001">\s*<\/div>/)
|
||||
assert.doesNotMatch(html, /<input id="abNummer"[^>]*required/, 'die Nummer ist freiwillig')
|
||||
assert.ok(html.indexOf('id="ab-nummer-wrap"') < html.indexOf('for="name"'), 'das Feld steht vor dem Namen')
|
||||
assert.doesNotMatch(html, /id="ab-nummer-wrap"[^>]*hidden/, 'ohne das Skript bleibt das Feld sichtbar')
|
||||
assert.match(html, /initLegalForm\(\{ endpoint: '\/api\/abo\/public\/withdraw', fields: \['order', 'abNummer', 'name', 'customerNumber', 'email'\] \}\)/)
|
||||
assert.match(html, /<script type="module" src="\/auftrag-widerruf\.js"><\/script>/)
|
||||
for (const wert of ['abschluss', 'wechsel', 'zusatzspeicher', 'funktion', 'termine', 'shop', 'zeiten']) assert.match(html, new RegExp(`name="order" value="${wert}"`), `${wert} bleibt`)
|
||||
})
|
||||
|
||||
const mit = (fn) => async () => {
|
||||
const fx = installFakeDom()
|
||||
try {
|
||||
await fn(fx)
|
||||
} finally {
|
||||
fx.restore()
|
||||
}
|
||||
}
|
||||
function aufbau(fx, order = 'abschluss') {
|
||||
const radios = ['abschluss', 'projekt'].map((wert) => h('input', { type: 'radio', name: 'order', value: wert, checked: wert === order }))
|
||||
const nummer = h('input', { id: 'abNummer', name: 'abNummer' })
|
||||
const wrap = h('div', { id: 'ab-nummer-wrap' }, h('label', { for: 'abNummer' }, 'Nummer'), nummer)
|
||||
const fehler = h('p', { id: 'form-error', class: 'error hidden' })
|
||||
const knopf = h('button', { type: 'submit' }, 'Widerruf bestätigen')
|
||||
const form = h('form', { id: 'decl-form' }, h('fieldset', {}, ...radios.map((r) => h('label', { class: 'radio' }, r))), wrap, fehler, knopf)
|
||||
fx.document.body.append(form)
|
||||
const gesendet = []
|
||||
form.addEventListener('submit', (ev) => {
|
||||
ev.preventDefault()
|
||||
gesendet.push(nummer.value)
|
||||
})
|
||||
initProjektWiderruf(form)
|
||||
return { form, radios, nummer, wrap, fehler, knopf, gesendet }
|
||||
}
|
||||
|
||||
test('Das Feld erscheint nur bei „Projektauftrag“ und wird beim Wegwählen geleert', mit(async (fx) => {
|
||||
const t = aufbau(fx, 'abschluss')
|
||||
assert.equal(t.wrap.classList.contains('hidden'), true)
|
||||
await fx.klick(t.radios[1])
|
||||
assert.equal(t.wrap.classList.contains('hidden'), false)
|
||||
await fx.setze(t.nummer, 'AB-2026-0001')
|
||||
await fx.klick(t.radios[0])
|
||||
assert.equal(t.wrap.classList.contains('hidden'), true)
|
||||
assert.equal(t.nummer.value, '')
|
||||
}))
|
||||
|
||||
test('Ist „Projektauftrag“ schon gewählt, ist das Feld sofort da', mit(async (fx) => {
|
||||
const t = aufbau(fx, 'projekt')
|
||||
assert.equal(t.wrap.classList.contains('hidden'), false)
|
||||
}))
|
||||
|
||||
test('Projektauftrag: eine eingegebene, aber unpassende Nummer wird gemeldet und stoppt das Absenden; Wechsel der Vertragsart nimmt die Meldung weg', mit(async (fx) => {
|
||||
const t = aufbau(fx, 'projekt')
|
||||
for (const eingabe of ['AB-2026-01', 'RE-2026-0001', 'irgendwas']) {
|
||||
await fx.setze(t.nummer, eingabe)
|
||||
await fx.klick(t.knopf)
|
||||
assert.deepEqual(t.gesendet, [], eingabe)
|
||||
assert.equal(t.fehler.classList.contains('hidden'), false)
|
||||
assert.equal(t.fehler.textContent, MELDUNG)
|
||||
assert.equal(t.nummer.value, eingabe)
|
||||
}
|
||||
assert.equal(fx.document.activeElement, t.nummer)
|
||||
await fx.klick(t.radios[0])
|
||||
assert.equal(t.fehler.classList.contains('hidden'), true)
|
||||
}))
|
||||
|
||||
test('Ohne Nummer geht der Projektauftrag durch (freiwilliges Feld); die eigene Meldung verschwindet, sobald der Widerruf durchgeht', mit(async (fx) => {
|
||||
const t = aufbau(fx, 'projekt')
|
||||
await fx.klick(t.knopf)
|
||||
assert.deepEqual(t.gesendet, [''])
|
||||
assert.equal(t.fehler.classList.contains('hidden'), true)
|
||||
await fx.setze(t.nummer, 'kaputt')
|
||||
await fx.klick(t.knopf)
|
||||
assert.deepEqual(t.gesendet, [''], 'unpassende Eingabe: nichts gesendet')
|
||||
assert.equal(t.fehler.classList.contains('hidden'), false)
|
||||
await fx.setze(t.nummer, ' ')
|
||||
await fx.klick(t.knopf)
|
||||
assert.deepEqual(t.gesendet, ['', ''], 'nur Leerzeichen zählen als leer')
|
||||
assert.equal(t.fehler.classList.contains('hidden'), true)
|
||||
}))
|
||||
|
||||
test('Gültige Nummer in anderer Schreibweise wird vereinheitlicht, dann geht der Widerruf durch', mit(async (fx) => {
|
||||
const t = aufbau(fx, 'projekt')
|
||||
await fx.setze(t.nummer, ' ab 2026 0001 ')
|
||||
await fx.klick(t.knopf)
|
||||
assert.deepEqual(t.gesendet, ['AB-2026-0001'])
|
||||
assert.equal(t.fehler.classList.contains('hidden'), true)
|
||||
}))
|
||||
|
||||
test('Andere Vertragsart: kein Zwang, das Feld wird leer gesendet', mit(async (fx) => {
|
||||
const t = aufbau(fx, 'abschluss')
|
||||
await fx.klick(t.knopf)
|
||||
assert.deepEqual(t.gesendet, [''])
|
||||
}))
|
||||
|
||||
test('Quelltext: nur ./auftrag-ansicht.js, kein fetch', () => {
|
||||
pruefeQuelltext('auftrag-widerruf.js', { importe: ['./auftrag-ansicht.js'] })
|
||||
})
|
||||
@@ -1456,7 +1456,7 @@ test('widerrufen_html_radio: Radioknöpfe order', () => {
|
||||
assert.match(src, /name="order" value="shop"[^>]*data-text="Webshop \(Einrichtung\)"[^>]*> Webshop \(Einrichtung\)<\/label>/)
|
||||
assert.match(src, /name="order" value="zeiten"[^>]*data-text="Öffnungszeiten live"[^>]*> Öffnungszeiten live<\/label>/)
|
||||
assert.doesNotMatch(src, /value="WEBklar Wartungs-Abo" disabled/)
|
||||
assert.match(src, /fields: \['order', 'name', 'customerNumber', 'email'\]/)
|
||||
assert.match(src, /fields: \['order', 'abNummer', 'name', 'customerNumber', 'email'\]/)
|
||||
})
|
||||
|
||||
test('legal-form.js: Auswahl statt festem Vertrag, packages nur beim Zusatzspeicher', () => {
|
||||
|
||||
Reference in new issue
Block a user