HaulHub

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!

Enabling the Haul Hub 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.

Haulhub Javascript Payload

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;
	}
};

Last updated

Logo