601 lines
20 KiB
JavaScript
601 lines
20 KiB
JavaScript
const path = require("path");
|
|
const fs = require("fs").promises;
|
|
require("dotenv").config({
|
|
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
|
});
|
|
const client = require("../graphql-client/graphql-client").client;
|
|
// const CalcualteAllocations = require("../cdk/cdk-calculate-allocations").default;
|
|
const CreateFortellisLogEvent = require("./fortellis-logger");
|
|
const logger = require("../utils/logger");
|
|
const { INSERT_IOEVENT } = require("../graphql-client/queries");
|
|
const uuid = require("uuid").v4;
|
|
const AxiosLib = require("axios").default;
|
|
const axios = AxiosLib.create();
|
|
const axiosCurlirize = require("axios-curlirize").default;
|
|
|
|
// Custom error class for Fortellis API errors
|
|
class FortellisApiError extends Error {
|
|
constructor(message, details) {
|
|
super(message);
|
|
this.name = "FortellisApiError";
|
|
this.reqId = details.reqId;
|
|
this.url = details.url;
|
|
this.apiName = details.apiName;
|
|
this.errorData = details.errorData;
|
|
this.errorStatus = details.errorStatus;
|
|
this.errorStatusText = details.errorStatusText;
|
|
this.originalError = details.originalError;
|
|
}
|
|
}
|
|
|
|
axiosCurlirize(axios, (_result, _err) => {
|
|
//Left intentionally blank. We don't want to console.log. We handle logging the cURL in MakeFortellisCall once completed.
|
|
});
|
|
|
|
const getTransactionType = (jobid) => `fortellis:${jobid}`;
|
|
const defaultFortellisTTL = 60 * 60;
|
|
|
|
async function GetAuthToken() {
|
|
//Done with Authorization Code Flow
|
|
//https://docs.fortellis.io/docs/tutorials/solution-integration/authorization-code-flow/
|
|
const {
|
|
data: { access_token, expires_in, token_type }
|
|
} = await axios.post(
|
|
process.env.FORTELLIS_AUTH_URL,
|
|
{},
|
|
{
|
|
auth: {
|
|
username: process.env.FORTELLIS_KEY,
|
|
password: process.env.FORTELLIS_SECRET
|
|
},
|
|
params: {
|
|
grant_type: "client_credentials",
|
|
scope: "anonymous"
|
|
}
|
|
}
|
|
);
|
|
return access_token;
|
|
}
|
|
|
|
async function FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObject }) {
|
|
try {
|
|
const { setSessionTransactionData, getSessionTransactionData } = redisHelpers;
|
|
|
|
//Get Subscription ID from Transaction Envelope
|
|
const { SubscriptionID } = SubscriptionObject
|
|
? SubscriptionObject
|
|
: await getSessionTransactionData(socket.id, getTransactionType(jobid), `txEnvelope`);
|
|
if (!SubscriptionID) {
|
|
throw new Error("Subscription ID not found in transaction envelope.");
|
|
}
|
|
|
|
//Check to See if the subscription meta is in the Redis Cache.
|
|
const SubscriptionMetaFromCache = await getSessionTransactionData(
|
|
socket.id,
|
|
getTransactionType(jobid),
|
|
FortellisCacheEnums.SubscriptionMeta
|
|
);
|
|
|
|
// If it is, return it.
|
|
if (SubscriptionMetaFromCache) {
|
|
return SubscriptionMetaFromCache;
|
|
} else {
|
|
const access_token = await GetAuthToken();
|
|
const subscriptions = await axios.get(FortellisActions.GetSubscription.url, {
|
|
headers: { Authorization: `Bearer ${access_token}` },
|
|
logRequest: false
|
|
});
|
|
const SubscriptionMeta = subscriptions.data.subscriptions.find((s) => s.subscriptionId === SubscriptionID);
|
|
if (!SubscriptionMeta) {
|
|
throw new Error(`Subscription metadata not found for SubscriptionID: ${SubscriptionID}`);
|
|
}
|
|
if (setSessionTransactionData) {
|
|
await setSessionTransactionData(
|
|
socket.id,
|
|
getTransactionType(jobid),
|
|
FortellisCacheEnums.SubscriptionMeta,
|
|
SubscriptionMeta,
|
|
defaultFortellisTTL
|
|
);
|
|
}
|
|
return SubscriptionMeta;
|
|
}
|
|
} catch (error) {
|
|
CreateFortellisLogEvent(socket, "ERROR", `Error fetching subscription metadata.`, {
|
|
error: error.message,
|
|
stack: error.stack
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function GetDepartmentId({ apiName, debug = false, SubscriptionMeta, overrideDepartmentId }) {
|
|
if (!apiName) throw new Error("apiName not provided. Unable to get department without apiName.");
|
|
if (!SubscriptionMeta || !Array.isArray(SubscriptionMeta.apiDmsInfo)) {
|
|
throw new Error("Subscription metadata missing apiDmsInfo.");
|
|
}
|
|
if (debug) {
|
|
console.log("API Names & Departments ");
|
|
console.log("===========");
|
|
console.log(JSON.stringify(SubscriptionMeta.apiDmsInfo, null, 4));
|
|
console.log("===========");
|
|
}
|
|
|
|
const departmentIds = SubscriptionMeta.apiDmsInfo //Get the subscription object.
|
|
.find((info) => info.name === apiName)?.departments; //Departments are categorized by API name and have an array of departments.
|
|
|
|
if (overrideDepartmentId) {
|
|
return departmentIds && departmentIds.find((d) => d.id === overrideDepartmentId)?.id;
|
|
} else {
|
|
return departmentIds && departmentIds[0] && departmentIds[0].id; //TODO: This makes the assumption that there is only 1 department.
|
|
}
|
|
}
|
|
|
|
//Highest level function call to make a call to fortellis. This should be the only call required, and it will handle all the logic for making the call.
|
|
async function MakeFortellisCall({
|
|
apiName,
|
|
url,
|
|
headers = {},
|
|
body = {},
|
|
type = "post",
|
|
debug = false,
|
|
requestPathParams,
|
|
requestSearchParams = [], //Array of key/value strings like [["key", "value"]]
|
|
jobid,
|
|
redisHelpers,
|
|
socket,
|
|
SubscriptionObject, //This is used because of the get make models to bypass all of the redis calls.
|
|
overrideDepartmentId
|
|
}) {
|
|
//const { setSessionTransactionData, getSessionTransactionData } = redisHelpers;
|
|
|
|
const fullUrl = constructFullUrl({ url, pathParams: requestPathParams, requestSearchParams });
|
|
if (debug) console.log(`Executing ${type} to ${fullUrl}`);
|
|
const ReqId = uuid();
|
|
const access_token = await GetAuthToken();
|
|
const SubscriptionMeta = await FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObject });
|
|
const DepartmentId = await GetDepartmentId({ apiName, debug, SubscriptionMeta, overrideDepartmentId });
|
|
|
|
if (debug) {
|
|
console.log(
|
|
`ReqID: ${ReqId} | SubscriptionID: ${SubscriptionMeta.subscriptionId} | DepartmentId: ${DepartmentId}`
|
|
);
|
|
console.log(`Body Contents: ${JSON.stringify(body, null, 4)}`);
|
|
}
|
|
|
|
//Write to the IOEvent Log
|
|
|
|
await WriteToIOEventLog({
|
|
apiName,
|
|
fullUrl,
|
|
logger,
|
|
socket,
|
|
useremail: socket.user?.email,
|
|
type,
|
|
bodyshopid: ""
|
|
//bodyshopid: job.bodyshop.id,
|
|
})
|
|
|
|
try {
|
|
let result;
|
|
switch (type) {
|
|
case "post":
|
|
default:
|
|
result = await axios.post(fullUrl, body, {
|
|
headers: {
|
|
Authorization: `Bearer ${access_token}`,
|
|
"Subscription-Id": SubscriptionMeta.subscriptionId,
|
|
"Request-Id": ReqId,
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json",
|
|
...(DepartmentId && { "Department-Id": DepartmentId }),
|
|
...headers
|
|
}
|
|
});
|
|
break;
|
|
case "get":
|
|
result = await axios.get(fullUrl, {
|
|
headers: {
|
|
Authorization: `Bearer ${access_token}`,
|
|
"Subscription-Id": SubscriptionMeta.subscriptionId,
|
|
"Request-Id": ReqId,
|
|
Accept: "application/json",
|
|
"Department-Id": DepartmentId,
|
|
...headers
|
|
}
|
|
});
|
|
break;
|
|
case "put":
|
|
result = await axios.put(fullUrl, body, {
|
|
headers: {
|
|
Authorization: `Bearer ${access_token}`,
|
|
"Subscription-Id": SubscriptionMeta.subscriptionId,
|
|
"Request-Id": ReqId,
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json",
|
|
"Department-Id": DepartmentId,
|
|
...headers
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
if (debug) {
|
|
console.log(`ReqID: ${ReqId} Data`);
|
|
console.log(JSON.stringify(result.data, null, 4));
|
|
}
|
|
|
|
// await writeFortellisLogToFile({
|
|
// timestamp: new Date().toISOString(),
|
|
// reqId: ReqId,
|
|
// url: fullUrl,
|
|
// request:
|
|
// {
|
|
// requestcurl: result.config.curlCommand,
|
|
// reqid: result.config.headers["Request-Id"] || null,
|
|
// subscriptionId: result.config.headers["Subscription-Id"] || null,
|
|
// resultdata: result.data,
|
|
// resultStatus: result.status
|
|
// },
|
|
// user: socket?.user?.email,
|
|
// jobid: socket?.recordid
|
|
// });
|
|
|
|
logger.log(
|
|
"fortellis-log-event-json",
|
|
"DEBUG",
|
|
socket?.user?.email,
|
|
jobid,
|
|
{
|
|
requestcurl: result.config.curlCommand,
|
|
reqid: result.config.headers["Request-Id"] || null,
|
|
subscriptionId: result.config.headers["Subscription-Id"] || null,
|
|
resultdata: result.data,
|
|
resultStatus: result.status
|
|
},
|
|
);
|
|
|
|
if (result.data.checkStatusAfterSeconds) {
|
|
return DelayedCallback({
|
|
delayMeta: result.data,
|
|
access_token,
|
|
SubscriptionID: SubscriptionMeta.subscriptionId,
|
|
ReqId,
|
|
departmentIds: DepartmentId,
|
|
jobid,
|
|
socket
|
|
});
|
|
}
|
|
|
|
return result.data;
|
|
} catch (error) {
|
|
const errorDetails = {
|
|
reqId: ReqId,
|
|
url: fullUrl,
|
|
apiName,
|
|
errorData: error.response?.data,
|
|
errorStatus: error.response?.status,
|
|
errorStatusText: error.response?.statusText,
|
|
originalError: error
|
|
};
|
|
// await writeFortellisLogToFile({
|
|
// timestamp: new Date().toISOString(),
|
|
// reqId: ReqId,
|
|
// url: fullUrl,
|
|
// request:
|
|
// {
|
|
// requestcurl: error.config.curlCommand,
|
|
// reqid: error.config.headers["Request-Id"] || null,
|
|
// subscriptionId: error.config.headers["Subscription-Id"] || null,
|
|
// resultdata: error.message,
|
|
// resultStatus: error.status
|
|
// },
|
|
// user: socket?.user?.email,
|
|
// jobid: socket?.recordid
|
|
// });
|
|
|
|
logger.log(
|
|
"fortellis-log-event-error",
|
|
"ERROR",
|
|
socket?.user?.email,
|
|
socket?.recordid,
|
|
{
|
|
requestcurl: error.config.curlCommand,
|
|
reqid: error.config.headers["Request-Id"] || null,
|
|
subscriptionId: error.config.headers["Subscription-Id"] || null,
|
|
resultdata: error.message,
|
|
resultStatus: error.status
|
|
},
|
|
true
|
|
);
|
|
|
|
throw new FortellisApiError(`Fortellis API call failed for ${apiName}: ${error.message} | ${errorDetails?.errorData?.message}`, errorDetails);
|
|
}
|
|
}
|
|
|
|
//Some Fortellis calls return a batch result that isn't ready immediately.
|
|
//This function will check the status of the call and wait until it is ready.
|
|
//It will try 5 times before giving up.
|
|
async function DelayedCallback({ delayMeta, access_token, SubscriptionID, ReqId, departmentIds, jobid, socket }) {
|
|
for (let index = 0; index < 5; index++) {
|
|
await sleep(delayMeta.checkStatusAfterSeconds * 1000);
|
|
//Check to see if the call is ready.
|
|
const statusResult = await axios.get(delayMeta._links.status.href, {
|
|
headers: {
|
|
Authorization: `Bearer ${access_token}`,
|
|
"Subscription-Id": SubscriptionID,
|
|
"Request-Id": ReqId,
|
|
"Department-Id": departmentIds[0].id
|
|
}
|
|
});
|
|
|
|
//TODO: Add a check if the status result is not ready, to try again.
|
|
if (statusResult.data.status === "complete") {
|
|
//This may have to check again if it isn't ready.
|
|
const batchResult = await axios.get(statusResult.data._links.result.href, {
|
|
headers: {
|
|
Authorization: `Bearer ${access_token}`,
|
|
"Subscription-Id": SubscriptionID,
|
|
"Request-Id": ReqId
|
|
//"Department-Id": departmentIds[0].id
|
|
}
|
|
});
|
|
logger.log(
|
|
"fortellis-log-event-json-DelayedCallback",
|
|
"DEBUG",
|
|
socket?.user?.email,
|
|
jobid,
|
|
{
|
|
requestcurl: batchResult.config.curlCommand,
|
|
reqid: batchResult.config.headers["Request-Id"] || null,
|
|
subscriptionId: batchResult.config.headers["Subscription-Id"] || null,
|
|
resultdata: batchResult.data,
|
|
resultStatus: batchResult.status
|
|
},
|
|
);
|
|
// await writeFortellisLogToFile({
|
|
// timestamp: new Date().toISOString(),
|
|
// reqId: ReqId,
|
|
// url: statusResult.data._links.result.href,
|
|
// request:
|
|
// {
|
|
// requestcurl: batchResult.config.curlCommand,
|
|
// reqid: batchResult.config.headers["Request-Id"] || null,
|
|
// subscriptionId: batchResult.config.headers["Subscription-Id"] || null,
|
|
// resultdata: batchResult.data,
|
|
// resultStatus: batchResult.status
|
|
// },
|
|
// });
|
|
return batchResult;
|
|
} else {
|
|
return "Error!!! Still need to implement batch waiting.";
|
|
}
|
|
}
|
|
}
|
|
function sleep(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function writeFortellisLogToFile(logObject) {
|
|
//The was only used for the certification. Commented out in case of future need.
|
|
try {
|
|
const logsDir = path.join(process.cwd(), 'logs');
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const filename = `fortellis-${timestamp}.json`;
|
|
const filepath = path.join(logsDir, filename);
|
|
|
|
console.log(`[writeFortellisLogToFile] Writing to: ${filepath}`);
|
|
console.log(`[writeFortellisLogToFile] process.cwd(): ${process.cwd()}`);
|
|
|
|
// Ensure logs directory exists
|
|
await fs.mkdir(logsDir, { recursive: true });
|
|
|
|
// Write log object to file
|
|
await fs.writeFile(filepath, JSON.stringify(logObject, null, 2), 'utf8');
|
|
|
|
console.log(`Fortellis log written to: ${filepath}`,);
|
|
} catch (err) {
|
|
console.error('Failed to write Fortellis log to file:', err);
|
|
}
|
|
}
|
|
|
|
async function WriteToIOEventLog({ apiName, type, fullUrl, bodyshopid, useremail, logger, socket }) {
|
|
try {
|
|
await client.request(INSERT_IOEVENT, {
|
|
event: {
|
|
operationname: `fortellis-${apiName}-${type}`,
|
|
//time,
|
|
//dbevent,
|
|
env: isProduction ? "production" : "test",
|
|
variables: { fullUrl, type, apiName },
|
|
bodyshopid: socket.bodyshopId,
|
|
useremail
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.log(
|
|
"fortellis-tracking-error",
|
|
"ERROR",
|
|
socket?.user?.email,
|
|
socket?.recordid,
|
|
{
|
|
operationname: `fortellis-${apiName}-`,
|
|
//time,
|
|
//dbevent,
|
|
env: isProduction ? "production" : "test",
|
|
variables: {},
|
|
bodyshopid,
|
|
useremail,
|
|
error: error.message,
|
|
stack: error.stack
|
|
},
|
|
true
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
const isProduction = process.env.NODE_ENV === "production";
|
|
|
|
//Get requests should have the trailing slash as they are used that way in the calls.
|
|
const FortellisActions = {
|
|
GetSubscription: {
|
|
url: isProduction
|
|
? "https://subscriptions.fortellis.io/v1/solution/subscriptions"
|
|
: "https://subscriptions.fortellis.io/v1/solution/subscriptions",
|
|
type: "get",
|
|
apiName: "Fortellis Get Subscriptions"
|
|
},
|
|
QueryVehicles: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdkdrive/service/v1/vehicles/"
|
|
: "https://api.fortellis.io/cdk-test/cdkdrive/service/v1/vehicles/",
|
|
type: "get",
|
|
apiName: "Service Vehicle - Query Vehicles"
|
|
},
|
|
GetMakeModel: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/makemodel/v2/bulk"
|
|
: "https://api.fortellis.io/cdk-test/drive/makemodel/v2/bulk",
|
|
type: "get",
|
|
apiName: "CDK Drive Get Make Model Lite"
|
|
},
|
|
GetVehicleId: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/vehicle-ids/" //Request path params of vins
|
|
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/vehicle-ids/",
|
|
type: "get",
|
|
apiName: "CDK Drive Post Service Vehicle"
|
|
},
|
|
GetVehicleById: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/" //Request path params of vehicleId
|
|
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/",
|
|
type: "get",
|
|
apiName: "CDK Drive Post Service Vehicle"
|
|
},
|
|
QueryCustomerByName: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/customerpost/v1/search"
|
|
: "https://api.fortellis.io/cdk-test/drive/customerpost/v1/search",
|
|
type: "get",
|
|
apiName: "CDK Drive Post Customer"
|
|
},
|
|
ReadCustomer: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/customerpost/v1/" //Customer ID is request param.
|
|
: "https://api.fortellis.io/cdk-test/drive/customerpost/v1/",
|
|
type: "get",
|
|
apiName: "CDK Drive Post Customer"
|
|
},
|
|
CreateCustomer: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/customerpost/v1/"
|
|
: "https://api.fortellis.io/cdk-test/drive/customerpost/v1/",
|
|
type: "post",
|
|
apiName: "CDK Drive Post Customer"
|
|
},
|
|
InsertVehicle: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/"
|
|
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/",
|
|
type: "post",
|
|
apiName: "CDK Drive Post Service Vehicle"
|
|
},
|
|
UpdateVehicle: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/service-vehicle-mgmt/v2/"
|
|
: "https://api.fortellis.io/cdk-test/drive/service-vehicle-mgmt/v2/",
|
|
type: "put",
|
|
apiName: "CDK Drive Post Service Vehicle"
|
|
},
|
|
GetCOA: {
|
|
type: "get",
|
|
apiName: "CDK Drive Get Chart of Accounts",
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/chartofaccounts/v2/bulk"
|
|
: "https://api.fortellis.io/cdk-test/drive/chartofaccounts/v2/bulk",
|
|
waitForResult: true
|
|
},
|
|
StartWip: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/glpost/startWIP"
|
|
: "https://api.fortellis.io/cdk-test/drive/glpost/startWIP",
|
|
type: "post",
|
|
apiName: "CDK Drive Post Accounts GL"
|
|
},
|
|
// TranBatchWip: {
|
|
// url: isProduction
|
|
// ? "https://api.fortellis.io/cdk/drive/glpost/transBatchWIP"
|
|
// : "https://api.fortellis.io/cdk-test/drive/glpost/transBatchWIP",
|
|
// type: "post",
|
|
// apiName: "CDK Drive Post Accounts GL"
|
|
// },
|
|
PostBatchWip: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/glpost/postBatchWIP"
|
|
: "https://api.fortellis.io/cdk-test/drive/glpost/postBatchWIP",
|
|
type: "post",
|
|
apiName: "CDK Drive Post Accounts GL"
|
|
},
|
|
DeleteTranWip: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/glpost/postWIP"
|
|
: "https://api.fortellis.io/cdk-test/drive/glpost/postWIP",
|
|
type: "post",
|
|
apiName: "CDK Drive Post Accounts GL"
|
|
},
|
|
QueryErrorWip: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/glpost/errWIP/"
|
|
: "https://api.fortellis.io/cdk-test/drive/glpost/errWIP/",
|
|
type: "get",
|
|
apiName: "CDK Drive Post Accounts GL"
|
|
},
|
|
ServiceHistoryInsert: {
|
|
url: isProduction
|
|
? "https://api.fortellis.io/cdk/drive/post/service-vehicle-history-mgmt/v2/"
|
|
: "https://api.fortellis.io/cdk-test/drive/post/service-vehicle-history-mgmt/v2/",
|
|
type: "post",
|
|
apiName: "CDK Drive Post Service Vehicle History"
|
|
}
|
|
};
|
|
|
|
const FortellisCacheEnums = {
|
|
txEnvelope: "txEnvelope",
|
|
DMSBatchTxn: "DMSBatchTxn",
|
|
SubscriptionMeta: "SubscriptionMeta",
|
|
DepartmentId: "DepartmentId",
|
|
JobData: "JobData",
|
|
DMSVid: "DMSVid",
|
|
DMSVeh: "DMSVeh",
|
|
DMSVehCustomer: "DMSVehCustomer",
|
|
DMSCustList: "DMSCustList",
|
|
DMSCust: "DMSCust",
|
|
selectedCustomerId: "selectedCustomerId",
|
|
DMSTransHeader: "DMSTransHeader",
|
|
transWips: "transWips",
|
|
DmsBatchTxnPost: "DmsBatchTxnPost",
|
|
DMSVehHistory: "DMSVehHistory"
|
|
};
|
|
|
|
function constructFullUrl({ url, pathParams = "", requestSearchParams = [] }) {
|
|
// Ensure the base URL ends with a single "/"
|
|
url = url.replace(/\/+$/, "/");
|
|
const fullPath = pathParams ? `${url}${pathParams}` : url;
|
|
const searchParams = new URLSearchParams(requestSearchParams).toString();
|
|
const fullUrl = searchParams ? `${fullPath}?${searchParams}` : fullPath;
|
|
return fullUrl;
|
|
}
|
|
|
|
module.exports = {
|
|
GetAuthToken,
|
|
FortellisCacheEnums,
|
|
MakeFortellisCall,
|
|
FortellisActions,
|
|
getTransactionType,
|
|
defaultFortellisTTL,
|
|
FortellisApiError,
|
|
GetDepartmentId
|
|
};
|