Merge branch 'feature/cdk' into feature/2021-08-13

This commit is contained in:
Patrick Fic
2021-08-10 13:58:58 -07:00
6 changed files with 620 additions and 50 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,77 @@
import { Button, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { socket } from "../../pages/dms/dms.container";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { alphaSort } from "../../utils/sorters";
export default function DmsCustomerSelector() {
const { t } = useTranslation();
const [customerList, setcustomerList] = useState([]);
const [visible, setVisible] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null);
socket.on("cdk-select-customer", (customerList, callback) => {
setVisible(true);
setcustomerList(customerList);
});
const onOk = () => {
setVisible(false);
socket.emit("cdk-selected-customer", selectedCustomer);
};
const columns = [
{
title: t("dms.fields.name1"),
dataIndex: ["name1", "fullName"],
key: "name1",
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
},
{
title: t("dms.fields.name2"),
dataIndex: ["name2", "fullName"],
key: "name2",
sorter: (a, b) => alphaSort(a.name2?.fullName, b.name2?.fullName),
},
{
title: t("dms.fields.phone"),
dataIndex: ["contactInfo", "mainTelephoneNumber", "value"],
key: "phone",
render: (record, value) => (
<PhoneFormatter>
{record.contactInfo?.mainTelephoneNumber?.value}
</PhoneFormatter>
),
},
{
title: t("dms.fields.address"),
//dataIndex: ["name2", "fullName"],
key: "address",
render: (record, value) =>
`${record.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
},
];
if (!visible) return <></>;
return (
<Table
title={() => (
<div>
<Button onClick={onOk}>Select</Button>
</div>
)}
pagination={{ position: "top" }}
columns={columns}
rowKey={(record) => record.id.value}
dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{
onSelect: (props) => {
setSelectedCustomer(props.id.value);
},
type: "radio",
selectedRowKeys: [selectedCustomer],
}}
/>
);
}

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { Button, Result } from "antd";
import { Button, Result, Select, Space } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -9,6 +9,7 @@ import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client";
import AlertComponent from "../../components/alert/alert.component";
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { auth } from "../../firebase/firebase.utils";
@@ -45,6 +46,7 @@ export const socket = SocketIO(
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const [logLevel, setLogLevel] = useState("DEBUG");
const [logs, setLogs] = useState([]);
const search = queryString.parse(useLocation().search);
const { jobId } = search;
@@ -69,6 +71,19 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
socket.on("connected", () => {
console.log("Connected again.");
});
socket.on("reconnect", () => {
console.log("Connected again.");
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service",
},
];
});
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
@@ -77,12 +92,13 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
});
socket.connect();
socket.emit("set-log-level", "TRACE");
socket.emit("set-log-level", logLevel);
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!jobId || !bodyshop.cdk_dealerid) return <Result status="404" />;
@@ -94,35 +110,42 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
return (
<div>
<Button
onClick={() => {
socket.emit(
`${dmsType}-export-job`,
"752a4f5f-22ab-414b-b182-98d4e62227ef"
);
}}
>
Export
</Button>
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
}}
>
reconnect
</Button>
<DmsAllocationsSummary
socket={socket}
job={data ? data.jobs_by_pk : null}
/>
<Space>
<Button
onClick={() => {
socket.emit(
`${dmsType}-export-job`,
"752a4f5f-22ab-414b-b182-98d4e62227ef"
);
}}
>
Export
</Button>
<Select
placeholder="Log Level"
value={logLevel}
onChange={(value) => {
setLogLevel(value);
socket.emit("set-log-level", value);
}}
>
<Select.Option>TRACE</Select.Option>
<Select.Option>DEBUG</Select.Option>
<Select.Option>INFO</Select.Option>
<Select.Option>WARNING</Select.Option>
<Select.Option>ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>
</Space>
<div>
<DmsAllocationsSummary
socket={socket}
job={data ? data.jobs_by_pk : null}
/>
<DmsLogEvents socket={socket} logs={logs} />
</div>
<DmsCustomerSelector />
</div>
);
}

View File

@@ -14,6 +14,10 @@ const logger = require("../utils/logger");
const IMEX_CDK_USER = process.env.IMEX_CDK_USER,
IMEX_CDK_PASSWORD = process.env.IMEX_CDK_PASSWORD;
const CDK_CREDENTIALS = {
password: IMEX_CDK_PASSWORD,
username: IMEX_CDK_USER,
};
exports.default = async function (socket, jobid) {
socket.logEvents = [];
@@ -24,27 +28,155 @@ exports.default = async function (socket, jobid) {
"DEBUG",
`Received Job export request for id ${jobid}`
);
//The following values will be stored on the socket to allow callbacks.
//let clVFV, clADPV, clADPC;
const JobData = await QueryJobData(socket, jobid);
const DealerId = JobData.bodyshop.cdk_dealerid;
CdkBase.createLogEvent(
socket,
"TRACE",
`Dealer ID detected: ${JSON.stringify(DealerId)}`
);
// Begin Calculate VID from DMS {1}
await DetermineDMSVid(socket, JobData);
//{1} Begin Calculate DMS Vehicle Id
socket.clVFV = await CalculateDmsVid(socket, JobData);
if (socket.clVFV.newId === "Y") {
//{1.2} This is a new Vehicle ID
CdkBase.createLogEvent(
socket,
"DEBUG",
`{1.2} clVFV DMSVid does *not* exist.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{1.2} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
);
//Check if DMSCustId is Empty - which it should always be?
//{6.6} Should check to see if a customer exists so that we can marry it to the new vehicle.
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6.6} Trying to find customer ID in DMS.`
);
//Array
const strIDS = await FindCustomerIdFromDms(socket, JobData);
if (strIDS && strIDS.length > 0) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{8.2} ${strIDS.length} Customer ID(s) found.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{8.2} strIDS: ${JSON.stringify(strIDS, null, 2)}`
);
if (strIDS.length > 1) {
//We have multiple IDs
//TODO: Do we need to let the person select it?
CdkBase.createLogEvent(
socket,
"WARNING",
`{F} Mutliple customer ids have been found (${strIDS.length})`
);
CdkBase.createLogEvent(
socket,
"DEBUG",
`Asking for user intervention to select customer.`
);
socket.emit("cdk-select-customer", strIDS);
return;
}
} else {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{8.5} Customer ID(s) *not* found.`
);
//Create a customer number, then use that to insert the customer record.
const newCustomerNumber = await GenerateCustomerNumberFromDms(
socket,
JobData
);
CdkBase.createLogEvent(
socket,
"DEBUG",
`{10.1} New Customer number generated. newCustomerNumber: ${newCustomerNumber}`
);
//Use the new customer number to insert the customer record.
socket.clADPC = await CreateCustomerInDms(
socket,
JobData,
newCustomerNumber
);
CdkBase.createLogEvent(
socket,
"DEBUG",
`{11.1} New Customer inserted.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{11.1} clADPC: ${JSON.stringify(socket.clADPC, null, 2)}`
);
}
} else {
CdkBase.createLogEvent(socket, "DEBUG", `{1.1} clVFV DMSVid does exist.`);
CdkBase.createLogEvent(
socket,
"TRACE",
`{1.1} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
);
//{2} Begin Find Vehicle in DMS
socket.clADPV = await FindVehicleInDms(socket, JobData, socket.clVFV); //TODO: Verify that this should always return a result. If an ID was found previously, it should be correct?
//{2.2} Check if the vehicle was found in the DMS.
if (socket.clADPV.AppErrorNo === "0") {
//Vehicle was found.
CdkBase.createLogEvent(
socket,
"DEBUG",
`{1.4} Vehicle was found in the DMS.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{1.4} clADPV: ${JSON.stringify(socket.clADPV, null, 2)}`
);
} else {
//Vehicle was not found.
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6.4} Vehicle does not exist in DMS. Will have to create one.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{6.4} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
);
}
}
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error encountered in JobExport. ${error}`
`Error encountered in CdkJobExport. ${error}`
);
} finally {
//Ensure we always insert logEvents
//GQL to insert logevents.
CdkBase.createLogEvent(
socket,
"DEBUG",
`Capturing log events to database.`
);
}
};
@@ -62,27 +194,319 @@ async function QueryJobData(socket, jobid) {
return result.jobs_by_pk;
}
async function DetermineDMSVid(socket, JobData) {
CdkBase.createLogEvent(socket, "TRACE", "{1} Begin Determine DMS VehicleID");
async function CreateCustomerInDms(socket, JobData, newCustomerNumber) {
CdkBase.createLogEvent(socket, "DEBUG", `{11} Begin Create Customer in DMS`);
try {
//Create SOAP Request for <getVehIds/>
const soapClient = await soap.createClientAsync(CdkWsdl.VehicleSearch);
const result = await soapClient.searchIDsByVINAsync(
{
arg0: { password: IMEX_CDK_PASSWORD, username: IMEX_CDK_USER },
arg1: { id: JobData.bodyshop.cdk_dealerid },
arg2: { VIN: JobData.v_vin },
},
{}
const soapClientCustomerInsertUpdate = await soap.createClientAsync(
CdkWsdl.CustomerInsertUpdate
);
console.log(result);
const soapResponseCustomerInsertUpdate =
await soapClientCustomerInsertUpdate.insertAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
arg2: { userId: null },
arg3: {
//Copied the required fields from the other integration.
//TODO: Verify whether we need to bring more information in.
id: { value: newCustomerNumber },
address: {
city: JobData.ownr_city,
country: null,
postalcode: JobData.ownr_zip,
stateOrProvince: JobData.ownr_st,
},
contactInfo: {
mainTelephoneNumber: { main: true, value: JobData.ownr_ph1 },
},
demographics: null,
name1: {
companyname: null,
firstName: JobData.ownr_fn,
fullname: null,
lastName: JobData.ownr_ln,
middleName: null,
nameType: "Person",
suffix: null,
title: null,
},
},
},
{}
);
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseCustomerInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const customer = result && result.return;
return customer;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in DetermineDMSVid - ${JSON.stringify(error, null, 2)}`
`Error in CreateCustomerInDms - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}
async function GenerateCustomerNumberFromDms(socket, JobData) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{10} Begin Generate Customer Number from DMS`
);
try {
const soapClientCustomerInsertUpdate = await soap.createClientAsync(
CdkWsdl.CustomerInsertUpdate
);
const soapResponseCustomerInsertUpdate =
await soapClientCustomerInsertUpdate.getCustomerNumberAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
arg2: { userId: null },
},
{}
);
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseCustomerInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const customerNumber =
result && result.return && result.return.customerNumber;
return customerNumber;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in GenerateCustomerNumberFromDms - ${JSON.stringify(
error,
null,
2
)}`
);
throw new Error(error);
}
}
async function FindCustomerIdFromDms(socket, JobData) {
const ownerName = `${JobData.ownr_ln},${JobData.ownr_fn}`;
CdkBase.createLogEvent(
socket,
"DEBUG",
`{8} Begin Read Customer from DMS using OWNER NAME: ${ownerName}`
);
try {
const soapClientCustomerSearch = await soap.createClientAsync(
CdkWsdl.CustomerSearch
);
const soapResponseCustomerSearch =
await soapClientCustomerSearch.executeSearchAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
arg2: {
verb: "EXACT",
key: ownerName,
},
},
{}
);
CheckCdkResponseForError(socket, soapResponseCustomerSearch);
const [
result, // rawResponse, soapheader, rawRequest
] = soapResponseCustomerSearch;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const CustomersFromDms = result && result.return;
return CustomersFromDms;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in FindCustomerIdFromDms - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}
async function FindVehicleInDms(socket, JobData, clVFV) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{2}/{6} Begin Find Vehicle In DMS using clVFV: ${clVFV}`
);
try {
const soapClientVehicleInsertUpdate = await soap.createClientAsync(
CdkWsdl.VehicleInsertUpdate
);
const soapResponseVehicleInsertUpdate =
await soapClientVehicleInsertUpdate.readBulkAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { id: JobData.bodyshop.cdk_dealerid },
arg2: {
fileType: "VEHICLES",
vehiclesVehicleId: clVFV.vehiclesVehId,
},
},
{}
);
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseVehicleInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientVehicleInsertUpdate.readBulkAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const VehicleFromDMS = result && result.return && result.return[0];
return VehicleFromDMS;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in FindVehicleInDms - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}
async function CalculateDmsVid(socket, JobData) {
CdkBase.createLogEvent(
socket,
"TRACE",
`{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`
);
try {
const soapClientVehicleInsertUpdate = await soap.createClientAsync(
CdkWsdl.VehicleInsertUpdate
);
const soapResponseVehicleInsertUpdate =
await soapClientVehicleInsertUpdate.getVehIdsAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { id: JobData.bodyshop.cdk_dealerid },
arg2: { VIN: JobData.v_vin },
},
{}
);
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseVehicleInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientVehicleInsertUpdate.searchIDsByVINAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const DmsVehicle = result && result.return && result.return[0];
return DmsVehicle;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in CalculateDmsVid - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}
function CheckCdkResponseForError(socket, soapResponse) {
if (!soapResponse[0]) {
//The response was null, this might be ok, it might not.
CdkBase.createLogEvent(
socket,
"WARNING",
`Warning detected in CDK Response - it appears to be null. Stack: ${
new Error().stack
}`
);
return;
}
const ResultToCheck = soapResponse[0].return;
if (Array.isArray(ResultToCheck)) {
ResultToCheck.forEach((result) => checkIndividualResult(socket, result));
} else {
checkIndividualResult(socket, ResultToCheck);
}
}
function checkIndividualResult(socket, ResultToCheck) {
if (
ResultToCheck.errorLevel === 0 ||
ResultToCheck.errorLevel === "0" ||
ResultToCheck.code === "success" ||
(!ResultToCheck.code && !ResultToCheck.errorLevel)
)
//TODO: Verify that this is the best way to detect errors.
return;
else {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error detected in CDK Response - ${JSON.stringify(
ResultToCheck,
null,
2
)}`
);
throw new Error(
`Error found while validating CDK response for ${JSON.stringify(
ResultToCheck,
null,
2
)}:`
);
}
}

View File

@@ -1,4 +1,33 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
// const cdkDomain =
// process.env.NODE_ENV === "production"
// ? "https://3pa.dmotorworks.com"
// : "https://uat-3pa.dmotorworks.com";
const cdkDomain = "https://uat-3pa.dmotorworks.com";
exports.default = {
VehicleSearch:
"https://uat-3pa.dmotorworks.com/pip-vehicle/services/VehicleSearch?wsdl",
// VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`,
VehicleInsertUpdate: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`,
CustomerInsertUpdate: `${cdkDomain}/pip-customer/services/CustomerInsertUpdate?wsdl`,
CustomerSearch: `${cdkDomain}/pip-customer/services/CustomerSearch?wsdl`,
};
// The following login credentials will be used for all PIPs and all environments (User Acceptance Testing and Production).
// Only the URLs will change from https://uat-3pa.dmoto... to https://3pa.dmoto... or https://api-dit.connect... to https://api.connect...
// Accounting GL/Accounting GL WIP Update - https://uat-3pa.dmotorworks.com/pip-accounting-gl/services/AccountingGLInsertUpdate?wsdl
// Customer Insert Update - https://uat-3pa.dmotorworks.com/pip-customer/services/CustomerInsertUpdate?wsdl
// Help Database Location - https://uat-3pa.dmotorworks.com/pip-help-database-location/services/HelpDatabaseLocation?wsdl
// Parts Inventory Insert Update - https://uat-3pa.dmotorworks.com/pip-parts-inventory/services/PartsInventoryInsertUpdate?wsdl
// Purchase Order Insert - https://uat-3pa.dmotorworks.com/pip-purchase-order/services/PurchaseOrderInsert?wsdl
// Repair Order MLS Insert Update - https://uat-3pa.dmotorworks.com/pip-repair-order-mls/services/RepairOrderMLSInsertUpdate?wsdl
// Repair Order Parts Insert Update - https://uat-3pa.dmotorworks.com/pip-repair-order-parts/services/RepairOrderPartsInsertUpdate?wsdl
// Service History Insert - https://uat-3pa.dmotorworks.com/pip-service-history-insert/services/ServiceHistoryInsert?wsdl
// Service Repair Order Update - https://uat-3pa.dmotorworks.com/pip-service-repair-order/services/ServiceRepairOrderUpdate?wsdl
// Service Vehicle Insert Update - https://uat-3pa.dmotorworks.com/pip-vehicle/services/VehicleInsertUpdate?wsdl

View File

@@ -1,5 +1,4 @@
const path = require("path");
const _ = require("lodash");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -51,6 +50,15 @@ io.on("connection", (socket) => {
socket.on("cdk-export-job", (jobid) => {
CdkJobExport(socket, jobid);
});
socket.on("cdk-selected-customer", (selectedCustomerId) => {
createLogEvent(
socket,
"DEBUG",
`User selected customer ID ${selectedCustomerId}`
);
socket.selectedCustomerId = selectedCustomerId;
//CdkJobExport(socket, jobid);
});
socket.on("disconnect", () => {
createLogEvent(socket, "DEBUG", `User disconnected.`);
@@ -81,6 +89,9 @@ function createLogEvent(socket, level, message) {
message,
});
}
// if (level === "ERROR") {
// throw new Error(message);
// }
}
}