import { schema } from 'nexus' import * as SegmentController from '../../controllers/segment' import moment from 'moment' import * as Auth from '../../utils/auth' import * as GeneralUtils from '../../utils/general' import * as AmenityController from '../../controllers/amenity' import { AmenityOrderByInput, AmenityWhereInput } from 'nexus-plugin-prisma/client' export const Amenity = schema.objectType({ name: 'Amenity', definition(t) { t.model.id() t.model.createdAt() t.model.updatedAt() t.model.title() t.model.description() t.model.info() t.model.hasReservation() t.model.reservationType() t.model.position() t.model.isDeleted() t.model.price() t.model.usageDuration() t.model.maximumUsageTime() t.model.eventDate() t.model.bookingInAdvanceMaxMS() t.model.bookingInAdvanceMinMS() t.model.maximumUsageTime2() t.model.disclaimerFile() t.model.image() t.model.project() t.model.termsFile() t.model.blockedDates() t.model.reservations() t.model.bookingOptions() t.model.seenBy() t.model.images() t.model.sharedWithProjects() }, }) export const TimeSlotType = schema.objectType({ name: 'TimeSlotType', definition(t) { t.int('startMS') t.int('endMS') t.boolean('noMoreBookingAvailable') t.boolean('currentUserBookedThisTimeSlot') t.string('displayText') }, }) export const TimeSlotsForDayInput = schema.objectType({ name: 'TimeSlotsForDayInput', definition(t) { t.int('startMS') t.int('endMS') t.boolean('noMoreBookingAvailable') t.boolean('currentUserBookedThisTimeSlot') t.string('displayText') }, }) export const AmenityMutations = schema.extendType({ type: 'Mutation', definition: (t) => { t.crud.createOneAmenity({ alias: 'createAmenity' }) t.crud.updateOneAmenity({ alias: 'updateAmenity' }) t.crud.deleteOneAmenity({ alias: 'deleteAmenity' }) t.field('createAmenity', { type: 'Amenity', args: { data: schema.arg({ type: 'AmenityCreateInput' }), }, resolve: async (parent, { data }, ctx: NexusContext) => { const createdAmenity = await ctx.prisma.amenity.create({ data: data, select: { id: true, title: true, project: { select: { id: true, // USERS // A }, }, }, }) // const createdAmenity = await ctx.prisma.amenity.createAmenity(data).$fragment( // `{ // id // title // project { // id // users { // ${Fragments.NOTIFICATION_INFO_ON_USER} // } // } // }` // ) // For now until we have more proper notification // Notification.send({ // noSMS: true, // noEmail: true, // project: createdAmenity.project, // users: createdAmenity.project.users, // title: `New amenity`, // titleFr: `Nouvelle commodité ajoutée`, // message: createdAmenity.title, // data: { // id: createdAmenity.id // } // }) // return ctx.prisma.amenity({ id: createdAmenity.id }) return ctx.prisma.amenity.findOne({ where: { id: createdAmenity.id } }) }, }) }, }) export const AmenityQuery = schema.extendType({ type: 'Query', definition(t) { t.crud.amenity() t.crud.amenities({ filtering: true, ordering: true }) t.field('amenities', { type: 'Amenity', list: true, args: { where: schema.arg({ type: 'AmenityWhereInput' }), orderBy: schema.arg({ type: 'AmenityOrderByInput', list: true }), first: schema.arg({ type: 'Int' }), skip: schema.arg({ type: 'Int' }), }, resolve: async ( parent, { where, orderBy, first, skip, }: { where?: AmenityWhereInput orderBy?: AmenityOrderByInput[] first?: number skip?: number }, ctx: NexusContext ) => { return ctx.prisma.amenity.findMany({ where: where, ...(orderBy?.length > 0 && { orderBy: orderBy[0] }), take: first, skip: skip, }) }, }) t.field('getTimeSlotsForDay', { list: true, type: 'TimeSlotsForDayInput', args: { startDate: schema.stringArg({ required: true }), amenityId: schema.stringArg({ required: true }), projectId: schema.stringArg({ required: true }), usageDurationMS: schema.intArg(), }, // Without ts-ignore, it complains... and it's frustrating because the error is weird! // @ts-ignore resolve: async (_, { startDate, amenityId, projectId, usageDurationMS }, ctx) => { // 1 hour by default if (!usageDurationMS) { usageDurationMS = 60 * 60 * 1000 } const endDate = moment(startDate).add(usageDurationMS, 'milliseconds') // Will need to put this somewhere else const convertHoursToMS = (hours) => hours * 1000 * 60 * 60 const convertMinutesToSeconds = (minutes) => minutes * 60 const convertSecondsToMs = (ms) => (ms ? ms * 1000 : null) const currentUserId = Auth.getUserId(ctx) const currentUser = await ctx.prisma.user.findOne({ where: { id: currentUserId } }) const amenity = await ctx.prisma.amenity.findOne({ where: { id: amenityId }, include: { bookingOptions: { include: { segments: true, project: true, businessHours: true } }, }, }) let bookingOption = null if (amenity?.bookingOptions?.length) { // Take only the bookingOption of currentProject let bookingOptionsForProject = amenity.bookingOptions.filter( (bookingOption) => bookingOption?.project?.id === projectId ) // otherwise take any other if (bookingOptionsForProject.length === 0) { bookingOptionsForProject = amenity.bookingOptions } // Try to find it with correct segment await Promise.all( bookingOptionsForProject.map(async (bO) => { if (bO.segments.length) { const users = await SegmentController.getUsersForSegments(bO.segments) if (users.some((u) => u.id === currentUser.id)) { bookingOption = bO } } }) ) // Otherwise, take the one without any segment which is the default one if (!bookingOption) { bookingOption = bookingOptionsForProject.find( (bookingOption) => bookingOption.segments.length === 0 ) } } const reservations = await ctx.prisma.reservation.findMany({ where: { amenity: { id: amenity.id, }, }, }) const reservationsForSelectedDates = amenity.reservationType === 'SINGLE_DATE' || amenity.reservationType === 'SINGLE_DATE_TIME' ? reservations.filter((r) => moment(r.start).isSame(startDate, 'day')) : reservations.filter((r) => { return ( moment(r.start).isSameOrBefore(startDate) && moment(r.end).isSameOrAfter(endDate) ) }) let businessHourForSelectedDate = bookingOption?.businessHours.find( (bH) => bH.dayOfWeek === moment(startDate).day() ) if (!businessHourForSelectedDate) { // Default business hours businessHourForSelectedDate = { isClosed: false, openAtMS: convertHoursToMS(8), closeAtMS: convertHoursToMS(18), } } const allowMultipleBookings = bookingOption?.allowMultipleBookings const timeBetweenSlotMS = convertSecondsToMs( bookingOption?.timeRequiredBetweenEachBookingSeconds || 0 ) const currentTimeMS = convertHoursToMS(moment().hours() + 1) const timeSlots = AmenityController.getTimeSlots({ openAtMS: moment(startDate).isSame(moment(), 'day') ? Math.max(businessHourForSelectedDate.openAtMS, currentTimeMS) : businessHourForSelectedDate.openAtMS, closeAtMS: businessHourForSelectedDate.closeAtMS, usageDurationMS, allowMultipleBookings, timeslotIncrementalMS: convertSecondsToMs( bookingOption?.timeslotIncrementalInSeconds || convertMinutesToSeconds(30) ), maximumUserAtSameTime: bookingOption?.maximumBookingsAtSameTime, timeBetweenSlotMS, existingBookings: reservationsForSelectedDates.map( ({ start, durationMS, numberOfResidents }) => ({ numberOfResidents, start, durationMS: durationMS || (endDate && startDate ? moment(endDate).milliseconds() - moment(startDate).milliseconds() : usageDurationMS), }) ), }) return timeSlots }, }) }, })