Source: lib/QSR.js

/**
 * QSR Class File
 * @author Steve Ostopchuck
 */
const crypto = require('crypto');

/**
 *  Class for interacting with the QSR Enterprize API 
 */
class QSR {
    'use strict'
    /**
     * Create a QSR instance.
     * @param {string} companyUID - Globally unique identifier for a company. This is provided by QSR.
     * @param {Object} [apiKeys] - Optional. API Keys Object.
     * @param {string} [apiKeys.accessKey] - Optional. Access Key provided by QSR. Defaults to environment variable QSR_ACCESSKEY.
     * @param {string} [apiKeys.secretKey] - Optional. Secret Key provided by QSR. Defaults to environment variable QSR_SECRETKEY.
     */
    constructor(companyUID, apiKeys = {
        accessKey: process.env.QSR_ACCESSKEY,
        secretKey: process.env.QSR_SECRETKEY
    }) {
        if (!companyUID) throw "QSR Error: Cannot instantiate object, companyUID missing.";
        if (!apiKeys.accessKey) throw "QSR Error: Cannot instantiate object, apiKeys.accessKey missing.";
        if (!apiKeys.secretKey) throw "QSR Error: Cannot instantiate object, apiKeys.secretKey missing.";
        
        this.companyUID = companyUID;
        this.accessKey = apiKeys.accessKey;
        this.secretKey = apiKeys.secretKey;
        this.axios = require('axios');
        this.axios.defaults.baseURL = 'https://api.dinetime.com'

        this.axios.interceptors.request.use(config => {
            // Step 1: Create a Canonical Request
            const requestMethod = config.method.toUpperCase() || 'GET';
            const canonicalUrl = encodeURIComponent(config.url);
            const params = new URLSearchParams(config.params);
            params.sort();
            const body = (new URLSearchParams(config.data ? config.data : '')).toString();
            const bodyHexHash = crypto.createHash('SHA256').update(body).digest('hex');
            const canonicalRequeast = `${requestMethod}&${canonicalUrl}&${params.toString()}&${bodyHexHash}`;

            // Step 2: Create a String to Sign
            const algorithm = 'HMAC-SHA1';
            const isoDate = (new Date()).toISOString();
            const crHexHash = crypto.createHash('SHA256').update(canonicalRequeast).digest('hex');
            const stringToSign = `${algorithm}&${isoDate}&${this.accessKey}&${crHexHash}`;

            // Step 3: Create the Signature
            const signature = crypto.createHmac('SHA1', this.secretKey).update(stringToSign).digest('hex');

            // Step 4: Add Sigining Inforation to the Request
            const sigVersion = 'dinetime-sv2-hmac-sha1'
            config.headers.Authorization = `${sigVersion} Algorithm=SHA256&Credentials=${this.accessKey}&Signature=${signature}`
            
            // Add other required headers
            config.headers['x-dinetime-timestamp'] = isoDate;
            config.headers['x-dinetime-signature-version'] = sigVersion;

            return config;
        }, err => { return Promise.reject(err) });
    }

    /**
     * Internal error handling function
     * @param {Object} err - The error thrown by axios
     */
    #errorHandler(err) {
        switch (err.response?.status) {
            case 401:
            case 403:
                console.error('QSR Error: Not Authorized')
                break;
            case 404:
                console.error('QSR Error: The visit was not found.');
                break;
            case 405:
                console.error('QSR Error: The visit has already arrived or been seated.');
                break;
            case 410:
                console.error('QSR Error: The WebAhead is no longer considered Active.');
                break;
            case 500:
                console.error('QSR Error: API server error')
                break;
            default:
                break;
        }
        throw err;
    }

    /**
     * Get information of all active sites for a company by CompanyUID.
     * @returns {Site[]} - A collection of Site objects
     */
    async getCompanySites() {
        const config = {
            url: `/Company/${this.companyUID}/Sites`,
            method: 'GET',
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get information for a site by SiteUID.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @returns {Site} - Site object
     */
    async getSite(siteUID) {
        const config = {
            url: `/Site/${siteUID}`,
            method: 'GET',
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get information about Concepts by CompanyUID.
     * @returns {string} - BrandUID - A globally unique identifier for a Brand/Concept
     */
    async getBrands() {
        const config = {
            url: `/Companies/${this.companyUID}/Brands`,
            method: 'GET',
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get SiteUID and CustomerSiteID for all sites within a Company.
     * @param {boolean} onlyActiveSites If true, only records for active sites returned.
     * @returns {string} - CustomerSiteID - Customizable site identifier
     */
    async getCustomerSiteIdMap(onlyActiveSites) {
        const config = {
            url: `/Companies/${this.companyUID}/Sites/CustomerSiteIdMap`,
            method: 'GET',
            params: {}
        };
        if (onlyActiveSites) config.params[getOnlyActiveSites] = true ;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get the list of operating information.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @returns {OperatingInfo[]} - Collection of operating information.
     */
    async getOperatingInfo(siteUID) {
        const config = {
            url: `/Site/${siteUID}/operatingInfo`,
            method: 'GET',
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Gets the list of team members for the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time of updates.
     * @param {Date} endTime - End time of updates.
     * @returns {TeamMember[]} - Collection of TeamMember.
     */
    async getTeamMembers(siteUID, startTime, endTime) {
        const config = {
            url: `/Site/${siteUID}/TeamMembers`,
            method: 'GET',
            params: {}
        };
        if (startTime) config.params['startTime'] = startTime.toISOString();
        if (endTime) config.params['endTime'] = endTime.toISOString();
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get the list of all team member events for the specified site within a specified time range. 
     * This is ordered by last server update timestamp of the records.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time of updates.
     * @param {Date} endTime - End time of updates.
     * @param {number} [numPages] - Optional. Limit number of pages (100 records) to return.
     * @returns {TeamMemberEvent[]} - Collection of TeamMemberEvent.
     */
    async getAllTeamMemberEvents(siteUID, startTime, endTime, numPages = 0) {
        const config = {
            url: `/Site/${siteUID}/TeamMembers/Events`,
            method: 'GET',
            params: {
                startTime: startTime.toISOString(),
                endTime: endTime.toISOString()
            }
        };
        const data = [];
        let response;
        do {
            response = await this.axios.request(config).catch(this.#errorHandler);
            data.push(...response.data.Events)
            config.params.startTime = response.data.TimeStampCutoff;
            numPages--;
        } while (response.data.MoreData && numPages !== 0);
        return data;
    }

    /**
     * Get the paged list of team member events for the specified site within a specified time range. 
     * This is ordered by last server update timestamp of the records.  Page size is 100.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time of updates.
     * @param {Date} endTime - End time of updates.
     * @returns {TeamMemberEventPage} - TeamMemberEventPage.
     */
     async getTeamMemberEvents(siteUID, startTime, endTime) {
        const config = {
            url: `/Site/${siteUID}/TeamMembers/Events`,
            method: 'GET',
            params: {
                startTime: startTime.toISOString(),
                endTime: endTime.toISOString()
            }
        };
        const data = [];
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Add team members to the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {TeamMember} teamMember - TeamMember object to add
     * @returns {TeamMember[]} - Collection of TeamMember.
     */
    async addTeamMember(siteUID, teamMember) {
        const config = {
            url: `/Site/${siteUID}/TeamMembers`,
            method: 'POST',
            data: teamMember
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Update team members for the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} teamMemberId - Globally unique identifier of the TeamMember record.
     * @param {TeamMemeber} teamMemberUpdate - TeamMember object to update from.
     * @returns {TeamMemeber[]} - Collection of TeamMember.
     */
    async updateTeamMember(siteUID, teamMemberId, teamMemberUpdate) {
        const config = {
            url: `/Site/${siteUID}/TeamMembers/${teamMemberId}`,
            method: 'PATCH',
            data: teamMemberUpdate
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Delete team members for the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} teamMemberId - Globally unique identifier of the TeamMember record.
     * @returns {boolean} - Returns true if successful.
     */
    async removeTeamMember(siteUID, teamMemberId, teamMemberUpdate) {
        const config = {
            url: `/Site/${siteUID}/TeamMembers/${teamMemberId}`,
            method: 'DELETE',
            data: teamMemberUpdate
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Gets the list of tables for the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @returns {Object[]} - Collection of Table objects.
     */
    async getTables(siteUID) {
        const config = {
            url: `/Site/${siteUID}/Tables`,
            method: 'GET'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    } 

    /**
     * Gets the list of table statuses for the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time in ISO 8601 format.
     * @param {Date} endTime - End time in ISO 8601 format.
     * @returns {TableStatus[]} - Collection of Table statuses.
     */
    async getTableStatus(siteUID, startTime, endTime) {
        const config = {
            url: `/Site/${siteUID}/Tables/Status`,
            method: 'GET',
            params: {
                startTime: startTime.toISOString(),
                endTime: endTime.toISOString()
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get the list of all table history updates for a specified site within a specified time range. 
     * This is ordered by last server update timestamp of the records.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time of updates.
     * @param {Date} endTime - End time of updates.
     * @param {number} [numPages] - Optional. Limit number of pages (100 records) to return.
     * @returns {TableHistory[]} - Collection of TableHistory.
     */
    async getAllTableHistory(siteUID, startTime, endTime, numPages = 0) {
        const config = {
            url: `/Site/${siteUID}/Tables/History`,
            method: 'GET',
            params: {
                startTime: startTime.toISOString(),
                endTime: endTime.toISOString()
            }
        };
        const data = [];
        let response;
        do {
            response = await this.axios.request(config).catch(this.#errorHandler);
            data.push(...response.data.History)
            config.params.startTime = response.data.TimeStampCutoff;
            numPages--;
        } while (response.data.HasMoreData && numPages !== 0);
        return data;
    }

    /**
     * Get the paged list of table history updates for a specified site within a specified time range. 
     * This is ordered by last server update timestamp of the records. Page size is 100.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time of updates.
     * @param {Date} endTime - End time of updates.
     * @returns {TableHiTableHistoryPage} - TableHiTableHistoryPage.
     */
     async getTableHistory(siteUID, startTime, endTime) {
        const config = {
            url: `/Site/${siteUID}/Tables/History`,
            method: 'GET',
            params: {
                startTime: startTime.toISOString(),
                endTime: endTime.toISOString()
            }
        };
        const data = [];
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get the list of all table events updates for a specified site within a specified time range. 
     * This is ordered by last server update timestamp of the records. 
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time of updates.
     * @param {Date} endTime - End time of updates.
     * @param {number} [numPages] - Optional. Number of pages (100 records) to return.
     * @returns {TableEvent[]} - Collection of TableEvent.
     */
    async getAllTableEvents(siteUID, startTime, endTime, numPages = 0) {
        const config = {
            url: `/Site/${siteUID}/Tables/Events`,
            method: 'GET',
            params: {
                startTime: startTime.toISOString(),
                endTime: endTime.toISOString()
            }
        };
        const data = [];
        let response;
        do {
            response = await this.axios.request(config).catch(this.#errorHandler);
            data.push(...response.data.Events)
            config.params.startTime = response.data.TimeStampCutoff;
            numPages--;
        } while (response.data.MoreData && numPages !== 0);
        return data;
    }

    /**
     * Get the paged list of table events updates for a specified site within a specified time range. 
     * This is ordered by last server update timestamp of the records. Page size is 100.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} startTime - Start time of updates.
     * @param {Date} endTime - End time of updates.
     * @returns {TableEventPage} - TableEventPage.
     */
     async getTableEvents(siteUID, startTime, endTime) {
        const config = {
            url: `/Site/${siteUID}/Tables/Events`,
            method: 'GET',
            params: {
                startTime: startTime.toISOString(),
                endTime: endTime.toISOString()
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Raise a table event for the specified table at the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Object} info - The table event to post.
     * @param {string} info.eventType - Type of TableEvent. The following are considered acceptable values: CheckPaid, TableScanned, CheckPartialPayment, CourseComplete, CheckPrinted, ItemsOrdered, TableOpened, TableCleared, TableDirtied.
     * @param {string} info.tableName - Name of the specified table.
     * @param {string} info.timestampUtc - Time stamp of the TableEvent in UTC (in ISO8601 format).
     * @param {number} [info.transactionNumber] - Time stamp of the TableEvent in UTC (in ISO8601 format).
     * @param {number} [info.checkAmount] - Time stamp of the TableEvent in UTC (in ISO8601 format).
     * @param {string} [info.id] - Time stamp of the TableEvent in UTC (in ISO8601 format).
     * @returns {boolean} - True if is successful.
     */
    async addTableEvent(siteUID, info = {}) {
        const config = {
            url: `/Site/${siteUID}/Visit/TableEvent`,
            method: 'POST',
            data: {
                EventType: info.eventType,
                TableName: info.tableName,
                TimstampUtc: info.timestampUtc
            }
        };
        if (info.transactionNumber) config.data.TransactionNumber = info.transactionNumber;
        if (info.checkAmount) config.data.CheckAmount = info.checkAmount;
        if (info.id) config.data.ID = info.id;
        const response = await this.axios.request(config).catch(this.#errorHandler);   
        return response.status === 200;
    }


    /**
     * Arrive a Visit.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - Globally unique identifier for a QSR visit record (WebAhead, reservation, or walk-in).
     * @returns {boolean} - Returns true is successful.
     */
    async arriveReservation(siteUID, visitID) {
        const config = {
            url: `/Site/${siteUID}/reservations/${visitID}/Arrive`,
            method: 'POST'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Gets the reservation availability for the specified site, the target business date, and target party size.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Date} date - Target date.
     * @param {number} partySize - Size of the party.
     * @returns {ReservationAvailabilityDay[]} - Collection of ReservationAvailabilityDay, each representing availability for a calendar date. 
     */
    async getReservationAvailability(siteUID, date, partySize) {
        const config = {
            url: `/Site/${siteUID}/reservations/availability`,
            method: 'GET',
            params: {
                date: date.toISOString(),
                partySize: Math.floor(partySize)
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Gets the reservation visit for the specified site and the specified confirmation number.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} confimationNumber - Confirmation number of the reservation.
     * @returns {Visit} - QSR visit record.
     */
    async getReservationByConfirmation(siteUID, confimationNumber) {
        const config = {
            url: `/site/${siteUID}/reservations`,
            method: 'GET',
            params: {
                conf: confimationNumber
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Adds a reservation to the site for the specified date and time.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Object} info - Information for the reservation.
     * @param {Date} info.arrivalTime - Reservation time.
     * @param {number} info.partySize - Party size of the reservation.
     * @param {string} [info.email] - Optional. Email of the guest.
     * @param {string} [info.firstName] - Optional. First name of the guest.
     * @param {string} [info.lastName] - Optional. Last name of the guest.
     * @param {string} [info.guestId] - Optional. Unique identifier of a guest record. 
     * @param {boolean} [info.subedToSms] - Optional. Flag to indicate whether guest is subscribed to SMS marketing. 
     * @param {boolean} [info.subedToEmail] - Optional. Flag to indicate whether guest is subscribed to email marketing. 
     * @param {boolean} [info.subedToQsr] - Optional. Flag to indicate whether guest is subscribed to QSR's marketing. 
     * @param {string} [info.notes] - Optional. Notes of the reservation. 
     * @param {string} [info.pagerId] - Optional. ID number of associated pager. 
     * @param {string} [info.phoneNumber] - Optional. Phone number of the guest, numeric only. 
     * @param {string} [info.phoneNumberString] - Optional. Phone number of the guest, formatted.
     * @param {string} [info.seatingAreaUID] - Optional. Unique identifier of the expected seating area for the reservation.
     * @param {string} [info.notificationType] - Optional. Set Notification Type to None, Call, SMS, or Pager.
     * @returns {Visit} - QSR visit record.
     */
     async addReservation(siteUID, info) {
        const config = {
            url: `/site/${siteUID}/reservations`,
            method: 'GET',
            params: {
                EstimatedArrivalTime: arrivalTime.toISOString(),
                PartySize: Math.floor(info.partSize)
            }
        };
        if (info.email) params.Email = info.email;
        if (info.firstName) params.FirstName = info.firstName;
        if (info.lastName) params.LastName = info.lastName;
        if (info.guestId) params.GuestId = info.guestId;
        if (info.subedToSms) params.IsSubscribedToSmsMarketing = info.subedToSms;
        if (info.subedToEmail) params.IsSubscribedToEmailMarketing = info.subedToEmail;
        if (info.subedToQsr) params.IsSubscribedToQsrMarketing = info.subedToQsr;
        if (info.notes) params.Notes = info.notes;
        if (info.pagerId) params.PagerId = info.pagerId;
        if (info.phoneNumber) params.PhoneNumber = info.phoneNumber;
        if (info.phoneNumberString) params.PhoneNumberString = info.phoneNumberString;
        if (info.seatingAreaUID) params.SeatingAreaUID = info.seatingAreaUID;
        if (info.notificationType) params.NotificationType = info.notificationType;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Adds a reservation to the site for the specified date and time.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitUID - Globally unique identifier for a site.
     * @param {Object} info - Information for the reservation.
     * @param {Date} info.arrivalTime - Reservation time.
     * @param {number} info.partySize - Party size of the reservation.
     * @param {string} [info.notes] - Optional. Notes of the reservation. 
     * @param {string} [info.pagerId] - Optional. ID number of associated pager. 
     * @param {string} [info.phoneNumber] - Optional. Phone number of the guest, numeric only. 
     * @param {string} [info.notificationType] - Optional. Set Notification Type to None, Call, SMS, or Pager.
     * @param {string} [info.foodAllergies] - Optional. The guest's food allergies.
     * @returns {boolean} - True if successful.
     */
     async updateReservation(siteUID, visitID, info) {
        const config = {
            url: `/site/${siteUID}/reservations/${visitID}`,
            method: 'PATCH'
        };
        if (info.arrivalTime) params.EstimatedArrivalTime = info.arrivalTime;
        if (info.partySize) params.PartySize = Math.floor(info.partySize);
        if (info.notes) params.Notes = info.notes;
        if (info.pagerId) params.PagerId = info.pagerId;
        if (info.phoneNumber) params.PhoneNumber = info.phoneNumber;
        if (info.notificationType) params.NotificationType = info.notificationType;
        if (info.foodAllergies) params.FoodAllergies = info.foodAllergies;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Cancel the specified reservation visit for the specified site.
     * Note: Sync bypasses thea vailability check.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitUID - Globally unique identifier for a site.
     * @returns {boolean} - True if successful.
     */
    async removeReservation(siteUID, visitUID) {
        const config = {
            url: `/site/${siteUID}/reservations/${visitID}`,
            method: 'DELETE'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Sync a reservation record to DineTime Enterprise.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {*} externalId - External identifier of the reservation.
     * @param {Object} info - External reservation information.
     * @param {string} info.arrivalTime - Required for new reservation, Optional for exiting.  Reservation Time.
     * @param {number} info.partySize - Party size of the reservation.
     * @param {Guest} info.guest - Required for new reservation, Optional for exiting. Guest record of the reservation.
     * @param {number} info.size - Required for new reservation, Optional for exiting. Party size of the reservation.
     * @param {Date} [info.canceledTime] - Optional. Reservation cancellation timestamp.
     * @param {Object[]} [info.customValues] - Optional. Collection of VisitCustomValue objects representing visit custom value.
     * @param {string} [info.notes] - Optional. Notes of the reservation cancellation timestamp.
     * @param {Object} [info.partyMix] - Optional. Visit party mix data.
     * @param {string} [info.syncSource] - Optional. String representing source of the reservation record.
     * @returns {boolean} - True if successful.
     */
    async syncExternalReservation(siteUID, externalId, info) {
        const config = {
            url: `/site/${siteUID}/externalreservations/${externalId}`,
            method: 'PUT'
        };
        if (info.arrivalTime) params.EstimatedArrivalTime = info.arrivalTime;
        if (info.partySize) params.PartySize = Math.floor(info.partySize);
        if (info.guest) params.Guest = info.guest;
        if (info.size) params.Size = Math.floor(info.size);
        if (info.canceledTime) params.CanceledTime = info.canceledTime;
        if (info.customValues) params.CustomValues = info.customValues;
        if (info.notes) params.Notes = info.notes;
        if (info.partyMix) params.PartyMix = info.partyMix;
        if (info.syncSource) params.SyncSource = info.syncSource;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Adds an arrived WalkIn visit. 
     * Providing Guest.ID or Guest.Loyalty.LoyaltyCardID indicates an existing QSR guest record.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {*} info - WalkIn information.
     * @param {number} info.partySize - Party size of the reservation.
     * @param {string} [info.arrivalTime] - Optional. Arrival Time.
     * @param {string} [info.externalId] - Optional. External identifier of the visit.
     * @param {Guest|string} [info.guest] - Optional. Guest record of the reservation | Guest.ID or Guest.Loyalty.LoyaltyCardID
     * @param {string} [info.notes] - Optional. Notes of the reservation cancellation timestamp.
     * @returns {Visit} - QSR visit record. 
     */
    async addWalkIn(siteUID, info) {
        const config = {
            url: `/Site/${siteUID}/WalkIn`,
            method: 'POST'
        };
        if (info.partySize) params.PartySize = Math.floor(info.partySize);
        if (info.arrivalTime) params.ArrivalTime = info.arrivalTime;
        if (info.externalId) params.ExternalID = info.externalId;
        if (info.guest) params.Guest = info.guest;
        if (info.notes) params.Notes = info.notes;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get a list of all sites associated with the partner on the API key.
     * @param {number} [numSites] - Optional.  Number of sites to return. Default all.
     * @returns {Object[]} - Associated sites.
     */
    async getAllPartnerSites(numSites = 0) {
        const config = {
            url: `/Site/Sites`,
            method: 'GET'
        };
        const data = [];
        let response;
        do {
            response = await this.axios.request(config).catch(this.#errorHandler);
            data.push({
                pageData: response.data.pageData,
                siteUID: response.data.SiteUID,
                externalSiteID: response.data.ExternalSiteID
            });
            config.params = { Token: response.data.Token };
            numSites--;
        } while (response.data.HasMore && numSites !== 0);
        return data;
    }

    /**
     * Get a list of all sites associated with the partner on the API key.
     * @param {number} [numSites] - Optional.  Number of sites to return. Default all.
     * @returns {GetPartnerSites} - GetPartnerSites.
     */
     async getPartnerSites(numSites = 0) {
        const config = {
            url: `/Site/Sites`,
            method: 'GET'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Adds a WebAhead, therefore adding the specified party to the Waitlist.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {Object} info - Information object for WebAhead.
     * @param {number} info.partySize - Party Size
     * @param {string} info.phoneNumberString - Formatted phone number string.
     * @param {string} info.lastName - Optional if GuestID is provided.  Last name.
     * @param {string} [info.firstName] - First name
     * @param {string} [info.email] - Email
     * @param {string} [info.guestID] - Unique identifier of a QSR guest record. If GuestID is provided, guest information provided will be applied to the specified guest record.
     * @param {string} [info.estimatedArrivalTime] - Estimated arrival time in ISO 8601 format.
     * @param {boolean} [info.expandGuest] - Specifies whether a guest record should be included in the returned WebAhead data.
     * @param {string} [info.notes] - Notes
     * @param {string} [info.notificationType] - Set Notification Type to None, Call, SMS, or Pager.
     * @param {boolean} [info.isSubedToSms] - Flag to indicate whether guest subscribes to SMS marketing.
     * @param {boolean} [info.isSubedToEmail] - Flag to indicate whether guest subscribes to email marketing.
     * @param {boolean} [info.isSubedToQsr] - Flag to indicate whether guest subscribes to QSR's marketing.
     * @returns {boolean} - True if post is successful.
     */
    async addWalkIn(siteUID, info) {
        const config = {
            url: `/Site/${siteUID}/WebAhead`,
            method: 'POST',
            data: {
                PartySize: Math.floor(info.partSize),
                PhoneNumberString: info.phoneNumberString
            }
        };
        if (info.lastName) params.LastName = info.lastName;
        if (info.email) params.Email = info.email;
        if (info.estimatedArrivalTime) params.EstimatedArrivalTime = info.estimatedArrivalTime;
        if (info.expandGuest) params.ExpandGuest = info.expandGuest;
        if (info.firstName) params.FirstName = info.firstName;
        if (info.guestID) params.GuestID = info.guestID;
        if (info.notes) params.Notes = info.notes;
        if (info.notificationType) params.NotificationType = info.notificationType;
        if (info.isSubedToSms) params.IsSubscribedToSmsMarketing = info.isSubedToSms;
        if (info.isSubedToEmail) params.IsSubscribedToEmailMarketing = info.isSubedToEmail;
        if (info.isSubedToQsr) params.IsSubscribedToQsrMarketing = info.isSubedToQsr;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Get wait list status and quote for web ahead based on party size.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {number} partySize - Party size.
     * @returns {WaitListStatus} - WaitListStatus.
     */
    async getWaitListStatus(siteUID, partySize) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/Status`,
            method: 'GET',
            params: {
                PartySize: Math.floor(partySize)
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get wait list status and quote for web ahead for multiple party sizes.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {number[]} partySizes - Array of party sizes.
     * @returns {WaitListStatus} - WaitListStatus with quotes.
     */
    async getWaitListStatus(siteUID, partySizes = []) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/StatusforPartySize`,
            method: 'GET',
            params: {}
        };
        for (let i = 0; i < partySizes.length; i++) params[`PartySize${i+1}`] = Math.floor(partySizes[i]);   
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get wait list status and quote for web ahead based on party size, for multiple sites.
     * @param {string[]} siteUID - Globally unique identifier for a site.
     * @param {number[]} partySizes - Array of party sizes.
     * @returns {WaitListStatusForSites} - WaitListStatusForSites.
     */
    async getWaitListStatusMultipleSites(siteUIDs = [], partySizes = []) {
        const config = {
            url: `/Site/WebAhead/Status`,
            method: 'GET',
            params: {}
        };
        for (let i = 0; i < siteUIDs.length; i++) params[`SiteUID${i+1}`] = siteUIDs[i];   
        for (let i = 0; i < partySizes.length; i++) params[`PartySize${i+1}`] = Math.floor(partySizes[i]);   
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get a WebAhead. By default, only active WebAheads will be returned. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified. 
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - A globally unique identifier for a QSR visit record.
     * @param {string} [expand] - Optional. ‘guest’ is currently the only acceptable value. If this parameter is passed, the guest record should be included in the returned WebAhead data.
     * @param {boolean} [inclQuote] - Optional. If True, this will return an additional object named "UpdatedQuote" which contains the most recent updated quote for the party, if it exists.
     * @param {boolean} [all] - Optional. If True, ignores status for current business day and returns all WebAheads created for site local business day.
     * @returns {WebAhead|WebAhead[]} - The requested WebHead(s)
     */
    async getWebAhead(siteUID, visitID, expand, inclQuote, all) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/${visitID}`,
            method: 'GET'
        };
        if (expand) params.expand = expand;
        if (inclQuote) params.ignoreStatusForCurrentBusinessDay = inclQuote;
        if (all) params.includeUpdatedQuote = all;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get a WebAhead. By default, only active WebAheads will be returned. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified. 
     * @param {string} confirmationNumber - Confirmation number.
     * @param {boolean} [all] - Optional. If True, eturn all WebAheads created within the current site local business day.
     * @returns {WebAhead|WebAhead[]} - The requested WebHead(s)
     */
    async getWebAheadByConfirmation(confirmationNumber, all) {
        const config = {
            url: `/WebAhead/${confirmationNumber}`,
            method: 'GET'
        };
        if (all) params.ignoreStatusForCurrentBusinessDay = all;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get a WebAhead. By default, only active WebAheads will be returned. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified. 
     * @param {number} confirmationNumber - Integer identifier of the visit confirmation number.
     * @param {boolean} [all] - Optional. If True, eturn all WebAheads created within the current site local business day.
     * @returns {WebAhead|WebAhead[]} - The requested WebHead(s)
     */
     async getWebAheadByConfirmationId(confirmationNumberId, all) {
        const config = {
            url: `/WebAhead`,
            method: 'GET',
            params: {
                ConfirmationNumberId: confirmationNumberId
            }
        };
        if (all) params.ignoreStatusForCurrentBusinessDay = all;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Update a WebAhead. Call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, PartiallyArrived or Notified.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - A globally unique identifier for a QSR visit record.
     * @param {Object} [info] - Information object for WebAhead.
     * @param {number} [info.partySize] - Party Size
     * @param {string} [info.phoneNumberString] - Formatted phone number string.
     * @param {string} [info.lastName] - Optional if GuestID is provided.  Last name.
     * @param {string} [info.firstName] - First name
     * @param {string} [info.email] - Email
     * @param {string} [info.estimatedArrivalTime] - Estimated arrival time in ISO 8601 format.
     * @param {boolean} [info.expandGuest] - Specifies whether a guest record should be included in the returned WebAhead data.
     * @param {string} [info.notes] - Notes
     * @param {boolean} [info.isSubedToSms] - Flag to indicate whether guest subscribes to SMS marketing.
     * @param {boolean} [info.isSubedToEmail] - Flag to indicate whether guest subscribes to email marketing.
     * @param {boolean} [info.isSubedToQsr] - Flag to indicate whether guest subscribes to QSR's marketing.
     * @returns {WebAhead} - The updated WebHead.
     */
    async updateWebAhead(siteUID, visitID, info) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/${visitID}`,
            method: 'PATCH'
        };
        if (info.partySize) data.PartySize = info.partySize;
        if (info.phoneNumberString) data.PhoneNumberString = info.phoneNumberString;
        if (info.lastName) data.LastName = info.lastName;
        if (info.email) data.Email = info.email;
        if (info.estimatedArrivalTime) data.EstimatedArrivalTime = info.estimatedArrivalTime;
        if (info.expandGuest) data.ExpandGuest = info.expandGuest;
        if (info.firstName) data.FirstName = info.firstName;
        if (info.guestID) data.GuestID = info.guestID;
        if (info.notes) data.Notes = info.notes;
        if (info.notificationType) data.NotificationType = info.notificationType;
        if (info.isSubedToSms) data.IsSubscribedToSmsMarketing = info.isSubedToSms;
        if (info.isSubedToEmail) data.IsSubscribedToEmailMarketing = info.isSubedToEmail;
        if (info.isSubedToQsr) data.IsSubscribedToQsrMarketing = info.isSubedToQsr;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Update a WebAhead. Call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, PartiallyArrived or Notified.
     * @param {string} confirmationNumber - Confirmation number.
     * @param {Object} [info] - Information object for WebAhead.
     * @param {number} [info.partySize] - Party Size
     * @param {string} [info.phoneNumberString] - Formatted phone number string.
     * @param {string} [info.lastName] - Optional if GuestID is provided.  Last name.
     * @param {string} [info.firstName] - First name
     * @param {string} [info.email] - Email
     * @param {string} [info.estimatedArrivalTime] - Estimated arrival time in ISO 8601 format.
     * @param {boolean} [info.expandGuest] - Specifies whether a guest record should be included in the returned WebAhead data.
     * @param {string} [info.notes] - Notes
     * @param {boolean} [info.isSubedToSms] - Flag to indicate whether guest subscribes to SMS marketing.
     * @param {boolean} [info.isSubedToEmail] - Flag to indicate whether guest subscribes to email marketing.
     * @param {boolean} [info.isSubedToQsr] - Flag to indicate whether guest subscribes to QSR's marketing.
     * @returns {WebAhead} - The updated WebHead.
     */
    async updateWebAheadByConfirmation(confirmationNumber, info) {
        const config = {
            url: `/WebAhead/${confirmationNumber}`,
            method: 'PATCH'
        };
        if (info.partySize) data.PartySize = info.partySize;
        if (info.phoneNumberString) data.PhoneNumberString = info.phoneNumberString;
        if (info.lastName) data.LastName = info.lastName;
        if (info.email) data.Email = info.email;
        if (info.estimatedArrivalTime) data.EstimatedArrivalTime = info.estimatedArrivalTime;
        if (info.expandGuest) data.ExpandGuest = info.expandGuest;
        if (info.firstName) data.FirstName = info.firstName;
        if (info.guestID) data.GuestID = info.guestID;
        if (info.notes) data.Notes = info.notes;
        if (info.notificationType) data.NotificationType = info.notificationType;
        if (info.isSubedToSms) data.IsSubscribedToSmsMarketing = info.isSubedToSms;
        if (info.isSubedToEmail) data.IsSubscribedToEmailMarketing = info.isSubedToEmail;
        if (info.isSubedToQsr) data.IsSubscribedToQsrMarketing = info.isSubedToQsr;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Update a WebAhead. Call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, PartiallyArrived or Notified.
     * @param {number} confirmationNumberId - Integer identifier of the visit confirmation number.
     * @param {Object} [info] - Information object for WebAhead.
     * @param {number} [info.partySize] - Party Size
     * @param {string} [info.phoneNumberString] - Formatted phone number string.
     * @param {string} [info.lastName] - Optional if GuestID is provided.  Last name.
     * @param {string} [info.firstName] - First name
     * @param {string} [info.email] - Email
     * @param {string} [info.estimatedArrivalTime] - Estimated arrival time in ISO 8601 format.
     * @param {boolean} [info.expandGuest] - Specifies whether a guest record should be included in the returned WebAhead data.
     * @param {string} [info.notes] - Notes
     * @param {boolean} [info.isSubedToSms] - Flag to indicate whether guest subscribes to SMS marketing.
     * @param {boolean} [info.isSubedToEmail] - Flag to indicate whether guest subscribes to email marketing.
     * @param {boolean} [info.isSubedToQsr] - Flag to indicate whether guest subscribes to QSR's marketing.
     * @returns {WebAhead} - The updated WebHead.
     */
    async updateWebAheadByConfirmationId(confirmationNumberId, info) {
        const config = {
            url: `/WebAhead/`,
            method: 'PATCH',
            params: {
                ConfirmationNumberID: confirmationNumberId
            }
        };
        if (info.partySize) data.PartySize = info.partySize;
        if (info.phoneNumberString) data.PhoneNumberString = info.phoneNumberString;
        if (info.lastName) data.LastName = info.lastName;
        if (info.email) data.Email = info.email;
        if (info.estimatedArrivalTime) data.EstimatedArrivalTime = info.estimatedArrivalTime;
        if (info.expandGuest) data.ExpandGuest = info.expandGuest;
        if (info.firstName) data.FirstName = info.firstName;
        if (info.guestID) data.GuestID = info.guestID;
        if (info.notes) data.Notes = info.notes;
        if (info.notificationType) data.NotificationType = info.notificationType;
        if (info.isSubedToSms) data.IsSubscribedToSmsMarketing = info.isSubedToSms;
        if (info.isSubedToEmail) data.IsSubscribedToEmailMarketing = info.isSubedToEmail;
        if (info.isSubedToQsr) data.IsSubscribedToQsrMarketing = info.isSubedToQsr;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Cancel a WebAhead. This call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - A globally unique identifier for a QSR visit record.
     * @param {boolean} [checkStatus] - Optional. If true, only a WebAhead not yet arrived will successfully cancel.
     * @returns {boolean} - True if is successful.
     */
    async cancelWebAhead(siteUID, visitID, checkStatus) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/${visitID}/cancel`,
            method: 'POST'
        };
        if (checkStatus) config.params = { checkVisitArrivalStatus: checkStatus };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Cancel a WebAhead. This call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified.
     * @param {string} confirmationNumber - Confirmation number.
     * @param {boolean} [checkStatus] - Optional. If true, only a WebAhead not yet arrived will successfully cancel.
     * @returns {boolean} - True if successful.
     */
    async cancelWebAheadByConfirmation(confirmationNumber, checkStatus) {
        const config = {
            url: `/WebAhead/${confirmationNumber}/cancel`,
            method: 'POST'
        };
        if (checkStatus) config.params = { checkVisitArrivalStatus: checkStatus };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Cancel a WebAhead. This call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified.
     * @param {number} confirmationNumberId - Integer identifier of the visit confirmation number.
     * @param {boolean} [checkStatus] - Optional. If true, only a WebAhead not yet arrived will successfully cancel.
     * @returns {boolean} - True if successful.
     */
    async cancelWebAheadByConfirmationId(confirmationNumberId, checkStatus) {
        const config = {
            url: `/WebAhead/Cancel`,
            method: 'POST',
            params: {
                confirmationNumberId: confirmationNumberId
            }
        };
        if (checkStatus) config.params.checkVisitArrivalStatus = checkStatus ;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Arrive a WebAhead. Call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - A globally unique identifier for a QSR visit record.
     * @returns {boolean} - True if successful.
     */
    async arriveWebAhead(siteUID, visitID) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/${visitID}/arrive`,
            method: 'POST'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Arrive a WebAhead. Call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified.
     * @param {string} confirmationNumber - Confirmation number.
     * @returns {boolean} - True if successful.
     */
    async arriveWebAheadByConfirmation(confirmationNumber) {
        const config = {
            url: `/WebAhead/${confirmationNumber}/arrive`,
            method: 'POST'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Arrive a WebAhead. Call will only succeed if the target WebAhead is Active. 
     * A WebAhead is considered Active if in one of the following states: NotYetArrived, Waiting, Partially Arrived or Notified.
     * @param {number} confirmationNumberId - Integer identifier of the visit confirmation number.
     * @returns {boolean} - True if successful.
     */
    async arriveWebAheadByConfirmationId(confirmationNumberId) {
        const config = {
            url: `/WebAhead/Arrive`,
            method: 'POST',
            params: {
                confirmationNumberId: confirmationNumberId
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Enable WebAhead for the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @returns {boolean} - True if successful.
     */
    async enableWebAhead(siteUID) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/enable`,
            method: 'POST'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Disable WebAhead for the specified site.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @returns {boolean} - True if successful.
     */
    async disableWebAhead(siteUID) {
        const config = {
            url: `/Site/${siteUID}/WebAhead/disable`,
            method: 'POST'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Retrieve the current quote times at a site for a specific party size or all party sizes.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {number} [partySize] - The Party Size for which quote time is being requested. Quote times for all party sizes 1-12 will be returned if not specified.
     * @returns {PrecalculatedQuotes|PrecalculatedQuotes[]} - PrecalculatedQuotes for a single party size or all party sizes.
     */
    async getPrecalculatedQuotes(siteUID, partySize) {
        const config = {
            url: `/Site/${siteUID}/PrecalculatedQuotes`,
            method: 'GET'
        };
        if (partySize) params = { PartySize: partySize };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Adds a guest record to the company guestbook.
     * @param {Object} data - Guest data to add.
     * @param {string} data.lastName - Last name.
     * @param {string} [data.firstName] - Optional. First name.
     * @param {string} [data.email] - Optional. Email.
     * @param {string} [data.notes] - Optional. Notes.
     * @param {boolean} [data.isAnon] - Optional. Is anonymous guest record.
     * @param {boolean} [data.isSubedToSms] - Optional. Flag to indicate whether guest subscribes to SMS marketing.
     * @param {boolean} [data.isSubedToEmail] - Optional. Flag to indicate whether guest subscribes to email marketing.
     * @param {boolean} [data.isSubedToQsr] - Optional. Flag to indicate whether guest subscribes to QSR's marketing.
     * @param {GuestLoyalty} [data.loyalty] - Optional. Guest loyalty info.
     * @param {GuestPhoneNumber[]} [data.phoneNumbers] - Optional. Collection of GuestPhoneNumber objects
     * @param {GuestAddress[]} [data.addresses] - Optional. Collection of GuestAddress objects
     * @param {GuestCustomValue[]} [data.customValues] - Optional. Collection of GuestCustomValue objects
     * @param {string} [syncSource] - Optional. QSR-defined string representing a source of records.
     * @returns {Guest} - Guest.
     */
    async addGuest(data = {}, syncSource) {
        const config = {
            url: `/company/${this.companyUID}/GuestBook`,
            method: 'POST',
            data: {
                LastName: data.lastName
            }
        };
        if (syncSource) params = { SyncSource: syncSource };
        if (data.firstName) config.data.FirstName = data.firstName;
        if (data.email) config.data.Email = data.email;
        if (data.notes) config.data.Notes = data.notes;
        if (data.isAnon) config.data.IsAnonymous = data.isAnon;
        if (data.isSubedToSms) config.data.IsSubscribedToSmsMarketing = data.isSubedToSms;
        if (data.isSubedToEmail) config.data.IsSubscribedToEmailMarketing = data.isSubedToEmail;
        if (data.isSubedToQsr) config.data.IsSubscribedToQsrMarketing = data.isSubedToQsr;
        if (data.loyalty) config.data.Loyalty = data.loyalty;
        if (data.phoneNumbers) config.data.PhoneNumbers = data.phoneNumbers;
        if (data.addresses) config.data.Addresses = data.addresses;
        if (data.customValues) config.data.CustomValues = data.customValues;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Updates the specified guest record.
     * @param {string} guestId - A globally unique identifier for a QSR guest record.
     * @param {Object} [data] - Optional. Guest data to add.
     * @param {string} [data.lastName] - Optional. Last name.
     * @param {string} [data.firstName] - Optional. First name.
     * @param {string} [data.email] - Optional. Email.
     * @param {string} [data.notes] - Optional. Notes.
     * @param {boolean} [data.isAnon] - Optional. Is anonymous guest record.
     * @param {boolean} [data.isSubedToSms] - Optional. Flag to indicate whether guest subscribes to SMS marketing.
     * @param {boolean} [data.isSubedToEmail] - Optional. Flag to indicate whether guest subscribes to email marketing.
     * @param {boolean} [data.isSubedToQsr] - Optional. Flag to indicate whether guest subscribes to QSR's marketing.
     * @param {GuestLoyalty} [data.loyalty] - Optional. Guest loyalty info.
     * @param {GuestPhoneNumber[]} [data.phoneNumbers] - COptional. ollection of GuestPhoneNumber objects
     * @param {GuestAddress[]} [data.addresses] - Optional. Collection of GuestAddress objects
     * @param {GuestCustomValue[]} [data.customValues] - Optional. Collection of GuestCustomValue objects
     * @param {string} [syncSource] - Optional. Optional. QSR-defined string representing a source of records.
     * @returns {boolean} - True if successful.
     */
    async updateGuest(guestId, data = {}, syncSource) {
        const config = {
            url: `/company/${this.companyUID}/GuestBook/${guestId}`,
            method: 'PATCH'
        };
        if (syncSource) params = { SyncSource: syncSource };
        if (data.firstName) config.data.FirstName = data.firstName;
        if (data.email) config.data.Email = data.email;
        if (data.notes) config.data.Notes = data.notes;
        if (data.isAnon) config.data.IsAnonymous = data.isAnon;
        if (data.isSubedToSms) config.data.IsSubscribedToSmsMarketing = data.isSubedToSms;
        if (data.isSubedToEmail) config.data.IsSubscribedToEmailMarketing = data.isSubedToEmail;
        if (data.isSubedToQsr) config.data.IsSubscribedToQsrMarketing = data.isSubedToQsr;
        if (data.loyalty) config.data.Loyalty = data.loyalty;
        if (data.phoneNumbers) config.data.PhoneNumbers = data.phoneNumbers;
        if (data.addresses) config.data.Addresses = data.addresses;
        if (data.customValues) config.data.CustomValues = data.customValues;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Deletes the specified guest record.
     * @param {string} guestId - A globally unique identifier for a QSR guest record.
     * @param {string} [syncSource] - Optional. Optional. QSR-defined string representing a source of records.
     * @returns {boolean} - True if successful.
     */
    async removeGuest(guestId, syncSource) {
        const config = {
            url: `/company/${this.companyUID}/GuestBook/${guestId}`,
            method: 'DELETE'
        };
        if (syncSource) params = { SyncSource: syncSource };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Search guestbook by the specified filters.
     * Returns paged search results ordered by last name field,
     * specified by parameters: info.guestsPerPage and info.pageNumber.
     * @param {Object} info - Search parameters
     * @param {string} [info.guestId] - Optional. A globally unique identifier for a QSR guest record.
     * @param {string} [info.firstName] - Optional. First name.
     * @param {string} [info.lastName] - Optional. Last name.
     * @param {string} [info.loyaltyCardID] - Optional. Loyality card ID.
     * @param {string} [info.email] - Optional. Email.
     * @param {string} [info.city] - Optional. City.
     * @param {string} [info.state] - Optional. State.
     * @param {string} [info.postalCode] - Optional. Postal code.
     * @param {string} [info.mobilePhoneNumber] - Optional. Mobile phone number.
     * @param {string} [info.guestsPerPage] - Optional. Number of guest records per page.
     * @param {string} [info.pageNumber] - Optional. Number representing the requested page of results.
     * @returns {Guest[]} - Collection of Guest. 
     */
    async searchGuestbook(info = {}) {
        const config = {
            url: `/company/${this.companyUID}/GuestBook`,
            method: 'GET',
            params: {}
        };
        if (info.guestId) config.params.GuestId = info.guestId;
        if (info.firstName) config.params.FirstName = info.firstName;
        if (info.lastName) config.params.LastName = info.lastName;
        if (info.loyaltyCardID) config.params.LoyaltyCardID = info.loyaltyCardID;
        if (info.email) config.params.Email = info.email;
        if (info.city) config.params.City = info.city;
        if (info.state) config.params.State = info.state;
        if (info.postalCode) config.params.PostalCode = info.postalCode;
        if (info.mobilePhoneNumber) config.params.MobilePhoneNumber = info.mobilePhoneNumber;
        if (info.guestsPerPage) config.params.GuestsPerPage = info.guestsPerPage;
        if (info.pageNumber) config.params.PageNumber = info.pageNumber;

        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data.Guests;
    }

    /**
     * Arrive a Visit.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - A globally unique identifier for a QSR visit record.
     * @returns {boolean} - True if successful.
     */
    async arriveVisit(siteUID, visitID) {
        const config = {
            url: `/Site/${siteUID}/Visit/${visitID}/Arrive`,
            method: 'POST',
            params: {
                SiteUID: siteUID,
                VisitID: visitID
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * This endpoint is for third party apps or services to update the distance and position that a prospective visit is from a site. 
     * The data is stored in the SiteEventQueue.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - A globally unique identifier for a QSR visit record.
     * @param {Object} info - Proximiy info.
     * @param {string} [info.source] - Optional. The source of the guest proximity data.
     * @param {number} [info.latitude] - Optional. Any decimal 0 to 90. South latitudes will be negative.
     * @param {number} [info.longitude] - Optional. Any decimal 0 to 180. West longitudes will be negative.
     * @param {number} [info.distance] - Optional. Can be any decimal.
     * @returns {boolean} - True if successful.
     */
    async updateVisitProximity(siteUID, visitID) {
        const config = {
            url: `/Site/${siteUID}/Visit/${visitID}/Proximity`,
            method: 'PATCH',
            data: {
                SiteUID: siteUID,
                VisitID: visitID,
                Timestamp: (new Date()).toISOString()
            }
        };
        if (info.source) config.data.Source = info.source;
        if (info.latitude) config.data.Latitude = info.latitude;
        if (info.longitude) config.data.Longitude = info.longitude;
        if (info.distance) config.data.Distance = info.distance;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.status === 200;
    }

    /**
     * Get all visit updates for a specified site within a specified time range, 
     * excluding updates made by the specified 'syncSource'. 
     * This is ordered by last update timestamp of the visit records.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} startTime - Start time of updates.
     * @param {string} stopTime - End time of updates.
     * @param {string} [syncSource] - Exclude results made by specified 'syncSource'
     * @returns {Visit[]} - Collection of Visit.
     */
    async getAllVisitUpdates(siteUID, startTime, stopTime, syncSource) {
        const config = {
            url: `/Site/${siteUID}/Visits`,
            method: 'GET',
            params: {
                SiteUID: siteUID,
                start: startTime.toISOString(),
                stop: stopTime.toISOString()
            }
        };
        if (syncSource) config.params.SyncSource = syncSource;
        const data = [];
        let response;
        do {
            response = await this.axios.request(config).catch(this.#errorHandler);
            data.push(...response.data.Visits)
            config.params.start = (new Date(response.data.TimestampCutoff)).toISOString();
        } while (response.data.MoreData);
        return data;
    }

    /**
     * Get the paged list of visit updates for a specified site within a specified time range, 
     * excluding updates made by the specified 'syncSource'. 
     * This is ordered by last update timestamp of the visit records. Page size is 30.
     * If the total number of visits exceeds 30: 
     *   - The first 30 records are returned.
     *   - 'MoreData' is "True"
     *   - 'TimeStampCutoff' is the last update timestamp of the last record returned.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} startTime - Start time of updates.
     * @param {string} stopTime - End time of updates.
     * @param {string} [syncSource] - Exclude results made by specified 'syncSource'
     * @returns {VisitPollResponse} - Paged results for Visits.
     */
     async getVisitUpdates(siteUID, startTime, stopTime, syncSource) {
        const config = {
            url: `/Site/${siteUID}/Visits`,
            method: 'GET',
            params: {
                SiteUID: siteUID,
                start: startTime.toISOString(),
                stop: stopTime.toISOString()
            }
        };
        if (syncSource) config.params.SyncSource = syncSource;
        const data = [];
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get a visit by its external ID.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} externalUID - External ID of the target QSR visit record.
     * @returns {Visit} - Visit.
     */
    async getVisitByExternalId(siteUID, externalUID) {
        const config = {
            url: `/Site/${siteUID}/Visit/ExternalID/${externalUID}`,
            method: 'GET'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get a visit by ID.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - Unique identifier of the target QSR visit record.
     * @returns {Visit} - Visit.
     */
    async getVisit(siteUID, visitID) {
        const config = {
            url: `/Site/${siteUID}/Visit/${visitID}`,
            method: 'GET'
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get an "open" visit by guest’s Loyalty Card ID 
     * An "open" visit is a visit that is not completed and not canceled.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} loyalityCardID - Loyalty card ID of the guest for the target visits.
     * @param {string} [status] - Only "open" is currently accepted.
     * @returns {Visit} - Visit.
     */
    async getVisitByLoyalityCard(siteUID, loyalityCardID, status) {
        const config = {
            url: `/Site/${siteUID}/Visit`,
            method: 'GET',
            params: {
                LoyaltyCard: loyalityCardID
            }
        };
        if (status) params.status = status;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get an "open" visit by guest’s phone number.
     * An "open" visit is a visit that is not completed and not canceled.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} phoneNumber - Phone number of the guest for the target visits. Only US numbers can be used. Accepted formats include: E.164 format, national format. Example: ‘+12223334444’, ‘(222) 333-4444’, ‘2223334444’.
     * @param {sring} countryCode - Country code of the phone number of the guest for the target visits. Accepted values include ISO 3166-1 alpha-2. Example: US for the United States, and GB for the United Kingdom.
     * @param {string} [status] - Only "open" is currently accepted.
     * @returns {Visit} - Visit.
     */
    async getVisitByLoyalityCard(siteUID, phoneNumber, countryCode, status) {
        const config = {
            url: `/Site/${siteUID}/Visit`,
            method: 'GET',
            params: {
                PhoneNumber: phoneNumber,
                CountryCode: countryCode
            }
        };
        if (status) params.status = status;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Get an "open" visit by guest’s pager number.
     * An "open" visit is a visit that is not completed and not canceled.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} pagerID - The pager number associated with the visit.
     * @param {string} [status] - Only "open" is currently accepted.
     * @returns {Visit} - Visit.
     */
    async getVisitByLoyalityCard(siteUID, pagerID, status) {
        const config = {
            url: `/Site/${siteUID}/Visit`,
            method: 'GET',
            params: {
                PagerID: pagerID
            }
        };
        if (status) params.status = status;
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }

    /**
     * Update visit Party Mix.
     * Note: The total guest count of all party mix values must equal the party size of the visit.
     * @param {string} siteUID - Globally unique identifier for a site.
     * @param {string} visitID - Unique identifier of the target QSR visit record.
     * @param {string} partyMix - Type and count of the party mixes.
     * @returns {PartyMix} - Party Mix.
     */
    async updatePartyMix(siteUID, visitID, partyMix) {
        const config = {
            url: `/Site/${siteUID}/Visit/${visitID}/PartyMix`,
            method: 'POST',
            params: {
                partyMix: partyMix
            }
        };
        const response = await this.axios.request(config).catch(this.#errorHandler);
        return response.data;
    }
}

module.exports = QSR;


/** Type Definitions */

/**
 * QSR Visit record
 * @typedef {Object} Visit 
 * @property {string} ArrivalTime - Arrival time in ISO 8601 format.
 * @property {string} CanceledTime - Canceled time in ISO 8601 format.
 * @property {string} CompletedTime - Completed time in ISO 8601 format.
 * @property {string} ConfirmationNumber - Confirmation number.
 * @property {string} CreationTime - Creation time in ISO 8601 format.
 * @property {VisitCustomValue[]} CustomValues - Collection of VisitCustomValue objects representing the visit custom value.
 * @property {string} EstimatedArrivalTime - Estimated arrival time in ISO 8601 format.
 * @property {string} ExternalID - External identifier of the visit record from a 3rd party.
 * @property {Guest} Guest - A guest record tied to current QSR visit record. 
 * @property {string} GuestID - A globally unique identifier for the QSR guest record tied to the current QSR visit record.
 * @property {string} ID - A globally unique identifier for a QSR visit record.
 * @property {number} IID - An integer type identifier for a QSR visit record.
 * @property {string} Notes - Notes.
 * @property {string} NotificationType - Set Notification Type to None, Call, SMS, or Pager.
 * @property {string} PagedTime - Paged time in ISO 8601 format.
 * @property {string} PagerID - ID number of associated pager.
 * @property {VisitPartyMix} PartyMix - Visit party mix data.
 * @property {VisitPreassignedTable[]} PreassignedTables - Collection of VisitPreassignedTable objects representing preassigned tables with the visit.
 * @property {Quote} Quote - Quote-related information.
 * @property {VisitSeatedTable[]} SeatedTables - Collection of VisitSeatedTable objects representing seated tables with the visit.
 * @property {string} SeatedTime - Seated time in ISO 8601 format.
 * @property {string} SeatingAreaID - A globally unique identifier for a QSR seating area.
 * @property {string} SeatingAreaName - Seating area name
 * @property {number} Size - Cover count
 * @property {string} Status - Status of current QSR visit record. The following are considered acceptable values: NotYetArrived, PartiallyArrived, Waiting, NoShow, WalkAway, Notified, PartiallySeated, Seated, AlmostFinished, Payment, Completed, CheckStarted, Canceled.
 * @property {string} Type - Type of current QSR visit record. The following are considered acceptable values: WalkIn, CallAhead, Reservation, Carryout.
 * @property {string} VisitSource - Origination of the visit.
 */

/**
 * QSR visit custom record.
 * @typedef {Object} VisitCustomValue
 * @property {string} Name - Name of the visit custom record.
 * @property {string} Value - Value of the visit custom record.
 */

/**
 * QSR VisitSeatedTable record representing a seated table for a visit.
 * @typedef {Object} VisitSeatedTable
 * @property {string} StartTime - Start time in ISO 8601 format. Time when the visit is seated at the table.
 * @property {string} EndTime - End time in ISO 8601 format. Time when the visit leaves the table.
 * @property {boolean} IsCurrent - Flag to indicate whether this record is current.
 * @property {boolean} IsActive - Flag to indicate whether this record is active.
 * @property {string} TableID - A globally unique identifier for a QSR table record.
 * @property {string} TableName - Name of the seated table.
 */

/**
 * QSR VisitPreassignedTable record representing a preassigned table for a visit.
 * @typedef {Object} VisitPreassignedTable
 * @property {string} StartTime - Start time in ISO 8601 format. Time when the visit is seated at the table.
 * @property {string} EndTime - End time in ISO 8601 format. Time when the visit leaves the table.
 * @property {boolean} IsCurrent - Flag to indicate whether this record is current.
 * @property {boolean} IsActive - Flag to indicate whether this record is active.
 * @property {string} TableID - A globally unique identifier for a QSR table record.
 * @property {string} TableName - Name of the seated table.
 */

/**
 * @typedef {Object} VisitPartyMix
 * @property {VisitPartyMixEntry[]} Mixes - Collection of VisitPartyMixEntry. 
 */

/**
 * @typedef {Object} VisitPartyMixEntry
 * @property {number} Count - Count of the party mix type.
 * @property {string} Type - Type of the party mix. The following are considered acceptable values: Adult, Child, Infant, Senior.
 */

/**
 * @typedef {Object} TableHistory
 * @property {string} CleanedTime - Cleaned time of the table, in ISO 8601 format.
 * @property {string} CreatedTime - Creation time of the table, in ISO 8601 format.
 * @property {string} DirtiedTime - Dirty time of the table, in ISO 8601 format.
 * @property {string} FloorPlanName - Name of the related floorplan for the table history record.
 * @property {string} ID - Unique identifier of the table.
 * @property {boolean} IsActive - Boolean flag to represent whether the table is active.
 * @property {number} MaximumSeatCount - Maximum seat count of the table.
 * @property {number} MiniumumSeatCount - Minimum seat count of the table.
 * @property {string} Name - Name of the table.
 * @property {string} OperatingPeriodName - Name of the related operating period for the table history record.
 * @property {number} SeatCount - Seat count of the table.
 * @property {string} ShiftName - Name of the related shift for the table history record.
 */

/**
 * @typedef {Object} TableEvent
 * @property {string} Category - Category of the event.
 * @property {TableEventContent} Content - Content of the event.
 * @property {string} LastUpdate - Last update timestamp of the event, in ISO 8601 format.
 * @property {string} ServerLastUpdate - Server last update timestamp of the event, in ISO 8601 format.
 * @property {string} SyncSource - Sync source of the event.
 * @property {string} Type - Type of event.
 * @property {number} UID - Globally unique identifier of the event.
 * @property {number} Version - Version of the event.
 */

/**
 * @typedef {Object} TableEventContent
 * @property {FloorPlanSnapshot} FloorPlan - Snapshot of the floorplan information at the time of the event.
 * @property {ShiftSnapshot} Shift - Snapshot of the shift information at the time of the event.
 * @property {StationSnapshot} Station - Snapshot of the related stations information at the time of the event. 
 * @property {TableSnapshot} Table - Snapshot of the table information at the time of the event.
 */

/**
 * @typedef {Object} FloorPlanSnapshot
 * @property {string} Name - Name of the floorplan.
 * @property {string} UID - Globally unique identifier of the floorplan record.
 */

/**
 * @typedef {Object} ShiftSnapshot
 * @property {string} Name - Name of the shift.
 * @property {string} UID - Globally unique identifier of the shift record.
 */

/**
 * @typedef {Object} StationSnapshot
 * @property {string} Name - Name of the station.
 * @property {string} UID - Globally unique identifier of the station record.
 */

/**
 * @typedef {Object} TableSnapshot
 * @property {boolean} EndSeats - Flag to indicate whether table has endseat. "True" means active.
 * @property {string} LastUpdate - The last update timestamp of the table record, in ISO 8601 format.
 * @property {number} MaximumSeatCount - Maximum seat count of the table.
 * @property {number} MiniumumSeatCount - Minimum seat count of the table.
 * @property {string} Name - Name of the table.
 * @property {number} QsrTableShapeID - QSR-defined integer ID representing the shape of the table.
 * @property {number} SeatCount - Seat count of the table.
 * @property {TableStatisticSnapshot} Statistics - TableStatisticSnapshot object of the table.
 * @property {string} UID - Globally unique identifier of the event.
 */

/**
 * @typedef {Object} TableStatisticSnapshot
 * @property {string} OpenedTime - The open timestamp of the table record, in ISO 8601 format.
 * @property {string} ClosedTime - The close timestamp of the table record, in ISO 8601 format.
 */

/**
 * @typedef {Object} ReservationAvailabilityDay
 * @property {string} Date - Target date of the ReservationAvailabilityDay record, in ISO 8601 format. Note: This field should be treated as site local business date and should never parsed as a DateTime.
 * @property {ReservationAvailabilitySession[]} Sessions - Collection of ReservationAvailabilitySession, each representing availability for a session within a day.
 */

/**
 * @typedef {Object} ReservationAvailabilitySession
 * @property {string} SessionName - Name of the session record.
 * @property {ReservationAvailabilitySeatingArea[]} SeatingAreas - Collection of ReservationAvailabilitySeatingArea, each representing availability for a seating area within a session.
 */

/**
 * @typedef {Object} ReservationAvailabilitySeatingArea
 * @property {string} GuestMessage - Message shown to guests when guests book a reservation with the seating area and the session.
 * @property {string} SeatingAreaName - Name of the seating area.
 * @property {string} SeatingAreaUID - Unique identifier of the seating area.
 * @property {ReservationAvailabilityTimeSlot[]} Times - Collection of ReservationAvailabilityTimeSlot, each representing availability for a calendar date. 
 */

/**
 * @typedef {Object} ReservationAvailabilityTimeSlot
 * @property {boolean} IsAvailable - If time slot is available, "True." Otherwise, "False."
 * @property {string} Time - It represents time of the time slot, in ISO 8601 format.
 * @property {boolean} OnlineSuspended - If online reservations have been temporarily suspended by the restaurant, "True." Otherwise, "False."
 */

/**
 * @typedef {Object} Site
 * @property {string} SiteUID - A globally unique identifier for a site that will be provided by QSR.
 * @property {string} CustomerSiteID - Customizable site identifier.
 * @property {number} IID - An integer type identifier for a QSR visit record..
 * @property {string} CompanyUID - A globally unique identifier for a company that will be provided by QSR.
 * @property {string} CreationTime - Creation time in ISO 8601 format..
 * @property {string} Name - Site name.
 * @property {string} ContactNumber - Site contact phone number.
 * @property {string} Hours - Site operation hours text.
 * @property {string} Address - Address.
 * @property {string} City - City.
 * @property {string} County - Country.
 * @property {string} State - State.
 * @property {string} Postal - Postal code.
 * @property {string} Country - Country.
 * @property {string} TimeZoneId - Name of site time zone in Olson time zone list..
 * @property {number} Latitude - Latitude of site location.
 * @property {number} Longitude - Longitude of site location.
 * @property {string} BrandUID - A globally unique identifier for a Brand/Concept that will be provided by QSR.
 * @property {string} DisplayName - Descriptive alternate site name.
 * @property {string} PrimaryCuisine - Name of the main type cuisine associated with the site.
 * @property {string[]} Cuisines - Name of cuisines associated with the site.
 * @property {string} LastUpdate - The timestamp of the last update in ISO 8601 format.
 */

/**
 * @typedef {Object} OperatingInfo
 * @property {Days[]} Days - Collection of operating days data.
 * @property {Sessions[]} Sessions - Collection of Sessions data. See Sessions.
 * @property {string} Session - Name of the corresponding session.
 * @property {string} StartTime - Beginning time of the operating period, in ISO 8601 format.
 * @property {string} Status - Operating status of the operating period. The following are considered accepted values: ‘Open’, ‘Closed’ and ‘Unknown’.
 */

/**
 * @typedef {Object} Days
 * @property {string} ID - Identifier for the Day record.
 * @property {Sessions[]} Schedule - Collection of schedule data.
 * @property {string} Session - Name of the corresponding session.
 * @property {string} StartTime - Beginning time of the operating period, in ISO 8601 format.
 * @property {string} Status - Operating status of the operating period. The following are considered accepted values: ‘Open’, ‘Closed’ and ‘Unknown’.
 */

/**
 * @typedef {Object} Sessions
 */

/**
 * @typedef {Object} TeamMember
 * @property {string} CardId - Card ID of the TeamMember record.
 * @property {string} CreatedTime - The creation timestamp of the TeamMember record, in ISO 8601 format.
 * @property {string} Email - Email of the TeamMember record.
 * @property {string} ExternalId - External ID of the TeamMember record.
 * @property {string} FirstName - FirstName of the TeamMember record.
 * @property {string} HomePhone - HomePhone of the TeamMember record.
 * @property {boolean} IsActive - Flag indicating whether the TeamMember record is active. "True" means active.
 * @property {string} LastName - LastName of the TeamMember record.
 * @property {string} MobilePhone - MobilePhone of the TeamMember record.
 * @property {string} Notes - Notes of the TeamMember record.
 * @property {number} ServerID - Integer ID of the TeamMember record.
 * @property {string} UID - Globally unique identifier of the TeamMember record.
 */

/**
 * @typedef {Object} TeamMemberEvent
 * @property {string} Category - Category of the event.
 * @property {TeamMemberEventContent} Content - Content of the event.
 * @property {string} LastUpdate - Last update timestamp of the event, in ISO 8601 format.
 * @property {string} ServerLastUpdate - Server last update timestamp of the event, in ISO 8601 format.
 * @property {string} SyncSource - Sync source of the event. See Standard Parameters & DTOs.
 * @property {string} Type - Type of event.
 * @property {string} UID - Globally unique identifier of the event.
 * @property {string} Version - Version of the event.
 */

/**
 * @typedef {Object} TeamMemberEventContent
 * @property {FloorPlanSnapshot} FloorPlan - Snapshot of the floorplan information at the time of the event.
 * @property {ShiftSnapshot} Shift - Snapshot of the shift information at the time of the event.
 * @property {StationSnapshot} Station - Snapshot of the related stations information at the time of the event. 
 * @property {TeamMemberSnapshot} TeamMember - Snapshot of the team member information at the time of the event. 
 */

/**
 * @typedef {Object} TeamMemberSnapshot
 * @property {string} CardId - Card ID of the TeamMember record.
 * @property {string} CreatedTime - The creation timestamp of the TeamMember record, in ISO 8601 format.
 * @property {string} Email - Email of the TeamMember record.
 * @property {string} ExternalId - External ID of the TeamMember record.
 * @property {string} FirstName - FirstName of the TeamMember record.
 * @property {string} HomePhone - HomePhone of the TeamMember record.
 * @property {number} Id - number ID of the TeamMember record.
 * @property {number} ImageId - number ID of the image of the TeamMember record.
 * @property {string} LastName - LastName of the TeamMember record.
 * @property {string} MobilePhone - MobilePhone of the TeamMember record.
 * @property {string} Notes - Notes of the TeamMember record.
 * @property {number} SiteID - number ID of the site for the TeamMember record.
 * @property {string} UID - Globally unique identifier of the TeamMember record.
 */

/**
 * Quote related information.
 * @typedef {Object} Quote
 * @property {string} ConsumerQuoteString - Quote string presented to guest/consumer.
 * @property {number} QuoteHigh - High value of quote range.
 * @property {number} QuoteLow - Low value of quote range.
 * @property {string} SiteQuoteString - Quote string presented to site.
 */

/**
 * QSR guest record. 
 * @typedef {Object} Guest
 * @property {GuestAddress[]} Addresses - Collection of GuestAddress objects representing the guest address records.
 * @property {GuestCustomValue[]} CustomValues - Collection of GuestCustomValue objects representing the guest custom value.
 * @property {string} Email - Email.
 * @property {string} FirstName - First name.
 * @property {string} ID - A globally unique identifier for a QSR guest record.
 * @property {boolean} IsAnonymous - Is anonymous guest record.
 * @property {boolean} IsSubscribedToEmailMarketing - Flag to indicate whether a guest subscribes to email marketing.
 * @property {boolean} IsSubscribedToQsrMarketing - Flag to indicate whether a guest subscribes to QSR’s marketing.
 * @property {boolean} IsSubscribedtoSmsMarketing - Flag to indicate whether a guest subscribes to SMS marketing.
 * @property {string} LastName - Last name.
 * @property {GuestLoyalty} Loyalty - Guest loyalty info.
 * @property {string} Notes - Notes.
 * @property {GuestPhoneNumber[]} PhoneNumbers - Collection of GuestPhoneNumber objects representing the guest phone number records. 
 * @property {string} NotificationType - Set NotificationType to 'None 'or 'SMS' to indicate guest's notification preference.
 */

/**
 * @typedef {Object} GuestAddress
 * @property {string} Address - Address.
 * @property {string} Address2 - Address line 2.
 * @property {string} City - City.
 * @property {string} County - County.
 * @property {string} Postal - Postal.
 * @property {string} State - State.
 * @property {string} Country - Country.
 * @property {number} Sort - Sort order.
 */

/**
 * @typedef {Object} GuestCustomValue
 * @property {string} Name - Name of the guest custom record.
 * @property {string} Value - Value of the guest custom record.
 */

/**
 * @typedef {Object} GuestLoyalty
 * @property {string} LoyaltyCardID - Loyalty card ID
 */

/**
 * @typedef {Object} GuestPhoneNumber
 * @property {string} ID - A globally unique identifier for a QSR guest phone record.
 * @property {string} PhoneNumber - Phone number.
 * @property {string} PhoneNumberString - Formatted phone number string..
 * @property {string} Type - Type of phone number. The following are considered acceptable values: Mobile, Home, Work, Other.
 * @property {number} Sort - Sort order.
 */

/**
 * @typedef {Object} TableStatus
 * @property {boolean} IsDirty - True flag indicates table is dirty.
 * @property {string} OpenedTime - The open timestamp of the table status in ISO 8601 format.
 * @property {string} ClosedTime - The close timestamp of the table status in ISO 8601 format.
 * @property {string} DirtyTime - Dirty time of the table in ISO 8601 format.
 * @property {string} CleanedTime - Cleaned time of the table in ISO 8601 format.
 * @property {string} LastSeatedTime - Time of last seating in ISO 8601 format.
 * @property {string} LastCompletedTime - Time table was last complete in ISO 8601 format.
 * @property {string} LastAvailableTime - Last time the table was available in ISO 8601 format.
 * @property {string} Status - Current status of the table.
 * @property {string} ID - Identification number of the table.
 * @property {string} LastUpdate - The timestamp of the last update of table status in ISO 8601 format.
 * @property {string} Name - The table name.
 * @property {number} SeatCount - The number of guests seated at the table.
 * @property {number} MinimumSeatCount - The minimum number of guests that may be seated at table.
 * @property {number} MaximumSeatCount - The maximum number of guests that may be seated at table.
 * @property {boolean} IsActive - True flag indicates table is active.
 * @property {string} SeatingAreaId - The area identification number in which the table is located.
 * @property {boolean} HasMoreData - True flag indicates there is more table status data available.
 * @property {string} CutOffDate - The cut off timestamp in ISO 8601 format.
 */

/**
 * @typedef {Object} WebAhead
 * @property {string} ID - A globally unique identifier for a QSR visit record.
 * @property {string} SiteUID - Globally unique identifier for a site. This is provided by QSR.
 * @property {string} NotificationType - Set Notification Type to None, Call, SMS, or Pager.
 * @property {number} PlaceInWaitList - 1-based position of a current WebAhead visit in the wait list.
 * @property {string} Type - Type of current QSR visit record. The following are considered acceptable values: WalkIn, CallAhead, Reservation.
 * @property {string} Status - Status of a current QSR visit record. The following are considered acceptable values: NotYetArrived, PartiallyArrived, Waiting, NoShow, WalkAway, Notified, PartiallySeated, Seated, AlmostFinished, Payment, Completed, CheckStarted, and Canceled.
 * @property {number} Size - Cover count
 * @property {string} ConfirmationNumber - Confirmation number
 * @property {number} ConfirmationNumberID - number identifier of the visit confirmation number.
 * @property {string} Notes - Notes
 * @property {string} CreationTime - Creation time in ISO 8601 format.
 * @property {string} EstimatedArrivalTime - Estimated arrival time in ISO 8601 format.
 * @property {string} ArrivalTime - Arrival time in ISO 8601 format.
 * @property {string} PagedTime - Paged time in ISO 8601 format.
 * @property {Quote} Quote - Quote-related information.
 * @property {UpdatedQuote} UpdatedQuote - The party's most recently updated Quote information with a GeneratedDateTime in ISO 8601 format.
 * @property {VisitPreassignedTable[]} PreassignedTables - Collection of VisitPreassignedTable objects representing preassigned tables.
 * @property {VisitCustomValue[]} CustomValues - Collection of VisitCustomValue objects representing visit custom value.
 * @property {Guest} Guest - A QSR guest record tied to a current QSR visit record.
 * @property {string} GuestID - A globally unique identifier for the QSR guest record tied to a current QSR WebAhead record.
 * @property {string} SeatingAreaID - A globally unique identifier for a QSR seating area.
 * @property {string} SeatingAreaName - Seating area name.
 * @property {string} SeatedTime - Seated time in ISO 8601 format.
 * @property {string} CompletedTime - Completed time in ISO 8601 format.
 * @property {VisitSeatedTable[]} SeatedTables - Collection of VisitSeatedTime objects representing seated tables.
 */

/**
 * @typedef {Object} WaitListStatus
 * @property {string} SiteUID - A globally unique identifier for a site that will be provided by QSR.
 * @property {string} WebAheadStatus - Status of site WebAhead operations. The following are considered acceptable values: Disabled, NotAccepting, Available.
 * @property {number} NumberWaitingVisits - Number of current waiting visits on site.
 * @property {Quote} Quote - Current quote information.
 * @property {Status[]} Statuses - Current quote information.
 */

/**
 * @typedef {Object} Status
 * @property {number} PartySize - Number of guests in party.
 * @property {Quote[]} Quote - Collention of Quotes.
 * @property {string} WebAheadStatus - Status of site WebAhead operations. The following are considered acceptable values: Disabled, NotAccepting, Available.
 * @property {string} WebAheadStatusText - Status of site WebAhead operations in text form. Possible values are: Disabled, NotAccepting, Available.
 */

/**
 * @typedef {Object} WaitListStatusForSites
 * @property {WaitListStatus} WaitListStatusList - List of WaitListStatus for specified sites.
 */

/**
 * @typedef {Object} PreCalculatedQuotes
 * @property {PrecalculatedQuotePerPartySize[]} Quotes - Collection of PrecalculatedQuotes representing quotes per party size.
 */

/**
 * @typedef {Object} PrecalculatedQuotePerPartySize
 * @property {PreCalculatedQuote[]} quote - Collection PrecalculatedQuote data
 * @property {number} partySize - Number of guests within a party for which the quote applies.
 */

/**
 * @typedef {Object} PrecalculatedQuote
 * @property {number} quoteLow - Low quote range value as determined by your quote settings.
 * @property {number} quoteHigh - High quote range value as determined by your quote settings.
 * @property {string} siteQuoteString - "Exact Quote Label" configured for the quote time.
 * @property {string} consumerQuoteString - "Quote Range Label" configured for the quote time.
 * @property {number} exactQuote - Exact quote value generated by the system.
 * @property {number} checkBackTime - Unused by QSR applications .Configurable value to provide to party to check back on.
 * @property {number} autoNoShowTime - Unused by QSR applications .Configurable value for when to automatically no show a visit after check back time.
 * @property {string} guestChitString - Configurable quote text to print on guest printer receipt.
 */

/**
 * @typedef {Object} PartyMix
 * @property {string} ID - Globally unique identifier of a site.
 * @property {number} IID - Integer ID.
 * @property {string} LastUpdate - The timestamp of the last update in ISO 8601 format.
 * @property {string} ServerLastUpdate - Server last update timestamp of the event in ISO 8601 format.
 * @property {Guest[]} Guest - Collection of Guest.
 * @property {string} GuestID - Unique identifier of a guest record.
 * @property {string} Type - The type of.
 * @property {string} Status - The current status of.
 * @property {number} Size - Cover count.
 * @property {SeatedTables[]} SeatedTables - Collection of SeatedTables.
 * @property {VisitPreassignedTable[]} PreassignedTables - Collection of VisitPreassignedTable
 * @property {Quote} Quote - Quote related information.
 * @property {string} CreationTime - The creation timestamp of
 * @property {string} EstimatedArrivalTime - Reservation time in ISO 8601 format.
 * @property {string} ArrivalTime - Arrival time in ISO 8601 format.
 * @property {string} PagedTime - Paged time in ISO 8601 format.
 * @property {string} SeatedTime - Seated time in ISO 8601 format.
 * @property {string} CompletedTime - Completed time in ISO 8601 format.
 * @property {string} CanceledTime - Reservation cancellation timestamp in ISO 8601 format.
 * @property {string} ConfirmationNumber - Confirmation number.
 * @property {number} ConfirmationNumberId - Integer identifier of the visit confirmation number.
 * @property {string} SeatingAreaId - A globally unique identifier for a QSR seating area.
 * @property {string} SeatingAreaName - Seating area name.
 * @property {string} Notes - Notes.
 * @property {CustomValues[]} CustomValues - Collection of CustomValues.
 * @property {string} ExternalID - External ID.
 * @property {string} PartyMix - Type and count of the party mixes.
 * @property {VisitPartyMixEntry[]} Mixes - Collection of VisitPartyMixEntry.
 */

/**
 * @typedef {Object} SeatedTables
 * @property {string} TableID - A globally unique identifier for a QSR table record.
 * @property {string} TableName - Name of the seated table.
 * @property {string} StartTime - Start time in ISO 8601 format. Time when the visit is seated at the table.
 * @property {string} EndTime - End time in ISO 8601 format. Time when the visit leaves the table.
 * @property {boolean} IsActive - Flag to indicate whether this record is active.
 * @property {boolean} IsCurrent - Flag to indicate whether this record is current.
 */

/**
 * @typedef {Object} CustomValues
 * @property {string} Name - Name of the guest custom record.
 * @property {string} Value - Value of the guest custom record.
 */

/** 
 * @typedef {Object} VisitPollResponse
 * @property {Visit[]} Visits - Collection of Visit objects representing the requested page of matching guest records.
 * @property {boolean} MoreData - 'True' indicates that the number of total results exceeds 30.
 * @property {string} TimestampCutoff - The last update timestamp of the last record in the returned result set in ISO 8601 format.
 */

/**
 * @typedef {Object} TableHiTableHistoryPage
 * @property {TableHistory[]} History - Collection of TableHistory objects representing the requested page of TableHistory records.
 * @property {boolean} HasMoreData - "True" indicates that the number of total results exceeds 100.
 * @property {string} CutOffDate - The last update timestamp of the last record in the returned result set, in ISO 8601 format.
 */

/**
 * @typedef {Object} TeamMemberEventPage
 * @property {TeamMemberEvent[]} Events - Collection of TeamMemberEvent objects representing the requested page of matching team member event records.
 * @property {boolean} MoreData - "True" indicates that the number of total results exceeds 100.
 * @property {string} DownloadCutoff - The last update timestamp of the last record in the returned result set, in ISO 8601 format.
 */

/**
 * @typedef {Object} TableEventPage
 * @property {TableEvent[]} Events - Collection of TableEvent objects representing the requested page of TableEvent records.
 * @property {boolean} MoreData - "True" indicates that the number of total results exceeds 100.
 * @property {string} DownloadCutoff - The last update timestamp of the last record in the returned result set, in ISO 8601 format.
 */

/**
 * @typedef {Object} GetPartnerSites
 * @property {string} PageData - Content of the page.
 * @property {string} SiteUID - Globally unique identifier for a site. This is provided by QSR.
 * @property {string} ExternalSiteID - External ID of the site.
 * @property {boolean} HasMore - Indicates if additional page(s) are available. True or False.
 * @property {string} Token - A token which can be provided as the Token URL parameter to retrieve the next page of sites.
 */