fix some bug and produce many problem to solve later
This commit is contained in:
65
src/Schedule/schedule.d.ts
vendored
Normal file
65
src/Schedule/schedule.d.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
export interface ScheduleDateInput {
|
||||
scheduleId: string
|
||||
start: string
|
||||
end: string
|
||||
dayOfWeek: number
|
||||
slot: number
|
||||
serviceId: string
|
||||
orderId: string | null
|
||||
}
|
||||
|
||||
export interface ScheduleConfigType {
|
||||
midDayBreakTimeStart: string
|
||||
midDayBreakTimeEnd: string
|
||||
slotDuration: string
|
||||
slotBreakDuration: string
|
||||
dayStartTime: string
|
||||
dayEndTime: string
|
||||
}
|
||||
|
||||
export interface ScheduleConfigTypeForCenter {
|
||||
startDate: string
|
||||
endDate: string
|
||||
slots: number[]
|
||||
days: number[]
|
||||
}
|
||||
|
||||
export interface ScheduleSlotType {
|
||||
slot: string
|
||||
dayOfWeek: number
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export interface PreviewScheduleType {
|
||||
totalSlots: number
|
||||
slots: ScheduleSlotType[]
|
||||
}
|
||||
|
||||
export interface ScheduleConfigType {
|
||||
midDayBreakTimeStart: string
|
||||
midDayBreakTimeEnd: string
|
||||
slotDuration: string
|
||||
slotBreakDuration: string
|
||||
dayStartTime: string
|
||||
dayEndTime: string
|
||||
}
|
||||
|
||||
export interface ScheduleConfigTypeForCenter {
|
||||
startDate: string
|
||||
endDate: string
|
||||
slots: number[]
|
||||
days: number[]
|
||||
}
|
||||
|
||||
export interface ScheduleSlotType {
|
||||
slot: string
|
||||
dayOfWeek: number
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export interface PreviewScheduleType {
|
||||
totalSlots: number
|
||||
slots: ScheduleSlotType[]
|
||||
}
|
||||
@@ -10,37 +10,7 @@ import { PrismaService } from '../Prisma/prisma.service'
|
||||
import { ScheduleStatus } from '@prisma/client'
|
||||
import { ScheduleService } from './schedule.service'
|
||||
import { AppConfigService } from '../AppConfig/appconfig.service'
|
||||
import { forEach } from 'lodash'
|
||||
|
||||
export type ScheduleConfigType =
|
||||
| {
|
||||
midDayBreakTimeStart?: string | null | undefined
|
||||
midDayBreakTimeEnd?: string | null | undefined
|
||||
slotDuration?: string | null | undefined
|
||||
slotBreakDuration?: string | null | undefined
|
||||
slotEndTime?: string | null | undefined
|
||||
slotStartTime?: string | null | undefined
|
||||
}
|
||||
| null
|
||||
| undefined
|
||||
|
||||
export type ScheduleConfigTypeForCenter = {
|
||||
startDate: string
|
||||
endDate: string
|
||||
slots: number[]
|
||||
days: number[]
|
||||
}
|
||||
|
||||
export type ScheduleSlotType = {
|
||||
slot: string
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export type PreviewScheduleType = {
|
||||
totalSlots: number
|
||||
slots: ScheduleSlotType[]
|
||||
}
|
||||
import { ScheduleConfigType } from './schedule'
|
||||
|
||||
@Injectable()
|
||||
export class ScheduleSchema extends PothosSchema {
|
||||
@@ -109,6 +79,7 @@ export class ScheduleSchema extends PothosSchema {
|
||||
slot: t.string({}),
|
||||
start: t.string({}),
|
||||
end: t.string({}),
|
||||
dayOfWeek: t.int({}),
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -168,12 +139,24 @@ export class ScheduleSchema extends PothosSchema {
|
||||
return this.builder.inputType('ScheduleConfigInput', {
|
||||
description: 'A schedule config in the system.',
|
||||
fields: (t) => ({
|
||||
midDayBreakTimeStart: t.string(),
|
||||
midDayBreakTimeEnd: t.string(),
|
||||
slotDuration: t.string(),
|
||||
slotBreakDuration: t.string(),
|
||||
slotEndTime: t.string(),
|
||||
slotStartTime: t.string(),
|
||||
midDayBreakTimeStart: t.string({
|
||||
required: true,
|
||||
}),
|
||||
midDayBreakTimeEnd: t.string({
|
||||
required: true,
|
||||
}),
|
||||
slotDuration: t.string({
|
||||
required: true,
|
||||
}),
|
||||
slotBreakDuration: t.string({
|
||||
required: true,
|
||||
}),
|
||||
dayStartTime: t.string({
|
||||
required: true,
|
||||
}),
|
||||
dayEndTime: t.string({
|
||||
required: true,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -254,7 +237,17 @@ export class ScheduleSchema extends PothosSchema {
|
||||
}),
|
||||
},
|
||||
resolve: async (_parent, args, _context, _info) => {
|
||||
return await this.scheduleService.createSchedulePreview(
|
||||
// if no scheduleConfig, use default config
|
||||
if (!args.scheduleConfig) {
|
||||
args.scheduleConfig = (
|
||||
await this.appConfigService.getVisibleConfigs()
|
||||
).reduce((acc, curr) => {
|
||||
// @ts-ignore
|
||||
acc[curr.key] = curr.value
|
||||
return acc
|
||||
}, {} as ScheduleConfigType)
|
||||
}
|
||||
return await this.scheduleService.createSchedulePreviewForSingleDay(
|
||||
args.scheduleConfig,
|
||||
)
|
||||
},
|
||||
@@ -278,6 +271,7 @@ d72a864e-2f41-45ab-9c9b-bf0512a31883,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-1
|
||||
'status',
|
||||
'customerId',
|
||||
'orderId',
|
||||
'dates',
|
||||
]),
|
||||
required: true,
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as DateTimeUtils from '../common/utils/datetime.utils'
|
||||
import { DateTimeUtils } from '../common/utils/datetime.utils'
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { PrismaService } from 'src/Prisma/prisma.service'
|
||||
@@ -9,28 +9,12 @@ import {
|
||||
ScheduleConfigType,
|
||||
ScheduleConfigTypeForCenter,
|
||||
ScheduleSlotType,
|
||||
} from './schedule.schema'
|
||||
} from './schedule.d'
|
||||
import { Config, Schedule, ScheduleDate } from '@prisma/client'
|
||||
import { DateTime, Settings, Zone } from 'luxon'
|
||||
import * as _ from 'lodash'
|
||||
import { ScheduleDateInput } from './schedule'
|
||||
|
||||
Settings.defaultLocale = 'en-US'
|
||||
Settings.defaultZone = 'utc'
|
||||
Settings.defaultWeekSettings = {
|
||||
firstDay: 2,
|
||||
minimalDays: 1,
|
||||
weekend: [6, 7],
|
||||
}
|
||||
|
||||
interface ScheduleDateInput {
|
||||
scheduleId: string
|
||||
start: string
|
||||
end: string
|
||||
dayOfWeek: number
|
||||
slot: number
|
||||
serviceId: string
|
||||
orderId: string | null
|
||||
}
|
||||
@Injectable()
|
||||
export class ScheduleService {
|
||||
constructor(
|
||||
@@ -38,17 +22,11 @@ export class ScheduleService {
|
||||
private readonly appConfigService: AppConfigService,
|
||||
) {}
|
||||
|
||||
async createSchedulePreview(
|
||||
async createSchedulePreviewForSingleDay(
|
||||
scheduleConfig: ScheduleConfigType,
|
||||
): Promise<PreviewScheduleType> {
|
||||
const config: Config[] = await this.appConfigService.getVisibleConfigs()
|
||||
// process scheduleConfig input by filling with default values from config
|
||||
const scheduleConfigFilled = this.processScheduleConfig(
|
||||
scheduleConfig,
|
||||
config,
|
||||
)
|
||||
// generate Slot By config
|
||||
const slots = this.generateSlots(scheduleConfigFilled)
|
||||
const slots = this.generateSlots(scheduleConfig)
|
||||
|
||||
return {
|
||||
totalSlots: slots.length,
|
||||
@@ -60,7 +38,13 @@ export class ScheduleService {
|
||||
async createSchedulePreviewForCenter(
|
||||
scheduleConfig: ScheduleConfigTypeForCenter,
|
||||
): Promise<PreviewScheduleType> {
|
||||
const config: Config[] = await this.appConfigService.getVisibleConfigs()
|
||||
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,
|
||||
@@ -70,30 +54,41 @@ export class ScheduleService {
|
||||
|
||||
async generateScheduleDates(schedule: Schedule): Promise<ScheduleDate[]> {
|
||||
// generate schedule dates based on data and config
|
||||
const config: Config[] = await this.appConfigService.getVisibleConfigs()
|
||||
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 = schedule.scheduleStart
|
||||
const scheduleEnd = schedule.scheduleEnd
|
||||
const slotDuration = config.find((c) => c.key === 'SLOT_DURATION')?.value
|
||||
const slotBreakDuration = config.find(
|
||||
(c) => c.key === 'SLOT_BREAK_DURATION',
|
||||
)?.value
|
||||
const slotStartTime = config.find((c) => c.key === 'SLOT_START_TIME')?.value
|
||||
const scheduleStart = DateTime.fromJSDate(schedule.scheduleStart)
|
||||
const scheduleEnd = DateTime.fromJSDate(schedule.scheduleEnd)
|
||||
const slotDuration = parseInt(config.slotDuration)
|
||||
const slotBreakDuration = parseInt(config.slotBreakDuration)
|
||||
|
||||
const scheduleDates: ScheduleDateInput[] = []
|
||||
|
||||
// loop each day from scheduleStart to scheduleEnd
|
||||
let date = DateTime.fromJSDate(scheduleStart)
|
||||
while (date <= DateTime.fromJSDate(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 ?? '',
|
||||
slotBreakDuration ?? '',
|
||||
slotStartTime ?? '',
|
||||
slotDuration.toString(),
|
||||
slotBreakDuration.toString(),
|
||||
DateTimeUtils.getATimeWithDateB(
|
||||
DateTime.fromISO(config.dayStartTime),
|
||||
date,
|
||||
).toISO() ?? '',
|
||||
)
|
||||
scheduleDates.push({
|
||||
scheduleId: schedule.id,
|
||||
@@ -106,9 +101,8 @@ export class ScheduleService {
|
||||
})
|
||||
}
|
||||
}
|
||||
// Move to the next day
|
||||
date = date.plus({ days: 1 })
|
||||
}
|
||||
|
||||
const scheduleDatesCreated =
|
||||
await this.prisma.scheduleDate.createManyAndReturn({
|
||||
data: scheduleDates,
|
||||
@@ -117,100 +111,102 @@ export class ScheduleService {
|
||||
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: Config[],
|
||||
_scheduleConfig: ScheduleConfigTypeForCenter,
|
||||
_config: ScheduleConfigType,
|
||||
): ScheduleSlotType[] {
|
||||
const startDate = DateTime.fromISO(scheduleConfig.startDate)
|
||||
const endDate = DateTime.fromISO(scheduleConfig.endDate)
|
||||
const daysOfWeeks = scheduleConfig.days
|
||||
|
||||
// Retrieve slot configuration values once
|
||||
const slotDuration =
|
||||
config.find((c) => c.key === 'SLOT_DURATION')?.value ?? ''
|
||||
const slotBreakDuration =
|
||||
config.find((c) => c.key === 'SLOT_BREAK_DURATION')?.value ?? ''
|
||||
const slotStartTime =
|
||||
config.find((c) => c.key === 'SLOT_START_TIME')?.value ?? ''
|
||||
const slotEndTime =
|
||||
config.find((c) => c.key === 'SLOT_END_TIME')?.value ?? ''
|
||||
|
||||
// Calculate the number of slots based on configuration
|
||||
const numberOfSlots = this.calculateNumberOfSlots(
|
||||
slotStartTime,
|
||||
slotEndTime,
|
||||
slotDuration,
|
||||
slotBreakDuration,
|
||||
)
|
||||
|
||||
const slots: ScheduleSlotType[] = []
|
||||
|
||||
// Loop through each day between start and end dates
|
||||
for (let date = startDate; date <= endDate; date = date.plus({ days: 1 })) {
|
||||
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)) {
|
||||
Logger.log(`Generating slots for date: ${date.toISO()}`)
|
||||
// For each slot number, calculate start and end times
|
||||
for (let i = 1; i <= numberOfSlots; i++) {
|
||||
// loop through slots
|
||||
for (const slot of _scheduleConfig.slots) {
|
||||
// get slot start and end time
|
||||
const { startTime, endTime } = this.getSlotStartAndEndTime(
|
||||
i,
|
||||
slotDuration,
|
||||
slotBreakDuration,
|
||||
slotStartTime,
|
||||
slot,
|
||||
_config.slotDuration,
|
||||
_config.slotBreakDuration,
|
||||
DateTimeUtils.getATimeWithDateB(
|
||||
DateTime.fromISO(_config.dayStartTime),
|
||||
date,
|
||||
).toISO() ?? '',
|
||||
)
|
||||
|
||||
slots.push({
|
||||
slot: i.toString(),
|
||||
start: startTime.toISO() ?? '',
|
||||
end: endTime.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(scheduleConfigFilled: ScheduleConfigType): ScheduleSlotType[] {
|
||||
generateSlots(scheduleConfig: ScheduleConfigType): ScheduleSlotType[] {
|
||||
const slots: ScheduleSlotType[] = []
|
||||
const numberOfSlots = this.calculateNumberOfSlots(
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.slotStartTime,
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.slotEndTime,
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.slotDuration,
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.slotBreakDuration,
|
||||
scheduleConfig.dayStartTime,
|
||||
scheduleConfig.dayEndTime,
|
||||
scheduleConfig.slotDuration,
|
||||
scheduleConfig.slotBreakDuration,
|
||||
)
|
||||
for (let i = 1; i <= numberOfSlots; i++) {
|
||||
const { startTime, endTime } = this.getSlotStartAndEndTime(
|
||||
i,
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.slotDuration,
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.slotBreakDuration,
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.slotStartTime,
|
||||
scheduleConfig.slotDuration,
|
||||
scheduleConfig.slotBreakDuration,
|
||||
scheduleConfig.dayStartTime,
|
||||
)
|
||||
// if the slot is not overlapping with mid day break time, add it to the slots
|
||||
if (
|
||||
!this.isOverLapping(
|
||||
!DateTimeUtils.isOverlap(
|
||||
startTime,
|
||||
endTime,
|
||||
DateTime.fromISO(
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.midDayBreakTimeStart,
|
||||
),
|
||||
DateTime.fromISO(
|
||||
// @ts-ignore
|
||||
scheduleConfigFilled?.midDayBreakTimeEnd,
|
||||
),
|
||||
DateTimeUtils.fromIsoString(scheduleConfig.midDayBreakTimeStart),
|
||||
DateTimeUtils.fromIsoString(scheduleConfig.midDayBreakTimeEnd),
|
||||
)
|
||||
) {
|
||||
slots.push({
|
||||
slot: i.toString(),
|
||||
start: startTime.toString(),
|
||||
end: endTime.toString(),
|
||||
dayOfWeek: startTime.weekday,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -235,13 +231,26 @@ export class ScheduleService {
|
||||
slotDuration: string,
|
||||
slotBreakDuration: string,
|
||||
) {
|
||||
const startDate = DateTime.fromISO(startTime)
|
||||
const endDate = DateTime.fromISO(endTime)
|
||||
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 / (parseInt(slotDuration) + parseInt(slotBreakDuration)),
|
||||
totalMinutes / (_slotDuration + _slotBreakDuration),
|
||||
)
|
||||
return numberOfSlots
|
||||
}
|
||||
@@ -250,12 +259,13 @@ export class ScheduleService {
|
||||
slotNumber: number,
|
||||
slotDuration: string,
|
||||
slotBreakDuration: string,
|
||||
slotStartTime: string,
|
||||
dayStartTime: string,
|
||||
) {
|
||||
const durationInMinutes = parseInt(slotDuration)
|
||||
const breakDurationInMinutes = parseInt(slotBreakDuration)
|
||||
const initialStartTime = DateTime.fromISO(dayStartTime)
|
||||
|
||||
const startTime = DateTime.fromISO(slotStartTime).plus({
|
||||
const startTime = initialStartTime.plus({
|
||||
minutes: (slotNumber - 1) * (durationInMinutes + breakDurationInMinutes),
|
||||
})
|
||||
|
||||
@@ -265,63 +275,4 @@ export class ScheduleService {
|
||||
endTime: endTime,
|
||||
}
|
||||
}
|
||||
|
||||
processScheduleConfig(
|
||||
scheduleConfig: ScheduleConfigType,
|
||||
config: Config[],
|
||||
): ScheduleConfigType {
|
||||
// if scheduleConfig is undefined, create a new object and seed all the values with default values from config
|
||||
if (scheduleConfig === undefined) {
|
||||
scheduleConfig = config.reduce((acc, curr) => {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
;(acc as any)[this.sneakyCaseToCamelCase(curr.key)] = curr.value
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
// loop through scheduleConfig and fill with default values from config
|
||||
for (const key in scheduleConfig) {
|
||||
if (
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
(scheduleConfig as any)[key] === undefined ||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
(scheduleConfig as any)[key] === null ||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
(scheduleConfig as any)[key] === ''
|
||||
) {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
;(scheduleConfig as any)[key] =
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
config[this.camelCaseToUpperSneakyCase(key) as any]
|
||||
}
|
||||
}
|
||||
return scheduleConfig
|
||||
}
|
||||
|
||||
camelCaseToUpperSneakyCase(str: string) {
|
||||
return _.snakeCase(str).toUpperCase()
|
||||
}
|
||||
|
||||
sneakyCaseToCamelCase(str: string) {
|
||||
return _.camelCase(str.toLowerCase())
|
||||
}
|
||||
|
||||
getTodayWithTime(date: DateTime) {
|
||||
let today = DateTime.now()
|
||||
today = today.set({
|
||||
hour: date.hour,
|
||||
minute: date.minute,
|
||||
second: date.second,
|
||||
})
|
||||
return today
|
||||
}
|
||||
|
||||
getSpecificDateWithTime(date: DateTime) {
|
||||
let specificDate = DateTime.now()
|
||||
specificDate = specificDate.set({
|
||||
hour: date.hour,
|
||||
minute: date.minute,
|
||||
second: date.second,
|
||||
})
|
||||
return specificDate
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user