import { IDataObject, IExecuteFunctions, IHookFunctions, ILoadOptionsFunctions, INodeExecutionData, IWebhookFunctions, NodeOperationError, } from 'n8n-workflow'; import { OnfleetAdmins, OnfleetCloneOverrideTaskOptions, OnfleetCloneTask, OnfleetCloneTaskOptions, OnfleetDestination, OnfleetHubs, OnfleetListTaskFilters, OnfleetRecipient, OnfleetTask, OnfleetTaskComplete, OnfleetTaskUpdate, OnfleetTeamAutoDispatch, OnfleetTeams, OnfleetWebhook, OnfleetWorker, OnfleetWorkerEstimates, OnfleetWorkerFilter, OnfleetWorkerSchedule, OnfleetWorkerScheduleEntry, } from './interfaces'; import { onfleetApiRequest, onfleetApiRequestAllItems } from './GenericFunctions'; import moment from 'moment-timezone'; const formatAddress = ( unparsed: boolean, address: string | undefined, addressNumber: string | undefined, addressStreet: string | undefined, addressCity: string | undefined, addressCountry: string | undefined, additionalFields: IDataObject, ): OnfleetDestination => { let destination: OnfleetDestination; if (unparsed) { destination = { address: { unparsed: address } }; } else { destination = { address: { number: addressNumber, street: addressStreet, city: addressCity, country: addressCountry, }, }; } // Adding destination extra fields if (additionalFields.addressName) { destination.address.name = additionalFields.addressName as string; } if (additionalFields.addressApartment) { destination.address.apartment = additionalFields.addressApartment as string; } if (additionalFields.addressState) { destination.address.state = additionalFields.addressState as string; } if (additionalFields.addressPostalCode) { destination.address.postalCode = additionalFields.addressPostalCode as string; } if (additionalFields.addressNotes) { destination.notes = additionalFields.addressNotes as string; } return destination; }; export class Onfleet { /** * Returns a valid formatted destination object * @param unparsed Whether the address is parsed or not * @param address Destination address * @param addressNumber Destination number * @param addressStreet Destination street * @param addressCity Destination city * @param addressCountry Destination country * @param additionalFields Destination additional fields */ /** * Gets the properties of a destination according to the operation chose * @param item Current execution data * @param operation Current destination operation * @param shared Whether the collection is in other resource or not */ static getDestinationFields( this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, item: number, operation: string, shared: { parent: string } | boolean = false, ): OnfleetDestination | OnfleetDestination[] | null { if (['create', 'update'].includes(operation)) { /* -------------------------------------------------------------------------- */ /* Get fields for create and update a destination */ /* -------------------------------------------------------------------------- */ if (shared !== false) { let destination; if (typeof shared === 'boolean' && shared) { const { destinationProperties = {} } = this.getNodeParameter( 'destination', item, ) as IDataObject; destination = destinationProperties; } else if (typeof shared !== 'boolean') { const { destination: destinationCollection = {} } = this.getNodeParameter( shared.parent, item, ) as IDataObject; destination = (destinationCollection as IDataObject).destinationProperties; } if (!destination || Object.keys(destination as IDataObject).length === 0) { return []; } const { unparsed, address, addressNumber, addressStreet, addressCity, addressCountry, ...additionalFields } = destination as IDataObject; return formatAddress( unparsed as boolean, address as string, addressNumber as string, addressStreet as string, addressCity as string, addressCountry as string, additionalFields as IDataObject, ) as OnfleetDestination; } else { let unparsed, address, addressNumber, addressStreet, addressCity, addressCountry, additionalFields; unparsed = this.getNodeParameter('unparsed', item) as boolean; if (unparsed) { address = this.getNodeParameter('address', item) as string; } else { addressNumber = this.getNodeParameter('addressNumber', item) as string; addressStreet = this.getNodeParameter('addressStreet', item) as string; addressCity = this.getNodeParameter('addressCity', item) as string; addressCountry = this.getNodeParameter('addressCountry', item) as string; } additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; return formatAddress( unparsed, address, addressNumber, addressStreet, addressCity, addressCountry, additionalFields, ) as OnfleetDestination; } } return null; } /** * Gets the properties of an administrator according to the operation chose * @param item Current execution data * @param operation Current administrator operation */ static getAdminFields( this: IExecuteFunctions, item: number, operation: string, ): OnfleetAdmins | null { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Get fields for create admin */ /* -------------------------------------------------------------------------- */ const name = this.getNodeParameter('name', item) as string; const email = this.getNodeParameter('email', item) as string; const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; const adminData: OnfleetAdmins = { name, email }; // Adding additional fields Object.assign(adminData, additionalFields); return adminData; } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Get fields for update admin */ /* -------------------------------------------------------------------------- */ const updateFields = this.getNodeParameter('updateFields', item) as IDataObject; const adminData: OnfleetAdmins = {}; if (!Object.keys(updateFields).length) { throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated'); } // Adding additional fields Object.assign(adminData, updateFields); return adminData; } return null; } /** * Gets the properties of a hub according to the operation chose * @param item Current execution data * @param operation Current hub operation */ static getHubFields( this: IExecuteFunctions, item: number, operation: string, ): OnfleetHubs | null { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Get fields for create hub */ /* -------------------------------------------------------------------------- */ const destination = Onfleet.getDestinationFields.call( this, item, operation, true, ) as OnfleetDestination; const name = this.getNodeParameter('name', item) as string; const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; const hubData: OnfleetHubs = { name, ...destination }; // Adding additional fields Object.assign(hubData, additionalFields); return hubData; } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Get fields for update hub */ /* -------------------------------------------------------------------------- */ const destination = Onfleet.getDestinationFields.call(this, item, operation, { parent: 'updateFields', }) as OnfleetDestination; const hubData: OnfleetHubs = { ...destination }; // Adding additional fields const updateFields = this.getNodeParameter('updateFields', item) as IDataObject; if (!Object.keys(updateFields).length) { throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated'); } Object.assign(hubData, updateFields); return hubData; } return null; } /** * Gets the properties of a worker according to the operation chose * @param item Current execution data * @param operation Current worker operation */ static getWorkerFields( this: IExecuteFunctions, item: number, operation: string, ): OnfleetWorker | OnfleetWorkerFilter | OnfleetWorkerSchedule | null { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Get fields for create worker */ /* -------------------------------------------------------------------------- */ const name = this.getNodeParameter('name', item) as string; const phone = this.getNodeParameter('phone', item) as string; const teams = this.getNodeParameter('teams', item) as string[]; const workerData: OnfleetWorker = { name, phone, teams }; // Adding additional fields const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; if (additionalFields.vehicle) { const { vehicleProperties } = additionalFields.vehicle as IDataObject; Object.assign(workerData, { vehicle: vehicleProperties }); delete additionalFields.vehicle; } Object.assign(workerData, additionalFields); return workerData; } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Get fields for update worker */ /* -------------------------------------------------------------------------- */ const workerData: OnfleetWorker = {}; // Adding additional fields const updateFields = this.getNodeParameter('updateFields', item) as IDataObject; if (!Object.keys(updateFields).length) { throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated'); } Object.assign(workerData, updateFields); return workerData; } else if (operation === 'get') { const options = this.getNodeParameter('options', item, {}) as IDataObject; const workerFilter: OnfleetWorkerFilter = {}; if (options.filter) { options.filter = (options.filter as string[]).join(','); } if (typeof options.analytics === 'boolean') { options.analytics = options.analytics ? 'true' : 'false'; } Object.assign(workerFilter, options); return workerFilter; } else if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get fields for get and getAll workers */ /* -------------------------------------------------------------------------- */ const options = this.getNodeParameter('options', item, {}) as IDataObject; const filters = this.getNodeParameter('filters', item, {}) as IDataObject; const workerFilter: OnfleetWorkerFilter = {}; if (filters.states) { filters.states = (filters.states as number[]).join(','); } if (filters.teams) { filters.teams = (filters.teams as string[]).join(','); } if (filters.phones) { filters.phones = (filters.phones as string[]).join(','); } if (options.filter) { options.filter = (options.filter as string[]).join(','); } Object.assign(workerFilter, options); Object.assign(workerFilter, filters); return workerFilter; } else if (operation === 'setSchedule') { /* -------------------------------------------------------------------------- */ /* Set a worker schedule */ /* -------------------------------------------------------------------------- */ const { scheduleProperties } = this.getNodeParameter('schedule', item) as IDataObject; const entries = ((scheduleProperties as IDataObject[]) || []).map((entry) => { const { timezone, date, shifts } = entry as IDataObject; const { shiftsProperties } = shifts as IDataObject; return { timezone: timezone as string, date: moment(date as Date).format('YYYY-MM-DD'), shifts: (shiftsProperties as IDataObject[]).map(({ start, end }) => [ new Date(start as Date).getTime(), new Date(end as Date).getTime(), ]), } as OnfleetWorkerScheduleEntry; }) as OnfleetWorkerScheduleEntry[]; return { entries } as OnfleetWorkerSchedule; } return null; } /** * Gets the properties of a webhooks according to the operation chose * @param item Current execution data * @param operation Current webhooks operation */ static getWebhookFields( this: IExecuteFunctions, item: number, operation: string, ): OnfleetWebhook | null { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Get fields for create webhook */ /* -------------------------------------------------------------------------- */ const url = this.getNodeParameter('url', item) as string; const name = this.getNodeParameter('name', item) as string; const trigger = this.getNodeParameter('trigger', item) as number; const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; const webhookData: OnfleetWebhook = { url, name, trigger }; // Adding additional fields Object.assign(webhookData, additionalFields); return webhookData; } return null; } /** * Returns a valid formatted recipient object * @param name Recipient name * @param phone Recipient phone * @param additionalFields Recipient additional fields */ static formatRecipient( name: string, phone: string, additionalFields: IDataObject, options: IDataObject = {}, ): OnfleetRecipient { const recipient: OnfleetRecipient = { name, phone }; // Adding recipient extra fields if (additionalFields.recipientNotes) { recipient.notes = additionalFields.recipientNotes as string; } if (additionalFields.recipientSkipSMSNotifications) { recipient.skipSMSNotifications = additionalFields.recipientSkipSMSNotifications as boolean; } if ('recipientSkipPhoneNumberValidation' in options) { recipient.skipPhoneNumberValidation = (options.recipientSkipPhoneNumberValidation as boolean) || false; } return recipient; } /** * Gets the properties of a recipient according to the operation chose * @param item Current execution data * @param operation Current recipient operation * @param shared Whether the collection is in other resource or not */ static getRecipientFields( this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, item: number, operation: string, shared = false, ): OnfleetRecipient | OnfleetRecipient[] | null { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Get fields to create recipient */ /* -------------------------------------------------------------------------- */ if (shared) { const { recipient: recipientData = {} } = this.getNodeParameter( 'additionalFields', item, {}, ) as IDataObject; const options = this.getNodeParameter('options', item, {}) as IDataObject; const { recipientProperties: recipient = {} } = recipientData as IDataObject; if (!recipient || Object.keys(recipient).length === 0) { return null; } const { recipientName: name, recipientPhone: phone, ...additionalFields } = recipient as IDataObject; return Onfleet.formatRecipient( name as string, phone as string, additionalFields as IDataObject, options, ); } else { const name = this.getNodeParameter('recipientName', item) as string; const phone = this.getNodeParameter('recipientPhone', item) as string; const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; const options = this.getNodeParameter('options', item) as IDataObject; return Onfleet.formatRecipient(name, phone, additionalFields, options) as OnfleetRecipient; } } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Get fields to update recipient */ /* -------------------------------------------------------------------------- */ const { recipientName: name = '', recipientPhone: phone = '', ...additionalFields } = this.getNodeParameter('updateFields', item) as IDataObject; const recipientData: OnfleetRecipient = {}; // Adding additional fields if (name) { recipientData.name = name as string; } if (phone) { recipientData.phone = phone as string; } Object.assign(recipientData, additionalFields); return recipientData; } return null; } /** * Gets the properties of a task according to the operation chose * @param item Current execution data * @param operation Current task operation */ static getTaskFields( this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, item: number, operation: string, ): | OnfleetListTaskFilters | OnfleetTask | OnfleetCloneTask | OnfleetTaskComplete | OnfleetTaskUpdate | null { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Get fields to create a task */ /* -------------------------------------------------------------------------- */ const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; const destination = Onfleet.getDestinationFields.call( this, item, operation, true, ) as OnfleetDestination; // Adding recipients information const recipient = Onfleet.getRecipientFields.call( this, item, operation, true, ) as OnfleetRecipient; const taskData: OnfleetTask = { destination, recipients: [recipient] }; const { completeAfter = null, completeBefore = null, ...extraFields } = additionalFields; if (completeAfter) taskData.completeAfter = new Date(completeAfter as Date).getTime(); if (completeBefore) taskData.completeBefore = new Date(completeBefore as Date).getTime(); Object.assign(taskData, extraFields); return taskData; } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Get fields to update task */ /* -------------------------------------------------------------------------- */ const updateFields = this.getNodeParameter('updateFields', item) as IDataObject; const taskData: OnfleetTaskUpdate = {}; if (!Object.keys(updateFields).length) { throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated'); } const { completeAfter = null, completeBefore = null, ...extraFields } = updateFields; if (completeAfter) taskData.completeAfter = new Date(completeAfter as Date).getTime(); if (completeBefore) taskData.completeBefore = new Date(completeBefore as Date).getTime(); Object.assign(taskData, extraFields); return taskData; } else if (operation === 'clone') { /* -------------------------------------------------------------------------- */ /* Get fields to clone task */ /* -------------------------------------------------------------------------- */ const overrideFields = this.getNodeParameter('overrideFields', item) as IDataObject; const options: OnfleetCloneTaskOptions = {}; if (overrideFields.includeMetadata) { options.includeMetadata = overrideFields.includeMetadata as boolean; } if (overrideFields.includeBarcodes) { options.includeBarcodes = overrideFields.includeBarcodes as boolean; } if (overrideFields.includeDependencies) { options.includeDependencies = overrideFields.includeDependencies as boolean; } // Adding overrides data const { notes, pickupTask, serviceTime, completeAfter, completeBefore } = overrideFields as IDataObject; const overridesData = {} as OnfleetCloneOverrideTaskOptions; if (notes) overridesData.notes = notes as string; if (typeof pickupTask !== 'undefined') overridesData.pickupTask = pickupTask as boolean; if (serviceTime) overridesData.serviceTime = serviceTime as number; if (completeAfter) overridesData.completeAfter = new Date(completeAfter as Date).getTime(); if (completeBefore) overridesData.completeBefore = new Date(completeBefore as Date).getTime(); if (overridesData && Object.keys(overridesData).length > 0) { options.overrides = overridesData; } return { options } as OnfleetCloneTask; } else if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get fields to list tasks */ /* -------------------------------------------------------------------------- */ const filters = this.getNodeParameter('filters', item) as IDataObject; const listTaskData: OnfleetListTaskFilters = {}; const allStates = '0,1,2,3'; const twoWeeksInMilisecods = () => 604800 * 1000; // Adding extra fields to search tasks if (filters.from) { listTaskData.from = new Date(filters.from as Date).getTime(); } else { listTaskData.from = new Date().getTime() - twoWeeksInMilisecods(); } if (filters.to) { listTaskData.to = new Date(filters.to as Date).getTime(); } if (filters.state) { listTaskData.state = (filters.state as number[]).join(','); if (listTaskData.state.includes('all')) { listTaskData.state = allStates; } } return listTaskData; } else if (operation === 'complete') { /* -------------------------------------------------------------------------- */ /* Get fields to complete a task */ /* -------------------------------------------------------------------------- */ const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; const success = this.getNodeParameter('success', item) as boolean; const taskData: OnfleetTaskComplete = { completionDetails: { success } }; if (additionalFields.notes) { taskData.completionDetails.notes = additionalFields.notes as string; } return taskData; } return null; } /** * Gets the properties of a team according to the operation chose * @param item Current execution data * @param operation Current team operation */ static getTeamFields( this: IExecuteFunctions, item: number, operation: string, ): OnfleetTeams | OnfleetWorkerEstimates | OnfleetTeamAutoDispatch | null { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Get fields to create a team */ /* -------------------------------------------------------------------------- */ const name = this.getNodeParameter('name', item) as string; const workers = this.getNodeParameter('workers', item) as string[]; const managers = this.getNodeParameter('managers', item) as string[]; const additionalFields = this.getNodeParameter('additionalFields', item) as IDataObject; const teamData: OnfleetTeams = { name, workers, managers }; // Adding additional fields Object.assign(teamData, additionalFields); return teamData; } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Get fields to update a team */ /* -------------------------------------------------------------------------- */ const teamData: OnfleetTeams = {}; // Adding additional fields const updateFields = this.getNodeParameter('updateFields', item) as IDataObject; if (!Object.keys(updateFields).length) { throw new NodeOperationError(this.getNode(), 'Select at least one field to be updated'); } Object.assign(teamData, updateFields); return teamData; } else if (operation === 'getTimeEstimates') { /* -------------------------------------------------------------------------- */ /* Get driver time estimates for tasks that haven't been created yet */ /* -------------------------------------------------------------------------- */ const { dropOff = {}, pickUp = {}, ...additionalFields } = this.getNodeParameter('filters', item) as IDataObject; const { dropOffProperties = {} } = dropOff as IDataObject; const { pickUpProperties = {} } = pickUp as IDataObject; const hasPickUp = pickUp && Object.keys(pickUpProperties as IDataObject).length > 0; const hasDropOff = dropOffProperties && Object.keys(dropOffProperties as IDataObject).length > 0; if (!hasPickUp && !hasDropOff) { throw new NodeOperationError( this.getNode(), 'At least 1 of Drop-Off location or Pick-Up location must be selected', ); } const workerTimeEstimates = {} as OnfleetWorkerEstimates; if (hasPickUp) { const { pickupLongitude: longitude, pickupLatitude: latitude, pickupTime, } = pickUpProperties as IDataObject; workerTimeEstimates.pickupLocation = `${longitude},${latitude}`; if (pickupTime) { workerTimeEstimates.pickupTime = moment(new Date(pickupTime as Date)) .local() .unix(); } } if (hasDropOff) { const { dropOffLongitude: longitude, dropOffLatitude: latitude } = dropOffProperties as IDataObject; workerTimeEstimates.dropoffLocation = `${longitude},${latitude}`; } Object.assign(workerTimeEstimates, additionalFields); return workerTimeEstimates; } else if (operation === 'autoDispatch') { /* -------------------------------------------------------------------------- */ /* Dynamically dispatching tasks on the fly */ /* -------------------------------------------------------------------------- */ const teamAutoDispatch = {} as OnfleetTeamAutoDispatch; const { scheduleTimeWindow = {}, taskTimeWindow = {}, endingRoute = {}, ...additionalFields } = this.getNodeParameter('additionalFields', item) as IDataObject; const { endingRouteProperties = {} } = endingRoute as IDataObject; const { scheduleTimeWindowProperties = {} } = scheduleTimeWindow as IDataObject; const { taskTimeWindowProperties = {} } = taskTimeWindow as IDataObject; if ( scheduleTimeWindowProperties && Object.keys(scheduleTimeWindowProperties as IDataObject).length > 0 ) { const { startTime, endTime } = scheduleTimeWindowProperties as IDataObject; teamAutoDispatch.scheduleTimeWindow = [ moment(new Date(startTime as Date)) .local() .unix(), moment(new Date(endTime as Date)) .local() .unix(), ]; } if (endingRouteProperties && Object.keys(endingRouteProperties as IDataObject).length > 0) { const { routeEnd, hub } = endingRouteProperties as IDataObject; teamAutoDispatch.routeEnd = { anywhere: null, hub: `hub://${hub}`, team_hub: 'teams://DEFAULT', worker_routing_address: 'workers://ROUTING_ADDRESS', }[routeEnd as string] as string; } if ( taskTimeWindowProperties && Object.keys(taskTimeWindowProperties as IDataObject).length > 0 ) { const { startTime, endTime } = taskTimeWindowProperties as IDataObject; teamAutoDispatch.taskTimeWindow = [ moment(new Date(startTime as Date)) .local() .unix(), moment(new Date(endTime as Date)) .local() .unix(), ]; } Object.assign(teamAutoDispatch, additionalFields); return teamAutoDispatch; } return null; } /** * Execute the task operations * @param resource Resource to be executed (Task) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeTaskOperations( this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { if (operation === 'create' && Object.keys(items).length > 1) { /* -------------------------------------------------------------------------- */ /* Create multiple tasks by batch */ /* -------------------------------------------------------------------------- */ const path = `${resource}/batch`; const tasksData = { tasks: items.map((_item, index) => Onfleet.getTaskFields.call(this, index, operation)), }; //@ts-ignore const { tasks: tasksCreated } = await onfleetApiRequest.call(this, 'POST', path, tasksData); return tasksCreated; } const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create a new task */ /* -------------------------------------------------------------------------- */ const taskData = Onfleet.getTaskFields.call(this, index, operation); if (!taskData) { continue; } responseData.push(await onfleetApiRequest.call(this, 'POST', resource, taskData)); } else if (operation === 'get') { /* -------------------------------------------------------------------------- */ /* Get a single task */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const shortId = String(id).length <= 8; const path = `${resource}${shortId ? '/shortId' : ''}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } else if (operation === 'clone') { /* -------------------------------------------------------------------------- */ /* Clone a task */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; // tslint:disable-next-line: no-any const taskData = Onfleet.getTaskFields.call(this, index, operation) as any; if (!taskData) { continue; } const path = `${resource}/${id}/clone`; responseData.push(await onfleetApiRequest.call(this, 'POST', path, taskData)); } else if (operation === 'delete') { /* -------------------------------------------------------------------------- */ /* Delete a single task */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; await onfleetApiRequest.call(this, 'DELETE', path); responseData.push({ success: true }); } else if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get all tasks */ /* -------------------------------------------------------------------------- */ const taskData = Onfleet.getTaskFields.call(this, 0, operation) as IDataObject; if (!taskData) return []; const returnAll = this.getNodeParameter('returnAll', 0, false); const path = `${resource}/all`; let tasks; if (returnAll === true) { tasks = await onfleetApiRequestAllItems.call(this, 'tasks', 'GET', path, {}, taskData); } else { const limit = this.getNodeParameter('limit', 0); tasks = await onfleetApiRequest.call(this, 'GET', path, {}, taskData); tasks = tasks.tasks; tasks = tasks.splice(0, limit); } responseData.push(...tasks); } else if (operation === 'complete') { /* -------------------------------------------------------------------------- */ /* Force complete a task */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const taskData = Onfleet.getTaskFields.call(this, index, operation); if (!taskData) { continue; } const path = `${resource}/${id}/complete`; await onfleetApiRequest.call(this, 'POST', path, taskData); responseData.push({ success: true }); } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Update a task */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', 0) as string; const path = `${resource}/${id}`; const taskData = Onfleet.getTaskFields.call(this, index, operation); if (!taskData) { continue; } responseData.push(await onfleetApiRequest.call(this, 'PUT', path, taskData)); } } catch (error) { //@ts-ignore if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the destination operations * @param resource Resource to be executed (Destination) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeDestinationOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create destination */ /* -------------------------------------------------------------------------- */ const destinationData = Onfleet.getDestinationFields.call(this, index, operation); if (!destinationData) { continue; } responseData.push(await onfleetApiRequest.call(this, 'POST', resource, destinationData)); } else if (operation === 'get') { /* -------------------------------------------------------------------------- */ /* Get single destination */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the organization operations * @param resource Resource to be executed (Organization) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeOrganizationOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'get') { /* -------------------------------------------------------------------------- */ /* Get organization details */ /* -------------------------------------------------------------------------- */ const path = 'organization'; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } else if (operation === 'getDelegatee') { /* -------------------------------------------------------------------------- */ /* Get organization delegatee */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the recipient operations * @param resource Resource to be executed (Recipient) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeRecipientOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create a new recipient */ /* -------------------------------------------------------------------------- */ const recipientData = Onfleet.getRecipientFields.call(this, index, operation); if (!recipientData) { continue; } responseData.push(await onfleetApiRequest.call(this, 'POST', resource, recipientData)); } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Update a recipient */ /* -------------------------------------------------------------------------- */ const recipientData = Onfleet.getRecipientFields.call(this, index, operation); if (!recipientData) { continue; } const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'PUT', path, recipientData)); } else if (operation === 'get') { /* -------------------------------------------------------------------------- */ /* Get recipient information */ /* -------------------------------------------------------------------------- */ const lookupBy = this.getNodeParameter('getBy', index) as string; const lookupByValue = this.getNodeParameter(lookupBy, index) as string; const path = `${resource}${lookupBy === 'id' ? '' : '/' + lookupBy}/${lookupByValue}`; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the administrator operations * @param resource Resource to be executed (Administrator) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeAdministratorOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get administrators */ /* -------------------------------------------------------------------------- */ const returnAll = this.getNodeParameter('returnAll', 0, false); let adminUsers = await onfleetApiRequest.call(this, 'GET', resource); if (!returnAll) { const limit = this.getNodeParameter('limit', 0); adminUsers = adminUsers.slice(0, limit); } responseData.push(...adminUsers); } else if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create a new admin */ /* -------------------------------------------------------------------------- */ const adminData = Onfleet.getAdminFields.call(this, index, operation); responseData.push(await onfleetApiRequest.call(this, 'POST', resource, adminData)); } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Update admin */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const adminData = Onfleet.getAdminFields.call(this, index, operation); const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'PUT', path, adminData)); } else if (operation === 'delete') { /* -------------------------------------------------------------------------- */ /* Delete admin */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; await onfleetApiRequest.call(this, 'DELETE', path); responseData.push({ success: true }); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the hub operations * @param resource Resource to be executed (Hub) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeHubOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get all hubs */ /* -------------------------------------------------------------------------- */ const returnAll = this.getNodeParameter('returnAll', 0, false); let hubs = await onfleetApiRequest.call(this, 'GET', resource); if (returnAll === false) { const limit = this.getNodeParameter('limit', 0); hubs = hubs.slice(0, limit); } responseData.push(...hubs); } else if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create a new hub */ /* -------------------------------------------------------------------------- */ const hubData = Onfleet.getHubFields.call(this, index, operation); responseData.push(await onfleetApiRequest.call(this, 'POST', resource, hubData)); } if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Update a hub */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const hubData = Onfleet.getHubFields.call(this, index, operation); const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'PUT', path, hubData)); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the worker operations * @param resource Resource to be executed (Worker) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeWorkerOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get all workers */ /* -------------------------------------------------------------------------- */ const byLocation = this.getNodeParameter('byLocation', index) as boolean; const returnAll = this.getNodeParameter('returnAll', index, false) as boolean; let workers; if (byLocation) { const longitude = this.getNodeParameter('longitude', index) as string; const latitude = this.getNodeParameter('latitude', index) as number; const filters = this.getNodeParameter('filters', index) as IDataObject; const path = `${resource}/location`; workers = await onfleetApiRequest.call( this, 'GET', path, {}, { longitude, latitude, ...filters }, ); workers = workers.workers; } else { const workerFilters = Onfleet.getWorkerFields.call( this, 0, operation, ) as OnfleetWorkerFilter; workers = await onfleetApiRequest.call(this, 'GET', resource, {}, workerFilters); } if (!returnAll) { const limit = this.getNodeParameter('limit', index) as number; workers = workers.slice(0, limit); } responseData.push(...workers); } else if (operation === 'get') { /* -------------------------------------------------------------------------- */ /* Get a worker */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const workerFilters = Onfleet.getWorkerFields.call( this, index, operation, ) as OnfleetWorkerFilter; const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'GET', path, {}, workerFilters)); } else if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create a new worker */ /* -------------------------------------------------------------------------- */ const workerData = Onfleet.getWorkerFields.call(this, index, operation); responseData.push(await onfleetApiRequest.call(this, 'POST', resource, workerData)); } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Update worker */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const workerData = Onfleet.getWorkerFields.call(this, index, operation); const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'PUT', path, workerData)); } else if (operation === 'delete') { /* -------------------------------------------------------------------------- */ /* Delete worker */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; await onfleetApiRequest.call(this, 'DELETE', path); responseData.push({ success: true }); } else if (operation === 'getSchedule') { /* -------------------------------------------------------------------------- */ /* Get worker schedule */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}/schedule`; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } else if (operation === 'setSchedule') { /* -------------------------------------------------------------------------- */ /* Set a worker schedule */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const workerSchedule = Onfleet.getWorkerFields.call( this, index, operation, ) as OnfleetWorkerSchedule; const path = `${resource}/${id}/schedule`; responseData.push(await onfleetApiRequest.call(this, 'POST', path, workerSchedule)); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the webhook operations * @param resource Resource to be executed (Webhook) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeWebhookOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get all webhooks */ /* -------------------------------------------------------------------------- */ responseData.push(...(await onfleetApiRequest.call(this, 'GET', resource))); } else if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create a new webhook */ /* -------------------------------------------------------------------------- */ const webhookData = Onfleet.getWebhookFields.call(this, index, operation); responseData.push(await onfleetApiRequest.call(this, 'POST', resource, webhookData)); } else if (operation === 'delete') { /* -------------------------------------------------------------------------- */ /* Delete a webhook */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; await onfleetApiRequest.call(this, 'DELETE', path); responseData.push({ success: true }); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the containers operations * @param resource Resource to be executed (Container) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeContainerOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'get') { /* -------------------------------------------------------------------------- */ /* Get container by id and type */ /* -------------------------------------------------------------------------- */ const containerId = this.getNodeParameter('containerId', index) as string; const containerType = this.getNodeParameter('containerType', index) as string; const path = `${resource}/${containerType}/${containerId}`; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } else if (['addTask', 'updateTask'].includes(operation)) { /* -------------------------------------------------------------------------- */ /* Add or update tasks to container */ /* -------------------------------------------------------------------------- */ const containerId = this.getNodeParameter('containerId', index) as string; const containerType = this.getNodeParameter('containerType', index, 'workers') as string; const options = this.getNodeParameter('options', index) as IDataObject; const tasks = this.getNodeParameter('tasks', index) as Array; if (operation === 'addTask') { const type = this.getNodeParameter('type', index) as number; if (type === 1) { const tasksIndex = this.getNodeParameter('index', index) as number; tasks.unshift(tasksIndex); } else { tasks.unshift(type); } } const path = `${resource}/${containerType}/${containerId}`; responseData.push(await onfleetApiRequest.call(this, 'PUT', path, { tasks, ...options })); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } /** * Execute the team operations * @param resource Resource to be executed (Team) * @param operation Operation to be executed * @param items Number of items to process by the node */ static async executeTeamOperations( this: IExecuteFunctions, resource: string, operation: string, items: INodeExecutionData[], ): Promise { const responseData = []; for (const key of Object.keys(items)) { const index = Number(key); try { if (operation === 'getAll') { /* -------------------------------------------------------------------------- */ /* Get all teams */ /* -------------------------------------------------------------------------- */ const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean; let teams = await onfleetApiRequest.call(this, 'GET', resource); if (!returnAll) { const limit = this.getNodeParameter('limit', 0) as number; teams = teams.slice(0, limit); } responseData.push(...teams); } else if (operation === 'get') { /* -------------------------------------------------------------------------- */ /* Get a single team */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'GET', path)); } else if (operation === 'create') { /* -------------------------------------------------------------------------- */ /* Create a new team */ /* -------------------------------------------------------------------------- */ const teamData = Onfleet.getTeamFields.call(this, index, operation); responseData.push(await onfleetApiRequest.call(this, 'POST', resource, teamData)); } else if (operation === 'update') { /* -------------------------------------------------------------------------- */ /* Update a team */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const teamData = Onfleet.getTeamFields.call(this, index, operation); const path = `${resource}/${id}`; responseData.push(await onfleetApiRequest.call(this, 'PUT', path, teamData)); } else if (operation === 'delete') { /* -------------------------------------------------------------------------- */ /* Delete a team */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const path = `${resource}/${id}`; await onfleetApiRequest.call(this, 'DELETE', path); responseData.push({ success: true }); } else if (operation === 'getTimeEstimates') { /* -------------------------------------------------------------------------- */ /* Get driver time estimates for tasks that haven't been created yet */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const workerTimeEstimates = Onfleet.getTeamFields.call( this, index, operation, ) as OnfleetWorkerSchedule; const path = `${resource}/${id}/estimate`; responseData.push( await onfleetApiRequest.call(this, 'GET', path, {}, workerTimeEstimates), ); } else if (operation === 'autoDispatch') { /* -------------------------------------------------------------------------- */ /* Dynamically dispatching tasks on the fly */ /* -------------------------------------------------------------------------- */ const id = this.getNodeParameter('id', index) as string; const teamAutoDispatch = Onfleet.getTeamFields.call( this, index, operation, ) as OnfleetWorkerSchedule; const path = `${resource}/${id}/dispatch`; responseData.push(await onfleetApiRequest.call(this, 'POST', path, teamAutoDispatch)); } } catch (error) { if (this.continueOnFail()) { responseData.push({ error: (error as IDataObject).toString() }); continue; } throw error; } } return responseData; } }