This commit is contained in:
2024-11-01 20:02:43 +07:00
parent ec77f07de1
commit db9f3213fd
5 changed files with 1039 additions and 975 deletions

View File

@@ -29,9 +29,6 @@ export class OrderSchema extends PothosSchema {
userId: t.exposeID('userId', { userId: t.exposeID('userId', {
description: 'The ID of the user.', description: 'The ID of the user.',
}), }),
paymentId: t.exposeString('paymentId', {
description: 'The ID of the payment.',
}),
serviceId: t.exposeID('serviceId', { serviceId: t.exposeID('serviceId', {
description: 'The ID of the service.', description: 'The ID of the service.',
}), }),
@@ -56,17 +53,17 @@ export class OrderSchema extends PothosSchema {
user: t.relation('user', { user: t.relation('user', {
description: 'The user who made the order.', description: 'The user who made the order.',
}), }),
payment: t.relation('payment', {
description: 'The payment for the order.',
}),
service: t.relation('service', { service: t.relation('service', {
description: 'The service for the order.', description: 'The service for the order.',
}), }),
refundTicket: t.relation('refundTicket', { refundTicket: t.relation('refundTicket', {
description: 'The refund ticket for the order.', description: 'The refund ticket for the order.',
}), }),
schedule: t.relation('schedule', { payment: t.relation('payment', {
description: 'The schedule for the order.', description: 'The payment for the order.',
}),
paymentId: t.exposeString('paymentId', {
description: 'The ID of the payment.',
}), }),
}), }),
}) })

View File

@@ -30,7 +30,7 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
level: 'warn', level: 'warn',
}, },
], ],
errorFormat: 'pretty', errorFormat: 'colorless',
transactionOptions: { transactionOptions: {
maxWait: 30 * 1000, maxWait: 30 * 1000,
timeout: 60 * 1000, timeout: 60 * 1000,

View File

@@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable, Logger } from '@nestjs/common'
import { import {
Pothos, Pothos,
PothosRef, PothosRef,
@@ -10,6 +10,7 @@ import { PrismaService } from '../Prisma/prisma.service'
import { ScheduleStatus } from '@prisma/client' import { ScheduleStatus } from '@prisma/client'
import { ScheduleService } from './schedule.service' import { ScheduleService } from './schedule.service'
import { AppConfigService } from '../AppConfig/appconfig.service' import { AppConfigService } from '../AppConfig/appconfig.service'
import { forEach } from 'lodash'
export type ScheduleConfigType = export type ScheduleConfigType =
| { | {
@@ -63,10 +64,18 @@ export class ScheduleSchema extends PothosSchema {
id: t.exposeID('id', { id: t.exposeID('id', {
description: 'The ID of the schedule.', description: 'The ID of the schedule.',
}), }),
customerId: t.exposeID('customerId', {
description: 'The ID of the customer the schedule belongs to.',
nullable: true,
}),
managedServiceId: t.exposeID('managedServiceId', { managedServiceId: t.exposeID('managedServiceId', {
description: 'The ID of the managed service the schedule belongs to.', description: 'The ID of the managed service the schedule belongs to.',
nullable: false, nullable: false,
}), }),
orderId: t.exposeID('orderId', {
description: 'The ID of the order the schedule belongs to.',
nullable: true,
}),
scheduleStart: t.expose('scheduleStart', { scheduleStart: t.expose('scheduleStart', {
type: 'DateTime', type: 'DateTime',
nullable: false, nullable: false,
@@ -75,6 +84,12 @@ export class ScheduleSchema extends PothosSchema {
type: 'DateTime', type: 'DateTime',
nullable: false, nullable: false,
}), }),
slots: t.exposeIntList('slots', {
nullable: false,
}),
daysOfWeek: t.exposeIntList('daysOfWeek', {
nullable: false,
}),
dates: t.relation('dates', { dates: t.relation('dates', {
description: 'The dates of the schedule.', description: 'The dates of the schedule.',
}), }),
@@ -132,6 +147,18 @@ export class ScheduleSchema extends PothosSchema {
type: 'DateTime', type: 'DateTime',
nullable: false, nullable: false,
}), }),
dayOfWeek: t.exposeInt('dayOfWeek', {
nullable: false,
}),
slot: t.exposeInt('slot', {
nullable: false,
}),
serviceId: t.exposeID('serviceId', {
nullable: false,
}),
orderId: t.exposeID('orderId', {
nullable: true,
}),
schedule: t.relation('schedule', { schedule: t.relation('schedule', {
description: 'The schedule the schedule date belongs to.', description: 'The schedule the schedule date belongs to.',
}), }),
@@ -227,5 +254,78 @@ export class ScheduleSchema extends PothosSchema {
}, },
}), }),
})) }))
/* overlapping case
46836288-bb2c-4da6-892b-a559a480cbf8,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-11-22 00:00:00.000,2024-11-02 00:00:00.000,UNPUBLISHED,,"{3,5}",,"{2,4}"
d72a864e-2f41-45ab-9c9b-bf0512a31883,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-11-22 00:00:00.000,2024-11-02 00:00:00.000,UNPUBLISHED,,"{3,5}",,"{2,4}"
*/
this.builder.mutationFields((t) => ({
// Mutations
createSchedule: t.prismaField({
type: this.schedule(),
description: 'Create a new schedule.',
args: {
schedule: t.arg({
type: this.builder.generator.getCreateInput('Schedule', [
'id',
'status',
'customerId',
'orderId',
]),
required: true,
}),
},
resolve: async (query, _root, args, _ctx, _info) => {
Logger.log('args.schedule', args.schedule)
// check if there is any overlapping schedule
const overlappingSchedules = await this.prisma.schedule.findMany({
where: {
OR: [
{
scheduleStart: {
gte: args.schedule.scheduleStart,
},
scheduleEnd: {
lte: args.schedule.scheduleEnd,
},
},
],
},
})
// check if is same managedServiceId
if (
overlappingSchedules.some(
(schedule) =>
schedule.managedServiceId ===
args.schedule.managedService.connect?.id,
)
) {
throw new Error(
`Overlapping schedule with ${JSON.stringify(
overlappingSchedules.map((schedule) => schedule.id),
)}`,
)
}
const schedule = await this.prisma.schedule.create({
...query,
data: args.schedule,
})
// generate schedule dates based on data and config
const scheduleDates =
await this.scheduleService.generateScheduleDates(schedule)
// update schedule with schedule dates
return await this.prisma.schedule.update({
...query,
where: { id: schedule.id },
data: {
dates: {
connect: scheduleDates.map((date) => ({ id: date.id })),
},
},
})
},
}),
}))
} }
} }

View File

@@ -10,18 +10,27 @@ import {
ScheduleConfigTypeForCenter, ScheduleConfigTypeForCenter,
ScheduleSlotType, ScheduleSlotType,
} from './schedule.schema' } from './schedule.schema'
import { Config } from '@prisma/client' import { Config, Schedule, ScheduleDate } from '@prisma/client'
import { DateTime, Settings, Zone } from 'luxon' import { DateTime, Settings, Zone } from 'luxon'
import * as _ from 'lodash' import * as _ from 'lodash'
Settings.defaultLocale = 'en-US' Settings.defaultLocale = 'en-US'
Settings.defaultZone = 'utc' Settings.defaultZone = 'utc'
// Settings.defaultWeekSettings = { Settings.defaultWeekSettings = {
// firstDay: 2, firstDay: 2,
// minimalDays: 1, minimalDays: 1,
// weekend: [6, 7], weekend: [6, 7],
// } }
interface ScheduleDateInput {
scheduleId: string
start: string
end: string
dayOfWeek: number
slot: number
serviceId: string
orderId: string | null
}
@Injectable() @Injectable()
export class ScheduleService { export class ScheduleService {
constructor( constructor(
@@ -60,8 +69,56 @@ 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 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 scheduleDates: ScheduleDateInput[] = []
// loop each day from scheduleStart to scheduleEnd
let date = DateTime.fromJSDate(scheduleStart)
while (date <= DateTime.fromJSDate(scheduleEnd)) {
// 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 ?? '',
)
scheduleDates.push({
scheduleId: schedule.id,
start: startTime.toISO() ?? '',
end: endTime.toISO() ?? '',
dayOfWeek: date.weekday,
slot: slot,
serviceId: schedule.managedServiceId,
orderId: schedule.orderId,
})
}
}
// Move to the next day
date = date.plus({ days: 1 })
}
const scheduleDatesCreated =
await this.prisma.scheduleDate.createManyAndReturn({
data: scheduleDates,
})
return scheduleDatesCreated
}
generateSlots(scheduleConfigFilled: ScheduleConfigType): ScheduleSlotType[] { generateSlots(scheduleConfigFilled: ScheduleConfigType): ScheduleSlotType[] {
Logger.log(`Generating slots with config: ${scheduleConfigFilled}`)
const slots: ScheduleSlotType[] = [] const slots: ScheduleSlotType[] = []
const numberOfSlots = this.calculateNumberOfSlots( const numberOfSlots = this.calculateNumberOfSlots(
// @ts-ignore // @ts-ignore
@@ -143,15 +200,18 @@ export class ScheduleService {
slotBreakDuration: string, slotBreakDuration: string,
slotStartTime: string, slotStartTime: string,
) { ) {
const durationInMinutes = parseInt(slotDuration); const durationInMinutes = parseInt(slotDuration)
const breakDurationInMinutes = parseInt(slotBreakDuration); const breakDurationInMinutes = parseInt(slotBreakDuration)
const startTime = DateTime.fromISO(slotStartTime).plus({ const startTime = DateTime.fromISO(slotStartTime).plus({
minutes: (slotNumber - 1) * (durationInMinutes + breakDurationInMinutes), minutes: (slotNumber - 1) * (durationInMinutes + breakDurationInMinutes),
}); })
const endTime = startTime.plus({ minutes: durationInMinutes }) const endTime = startTime.plus({ minutes: durationInMinutes })
return { startTime, endTime } return {
startTime: startTime,
endTime: endTime,
}
} }
processScheduleConfig( processScheduleConfig(

File diff suppressed because one or more lines are too long