import { config } from '../../config.js' import { Query, createDocument, deleteDocument, getDocument, listAllDocuments, updateDocument } from '../appwriteAdmin.js' /** * Buchungskalender: Lesen und Schreiben der sechs Collections (Schema: schema.js). JSON-Felder liegen als Text in Appwrite; * hier werden sie beim Lesen geparst (kaputter Text => null) und beim Schreiben serialisiert. Rueckgaben sind flache Objekte * mit $id. Beim Schreiben werden $-Felder aus der Eingabe verworfen, undefined-Felder uebersprungen. */ const JSON_FELDER = { ukUnterkuenfte: ['icalImporte'], ukPreise: ['staffeln'], ukSperren: [], ukAnfragen: ['gast', 'rechnungsadresse', 'posten', 'summen'], ukRechnungen: ['inhalt'], ukEinstellungen: ['rechnungZaehler', 'stripeStatus', 'anfrage'], } function lesen(name, doc) { if (!doc) return null const out = { ...doc } for (const f of JSON_FELDER[name]) { if (typeof out[f] !== 'string') continue try { out[f] = JSON.parse(out[f]) } catch { out[f] = null } } return out } function schreiben(name, felder) { const out = {} for (const [k, v] of Object.entries(felder ?? {})) { if (k.startsWith('$') || v === undefined) continue out[k] = JSON_FELDER[name].includes(k) && v !== null && typeof v !== 'string' ? JSON.stringify(v) : v } return out } const nichtGefunden = (err) => err?.status === 404 && err.type !== 'collection_not_found' && err.type !== 'database_not_found' /** Dokument-IDs landen roh im URL-Pfad: nur harmlose Zeichen durchlassen. */ export const ID_RE = /^[A-Za-z0-9_-]{1,36}$/ async function ladeDok(name, id) { if (typeof id !== 'string' || !ID_RE.test(id)) return null try { return lesen(name, await getDocument(config.collections[name], id)) } catch (err) { if (nichtGefunden(err)) return null throw err } } const liste = async (name, queries) => (await listAllDocuments(config.collections[name], queries)).map((d) => lesen(name, d)) // ---- ukEinstellungen (Dokument-ID = featureId) export const ladeEinstellungen = (featureId) => ladeDok('ukEinstellungen', featureId) export async function speichereEinstellungen(featureId, felder) { const daten = schreiben('ukEinstellungen', felder) if (await ladeEinstellungen(featureId)) { return lesen('ukEinstellungen', await updateDocument(config.collections.ukEinstellungen, featureId, daten)) } return lesen('ukEinstellungen', await createDocument(config.collections.ukEinstellungen, { ...daten, featureId }, featureId)) } // ---- ukUnterkuenfte export async function listeUnterkuenfte(featureId, { nurAktive = false } = {}) { const alle = await liste('ukUnterkuenfte', [Query.equal('featureId', featureId)]) return alle.filter((u) => !nurAktive || u.aktiv === true).sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0) || String(a.$id).localeCompare(String(b.$id))) } export const ladeUnterkunft = (id) => ladeDok('ukUnterkuenfte', id) export async function speichereUnterkunft(featureId, felder, id) { const daten = schreiben('ukUnterkuenfte', felder) if (id) return lesen('ukUnterkuenfte', await updateDocument(config.collections.ukUnterkuenfte, id, daten)) return lesen('ukUnterkuenfte', await createDocument(config.collections.ukUnterkuenfte, { ...daten, featureId })) } export const loescheUnterkunft = (id) => deleteDocument(config.collections.ukUnterkuenfte, id) // ---- ukPreise (1:1 je Unterkunft) async function preiseDok(unterkunftId) { const alle = await liste('ukPreise', [Query.equal('unterkunftId', unterkunftId)]) return alle[0] ?? null } export const ladePreise = (unterkunftId) => preiseDok(unterkunftId) export async function speicherePreise(unterkunftId, featureId, felder) { const daten = schreiben('ukPreise', felder) const vorhanden = await preiseDok(unterkunftId) if (vorhanden) return lesen('ukPreise', await updateDocument(config.collections.ukPreise, vorhanden.$id, daten)) return lesen('ukPreise', await createDocument(config.collections.ukPreise, { ...daten, unterkunftId, featureId })) } export async function loeschePreise(unterkunftId) { const vorhanden = await preiseDok(unterkunftId) if (vorhanden) await deleteDocument(config.collections.ukPreise, vorhanden.$id) } // ---- Belegungsstand: wird mit jedem Schreiben an Sperren oder Anfragen erhoeht (der Speicher der oeffentlichen /belegung-Antwort gilt nur bis dahin) let belegungStand = 0 export const belegungsStand = () => belegungStand // ---- ukSperren /** bisNach: nur Sperren, die nach diesem Tag enden (bis > bisNach); die uebrigen koennen kein Fenster ab diesem Tag beruehren. */ export const listeSperren = (unterkunftId, { bisNach } = {}) => liste('ukSperren', [Query.equal('unterkunftId', unterkunftId), ...(bisNach ? [Query.greaterThan('bis', bisNach)] : [])]) export async function legeSperreAn(felder) { try { return lesen('ukSperren', await createDocument(config.collections.ukSperren, schreiben('ukSperren', felder))) } finally { belegungStand++ } } export async function loescheSperre(id) { try { return await deleteDocument(config.collections.ukSperren, id) } finally { belegungStand++ } } export const ladeSperre = (id) => ladeDok('ukSperren', id) // ---- ukAnfragen export const ladeAnfrage = (id) => ladeDok('ukAnfragen', id) export async function legeAnfrageDokAn(felder) { try { return lesen('ukAnfragen', await createDocument(config.collections.ukAnfragen, schreiben('ukAnfragen', felder))) } finally { belegungStand++ } } export async function aktualisiereAnfrage(id, felder) { try { return lesen('ukAnfragen', await updateDocument(config.collections.ukAnfragen, id, schreiben('ukAnfragen', felder))) } finally { belegungStand++ } } export function listeAnfragen(featureId, { status, unterkunftId } = {}) { const queries = [Query.equal('featureId', featureId)] if (unterkunftId) queries.push(Query.equal('unterkunftId', unterkunftId)) if (status?.length) queries.push(Query.equal('status', status)) return liste('ukAnfragen', queries) } /** Fuer belegung.js: Anfragen einer Unterkunft mit einem der Status. */ export const listeAnfragenNachStatus = (unterkunftId, status, { bisNach } = {}) => liste('ukAnfragen', [Query.equal('unterkunftId', unterkunftId), Query.equal('status', status), ...(bisNach ? [Query.greaterThan('bis', bisNach)] : [])]) // ---- ukRechnungen export const ladeRechnung = (id) => ladeDok('ukRechnungen', id) export async function legeRechnungAn(felder) { return lesen('ukRechnungen', await createDocument(config.collections.ukRechnungen, schreiben('ukRechnungen', felder))) } export async function listeRechnungen(featureId) { const alle = await liste('ukRechnungen', [Query.equal('featureId', featureId)]) return alle.sort((a, b) => String(a.nummer).localeCompare(String(b.nummer))) } /** Alle Anfragen (aller Kunden) mit einem Status; fuer den Fristen-Job. */ export const listeAnfragenAlle = (status) => liste('ukAnfragen', [Query.equal('status', status)]) /** Vorhandene Rechnung einer Anfrage zu einer Art ('rechnung' | 'storno'); fuer idempotente Wiederholung nach Teilfehlern. */ export async function findeRechnung(anfrageId, art) { const alle = await liste('ukRechnungen', [Query.equal('anfrageId', anfrageId)]) return alle.filter((r) => r.art === art).sort((a, b) => String(a.nummer).localeCompare(String(b.nummer)))[0] ?? null }