Öffnungszeiten: Fixrunde Server (Schema-Kopplung, K27, Wertersatz, Widerruf, Ticket nachlegen)

- activate() schreibt die Zeiten-Felder (aboEndeAt, behaltenSessionId) nur noch über extra
  (nur Abo-Rückkehr und Team-Aktivierung der Zeiten); News und Terminplaner laufen auch vor zeiten-setup.mjs
- Abo-Abgleich: K27 gilt als offen, solange der Marker vor disabledAt liegt (kein Zurücksetzen nach disable() nötig);
  K27 geht erst nach dem Ersatz-Ticket raus (scheitert es, wiederholt der nächste Lauf beides);
  Löschung setzt auch activatedAt zurück
- Zustimmung "beginn" (bezahlt) mit Wertersatz-Satz (§ 357a Abs. 2 BGB) wie bei News und Terminplaner, Fassung 2026-10-05
- Widerruf: Bestellung "zeiten" in ORDERS, FEATURE_OF_ORDER, orderLabel und widerrufen.html
- Team "Ticket nachlegen" der Zeiten ohne Kauf geht über holeZeitenNach statt über den Zahlungsweg
- Einschalten-Route nimmt nur { zustimmung }, nicht die Altform { consent: true }
- Team-Aktivierung behält kauf/unlockedByPurchase bei einem bisher gekauften Dokument

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 09:04:05 +00:00
1 parent c10e50d0b3
commit ecf67d6978
13 files changed
+337 -48

No files matched your search

+33
View File
@@ -277,6 +277,18 @@ test('Zustimmung fehlt oder veraltet ⇒ 400/409 wie beim Checkout, mit der Grat
keineSpuren()
})
test('Altform { consent: true } (News, ohne Fassungsprüfung) wird bei dieser neuen Route nicht angenommen: 400, keine Spur', async () => {
// sonst würde nach einer Textänderung der Gratis-Zustimmung ein alter Tab den neuen Text bestätigen, ohne ihn gesehen zu haben
for (const body of [{ projectId: 'p1', consent: true }, { projectId: 'p1', consent: true, zustimmung: null }, { projectId: 'p1', consent: true, zustimmung: {} }]) {
const res = await einschalten(body)
assert.equal(res.status, 400, JSON.stringify(body))
assert.equal(res.body.code, 'zustimmung_fehlt')
}
keineSpuren()
// die Form mit Fassung und Haken geht weiter
assert.equal((await einschalten(koerper({ consent: true }))).status, 200)
})
test('Projekt: fehlt ⇒ 400, fremd oder unbekannt ⇒ 403, archiviert ⇒ 409', async () => {
assert.equal((await einschalten({ zustimmung: zustimmung() })).status, 400)
assert.equal((await einschalten(koerper({ projectId: 'p3' }))).status, 403)
@@ -455,6 +467,27 @@ test('Team aus disabled: zurück auf setup, Neubau erneut, kein neues Ticket', a
assert.equal(tickets().length, 0)
})
test('Team aus disabled bei einem gekauften Dokument: kauf und unlockedByPurchase bleiben (Widerrufs-Zuordnung), einbau bleibt, kein Neubau', async () => {
for (const [nr, kauf] of [[1, { unlockedBy: 'kauf', unlockedByPurchase: true }], [2, { unlockedByPurchase: true }]]) {
const projectId = `p_k${nr}`
projekt(projectId)
aw.seed('portalFeatures', {
$id: `f_${projectId}_zeiten`, customerId: 'c1', projectId, featureKey: 'zeiten', state: 'disabled', enabled: false, siteKey: 'abcdefghijklmnop',
einbau: 'angestossen', ticketId: 'ticket_alt', disabledReason: 'team', disabledAt: '2026-10-01T10:00:00.000Z', ...kauf,
})
const { doc } = await einschaltenZeiten({ customerId: 'c1', projectId, by: 'team', now: JETZT })
assert.deepEqual([doc.state, sf.unlockedByVon(doc), doc.unlockedByPurchase, doc.disabledAt, doc.disabledReason, doc.ticketId, doc.einbau], ['setup', 'kauf', true, null, null, 'ticket_alt', 'angestossen'], `Fall ${nr}`)
}
assert.deepEqual([rebuilds.length, tickets().length], [0, 0], 'bei gekauften Dokumenten kümmert sich markPaid um Neubau und Ticket, nicht das Nachholen')
// ein Dokument, das nie gekauft war, wird weiter ein Team-Dokument; ohne Dokument ebenso
aw.seed('portalFeatures', {
$id: KEY_P1, customerId: 'c1', projectId: 'p1', featureKey: 'zeiten', state: 'disabled', enabled: false, siteKey: 'abcdefghijklmnop',
unlockedBy: 'abo', unlockedByPurchase: false, einbau: 'angestossen', ticketId: 'ticket_alt',
})
const team = await einschaltenZeiten({ customerId: 'c1', projectId: 'p1', by: 'team', now: JETZT })
assert.deepEqual([team.doc.unlockedBy, team.doc.unlockedByPurchase], ['team', false])
})
test('holeZeitenNach: Ticket fehlt in setup ⇒ anlegen; K24 nur bei unlockedBy abo und leerem k24At, danach Marker', async () => {
aw.seed('portalFeatures', { $id: KEY_P1, customerId: 'c1', projectId: 'p1', featureKey: 'zeiten', state: 'setup', enabled: false, siteKey: 'abcdefghijklmnop', unlockedBy: 'abo', einbau: 'manuell' })
const doc = await holeZeitenNach(zeitenDoc(), { now: JETZT })