2025-05-14 21:45:16 +02:00

157 lines
6.3 KiB
TypeScript

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<T> {
model: T;
input: BookingNewInputSchema;
status: StatusEnum;
availabilities?: AvailabilityComponent;
ctx: ApolloContext;
}
export interface SaveOneBookingInterface<T> extends NewOneBookingInterface<T> {
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<BookingSchema[]> {
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<string[]> {
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<T extends EngineModel<any, any, any>>(data: NewOneBookingInterface<T>): Promise<BookingSchema> {
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<T>({ input, ressourceModel, ressourceId, rest, status, model, ctx, permissions, availabilities });
}
async createOneBookingWithModel<T extends BaseGraphModel>(data: NewOneBookingInterface<T>): Promise<BookingSchema> {
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<T>({ input, ressourceModel, ressourceId, rest, status, model, ctx, permissions, availabilities });
}
async saveBooking<T extends EngineModel<any, any, any> | BaseGraphModel>(data: SaveOneBookingInterface<T>) {
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,
});
}
}