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

279 lines
8.4 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 * as _ 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,
}
}
}