For assistance in building the Haul Hub Integration, contact us at 865-219-2980 or support@tacinsight.com
Note: You will need access to the API Module in order to build this integration!
Navigate to Admin Settings (Gear Icon) > API Info
Click on the "Show" button to access the Webhooks Dashboard.
Click the "+ Add Endpoint" button to build the webhook.
In the Message Filtering options, select ticket.created
, ticket.updated
, and ticket.voided
.
Build the Endpoint URL following this layout:
https://integrations.fast-weigh.dev/api/HaulHubDOT?fw_api_key=<insert_fast-weigh_api_key_here>&hh_api_key=<insert_haulhub_api_key_here>&fw_graphql_server=<insert_graphql_server_prefix_here>
You can find your API Key and GraphQL server (the first part of the GraphQL Endpoint URL) on the API Info Page.
It should look something like this when you are finished:
https://integrations.fast-weigh.dev/api/HaulHubDOT?fw_api_key=11111-11111-1111-11111&hh_api_key=2222222-22222-22222-22222&fw_graphql_server=fwt
Hit the Create button to create the new webhook.
Check the "Certified" checkbox on any order that you want tickets to flow to HaulHub.
const { GraphQLClient, gql } = require("graphql-request");
const { DateTime } = require("luxon");
require("isomorphic-fetch");
module.exports = async function (context, req) {
try {
context.log("===> HaulHubDOT firing <===");
const { query, body } = req;
// Validate the request
if (!query.fw_api_key || !query.hh_api_key || !query.fw_graphql_server) {
context.log.error(
"===> HaulHubDOT: Missing required query parameters <==="
);
context.res = {
status: 400,
body: JSON.stringify({
message:
"Please pass fw_api_key, hh_api_key, and fw_graphql_server on the query string",
}),
};
return;
}
// Created or Updated?
const CREATED_OR_UPDATED =
body.eventType === "ticket.created" ? "Created" : "Updated";
// Get the ticket from API
const gqlQuery = gql`
query get_ticket {
LoadTicket(where: { TicketKey: { _eq: ${body.key} } }) {
TicketNumber
TicketDate
TicketDateTime
UTCOffset
GrossWeight
TareWeight
NetWeight
Job
Phase
Operator
Note
Void
Truck {
TruckID
LicenseNum
Hauler {
HaulerID
HaulerName
}
}
Order {
OrderNumber
Description
Certified
PONumber
DefaultJob
Customer {
CustomerID
CustomerName
}
}
OrderProductKey
OrderProduct {
Yard {
YardName
YardDescription
Location {
LocationName
LocationDescription
Region {
RegionName
RegionDescription
RemitName
}
}
}
Product {
ProductID
ProductDescription
UnitOfMeasure
}
}
}
}
`;
const server =
"https://" + query.fw_graphql_server + ".fast-weigh.dev/v1/graphql";
const graphqlClient = new GraphQLClient(server, {
headers: {
"x-api-key": query.fw_api_key,
},
});
const data = await graphqlClient.request(gqlQuery);
let ticket = data.LoadTicket[0];
// Log the ticket
context.log("===> HaulHubDOT: Ticket <===");
context.log(ticket);
// If ticket is not certified, stop here
if (!ticket.Order.Certified) {
context.log(
"===> HaulHubDOT: Ticket is not certified. Not posting. <==="
);
context.res = {
status: 200,
body: JSON.stringify({
message: "Ticket is not marked as certified. Not posting to HaulHub.",
}),
};
return;
}
// Query for all tickets matching the ticket.OrderProductKey and ticket.TicketDateTime
const gqlQuerySummary = gql`
query get_all_tickets {
LoadTicket(where: {
_and: [
{OrderProductKey: {_eq: ${ticket.OrderProductKey}}},
{TicketDate: {_eq: "${ticket.TicketDate}"}},
{Void: {_eq: false}}
]}) {
TicketNumber
NetWeight
}
}
`;
const dataSummary = await graphqlClient.request(gqlQuerySummary);
let tickets = dataSummary.LoadTicket;
// Log the tickets
context.log(
"===> HaulHubDOT: Tickets matching OrderProductKey and TicketDateTime <==="
);
context.log(tickets);
// Sum the net weights
let netWeightSum = 0;
tickets.forEach((t) => {
netWeightSum += t.NetWeight;
});
// Round to 2 decimal places
netWeightSum = Math.round(netWeightSum * 100) / 100;
// Count the tickets
let ticketCount = tickets.length;
// Create the utcOffset suffix
const offset = "UTC" + ticket.UTCOffset.toString();
// Format the date
const datetimestamp = DateTime.fromISO(ticket.TicketDateTime + "Z", {
zone: offset,
}).toISO();
// Build HaulHub DOT payload
const haulhubPayload = {
tickets: [
{
slip_number: ticket.TicketNumber,
dispatched_at: datetimestamp,
last_updated: datetimestamp,
timestamp: datetimestamp,
gross_amount: ticket.GrossWeight,
tare_amount: ticket.TareWeight,
net_amount: ticket.NetWeight,
quantity_uom_code: ticket.OrderProduct.Product.UnitOfMeasure,
client_id: ticket.Order.Customer.CustomerID,
client_name: ticket.Order.Customer.CustomerName,
comments: ticket.Note,
contract_number: ticket.Order.PONumber,
customer_name: ticket.Order.Customer.CustomerName,
deputy: ticket.Operator,
dispatch_number: ticket.TicketNumber,
job: ticket.Order.Description,
job_number: ticket.Job ? ticket.Job : ticket.Order.DefaultJob,
line_item_description: ticket.OrderProduct.Product.ProductDescription,
line_item_quantity: ticket.NetWeight,
loads_today: ticketCount,
material_number: ticket.OrderProduct.Product.ProductID,
phase_code: ticket.Phase,
plant_id: ticket.OrderProduct.Yard.YardName,
plant_name: ticket.OrderProduct.Yard.YardDescription,
product_code: ticket.OrderProduct.Product.ProductID,
purchase_order_number: ticket.Order.PONumber,
quantity_shipped_today: netWeightSum,
supplier: ticket.OrderProduct.Yard.Location.Region.RemitName,
supplier_code: ticket.OrderProduct.Yard.Location.Region.RegionName,
tonnage: ticket.NetWeight,
void: ticket.Void,
weighmaster: ticket.Operator,
fleet_id: ticket.Truck.Hauler.HaulerID,
fleet_name: ticket.Truck.Hauler.HaulerName,
truck_id: ticket.Truck.TruckID,
license_1: ticket.Truck.LicenseNum,
},
],
};
// If this is a ticket update, ignore loads_today and quantity_shipped_today
if (CREATED_OR_UPDATED !== "Created") {
delete haulhubPayload.tickets[0].loads_today;
delete haulhubPayload.tickets[0].quantity_shipped_today;
}
context.log("===> HaulHubDOT: Payload sent to HaulHub <===");
context.log(haulhubPayload);
// Send to HaulHub
const haulhubResponse = await fetch(
"https://app.haulhub.com/integrations/api/v1/tickets",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-hh-api-token": query.hh_api_key,
},
body: JSON.stringify(haulhubPayload),
}
);
const haulhubResponseJson = await haulhubResponse.json();
if (haulhubResponseJson.status > 299) {
// Handle HH response error
context.log.error("===> HaulHubDOT: HaulHub returned an error <===");
context.res = {
status: haulhubResponseJson.status,
body: haulhubResponseJson.message,
};
return;
}
// Success!
context.log("===> HaulHubDOT: Success! <===");
context.log(haulhubResponseJson);
context.res = {
body: JSON.stringify({
success: true,
haulhubResponse: haulhubResponseJson,
}),
};
return;
} catch (error) {
// Global error catch
context.log.error("===> HaulHubDOT: Global error catch <===");
context.log(error.message);
context.res = {
status: 500,
body: JSON.stringify(error.message),
};
return;
}
};