Files
epess-web-backend/src/Schedule/schedule.service.ts

230 lines
8.1 KiB
TypeScript

import { DateTimeUtils } from '../common/utils/datetime.utils'
import { Injectable, Logger } from '@nestjs/common'
import { PrismaService } from 'src/Prisma/prisma.service'
import { AppConfigService } from 'src/AppConfig/appconfig.service'
import { PreviewScheduleType, ScheduleConfigType, ScheduleConfigTypeForCenter, ScheduleSlotType } from './schedule.d'
import { Config, Schedule, ScheduleDate } from '@prisma/client'
import { DateTime, Settings, Zone } from 'luxon'
import _ from 'lodash'
import { ScheduleDateInput } from './schedule'
@Injectable()
export class ScheduleService {
constructor(
private readonly prisma: PrismaService,
private readonly appConfigService: AppConfigService,
) {}
async createSchedulePreviewForSingleDay(scheduleConfig: ScheduleConfigType): Promise<PreviewScheduleType> {
// generate Slot By config
const slots = this.generateSlots(scheduleConfig)
return {
totalSlots: slots.length,
slots: slots,
}
}
// create preview for center require scheduleConfigInput: { startDate: "2024-11-02T00:00:00.000Z", endDate: "2024-11-22T00:00:00.000Z", slots: [1, 3], days: [2, 5] }
async createSchedulePreviewForCenter(scheduleConfig: ScheduleConfigTypeForCenter): Promise<PreviewScheduleType> {
const config: ScheduleConfigType = (await this.appConfigService.getVisibleConfigs()).reduce((acc, curr) => {
// @ts-ignore
acc[curr.key] = curr.value
return acc
}, {} as ScheduleConfigType)
const slots = this.generateSlotsPreviewForCenter(scheduleConfig, config)
return {
totalSlots: slots.length,
slots: slots,
}
}
async generateScheduleDates(schedule: Schedule): Promise<ScheduleDate[]> {
// generate schedule dates based on data and config
const config: ScheduleConfigType = (await this.appConfigService.getVisibleConfigs()).reduce((acc, curr) => {
// @ts-ignore
acc[curr.key] = curr.value
return acc
}, {} as ScheduleConfigType)
const daysOfWeeks = schedule.daysOfWeek
const slots = schedule.slots
const scheduleStart = DateTime.fromJSDate(schedule.scheduleStart)
const scheduleEnd = DateTime.fromJSDate(schedule.scheduleEnd)
const slotDuration = parseInt(config.slotDuration)
const slotBreakDuration = parseInt(config.slotBreakDuration)
// add participants to schedule dates
const scheduleDates: ScheduleDateInput[] = []
// loop each day from scheduleStart to scheduleEnd
for (let date = scheduleStart; date <= scheduleEnd; date = date.plus({ days: 1 })) {
// Check if the current date matches one of the specified days of the week
if (daysOfWeeks.includes(date.weekday)) {
// loop through slots
for (const slot of slots) {
const { startTime, endTime } = this.getSlotStartAndEndTime(
slot,
slotDuration.toString(),
slotBreakDuration.toString(),
DateTimeUtils.getATimeWithDateB(DateTime.fromISO(config.dayStartTime), date).toISO() ?? '',
)
scheduleDates.push({
scheduleId: schedule.id,
start: startTime.toISO() ?? '',
end: endTime.toISO() ?? '',
dayOfWeek: date.weekday,
slot: slot,
participantIds: [],
serviceId: schedule.managedServiceId,
orderId: schedule.orderId,
})
}
}
}
const scheduleDatesCreated = await this.prisma.scheduleDate.createManyAndReturn({
data: scheduleDates,
})
return scheduleDatesCreated
}
/*
example query:
query CenterPreviewSchedule {
centerPreviewSchedule(
scheduleConfigInput: { days: [3,5], endDate: "2024-11-22T00:00:00.000Z", slots: [2,6], startDate: "2024-11-02T00:00:00.000Z" }
) {
totalSlots
slots {
dayOfWeek
end
slot
start
}
}
}
*/
generateSlotsPreviewForCenter(
_scheduleConfig: ScheduleConfigTypeForCenter,
_config: ScheduleConfigType,
): ScheduleSlotType[] {
const slots: ScheduleSlotType[] = []
const daysOfWeeks = _scheduleConfig.days
const scheduleStart = DateTime.fromISO(_scheduleConfig.startDate)
const scheduleEnd = DateTime.fromISO(_scheduleConfig.endDate)
// loop each day from scheduleStart to scheduleEnd
for (let date = scheduleStart; date <= scheduleEnd; date = date.plus({ days: 1 })) {
// Check if the current date matches one of the specified days of the week
if (daysOfWeeks.includes(date.weekday)) {
// loop through slots
for (const slot of _scheduleConfig.slots) {
// get slot start and end time
const { startTime, endTime } = this.getSlotStartAndEndTime(
slot,
_config.slotDuration,
_config.slotBreakDuration,
DateTimeUtils.getATimeWithDateB(DateTime.fromISO(_config.dayStartTime), date).toISO() ?? '',
)
// if the slot is not overlapping with mid day break time, add it to the slots
if (
!DateTimeUtils.isOverlap(
startTime,
endTime,
DateTimeUtils.fromIsoString(_config.midDayBreakTimeStart),
DateTimeUtils.fromIsoString(_config.midDayBreakTimeEnd),
)
) {
slots.push({
slot: slot.toString(),
start: startTime.toString(),
end: endTime.toString(),
dayOfWeek: date.weekday,
})
}
}
}
}
return slots
}
generateSlots(scheduleConfig: ScheduleConfigType): ScheduleSlotType[] {
const slots: ScheduleSlotType[] = []
const numberOfSlots = this.calculateNumberOfSlots(
scheduleConfig.dayStartTime,
scheduleConfig.dayEndTime,
scheduleConfig.slotDuration,
scheduleConfig.slotBreakDuration,
)
for (let i = 1; i <= numberOfSlots; i++) {
const { startTime, endTime } = this.getSlotStartAndEndTime(
i,
scheduleConfig.slotDuration,
scheduleConfig.slotBreakDuration,
scheduleConfig.dayStartTime,
)
// if the slot is not overlapping with mid day break time, add it to the slots
if (
!DateTimeUtils.isOverlap(
startTime,
endTime,
DateTimeUtils.fromIsoString(scheduleConfig.midDayBreakTimeStart),
DateTimeUtils.fromIsoString(scheduleConfig.midDayBreakTimeEnd),
)
) {
slots.push({
slot: i.toString(),
start: startTime.toString(),
end: endTime.toString(),
dayOfWeek: startTime.weekday,
})
}
}
return slots
}
isOverLapping(startTime1: DateTime, endTime1: DateTime, startTime2: DateTime, endTime2: DateTime) {
return Math.max(startTime1.toMillis(), startTime2.toMillis()) < Math.min(endTime1.toMillis(), endTime2.toMillis())
}
calculateNumberOfSlots(startTime: string, endTime: string, slotDuration: string, slotBreakDuration: string) {
const _startTime = DateTimeUtils.toTime(startTime)
const _endTime = DateTimeUtils.toTime(endTime)
const _slotDuration = parseInt(slotDuration) // minutes
const _slotBreakDuration = parseInt(slotBreakDuration) // minutes
const startDate = DateTime.fromObject({
hour: _startTime.hour,
minute: _startTime.minute,
second: _startTime.second,
})
const endDate = DateTime.fromObject({
hour: _endTime.hour,
minute: _endTime.minute,
second: _endTime.second,
})
const totalMinutes = (endDate.toMillis() - startDate.toMillis()) / (60 * 1000)
const numberOfSlots = Math.floor(totalMinutes / (_slotDuration + _slotBreakDuration))
return numberOfSlots
}
getSlotStartAndEndTime(slotNumber: number, slotDuration: string, slotBreakDuration: string, dayStartTime: string) {
const durationInMinutes = parseInt(slotDuration)
const breakDurationInMinutes = parseInt(slotBreakDuration)
const initialStartTime = DateTime.fromISO(dayStartTime)
const startTime = initialStartTime.plus({
minutes: (slotNumber - 1) * (durationInMinutes + breakDurationInMinutes),
})
const endTime = startTime.plus({ minutes: durationInMinutes })
return {
startTime: startTime,
endTime: endTime,
}
}
}