Öffnungszeiten: Einschalten im Wartungs-Abo
POST /api/funktionen/zeiten/einschalten (Gratisweg, Spec 3.2): Abo frisch prüfen, Zustimmung (consentFrei) und Nachweis ohne Stripe-Sitzung, Zustand setup mit unlockedBy abo, dann Neubau, Einbau-Ticket und K24; ein zweiter Klick holt Fehlendes nach (holeZeitenNach, eigene Sperre). GET /api/funktionen liefert das Angebot (zeiten), features.zeiten mit unlockedBy, aboEndeAt, einbau, loeschungAm sowie zeitenStatus und zeitenLeer je Projekt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d670ca5dc
commit
66793d06d5
9 files changed
+1124
-58
No files matched your search
@@ -71,7 +71,11 @@ export async function getFeatureDoc(customerId, projectId, key) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertOwnProject(customerId, projectId) {
|
||||
/**
|
||||
* Das eigene Projekt des Kunden (sonst 403). forNewOrder: fuer alles, was eine Funktion neu einschaltet oder bestellt; ein archiviertes
|
||||
* Projekt ist dann 409 (Spec 11: eine laufende Funktion laeuft weiter, aber Neubestellung ist gesperrt).
|
||||
*/
|
||||
export async function assertOwnProject(customerId, projectId, { forNewOrder = false } = {}) {
|
||||
let project = null
|
||||
try {
|
||||
project = await getDocument(config.collections.websiteProjects, projectId)
|
||||
@@ -79,6 +83,7 @@ export async function assertOwnProject(customerId, projectId) {
|
||||
if (err.status !== 404) throw err
|
||||
}
|
||||
if (!project || !customerId || project.customerId !== customerId) throw httpError(403, 'Kein Zugriff auf dieses Projekt')
|
||||
if (forNewOrder && project.archived === true) throw httpError(409, 'Dieses Projekt ist archiviert. Dafür kann keine Funktion neu eingeschaltet werden.')
|
||||
return project
|
||||
}
|
||||
|
||||
@@ -109,7 +114,8 @@ export async function ensureFeatureDoc(customerId, projectId, key) {
|
||||
}
|
||||
}
|
||||
|
||||
async function ticketWoid(ticketId) {
|
||||
/** Ticketnummer (woid) zur Ticket-ID; ein unbekanntes Ticket ergibt null. */
|
||||
export async function ticketWoid(ticketId) {
|
||||
if (!ticketId) return null
|
||||
try {
|
||||
const ticket = await getDocument(config.collections.workorders, ticketId)
|
||||
@@ -120,16 +126,28 @@ async function ticketWoid(ticketId) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wodurch die Funktion freigeschaltet wurde: 'kauf' | 'abo' | 'team'. Altbestand ohne unlockedBy: unlockedByPurchase => 'kauf', sonst
|
||||
* 'team' (Kulanz, Pilot, Team-Aktivierung).
|
||||
*/
|
||||
export const unlockedByVon = (doc) => doc?.unlockedBy || (doc?.unlockedByPurchase ? 'kauf' : 'team')
|
||||
|
||||
/**
|
||||
* Ansicht einer Funktion fuer Kunde und Team. Abschaltgrund (disabledReason, ohne Grund = Altbestand => null), Zeitpunkt
|
||||
* (disabledAt) und Berliner Loeschtag (loeschungAm, 30 Tage danach, termine/frist.js) gibt es nur im Zustand `disabled`;
|
||||
* in jedem anderen Zustand sind alle drei null, auch wenn am Dokument noch Reste einer frueheren Abschaltung stehen.
|
||||
* loeschungAm gibt es nur fuer den Terminplaner (R105: der 30-Tage-Loeschlauf in jobs.js loescht nur Terminplaner-Daten,
|
||||
* bei News verspraeche das Datum eine Loeschung, die nie kommt) und ist null, wenn disabledAt fehlt oder nicht lesbar ist.
|
||||
* bei News verspraeche das Datum eine Loeschung, die nie kommt) und fuer Oeffnungszeiten live, dort nur beim Abschaltgrund
|
||||
* abo_ende (bei einer Abschaltung durch das Team gibt es keine Loeschfrist); es ist null, wenn disabledAt fehlt oder nicht lesbar ist.
|
||||
* disabledReason und disabledAt gelten fuer jede Funktion.
|
||||
* Funktionen mit aboInklusive (Oeffnungszeiten live) tragen zusaetzlich unlockedBy (kauf | abo | team, null solange die Funktion
|
||||
* aus ist), aboEndeAt (Ende des Wartungs-Abos erkannt) und einbau (angestossen | manuell, Ergebnis des Neubaus).
|
||||
* key: Funktionsschluessel (auch ohne Dokument); ohne Angabe der des Dokuments.
|
||||
*/
|
||||
async function featureView(doc) {
|
||||
export async function featureView(doc, key = doc?.featureKey) {
|
||||
const abgeschaltet = doc?.state === 'disabled'
|
||||
const mitAbo = featureConfig(key)?.aboInklusive === true
|
||||
const loeschtag = abgeschaltet && (key === 'termine' || (mitAbo && doc.disabledReason === 'abo_ende'))
|
||||
return {
|
||||
state: doc?.state || 'off',
|
||||
siteKey: doc?.siteKey || null,
|
||||
@@ -139,7 +157,14 @@ async function featureView(doc) {
|
||||
invoiceUrl: doc?.invoiceUrl || null,
|
||||
disabledReason: abgeschaltet ? doc.disabledReason || null : null,
|
||||
disabledAt: abgeschaltet ? doc.disabledAt || null : null,
|
||||
loeschungAm: abgeschaltet && doc.featureKey === 'termine' ? loeschTag(doc.disabledAt) : null,
|
||||
loeschungAm: loeschtag ? loeschTag(doc.disabledAt) : null,
|
||||
...(mitAbo
|
||||
? {
|
||||
unlockedBy: doc && doc.state !== 'off' ? unlockedByVon(doc) : null,
|
||||
aboEndeAt: doc?.aboEndeAt || null,
|
||||
einbau: doc?.einbau || null,
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +186,7 @@ export async function listCustomerFeatures(customerId, { docs: geladen } = {}) {
|
||||
const features = {}
|
||||
for (const key of FEATURE_KEYS) {
|
||||
const doc = docs.find((d) => d.projectId === p.$id && d.featureKey === key)
|
||||
features[key] = await featureView(doc)
|
||||
features[key] = await featureView(doc, key)
|
||||
}
|
||||
result.push({
|
||||
projectId: p.$id,
|
||||
|
||||
Reference in new issue
Block a user