Öffnungszeiten: Tests und Belehrung nach dem Merge mit TP2
Zeiten nutzen die echte Widerrufsbelehrung wie der Terminplaner (der Platzhalter BELEHRUNG_PLATZHALTER ist mit TP2 F7 entfallen). Gepinnte Listen als Vereinigung beider Seiten: K15-Blöcke, T16-Blöcke, legal-Schlüssel der Projektliste, hindernis in der Team-Ansicht für alle Funktionen. Neue Tests: Abnahme (Freischalten) der Zeiten über den Haken freischaltbar und mit Ersatz-Hash, abnahme gibt für Zeiten nie ab. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1d5a565e9f
commit
019833d385
6 files changed
+71
-19
No files matched your search
@@ -68,12 +68,6 @@ async function zeitenNachFreischaltung(doc) {
|
||||
await setzeErsatzHash(doc, wochenplanHash((await ladeZeiten(doc.$id))?.daten ?? null))
|
||||
}
|
||||
|
||||
/**
|
||||
* Platzhalter der Widerrufsbelehrung der Oeffnungszeiten (Haken belehrung von zeiten). Der Terminplaner liefert seine echte
|
||||
* Belehrung mit termineBelehrung (Task F7); fuer die Oeffnungszeiten bleibt dieser Platzhalter, bis ihr Belehrungstext feststeht.
|
||||
*/
|
||||
export const BELEHRUNG_PLATZHALTER = 'ENTWURF (Block F): Widerrufsbelehrung mit Muster-Widerrufsformular.'
|
||||
|
||||
/**
|
||||
* Vertragsunterlagen des Terminplaners abrufbar? (Stufe 5b des Checkouts, vor jeder Stripe-Sitzung): HTML-Seiten und PDFs der
|
||||
* aktuellen Fassung sind da und lesbar (legalDocs.featureLegalReady, Task F8; fail-closed, wirft nie). Geladen wird legalDocs erst
|
||||
@@ -87,7 +81,8 @@ async function termineUnterlagenBereit() {
|
||||
/**
|
||||
* Widerrufsbelehrung mit Muster-Widerrufsformular der Funktionen (Gesetzesmuster, Sie-Form), die die Bestellseite vor dem Knopf
|
||||
* zeigt und der Zustimmungsnachweis speichert: dieselbe Zeichenkette an beiden Stellen (Task F7, Text aus Task F3). Geladen
|
||||
* wird sie erst beim Aufruf (kein Importzyklus).
|
||||
* wird sie erst beim Aufruf (kein Importzyklus). Sie gilt fuer jede kostenpflichtige Funktion; auch die Oeffnungszeiten live
|
||||
* (Haken belehrung von zeiten) liefern sie.
|
||||
*/
|
||||
async function termineBelehrung() {
|
||||
const { widerrufsbelehrungFunktion } = await import('./termine/rechtstexte.js')
|
||||
@@ -109,12 +104,13 @@ const STANDARD_HAKEN = {
|
||||
belehrung: termineBelehrung,
|
||||
freischaltbar: termineFreischaltbar,
|
||||
},
|
||||
// Oeffnungszeiten live: keine Bestellpruefung (die Einstellung entsteht erst im Editor); belehrung und
|
||||
// unterlagenBereit wie beim Terminplaner; freischaltbar: erst mit eingetragenen Zeiten; nachFreischaltung: Ersatz-Hash merken
|
||||
// Oeffnungszeiten live: keine Bestellpruefung (die Einstellung entsteht erst im Editor); belehrung wie beim Terminplaner,
|
||||
// unterlagenBereit immer (die Oeffnungszeiten haben nur die Leistungsbeschreibung als Seite, keine PDFs); freischaltbar: erst mit
|
||||
// eingetragenen Zeiten; nachFreischaltung: Ersatz-Hash merken
|
||||
zeiten: {
|
||||
verfuegbar: (email) => zeitenFuerKunde(email),
|
||||
unterlagenBereit: async () => true,
|
||||
belehrung: async () => BELEHRUNG_PLATZHALTER,
|
||||
belehrung: termineBelehrung,
|
||||
freischaltbar: zeitenFreischaltbar,
|
||||
nachFreischaltung: zeitenNachFreischaltung,
|
||||
},
|
||||
|
||||
@@ -115,8 +115,10 @@ test('liste_legal: legal.termine mit Fassung, https-Links mit Fassung und der Be
|
||||
const { legalPaths } = await import('../server/services/legalDocs.js')
|
||||
const V = config.termine.avvVersion
|
||||
const body = await get('/api/funktionen', { cookie })
|
||||
assert.deepEqual(Object.keys(body.legal).sort(), ['news', 'termine'])
|
||||
assert.deepEqual(Object.keys(body.legal).sort(), ['news', 'termine', 'zeiten'])
|
||||
assert.equal(body.legal.news, null)
|
||||
// die Oeffnungszeiten haben keine Rechtsdokumente zum Anhaengen (avv und widerruf null): kein Eintrag wie beim Terminplaner
|
||||
assert.equal(body.legal.zeiten, null)
|
||||
const termine = body.legal.termine
|
||||
assert.deepEqual(Object.keys(termine).sort(), ['belehrung', 'links', 'version'])
|
||||
assert.equal(termine.version, V)
|
||||
|
||||
@@ -272,6 +272,38 @@ test('nachFreischaltung des Registers wird je freigeschaltetem Dokument mit dem
|
||||
assert.match(logText(), /Haken kaputt/)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------------------ Freischalten (Abnahme) durch das Team
|
||||
|
||||
// Merge mit TP2: freischaltenNachAbnahme (WOMS "Freischalten (Abnahme)") gilt fuer jede Funktion mit ticketFreischaltung. Die
|
||||
// Voraussetzung der Zeiten ist ihr Haken freischaltbar (eingetragene Zeiten), nicht Abo und Einstellung des Terminplaners.
|
||||
test('Abnahme der Zeiten: ohne eingetragene Zeiten 409 und alles bleibt setup, auch ohne laufendes Wartungs-Abo', async () => {
|
||||
const id = await einrichtung({ ticket: 'Open' })
|
||||
const projectId = feld(id, 'projectId')
|
||||
await assert.rejects(sf.freischaltenNachAbnahme({ customerId: 'c1', projectId, key: 'zeiten', by: 'Kenso', now: JETZT }), (err) => {
|
||||
assert.equal(err.status, 409)
|
||||
assert.match(err.message, /Voraussetzungen/)
|
||||
return true
|
||||
})
|
||||
assert.equal(zustand(id), 'setup')
|
||||
assert.equal(sink.sent.length, 0)
|
||||
})
|
||||
|
||||
test('Abnahme der Zeiten: mit eingetragenen Zeiten active, K16, Ersatz-Hash (nachFreischaltung) und ohne Abo-Prüfung des Terminplaners', async () => {
|
||||
const id = await einrichtung({ ticket: 'Open', zeiten: WOCHE })
|
||||
const projectId = feld(id, 'projectId')
|
||||
const vorher = await ladeZeiten(id)
|
||||
assert.equal(vorher.ersatzHash, '')
|
||||
const { doc, activated } = await sf.freischaltenNachAbnahme({ customerId: 'c1', projectId, key: 'zeiten', by: 'Kenso', now: JETZT })
|
||||
assert.equal(activated, true)
|
||||
assert.equal(doc.state, 'active')
|
||||
assert.equal(zustand(id), 'active')
|
||||
assert.equal(k16().length, 1)
|
||||
assert.equal((await ladeZeiten(id)).ersatzHash, wochenplanHash(vorher.daten))
|
||||
// schon aktiv: Erfolg ohne zweite Mail
|
||||
assert.equal((await sf.freischaltenNachAbnahme({ customerId: 'c1', projectId, key: 'zeiten', now: JETZT })).activated, false)
|
||||
assert.equal(k16().length, 1)
|
||||
})
|
||||
|
||||
// ------------------------------------------------------------------------------------------- Selbstheilung
|
||||
|
||||
test('Selbstheilung: setup ohne ticketId ⇒ der Job legt das Ticket an (und sendet die K24)', async () => {
|
||||
|
||||
@@ -330,10 +330,11 @@ test('K15: Block behalten ersetzt den Einrichtungstext, ohne ihn bleibt die Mail
|
||||
const feature = reg.featureConfig('zeiten')
|
||||
const ctx = { kundeName: 'Silke Müller', projekt: 'Praxis Muster', invoiceUrl: 'https://invoice.stripe.test/i/1' }
|
||||
const neu = sf.bestellMail(feature, ctx)
|
||||
assert.deepEqual(neu.blocks, { rechnung: true, behalten: false, neu: true })
|
||||
// anlagen, anhang und zustimmungen (TP2 F8) bleiben aus: die Oeffnungszeiten haben keine Rechtsdokumente zum Anhaengen
|
||||
assert.deepEqual(neu.blocks, { rechnung: true, anlagen: false, anhang: false, zustimmungen: false, behalten: false, neu: true })
|
||||
assert.deepEqual(sf.bestellMail(feature, { ...ctx, behalten: false }).blocks, neu.blocks)
|
||||
const behalten = sf.bestellMail(feature, { ...ctx, behalten: true })
|
||||
assert.deepEqual(behalten.blocks, { rechnung: true, behalten: true, neu: false })
|
||||
assert.deepEqual(behalten.blocks, { rechnung: true, anlagen: false, anhang: false, zustimmungen: false, behalten: true, neu: false })
|
||||
assert.deepEqual(behalten.values, neu.values, 'dieselben Werte, nur der Wortlaut wechselt')
|
||||
|
||||
const textNeu = render(neu)
|
||||
@@ -359,7 +360,7 @@ test('K15: Block behalten ersetzt den Einrichtungstext, ohne ihn bleibt die Mail
|
||||
|
||||
test('K15: die Blöcke behalten und neu stehen im Kopf der Vorlage', () => {
|
||||
const tpl = loadTemplate('K15')
|
||||
assert.deepEqual(tpl.blocks, ['rechnung', 'behalten', 'neu'])
|
||||
assert.deepEqual(tpl.blocks, ['rechnung', 'behalten', 'neu', 'anlagen', 'anhang', 'zustimmungen'])
|
||||
// jeder Block erscheint, und in der Behalten-Fassung (neu aus) fehlt jeder Rest von zeit/danach
|
||||
const behalten = renderMail('K15', Object.fromEntries(tpl.placeholders.map((p) => [p, `Wert-${p}`])), { behalten: true })
|
||||
assert.deepEqual(behalten.problems, [])
|
||||
@@ -368,7 +369,7 @@ test('K15: die Blöcke behalten und neu stehen im Kopf der Vorlage', () => {
|
||||
|
||||
test('T16: Blöcke abo und team nennen den Weg ohne Zahlung', () => {
|
||||
const tpl = loadTemplate('T16')
|
||||
assert.deepEqual(tpl.blocks, ['zahlung_fehlgeschlagen', 'ungeklaert', 'kein_abo', 'bereits_aktiv', 'anleitung', 'gesundheit', 'abo', 'team'])
|
||||
assert.deepEqual(tpl.blocks, ['zahlung_fehlgeschlagen', 'ungeklaert', 'kein_abo', 'bereits_aktiv', 'anleitung', 'gesundheit', 'abo', 'team', 'anhang_fehlt'])
|
||||
const werte = { kunde_name: 'Silke Müller', email: 'silke@example.com', projekt: 'Praxis Muster', funktion: 'Öffnungszeiten live', woid: '10042', sitekey: 'abc', sitzung: '', zustand: '', anleitung_url: '', gesundheit_text: '' }
|
||||
const aus = { zahlung_fehlgeschlagen: false, ungeklaert: false, kein_abo: false, bereits_aktiv: false, anleitung: false, gesundheit: false }
|
||||
const abo = renderMail('T16', werte, { ...aus, abo: true, team: false })
|
||||
@@ -382,5 +383,5 @@ test('T16: Blöcke abo und team nennen den Weg ohne Zahlung', () => {
|
||||
const keiner = renderMail('T16', werte, aus)
|
||||
assert.doesNotMatch(keiner.text, /im Wartungs-Abo enthalten|vom Team freigeschaltet/i)
|
||||
// sendT16 führt beide Blöcke als Vorgabe (aus), damit jede Team-Mail sie nennt
|
||||
assert.match(readFileSync(new URL('../server/services/siteFeatures.js', import.meta.url), 'utf8'), /gesundheit: false, abo: false, team: false, \.\.\.bloecke/)
|
||||
assert.match(readFileSync(new URL('../server/services/siteFeatures.js', import.meta.url), 'utf8'), /abo: false,\s+team: false,\s+\.\.\.bloecke/)
|
||||
})
|
||||
@@ -110,7 +110,6 @@ test('Standard-Haken: verfuegbar, unterlagenBereit, belehrung, freischaltbar, na
|
||||
const termine = reg.featureHaken('termine')
|
||||
assert.equal(await haken.unterlagenBereit(), true)
|
||||
assert.equal(await haken.belehrung(), await termine.belehrung(), 'wie beim Terminplaner')
|
||||
assert.equal(await haken.belehrung(), reg.BELEHRUNG_PLATZHALTER)
|
||||
// kein Platzhalter mehr (der alte Haken lieferte immer true): Verhalten und Nacharbeit prüft test/zeitenFreischalten.test.js
|
||||
for (const name of ['freischaltbar', 'nachFreischaltung']) assert.equal(typeof haken[name], 'function', name)
|
||||
})
|
||||
|
||||
+24
-2
@@ -180,6 +180,27 @@ test('Team aktiviert eine abgeschaltete Funktion wieder: setup, Abschaltgrund ge
|
||||
assert.match(doc.adminNote, / Andrej: activate \| alte Notiz$/)
|
||||
})
|
||||
|
||||
// Merge mit TP2: die Aktionen abnahme und ticket gibt es fuer jede Funktion mit ticketFreischaltung; fuer die Zeiten darf
|
||||
// abnahme weder abschalten noch den Gratis-Pfad nehmen, sondern gibt in setup frei (Haken freischaltbar, Ersatz-Hash)
|
||||
test('Team „Freischalten (Abnahme)“ der Zeiten: ohne Zeiten 409 (bleibt setup), mit Zeiten active mit Ersatz-Hash, nie disabled', async () => {
|
||||
const doc = featureDoc('p1', { ticketId: 't_offen', einbau: 'angestossen' })
|
||||
aw.seed('workorders', { $id: 't_offen', woid: '10002', topic: 'Zeiten', status: 'Open', customerId: 'c1' })
|
||||
aw.seed('portalFeatures', doc)
|
||||
const zu = await aktion('abnahme')
|
||||
assert.equal(zu.status, 409, JSON.stringify(zu.body))
|
||||
assert.equal(zeitenDoc().state, 'setup')
|
||||
assert.equal(sink.sent.length, 0)
|
||||
|
||||
await mitZeiten(doc, EIN_PLAN())
|
||||
const ok = await aktion('abnahme')
|
||||
assert.equal(ok.status, 200, JSON.stringify(ok.body))
|
||||
assert.equal(ok.body.projects[0].features.zeiten.state, 'active')
|
||||
assert.equal(zeitenDoc().state, 'active')
|
||||
assert.match((await ladeZeiten(doc.$id)).ersatzHash, /^[0-9a-f]{16}$/)
|
||||
assert.equal(sink.sent.length, 1, 'K16 an den Kunden')
|
||||
assert.equal(ok.body.ersatzTicket, undefined, 'abnahme legt kein Ersatz-Ticket an')
|
||||
})
|
||||
|
||||
test('Aktivieren: fremdes oder unbekanntes Projekt 403, unbekannte Aktion 400, ohne Anmeldung 401, nichts entsteht', async () => {
|
||||
assert.equal((await aktion('activate', {}, { projectId: 'p3' })).status, 403)
|
||||
assert.equal((await aktion('activate', {}, { projectId: 'gibtsnicht' })).status, 403)
|
||||
@@ -527,9 +548,10 @@ test('adminView zeigt Pläne, team und hindernis (nur für zeiten)', async () =>
|
||||
assert.equal(z2.teamVersion, 0)
|
||||
assert.equal(z2.hindernis, 'zeiten_fehlen')
|
||||
|
||||
// andere Funktionen bekommen die Felder nicht
|
||||
// andere Funktionen bekommen die Zeiten-Felder nicht; hindernis tragen seit TP2 alle (null ausser beim Terminplaner in setup)
|
||||
for (const key of ['news', 'termine']) {
|
||||
for (const feld of ['plaene', 'team', 'teamVersion', 'hindernis']) assert.equal(feld in projekte[0].features[key], false, `${key}.${feld}`)
|
||||
for (const feld of ['plaene', 'team', 'teamVersion']) assert.equal(feld in projekte[0].features[key], false, `${key}.${feld}`)
|
||||
assert.equal(projekte[0].features[key].hindernis, null, `${key}.hindernis`)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user