import moment from 'moment' import { schema } from 'nexus' // import * as Slack from '../../utils/slack' import * as Auth from '../../utils/auth' import GeneralUtils from '../../utils/general' import { send as sendNotifications } from '../../utils/notification' import { PrismaClient, ReservationGetPayload, ReservationWhereInput, ReservationOrderByInput, } from 'nexus-plugin-prisma/client' import { logger } from '../../utils/logger' const RESERVATION_FIELDS = { id: true, start: true, end: true, status: true, amenity: { select: { id: true, title: true, reservationType: true, bookingOptions: { select: { bookingRequiresStaffApproval: true, }, }, }, }, project: { select: { id: true, name: true, }, }, customer: { select: { id: true, currentProject: { select: { id: true, name: true, managingCompany: { select: { id: true, users: { select: { id: true, }, }, }, }, }, }, }, }, } export const Reservation = schema.objectType({ name: 'Reservation', definition(t) { t.model.id() t.model.status() t.model.createdAt() t.model.updatedAt() t.model.start() t.model.end() t.model.durationDays() t.model.durationMS() t.model.note() t.model.numberOfResidents() t.model.address() t.model.amenity() t.model.customer() t.model.project() t.model.payments() }, }) export const ReservationQueries = schema.extendType({ type: 'Query', definition(t) { t.crud.reservation() t.crud.reservations({ filtering: true, ordering: true, pagination: true }) t.field('reservations', { type: 'Reservation', list: true, args: { where: schema.arg({ type: 'ReservationWhereInput' }), orderBy: schema.arg({ type: 'ReservationOrderByInput', list: true }), first: schema.arg({ type: 'Int' }), skip: schema.arg({ type: 'Int' }), }, resolve: async ( parent, { where, orderBy, first, skip, }: { where?: ReservationWhereInput orderBy?: ReservationOrderByInput[] first?: number skip?: number }, ctx: NexusContext ) => { return ctx.prisma.reservation.findMany({ where: where, ...(orderBy?.length > 0 && { orderBy: orderBy[0] }), take: first, skip: skip, }) }, }) }, }) export const ReservationMutation = schema.extendType({ type: 'Mutation', definition: (t) => { t.crud.deleteOneReservation({ alias: 'deleteReservation' }) t.crud.updateOneReservation({ alias: 'updateReservation' }) t.crud.createOneReservation({ alias: 'createReservation' }) t.field('createReservation', { type: 'Reservation', args: { data: schema.arg({ type: 'ReservationCreateInput' }), }, resolve: async (parent, { data }, ctx: NexusContext) => { const userId = Auth.getUserId(ctx) const currentUser = await ctx.prisma.user.findOne({ where: { id: userId } }) let createdReservation = await ctx.prisma.reservation.create({ data, select: RESERVATION_FIELDS, }) // Automatically approve if doesn't require staff approval const bookingOption = createdReservation.amenity && createdReservation.amenity.bookingOptions ? createdReservation.amenity.bookingOptions[0] : null if (bookingOption && !bookingOption?.bookingRequiresStaffApproval) { createdReservation = await ctx.prisma.reservation.update({ where: { id: createdReservation.id }, data: { status: 'CONFIRMED' }, select: RESERVATION_FIELDS, }) } // Reservation info let reservationInfo = `Amenity: ${createdReservation.amenity.title}\nProject: ${ createdReservation.customer.currentProject.name }\nCustomer: ${await GeneralUtils.getUserNameAndUnits( createdReservation.customer, createdReservation.project )}\nStart: ${moment(createdReservation.start).format('LLLL')}` if (createdReservation.end) { reservationInfo += `\nEnd: ${moment(createdReservation.end).format('LLLL')}` } // If reservation was created by a Manager if (currentUser.role === 'MANAGER') { sendNotifications({ projectId: createdReservation.project.id, users: [createdReservation.customer], title: `New reservation for ${createdReservation.amenity.title}`, message: createdReservation.status === 'PENDING' ? "We've received your reservation. We will let you know when it's confirmed." : `Your reservation for ${createdReservation.amenity.title} on ${moment( createdReservation.start ).format('LLLL')}${ createdReservation.end ? ` - ${moment(createdReservation.end).format('LLLL')}` : '' } is now confirmed.`, type: 'NEW_RESERVATION', data: { id: createdReservation.id, }, }).catch((error) => logger.error(error)) } // Reservation was created by the resident else { // Send to the user to confirm it was well received sendNotifications({ users: [currentUser], title: 'Booking request', titleFr: 'Demande de réservation', message: `Your booking for ${createdReservation.amenity.title} on ${moment( createdReservation.start ).format( createdReservation.amenity.reservationType === 'SINGLE_DATE_TIME' ? 'LLLL' : 'LLL' )} was well received`, messageFr: `Votre demande de réservation pour ${ createdReservation.amenity.title } le ${moment(createdReservation.start) .locale('fr') .format( createdReservation.amenity.reservationType === 'SINGLE_DATE_TIME' ? 'LLLL' : 'LLL' )} a bien été reçue`, type: 'NEW_RESERVATION', projectId: createdReservation.customer.currentProject.id, data: { id: createdReservation.id, }, }).catch((error) => logger.error(error)) // Send to manager sendNotifications({ users: createdReservation.customer.currentProject.managingCompany.users, title: 'New amenity reservation', message: reservationInfo, type: 'NEW_RESERVATION', projectId: createdReservation.customer.currentProject.id, data: { id: createdReservation.id, }, }).catch((error) => logger.error(error)) } return ctx.prisma.reservation.findOne({ where: { id: createdReservation.id } }) }, }) t.field('updateReservation', { type: 'Reservation', args: { data: schema.arg({ type: 'ReservationUpdateInput' }), where: schema.arg({ type: 'ReservationWhereUniqueInput' }), }, resolve: async (parent, { data, where }, ctx: NexusContext) => { const prisma: PrismaClient = ctx.prisma const [updatedReservation, oldReservation] = await Promise.all([ prisma.reservation.update({ data, where, select: RESERVATION_FIELDS }), prisma.reservation.findOne({ where: { ...where }, select: RESERVATION_FIELDS }), ]) let notificationMessage = '' let notificationMessageFr = '' // New date if ( (data.start && oldReservation.start !== data.start) || (data.end && oldReservation.end !== data.end) ) { // prettier-ignore notificationMessage = `Your reservation for ${oldReservation.amenity.title} has changed. New Check in: ${moment(updatedReservation.start).format('LLL')}. New Check out: ${moment(updatedReservation.end).format('LLL')}.` // prettier-ignore notificationMessageFr = `Votre réservation pour '${oldReservation.amenity.title}' a été modifiée. Heure d'arrivé: ${moment(updatedReservation.start).locale('fr').format('LLL')}. Heure de départ: ${moment(updatedReservation.end).locale('fr').format('LLL')}.` } // New status else if (updatedReservation.status !== oldReservation.status) { // prettier-ignore notificationMessage = `Your reservation on ${moment(oldReservation.start).format('LLL')} for ${oldReservation.amenity.title} is now ${updatedReservation.status.toLowerCase()}.` // prettier-ignore notificationMessageFr = `Votre réservation pour '${oldReservation.amenity.title}' le ${moment(oldReservation.start).locale('fr').format('LLL',)} est maintenant '${updatedReservation.status.toLowerCase()}'.` } // New amenity else if ( data.amenity && data.amenity.connect && data.amenity.connect.id && data.amenity.connect.id !== oldReservation.amenity.id ) { // prettier-ignore notificationMessage = `Your reservation for '${oldReservation.amenity.title}' has changed and is now for ${updatedReservation.amenity.title}` // prettier-ignore notificationMessage = `Votre réservation pour '${oldReservation.amenity.title}' a changé et est maintenant rendu pour '${updatedReservation.amenity.title}'` } if (notificationMessage) { await sendNotifications({ title: 'Your reservation has been updated', titleFr: 'Votre réservation a été modifiée', message: notificationMessage, messageFr: notificationMessageFr, type: 'UPDATE_RESERVATION', users: [updatedReservation.customer], data: { id: updatedReservation.id, }, projectId: updatedReservation.project.id, }) // Slack.send({ // channel: 'order', // subject: `Updated reservation`, // text: `Customer: ${GeneralUtils.getUserName(updatedReservation.customer)}\nProject: ${ // updatedReservation.customer.currentProject.name // }\nMessage: ${notificationMessage}` // }) } return prisma.reservation.findOne({ where: { id: updatedReservation.id } }) }, }) t.field('residentBookAmenity', { type: 'Reservation', args: { amenityId: schema.stringArg({ required: true }), projectId: schema.stringArg({ required: true }), start: schema.stringArg({ required: true }), end: schema.stringArg(), note: schema.stringArg(), numberOfResidents: schema.intArg(), }, resolve: async ( parent, { start, end, note, projectId, amenityId, numberOfResidents }, ctx ) => { const userId = Auth.getUserId(ctx) const currentUser = await ctx.prisma.user.findOne({ where: { id: userId } }) let createdReservation = await ctx.prisma.reservation.create({ data: { numberOfResidents, start, end, note, customer: { connect: { id: currentUser.id } }, durationMS: moment.duration(moment(end).diff(moment(start))).asMilliseconds(), project: { connect: { id: projectId } }, amenity: { connect: { id: amenityId } }, }, select: RESERVATION_FIELDS, }) // Automatically approve if doesn't require staff approval const bookingOption = createdReservation.amenity.bookingOptions[0] if (bookingOption && !bookingOption?.bookingRequiresStaffApproval) { createdReservation = await ctx.prisma.reservation.update({ where: { id: createdReservation.id }, data: { status: 'CONFIRMED' }, select: RESERVATION_FIELDS, }) } // Reservation info let reservationInfo = `Amenity: ${createdReservation.amenity.title}\nProject: ${ createdReservation.customer.currentProject.name }\nCustomer: ${await GeneralUtils.getUserNameAndUnits( createdReservation.customer, createdReservation.project )}\nStart: ${moment(createdReservation.start).format('LLLL')}` if (createdReservation.end) { reservationInfo += `\nEnd: ${moment(createdReservation.end).format('LLLL')}` } // Send to the user to confirm it was well received sendNotifications({ users: [currentUser], title: 'Booking request', titleFr: 'Demande de réservation', message: `Your booking for ${createdReservation.amenity.title} on ${moment( createdReservation.start ).format( createdReservation.amenity.reservationType === 'SINGLE_DATE_TIME' ? 'LLLL' : 'LLL' )} was well received`, messageFr: `Votre demande de réservation pour ${ createdReservation.amenity.title } le ${moment(createdReservation.start) .locale('fr') .format( createdReservation.amenity.reservationType === 'SINGLE_DATE_TIME' ? 'LLLL' : 'LLL' )} a bien été reçue`, type: 'NEW_RESERVATION', projectId, data: { id: createdReservation.id, }, }).catch((error) => logger.error(error)) // Send to manager sendNotifications({ users: createdReservation.customer.currentProject.managingCompany.users, title: 'New amenity reservation', message: reservationInfo, type: 'NEW_RESERVATION', projectId: createdReservation.customer.currentProject.id, data: { id: createdReservation.id, }, }).catch((error) => logger.error(error)) return ctx.prisma.reservation.findOne({ where: { id: createdReservation.id } }) }, }) }, })