Buchungskalender: Abschluss – S6 Belegung laedt nur Relevantes, bis-Indizes, 10-s-Speicher

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Opus 5.5 committed 2026-10-07 15:25:17 +00:00
1 parent ab838d07bc
commit 17400cac1f
7 files changed
+99 -17

No files matched your search

+19 -2
View File
@@ -7,7 +7,7 @@ import { belegteBereiche } from '../services/unterkunft/belegung.js'
import { istTag, naechte } from '../services/unterkunft/datum.js'
import { UkFehler } from '../services/unterkunft/fehler.js'
import { unterkunftPerSiteKey } from '../services/unterkunft/kontext.js'
import { ID_RE, findeRechnung, ladeAnfrage, ladeRechnung, ladeUnterkunft, listeUnterkuenfte } from '../services/unterkunft/speicher.js'
import { ID_RE, belegungsStand, findeRechnung, ladeAnfrage, ladeRechnung, ladeUnterkunft, listeUnterkuenfte } from '../services/unterkunft/speicher.js'
import { checkoutFuer, schliesseAbWennBezahlt } from '../services/unterkunft/zahlung.js'
import { formToken, ipHashVon, pruefeFormToken, tokenPasst } from '../services/unterkunft/token.js'
import { darfAnfrage, darfAnfrageSite, darfBuchungsversuch, zaehle } from '../services/termine/limits.js'
@@ -264,6 +264,12 @@ function verschmelze(bereiche) {
return aus
}
// Antwort von /belegung 10 s im Speicher je siteKey+unterkunft+von+bis; jedes Schreiben an Sperren/Anfragen (speicher.belegungsStand) macht sie ungueltig
const BELEGUNG_TTL_MS = 10 * 1000
const BELEGUNG_MAX = 2000
const belegungSpeicher = new Map()
export const leereBelegungSpeicherFuerTests = () => belegungSpeicher.clear()
router.get(
'/:siteKey/belegung',
route(async (req, now) => {
@@ -277,13 +283,24 @@ router.get(
throw ungueltig({ zeitraum: 'Ungültiger Zeitraum.' })
}
const tag = heute(now)
const nowMs = now instanceof Date ? now.getTime() : Number(now)
const speicherKey = `${req.params.siteKey}|${unterkunft}|${von}|${bis}`
const gemerkt = belegungSpeicher.get(speicherKey)
if (gemerkt && gemerkt.bis > nowMs && gemerkt.stand === belegungsStand() && gemerkt.tag === tag) return { body: gemerkt.body }
const stand = belegungsStand()
const bisMax = monateDazu(tag, config.unterkunft.horizontMonate)
const bisBis = bis > bisMax ? bisMax : bis // Fenster des Widgets (UTC-Datum des Browsers) darf nie an einem Tag Abweichung scheitern
const u = await ladeUnterkunft(unterkunft)
if (!u || u.featureId !== ctx.featureId || u.aktiv !== true) throw nichtGefunden()
const ab = von < tag ? tag : von
const belegt = ab < bisBis ? verschmelze(await belegteBereiche(u.$id, ab, bisBis, { now })) : []
return { body: { belegt, heute: tag, bisMax } }
const body = { belegt, heute: tag, bisMax }
if (belegungSpeicher.size >= BELEGUNG_MAX) {
for (const [k, e] of belegungSpeicher) if (e.bis <= nowMs) belegungSpeicher.delete(k)
if (belegungSpeicher.size >= BELEGUNG_MAX) belegungSpeicher.delete(belegungSpeicher.keys().next().value)
}
belegungSpeicher.set(speicherKey, { bis: nowMs + BELEGUNG_TTL_MS, stand, tag, body })
return { body }
}),
)
+3 -2
View File
@@ -15,10 +15,11 @@ export async function belegteBereiche(unterkunftId, von, bis, { ohneAnfrage, now
const jetzt = now instanceof Date ? now.getTime() : now
const fenster = { von, bis }
const roh = []
for (const s of await listeSperren(unterkunftId)) {
// nur was nach dem Fensteranfang endet (Index auf bis): alles andere kann das Fenster nicht beruehren
for (const s of await listeSperren(unterkunftId, { bisNach: von })) {
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'])) {
for (const a of await listeAnfragenNachStatus(unterkunftId, [...AKTIVE_STATUS, 'freigegeben'], { bisNach: von })) {
if (ohneAnfrage && a.$id === ohneAnfrage) continue
if (a.status === 'freigegeben') {
const frist = Date.parse(a.fristAt)
+3 -2
View File
@@ -29,7 +29,7 @@ export const UNTERKUNFT_COLLECTIONS = {
str('checkOut', 5),
int('sort'),
bool('aktiv'),
str('icalImporte', 4000),
str('icalImporte', 8000),
str('icalExportToken', 64),
],
indexes: [index('idx_feature', 'featureId'), index('idx_export_token', 'icalExportToken')],
@@ -60,7 +60,7 @@ export const UNTERKUNFT_COLLECTIONS = {
str('quelle', 60),
str('notiz', 200),
],
indexes: [index('idx_unterkunft', 'unterkunftId'), index('idx_unterkunft_quelle', 'unterkunftId', 'quelle')],
indexes: [index('idx_unterkunft', 'unterkunftId'), index('idx_unterkunft_quelle', 'unterkunftId', 'quelle'), index('idx_bis', 'bis')],
},
ukAnfragen: {
attributes: [
@@ -103,6 +103,7 @@ export const UNTERKUNFT_COLLECTIONS = {
index('idx_unterkunft', 'unterkunftId'),
index('idx_unterkunft_status', 'unterkunftId', 'status'),
index('idx_status', 'status'),
index('idx_bis', 'bis'),
],
},
ukRechnungen: {
+32 -7
View File
@@ -106,15 +106,32 @@ export async function loeschePreise(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
export const listeSperren = (unterkunftId) => liste('ukSperren', [Query.equal('unterkunftId', unterkunftId)])
/** 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) {
return lesen('ukSperren', await createDocument(config.collections.ukSperren, schreiben('ukSperren', felder)))
try {
return lesen('ukSperren', await createDocument(config.collections.ukSperren, schreiben('ukSperren', felder)))
} finally {
belegungStand++
}
}
export const loescheSperre = (id) => deleteDocument(config.collections.ukSperren, id)
export async function loescheSperre(id) {
try {
return await deleteDocument(config.collections.ukSperren, id)
} finally {
belegungStand++
}
}
export const ladeSperre = (id) => ladeDok('ukSperren', id)
// ---- ukAnfragen
@@ -122,11 +139,19 @@ export const ladeSperre = (id) => ladeDok('ukSperren', id)
export const ladeAnfrage = (id) => ladeDok('ukAnfragen', id)
export async function legeAnfrageDokAn(felder) {
return lesen('ukAnfragen', await createDocument(config.collections.ukAnfragen, schreiben('ukAnfragen', felder)))
try {
return lesen('ukAnfragen', await createDocument(config.collections.ukAnfragen, schreiben('ukAnfragen', felder)))
} finally {
belegungStand++
}
}
export async function aktualisiereAnfrage(id, felder) {
return lesen('ukAnfragen', await updateDocument(config.collections.ukAnfragen, id, schreiben('ukAnfragen', felder)))
try {
return lesen('ukAnfragen', await updateDocument(config.collections.ukAnfragen, id, schreiben('ukAnfragen', felder)))
} finally {
belegungStand++
}
}
export function listeAnfragen(featureId, { status, unterkunftId } = {}) {
@@ -137,8 +162,8 @@ export function listeAnfragen(featureId, { status, unterkunftId } = {}) {
}
/** 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)])
export const listeAnfragenNachStatus = (unterkunftId, status, { bisNach } = {}) =>
liste('ukAnfragen', [Query.equal('unterkunftId', unterkunftId), Query.equal('status', status), ...(bisNach ? [Query.greaterThan('bis', bisNach)] : [])])
// ---- ukRechnungen