Merge remote-tracking branch 'origin/feature/IO-2776-cdk-fortellis' into feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration

This commit is contained in:
Dave
2025-11-14 15:22:23 -05:00
18 changed files with 395 additions and 56587 deletions

View File

@@ -157,7 +157,7 @@ async function QueryJobData(socket, jobid) {
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid });
WsLogger.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
//WsLogger.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
@@ -687,13 +687,13 @@ async function InsertFailedExportLog(socket, error) {
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.INSERT_EXPORT_LOG, {
log: {
logs: [{
bodyshopid: socket.JobData.bodyshop.id,
jobid: socket.JobData.id,
successful: false,
message: JSON.stringify(error),
useremail: socket.user.email
}
}]
});
return result;

View File

@@ -4,7 +4,7 @@ const queries = require("../graphql-client/queries");
const CreateFortellisLogEvent = require("../fortellis/fortellis-logger");
const Dinero = require("dinero.js");
const _ = require("lodash");
const WsLogger = require("../web-sockets/createLogEvent")
const WsLogger = require("../web-sockets/createLogEvent");
const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
@@ -14,13 +14,12 @@ const { DiscountNotAlreadyCounted } = InstanceManager({
exports.defaultRoute = async function (req, res) {
try {
//Fortellis TODO: determine when this is called and whether refactor is required.
WsLogger.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
return res.status(200).json({ data: calculateAllocations(req, jobData) });
} catch (error) {
////console.log(error);
WsLogger.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
WsLogger.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error.stack}`);
res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
}
};
@@ -30,9 +29,9 @@ exports.default = async function (socket, jobid, isFortellis = false) {
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid, isFortellis);
return calculateAllocations(socket, jobData, isFortellis);
} catch (error) {
////console.log(error);
const loggingFunction = isFortellis ? CreateFortellisLogEvent : WsLogger.createLogEvent;
loggingFunction(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
loggingFunction(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error.stack}`);
}
};
@@ -42,7 +41,7 @@ async function QueryJobData(connectionData, token, jobid, isFortellis) {
loggingFunction(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
loggingFunction(connectionData, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
//loggingFunction(connectionData, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
@@ -137,11 +136,11 @@ function calculateAllocations(connectionData, job, isFortellis) {
? val.prt_dsmk_m
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(val.act_price * 100)
})
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
amount: Math.round(val.act_price * 100)
})
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
: Dinero()
);
@@ -199,8 +198,8 @@ function calculateAllocations(connectionData, job, isFortellis) {
let TicketTotal = Dinero({
amount: Math.round(
ticket.rate *
(ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) *
100
(ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) *
100
)
});
//Add it to the right cost center.
@@ -432,37 +431,37 @@ function calculateAllocations(connectionData, job, isFortellis) {
...(job.job_totals.totals.ttl_adjustment
? [
{
center: "SUB ADJ",
sale: Dinero(job.job_totals.totals.ttl_adjustment),
cost: Dinero(),
profitCenter: {
name: "SUB ADJ",
accountdesc: "SUB ADJ",
accountitem: "SUB ADJ",
accountname: "SUB ADJ",
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber
},
costCenter: {}
}
]
{
center: "SUB ADJ",
sale: Dinero(job.job_totals.totals.ttl_adjustment),
cost: Dinero(),
profitCenter: {
name: "SUB ADJ",
accountdesc: "SUB ADJ",
accountitem: "SUB ADJ",
accountname: "SUB ADJ",
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber
},
costCenter: {}
}
]
: []),
...(job.job_totals.totals.ttl_tax_adjustment
? [
{
center: "TAX ADJ",
sale: Dinero(job.job_totals.totals.ttl_tax_adjustment),
cost: Dinero(),
profitCenter: {
name: "TAX ADJ",
accountdesc: "TAX ADJ",
accountitem: "TAX ADJ",
accountname: "TAX ADJ",
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber
},
costCenter: {}
}
]
{
center: "TAX ADJ",
sale: Dinero(job.job_totals.totals.ttl_tax_adjustment),
cost: Dinero(),
profitCenter: {
name: "TAX ADJ",
accountdesc: "TAX ADJ",
accountitem: "TAX ADJ",
accountname: "TAX ADJ",
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber
},
costCenter: {}
}
]
: [])
];
}

View File

@@ -5,6 +5,12 @@ const CdkWsdl = require("./cdk-wsdl").default;
const logger = require("../utils/logger");
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const {
MakeFortellisCall,
FortellisActions,
GetAuthToken,
GetDepartmentId
} = require("../fortellis/fortellis-helpers");
// exports.default = async function (socket, cdk_dealerid) {
// try {
@@ -105,3 +111,85 @@ async function GetCdkMakes(req, cdk_dealerid) {
throw new Error(error);
}
}
async function GetFortellisMakes(req, cdk_dealerid) {
logger.log("fortellis-replace-makes-models", "DEBUG", req.user.email, null, {
cdk_dealerid
});
try {
const result = await MakeFortellisCall({
...FortellisActions.GetMakeModel,
headers: {},
redisHelpers: {
setSessionTransactionData: () => {
return null;
},
getSessionTransactionData: () => {
return null;
}
},
socket: { emit: () => null },
jobid: null,
body: {},
SubscriptionObject: {
SubscriptionID: cdk_dealerid
}
});
logger.log("fortellis-replace-makes-models-response", "ERROR", req.user.email, null, {
cdk_dealerid,
xml: result
});
return result.data;
} catch (error) {
logger.log("fortellis-replace-makes-models-error", "ERROR", req.user.email, null, {
cdk_dealerid,
error
});
throw new Error(error);
}
}
exports.fortellis = async function ReloadFortellisMakes(req, res) {
const { bodyshopid, cdk_dealerid } = req.body;
try {
//Query all CDK Models
const newList = await GetFortellisMakes(req, cdk_dealerid);
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
const deleteResult = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.DELETE_ALL_DMS_VEHICLES, {});
//Insert the new ones.
const insertResult = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_DMS_VEHICLES, {
vehicles: newList.map((i) => {
return {
bodyshopid,
makecode: i.makeCode,
modelcode: i.modelCode,
make: i.makeFullName,
model: i.modelFullName
};
})
});
logger.log("fortellis-replace-makes-models-success", "DEBUG", req.user.email, null, {
cdk_dealerid,
count: newList.length
});
res.sendStatus(200);
} catch (error) {
logger.log("fortellis-replace-makes-models-error", "ERROR", req.user.email, null, {
cdk_dealerid,
error: error.message,
stack: error.stack
});
res.status(500).json(error);
}
};

View File

@@ -159,7 +159,7 @@ async function QueryJobData(socket, jobid) {
.setHeaders({ Authorization: `Bearer ${currentToken}` })
.request(queries.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid });
WsLogger.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
//WsLogger.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
@@ -993,13 +993,13 @@ async function InsertFailedExportLog(socket, error) {
const result = await client
.setHeaders({ Authorization: `Bearer ${currentToken}` })
.request(queries.INSERT_EXPORT_LOG, {
log: {
logs: [{
bodyshopid: socket.JobData.bodyshop.id,
jobid: socket.JobData.id,
successful: false,
message: JSON.stringify(error),
useremail: socket.user.email
}
}]
});
return result;

View File

@@ -9,13 +9,13 @@ const logger = require("../utils/logger");
const uuid = require("uuid").v4;
const AxiosLib = require("axios").default;
const axios = AxiosLib.create();
const axiosCurlirize = require('axios-curlirize').default;
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.name = "FortellisApiError";
this.reqId = details.reqId;
this.url = details.url;
this.apiName = details.apiName;
@@ -26,14 +26,8 @@ class FortellisApiError extends Error {
}
}
axiosCurlirize(axios, (result, err) => {
const { command } = result;
console.log("*** ~ axiosCurlirize ~ command:", command);
// if (err) {
// use your logger here
// } else {
// }
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}`;
@@ -63,12 +57,14 @@ async function GetAuthToken() {
return access_token;
}
async function FetchSubscriptions({ redisHelpers, socket, jobid }) {
async function FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObject }) {
try {
const { setSessionTransactionData, getSessionTransactionData } = redisHelpers;
//Get Subscription ID from Transaction Envelope
const { SubscriptionID } = await getSessionTransactionData(socket.id, getTransactionType(jobid), `txEnvelope`);
const { SubscriptionID } = SubscriptionObject
? SubscriptionObject
: await getSessionTransactionData(socket.id, getTransactionType(jobid), `txEnvelope`);
if (!SubscriptionID) {
throw new Error("Subscription ID not found in transaction envelope.");
}
@@ -85,17 +81,20 @@ async function FetchSubscriptions({ redisHelpers, socket, jobid }) {
return SubscriptionMetaFromCache;
} else {
const access_token = await GetAuthToken();
const subscriptions = await axios.get(`https://subscriptions.fortellis.io/v1/solution/subscriptions`, {
headers: { Authorization: `Bearer ${access_token}` }
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);
await setSessionTransactionData(
socket.id,
getTransactionType(jobid),
FortellisCacheEnums.SubscriptionMeta,
SubscriptionMeta,
defaultFortellisTTL
);
if (setSessionTransactionData) {
await setSessionTransactionData(
socket.id,
getTransactionType(jobid),
FortellisCacheEnums.SubscriptionMeta,
SubscriptionMeta,
defaultFortellisTTL
);
}
return SubscriptionMeta;
}
} catch (error) {
@@ -106,25 +105,24 @@ async function FetchSubscriptions({ redisHelpers, socket, jobid }) {
}
}
async function GetDepartmentId({ apiName, debug = false, SubscriptionMeta }) {
async function GetDepartmentId({ apiName, debug = false, SubscriptionMeta, overrideDepartmentId }) {
if (!apiName) throw new Error("apiName not provided. Unable to get department without apiName.");
if (debug) {
console.log("API Names & Departments ");
console.log("===========");
console.log(
JSON.stringify(
SubscriptionMeta.apiDmsInfo,
null,
4
)
);
console.log(JSON.stringify(SubscriptionMeta.apiDmsInfo, null, 4));
console.log("===========");
}
//TODO: Verify how to select the correct department.
const departmentIds2 = SubscriptionMeta.apiDmsInfo //Get the subscription object.
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.
return departmentIds2 && departmentIds2[0] && departmentIds2[0].id; //TODO: This makes the assumption that there is only 1 department.
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.
@@ -134,22 +132,23 @@ async function MakeFortellisCall({
headers = {},
body = {},
type = "post",
debug = true,
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 { setSessionTransactionData, getSessionTransactionData } = redisHelpers;
const fullUrl = constructFullUrl({ url, pathParams: requestPathParams, requestSearchParams });
if (debug) logger.log(`Executing ${type} to ${fullUrl}`);
if (debug) console.log(`Executing ${type} to ${fullUrl}`);
const ReqId = uuid();
const access_token = await GetAuthToken();
const SubscriptionMeta = await FetchSubscriptions({ redisHelpers, socket, jobid });
const DepartmentId = await GetDepartmentId({ apiName, debug, SubscriptionMeta });
const SubscriptionMeta = await FetchSubscriptions({ redisHelpers, socket, jobid, SubscriptionObject });
const DepartmentId = await GetDepartmentId({ apiName, debug, SubscriptionMeta, overrideDepartmentId });
if (debug) {
console.log(
@@ -168,7 +167,9 @@ async function MakeFortellisCall({
Authorization: `Bearer ${access_token}`,
"Subscription-Id": SubscriptionMeta.subscriptionId,
"Request-Id": ReqId,
...DepartmentId && { "Department-Id": DepartmentId },
"Content-Type": "application/json",
Accept: "application/json",
...(DepartmentId && { "Department-Id": DepartmentId }),
...headers
}
});
@@ -179,6 +180,7 @@ async function MakeFortellisCall({
Authorization: `Bearer ${access_token}`,
"Subscription-Id": SubscriptionMeta.subscriptionId,
"Request-Id": ReqId,
Accept: "application/json",
"Department-Id": DepartmentId,
...headers
}
@@ -190,6 +192,8 @@ async function MakeFortellisCall({
Authorization: `Bearer ${access_token}`,
"Subscription-Id": SubscriptionMeta.subscriptionId,
"Request-Id": ReqId,
Accept: "application/json",
"Content-Type": "application/json",
"Department-Id": DepartmentId,
...headers
}
@@ -211,11 +215,23 @@ async function MakeFortellisCall({
departmentIds: DepartmentId
});
}
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
},
);
return result.data;
} catch (error) {
console.log(`ReqID: ${ReqId} Error`, error.response?.data);
//console.log(`ReqID: ${ReqId} Full Error`, JSON.stringify(error, null, 4));
const errorDetails = {
reqId: ReqId,
url: fullUrl,
@@ -226,12 +242,20 @@ async function MakeFortellisCall({
originalError: error
};
// CreateFortellisLogEvent(socket, "ERROR", `Error in MakeFortellisCall for ${apiName}: ${error.message}`, {
// ...errorDetails,
// errorStack: error.stack
// });
logger.log(
"fortellis-log-event-error",
"ERROR",
socket?.user?.email,
socket?.recordid,
{
wsmessage: "",//message,
curl: error.config.curl.curlCommand,
reqid: error.request.headers["Request-Id"] || null,
subscriptionId: error.request.headers["Subscription-Id"] || null,
},
true
);
// Throw custom error with all the details
throw new FortellisApiError(`Fortellis API call failed for ${apiName}: ${error.message}`, errorDetails);
}
}
@@ -275,7 +299,15 @@ function sleep(ms) {
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/"
@@ -283,54 +315,61 @@ const FortellisActions = {
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",
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",
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",
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",
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",
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",
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",
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",
apiName: "CDK Drive Post Service Vehicle"
},
GetCOA: {
type: "get",
@@ -343,37 +382,43 @@ const FortellisActions = {
? "https://api.fortellis.io/cdk/drive/glpost/startWIP"
: "https://api.fortellis.io/cdk-test/drive/glpost/startWIP",
type: "post",
apiName: "CDK Drive Post Accounting GL",
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 Accounting GL",
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 Accounting GL",
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",
? "https://api.fortellis.io/cdk/drive/glpost/errWIP/"
: "https://api.fortellis.io/cdk-test/drive/glpost/errWIP/",
type: "get",
apiName: "CDK Drive Post Accounting GL",
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",
},
apiName: "CDK Drive Post Service Vehicle History"
}
};
const FortellisCacheEnums = {
@@ -391,7 +436,7 @@ const FortellisCacheEnums = {
DMSTransHeader: "DMSTransHeader",
transWips: "transWips",
DmsBatchTxnPost: "DmsBatchTxnPost",
DMSVehHistory: "DMSVehHistory",
DMSVehHistory: "DMSVehHistory"
};
function constructFullUrl({ url, pathParams = "", requestSearchParams = [] }) {
@@ -403,8 +448,6 @@ function constructFullUrl({ url, pathParams = "", requestSearchParams = [] }) {
return fullUrl;
}
module.exports = {
GetAuthToken,
FortellisCacheEnums,
@@ -412,5 +455,6 @@ module.exports = {
FortellisActions,
getTransactionType,
defaultFortellisTTL,
FortellisApiError
FortellisApiError,
GetDepartmentId
};

View File

@@ -1,7 +1,6 @@
const logger = require("../utils/logger");
const CreateFortellisLogEvent = (socket, level, message, txnDetails) => {
//TODO: Add detaisl to track the whole transaction between Fortellis and the server.
logger.log("fortellis-log-event", level, socket?.user?.email, null, { wsmessage: message, txnDetails });
socket.emit("fortellis-log-event", { level, message, txnDetails });
};

View File

@@ -75,7 +75,6 @@ async function FortellisJobExport({ socket, redisHelpers, txEnvelope, jobid }) {
defaultFortellisTTL
);
//TODO: Need to remove unnecessary stuff here to reduce the payload.
const JobData = await QueryJobData({ socket, jobid });
await setSessionTransactionData(
@@ -252,83 +251,70 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
CreateFortellisLogEvent(socket, "DEBUG", `{5.1} Creating Transaction with ID ${DMSTransHeader.transID}`);
const DMSBatchTxn = await InsertDmsBatchWip({ socket, redisHelpers, JobData });
await setSessionTransactionData(
socket.id,
getTransactionType(jobid),
FortellisCacheEnums.DMSBatchTxn,
DMSBatchTxn,
defaultFortellisTTL
);
if (DMSTransHeader.rtnCode === "0") {
try {
CreateFortellisLogEvent(socket, "DEBUG", `{6} Attempting to post Transaction with ID ${DMSTransHeader.transID}`);
if (DMSBatchTxn.rtnCode === "0") {
CreateFortellisLogEvent(socket, "DEBUG", `{6} Attempting to post Transaction with ID ${DMSTransHeader.transID}`);
const DmsBatchTxnPost = await PostDmsBatchWip({ socket, redisHelpers, JobData });
await setSessionTransactionData(
socket.id,
getTransactionType(jobid),
FortellisCacheEnums.DmsBatchTxnPost,
DmsBatchTxnPost,
defaultFortellisTTL
);
if (DmsBatchTxnPost.rtnCode === "0") {
//TODO: Validate this is a string and not #
//something
CreateFortellisLogEvent(socket, "DEBUG", `{6} Successfully posted transaction to DMS.`);
await MarkJobExported({ socket, jobid: JobData.id });
CreateFortellisLogEvent(socket, "DEBUG", `{5} Updating Service Vehicle History.`);
const DMSVehHistory = await InsertServiceVehicleHistory({ socket, redisHelpers, JobData });
const DmsBatchTxnPost = await PostDmsBatchWip({ socket, redisHelpers, JobData }); // 2 in 1 call that includes a post and the transactions.
await setSessionTransactionData(
socket.id,
getTransactionType(jobid),
FortellisCacheEnums.DMSVehHistory,
DMSVehHistory,
FortellisCacheEnums.DmsBatchTxnPost,
DmsBatchTxnPost,
defaultFortellisTTL
);
socket.emit("export-success", JobData.id);
} else {
//Get the error code
if (DmsBatchTxnPost.rtnCode === "0") {
//TODO: Validate this is a string and not #
//something
CreateFortellisLogEvent(socket, "DEBUG", `{6} Successfully posted transaction to DMS.`);
await MarkJobExported({ socket, jobid: JobData.id, JobData });
CreateFortellisLogEvent(socket, "DEBUG", `{5} Updating Service Vehicle History.`);
const DMSVehHistory = await InsertServiceVehicleHistory({ socket, redisHelpers, JobData });
await setSessionTransactionData(
socket.id,
getTransactionType(jobid),
FortellisCacheEnums.DMSVehHistory,
DMSVehHistory,
defaultFortellisTTL
);
socket.emit("export-success", JobData.id);
} else {
//There was something wrong. Throw an error to trigger clean up.
throw new Error("Error posting DMS Batch Transaction");
}
} catch (error) {
//Clean up the transaction and insert a faild error code
// //Get the error code
CreateFortellisLogEvent(socket, "DEBUG", `{6.1} Getting errors for Transaction ID ${DMSTransHeader.transID}`);
await QueryDmsErrWip({ socket, redisHelpers, JobData });
const DmsError = await QueryDmsErrWip({ socket, redisHelpers, JobData });
// //Delete the transaction
CreateFortellisLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${DMSTransHeader.transID}`);
//Delete the transaction
CreateFortellisLogEvent(socket, "DEBUG", `{{ 6.2 } Deleting Transaction ID ${socket.DMSTransHeader.transID}`);
// Delete DMS Wip
await DeleteDmsWip({ socket, redisHelpers, JobData });
DmsError.errMsg
.split("|")
.map(
(e) =>
e !== null &&
e !== "" &&
CreateFortellisLogEvent(socket, "ERROR", `Error(s) encountered in posting transaction.${e} `)
);
DmsError.errLine.map(
(e) =>
e !== null &&
e !== "" &&
CreateFortellisLogEvent(socket, "ERROR", `Error encountered in posting transaction => ${e} `)
);
await InsertFailedExportLog({
socket,
JobData,
error: DmsError.errLine
});
}
} else {
//Posting transaction failed.
CreateFortellisLogEvent(
socket,
"ERROR",
`DMS Batch Return code was not successful: ${DMSBatchTxn.rtnCode} - ${DMSBatchTxn.sendline}`
);
await InsertFailedExportLog({
socket,
JobData,
error: `DMS Batch Return code was not successful: ${DMSBatchTxn.rtnCode} - ${DMSBatchTxn.sendline}`
});
}
} catch (error) {
// CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer.${ error } `);
CreateFortellisLogEvent(socket, "ERROR", `Error in FortellisSelectedCustomer - ${error} `, {
error: error.message,
stack: error.stack,
@@ -369,7 +355,7 @@ async function CalculateDmsVid({ socket, JobData, redisHelpers }) {
vin: JobData.v_vin,
jobId: JobData.id
});
throw error; // Re-throw to maintain existing error handling flow
throw error;
}
}
@@ -420,12 +406,9 @@ async function QueryDmsCustomerByName({ socket, redisHelpers, JobData }) {
JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
? [["lastName", JobData.ownr_co_nm.replace(replaceSpecialRegex, "")]]
: [
["firstName", JobData.ownr_fn.replace(replaceSpecialRegex, "")],
["lastName", JobData.ownr_ln.replace(replaceSpecialRegex, "")]
];
CreateFortellisLogEvent(socket, "DEBUG", `Begin query DMS Customer by Name using ${JSON.stringify(ownerName)} `);
["firstName", JobData.ownr_fn.replace(replaceSpecialRegex, "")],
["lastName", JobData.ownr_ln.replace(replaceSpecialRegex, "")]
];
try {
const result = await MakeFortellisCall({
...FortellisActions.QueryCustomerByName,
@@ -526,18 +509,18 @@ async function InsertDmsCustomer({ socket, redisHelpers, JobData }) {
emailAddresses: [
...(!_.isEmpty(JobData.ownr_ea)
? [
{
//"uuid": "",
address: JobData.ownr_ea,
type: "PERSONAL"
// "doNotEmailSource": "",
// "doNotEmail": false,
// "isPreferred": true,
// "transactionEmailNotificationOptIn": false,
// "optInRequestDate": null,
// "optInDate": null
}
]
{
//"uuid": "",
address: JobData.ownr_ea,
type: "PERSONAL"
// "doNotEmailSource": "",
// "doNotEmail": false,
// "isPreferred": true,
// "transactionEmailNotificationOptIn": false,
// "optInRequestDate": null,
// "optInDate": null
}
]
: [])
// {
// "uuid": "",
@@ -660,7 +643,7 @@ async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMS
jobid: JobData.id,
body: {
dealer: {
company: JobData.bodyshop.cdk_configuration.srcco || "77",
//company: JobData.bodyshop.cdk_configuration.srcco || "77",
// "dealNumber": "",
// "dealerAssignedNumber": "82268",
// "dealerDefined1": "2WDSP",
@@ -677,9 +660,9 @@ async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMS
txEnvelope.dms_unsold === true
? ""
: moment(txEnvelope.inservicedate)
//.tz(JobData.bodyshop.timezone)
.startOf("day")
.toISOString()
//.tz(JobData.bodyshop.timezone)
.startOf("day")
.toISOString()
}),
//"lastServiceDate": "2011-11-23",
vehicleId: DMSVid.vehiclesVehId
@@ -721,8 +704,8 @@ async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMS
txEnvelope.dms_unsold === true
? ""
: moment()
// .tz(JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
// .tz(JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
// "deliveryMileage": 4,
// "doorsQuantity": 4,
// "engineNumber": "",
@@ -739,8 +722,8 @@ async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMS
: String(JobData.plate_no).replace(/([^\w]|_)/g, "").length === 0
? null
: String(JobData.plate_no)
.replace(/([^\w]|_)/g, "")
.toUpperCase(),
.replace(/([^\w]|_)/g, "")
.toUpperCase(),
make: txEnvelope.dms_make,
// "model": "CC10753",
modelAbrev: txEnvelope.dms_model,
@@ -886,13 +869,13 @@ async function UpdateDmsVehicle({ socket, redisHelpers, JobData, DMSVeh, DMSCust
},
...(oldOwner
? [
{
id: {
assigningPartyId: "PREVIOUS",
value: oldOwner.id
}
{
id: {
assigningPartyId: "PREVIOUS",
value: oldOwner.id
}
]
}
]
: [])
];
}
@@ -916,30 +899,30 @@ async function UpdateDmsVehicle({ socket, redisHelpers, JobData, DMSVeh, DMSCust
...DMSVehToSend,
dealer: {
...DMSVehToSend.dealer, //TODO: Check why company is blank on a queried record.
//company: "77",
...((txEnvelope.inservicedate || DMSVehToSend.dealer.inServiceDate) && {
inServiceDate:
txEnvelope.dms_unsold === true
? ""
: moment(DMSVehToSend.dealer.inServiceDate || txEnvelope.inservicedate)
// .tz(JobData.bodyshop.timezone)
.toISOString()
// .tz(JobData.bodyshop.timezone)
.toISOString()
})
},
vehicle: {
...DMSVehToSend.vehicle,
...(txEnvelope.dms_model_override
? {
make: txEnvelope.dms_make,
modelAbrev: txEnvelope.dms_model
}
make: txEnvelope.dms_make,
modelAbrev: txEnvelope.dms_model
}
: {}),
deliveryDate:
txEnvelope.dms_unsold === true
? ""
: moment(DMSVehToSend.vehicle.deliveryDate)
//.tz(JobData.bodyshop.timezone)
.toISOString()
//.tz(JobData.bodyshop.timezone)
.toISOString()
},
owners: ids
}
@@ -1027,9 +1010,24 @@ async function InsertDmsStartWip({ socket, redisHelpers, JobData }) {
srcCo: JobData.bodyshop.cdk_configuration.srcco,
srcJrnl: txEnvelope.journal,
transID: "",
userID: "csr" || JobData.bodyshop.cdk_configuration.cashierid,
userName: "BSMS"
}
userID: JobData.bodyshop.cdk_configuration.cashierid,
userName: "IMEX"
// acctgDate: "2025-07-07",
// desc: "DOCUMENT DESC. OPTIONAL REQUIREMENT",
// docType: "3",
// m13Flag: "0",
// refer: "707MISC01",
// rtnCode: "",
// sendline: "",
// groupName: "",
// srcCo: "77",
// srcJrnl: "80",
// transID: "",
// userID: "partprgm",
// userName: "PROGRAM, PARTNER"
},
//overrideDepartmentId: "D100152198" //TODO: REMOVE AFTER TESTING
});
return result;
} catch (error) {
@@ -1041,26 +1039,6 @@ async function InsertDmsStartWip({ socket, redisHelpers, JobData }) {
}
}
async function InsertDmsBatchWip({ socket, redisHelpers, JobData }) {
try {
const result = await MakeFortellisCall({
...FortellisActions.TranBatchWip,
headers: {},
redisHelpers,
socket,
jobid: JobData.id,
body: await GenerateTransWips({ socket, redisHelpers, JobData })
});
return result;
} catch (error) {
handleFortellisApiError(socket, error, "InsertDmsBatchWip", {
jobId: JobData.id,
errorStack: error.stack
});
throw error;
}
}
async function GenerateTransWips({ socket, redisHelpers, JobData }) {
//3rd prop sets fortellis to true to maintain logging.
const allocations = await CalculateAllocations(socket, JobData.id, true);
@@ -1079,9 +1057,9 @@ async function GenerateTransWips({ socket, redisHelpers, JobData }) {
acct: alloc.profitCenter.dms_acctnumber,
cntl:
alloc.profitCenter.dms_control_override &&
alloc.profitCenter.dms_control_override !== null &&
alloc.profitCenter.dms_control_override !== undefined &&
alloc.profitCenter.dms_control_override?.trim() !== ""
alloc.profitCenter.dms_control_override !== null &&
alloc.profitCenter.dms_control_override !== undefined &&
alloc.profitCenter.dms_control_override?.trim() !== ""
? alloc.profitCenter.dms_control_override
: JobData.ro_number,
cntl2: null,
@@ -1102,9 +1080,9 @@ async function GenerateTransWips({ socket, redisHelpers, JobData }) {
acct: alloc.costCenter.dms_acctnumber,
cntl:
alloc.costCenter.dms_control_override &&
alloc.costCenter.dms_control_override !== null &&
alloc.costCenter.dms_control_override !== undefined &&
alloc.costCenter.dms_control_override?.trim() !== ""
alloc.costCenter.dms_control_override !== null &&
alloc.costCenter.dms_control_override !== undefined &&
alloc.costCenter.dms_control_override?.trim() !== ""
? alloc.costCenter.dms_control_override
: JobData.ro_number,
cntl2: null,
@@ -1122,9 +1100,9 @@ async function GenerateTransWips({ socket, redisHelpers, JobData }) {
acct: alloc.costCenter.dms_wip_acctnumber,
cntl:
alloc.costCenter.dms_control_override &&
alloc.costCenter.dms_control_override !== null &&
alloc.costCenter.dms_control_override !== undefined &&
alloc.costCenter.dms_control_override?.trim() !== ""
alloc.costCenter.dms_control_override !== null &&
alloc.costCenter.dms_control_override !== undefined &&
alloc.costCenter.dms_control_override?.trim() !== ""
? alloc.costCenter.dms_control_override
: JobData.ro_number,
cntl2: null,
@@ -1146,9 +1124,9 @@ async function GenerateTransWips({ socket, redisHelpers, JobData }) {
acct: alloc.profitCenter.dms_acctnumber,
cntl:
alloc.profitCenter.dms_control_override &&
alloc.profitCenter.dms_control_override !== null &&
alloc.profitCenter.dms_control_override !== undefined &&
alloc.profitCenter.dms_control_override?.trim() !== ""
alloc.profitCenter.dms_control_override !== null &&
alloc.profitCenter.dms_control_override !== undefined &&
alloc.profitCenter.dms_control_override?.trim() !== ""
? alloc.profitCenter.dms_control_override
: JobData.ro_number,
cntl2: null,
@@ -1214,8 +1192,9 @@ async function PostDmsBatchWip({ socket, redisHelpers, JobData }) {
jobid: JobData.id,
body: {
opCode: "P",
transID: DMSTransHeader.transID
}
transID: DMSTransHeader.transID,
transWipReqList: await GenerateTransWips({ socket, redisHelpers, JobData })
},
});
return result;
} catch (error) {
@@ -1243,7 +1222,7 @@ async function QueryDmsErrWip({ socket, redisHelpers, JobData }) {
socket,
jobid: JobData.id,
requestPathParams: DMSTransHeader.transID,
body: {}
body: {},
});
return result;
} catch (error) {
@@ -1265,7 +1244,7 @@ async function DeleteDmsWip({ socket, redisHelpers, JobData }) {
);
const result = await MakeFortellisCall({
...FortellisActions.PostBatchWip,
...FortellisActions.DeleteTranWip,
headers: {},
redisHelpers,
socket,
@@ -1273,7 +1252,7 @@ async function DeleteDmsWip({ socket, redisHelpers, JobData }) {
body: {
opCode: "D",
transID: DMSTransHeader.transID
}
},
});
return result;
} catch (error) {
@@ -1285,7 +1264,7 @@ async function DeleteDmsWip({ socket, redisHelpers, JobData }) {
}
}
async function MarkJobExported({ socket, jobid }) {
async function MarkJobExported({ socket, jobid, JobData }) {
CreateFortellisLogEvent(socket, "ERROR", `Marking job as exported for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
@@ -1297,11 +1276,11 @@ async function MarkJobExported({ socket, jobid }) {
.request(queries.MARK_JOB_EXPORTED, {
jobId: jobid,
job: {
status: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
status: JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
date_exported: new Date()
},
log: {
bodyshopid: socket.JobData.bodyshop.id,
bodyshopid: JobData.bodyshop.id,
jobid: jobid,
successful: true,
useremail: socket.user.email,
@@ -1325,13 +1304,13 @@ async function InsertFailedExportLog({ socket, JobData, error }) {
const result = await client
.setHeaders({ Authorization: `Bearer ${currentToken}` })
.request(queries.INSERT_EXPORT_LOG, {
log: {
logs: [{
bodyshopid: JobData.bodyshop.id,
jobid: JobData.id,
successful: false,
message: JSON.stringify(error),
useremail: socket.user.email
}
}]
});
return result;

View File

@@ -2178,19 +2178,7 @@ mutation UPDATE_BILLS($billids: [uuid!]!, $bill: bills_set_input!, $logs: [expor
}
}`;
exports.INSERT_EXPORT_LOG = `
mutation INSERT_EXPORT_LOG($log: exportlog_insert_input!) {
insert_exportlog_one(object: $log) {
id
}
}`;
exports.QUERY_EXISTING_TRANSITION = `
mutation INSERT_EXPORT_LOG($log: exportlog_insert_input!) {
insert_exportlog_one(object: $log) {
id
}
}`;
exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $existingTransition: transitions_set_input!){
update_transitions(where:{jobid:{_eq:$jobid}, end:{_is_null:true

View File

@@ -8,6 +8,7 @@ const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLCl
router.use(validateFirebaseIdTokenMiddleware);
router.post("/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.default);
router.post("/fortellis/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.fortellis);
router.post("/calculate-allocations", withUserGraphQLClientMiddleware, cdkCalculateAllocations.defaultRoute);
module.exports = router;

View File

@@ -292,7 +292,7 @@ const redisSocketEvents = ({ io, redisHelpers, ioHelpers, logger }) => {
});
} catch (error) {
FortellisLogger(socket, "error", `Error during Fortellis export : ${error.message}`);
logger.log("fortellis-job-export-error", "error", null, null, {
logger.log("fortellis-job-export-error", "error", socket.user?.email, jobid, {
message: error.message,
stack: error.stack
});
@@ -320,7 +320,7 @@ const redisSocketEvents = ({ io, redisHelpers, ioHelpers, logger }) => {
});
} catch (error) {
FortellisLogger(socket, "error", `Error during Fortellis export : ${error.message}`);
logger.log("fortellis-selectd-customer-error", "error", null, null, {
logger.log("fortellis-selectd-customer-error", "error", socket.user?.email, jobid, {
message: error.message,
stack: error.stack
});
@@ -333,7 +333,7 @@ const redisSocketEvents = ({ io, redisHelpers, ioHelpers, logger }) => {
callback(allocations);
} catch (error) {
FortellisLogger(socket, "error", `Error during Fortellis export : ${error.message}`);
logger.log("fortellis-selectd-customer-error", "error", null, null, {
logger.log("fortellis-selectd-customer-error", "error", socket.user?.email, jobid, {
message: error.message,
stack: error.stack
});