fix some bug and produce many problem to solve later

This commit is contained in:
2024-11-02 16:27:28 +07:00
parent e86d979ddb
commit 1a5577f8e6
15 changed files with 751 additions and 509 deletions

65
src/Schedule/schedule.d.ts vendored Normal file
View 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[]
}

View File

@@ -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,
}),

View File

@@ -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
}
}