diff --git a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts index be42096..e4238f7 100644 --- a/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts +++ b/admin/src/app/features/calendar/components/calendar-view/single-event-dashboard-event-activation/single-event-dashboard-event-activation.component.ts @@ -1,12 +1,13 @@ import { Component, inject, input } from '@angular/core'; -import { CalendarEventDto } from '../../../models/events-in-range-dto.model'; +import { CalendarEventDto, EventExceptionDto } from '../../../models/events-in-range-dto.model'; import { - SingleEventDashboardEventDetailsView + SingleEventDashboardEventDetailsView, } from '../single-event-dashboard-event-details-view/single-event-dashboard-event-details-view'; import { Button } from '@rschneider/ng-daisyui'; import { SvgIcons } from '../../../../../svg-icons'; import { SafeHtmlPipe } from '../../../../../pipes/safe-html-pipe'; import { CalendarService } from '../../../services/calendar.service'; +import { CreateExceptionDto } from '../../../models/event-exception.model'; export type ACTIVATION_TYPE = 'cancel' | 'activate'; @@ -32,32 +33,49 @@ export class SingleEventDashboardEventActivation { protected setEventOccurrenceActivation(activated: boolean) { - const eventId =this.event()?.id!; + const event = this.event(); + console.info('setEventOccurrenceActivation', event); + const eventId = this.event()?.id!; const startTime = this.event()?.startTime!; + let payload: CreateExceptionDto | undefined = undefined; + const eventException = event?.exceptions ? event.exceptions[0] : undefined; + if (eventException) { + payload = { + ...eventException, + originalStartTime: new Date(eventException.originalStartTime), + newStartTime: eventException.newStartTime ? new Date(eventException.newStartTime) : undefined, + newEndTime: eventException.newEndTime ? new Date(eventException.newEndTime) :undefined, + isCancelled: !activated, + }; + } else { + payload = { + originalStartTime: new Date(startTime), + isCancelled: !activated, + }; + } - this.calendarService.applyException(eventId,{ - originalStartTime: new Date(startTime), - isCancelled: !activated, - }).subscribe( + this.calendarService.applyException(eventId, payload ).subscribe( { next: () => { this.onAction()('close'); }, error: err => { - alert("Failed to change event"); - } - } - ) + alert('Failed to change event'); + }, + }, + ); } protected cancelEventOccurrence() { -this.setEventOccurrenceActivation(false); + this.setEventOccurrenceActivation(false); } + protected activateEventOccurrence() { this.setEventOccurrenceActivation(true); } + protected closeDialog() { - this.onAction()('close') + this.onAction()('close'); } } diff --git a/admin/src/app/features/calendar/models/events-in-range-dto.model.ts b/admin/src/app/features/calendar/models/events-in-range-dto.model.ts index 818fa3a..46a032f 100644 --- a/admin/src/app/features/calendar/models/events-in-range-dto.model.ts +++ b/admin/src/app/features/calendar/models/events-in-range-dto.model.ts @@ -16,6 +16,18 @@ export type BookingWithUserDto = { id: number; }; +export interface EventExceptionDto{ + id: number, + description: string; + eventId: number; + isCancelled: boolean; + newEndTime: string; + newStartTime: string; + originalStartTime: string; + title: string; + +} + // The final shape of a calendar event occurrence export type CalendarEventDto = { id: number; @@ -29,4 +41,5 @@ export type CalendarEventDto = { isRecurring: boolean; eventType: EventType + exceptions: EventExceptionDto[] }; diff --git a/server/src/calendar/calendar.controller.ts b/server/src/calendar/calendar.controller.ts index 26fea73..e49c820 100644 --- a/server/src/calendar/calendar.controller.ts +++ b/server/src/calendar/calendar.controller.ts @@ -65,7 +65,10 @@ export class CalendarController { @Param('id', ParseIntPipe) eventId: number, @Body() createExceptionDto: CreateExceptionDto, ) { - return this.calendarService.createException(eventId, createExceptionDto); + return this.calendarService.createOrUpdateException( + eventId, + createExceptionDto, + ); } // Create a booking for a specific event occurrence diff --git a/server/src/calendar/calendar.service.ts b/server/src/calendar/calendar.service.ts index 6dee62f..8fc2550 100644 --- a/server/src/calendar/calendar.service.ts +++ b/server/src/calendar/calendar.service.ts @@ -335,6 +335,102 @@ export class CalendarService { }, ); } + async createOrUpdateException( + eventId: number, + createExceptionDto: CreateExceptionDto, + ): Promise { + const { originalStartTime, newStartTime } = createExceptionDto; + + const event = await this.eventRepository.findOneBy({ id: eventId }); + if (!event) { + throw new NotFoundException(`Event with ID ${eventId} not found`); + } + + // This logic now requires a transaction + return this.dataSource.manager.transaction( + async (transactionalEntityManager) => { + let oldNewStartTime: Date | null = null; + // Check if an exception for this originalStartTime already exists + let existingException = await transactionalEntityManager.findOne( + EventException, + { + where: { + eventId: eventId, + originalStartTime: originalStartTime, + }, + }, + ); + + if (existingException) { + oldNewStartTime = existingException.newStartTime; + existingException.title = createExceptionDto.title!; + existingException.description = createExceptionDto.description!; + existingException.isCancelled = createExceptionDto.isCancelled!; + existingException.newStartTime = createExceptionDto.newStartTime!; + existingException.newEndTime = createExceptionDto.newEndTime!; + + // More complex logic might be needed here depending on exact requirements. + } else { + // Create new exception + existingException = transactionalEntityManager.create( + EventException, + { + ...createExceptionDto, + event: event, + }, + ); + } + + const savedException = + await transactionalEntityManager.save(existingException); + // Step 1: Create and save the new exception + // const exception = transactionalEntityManager.create(EventException, { + // ...createExceptionDto, + // event: event, + // }); + // const savedException = await transactionalEntityManager.save(exception); + + // Step 2: Check if this is a RESCHEDULE operation + // A reschedule happens when there is a new start time. + let startTimeChanged = false; + if (newStartTime && !oldNewStartTime) { + startTimeChanged = true; + } else if (!newStartTime && oldNewStartTime) { + startTimeChanged = true; + } else if (newStartTime && oldNewStartTime) { + if (oldNewStartTime.getTime() !== newStartTime.getTime()) { + startTimeChanged = true; + } + } + + if (startTimeChanged) { + // Step 3: Find all existing bookings for the ORIGINAL time + const bookingsToMigrate = await transactionalEntityManager.find( + Booking, + { + where: { + eventId: eventId, + occurrenceStartTime: originalStartTime, + }, + }, + ); + + // Step 4: If we found any bookings, update their start time to the NEW time + if (bookingsToMigrate.length > 0) { + const bookingIds = bookingsToMigrate.map((b) => b.id); + + await transactionalEntityManager.update( + Booking, + bookingIds, // Update these specific bookings + { occurrenceStartTime: newStartTime }, // Set their time to the new value + ); + } + } + + return savedException; + }, + ); + } async getEventById(id: number): Promise { const event = await this.eventRepository.findOne({