Entferne Slots-Tab und Slot-RPCs; bereinige recurring-availability; Texte angepasst
This commit is contained in:
@@ -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,
|
||||
|
||||
|
Reference in New Issue
Block a user