import { EngineModel } from '@seed/engine/EngineModel'; import { BaseGraphModel } from '@seed/graphql/BaseGraphModel'; import { newError } from '@seed/helpers/Error'; import { converDateRangeToDateTime, isTimeSlotInDates, slotToDatesRange } from '@seed/helpers/Utils.dates'; import { checkIfDatesAreInAvailabilities } from '@seed/helpers/Utils.dates.intervals'; import { StatusEnum } from '@seed/interfaces/components'; import { AvailabilityComponent, DateRangeComponent, SlotComponent } from '@seed/interfaces/components.dates'; import { ApolloContext } from '@seed/interfaces/context'; import { baseSearchFunction } from '@seed/services/database/DBRequestService'; import { CheckoutEngineInput } from '@services/module-payments/functions/orders/schemas/order.schema.input'; import { BookingArgsSchema, BookingNewInputSchema } from '@src/bookings/schemas/bookings.input'; import { BookingSchema, BookingDBInterfaceSchema } from '@src/bookings/schemas/bookings.schema'; import { BookingsRessourceEnum } from '@src/__components/components'; import _ from 'lodash'; import { concat } from 'lodash'; import { DateTime } from 'luxon'; import { DurationTypeEnum } from '../components'; import BookingModel from './bookings.engine.model'; export interface NewOneBookingInterface { model: T; input: BookingNewInputSchema; status: StatusEnum; availabilities?: AvailabilityComponent; ctx: ApolloContext; } export interface SaveOneBookingInterface extends NewOneBookingInterface { ressourceModel: BookingsRessourceEnum; ressourceId: string; rest: { capacity: number; comments?: string | undefined; checkoutInfo?: CheckoutEngineInput | undefined; durationType: DurationTypeEnum; startToEnd?: DateRangeComponent | undefined; slot?: SlotComponent | undefined; }; permissions: any; } export class BookingService { async getBookings(args: BookingArgsSchema, ctx?: ApolloContext | null): Promise { const insi: any = { model: new BookingModel() as any, engine: true, query: args, ctx: ctx, }; if (args.pagination) insi.pagination = args.pagination; else insi.all = true; return baseSearchFunction(insi); } async getBookingsToFilter(ressourceType: BookingsRessourceEnum, args: SlotComponent): Promise { const idsToOmit: string[] = []; const bookings = await this.getBookings({ dateRange: { startDate: DateTime.fromJSDate(args.startDate).startOf('day').toJSDate(), endDate: DateTime.fromJSDate(args.endDate).endOf('day').toJSDate(), }, }); if (bookings && bookings.length > 0) { // Get the booking that are in those specifiq hours bookings.forEach((element) => { const bookingDates = converDateRangeToDateTime(element.dates); if (isTimeSlotInDates(bookingDates, args.startTime || '00:00', args.endTime || '23:59')) { const pIndex = _.findIndex(element.paths, { ressourceModel: ressourceType }); if (pIndex !== -1) idsToOmit.push(element.paths[pIndex].ressourceId); } }); } return _.uniq(idsToOmit); } async createOneBookingWithEngine>(data: NewOneBookingInterface): Promise { const { model, status, ctx, input, availabilities } = data; const { ressourceModel, ressourceId, ...rest } = input; const permissions: any = { r: [ctx.ctx.user._id, ...model.permissions.r], w: [ctx.ctx.user._id, ...model.permissions.w], d: [ctx.ctx.user._id, ...model.permissions.d], }; return await this.saveBooking({ input, ressourceModel, ressourceId, rest, status, model, ctx, permissions, availabilities }); } async createOneBookingWithModel(data: NewOneBookingInterface): Promise { const { model, status, ctx, input, availabilities } = data; const { ressourceModel, ressourceId, ...rest } = input; _.remove(model.r, function(n) { return n == "public";}) _.remove(model.w, function(n) { return n == "public";}) _.remove(model.d, function(n) { return n == "public";}) const permissions: any = { r: [ctx.ctx.user._id, ...model.r], w: [ctx.ctx.user._id, ...model.w], d: [ctx.ctx.user._id, ...model.d], }; return await this.saveBooking({ input, ressourceModel, ressourceId, rest, status, model, ctx, permissions, availabilities }); } async saveBooking | BaseGraphModel>(data: SaveOneBookingInterface) { const { ressourceModel, ressourceId, rest, status, model, ctx, permissions, input, availabilities } = data; const zone = process.env.TIMEZONE || 'utc'; const dates: DateRangeComponent[] = []; let ressourceIdsToOmit: string[] = []; // (1) First check, in the bookings already made if (input.durationType == DurationTypeEnum.startToEnd && input.startToEnd) dates.push(input.startToEnd); else if (input.durationType == DurationTypeEnum.slot && input.slot) dates.push(...slotToDatesRange(input.slot)); if (dates.length == 0) throw newError(7001); // (1) First check, in the availabilities if (availabilities) { const inAvailability = checkIfDatesAreInAvailabilities(converDateRangeToDateTime(dates, { zone }), availabilities); if (!inAvailability) throw newError(7002); } // (2) Second check // Verify if no overlaps with others if (input.slot) ressourceIdsToOmit = await this.getBookingsToFilter(ressourceModel, input.slot); // Check if overlap if (ressourceIdsToOmit.includes(ressourceId)) throw newError(7000); const newData: BookingDBInterfaceSchema = { ...rest, status, dates, paths: concat((model as any).getPath()), ownerId: ctx.ctx.user._id, }; return await new BookingModel(newData).saveOne({ additionnalData: { paths: concat((model as any).getPath()), permissions, }, ctx, }); } }