134 lines
5.1 KiB
JavaScript
134 lines
5.1 KiB
JavaScript
/**
|
|
* Terminplaner: Appwrite-Schema der vier Collections (Spezifikation 2026-10-02-terminplaner-design.md, Abschnitt 2).
|
|
* Reine Daten, absichtlich ohne Import von server/config.js: test/support/schema.js liest sie vor useTestEnv(),
|
|
* ein frueh geladenes config.js wuerde den Endpunkt des Appwrite-Nachbaus verfehlen. Die Collection-IDs stehen in
|
|
* config.collections.<Name>; scripts/termine-setup.mjs legt alles an.
|
|
*
|
|
* Attribut: { key, type: 'string' | 'integer' | 'boolean' | 'datetime', size (nur Text), required: false }
|
|
* Index: { key, type: 'key', attributes: [...] }
|
|
* Datumswerte sind UTC (toISOString()); Appwrite liefert sie als "+00:00" zurueck, daher immer mit new Date() lesen.
|
|
*/
|
|
|
|
const str = (key, size) => ({ key, type: 'string', size, required: false })
|
|
const int = (key) => ({ key, type: 'integer', required: false })
|
|
const bool = (key) => ({ key, type: 'boolean', required: false })
|
|
const dt = (key) => ({ key, type: 'datetime', required: false })
|
|
const index = (key, ...attributes) => ({ key, type: 'key', attributes })
|
|
|
|
export const TERMINE_COLLECTIONS = {
|
|
// Eine Einstellung je Funktion; Dokument-ID = ID des portalFeatures-Dokuments. Das Feld heisst einstellung,
|
|
// nicht data: appwriteAdmin.formatRequestBody haelt Bodies mit data fuer fertig verpackt.
|
|
termineConfig: {
|
|
attributes: [
|
|
str('customerId', 64),
|
|
str('projectId', 64),
|
|
str('einstellung', 200000),
|
|
int('version'),
|
|
dt('updatedAt'),
|
|
str('updatedBy', 100),
|
|
// TP2: Bestell-Fragebogen als JSON-Text. Der Inhalt ist auf 16000 Zeichen begrenzt (FRAGEBOGEN_MAX in
|
|
// termine/fragebogen.js); die groessere Spalte wird in Appwrite eine Textspalte und zaehlt nicht zur Zeilenbreite.
|
|
str('fragebogen', 20000),
|
|
],
|
|
indexes: [],
|
|
},
|
|
|
|
// Eine Buchung je Dokument (ID randomUUID)
|
|
termineBookings: {
|
|
attributes: [
|
|
str('featureId', 64),
|
|
str('customerId', 64),
|
|
str('projectId', 64),
|
|
str('leistungId', 64),
|
|
str('leistungName', 120),
|
|
str('ressourceId', 64),
|
|
str('ressourceName', 80),
|
|
dt('startsAt'),
|
|
dt('endsAt'),
|
|
dt('blockEndsAt'),
|
|
str('status', 16),
|
|
str('quelle', 16),
|
|
str('gastName', 100),
|
|
str('gastEmail', 254),
|
|
str('gastTelefon', 40),
|
|
str('gastNachricht', 500),
|
|
str('gastZusatz', 2000),
|
|
bool('erinnerung'),
|
|
dt('erinnerungGesendetAt'),
|
|
dt('heldUntil'),
|
|
str('verifyHash', 64),
|
|
str('manageHash', 64),
|
|
dt('bestaetigtAt'),
|
|
dt('angenommenAt'),
|
|
dt('abgesagtAt'),
|
|
str('abgesagtVon', 16),
|
|
str('absageGrund', 300),
|
|
dt('widerrufenAt'),
|
|
str('nachweis', 4000),
|
|
int('preisCent'),
|
|
str('preisArt', 8),
|
|
str('buchungsart', 8),
|
|
bool('verbraucher'),
|
|
str('zahlart', 8),
|
|
str('zahlStatus', 16),
|
|
str('stripeSessionId', 128),
|
|
str('stripePaymentIntent', 64),
|
|
int('icsSequenz'),
|
|
dt('createdAt'),
|
|
dt('updatedAt'),
|
|
],
|
|
indexes: [
|
|
index('idx_feature_start', 'featureId', 'startsAt'),
|
|
index('idx_status_held', 'status', 'heldUntil'),
|
|
index('idx_feature_email', 'featureId', 'gastEmail'),
|
|
index('idx_verify', 'verifyHash'),
|
|
index('idx_manage', 'manageHash'),
|
|
index('idx_status_start', 'status', 'startsAt'),
|
|
// Aufbewahrungs-Loeschung (Tagesjob): Status + Bezugszeit, verfallen laeuft ueber idx_status_held
|
|
index('idx_status_ends', 'status', 'endsAt'),
|
|
index('idx_status_updated', 'status', 'updatedAt'),
|
|
],
|
|
},
|
|
|
|
// Belegungszellen gegen Doppelbuchung; Dokument-ID c_ + 34 Hex von sha1(featureId|ressourceId|zelleIso)
|
|
termineClaims: {
|
|
attributes: [str('featureId', 64), str('ressourceId', 64), str('bookingId', 64), dt('zelle'), dt('bis')],
|
|
indexes: [index('idx_booking', 'bookingId'), index('idx_bis', 'bis')],
|
|
},
|
|
|
|
// Mail-Warteschlange (einziger Sendepfad); enthaelt personenbezogene Daten, daher kurze Aufbewahrung
|
|
termineOutbox: {
|
|
attributes: [
|
|
str('featureId', 64),
|
|
str('bookingId', 64),
|
|
str('art', 16),
|
|
str('an', 254),
|
|
str('fromName', 120),
|
|
str('replyTo', 254),
|
|
str('subject', 300),
|
|
str('html', 100000),
|
|
str('text', 50000),
|
|
str('ics', 20000),
|
|
str('icsMethod', 8),
|
|
// optional: Verweis auf einen Anhang (Buchungskalender: Rechnungs-PDF, Task 20); wird nur gespeichert, das Senden bleibt unveraendert
|
|
str('anhang', 500),
|
|
str('status', 16),
|
|
int('versuche'),
|
|
dt('naechsterVersuch'),
|
|
dt('gesendetAt'),
|
|
str('fehler', 300),
|
|
dt('createdAt'),
|
|
],
|
|
// idx_status_next: Minutenjob und Aufraeumen; idx_booking: zustellFehler und verwerfeWartende fragen nach der Buchung
|
|
indexes: [index('idx_status_next', 'status', 'naechsterVersuch'), index('idx_booking', 'bookingId')],
|
|
},
|
|
}
|
|
|
|
/** Datumsfelder je Collection, damit der Appwrite-Nachbau der Tests sie wie Appwrite formatieren kann. */
|
|
export const TERMINE_DATETIMES = Object.fromEntries(
|
|
Object.entries(TERMINE_COLLECTIONS).map(([name, { attributes }]) => [
|
|
name,
|
|
attributes.filter((attribute) => attribute.type === 'datetime').map((attribute) => attribute.key),
|
|
]),
|
|
)
|