improve exception saving

This commit is contained in:
Schneider Roland
2025-12-09 07:20:55 +01:00
parent 122ddae575
commit c53c00e13c
4 changed files with 144 additions and 14 deletions

View File

@@ -1,12 +1,13 @@
import { Component, inject, input } from '@angular/core'; 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 { import {
SingleEventDashboardEventDetailsView SingleEventDashboardEventDetailsView,
} from '../single-event-dashboard-event-details-view/single-event-dashboard-event-details-view'; } from '../single-event-dashboard-event-details-view/single-event-dashboard-event-details-view';
import { Button } from '@rschneider/ng-daisyui'; import { Button } from '@rschneider/ng-daisyui';
import { SvgIcons } from '../../../../../svg-icons'; import { SvgIcons } from '../../../../../svg-icons';
import { SafeHtmlPipe } from '../../../../../pipes/safe-html-pipe'; import { SafeHtmlPipe } from '../../../../../pipes/safe-html-pipe';
import { CalendarService } from '../../../services/calendar.service'; import { CalendarService } from '../../../services/calendar.service';
import { CreateExceptionDto } from '../../../models/event-exception.model';
export type ACTIVATION_TYPE = 'cancel' | 'activate'; export type ACTIVATION_TYPE = 'cancel' | 'activate';
@@ -32,32 +33,49 @@ export class SingleEventDashboardEventActivation {
protected setEventOccurrenceActivation(activated: boolean) { protected setEventOccurrenceActivation(activated: boolean) {
const event = this.event();
console.info('setEventOccurrenceActivation', event);
const eventId = this.event()?.id!; const eventId = this.event()?.id!;
const startTime = this.event()?.startTime!; const startTime = this.event()?.startTime!;
let payload: CreateExceptionDto | undefined = undefined;
this.calendarService.applyException(eventId,{ 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), originalStartTime: new Date(startTime),
isCancelled: !activated, isCancelled: !activated,
}).subscribe( };
}
this.calendarService.applyException(eventId, payload ).subscribe(
{ {
next: () => { next: () => {
this.onAction()('close'); this.onAction()('close');
}, },
error: err => { error: err => {
alert("Failed to change event"); alert('Failed to change event');
} },
} },
) );
} }
protected cancelEventOccurrence() { protected cancelEventOccurrence() {
this.setEventOccurrenceActivation(false); this.setEventOccurrenceActivation(false);
} }
protected activateEventOccurrence() { protected activateEventOccurrence() {
this.setEventOccurrenceActivation(true); this.setEventOccurrenceActivation(true);
} }
protected closeDialog() { protected closeDialog() {
this.onAction()('close') this.onAction()('close');
} }
} }

View File

@@ -16,6 +16,18 @@ export type BookingWithUserDto = {
id: number; 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 // The final shape of a calendar event occurrence
export type CalendarEventDto = { export type CalendarEventDto = {
id: number; id: number;
@@ -29,4 +41,5 @@ export type CalendarEventDto = {
isRecurring: boolean; isRecurring: boolean;
eventType: EventType eventType: EventType
exceptions: EventExceptionDto[]
}; };

View File

@@ -65,7 +65,10 @@ export class CalendarController {
@Param('id', ParseIntPipe) eventId: number, @Param('id', ParseIntPipe) eventId: number,
@Body() createExceptionDto: CreateExceptionDto, @Body() createExceptionDto: CreateExceptionDto,
) { ) {
return this.calendarService.createException(eventId, createExceptionDto); return this.calendarService.createOrUpdateException(
eventId,
createExceptionDto,
);
} }
// Create a booking for a specific event occurrence // Create a booking for a specific event occurrence

View File

@@ -335,6 +335,102 @@ export class CalendarService {
}, },
); );
} }
async createOrUpdateException(
eventId: number,
createExceptionDto: CreateExceptionDto,
): Promise<EventException> {
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<Event> { async getEventById(id: number): Promise<Event> {
const event = await this.eventRepository.findOne({ const event = await this.eventRepository.findOne({