Öffnungszeiten: Team-Aktionen und Ersatz-Ticket beim Abschalten

Team-Route für zeiten: Aktivieren läuft über den Gratis-Pfad (einschaltenZeiten, setup,
unlockedBy team, Neubau, Einbau-Ticket, keine K24), Abschalten setzt disabled mit Grund team
ohne Löschfrist und legt über nachAbschaltung bei geändertem Wochenplan das Ticket
"Ersatz-Tabelle aktualisieren" an. PATCH /:customerId/zeiten/team speichert die
Team-Einstellungen mit teamVersion. Die Team-Ansicht liefert für zeiten zusätzlich plaene,
team, teamVersion und hindernis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 02:39:43 +00:00
1 parent 11dc4b2c9f
commit 97e6971276
3 files changed
+780 -13

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
import { config } from '../../config.js'
import { berlinDateTime } from '../berlinTime.js'
import { getDocument } from '../appwriteAdmin.js'
import { createFeatureTicket } from '../ticketAdmin.js'
import { ersatzHtml, wochenplanHash } from './ersatz.js'
import { ladeZeiten, setzeErsatzHash } from './speicher.js'
import { hauptplan } from './zeitlogik.js'
/**
* Nach dem Abschalten von Oeffnungszeiten live (Spec 4.5): steht in den Platzhaltern der Kundenseite noch eine Ersatz-Tabelle, die
* nicht mehr zum aktuellen Wochenplan passt, bekommt das Team ein WOMS-Ticket "Ersatz-Tabelle aktualisieren - <Projekt>" mit dem
* aktuellen Ersatz-HTML (gehoert zum enthaltenen Einbau). Ohne Skript zeigt die Seite ab jetzt nur noch diese Tabelle.
*
* Aufrufer: die Team-Route (disable) und spaeter der Abo-Abgleich (Abo-Ende). Aufruf NACH dem Speichern des Zustands und ausserhalb
* jeder Sperre `feature-admin:<projekt>`: das Ticket legt eine Netzwerkanfrage an, und Sperren sind nicht wiederbetretbar.
*/
const THEMA = 'Ersatz-Tabelle aktualisieren'
/** Text des Tickets: Kopfzeilen wie bei den Einbau-Tickets, Anlass, Aufgabe und das aktuelle Ersatz-HTML des Hauptplans. */
function ticketDetails({ doc, project, daten, haupt, now }) {
const projektName = project?.projectName || project?.subdomain || ''
const domain = project?.liveDomain || project?.previewUrl || project?.subdomain || ''
return [
'Funktion: zeiten',
`Projekt: ${projektName}`,
`Domain: ${domain}`,
`siteKey: ${doc.siteKey ?? ''}`,
'',
`Öffnungszeiten live wurde am ${berlinDateTime(now).date} abgeschaltet. Der Wochenplan weicht von der Tabelle ab, die beim Einbau auf die Seite gesetzt wurde (oder der damalige Stand ist nicht bekannt).`,
'Aufgabe: Auf der Kundenseite in jedem Platzhalter <div data-webklar-zeiten ...> die Tabelle durch die aktuelle ersetzen. Ohne Skript zeigt die Seite nur noch diese Tabelle.',
'Weitere Pläne: WOMS → Funktionen → Ersatz-HTML kopieren.',
'',
haupt.kurz ? `Ersatz-HTML (Hauptplan ${haupt.kurz}):` : 'Ersatz-HTML (Hauptplan):',
ersatzHtml(daten, haupt),
].join('\n')
}
/** Das Projekt des Dokuments; ein geloeschtes Projekt (404) ist kein Fehler, der Name fehlt dann im Ticket. */
async function ladeProjekt(projectId) {
try {
return await getDocument(config.collections.websiteProjects, projectId)
} catch (err) {
if (err?.status === 404) return null
throw err
}
}
/**
* Pruefen, ob die Ersatz-Tabelle der Seite noch zum Wochenplan passt, und sonst das Ticket anlegen.
* Ruling R23: ein leerer oder fehlender gemerkter Hash (ersatzHash) gilt als "unbekannt", also wie geaendert, sobald Zeiten da sind
* (schlimmstenfalls ein unnoetiges Ticket). Nach dem Ticket wird der Hash auf den aktuellen Stand gesetzt: er gehoert dann zur Tabelle,
* die das Team einsetzt, und ein zweiter Aufruf (Doppelklick, spaeterer Lauf) legt kein zweites Ticket an. Scheitert das Ticket,
* bleibt der Hash, und der naechste Aufruf versucht es erneut.
* @param {{ $id: string, customerId?: string, projectId?: string, siteKey?: string }} doc portalFeatures-Dokument der Zeiten
* (aus disable(): der Stand nach dem Abschalten)
* @param {{ now?: Date }} [options]
* @returns {Promise<'angelegt'|'aktuell'|'fehler'>} angelegt: Ticket entstanden; aktuell: nichts zu tun (keine Zeiten oder Hash gleich);
* fehler: nicht pruefbar oder Ticket nicht angelegt (im Log ohne Kundendaten). Wirft nie.
*/
export async function nachAbschaltung(doc, { now = new Date() } = {}) {
try {
if (typeof doc?.$id !== 'string' || doc.$id === '') throw new TypeError('Dokument ohne $id')
const gespeichert = await ladeZeiten(doc.$id)
const daten = gespeichert?.daten ?? null
const haupt = hauptplan(daten)
// ohne Zeiten wurde nie eine Tabelle zum Ersetzen vorbereitet
if (!haupt) return 'aktuell'
const hash = wochenplanHash(daten)
if (gespeichert.ersatzHash !== '' && gespeichert.ersatzHash === hash) return 'aktuell'
const project = await ladeProjekt(doc.projectId)
const projektName = project?.projectName || project?.subdomain || ''
await createFeatureTicket({
customerId: doc.customerId,
project,
featureKey: 'zeiten',
siteKey: doc.siteKey,
topic: projektName ? `${THEMA} – ${projektName}` : THEMA,
details: ticketDetails({ doc, project, daten, haupt, now }),
})
// Das Ticket steht; scheitert nur das Merken, entsteht beim naechsten Abschalten ein zweites (Log, kein Fehler)
try {
await setzeErsatzHash(doc, hash)
} catch (err) {
console.error(`[zeiten] Ersatz-Hash für Projekt ${doc.projectId} nach dem Ticket nicht gemerkt: ${err?.name ?? 'Fehler'} ${err?.status ?? ''}`.trim())
}
return 'angelegt'
} catch (err) {
// nie Kundendaten ins Log: Art und Statuscode, keine Meldung (Appwrite-Meldungen koennen Feldinhalte nennen)
console.error(`[zeiten] Ersatz-Ticket nach dem Abschalten (Projekt ${doc?.projectId ?? '?'}): ${err?.name ?? 'Fehler'} ${err?.status ?? ''} ${err?.type ?? ''}`.replace(/\s+/g, ' ').trim())
return 'fehler'
}
}