Files
bodyshop/server/accounting/pbs/pbs-job-export.js
2025-11-14 08:36:42 -08:00

862 lines
32 KiB
JavaScript

const GraphQLClient = require("graphql-request").GraphQLClient;
const AxiosLib = require("axios").default;
const queries = require("../../graphql-client/queries");
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const WsLogger = require("../../web-sockets/createLogEvent");
const fs = require("fs");
const path = require("path");
//const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").default;
const moment = require("moment-timezone");
const Dinero = require("dinero.js");
const InstanceManager = require("../../utils/instanceMgr").default;
const axios = AxiosLib.create();
axios.interceptors.request.use((x) => {
const socket = x.socket;
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
//logRequestToFile(printable);
WsLogger.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data);
return x;
});
axios.interceptors.response.use((x) => {
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} ${x.statusText} |${JSON.stringify(x.data)}`;
//logRequestToFile(printable);
WsLogger.createJsonEvent(socket, "DEBUG", `Raw Response: ${printable}`, x.data);
return x;
});
function logRequestToFile(printable) {
try {
const logDir = path.join(process.cwd(), "logs");
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
const logFile = path.join(logDir, "pbs-http.log");
fs.appendFileSync(logFile, `${printable}\n`);
} catch (err) {
console.error("Unexpected error in logRequestToFile:", err);
}
}
const defaultHandler = async (socket, { txEnvelope, jobid }) => {
socket.logEvents = [];
socket.recordid = jobid;
socket.txEnvelope = txEnvelope;
try {
WsLogger.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`);
const JobData = await QueryJobData(socket, jobid);
socket.JobData = JobData;
WsLogger.createLogEvent(socket, "INFO", `Querying the DMS for the Vehicle Record.`);
//Query for the Vehicle record to get the associated customer.
socket.DmsVeh = await QueryVehicleFromDms(socket);
//Todo: Need to validate the lines and methods below.
if (socket.DmsVeh?.CustomerRef) {
//Get the associated customer from the Vehicle Record.
socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef);
}
socket.DMSCustList = await QueryCustomersFromDms(socket);
socket.emit("pbs-select-customer", [
...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []),
...socket.DMSCustList
]);
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
}
};
exports.default = defaultHandler;
exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) {
try {
socket.selectedCustomerId = selectedCustomerId;
if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle !== true) {
WsLogger.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`);
//Upsert the contact information as per Wafaa's Email.
WsLogger.createLogEvent(
socket,
"INFO",
`Upserting contact information to DMS for ${
socket.JobData.ownr_fn || ""
} ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}`
);
const ownerRef = await UpsertContactData(socket, selectedCustomerId);
socket.ownerRef = ownerRef;
WsLogger.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
const vehicleRef = await UpsertVehicleData(socket, ownerRef.ReferenceId);
socket.vehicleRef = vehicleRef;
} else {
WsLogger.createLogEvent(
socket,
"INFO",
`Contact and Vehicle updates disabled. Querying data and skipping to accounting data insert.`
);
//Must query for records to insert $0 RO.
if (!socket.ownerRef) {
const ownerRef = (await QueryCustomerBycodeFromDms(socket, selectedCustomerId))?.[0];
socket.ownerRef = ownerRef;
}
const vehicleRef = await GetVehicleData(socket, socket.ownerRef?.ReferenceId || socket.selectedCustomerId);
socket.vehicleRef = vehicleRef;
}
WsLogger.createLogEvent(socket, "INFO", `Inserting account posting data...`);
const insertResponse = await InsertAccountPostingData(socket);
if (insertResponse.WasSuccessful) {
if (socket.JobData.bodyshop.pbs_configuration.ro_posting) {
await CreateRepairOrderInPBS(socket, socket.ownerRef, socket.vehicleRef);
}
WsLogger.createLogEvent(socket, "INFO", `Marking job as exported.`);
await MarkJobExported(socket, socket.JobData.id);
socket.emit("export-success", socket.JobData.id);
} else {
WsLogger.createLogEvent(socket, "ERROR", `Export was not successful.`);
}
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
await InsertFailedExportLog(socket, error);
}
};
// Was Successful
async function CheckForErrors(socket, response) {
if (response.WasSuccessful === undefined || response.WasSuccessful === true) {
WsLogger.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`);
} else {
WsLogger.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
WsLogger.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`);
}
}
exports.CheckForErrors = CheckForErrors;
async function QueryJobData(socket, jobid) {
WsLogger.createLogEvent(socket, "INFO", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.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)}`);
return result.jobs_by_pk;
}
async function QueryVehicleFromDms(socket) {
try {
if (!socket.JobData.v_vin) return null;
const { data: VehicleGetResponse } = await axios.post(
PBS_ENDPOINTS.VehicleGet,
{
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
// VehicleId: "00000000000000000000000000000000",
// Year: "String",
// Make: "String",
// Model: "String",
// Trim: "String",
// ModelNumber: "String",
// StockNumber: "String",
VIN: socket.JobData.v_vin
// LicenseNumber: "String",
// Lot: "String",
// Status: "String",
// StatusList: ["String"],
// OwnerRef: "00000000000000000000000000000000",
// ModifiedSince: "0001-01-01T00:00:00.0000000Z",
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
// LastSaleSince: "0001-01-01T00:00:00.0000000Z",
// VehicleIDList: ["00000000000000000000000000000000"],
// IncludeInactive: false,
// IncludeBuildVehicles: false,
// ShortVIN: "String",
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, VehicleGetResponse);
return VehicleGetResponse;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
throw new Error(error);
}
}
async function QueryCustomersFromDms(socket) {
try {
const { data: CustomerGetResponse } = await axios.post(
PBS_ENDPOINTS.ContactGet,
{
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
//ContactId: "00000000000000000000000000000000",
// ContactCode: socket.JobData.owner.accountingid,
FirstName: socket.JobData.ownr_fn,
LastName: socket.JobData.ownr_co_nm ? socket.JobData.ownr_co_nm : socket.JobData.ownr_ln,
PhoneNumber: socket.JobData.ownr_ph1,
EmailAddress: socket.JobData.ownr_ea
// ModifiedSince: "0001-01-01T00:00:00.0000000Z",
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
// ContactIdList: ["00000000000000000000000000000000"],
// IncludeInactive: false,
// PayableAccount: "String",
// ReceivableAccount: "String",
// DriverLicense: "String",
// ZipCode: "String",
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, CustomerGetResponse);
return CustomerGetResponse?.Contacts;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
throw new Error(error);
}
}
async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
try {
const { data: CustomerGetResponse } = await axios.post(
PBS_ENDPOINTS.ContactGet,
{
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
ContactId: CustomerRef
//ContactCode: socket.JobData.owner.accountingid,
//FirstName: socket.JobData.ownr_co_nm
// ? socket.JobData.ownr_co_nm
// : socket.JobData.ownr_fn,
//LastName: socket.JobData.ownr_ln,
//PhoneNumber: socket.JobData.ownr_ph1,
// EmailAddress: "String",
// ModifiedSince: "0001-01-01T00:00:00.0000000Z",
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
// ContactIdList: ["00000000000000000000000000000000"],
// IncludeInactive: false,
// PayableAccount: "String",
// ReceivableAccount: "String",
// DriverLicense: "String",
// ZipCode: "String",
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, CustomerGetResponse);
return CustomerGetResponse?.Contacts;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
throw new Error(error);
}
}
async function UpsertContactData(socket, selectedCustomerId) {
try {
const { data: ContactChangeResponse } = await axios.post(
PBS_ENDPOINTS.ContactChange,
{
ContactInfo: {
// Id: socket.JobData.owner.id,
...(selectedCustomerId ? { ContactId: selectedCustomerId } : {}),
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
Code: socket.JobData.owner.accountingid,
...(socket.JobData.ownr_co_nm
? {
//LastName: socket.JobData.ownr_ln,
FirstName: socket.JobData.ownr_co_nm,
IsBusiness: true
}
: {
LastName: socket.JobData.ownr_ln,
FirstName: socket.JobData.ownr_fn,
IsBusiness: false
}),
//Salutation: "String",
//MiddleName: "String",
//ContactName: "String",
IsInactive: false,
//ApartmentNumber: "String",
Address: socket.JobData.ownr_addr1,
City: socket.JobData.ownr_city,
//County: socket.JobData.ownr_addr1,
State: socket.JobData.ownr_st,
ZipCode: socket.JobData.ownr_zip,
//BusinessPhone: "String",
//BusinessPhoneExt: "String",
HomePhone: socket.JobData.ownr_ph2,
CellPhone: socket.JobData.ownr_ph1,
//BusinessPhoneRawReverse: "String",
//HomePhoneRawReverse: "String",
//CellPhoneRawReverse: "String",
//FaxNumber: "String",
EmailAddress: socket.JobData.ownr_ea
//Notes: "String",
//CriticalMemo: "String",
//BirthDate: "0001-01-01T00:00:00.0000000Z",
// Gender: "String",
// DriverLicense: "String",
//PreferredContactMethods: ["String"],
// LastUpdate: "0001-01-01T00:00:00.0000000Z",
// CustomFields: [{ Key: "String", Value: "String", Type: "String" }],
// FleetType: "String",
// CommunicationPreferences: {
// Email: "String",
// Phone: "String",
// TextMessage: "String",
// Letter: "String",
// Preferred: "String",
// },
// SalesRepRef: "00000000000000000000000000000000",
// Language: "String",
// PayableAccount: "String",
// ReceivableAccount: "String",
// IsStatic: false,
// PrimaryImageRef: "00000000000000000000000000000000",
// PayableAccounts: [{ SerialNumber: "String", Account: "String" }],
// ReceivableAccounts: [{ SerialNumber: "String", Account: "String" }],
// ManufacturerLoyaltyNumber: "String",
},
IsAsynchronous: false
// UserRequest: "String",
// UserRef: "00000000000000000000000000000000",
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, ContactChangeResponse);
return ContactChangeResponse;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
throw new Error(error);
}
}
async function UpsertVehicleData(socket, ownerRef) {
try {
const { data: VehicleChangeResponse } = await axios.post(
PBS_ENDPOINTS.VehicleChange,
{
VehicleInfo: {
//Id: "string/00000000-0000-0000-0000-000000000000",
//VehicleId: "00000000000000000000000000000000",
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
//StockNumber: "String",
VIN: socket.JobData.v_vin,
LicenseNumber: socket.JobData.plate_no,
//FleetNumber: "String",
//Status: "String",
OwnerRef: ownerRef, // "00000000000000000000000000000000",
// ModelNumber: socket.JobData.vehicle?.v_makecode,
Make: socket.JobData.v_make_desc,
Model: socket.JobData.v_model_desc,
Trim: socket.JobData.vehicle?.v_trimcode,
//VehicleType: "String",
Year: socket.JobData.v_model_yr,
Odometer: socket.JobData.kmout,
ExteriorColor: {
// Code: socket.JobData.v_color,
Description: socket.JobData.v_color
}
// InteriorColor: { Code: "String", Description: "String" },
//Engine: "String",
// Cylinders: "String",
// Transmission: "String",
// DriveWheel: "String",
// Fuel: "String",
// Weight: 0,
// InServiceDate: "0001-01-01T00:00:00.0000000Z",
// LastServiceDate: "0001-01-01T00:00:00.0000000Z",
// LastServiceMileage: 0,
// Lot: "String",
// LotDescription: "String",
// Category: "String",
// Options: [
// {
// Group: "String",
// Code: "String",
// Description: "String",
// AdditionalInfo: "String",
// Price: 0,
// Cost: 0,
// Residual: 0,
// },
// ],
// Refurbishments: [
// {
// ReferenceNumber: "String",
// Description: "String",
// Price: 0,
// Cost: 0,
// Date: "0001-01-01T00:00:00.0000000Z",
// ApplicationModel: "String",
// },
// ],
// Order: {
// InvoiceNumber: "String",
// Price: 0,
// Status: "String",
// Eta: "String",
// EstimatedCost: 0,
// OrderDate: "String",
// StatusDate: "String",
// IgnitionKeyCode: "String",
// DoorKeyCode: "String",
// Description: "String",
// LocationStatus: "String",
// LocationStatusDate: "0001-01-01T00:00:00.0000000Z",
// },
// MSR: 0,
// BaseMSR: 0,
// Retail: 0,
// DateReceived: "0001-01-01T00:00:00.0000000Z",
// InternetPrice: 0,
// Lotpack: 0,
// Holdback: 0,
// InternetNotes: "String",
// Notes: "String",
// CriticalMemo: "String",
// IsCertified: false,
// LastSaleDate: "0001-01-01T00:00:00.0000000Z",
// LastUpdate: "0001-01-01T00:00:00.0000000Z",
// AppraisedValue: 0,
// Warranties: [
// {
// Type: "String",
// CompanyName: "String",
// CoveragePlan: "String",
// Description: "String",
// Price: 0,
// Cost: 0,
// Term: "String",
// Deductible: 0,
// PolicyNumber: "String",
// StartDate: "String",
// StartMileage: 0,
// ExpirationDate: "String",
// ExpirationMileage: 0,
// },
// ],
// Freight: 0,
// Air: 0,
// Inventory: 0,
// IsInactive: false,
// CustomFields: [{ Key: "String", Value: "String", Type: "String" }],
// FloorPlanCode: "String",
// FloorPlanAmount: 0,
// Insurance: {
// Company: "String",
// Policy: "String",
// ExpiryDate: "0001-01-01T00:00:00.0000000Z",
// AgentName: "String",
// AgentPhoneNumber: "String",
// },
// Body: "String",
// ShortVIN: "String",
// AdditionalDrivers: ["00000000000000000000000000000000"],
// OrderDetails: { Distributor: "String" },
// PrimaryImageRef: "00000000000000000000000000000000",
// Hold: {
// VehicleRef: "00000000000000000000000000000000",
// HoldFrom: "0001-01-01T00:00:00.0000000Z",
// HoldUntil: "0001-01-01T00:00:00.0000000Z",
// UserRef: "00000000000000000000000000000000",
// ContactRef: "00000000000000000000000000000000",
// Comments: "String",
// },
// SeatingCapacity: "String",
// DeliveryDate: "0001-01-01T00:00:00.0000000Z",
// WarrantyExpiry: "0001-01-01T00:00:00.0000000Z",
// IsConditionallySold: false,
// SalesDivision: 0,
// StyleRef: "String",
},
IsAsynchronous: false
// UserRequest: "String",
// UserRef: "00000000000000000000000000000000",
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, VehicleChangeResponse);
return VehicleChangeResponse;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
throw new Error(error);
}
}
async function GetVehicleData(socket, ownerRef) {
try {
const {
data: { Vehicles }
} = await axios.post(
PBS_ENDPOINTS.VehicleGet,
{
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
// "VehicleId": "00000000000000000000000000000000",
// "Year": "String",
// "YearFrom": "String",
// "YearTo": "String",
// "Make": "String",
// "Model": "String",
// "Trim": "String",
// "ModelNumber": "String",
// "StockNumber": "String",
VIN: socket.JobData.v_vin
// "LicenseNumber": "String",
// "Lot": "String",
// "Status": "String",
// "StatusList": ["String"],
// "OwnerRef": "00000000000000000000000000000000",
// "ModifiedSince": "0001-01-01T00:00:00.0000000Z",
// "ModifiedUntil": "0001-01-01T00:00:00.0000000Z",
// "LastSaleSince": "0001-01-01T00:00:00.0000000Z",
// "VehicleIDList": ["00000000000000000000000000000000"],
// "IncludeInactive": false,
// "IncludeBuildVehicles": false,
// "IncludeBlankLot": false,
// "ShortVIN": "String",
// "ResultLimit": 0,
// "LotAccessDivisions": [0],
// "OdometerTo": 0,
// "OdometerFrom": 0
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, Vehicles);
if (Vehicles.length === 1) {
return Vehicles[0];
} else {
WsLogger.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`);
}
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
throw new Error(error);
}
}
async function InsertAccountPostingData(socket) {
try {
const allocations = await CalculateAllocations(socket, socket.JobData.id);
const wips = [];
allocations.forEach((alloc) => {
//Add the sale item from each allocation.
if (alloc.sale.getAmount() > 0 && !alloc.tax) {
const item = {
Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item);
}
//Add the cost Item.
if (alloc.cost.getAmount() > 0 && !alloc.tax) {
const item = {
Account: alloc.costCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.cost.toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item);
const itemWip = {
Account: alloc.costCenter.dms_wip_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.cost.multiply(-1).toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(itemWip);
//Add to the WIP account.
}
if (alloc.tax) {
if (alloc.sale.getAmount() > 0) {
const item2 = {
Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item2);
}
}
});
socket.txEnvelope.payers.forEach((payer) => {
const item = {
Account: payer.dms_acctnumber,
ControlNumber: payer.controlnumber,
Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat("0.0"),
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item);
});
socket.transWips = wips;
const { data: AccountPostingChange } = await axios.post(
PBS_ENDPOINTS.AccountingPostingChange,
{
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
Posting: {
Reference: socket.JobData.ro_number,
JournalCode: socket.txEnvelope.journal,
//Sample TransactionDate: "0001-01-01T00:00:00.0000000Z",
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(),
Description: socket.txEnvelope.story,
//AdditionalInfo: "String",
Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
Lines: wips
}
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, AccountPostingChange);
return AccountPostingChange;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
throw new Error(error);
}
}
async function MarkJobExported(socket, jobid) {
WsLogger.createLogEvent(socket, "INFO", `Marking job as exported for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_JOB_EXPORTED, {
jobId: jobid,
job: {
status: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
date_exported: new Date()
},
log: {
bodyshopid: socket.JobData.bodyshop.id,
jobid: jobid,
successful: true,
useremail: socket.user.email,
metadata: socket.transWips
},
bill: {
exported: true,
exported_at: new Date()
}
});
return result;
}
async function InsertFailedExportLog(socket, error) {
try {
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.INSERT_EXPORT_LOG, {
logs: [{
bodyshopid: socket.JobData.bodyshop.id,
jobid: socket.JobData.id,
successful: false,
message: JSON.stringify(error),
useremail: socket.user.email
}]
});
return result;
} catch (error2) {
WsLogger.createLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error} - ${JSON.stringify(error2)}`);
}
}
async function CreateRepairOrderInPBS(socket) {
try {
const { RepairOrders } = await RepairOrderGet(socket);
if (RepairOrders.length === 0) {
const InsertedRepairOrder = await RepairOrderChange(socket);
socket.InsertedRepairOrder = InsertedRepairOrder;
WsLogger.createLogEvent(socket, "INFO", `No repair orders found for vehicle. Inserting record.`);
} else if (RepairOrders.length > 0) {
//Find out if it's a matching RO.
//This logic is used because the integration will simply add another line to an open RO if it exists.
const matchingRo = RepairOrders.find((ro) =>
ro.Memo?.toLowerCase()?.includes(socket.JobData.ro_number.toLowerCase())
);
if (!matchingRo) {
WsLogger.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`);
const InsertedRepairOrder = await RepairOrderChange(socket);
socket.InsertedRepairOrder = InsertedRepairOrder;
} else {
WsLogger.createLogEvent(
socket,
"WARN",
`Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`
);
}
}
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in CreateRepairOrderInPBS - ${error} - ${JSON.stringify(error)}`);
}
}
async function RepairOrderGet(socket) {
try {
const { data: RepairOrderGet } = await axios.post(
PBS_ENDPOINTS.RepairOrderGet,
{
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
//"RepairOrderId": "374728766",
//"RepairOrderNumber": "4" || socket.JobData.ro_number,
//"RawRepairOrderNumber": socket.JobData.ro_number,
// "Tag": "String",
//"ContactRef": socket.contactRef,
// "ContactRefList": ["00000000000000000000000000000000"],
VehicleRef: socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId
// "VehicleRefList": ["00000000000000000000000000000000"],
// "Status": "String",
// "CashieredSince": "0001-01-01T00:00:00.0000000Z",
// "CashieredUntil": "0001-01-01T00:00:00.0000000Z",
// "OpenDateSince": "0001-01-01T00:00:00.0000000Z",
// "OpenDateUntil": "0001-01-01T00:00:00.0000000Z",
//"ModifiedSince": "2025-01-01T00:00:00.0000000Z",
// "ModifiedUntil": "0001-01-01T00:00:00.0000000Z",
// "Shop": "String"
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, RepairOrderGet);
return RepairOrderGet;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
throw new Error(error);
}
}
async function RepairOrderChange(socket) {
try {
const { data: RepairOrderChangeResponse } = await axios.post(
PBS_ENDPOINTS.RepairOrderChange,
{
//Additional details at https://partnerhub.pbsdealers.com/json/metadata?op=RepairOrderChange
RepairOrderInfo: {
//"Id": "string/00000000-0000-0000-0000-000000000000",
//"RepairOrderId": "00000000000000000000000000000000",
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
RepairOrderNumber: "00000000000000000000000000000000", //This helps force a new RO.
RawRepairOrderNumber: "00000000000000000000000000000000",
// "RepairOrderNumber": socket.JobData.ro_number, //These 2 values are ignored as confirmed by PBS.
// "RawRepairOrderNumber": socket.JobData.ro_number,
DateOpened: moment(),
// "DateOpenedUTC": "0001-01-01T00:00:00.0000000Z",
// "DateCashiered": "0001-01-01T00:00:00.0000000Z",
// "DateCashieredUTC": "0001-01-01T00:00:00.0000000Z",
DatePromised: socket.JobData.scheduled_completion,
// "DatePromisedUTC": "0001-01-01T00:00:00.0000000Z",
DateVehicleCompleted: socket.JobData.actual_completion,
// "DateCustomerNotified": "0001-01-01T00:00:00.0000000Z",
// "CSR": "String",
// "CSRRef": "00000000000000000000000000000000",
// "BookingUser": "String",
// "BookingUserRef": "00000000000000000000000000000000",
ContactRef: socket.ownerRef?.ReferenceId || socket.ownerRef?.ContactId,
VehicleRef: socket.vehicleRef?.ReferenceId || socket.vehicleRef?.VehicleId,
MileageIn: socket.JobData.km_in,
Tag: "BODYSHOP",
//"Status": "CLOSED", //Values here do not impact the status. Confirmed by PBS support.
Requests: [
{
// "RepairOrderRequestRef": "b1842ecad62c4279bbc2fef4f6bf6cde",
// "RepairOrderRequestId": 1,
// "CSR": "PBS",
// "CSRRef": "1ce12ac692564e94bda955d529ee911a",
// "Skill": "GEN",
RequestCode: "MISC",
RequestDescription: `VEHICLE REPAIRED AT BODYSHOP. PLEASE REFERENCE IMEX SHOP MANAGEMENT SYSTEM. ${socket.txEnvelope.story}`,
Status: "Completed",
// "TechRef": "00000000000000000000000000000000",
AllowedHours: 0,
EstimateLabour: 0,
EstimateParts: 0,
ComeBack: false,
AddedOperation: true,
PartLines: [],
PartRequestLines: [],
LabourLines: [],
SubletLines: [],
TimePunches: [],
Summary: {
Labour: 0,
Parts: 0,
OilGas: 0,
SubletTow: 0,
Misc: 0,
Environment: 0,
ShopSupplies: 0,
Freight: 0,
WarrantyDeductible: 0,
Discount: 0,
SubTotal: 0,
Tax1: 0,
Tax2: 0,
InvoiceTotal: 0,
CustomerDeductible: 0,
GrandTotal: 0,
LabourDiscount: 0,
PartDiscount: 0,
ServiceFeeTotal: 0,
OEMDiscount: 0
},
LineType: "RequestLine"
}
],
Memo: socket.txEnvelope.story
},
IsAsynchronous: false
// "UserRequest": "String",
// "UserRef": "00000000000000000000000000000000"
},
{ auth: PBS_CREDENTIALS, socket }
);
CheckForErrors(socket, RepairOrderChangeResponse);
return RepairOrderChangeResponse;
} catch (error) {
WsLogger.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
throw new Error(error);
}
}