Untitled

mail@pastecode.io avatar
unknown
plain_text
14 days ago
41 kB
1
Indexable
Never
"use node"
import { PMSPlatformEnum, AvailabilityCalendar } from "../../types"
import { fetchCalendarForListingIdInRange as fetchHostawayCalendarForListingIdInRange } from "../integrations/hostaway"
import { fetchCalendarForListingIdInRange as fetchGuestyCalendarForListingIdInRange } from "../integrations/guesty"
import { getAvailabilityForPropertyWithDates as fetchCalryCalendarForListingIdInRange } from "../integrations/calry"
import { action, internalAction } from "../../_generated/server"
import { Doc, Id } from "../../_generated/dataModel"
import _, { get } from "lodash"
import { logSlackError } from "./slackLogger"
import { getIntegrationFunctions } from "../integrationFlow"
import { UpsellType } from "../../upsells/types"
import { internal } from "../../_generated/api"
import { fetchCalendarWithAvailability } from "../pmsData"
import { api } from "../../_generated/api"
import { fetchAvailabilityForPropertyIdInRange as fetchLodgifyCalendarForListingIdInRange } from "../integrations/lodgify"
import { decrypt } from "./crypto"
import moment from "moment"

export const getAvailabilityResponseWithStartEnd = async ({
    listing,
    reservation,
    pmsIntegration,
    startDate,
    endDate,
}: {
    listing: Doc<"listings">
    pmsIntegration?: Doc<"integrations"> | null
    reservation: Doc<"reservations">
    startDate: string
    endDate: string
}): Promise<{
    response: string
    possible: boolean
    calendar: AvailabilityCalendar
}> => {
    const pmsPlatform = listing?.pmsPlatform
    if (!pmsIntegration) {
        throw new Error(
            `No integration found for ${pmsPlatform}. Please connect to ${pmsPlatform} first.`
        )
    }

    let passedStartDate = startDate
    let passedEndDate = endDate

    // if there isn't a start date,
    //  if there is an end date, set start date to 10 days before end date
    //  else set start date to 1 month before today
    if (!startDate)
        passedStartDate = endDate
            ? changeDateWithDayDelta(endDate, -10)
            : getDateWithMonthDelta(-1)

    // if there isn't an end date, set end date to 10 days after start date
    if (!endDate)
        passedEndDate = changeDateWithDayDelta(startDate, 10)
    let calendar: any[] = []

    // if call is gonna be invalid, swap dates
    if (new Date(passedStartDate) > new Date(passedEndDate)) {
        const temp = passedStartDate
        passedStartDate = passedEndDate
        passedEndDate = temp
    }

    // check if passed dates are YYYY-MM-DD
    if (!passedStartDate.match(/^\d{4}-\d{2}-\d{2}$/)) {
        passedStartDate = moment(passedStartDate).format("YYYY-MM-DD")
    }

    if (!passedEndDate.match(/^\d{4}-\d{2}-\d{2}$/)) {
        passedEndDate = moment(passedEndDate).format("YYYY-MM-DD")
    }

    try {
        calendar = await fetchCalendarWithAvailability(
            pmsIntegration,
            listing.listingId as number,
            passedStartDate,
            passedEndDate
        )

        // const maxDate = new Date(Math.max(...dates.map(date => date.getTime())));

        if (startDate && endDate) {
            return checkAvailabilityWithStartEnd(
                reservation,
                startDate,
                endDate,
                calendar,
                reservation.status?.includes("inquiry") || true
            )
        }

        if (startDate && !endDate) {
            const reservationStart = new Date(reservation.arrivalDate)
            if (
                new Date(startDate).getTime() ===
                reservationStart.getTime()
            ) {
                return {
                    response: `The requested date of ${startDate} is available.`,
                    possible: true,
                    calendar,
                }
            }

            return checkAvailabilityWithStartEnd(
                reservation,
                startDate,
                reservation.arrivalDate,
                calendar,
                reservation.status?.includes("inquiry") || true
            )
        }

        if (!startDate && endDate) {
            const reservationEnd = new Date(reservation.departureDate)
            if (
                new Date(endDate).getTime() ===
                reservationEnd.getTime()
            ) {
                return {
                    response: `The requested date of ${endDate} is available.`,
                    possible: true,
                    calendar,
                }
            }

            return checkAvailabilityWithStartEnd(
                reservation,
                reservation.departureDate,
                endDate,
                calendar,
                reservation.status?.includes("inquiry") || true
            )
        }

        return {
            response: "",
            possible: false,
            calendar,
        }
    } catch (e: any) {
        console.log(e)
        await logSlackError(
            e.message,
            "getAvailabilityResponseWithStartEnd"
        )
        return {
            response: "",
            possible: false,
            calendar,
        }
    }
}

export const getAvailabilityResponseWithSingleDate = async ({
    listing,
    pmsIntegration,
    date,
    request,
}: {
    listing: Doc<"listings">
    pmsIntegration?: Doc<"integrations"> | null
    date: string
    request: string
}): Promise<{
    response: string
    possible: boolean
    calendar: AvailabilityCalendar
}> => {
    const pmsPlatform = listing?.pmsPlatform

    if (!pmsIntegration) {
        throw new Error(
            `No integration found for ${pmsPlatform}. Please connect to ${pmsPlatform} first.`
        )
    }

    let calendar: any[] = []
    let formattedStartDate = getCurrentDateString()
    formattedStartDate = changeDateWithDayDelta(
        formattedStartDate,
        -2
    )
    let formattedEndDate = changeDateWithDayDelta(date, 1)

    if (!formattedStartDate.match(/^\d{4}-\d{2}-\d{2}$/)) {
        formattedStartDate = moment(formattedStartDate).format(
            "YYYY-MM-DD"
        )
    }

    if (!formattedEndDate.match(/^\d{4}-\d{2}-\d{2}$/)) {
        formattedEndDate =
            moment(formattedEndDate).format("YYYY-MM-DD")
    }

    try {
        calendar = await fetchCalendarWithAvailability(
            pmsIntegration,
            listing.listingId as number,
            formattedStartDate,
            formattedEndDate
        )

        // Check dates
        if (!date) {
            return {
                response: `No requested date.`,
                possible: false,
                calendar,
            }
        }

        return checkSingleDateAvailability(date, calendar, request)
    } catch (e: any) {
        console.log(e)
        await logSlackError(
            e.message,
            "getAvailabilityResponseWithStartEnd"
        )
        return {
            response: "",
            possible: false,
            calendar,
        }
    }
}

export const changeDateWithMonthDelta = (
    dateString: string,
    delta: number
): string => {
    const date = new Date(dateString)
    date.setMonth(date.getMonth() + delta)
    const formatted =
        date.getFullYear() +
        "-" +
        (date.getMonth() + 1) +
        "-" +
        date.getDate()

    return formatDateString(formatted)
}

export const changeDateWithDayDelta = (
    dateString: string,
    delta: number
): string => {
    const date = new Date(dateString)
    date.setDate(date.getDate() + delta)
    const formatted =
        date.getFullYear() +
        "-" +
        (date.getMonth() + 1) +
        "-" +
        date.getDate()
    return formatDateString(formatted)
}

export const getDateWithMonthDelta = (delta: number): string => {
    const date = new Date()
    date.setMonth(date.getMonth() + delta)
    const formatted =
        date.getFullYear() +
        "-" +
        (date.getMonth() + 1) +
        "-" +
        date.getDate()
    return formatDateString(formatted)
}

export const getCurrentDateString = () => {
    const today = new Date()
    const date =
        today.getFullYear() +
        "-" +
        (today.getMonth() + 1) +
        "-" +
        today.getDate()
    return formatDateString(date)
}

function formatDateString(dateString: string): string {
    let parts = dateString.split("-")

    // Ensure the year, month, and day parts exist
    if (parts.length !== 3) {
        throw new Error("Invalid date format")
    }

    let [year, month, day] = parts

    // Pad month and day with leading zero if necessary
    month = month.length === 1 ? `0${month}` : month
    day = day.length === 1 ? `0${day}` : day

    return `${year}-${month}-${day}`
}

export type UpsellItem = {
    reservationId: string | number
    newStartDate: string
    newEndDate: string
    priceWithoutDiscount: number
    currency: string
}

export const getBookedNights = async ({
    listingId,
    reservationId,
    startDate,
    endDate,
    pmsIntegration,
}: {
    listingId: number | string
    reservationId: string | number
    startDate: string
    endDate: string
    pmsIntegration: Doc<"integrations">
}): Promise<any[] | null> => {
    let calendar = []
    try {
        calendar = await getCalendarWithReservationDetails({
            listingId,
            reservationId,
            pmsIntegration,
            pmsPlatform:
                pmsIntegration.pmsPlatform as PMSPlatformEnum,
            startDate,
            endDate,
        })

        const bookedNights = calendar
            .filter((item) => !item.isAvailable)
            .map((item) => item.date)

        return bookedNights
    } catch (e: any) {
        console.log(e)
        await logSlackError(e.message, "getBookedNights")
        return null
    }
}

async function getCalendarWithReservationDetails({
    listingId,
    reservationId,
    pmsIntegration,
    pmsPlatform,
    startDate,
    endDate,
}: {
    listingId: number | string
    reservationId: string | number
    pmsIntegration: Doc<"integrations">
    pmsPlatform: PMSPlatformEnum
    startDate: string
    endDate: string
}): Promise<any[]> {
    let calendar = []
    // if dates are more than a month in the past, ignroe
    const oneMonthLookback =
        new Date().getTime() - 30 * 24 * 60 * 60 * 1000
    if (new Date(startDate).getTime() < oneMonthLookback) {
        return []
    }
    try {
        switch (pmsPlatform) {
            case PMSPlatformEnum.HOSTAWAY:
                calendar =
                    await fetchHostawayCalendarForListingIdInRange(
                        pmsIntegration.accessToken!,
                        listingId as number,
                        startDate,
                        endDate
                    )

                calendar = calendar.map((item) => {
                    const { reservations } = item
                    if (reservations.length > 0) {
                        return {
                            ...item,
                            reservation: reservations[0],
                        }
                    } else {
                        return {
                            ...item,
                            reservation: null,
                        }
                    }
                })
                break
            case PMSPlatformEnum.HOSTFULLY:
                const hostfullyEncryptedAccessToken =
                    pmsIntegration?.accessToken
                if (!hostfullyEncryptedAccessToken) {
                    throw new Error("accessToken not provided")
                }
                calendar = (
                    await fetchCalryCalendarForListingIdInRange(
                        decrypt(hostfullyEncryptedAccessToken),
                        listingId as string,
                        startDate,
                        endDate
                    )
                ).data

                calendar = calendar.dateWiseAvailability.map(
                    (item: any) => {
                        const available =
                            item.status == "AVAILABLE" ? 1 : 0

                        const reservationIds =
                            item.reservationIds &&
                            item.reservationIds.length > 0
                                ? item.reservationIds
                                : []
                        return {
                            date: item.date,
                            isAvailable: available ? 1 : 0,
                            reservationIds: reservationIds,
                        }
                    }
                )
                break
            case PMSPlatformEnum.GUESTY:
                calendar =
                    await fetchGuestyCalendarForListingIdInRange(
                        pmsIntegration.accessToken,
                        listingId as string,
                        startDate,
                        endDate
                    )

                calendar = calendar.map((item) => {
                    const isAvailable = _.isNumber(item.allotment)
                        ? item.allotment > 0
                        : item.status === "available"

                    const { reservation } = item
                    if (reservation) {
                        return {
                            ...item,
                            reservation: {
                                ...reservation,
                                id: reservation?._id,
                                arrivalDate:
                                    reservation?.checkInDateLocalized,
                                departureDate:
                                    reservation?.checkOutDateLocalized,
                                currency: item.currency,
                            },
                            isAvailable: isAvailable ? 1 : 0,
                        }
                    } else {
                        return {
                            ...item,
                            reservation: null,
                            isAvailable: isAvailable ? 1 : 0,
                        }
                    }
                })
                break
            case PMSPlatformEnum.LODGIFY:
                const encryptedAccessToken =
                    pmsIntegration?.accessToken
                if (!encryptedAccessToken) {
                    throw new Error("accessToken not provided")
                }
                const accessToken = decrypt(encryptedAccessToken)

                calendar =
                    await fetchLodgifyCalendarForListingIdInRange(
                        accessToken,
                        listingId as string,
                        startDate,
                        endDate
                    )

                if (calendar.length === 0) {
                    return []
                }

                calendar = calendar[0].periods

                calendar = calendar.flatMap((item: any) => {
                    const isAvailable = item.available
                    const startDate = moment(item.start)
                    const endDate = moment(item.end)
                    const { bookings } = item

                    const getDateRange = (
                        start: moment.Moment,
                        end: moment.Moment
                    ): string[] => {
                        let dateArray: string[] = []
                        for (let i = 0; i < 1000; i++) {
                            // Large number to ensure coverage
                            let currentDate = moment(start).add(
                                i,
                                "day"
                            )
                            if (currentDate.isAfter(end)) break
                            dateArray.push(
                                currentDate.format("YYYY-MM-DD")
                            )
                        }
                        return dateArray
                    }

                    const dateList = getDateRange(startDate, endDate)

                    const reservation = bookings ? bookings[0] : null

                    return dateList.map((date) => ({
                        date,
                        reservation: reservation
                            ? {
                                  ...reservation,
                                  id: reservation.id,
                                  arrivalDate:
                                      startDate.format("YYYY-MM-DD"),
                                  departureDate:
                                      endDate.format("YYYY-MM-DD"),
                              }
                            : null,
                        isAvailable: isAvailable ? 1 : 0,
                    }))
                })
                break
            default:
                break
        }

        return calendar
    } catch (e: any) {
        console.log(e)
        await logSlackError(
            e.message,
            "getCalendarWithReservationDetails listingId:" +
                listingId +
                " pmsPlatform" +
                pmsPlatform
        )
        return []
    }
}

export const searchCalendarForGapNight = async ({
    listingId,
    reservationId,
    pmsIntegration,
    checkInDate,
    checkOutDate,
    config,
}: {
    listingId: number | string
    reservationId: string | number
    pmsIntegration: Doc<"integrations">
    checkInDate: string
    checkOutDate: string
    config: Doc<"upsellConfig">
}): Promise<UpsellItem[]> => {
    const pmsPlatform = pmsIntegration.pmsPlatform as PMSPlatformEnum
    if (!pmsIntegration) {
        throw new Error(
            `No integration found for ${pmsPlatform}. Please connect to ${pmsPlatform} first.`
        )
    }

    const { gapSize, daysBefore } = config

    console.log("input checkin", checkInDate)
    console.log("input checkout", checkOutDate)

    let calendar: any[] = []

    const todaysDate = getCurrentDateString()
    let formattedStartDate = changeDateWithDayDelta(checkInDate, -5) // get checkindate - daysbefore - 2
    let formattedEndDate = changeDateWithDayDelta(checkOutDate, 5) // get checkoutdate + 5

    const todayMs = new Date(todaysDate).getTime()

    calendar = await getCalendarWithReservationDetails({
        listingId,
        reservationId,
        pmsIntegration,
        pmsPlatform,
        startDate: formattedStartDate,
        endDate: formattedEndDate,
    })
    console.log("formattedStartDate", formattedStartDate)
    console.log("formattedEndDate", formattedEndDate)

    let upsellItems: UpsellItem[] = []

    console.log(
        "calendar",
        calendar.map((item: any) => ({
            date: item.date,
            available: item.isAvailable,
        }))
    )

    // search for gap nights and add upsell items to gap nights
    for (let i = 0; i < calendar.length; i++) {
        if (!calendar[i]) continue
        const { date } = calendar[i]

        // if the gap size is 1 and the current day is available and the next day is not available
        if (
            gapSize === 1 &&
            i < calendar.length - 1 && // for ioobe
            (!calendar[i - 1] || calendar[i - 1].isAvailable === 0) &&
            calendar[i].isAvailable === 1 && // if current day is available
            calendar[i + 1].isAvailable === 0 && // if next day is not available
            todayMs <= new Date(date).getTime() // if current day is not today
        ) {
            const isPreStay =
                config.type === UpsellType.PreStayGapNight

            // get the reservation from the previous day
            if (!isPreStay && i > 0) {
                const { reservation, reservationIds } =
                    calendar[i - 1]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { arrivalDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: arrivalDate,
                    newEndDate: changeDateWithDayDelta(date, 1), // for checkout, add one day
                    priceWithoutDiscount: calendar[i].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }

            // get the reservation from the next day
            if (isPreStay && i < calendar.length - 1) {
                const { reservation, reservationIds } =
                    calendar[i + 1]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { departureDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: date,
                    newEndDate: departureDate,
                    priceWithoutDiscount: calendar[i].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }
        }

        // gap size 2
        if (
            gapSize === 2 &&
            i < calendar.length - 2 && // for ioobe
            (!calendar[i - 1] || calendar[i - 1].isAvailable === 0) &&
            calendar[i].isAvailable === 1 && // if current day is available
            calendar[i + 1].isAvailable === 1 && // if next day is available
            calendar[i + 2].isAvailable === 0 && // if next next day is not available
            todayMs <= new Date(date).getTime() // if current day is not today
        ) {
            const isPreStay =
                config.type === UpsellType.PreStayGapNight

            // get the reservation from the previous day
            if (!isPreStay && i > 0) {
                const { reservation, reservationIds } =
                    calendar[i - 1]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { arrivalDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: arrivalDate,
                    newEndDate: changeDateWithDayDelta(
                        calendar[i + 1].date,
                        1
                    ), // for checkout, add one day
                    priceWithoutDiscount:
                        calendar[i].price + calendar[i + 1].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }

            // get the reservation from the next reservation (in 2 days because of gap size 2)
            if (isPreStay && i < calendar.length - 2) {
                const { reservation, reservationIds } =
                    calendar[i + 2]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { departureDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: date,
                    newEndDate: departureDate,
                    priceWithoutDiscount:
                        calendar[i].price + calendar[i + 1].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }
        }

        if (
            gapSize === 3 &&
            i < calendar.length - 3 && // for ioobe
            (!calendar[i - 1] || calendar[i - 1].isAvailable === 0) &&
            calendar[i].isAvailable === 1 && // if current day is available
            calendar[i + 1].isAvailable === 1 && // if next day is available
            calendar[i + 2].isAvailable === 1 && // if next day is available
            calendar[i + 3].isAvailable === 0 && // if next next day is not available
            todayMs <= new Date(date).getTime() // if current day is not today
        ) {
            const isPreStay =
                config.type === UpsellType.PreStayGapNight

            // get the reservation from the previous day
            if (!isPreStay && i > 0) {
                const { reservation, reservationIds } =
                    calendar[i - 1]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { arrivalDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: arrivalDate,
                    newEndDate: changeDateWithDayDelta(
                        calendar[i + 2].date,
                        1
                    ), // for checkout, add one day
                    priceWithoutDiscount:
                        calendar[i].price +
                        calendar[i + 1].price +
                        calendar[i + 2].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }

            // get the reservation from the next reservation (in 3 days because of gap size 2)
            if (isPreStay && i < calendar.length - 3) {
                const { reservation, reservationIds } =
                    calendar[i + 3]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { departureDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: date,
                    newEndDate: departureDate,
                    priceWithoutDiscount:
                        calendar[i].price +
                        calendar[i + 1].price +
                        calendar[i + 2].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }
        }

        if (
            gapSize === 4 &&
            i < calendar.length - 4 && // for ioobe
            (!calendar[i - 1] || calendar[i - 1].isAvailable === 0) &&
            calendar[i].isAvailable === 1 && // if current day is available
            calendar[i + 1].isAvailable === 1 && // if next day is available
            calendar[i + 2].isAvailable === 1 && // if next day is available
            calendar[i + 3].isAvailable === 1 && // if next day is available
            calendar[i + 4].isAvailable === 0 && // if next next day is not available
            todayMs <= new Date(date).getTime() // if current day is not today
        ) {
            const isPreStay =
                config.type === UpsellType.PreStayGapNight

            // get the reservation from the previous day
            if (!isPreStay && i > 0) {
                const { reservation, reservationIds } =
                    calendar[i - 1]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { arrivalDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: arrivalDate,
                    newEndDate: changeDateWithDayDelta(
                        calendar[i + 3].date,
                        1
                    ), // for checkout, add one day
                    priceWithoutDiscount:
                        calendar[i].price +
                        calendar[i + 1].price +
                        calendar[i + 2].price +
                        calendar[i + 2].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }

            // get the reservation from the next reservation (in 3 days because of gap size 2)
            if (isPreStay && i < calendar.length - 4) {
                const { reservation, reservationIds } =
                    calendar[i + 4]
                if (!reservation) {
                    if (
                        reservationIds &&
                        reservationIds.length > 0 &&
                        reservationIds.includes(reservationId)
                    ) {
                        const upsellItem: UpsellItem = {
                            reservationId,
                            newStartDate: checkInDate,
                            newEndDate: changeDateWithDayDelta(
                                date,
                                1
                            ), // for checkout, add one day
                            priceWithoutDiscount: calendar[i].price,
                            currency: "",
                        }
                        upsellItems.push(upsellItem)
                    }
                    continue
                }
                const { departureDate } = reservation

                const upsellItem: UpsellItem = {
                    reservationId: reservation.id,
                    newStartDate: date,
                    newEndDate: departureDate,
                    priceWithoutDiscount:
                        calendar[i].price +
                        calendar[i + 1].price +
                        calendar[i + 2].price +
                        calendar[i + 3].price,
                    currency: reservation.currency,
                }

                upsellItems.push(upsellItem)
            }
        }
    }

    return upsellItems
}

function isGapSized(
    calendar: any[],
    i: number,
    gapSize: number
): boolean {
    // Check if there's enough room in the calendar to avoid IndexOutOfBounds
    if (i >= calendar.length - gapSize) {
        return false
    }

    // Check if the current day is available
    if (calendar[i].isAvailable !== 1) {
        return false
    }

    // Check if the subsequent days up to gapSize are available, and the day after the gap is not
    for (let gap = 1; gap <= gapSize; gap++) {
        // If it's the last day of the gap, check if it's not available
        if (gap === gapSize) {
            if (calendar[i + gap].isAvailable !== 0) {
                return false
            }
        } else {
            // For days within the gap, check if they are available
            if (calendar[i + gap].isAvailable !== 1) {
                return false
            }
        }
    }

    // If all conditions are met
    return true
}

function checkAvailabilityWithStartEnd(
    reservation: { arrivalDate: string; departureDate: string },
    startDate: string,
    endDate: string,
    calendar: AvailabilityCalendar,
    isInquiry: boolean
): {
    response: string
    possible: boolean
    calendar: AvailabilityCalendar
} {
    const reservationStart = new Date(reservation.arrivalDate)
    const reservationEnd = new Date(reservation.departureDate)
    const requestedStart = new Date(startDate)
    const requestedEnd = new Date(endDate)

    let intervalsToCheck: [Date, Date][] = []

    if (isInquiry) {
        intervalsToCheck.push([reservationStart, reservationEnd])
    } else {
        if (
            reservationStart.getTime() === requestedStart.getTime() &&
            reservationEnd.getTime() === requestedEnd.getTime()
        ) {
            return {
                response: `The requested dates of ${startDate} to ${endDate} are available. The request from the guest is possible.`,
                possible: true,
                calendar,
            }
        }

        // Define intervals to check based on conditions
        if (reservationStart.getTime() === requestedStart.getTime()) {
            intervalsToCheck.push([reservationEnd, requestedEnd])
        } else if (
            reservationEnd.getTime() === requestedEnd.getTime()
        ) {
            intervalsToCheck.push([requestedStart, reservationStart])
        } else if (requestedStart < reservationStart) {
            if (requestedEnd < reservationStart) {
                intervalsToCheck.push([requestedStart, requestedEnd])
            } else {
                intervalsToCheck.push([
                    requestedStart,
                    reservationStart,
                ])
                if (requestedEnd >= reservationEnd) {
                    intervalsToCheck.push([
                        reservationEnd,
                        requestedEnd,
                    ])
                }
            }
        } else {
            if (requestedEnd < reservationEnd) {
                return {
                    response: `The requested dates of ${startDate} to ${endDate} are available. The request from the guest is possible.`,
                    possible: true,
                    calendar,
                }
            } else {
                intervalsToCheck.push([reservationEnd, requestedEnd])
            }
        }
    }

    // Check intervals
    const isUnavailable = intervalsToCheck.some(([start, end]) =>
        calendar.some(
            (item) =>
                new Date(item.date) >= start &&
                new Date(item.date) < end &&
                item.isAvailable === 0
        )
    )

    if (isUnavailable) {
        console.log(
            `The requested dates of ${startDate} to ${endDate} are not available. The request from the guest is not possible.`
        )
        return {
            response: `The requested dates of ${startDate} to ${endDate} are not available. The request from the guest is not possible.`,
            possible: false,
            calendar,
        }
    }

    return {
        response: `The requested dates of ${startDate} to ${endDate} are available. The request from the guest is possible.`,
        possible: true,
        calendar,
    }
}

function checkSingleDateAvailability(
    date: string | null,
    calendar: AvailabilityCalendar,
    request?: string
): {
    response: string
    possible: boolean
    calendar: AvailabilityCalendar
} {
    if (!date)
        return {
            response: "No date provided.",
            possible: true,
            calendar,
        }

    console.log("date to check", date)
    console.log("calendar", calendar)

    const isAvailable = calendar.some(
        (item) =>
            moment(item.date).isSame(moment(date), "day") &&
            item.isAvailable === 1
    )

    console.log("isAvailable", isAvailable)

    let processedDate = date
    if (request === "Early Check-in") {
        processedDate = changeDateWithDayDelta(date, 1)
    }

    if (isAvailable) {
        console.log(
            `The requested date of ${processedDate} is available.`
        )
        return {
            response: request
                ? `The requested date of ${processedDate} is available for ${request}. The request from the guest is possible.`
                : `The requested dates for ${processedDate} is available. The request from the guest is possible.`,
            possible: true,
            calendar,
        }
    }

    console.log(
        `The requested date of ${processedDate} is not available.`
    )
    return {
        response: request
            ? `The guest request for ${request} on ${processedDate} is not available. So the request cannot be fulfilled.`
            : `The requested date of ${processedDate} is not available.t The request from the guest is not possible.`,
        possible: false,
        calendar,
    }
}
Leave a Comment