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 { // 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 { 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 { // 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, } } }