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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
Fortellis Feedback
Create Customer
https://apidocs.fortellis.io/apis/c5cfb5b3-2013-4870-8645-0379c01ae56b
Request Body compoennts do not show on website. Unable to determine which components are required.

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -18,15 +19,26 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakesRefetch);
export function DmsCdkMakesRefetch({ currentUser, bodyshop }) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const {
treatments: { Fortellis }
} = useSplitTreatments({
attributes: {},
names: ["Fortellis"],
splitKey: bodyshop.imexshopid
});
if (!currentUser.email.includes("@imex.")) return null;
const handleRefetch = async () => {
setLoading(true);
await axios.post("/cdk/getvehicles", {
cdk_dealerid: bodyshop.cdk_dealerid,
bodyshopid: bodyshop.id
});
try {
setLoading(true);
await axios.post(`cdk${Fortellis.treatment === "on" ? "/fortellis" : ""}/getvehicles`, {
cdk_dealerid: bodyshop.cdk_dealerid,
bodyshopid: bodyshop.id
});
} catch (error) {
console.error(error);
}
setLoading(false);
};

View File

@@ -162,12 +162,12 @@ export default function CdkLikePostForm({ bodyshop, socket, job, logsRef, mode }
<Row gutter={[16, 12]}>
<Col xs={24} sm={12} md={8}>
<Form.Item name="dms_make" label={t("jobs.fields.dms.dms_make")} rules={[{ required: true }]}>
<Input />
<Input disabled />
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8}>
<Form.Item name="dms_model" label={t("jobs.fields.dms.dms_model")} rules={[{ required: true }]}>
<Input />
<Input disabled />
</Form.Item>
</Col>
<Col xs={24} sm={12} md={8}>

View File

@@ -424,10 +424,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
setLogs([]);
if (isWssMode(mode)) {
setActiveLogLevel(logLevel);
} else {
activeSocket.disconnect();
activeSocket.connect();
}
activeSocket.disconnect();
activeSocket.connect();
}}
>
Reconnect

View File

@@ -1,305 +0,0 @@
const path = require('path');
const Dinero = require('dinero.js');
const { gql } = require('graphql-request');
const queries = require('./server/graphql-client/queries');
const GraphQLClient = require('graphql-request').GraphQLClient;
const logger = require('./server/utils/logger');
const AxiosLib = require('axios').default;
const axios = AxiosLib.create();
const uuid = require('uuid').v4;
const FORTELLIS_KEY = 'X1FxzLyOk3kjHvMbzdPQXFZShkdbgzuo';
const FORTELLIS_SECRET = '7Yvs0wpQeHcUS5r95ht8pqOaAvBq7dHV';
const FORTELLIS_AUTH_URL = 'https://identity.fortellis.io/oauth2/aus1p1ixy7YL8cMq02p7/v1/token';
const FORTELLIS_URL = 'https://api.fortellis.io';
const SubscriptionID = '5b527d7d-baf3-40bc-adae-e7a541e37363';
let SubscriptionMeta = null;
//const SubscriptionID = "cb59fa04-e53e-4b57-b071-80a48ebc346c";
function sleep(time, callback) {
var stop = new Date().getTime();
while (new Date().getTime() < stop + time) {}
callback();
}
async function GetAuthToken() {
const {
data: { access_token, expires_in, token_type },
} = await axios.post(
FORTELLIS_AUTH_URL,
{},
{
auth: {
username: FORTELLIS_KEY,
password: FORTELLIS_SECRET,
},
params: {
grant_type: 'client_credentials',
scope: 'anonymous',
},
},
);
return access_token;
}
async function FetchSubscriptions() {
const access_token = await GetAuthToken();
try {
const subscriptions = await axios.get(
`https://subscriptions.fortellis.io/v1/solution/subscriptions`,
{
headers: { Authorization: `Bearer ${access_token}` },
},
);
return subscriptions.data.subscriptions;
} catch (error) {
console.log('🚀 ~ FetchSubscriptions ~ error:', error);
}
}
async function GetBulkVendors() {
const departmentIds = (await FetchSubscriptions())
.find((s) => s.subscriptionId === SubscriptionID) //Get the subscription object.
?.apiDmsInfo.find((info) => info.name === 'CDK Drive Async Vendors')?.departments; //Departments are categorized by API name and have an array of departments.
const access_token = await GetAuthToken();
const ReqId = uuid();
try {
//TODO: This is pointing towards the test environment. Need to carefully watch for the production switch.
const Vendors = await axios.get(`https://api.fortellis.io/cdk-test/drive/vendor/v2/bulk`, {
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': departmentIds[0].id,
},
});
//Returns a long poll. Need to wait specified seconds until checking.
console.log(
'🚀 ~ GetBulkVendors ~ Vendors - waiting to execute callback:',
Vendors.data.checkStatusAfterSeconds,
);
sleep(Vendors.data.checkStatusAfterSeconds * 1000, async () => {
const VendorsResult = await axios.get(Vendors.data._links.status.href, {
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': departmentIds[0].id,
},
});
//This may have to check again if it isn't ready.
const VendorsResult2 = await axios.get(VendorsResult.data._links.result.href, {
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': departmentIds[0].id,
},
});
console.log('🚀 ~ sleep ~ VendorsResult2:', VendorsResult2);
});
console.log('🚀 ~ GetBulkVendors ~ Vendors:', ReqId, Vendors.data);
} catch (error) {
console.log('🚀 ~ GetBulkVendors ~ error:', ReqId, error);
}
}
async function FetchVehicles({ SubscriptionID }) {
const access_token = await GetAuthToken();
try {
//This doesn't seem to work as it is for production only.
const Vehicles = await axios.get(`https://api.fortellis.io/cdkdrive/service/v1/vehicles/`, {
headers: { Authorization: `Bearer ${access_token}`, 'Subscription-Id': SubscriptionID },
});
console.log('🚀 ~ FetchVehicles ~ Vehicles:', Vehicles);
return Vehicles.data;
} catch (error) {
console.log('🚀 ~ FetchVehicles ~ error:', error);
}
}
async function PostVehicleServiceHistory() {
const access_token = await GetAuthToken();
const ReqId = uuid();
const departmentIds = (await FetchSubscriptions())
.find((s) => s.subscriptionId === SubscriptionID) //Get the subscription object.
?.apiDmsInfo.find((info) => info.name === 'CDK Drive Async Vendors')?.departments; //Departments are categorized by API name and have an array of departments.
//Need to get a vehicle ID from somewhere.
const vehicles = await FetchVehicles({ SubscriptionID });
try {
//TODO: This is pointing towards the test environment. Need to carefully watch for the production switch.
const Vendors = await axios.post(
`https://api.fortellis.io/cdk-test/drive/post/service-vehicle-history-mgmt/v2/
`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': departmentIds[0].id,
},
},
);
} catch (error) {
console.log('🚀 ~ PostVehicleServiceHistory ~ error:', ReqId, error);
}
}
//PostVehicleServiceHistory();
//GetBulkVendors();
async function GetDepartmentId() {
const departmentIds = await FetchSubscriptions();
console.log('🚀 ~ GetDepartmentId ~ departmentIds:', departmentIds);
const departmentIds2 = departmentIds
.find((s) => s.subscriptionId === SubscriptionID) //Get the subscription object.
?.apiDmsInfo.find((info) => info.name === 'CDK Drive Async Vendors')?.departments; //Departments are categorized by API name and have an array of departments.
return departmentIds[0].id;
}
//////////////////GL WIP Section //////////////////////
async function OrgHelpers() {
console.log('Executing Org Helpers');
const ReqId = uuid();
const access_token = await GetAuthToken();
const DepartmentId = await GetDepartmentId();
try {
//This doesn't seem to work as it is for production only.
const OrgHelpers = await axios.get(
`https://api.fortellis.io/cdk-test/drive/businessofficeglwippost/orgHelper`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': DepartmentId,
},
},
);
console.log('🚀 ~ OrgHelpers ~ Data:', OrgHelpers);
return OrgHelpers.data;
} catch (error) {
console.log('🚀 ~ OrgHelpers ~ error:', error);
}
}
async function JournalHelpers({ glCompanyNumber }) {
console.log('Executing Journal Helpers');
const ReqId = uuid();
const access_token = await GetAuthToken();
const DepartmentId = await GetDepartmentId();
try {
//This doesn't seem to work as it is for production only.
const JournalHelpers = await axios.get(
`https://api.fortellis.io/cdk-test/drive/businessofficeglwippost/jrnlHelper/${glCompanyNumber}`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': DepartmentId,
},
},
);
console.log('🚀 ~ JournalHelpers ~ Data:', JournalHelpers);
return JournalHelpers.data;
} catch (error) {
console.log('🚀 ~ JournalHelpers ~ error:', error);
}
}
async function GlSalesChain() {
console.log('Executing GL Sales Chain');
const ReqId = uuid();
const access_token = await GetAuthToken();
const DepartmentId = await GetDepartmentId();
try {
//This doesn't seem to work as it is for production only.
const GlSalesChain = await axios.get(
`https://api.fortellis.io/cdk-test/drive/businessofficeglwippost/glSalesChain`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': DepartmentId,
},
},
);
console.log('🚀 ~ GlSalesChain ~ Data:', GlSalesChain);
return GlSalesChain.data;
} catch (error) {
console.log('🚀 ~ GlSalesChain ~ error:', error);
}
}
async function GlExpenseAllocation() {
console.log('Executing GL Expense Allocation');
const ReqId = uuid();
const access_token = await GetAuthToken();
const DepartmentId = await GetDepartmentId();
try {
//This doesn't seem to work as it is for production only.
const GlExpenseAllocation = await axios.get(
`https://api.fortellis.io/cdk-test/drive/businessofficeglwippost/glExpenseAllocation`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': DepartmentId,
},
},
);
console.log('🚀 ~ GlExpenseAllocation ~ Data:', GlExpenseAllocation);
return GlExpenseAllocation.data;
} catch (error) {
console.log('🚀 ~ GlSalesChain ~ error:', error);
}
}
///EXEC FUNCTIONS
async function PostAccountsGLWIP() {
//const orgHelpers = await OrgHelpers();
//const jrnlHelpers = await JournalHelpers({ glCompanyNumber: orgHelpers[0].coID });
//const glSalesChain = await GlSalesChain();
const glExpenseAllocation = await GlExpenseAllocation();
}
//PostAccountsGLWIP();
async function GetCOA() {
console.log('Executing GetCOA');
const ReqId = uuid();
const access_token = await GetAuthToken();
const DepartmentId = await GetDepartmentId();
try {
//This doesn't seem to work as it is for production only.
const GetCOA = await axios.get(
`https://api.fortellis.io/cdk-test/drive/chartofaccounts/v2/bulk`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Subscription-Id': SubscriptionID,
'Request-Id': ReqId,
'Department-Id': DepartmentId,
},
},
);
console.log('🚀 ~ GetCOA ~ Data:', GetCOA);
return GetCOA.data;
} catch (error) {
console.log('🚀 ~ GetCOA ~ error:', error);
}
}
GetCOA();

View File

@@ -1,235 +0,0 @@
const path = require("path");
const AxiosLib = require("axios").default;
const axios = AxiosLib.create();
const uuid = require("uuid").v4;
const FORTELLIS_KEY = "X1FxzLyOk3kjHvMbzdPQXFZShkdbgzuo"; //TODO: Regenerate these keys after testing and move to env vars.
const FORTELLIS_SECRET = "7Yvs0wpQeHcUS5r95ht8pqOaAvBq7dHV";
const FORTELLIS_AUTH_URL = "https://identity.fortellis.io/oauth2/aus1p1ixy7YL8cMq02p7/v1/token";
const FORTELLIS_URL = "https://api.fortellis.io";
const ENVSubscriptionID = "5b527d7d-baf3-40bc-adae-e7a541e37363"; //TODO: Replace with the bodyshop.cdk_dealerid
let SubscriptionMeta = null;
//const ENVSubscriptionID = 'cb59fa04-e53e-4b57-b071-80a48ebc346c';
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(
FORTELLIS_AUTH_URL,
{},
{
auth: {
username: FORTELLIS_KEY,
password: FORTELLIS_SECRET
},
params: {
grant_type: "client_credentials",
scope: "anonymous"
}
}
);
return access_token;
}
async function FetchSubscriptions() {
const access_token = await GetAuthToken();
try {
const subscriptions = await axios.get(`https://subscriptions.fortellis.io/v1/solution/subscriptions`, {
headers: { Authorization: `Bearer ${access_token}` }
});
SubscriptionMeta = subscriptions.data.subscriptions.find((s) => s.subscriptionId === ENVSubscriptionID);
return SubscriptionMeta;
} catch (error) {
console.log("🚀 ~ FetchSubscriptions ~ error:", error);
}
}
async function GetDepartmentId({ apiName, debug = false }) {
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.map((a) => ({
name: a.name,
departments: a.departments.map((d) => d.id)
})),
null,
4
)
);
console.log("===========");
}
const departmentIds2 = 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[0].id; //TODO: This makes the assumption that there is only 1 department.
}
async function MakeFortellisCall({ apiName, url, headers = {}, body = {}, type = "post", debug = false }) {
if (debug) console.log(`Executing ${type} to ${url}`);
const ReqId = uuid();
const access_token = await GetAuthToken();
const DepartmentId = await GetDepartmentId({ apiName, debug });
if (debug) {
console.log(
`ReqID: ${ReqId} | SubscriptionID: ${SubscriptionMeta.subscriptionId} | DepartmentId: ${DepartmentId}`
);
console.log(`Body Contents: ${JSON.stringify(body, null, 4)}`);
}
try {
let result;
switch (type) {
case "post":
default:
result = await axios.post(url, body, {
headers: {
Authorization: `Bearer ${access_token}`,
"Subscription-Id": SubscriptionMeta.subscriptionId,
"Request-Id": ReqId,
"Department-Id": DepartmentId,
...headers
}
});
break;
case "get":
result = await axios.get(url, {
headers: {
Authorization: `Bearer ${access_token}`,
"Subscription-Id": SubscriptionMeta.subscriptionId,
"Request-Id": ReqId,
"Department-Id": DepartmentId,
...headers
}
});
break;
}
if (debug) {
console.log(`ReqID: ${ReqId} Data`);
console.log(JSON.stringify(result.data, null, 4));
}
if (result.data.checkStatusAfterSeconds) {
return DelayedCallback({
delayMeta: result.data,
access_token,
SubscriptionID: SubscriptionMeta.subscriptionId,
ReqId,
departmentIds: DepartmentId
});
}
return result.data;
} catch (error) {
console.log(`ReqID: ${ReqId} Error`, error.response?.data);
//console.log(`ReqID: ${ReqId} Full Error`, JSON.stringify(error, null, 4));
}
}
//Get the status meta, then keep checking and return the result.
async function DelayedCallback({ delayMeta, access_token, SubscriptionID, ReqId, departmentIds }) {
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
}
});
return batchResult;
} else {
return "Error!!! Still need to implement batch waiting.";
}
}
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function GetCOA() {
console.log("Executing GetCOA");
await MakeFortellisCall({
debug: true,
type: "get",
apiName: "CDK Drive Post Accounts GL WIP",
url: `https://api.fortellis.io/cdk-test/drive/chartofaccounts/v2/bulk`,
waitForResult: true
});
}
async function StartWIP() {
const TransactionWip = MakeFortellisCall({
url: "https://api.fortellis.io/cdk-test/drive/glwippost/startWIP",
apiName: "CDK Drive Post Accounts GL WIP",
body: {
acctgDate: "2023-09-26", //job.invoice
desc: "TEST TRANSACTION",
docType: "3", //pulled from Doc Type workbook
m13Flag: "0", // Is this a M13 entry. Presumanbly always 0
refer: "RO12345", //Supposed to be a doc reference number. Presumably the RO?
srcCo: "77",
srcJrnl: "80",
userID: "csr", //bodyshop user
userName: "PROGRAM, PARTNER*ADP" //Can leave blank to have this return to default.
},
debug: true
});
return TransactionWip;
}
async function InsertBatch({ transID }) {
const TransactionWip = MakeFortellisCall({
url: "https://api.fortellis.io/cdk-test/drive/glwippost/transWIP",
apiName: "CDK Drive Post Accounts GL WIP",
body: [
{
acct: "",
cntl: "",
cntl2: null,
credtMemoNo: null,
postAmt: Math.round(payer.amount * 100),
postDesc: "", //Required if required by the DMS setup
prod: null, //Productivity Number
statCnt: 1, //Auto count, leave as 1.
transID: transID,
trgtCoID: "77" //Add this to read from the header
}
],
debug: true
});
}
async function DoTheThings() {
await FetchSubscriptions();
//What do we have access to?
console.log("Sub Access : ", SubscriptionMeta.apiDmsInfo.map((i) => i.name).join(", "));
await GetCOA();
return;
//Insert Transactions
const TransactionHeader = await StartWIP();
const BatchResult = await InsertBatch({ transID: TransactionHeader.transID });
}
DoTheThings();

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