Öffnungszeiten: Register und Texte im Ticketsystem
Funktionen-Reiter kennt "Öffnungszeiten live" (zeiten) hinter dem Terminplaner: Bestätigungstexte, Site-Key, Aktionen je Zustand (Abnahme nur ohne Hindernis "zeiten_fehlen"). Neues Modul zeitenView.js: Statustext (Einbau läuft / wartet auf die Zeiten des Kunden / Abo-Ende mit Frist), navSelektorFehler (Spiegel der Portal-Regel) und teamBody für PATCH .../zeiten/team. Protokoll-Label für zeiten_gespeichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
72eedeb59c
commit
994347408d
6 files changed
+326
-13
No files matched your search
@@ -1,4 +1,4 @@
|
||||
// Kunden-Reiter "Funktionen": Register der Funktionen je Projekt (News, Terminplaner), Aktionen je Zustand und
|
||||
// Kunden-Reiter "Funktionen": Register der Funktionen je Projekt (News, Terminplaner, Öffnungszeiten live), Aktionen je Zustand und
|
||||
// Einarbeiten der Portal-Antwort. Rein, unter Node testbar.
|
||||
|
||||
import { fmtYmd, fmtBerlin } from './format'
|
||||
@@ -24,6 +24,16 @@ export const FUNKTIONEN = [
|
||||
ticket: 'Fehlendes Einrichtungs-Ticket neu anlegen?',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'zeiten',
|
||||
label: 'Öffnungszeiten live',
|
||||
bestaetigen: {
|
||||
activate: 'Öffnungszeiten live ohne Zahlung aktivieren? Es startet der Einbau: Das Einbau-Ticket wird angelegt und die Seite, wenn möglich, neu gebaut. Freigeschaltet wird, sobald das Ticket geschlossen ist und der Kunde seine Zeiten eingetragen hat.',
|
||||
disable: 'Öffnungszeiten live abschalten? Leisten und Live-Anzeige verschwinden von der Website, die feste Ersatz-Tabelle bleibt stehen. Die Angaben des Kunden bleiben gespeichert. Weicht der Wochenplan von der Ersatz-Tabelle ab, wird ein Ticket „Ersatz-Tabelle aktualisieren“ angelegt.',
|
||||
abnahme: 'Öffnungszeiten live freischalten? Der Kunde bekommt die Mail „bereit“ und der Einbau gilt als abgenommen.',
|
||||
ticket: 'Fehlendes Einbau-Ticket neu anlegen?',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/** Zustand einer Funktion -> [Ton des Abzeichens, Beschriftung]. */
|
||||
@@ -58,12 +68,12 @@ export function abgeschaltetText(feature) {
|
||||
export const funktionOf = (project, key) => project?.features?.[key] || { state: 'off' }
|
||||
|
||||
/**
|
||||
* Site-Key (Schluessel fuer den Einbau auf der Kundenwebsite) einer Funktion: nur beim Terminplaner und nur, wenn das Portal
|
||||
* einen nennt (feature.siteKey); sonst ''.
|
||||
* Site-Key (Schluessel fuer den Einbau auf der Kundenwebsite) einer Funktion: nur beim Terminplaner und bei Oeffnungszeiten live
|
||||
* und nur, wenn das Portal einen nennt (feature.siteKey); sonst ''.
|
||||
*/
|
||||
export function siteKeyOf(feature, key) {
|
||||
const wert = feature?.siteKey
|
||||
return key === 'termine' && typeof wert === 'string' ? wert.trim() : ''
|
||||
return (key === 'termine' || key === 'zeiten') && typeof wert === 'string' ? wert.trim() : ''
|
||||
}
|
||||
|
||||
/** Beschriftung der Aktionsknoepfe (Aktion = action im POST /funktionen/:customerId/:key). */
|
||||
@@ -76,13 +86,20 @@ export const AKTION_LABEL = {
|
||||
|
||||
/**
|
||||
* Moegliche Aktionen je Zustand. News (und ohne key): Aktivieren (ohne Zahlung) ausser bei "active", Abschalten solange bestellt, in
|
||||
* Einrichtung oder aktiv. Terminplaner: "ordered" nur Abschalten (Aktivieren ohne Zahlung wuerde eine spaetere Zahlung im Portal
|
||||
* verschlucken), "setup" Freischalten nach der Abnahme (mit ticketMissing zuerst "Ticket nachlegen"), "active" nur Abschalten.
|
||||
* Einrichtung oder aktiv. Terminplaner und Oeffnungszeiten live (beide mit Ticket-Freischaltung): "ordered" nur Abschalten (Aktivieren
|
||||
* ohne Zahlung wuerde eine spaetere Zahlung im Portal verschlucken), "setup" Freischalten nach der Abnahme (mit ticketMissing zuerst
|
||||
* "Ticket nachlegen"), "active" nur Abschalten; sonst ("off", "disabled", ohne Zustand) Aktivieren. Fuer "disabled" gibt es kein
|
||||
* Abschalten, auch wenn das Ersatz-Ticket beim Abschalten fehlschlug (das Portal merkt sich das nicht; die Antwort nennt es nur einmal).
|
||||
* hindernis (Portal: feature.hindernis): beim Terminplaner bleibt die Abnahme sichtbar und das Portal sperrt sie mit Begruendung
|
||||
* (hindernisText); bei Oeffnungszeiten live entfaellt sie, solange der Kunde noch keine Zeiten eingetragen hat ('zeiten_fehlen').
|
||||
*/
|
||||
export function aktionen(state, key, { ticketMissing = false } = {}) {
|
||||
if (key === 'termine') {
|
||||
export function aktionen(state, key, { ticketMissing = false, hindernis = null } = {}) {
|
||||
if (key === 'termine' || key === 'zeiten') {
|
||||
if (state === 'ordered' || state === 'active') return ['disable']
|
||||
if (state === 'setup') return ticketMissing ? ['ticket', 'abnahme', 'disable'] : ['abnahme', 'disable']
|
||||
if (state === 'setup') {
|
||||
const abnahme = key === 'zeiten' && hindernis ? [] : ['abnahme']
|
||||
return [...(ticketMissing ? ['ticket'] : []), ...abnahme, 'disable']
|
||||
}
|
||||
return ['activate']
|
||||
}
|
||||
const list = []
|
||||
|
||||
@@ -11,6 +11,7 @@ const LABELS = {
|
||||
termin_einstellungen: 'Terminplaner-Einstellungen geändert',
|
||||
termin_aktion: 'Termin bearbeitet',
|
||||
termin_manuell: 'Termin eingetragen',
|
||||
zeiten_gespeichert: 'Öffnungszeiten gespeichert',
|
||||
}
|
||||
|
||||
/** Deutsche Beschriftung einer Aktion; unbekannte Aktionen erscheinen roh. */
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Oeffnungszeiten live im Kunden-Reiter "Funktionen": Statustext je Projekt, Pruefung des Navigations-Selektors (Team-Einstellung) und
|
||||
// Rumpf fuer PATCH /funktionen/:customerId/zeiten/team. Rein, unter Node testbar (keine Appwrite-Importe).
|
||||
|
||||
import { berlinYmd, fmtYmd } from './format'
|
||||
|
||||
const TEXT_EINBAU = 'Leisten live – Einbau läuft'
|
||||
const TEXT_WARTET = 'wartet auf die Zeiten des Kunden'
|
||||
/** Tage zwischen dem erkannten Abo-Ende (aboEndeAt) und dem Abschalten (Portal: zeiten/aboAbgleich.js, FRIST_TAGE). */
|
||||
const FRIST_TAGE = 30
|
||||
|
||||
/** Kalendertag 'JJJJ-MM-TT' plus n Tage ('JJJJ-MM-TT'); reine Kalenderrechnung ohne Uhrzeit, also ohne Zeitumstellung. */
|
||||
function plusTage(ymd, tage) {
|
||||
const [jahr, monat, tag] = ymd.split('-').map(Number)
|
||||
return new Date(Date.UTC(jahr, monat - 1, tag + tage)).toISOString().slice(0, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hinweis auf das Ende des Wartungs-Abos: die Funktion wird am Berliner Tag von aboEndeAt plus 30 Tage abgeschaltet, ausser der
|
||||
* Kunde behaelt sie. Mit unlesbarem Datum ohne Tag ("nach 30 Tagen"), nie mit einem erfundenen.
|
||||
*/
|
||||
function aboEndeText(aboEndeAt) {
|
||||
const lesbar = typeof aboEndeAt === 'string' && !Number.isNaN(Date.parse(aboEndeAt))
|
||||
const wann = lesbar ? `am ${fmtYmd(plusTage(berlinYmd(aboEndeAt), FRIST_TAGE))}` : `nach ${FRIST_TAGE} Tagen`
|
||||
return `Abo beendet – Abschaltung ${wann}, falls der Kunde die Funktion nicht behält`
|
||||
}
|
||||
|
||||
/**
|
||||
* Statustext unter dem Abzeichen (Portal: project.features.zeiten): in der Einrichtung "Leisten live – Einbau läuft" bzw. bei
|
||||
* hindernis 'zeiten_fehlen' "wartet auf die Zeiten des Kunden"; in Einrichtung und aktiv zusaetzlich der Abo-Ende-Hinweis mit Frist
|
||||
* (feature.aboEndeAt), durch " · " getrennt. In allen anderen Zustaenden '': Grund und Loeschdatum einer Abschaltung nennt
|
||||
* abgeschaltetText (funktionenView). Ohne Anrede, nie "24 Uhr".
|
||||
*/
|
||||
export function zeitenStatusText(feature) {
|
||||
const state = feature?.state
|
||||
if (state !== 'setup' && state !== 'active') return ''
|
||||
const teile = []
|
||||
if (state === 'setup') teile.push(feature.hindernis === 'zeiten_fehlen' ? TEXT_WARTET : TEXT_EINBAU)
|
||||
if (feature.aboEndeAt) teile.push(aboEndeText(feature.aboEndeAt))
|
||||
return teile.join(' · ')
|
||||
}
|
||||
|
||||
// --- Team-Einstellungen (nur ueber WOMS aenderbar) ---
|
||||
|
||||
/** Hoechstlaenge des Navigations-Selektors (Portal: ZEITEN_MAX.navSelektor). */
|
||||
export const NAV_SELEKTOR_MAX = 120
|
||||
/** Erlaubt sind A-Z a-z 0-9, Leerzeichen und # . - _ > : [ ] = " ' (also nie < { } ;); Spiegel von NAV_RE in zeiten/pruefen.js. */
|
||||
const NAV_RE = /^[A-Za-z0-9 #.\-_>:[\]="']+$/
|
||||
/** Meldung wie im Portal (normalisiereTeam), damit Formular und Server dieselbe Regel gleich benennen. */
|
||||
export const NAV_SELEKTOR_FEHLER = `Der Selektor darf höchstens ${NAV_SELEKTOR_MAX} Zeichen lang sein und nur A–Z, 0–9, Leerzeichen und # . - _ > : [ ] = " ' enthalten.`
|
||||
|
||||
/**
|
||||
* Prueft den Navigations-Selektor (wohin die Hinweis-Leiste der Website sich haengt) wie normalisiereTeam im Portal: nach dem Trimmen
|
||||
* hoechstens 120 Zeichen aus der erlaubten Menge; leer (auch nur Leerraum, null, undefined) heisst "kein Selektor" und ist gueltig; kein Text
|
||||
* ist ungueltig. => Fehlermeldung oder null.
|
||||
*/
|
||||
export function navSelektorFehler(text) {
|
||||
if (text === undefined || text === null) return null
|
||||
const s = typeof text === 'string' ? text.trim() : null
|
||||
if (s === null || s.length > NAV_SELEKTOR_MAX || (s !== '' && !NAV_RE.test(s))) return NAV_SELEKTOR_FEHLER
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Rumpf fuer PATCH /funktionen/:customerId/zeiten/team: { projectId, teamVersion, navSelektor, kontaktleisteAus, hinweisleisteAus }.
|
||||
* Selektor getrimmt, leer => null; die Schalter sind immer echte Wahrheitswerte (nur true gilt als an). Die Version (teamVersion aus
|
||||
* der Ansicht) und ein ungueltiger Selektor gehen unveraendert mit: das Portal prueft beides (409 ZEITEN_VERALTET, 400 mit fehler).
|
||||
*/
|
||||
export function teamBody({ projectId, teamVersion, navSelektor, kontaktleisteAus, hinweisleisteAus }) {
|
||||
const selektor = typeof navSelektor === 'string' ? navSelektor.trim() : navSelektor
|
||||
return {
|
||||
projectId,
|
||||
teamVersion,
|
||||
navSelektor: selektor === '' || selektor === undefined ? null : selektor,
|
||||
kontaktleisteAus: kontaktleisteAus === true,
|
||||
hinweisleisteAus: hinweisleisteAus === true,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user