Entferne Slots-Tab und Slot-RPCs; bereinige recurring-availability; Texte angepasst

This commit is contained in:
2025-10-05 17:21:56 +02:00
parent 6cf657168b
commit 6d7e8eceba
5 changed files with 8 additions and 835 deletions

View File

@@ -30,14 +30,12 @@ export type TimeOffPeriod = z.output<typeof TimeOffPeriodSchema>;
const recurringRulesKV = createKV<RecurringRule>("recurringRules");
const timeOffPeriodsKV = createKV<TimeOffPeriod>("timeOffPeriods");
// Import existing availability KV
import { router as availabilityRouter } from "./availability.js";
// Import bookings and treatments KV stores for getAvailableTimes endpoint
const bookingsKV = createKV<any>("bookings");
const treatmentsKV = createKV<any>("treatments");
// Owner-Authentifizierung (kopiert aus availability.ts)
// Owner-Authentifizierung
type Session = { id: string; userId: string; expiresAt: string; createdAt: string };
type User = { id: string; username: string; email: string; passwordHash: string; role: "customer" | "owner"; createdAt: string };
const sessionsKV = createKV<Session>("sessions");
@@ -236,23 +234,8 @@ const createTimeOff = os
createdAt: new Date().toISOString(),
};
// Blockiere bestehende Slots in diesem Zeitraum
const existingSlots = await call(availabilityRouter.peekAll, {}, {});
let blockedCount = 0;
for (const slot of existingSlots) {
if (slot.date >= input.startDate && slot.date <= input.endDate && slot.status === "free") {
await call(availabilityRouter.remove, { sessionId: input.sessionId, id: slot.id }, {});
blockedCount++;
}
}
if (blockedCount > 0) {
console.log(`Blocked ${blockedCount} existing slots for time-off period: ${input.reason}`);
}
await timeOffPeriodsKV.setItem(id, timeOff);
return { ...timeOff, blockedSlots: blockedCount };
return timeOff;
} catch (err) {
console.error("recurring-availability.createTimeOff error", err);
throw err;
@@ -294,153 +277,6 @@ const adminListTimeOff = os
return allTimeOff.sort((a, b) => a.startDate.localeCompare(b.startDate));
});
// Slot-Generator-Endpoint
// DEPRECATED: This endpoint will be removed in a future version.
// The system is transitioning to dynamic availability calculation with 15-minute intervals.
// Slots are no longer pre-generated based on recurring rules.
const generateSlots = os
.input(
z.object({
sessionId: z.string(),
startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
overwriteExisting: z.boolean().default(false),
})
)
.handler(async ({ input }) => {
try {
await assertOwner(input.sessionId);
// Validierung: startDate <= endDate
if (input.startDate > input.endDate) {
throw new Error("Startdatum muss vor oder am Enddatum liegen.");
}
// Validierung: maximal 12 Wochen Zeitraum
const start = new Date(input.startDate);
const end = new Date(input.endDate);
const daysDiff = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
if (daysDiff > 84) { // 12 Wochen = 84 Tage
throw new Error("Zeitraum darf maximal 12 Wochen betragen.");
}
// Lade alle aktiven Regeln
const allRules = await recurringRulesKV.getAllItems();
const activeRules = allRules.filter(rule => rule.isActive);
// Lade alle Urlaubszeiten
const timeOffPeriods = await timeOffPeriodsKV.getAllItems();
// Lade bestehende Slots (ohne Auto-Cleanup)
const existingSlots = await call(availabilityRouter.peekAll, {}, {});
// Erstelle Set für effiziente Duplikat-Prüfung
const existing = new Set(existingSlots.map(s => `${s.date}T${s.time}`));
let created = 0;
let skipped = 0;
let updated = 0;
// Iteriere über jeden Tag im Zeitraum
const currentDate = new Date(start);
while (currentDate <= end) {
const dateStr = formatDate(currentDate);
// Verwende lokale Datumskomponenten für korrekte Wochentag-Berechnung
const [y, m, d] = dateStr.split('-').map(Number);
const localDate = new Date(y, m - 1, d);
const dayOfWeek = localDate.getDay(); // 0=Sonntag, 1=Montag, ...
// Prüfe, ob Datum in einer Urlaubszeit liegt
if (isDateInTimeOffPeriod(dateStr, timeOffPeriods)) {
currentDate.setDate(currentDate.getDate() + 1);
continue;
}
// Finde alle Regeln für diesen Wochentag
const matchingRules = activeRules.filter(rule => rule.dayOfWeek === dayOfWeek);
for (const rule of matchingRules) {
// Skip rules without slotDurationMinutes (legacy field for deprecated generateSlots)
if (!rule.slotDurationMinutes) {
console.log(`Skipping rule ${rule.id} - no slotDurationMinutes defined (legacy field)`);
continue;
}
const startMinutes = parseTime(rule.startTime);
const endMinutes = parseTime(rule.endTime);
// Generiere Slots in slotDurationMinutes-Schritten
let currentMinutes = startMinutes;
while (currentMinutes + rule.slotDurationMinutes <= endMinutes) {
const timeStr = formatTime(currentMinutes);
const key = `${dateStr}T${timeStr}`;
// Prüfe, ob bereits ein Slot für dieses Datum+Zeit existiert
if (existing.has(key)) {
if (input.overwriteExisting) {
// Finde den bestehenden Slot für Update
const existingSlot = existingSlots.find(
slot => slot.date === dateStr && slot.time === timeStr
);
if (existingSlot && existingSlot.status === "free") {
// Überschreibe Dauer des bestehenden Slots
const updatedSlot = {
...existingSlot,
durationMinutes: rule.slotDurationMinutes,
};
await call(availabilityRouter.update, {
sessionId: input.sessionId,
...updatedSlot
}, {});
updated++;
} else {
skipped++;
}
} else {
skipped++;
}
} else {
// Erstelle neuen Slot mit try/catch für Duplikat-Konflikte
try {
await call(availabilityRouter.create, {
sessionId: input.sessionId,
date: dateStr,
time: timeStr,
durationMinutes: rule.slotDurationMinutes,
}, {});
existing.add(key);
created++;
} catch (err: any) {
// Behandle bekannte Duplikat-Fehler
if (err.message && err.message.includes("bereits ein Slot")) {
skipped++;
} else {
throw err; // Re-throw unbekannte Fehler
}
}
}
currentMinutes += rule.slotDurationMinutes;
}
}
currentDate.setDate(currentDate.getDate() + 1);
}
const message = `${created} Slots erstellt, ${updated} aktualisiert, ${skipped} übersprungen.`;
console.log(`Slot generation completed: ${message}`);
return {
created,
updated,
skipped,
message,
};
} catch (err) {
console.error("recurring-availability.generateSlots error", err);
throw err;
}
});
// Get Available Times Endpoint
const getAvailableTimes = os
@@ -643,9 +479,6 @@ export const router = {
listTimeOff,
adminListTimeOff,
// Generator
generateSlots,
// Availability
getAvailableTimes,