"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ShiftSeriesService = void 0; const database_1 = require("../../../config/database"); const client_1 = require("@prisma/client"); const MAX_OCCURRENCES = 100; const MAX_DAYS = 365; class ShiftSeriesService { /** * Generate occurrence dates based on recurrence rules */ static generateOccurrences(input) { const dates = []; const startDate = new Date(input.startDate); const endDate = input.endDate ? new Date(input.endDate) : null; let currentDate = new Date(startDate); let count = 0; while (count < MAX_OCCURRENCES) { // Check end date if (endDate && currentDate > endDate) break; // Check max days const daysDiff = Math.floor((currentDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); if (daysDiff > MAX_DAYS) break; // Check if date matches recurrence pattern let shouldInclude = false; if (input.frequency === client_1.RecurrenceFrequency.DAILY) { shouldInclude = true; } else if (input.frequency === client_1.RecurrenceFrequency.WEEKLY) { const dayOfWeek = currentDate.getDay(); // 0=Sun, 6=Sat shouldInclude = input.daysOfWeek?.includes(dayOfWeek) ?? false; } else if (input.frequency === client_1.RecurrenceFrequency.MONTHLY) { shouldInclude = currentDate.getDate() === startDate.getDate(); } if (shouldInclude) { dates.push(new Date(currentDate)); count++; } // Increment date if (input.frequency === client_1.RecurrenceFrequency.DAILY) { currentDate.setDate(currentDate.getDate() + 1); } else if (input.frequency === client_1.RecurrenceFrequency.WEEKLY) { currentDate.setDate(currentDate.getDate() + 1); // Check each day } else if (input.frequency === client_1.RecurrenceFrequency.MONTHLY) { currentDate.setMonth(currentDate.getMonth() + 1); } } return dates; } /** * Create a shift series and generate all individual shifts */ static async createSeries(input, createdBy) { // Generate occurrence dates const dates = this.generateOccurrences(input); if (dates.length === 0) { throw new Error('No valid dates generated from recurrence rules'); } // Create series and shifts in transaction const result = await database_1.prisma.$transaction(async (tx) => { // Create series const series = await tx.shiftSeries.create({ data: { title: input.title, description: input.description, startTime: input.startTime, endTime: input.endTime, location: input.location, maxVolunteers: input.maxVolunteers, isPublic: input.isPublic ?? false, cutId: input.cutId, frequency: input.frequency, daysOfWeek: input.daysOfWeek, startDate: new Date(input.startDate), endDate: input.endDate ? new Date(input.endDate) : null, createdBy, }, }); // Create individual shifts const shifts = await Promise.all(dates.map((date) => tx.shift.create({ data: { title: input.title, description: input.description, date, startTime: input.startTime, endTime: input.endTime, location: input.location, maxVolunteers: input.maxVolunteers, isPublic: input.isPublic ?? false, cutId: input.cutId, seriesId: series.id, createdBy, }, }))); return { series, shifts }; }); return { series: result.series, generatedShiftsCount: result.shifts.length, }; } /** * Get count of non-exception shifts in a series */ static async getShiftCount(seriesId) { return database_1.prisma.shift.count({ where: { seriesId, isException: false }, }); } /** * Get series with all its shifts */ static async getSeries(seriesId) { const series = await database_1.prisma.shiftSeries.findUnique({ where: { id: seriesId }, include: { cut: true, shifts: { orderBy: { date: 'asc' }, include: { signups: true }, }, }, }); if (!series) { throw new Error('Shift series not found'); } return series; } /** * Update series shifts based on edit mode */ static async updateSeries(seriesId, input) { const series = await database_1.prisma.shiftSeries.findUnique({ where: { id: seriesId }, include: { shifts: true }, }); if (!series) { throw new Error('Shift series not found'); } const { editMode, fromDate, ...updates } = input; if (editMode === 'ALL') { // Update series + all shifts await database_1.prisma.$transaction(async (tx) => { await tx.shiftSeries.update({ where: { id: seriesId }, data: updates, }); await tx.shift.updateMany({ where: { seriesId, isException: false }, data: updates, }); }); } else if (editMode === 'FUTURE' && fromDate) { // Update shifts from date onwards await database_1.prisma.shift.updateMany({ where: { seriesId, date: { gte: new Date(fromDate) }, isException: false, }, data: updates, }); } // Note: THIS mode is handled by regular shift update with isException flag return this.getSeries(seriesId); } /** * Delete series (cascades to shifts via onDelete: SetNull) */ static async deleteSeries(seriesId) { // Delete all shifts in series first await database_1.prisma.shift.deleteMany({ where: { seriesId }, }); // Then delete series await database_1.prisma.shiftSeries.delete({ where: { id: seriesId }, }); } } exports.ShiftSeriesService = ShiftSeriesService; //# sourceMappingURL=shift-series.service.js.map