ConflictService
Detect booking conflicts and suggest alternatives
Overview
ConflictService checks for booking overlaps including partition tree conflicts, suggests alternative times and rooms, and resolves conflicts based on priority tiers.
import { ConflictService } from '@hfu.digital/roomkit-nestjs';Methods
checkConflicts
Check whether a proposed time range conflicts with existing bookings for a room. Checks both direct overlaps and partition tree overlaps (e.g., booking a parent room when a child partition is booked).
async checkConflicts(
roomId: string,
startsAt: Date,
endsAt: Date,
excludeBookingId?: string,
): Promise<ConflictCheckResult>| Parameter | Type | Description |
|---|---|---|
roomId | string | Room ID to check |
startsAt | Date | Proposed start time |
endsAt | Date | Proposed end time |
excludeBookingId | string | Optional booking ID to exclude (useful when modifying an existing booking) |
const result = await conflictService.checkConflicts(
'room-a1',
new Date('2026-03-02T09:00:00'),
new Date('2026-03-02T10:00:00'),
);
if (result.hasConflict) {
console.log('Direct overlaps:', result.directConflicts);
console.log('Partition overlaps:', result.partitionConflicts);
}suggestAlternatives
Suggest alternative booking options when a conflict is detected. Returns two types of suggestions: same room at different times, and different rooms at the same time.
async suggestAlternatives(
roomId: string,
startsAt: Date,
endsAt: Date,
filter?: AvailabilityFilter,
): Promise<AlternativeSuggestion[]>| Parameter | Type | Description |
|---|---|---|
roomId | string | Originally requested room ID |
startsAt | Date | Originally requested start time |
endsAt | Date | Originally requested end time |
filter | AvailabilityFilter | Optional filter to constrain alternative room suggestions |
Same-room-different-time suggestions scan forward in 30-minute increments over a 7-day window, returning up to 3 options.
Different-room-same-time suggestions find available rooms at the same time, scored by capacity similarity (score range 0.5 to 0.9).
const alternatives = await conflictService.suggestAlternatives(
'room-a1',
new Date('2026-03-02T09:00:00'),
new Date('2026-03-02T10:00:00'),
{ minCapacity: 20, requiredEquipment: ['projector'] },
);
for (const alt of alternatives) {
console.log(alt.type, alt.roomId, alt.startsAt, alt.score);
}resolveByPriority
Determine which booking wins a conflict based on priority tiers. The higher priority booking wins. In the case of a tie, the existing booking takes precedence.
resolveByPriority(
existingBooking: Booking,
newBooking: Booking,
): ConflictResolution| Parameter | Type | Description |
|---|---|---|
existingBooking | Booking | The already-persisted booking |
newBooking | Booking | The incoming booking requesting the same slot |
Returns a ConflictResolution indicating which booking wins and which should be displaced.
const resolution = conflictService.resolveByPriority(existingBooking, newBooking);
if (resolution.winner.id === newBooking.id) {
// New booking has higher priority — displace the existing one
await bookingService.cancel(existingBooking.id, 'system', 'Displaced by higher priority');
}recordResolution
Persist a conflict resolution record for audit purposes.
async recordResolution(data: CreateConflictRecordDto): Promise<ConflictRecord>| Parameter | Type | Description |
|---|---|---|
data | CreateConflictRecordDto | Resolution data to persist |
const record = await conflictService.recordResolution({
bookingId: newBooking.id,
conflictingBookingId: existingBooking.id,
resolution: 'displaced',
resolvedBy: 'system',
reason: 'Higher priority tier',
});ConflictCheckResult
interface ConflictCheckResult {
hasConflict: boolean; // True iff either array is non-empty
directConflicts: Booking[]; // Overlaps in the same room
partitionConflicts: { roomId: string; booking: Booking }[]; // Overlaps in a parent or child partition room
}AlternativeSuggestion
interface AlternativeSuggestion {
type: 'same_room_different_time' | 'different_room_same_time';
room: Room | null; // null when only the time changed
startsAt: Date;
endsAt: Date;
score: number; // Higher is better
}