feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint
This commit is contained in:
@@ -1,567 +1,133 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Reynolds & Reynolds (RR) Job Export flow (scaffold).
|
||||
//
|
||||
// Parity with Fortellis/CDK export shape so the UI + socket flows remain
|
||||
// consistent:
|
||||
//
|
||||
// - RRJobExport: initial VIN/customer discovery & prompt for customer select
|
||||
// - RRSelectedCustomer: create/update customer, insert/read vehicle,
|
||||
// post WIP batch, post history, mark success/failure, notify client
|
||||
//
|
||||
// What’s still missing (fill in from Rome/RR PDFs you provided):
|
||||
// - Exact request/response envelopes for each RR operation
|
||||
// (Customer Insert/Update, Vehicle Insert/Read, WIP APIs, Service History).
|
||||
// - Final success/error conditions for assertRrOk (we currently use heuristics).
|
||||
// - Precise field mappings inside CreateCustomer, InsertVehicle,
|
||||
// StartWip/TransBatchWip/PostBatchWip, InsertServiceVehicleHistory.
|
||||
// -----------------------------------------------------------------------------
|
||||
/**
|
||||
* @file rr-job-export.js
|
||||
* @description Orchestrates the full Reynolds & Reynolds DMS export flow.
|
||||
* Creates/updates customers, vehicles, and repair orders according to Rome specs.
|
||||
*/
|
||||
|
||||
const { GraphQLClient } = require("graphql-request");
|
||||
const moment = require("moment-timezone");
|
||||
|
||||
const CalculateAllocations = require("../cdk/cdk-calculate-allocations").default; // reuse allocations
|
||||
const CreateRRLogEvent = require("./rr-logger");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const { MakeRRCall, RRActions, getTransactionType, defaultRRTTL, RRCacheEnums } = require("./rr-helpers");
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Public entry points (wired in redisSocketEvents.js)
|
||||
// -----------------------------------------------------------------------------
|
||||
const { RrCustomerInsert, RrCustomerUpdate } = require("./rr-customer");
|
||||
const { CreateRepairOrder, UpdateRepairOrder } = require("./rr-repair-orders");
|
||||
const { MakeRRCall, RRActions, getDealerConfig } = require("./rr-helpers");
|
||||
const { assertRrOkXml, extractRrResponseData } = require("./rr-error");
|
||||
const RRLogger = require("./rr-logger");
|
||||
const { mapServiceVehicleInsert } = require("./rr-mappers");
|
||||
|
||||
/**
|
||||
* Seed export: cache txEnvelope + JobData, discover VIN->VehicleId + owner,
|
||||
* search by customer name, and prompt client to select/create a customer.
|
||||
* Inserts a service vehicle record for the repair order.
|
||||
* Follows the "Rome Insert Service Vehicle Interface Specification" via SOAP/XML.
|
||||
*/
|
||||
async function RRJobExport({ socket, redisHelpers, txEnvelope, jobid }) {
|
||||
const { setSessionTransactionData } = redisHelpers;
|
||||
|
||||
async function RrServiceVehicleInsert({ socket, redisHelpers, JobData, dealerConfig }) {
|
||||
try {
|
||||
CreateRRLogEvent(socket, "DEBUG", `[RR] Received Job export request`, { jobid });
|
||||
RRLogger(socket, "info", "RR Insert Service Vehicle started", { jobid: JobData?.id });
|
||||
|
||||
// cache txEnvelope for this job session
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.txEnvelope,
|
||||
txEnvelope,
|
||||
defaultRRTTL
|
||||
);
|
||||
// Build Mustache variables for server/rr/xml-templates/InsertServiceVehicle.xml
|
||||
const variables = mapServiceVehicleInsert(JobData, dealerConfig);
|
||||
|
||||
const JobData = await QueryJobData({ socket, jobid });
|
||||
await setSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.JobData, JobData, defaultRRTTL);
|
||||
const xml = await MakeRRCall({
|
||||
action: RRActions.InsertServiceVehicle,
|
||||
body: { template: "InsertServiceVehicle", data: variables },
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id,
|
||||
dealerConfig
|
||||
});
|
||||
|
||||
CreateRRLogEvent(socket, "DEBUG", `[RR] Get Vehicle Id via VIN`, { vin: JobData.v_vin });
|
||||
const ok = assertRrOkXml(xml, { apiName: "RR Insert Service Vehicle" });
|
||||
const normalized = extractRrResponseData(ok, { action: "InsertServiceVehicle" });
|
||||
|
||||
const DMSVid = await GetVehicleId({ socket, redisHelpers, JobData });
|
||||
await setSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.DMSVid, DMSVid, defaultRRTTL);
|
||||
RRLogger(socket, "debug", "RR Insert Service Vehicle success", {
|
||||
jobid: JobData?.id,
|
||||
vehicleId: normalized?.VehicleId || normalized?.vehicleId
|
||||
});
|
||||
|
||||
let DMSVehCustomer;
|
||||
if (!DMSVid?.newId) {
|
||||
// existing vehicle, load details
|
||||
const DMSVeh = await ReadVehicleById({ socket, redisHelpers, JobData, vehicleId: DMSVid.vehiclesVehId });
|
||||
await setSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.DMSVeh, DMSVeh, defaultRRTTL);
|
||||
|
||||
// Try to read the CURRENT owner (shape TBD per RR)
|
||||
const owner = DMSVeh?.owners && DMSVeh.owners.find((o) => o.id?.assigningPartyId === "CURRENT");
|
||||
if (owner?.id?.value) {
|
||||
DMSVehCustomer = await ReadCustomerById({ socket, redisHelpers, JobData, customerId: owner.id.value });
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.DMSVehCustomer,
|
||||
DMSVehCustomer,
|
||||
defaultRRTTL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Search customers by job owner name (param names TBD per RR)
|
||||
const DMSCustList = await SearchCustomerByName({ socket, redisHelpers, JobData });
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.DMSCustList,
|
||||
DMSCustList,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// Emit choices: (VIN owner first if present) + search results
|
||||
socket.emit("rr-select-customer", [
|
||||
...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []),
|
||||
...(Array.isArray(DMSCustList) ? DMSCustList : [])
|
||||
]);
|
||||
return normalized;
|
||||
} catch (error) {
|
||||
CreateRRLogEvent(socket, "ERROR", `[RR] RRJobExport failed: ${error.message}`, { stack: error.stack });
|
||||
RRLogger(socket, "error", `RR Insert Service Vehicle failed: ${error.message}`, { jobid: JobData?.id });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After client selects a customer (or requests create):
|
||||
* - Read or create the customer
|
||||
* - Insert vehicle if needed (or read existing)
|
||||
* - StartWip -> TransBatchWip -> PostBatchWip -> Mark exported
|
||||
* - Optionally insert service history
|
||||
*/
|
||||
async function RRSelectedCustomer({ socket, redisHelpers, selectedCustomerId, jobid }) {
|
||||
const { setSessionTransactionData, getSessionTransactionData } = redisHelpers;
|
||||
|
||||
try {
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.selectedCustomerId,
|
||||
selectedCustomerId,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
const JobData = await getSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.JobData);
|
||||
const txEnvelope = await getSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.txEnvelope);
|
||||
const DMSVid = await getSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.DMSVid);
|
||||
|
||||
// Ensure we have a customer to use
|
||||
let DMSCust;
|
||||
if (selectedCustomerId) {
|
||||
DMSCust = await ReadCustomerById({ socket, redisHelpers, JobData, customerId: selectedCustomerId });
|
||||
} else {
|
||||
const createRes = await CreateCustomer({ socket, redisHelpers, JobData });
|
||||
DMSCust = { customerId: createRes?.data || createRes?.customerId || createRes?.id };
|
||||
}
|
||||
await setSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.DMSCust, DMSCust, defaultRRTTL);
|
||||
|
||||
// Ensure the vehicle exists (ownership model TBD per RR)
|
||||
let DMSVeh;
|
||||
if (DMSVid?.newId) {
|
||||
DMSVeh = await InsertVehicle({ socket, redisHelpers, JobData, txEnvelope, DMSVid, DMSCust });
|
||||
} else {
|
||||
DMSVeh = await ReadVehicleById({ socket, redisHelpers, JobData, vehicleId: DMSVid.vehiclesVehId });
|
||||
// TODO: If RR supports “UpdateVehicle” to change ownership, add it here.
|
||||
}
|
||||
await setSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.DMSVeh, DMSVeh, defaultRRTTL);
|
||||
|
||||
// Start WIP header
|
||||
const DMSTransHeader = await StartWip({ socket, redisHelpers, JobData, txEnvelope });
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.DMSTransHeader,
|
||||
DMSTransHeader,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// Post lines
|
||||
const DMSBatchTxn = await TransBatchWip({ socket, redisHelpers, JobData });
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.DMSBatchTxn,
|
||||
DMSBatchTxn,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
// Decide success from envelope (heuristic until exact spec confirmed)
|
||||
if (String(DMSBatchTxn?.rtnCode || "0") === "0") {
|
||||
const DmsBatchTxnPost = await PostBatchWip({ socket, redisHelpers, JobData });
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.DmsBatchTxnPost,
|
||||
DmsBatchTxnPost,
|
||||
defaultRRTTL
|
||||
);
|
||||
|
||||
if (String(DmsBatchTxnPost?.rtnCode || "0") === "0") {
|
||||
await MarkJobExported({ socket, jobid: JobData.id, redisHelpers });
|
||||
|
||||
// Optional service history write (non-blocking)
|
||||
try {
|
||||
const DMSVehHistory = await InsertServiceVehicleHistory({ socket, redisHelpers, JobData });
|
||||
await setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.DMSVehHistory,
|
||||
DMSVehHistory,
|
||||
defaultRRTTL
|
||||
);
|
||||
} catch (e) {
|
||||
CreateRRLogEvent(socket, "WARN", `[RR] ServiceVehicleHistory optional step failed: ${e.message}`);
|
||||
}
|
||||
|
||||
socket.emit("export-success", JobData.id);
|
||||
} else {
|
||||
await HandlePostingError({ socket, redisHelpers, JobData, DMSTransHeader });
|
||||
}
|
||||
} else {
|
||||
await InsertFailedExportLog({
|
||||
socket,
|
||||
JobData,
|
||||
error: `RR DMSBatchTxn not successful: ${JSON.stringify(DMSBatchTxn)}`
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
CreateRRLogEvent(socket, "ERROR", `[RR] RRSelectedCustomer failed: ${error.message}`, { stack: error.stack });
|
||||
const JobData = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.JobData
|
||||
);
|
||||
if (JobData) await InsertFailedExportLog({ socket, JobData, error });
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// GraphQL job fetch
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
async function QueryJobData({ socket, jobid }) {
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const currentToken =
|
||||
(socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${currentToken}` })
|
||||
.request(queries.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid });
|
||||
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// RR API step stubs (wire to MakeRRCall). Replace request payloads once the
|
||||
// exact RR/Rome schemas are confirmed from the PDFs.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
async function GetVehicleId({ socket, redisHelpers, JobData }) {
|
||||
return await MakeRRCall({
|
||||
...RRActions.GetVehicleId,
|
||||
requestPathParams: JobData.v_vin,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function ReadVehicleById({ socket, redisHelpers, JobData, vehicleId }) {
|
||||
return await MakeRRCall({
|
||||
...RRActions.ReadVehicle,
|
||||
requestPathParams: vehicleId,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function ReadCustomerById({ socket, redisHelpers, JobData, customerId }) {
|
||||
return await MakeRRCall({
|
||||
...RRActions.ReadCustomer,
|
||||
requestPathParams: customerId,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function SearchCustomerByName({ socket, redisHelpers, JobData }) {
|
||||
// TODO: Confirm exact query param names from the RR search spec
|
||||
const ownerNameParams =
|
||||
JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== ""
|
||||
? [["lastName", JobData.ownr_co_nm]] // placeholder: business search
|
||||
: [
|
||||
["firstName", JobData.ownr_fn],
|
||||
["lastName", JobData.ownr_ln]
|
||||
];
|
||||
|
||||
return await MakeRRCall({
|
||||
...RRActions.QueryCustomerByName, // ✅ use action defined in rr-helpers
|
||||
requestSearchParams: ownerNameParams,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function CreateCustomer({ socket, redisHelpers, JobData }) {
|
||||
// TODO: Replace with exact RR Customer Insert envelope & fields
|
||||
const body = {
|
||||
customerType: JobData.ownr_co_nm ? "BUSINESS" : "INDIVIDUAL"
|
||||
};
|
||||
return await MakeRRCall({
|
||||
...RRActions.CreateCustomer,
|
||||
body,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function InsertVehicle({ socket, redisHelpers, JobData /*, txEnvelope, DMSVid, DMSCust*/ }) {
|
||||
// TODO: Replace with exact RR Service Vehicle Insert mapping
|
||||
const body = {
|
||||
vin: JobData.v_vin
|
||||
// owners, make/model, odometer, etc…
|
||||
};
|
||||
return await MakeRRCall({
|
||||
...RRActions.InsertVehicle,
|
||||
body,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function StartWip({ socket, redisHelpers, JobData, txEnvelope }) {
|
||||
// TODO: Replace body fields with RR WIP header schema
|
||||
const body = {
|
||||
acctgDate: moment().tz(JobData.bodyshop.timezone).format("YYYY-MM-DD"),
|
||||
desc: txEnvelope?.story || "",
|
||||
docType: "10",
|
||||
m13Flag: "0",
|
||||
refer: JobData.ro_number,
|
||||
srcCo: JobData.bodyshop?.cdk_configuration?.srcco || "00", // placeholder from CDK config; RR equivalent TBD
|
||||
srcJrnl: txEnvelope?.journal,
|
||||
userID: "BSMS"
|
||||
};
|
||||
return await MakeRRCall({
|
||||
...RRActions.StartWip,
|
||||
body,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function TransBatchWip({ socket, redisHelpers, JobData }) {
|
||||
const wips = await GenerateTransWips({ socket, redisHelpers, JobData });
|
||||
|
||||
// TODO: Ensure this body shape matches RR batch transaction schema
|
||||
return await MakeRRCall({
|
||||
...RRActions.TranBatchWip,
|
||||
body: wips,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function PostBatchWip({ socket, redisHelpers, JobData }) {
|
||||
const DMSTransHeader = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
RRCacheEnums.DMSTransHeader
|
||||
);
|
||||
|
||||
// TODO: Confirm final field names for “post” operation in RR
|
||||
const body = {
|
||||
opCode: "P",
|
||||
transID: DMSTransHeader?.transID
|
||||
};
|
||||
|
||||
return await MakeRRCall({
|
||||
...RRActions.PostBatchWip,
|
||||
body,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function QueryErrWip({ socket, redisHelpers, JobData }) {
|
||||
const DMSTransHeader = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
RRCacheEnums.DMSTransHeader
|
||||
);
|
||||
return await MakeRRCall({
|
||||
...RRActions.QueryErrorWip,
|
||||
requestPathParams: DMSTransHeader?.transID,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function DeleteWip({ socket, redisHelpers, JobData }) {
|
||||
const DMSTransHeader = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
RRCacheEnums.DMSTransHeader
|
||||
);
|
||||
|
||||
// TODO: Confirm if RR uses the same endpoint with opCode=D to delete/void
|
||||
const body = { opCode: "D", transID: DMSTransHeader?.transID };
|
||||
|
||||
return await MakeRRCall({
|
||||
...RRActions.PostBatchWip,
|
||||
body,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function InsertServiceVehicleHistory({ socket, redisHelpers, JobData }) {
|
||||
const txEnvelope = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
RRCacheEnums.txEnvelope
|
||||
);
|
||||
|
||||
// TODO: Replace with RR Service Vehicle History schema
|
||||
const body = {
|
||||
comments: txEnvelope?.story || ""
|
||||
};
|
||||
return await MakeRRCall({
|
||||
...RRActions.ServiceHistoryInsert,
|
||||
body,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: JobData.id
|
||||
});
|
||||
}
|
||||
|
||||
async function HandlePostingError({ socket, redisHelpers, JobData /*, DMSTransHeader*/ }) {
|
||||
const DmsError = await QueryErrWip({ socket, redisHelpers, JobData });
|
||||
await DeleteWip({ socket, redisHelpers, JobData });
|
||||
|
||||
const errString = DmsError?.errMsg || JSON.stringify(DmsError);
|
||||
errString?.split("|")?.forEach((e) => e && CreateRRLogEvent(socket, "ERROR", `[RR] Post error: ${e}`));
|
||||
await InsertFailedExportLog({ socket, JobData, error: errString });
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert app allocations to RR WIP lines.
|
||||
* Re-uses existing CalculateAllocations to keep parity with CDK/Fortellis.
|
||||
* Full DMS export sequence for Reynolds & Reynolds.
|
||||
*
|
||||
* TODO: Confirm exact RR posting model (accounts, control numbers, company ids,
|
||||
* and whether amounts are signed or need separate debit/credit flags).
|
||||
* 1. Ensure customer exists (insert or update)
|
||||
* 2. Ensure vehicle exists/linked
|
||||
* 3. Create or update repair order
|
||||
*/
|
||||
async function GenerateTransWips({ socket, redisHelpers, JobData }) {
|
||||
const allocations = await CalculateAllocations(socket, JobData.id, true); // true==verbose logging
|
||||
const DMSTransHeader = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
RRCacheEnums.DMSTransHeader
|
||||
);
|
||||
async function ExportJobToRR({ socket, redisHelpers, JobData }) {
|
||||
const jobid = JobData?.id;
|
||||
const bodyshopId = socket?.bodyshopId || JobData?.bodyshopid;
|
||||
|
||||
const wips = [];
|
||||
allocations.forEach((alloc) => {
|
||||
if (alloc.sale.getAmount() > 0 && !alloc.tax) {
|
||||
wips.push({
|
||||
acct: alloc.profitCenter.dms_acctnumber,
|
||||
cntl: alloc.profitCenter.dms_control_override || JobData.ro_number,
|
||||
postAmt: alloc.sale.multiply(-1).getAmount(), // sale is a credit in many GLs; confirm RR sign
|
||||
transID: DMSTransHeader?.transID,
|
||||
trgtCoID: JobData.bodyshop?.cdk_configuration?.srcco // RR equivalent TBD
|
||||
});
|
||||
}
|
||||
if (alloc.cost.getAmount() > 0 && !alloc.tax) {
|
||||
wips.push({
|
||||
acct: alloc.costCenter.dms_acctnumber,
|
||||
cntl: alloc.costCenter.dms_control_override || JobData.ro_number,
|
||||
postAmt: alloc.cost.getAmount(),
|
||||
transID: DMSTransHeader?.transID,
|
||||
trgtCoID: JobData.bodyshop?.cdk_configuration?.srcco
|
||||
});
|
||||
wips.push({
|
||||
acct: alloc.costCenter.dms_wip_acctnumber,
|
||||
cntl: alloc.costCenter.dms_control_override || JobData.ro_number,
|
||||
postAmt: alloc.cost.multiply(-1).getAmount(),
|
||||
transID: DMSTransHeader?.transID,
|
||||
trgtCoID: JobData.bodyshop?.cdk_configuration?.srcco
|
||||
});
|
||||
}
|
||||
if (alloc.tax && alloc.sale.getAmount() > 0) {
|
||||
wips.push({
|
||||
acct: alloc.profitCenter.dms_acctnumber,
|
||||
cntl: alloc.profitCenter.dms_control_override || JobData.ro_number,
|
||||
postAmt: alloc.sale.multiply(-1).getAmount(),
|
||||
transID: DMSTransHeader?.transID,
|
||||
trgtCoID: JobData.bodyshop?.cdk_configuration?.srcco
|
||||
});
|
||||
}
|
||||
});
|
||||
RRLogger(socket, "info", "Starting RR job export", { jobid, bodyshopId });
|
||||
|
||||
const txEnvelope = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
RRCacheEnums.txEnvelope
|
||||
);
|
||||
|
||||
txEnvelope?.payers?.forEach((payer) => {
|
||||
wips.push({
|
||||
acct: payer.dms_acctnumber,
|
||||
cntl: payer.controlnumber,
|
||||
postAmt: Math.round(payer.amount * 100), // assuming cents (confirm RR units)
|
||||
transID: DMSTransHeader?.transID,
|
||||
trgtCoID: JobData.bodyshop?.cdk_configuration?.srcco
|
||||
});
|
||||
});
|
||||
|
||||
await redisHelpers.setSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(JobData.id),
|
||||
RRCacheEnums.transWips,
|
||||
wips,
|
||||
defaultRRTTL
|
||||
);
|
||||
return wips;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// DB logging mirrors Fortellis (status + export log)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
async function MarkJobExported({ socket, jobid, redisHelpers }) {
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const currentToken =
|
||||
(socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);
|
||||
|
||||
// Pull JobData from the session to get bodyshop info + default statuses
|
||||
const JobData =
|
||||
(await redisHelpers.getSessionTransactionData(socket.id, getTransactionType(jobid), RRCacheEnums.JobData)) || {};
|
||||
|
||||
const transWips = await redisHelpers.getSessionTransactionData(
|
||||
socket.id,
|
||||
getTransactionType(jobid),
|
||||
RRCacheEnums.transWips
|
||||
);
|
||||
|
||||
return client.setHeaders({ Authorization: `Bearer ${currentToken}` }).request(queries.MARK_JOB_EXPORTED, {
|
||||
jobId: jobid,
|
||||
job: {
|
||||
status: JobData?.bodyshop?.md_ro_statuses?.default_exported || "Exported*",
|
||||
date_exported: new Date()
|
||||
},
|
||||
log: {
|
||||
bodyshopid: JobData?.bodyshop?.id,
|
||||
jobid,
|
||||
successful: true,
|
||||
useremail: socket.user?.email,
|
||||
metadata: transWips
|
||||
},
|
||||
bill: { exported: true, exported_at: new Date() }
|
||||
});
|
||||
}
|
||||
|
||||
async function InsertFailedExportLog({ socket, JobData, error }) {
|
||||
try {
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const currentToken =
|
||||
(socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token);
|
||||
// Pull dealer-level overrides once (DB), env/platform secrets come from rr-helpers internally.
|
||||
const dealerConfig = bodyshopId ? await getDealerConfig(bodyshopId) : {};
|
||||
|
||||
return await client.setHeaders({ Authorization: `Bearer ${currentToken}` }).request(queries.INSERT_EXPORT_LOG, {
|
||||
log: {
|
||||
bodyshopid: JobData.bodyshop.id,
|
||||
jobid: JobData.id,
|
||||
successful: false,
|
||||
message: typeof error === "string" ? error : JSON.stringify(error),
|
||||
useremail: socket.user?.email
|
||||
}
|
||||
//
|
||||
// STEP 1: CUSTOMER
|
||||
//
|
||||
RRLogger(socket, "info", "RR Step 1: Customer check/insert", { jobid });
|
||||
let rrCustomerResult;
|
||||
|
||||
if (JobData?.rr_customer_id) {
|
||||
rrCustomerResult = await RrCustomerUpdate({
|
||||
socket,
|
||||
redisHelpers,
|
||||
JobData,
|
||||
existingCustomer: { CustomerId: JobData.rr_customer_id },
|
||||
patch: JobData.customer_patch
|
||||
});
|
||||
} else {
|
||||
rrCustomerResult = await RrCustomerInsert({ socket, redisHelpers, JobData });
|
||||
}
|
||||
|
||||
//
|
||||
// STEP 2: VEHICLE
|
||||
//
|
||||
RRLogger(socket, "info", "RR Step 2: Vehicle insert", { jobid });
|
||||
const rrVehicleResult = await RrServiceVehicleInsert({ socket, redisHelpers, JobData, dealerConfig });
|
||||
|
||||
//
|
||||
// STEP 3: REPAIR ORDER
|
||||
//
|
||||
RRLogger(socket, "info", "RR Step 3: Repair Order create/update", { jobid });
|
||||
let rrRepairOrderResult;
|
||||
|
||||
if (JobData?.rr_ro_id) {
|
||||
rrRepairOrderResult = await UpdateRepairOrder({ socket, redisHelpers, JobData });
|
||||
} else {
|
||||
rrRepairOrderResult = await CreateRepairOrder({ socket, redisHelpers, JobData });
|
||||
}
|
||||
|
||||
//
|
||||
// FINALIZE
|
||||
//
|
||||
RRLogger(socket, "info", "RR Export completed successfully", {
|
||||
jobid,
|
||||
rr_customer_id: rrCustomerResult?.CustomerId || rrCustomerResult?.customerId,
|
||||
rr_vehicle_id: rrVehicleResult?.VehicleId || rrVehicleResult?.vehicleId,
|
||||
rr_ro_id: rrRepairOrderResult?.RepairOrderId || rrRepairOrderResult?.repairOrderId
|
||||
});
|
||||
} catch (error2) {
|
||||
CreateRRLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error2.message}`, { stack: error2.stack });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
customer: rrCustomerResult,
|
||||
vehicle: rrVehicleResult,
|
||||
repairOrder: rrRepairOrderResult
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
RRLogger(socket, "error", `RR job export failed: ${error.message}`, { jobid });
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RRJobExport,
|
||||
RRSelectedCustomer
|
||||
ExportJobToRR,
|
||||
RrServiceVehicleInsert
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user