Buchungskalender: Speicher und Belegung
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c38c8e7c13
commit
bdf191d945
3 files changed
+330
No files matched your search
@@ -0,0 +1,39 @@
|
||||
import { listeAnfragenNachStatus, listeSperren } from './speicher.js'
|
||||
|
||||
/** Status, die die Unterkunft endgueltig belegen. 'freigegeben' blockiert nur bis fristAt. */
|
||||
export const AKTIVE_STATUS = ['gebucht', 'bezahlt']
|
||||
|
||||
/** Halboffene Intervalle [von,bis) auf Tagesstrings: Abreisetag gleich Anreisetag ueberschneidet nicht. */
|
||||
export const ueberschneidet = (a, b) => a.von < b.bis && b.von < a.bis
|
||||
|
||||
/**
|
||||
* Belegte Bereiche einer Unterkunft im Fenster [von,bis), auf das Fenster zugeschnitten, nach von sortiert.
|
||||
* Quellen: manuell, ical (Sperre mit quelle 'ical:<id>'), gebucht (gebucht|bezahlt), reserviert (freigegeben mit fristAt in der Zukunft).
|
||||
* ohneAnfrage: ID einer Anfrage, die nicht zaehlt (z. B. beim Freigeben der Anfrage selbst). now: Date oder ms.
|
||||
*/
|
||||
export async function belegteBereiche(unterkunftId, von, bis, { ohneAnfrage, now = Date.now() } = {}) {
|
||||
const jetzt = now instanceof Date ? now.getTime() : now
|
||||
const fenster = { von, bis }
|
||||
const roh = []
|
||||
for (const s of await listeSperren(unterkunftId)) {
|
||||
roh.push({ von: s.von, bis: s.bis, quelle: String(s.quelle ?? '').startsWith('ical') ? 'ical' : 'manuell' })
|
||||
}
|
||||
for (const a of await listeAnfragenNachStatus(unterkunftId, [...AKTIVE_STATUS, 'freigegeben'])) {
|
||||
if (ohneAnfrage && a.$id === ohneAnfrage) continue
|
||||
if (a.status === 'freigegeben') {
|
||||
const frist = Date.parse(a.fristAt)
|
||||
if (!(frist > jetzt)) continue
|
||||
roh.push({ von: a.von, bis: a.bis, quelle: 'reserviert' })
|
||||
} else {
|
||||
roh.push({ von: a.von, bis: a.bis, quelle: 'gebucht' })
|
||||
}
|
||||
}
|
||||
return roh
|
||||
.filter((r) => r.von && r.bis && ueberschneidet(r, fenster))
|
||||
.map((r) => ({ von: r.von > von ? r.von : von, bis: r.bis < bis ? r.bis : bis, quelle: r.quelle }))
|
||||
.sort((a, b) => (a.von < b.von ? -1 : a.von > b.von ? 1 : 0))
|
||||
}
|
||||
|
||||
export async function istFrei(unterkunftId, von, bis, opts = {}) {
|
||||
return (await belegteBereiche(unterkunftId, von, bis, opts)).length === 0
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
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'],
|
||||
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'
|
||||
|
||||
async function ladeDok(name, id) {
|
||||
if (!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 }))
|
||||
}
|
||||
|
||||
// ---- 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 }))
|
||||
}
|
||||
|
||||
// ---- ukSperren
|
||||
|
||||
export const listeSperren = (unterkunftId) => liste('ukSperren', [Query.equal('unterkunftId', unterkunftId)])
|
||||
|
||||
export async function legeSperreAn(felder) {
|
||||
return lesen('ukSperren', await createDocument(config.collections.ukSperren, schreiben('ukSperren', felder)))
|
||||
}
|
||||
|
||||
export const loescheSperre = (id) => deleteDocument(config.collections.ukSperren, id)
|
||||
|
||||
// ---- ukAnfragen
|
||||
|
||||
export const ladeAnfrage = (id) => ladeDok('ukAnfragen', id)
|
||||
|
||||
export async function legeAnfrageDokAn(felder) {
|
||||
return lesen('ukAnfragen', await createDocument(config.collections.ukAnfragen, schreiben('ukAnfragen', felder)))
|
||||
}
|
||||
|
||||
export async function aktualisiereAnfrage(id, felder) {
|
||||
return lesen('ukAnfragen', await updateDocument(config.collections.ukAnfragen, id, schreiben('ukAnfragen', felder)))
|
||||
}
|
||||
|
||||
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) =>
|
||||
liste('ukAnfragen', [Query.equal('unterkunftId', unterkunftId), Query.equal('status', status)])
|
||||
Reference in new issue
Block a user