Funktionen: Terminplaner als zweite Funktion (Team-Route, Kundenansicht)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:43 +00:00
1 parent ae1886c030
commit 9e8fd33713
12 files changed
+434 -33

No files matched your search

+44 -9
View File
@@ -6,7 +6,12 @@ import { sendAdminNotice, sendCustomerMail } from './mailer.js'
import { getStripe, retrieveCheckoutSession } from './stripeBilling.js'
import { createFeatureTicket } from './ticketAdmin.js'
export const FEATURE_KEYS = ['news']
export const FEATURE_KEYS = ['news', 'termine']
/** Bezeichnung (name) und Verneinung im Satz "Für dieses Projekt gibt es noch {keine}" (keine) je Funktion. */
export const FEATURE_LABELS = {
news: { name: 'News-Pinnwand', keine: 'keine News-Pinnwand' },
termine: { name: 'Terminplaner', keine: 'keinen Terminplaner' },
}
export const STATES = ['off', 'ordered', 'setup', 'active', 'disabled']
export const DISABLED_MESSAGE =
@@ -119,16 +124,19 @@ async function featureView(doc) {
}
}
export async function listCustomerFeatures(customerId) {
/** Alle Funktionsdokumente eines Kunden (alle Projekte, alle Funktionen). */
export function listCustomerFeatureDocs(customerId) {
return listDocuments(config.collections.portalFeatures, [Query.equal('customerId', customerId), Query.limit(1000)])
}
/** docs: bereits geladene Funktionsdokumente des Kunden (listCustomerFeatureDocs), damit sie nicht doppelt gelesen werden. */
export async function listCustomerFeatures(customerId, { docs: geladen } = {}) {
const projects = await listDocuments(config.collections.websiteProjects, [
Query.equal('customerId', customerId),
Query.orderDesc('$createdAt'),
Query.limit(1000),
])
const docs = await listDocuments(config.collections.portalFeatures, [
Query.equal('customerId', customerId),
Query.limit(1000),
])
const docs = geladen ?? (await listCustomerFeatureDocs(customerId))
const result = []
for (const p of projects) {
const features = {}
@@ -287,6 +295,8 @@ export async function activate(docId, { by, adminNote } = {}) {
return updateDocument(config.collections.portalFeatures, docId, {
...stateFields('active'),
activatedAt: new Date().toISOString(),
// Eine wieder eingeschaltete Funktion darf nie in den 30-Tage-Loeschlauf des Terminplaners fallen (disabledAt-Marker)
disabledAt: null,
...(note ? { adminNote: note } : {}),
})
}
@@ -302,6 +312,30 @@ export async function disable(docId, { by, adminNote } = {}) {
return doc
}
/**
* Alle noch nicht abgeschalteten Dokumente einer Funktion eines Kunden abschalten (Kunde wird geloescht). Schon abgeschaltete
* behalten ihr disabledAt (Beginn der Loeschfrist), 'off' hat nichts zu sperren. Scheitert eines, werden die uebrigen trotzdem
* abgeschaltet und danach der erste Fehler weitergereicht.
* @returns {Promise<object[]>} die abgeschalteten Dokumente
*/
export async function disableCustomerFeatures(customerId, key, { by, adminNote } = {}) {
assertKey(key)
const docs = (await listCustomerFeatureDocs(customerId)).filter(
(d) => d.featureKey === key && d.state !== 'off' && d.state !== 'disabled'
)
const disabled = []
let failure = null
for (const doc of docs) {
try {
disabled.push(await disable(doc.$id, { by, adminNote }))
} catch (err) {
failure ||= err
}
}
if (failure) throw failure
return disabled
}
// Analyse gesperrt (auch Loeschfrist): Sammeldienst abschalten, Neubau und Entfernen-Ticket. Best effort, Zustand ist schon gespeichert.
// Dynamischer Import: analyse.js importiert dieses Modul.
async function stopAnalyseCollecting(doc) {
@@ -352,11 +386,12 @@ export async function activateClosedTickets({ now = new Date(), customerId = nul
return activated
}
export async function findActiveBySiteKey(siteKey) {
if (!/^[a-z2-7]{16}$/.test(String(siteKey || ''))) return null
/** Aktive Funktion zu einem siteKey; ohne featureKey 'news' (Pinnwand), unbekannte Funktion oder siteKey => null. */
export async function findActiveBySiteKey(siteKey, featureKey = 'news') {
if (!/^[a-z2-7]{16}$/.test(String(siteKey || '')) || !FEATURE_KEYS.includes(featureKey)) return null
const [doc] = await listDocuments(config.collections.portalFeatures, [
Query.equal('siteKey', siteKey),
Query.equal('featureKey', 'news'),
Query.equal('featureKey', featureKey),
Query.equal('state', 'active'),
Query.limit(1),
])