diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index 36ec1a0c9..86da3ae37 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -5305,6 +5305,27 @@
+
+ ro_posting
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
sendmaterialscosting
false
diff --git a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx
index 966cadf13..3445c88c1 100644
--- a/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx
+++ b/client/src/components/dms-allocations-summary/dms-allocations-summary.component.jsx
@@ -92,6 +92,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
rowKey="center"
dataSource={allocationsSummary}
locale={{ emptyText: t("dms.labels.refreshallocations") }}
+ scroll={{ x: true }}
summary={() => {
const totals =
allocationsSummary &&
diff --git a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx
index ccae00d4f..6e7a4edfd 100644
--- a/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx
+++ b/client/src/components/shop-info/shop-info.responsibilitycenters.component.jsx
@@ -138,6 +138,15 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
)}
+ {bodyshop.pbs_serialnumber && (
+
+
+
+ )}
{bodyshop.pbs_serialnumber && (
{
...x.headers[x.method],
...x.headers
};
- const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
- x.url
- } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
- //console.log(printable);
+ const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${x.url
+ } | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
+ //logRequestToFile(printable);
- CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
+ CdkBase.createJsonEvent(socket, "DEBUG", `Raw Request: ${printable}`, x.data);
return x;
});
@@ -32,23 +31,39 @@ axios.interceptors.request.use((x) => {
axios.interceptors.response.use((x) => {
const socket = x.config.socket;
- const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
- //console.log(printable);
- CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
+ const printable = `${new Date()} | Response: ${x.status} ${x.statusText} |${JSON.stringify(x.data)}`;
+ //logRequestToFile(printable);
+ CdkBase.createJsonEvent(socket, "DEBUG", `Raw Response: ${printable}`, x.data);
return x;
});
+const fs = require('fs');
+const path = require("path");
+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);
+ }
+}
+
+
exports.default = async function (socket, { txEnvelope, jobid }) {
socket.logEvents = [];
socket.recordid = jobid;
socket.txEnvelope = txEnvelope;
try {
- CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);
+ CdkBase.createLogEvent(socket, "INFO", `Received Job export request for id ${jobid}`);
const JobData = await QueryJobData(socket, jobid);
socket.JobData = JobData;
- CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`);
+ CdkBase.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.
@@ -69,42 +84,52 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) {
try {
- if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) {
- CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`);
+ socket.selectedCustomerId = selectedCustomerId;
+ if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle !== true) {
+ CdkBase.createLogEvent(socket, "INFO", `User selected customer ${selectedCustomerId || "NEW"}`);
//Upsert the contact information as per Wafaa's Email.
CdkBase.createLogEvent(
socket,
- "DEBUG",
- `Upserting contact information to DMS for ${
- socket.JobData.ownr_fn || ""
+ "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);
-
- CdkBase.createLogEvent(socket, "DEBUG", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
- await UpsertVehicleData(socket, ownerRef.ReferenceId);
+ socket.ownerRef = ownerRef;
+ CdkBase.createLogEvent(socket, "INFO", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
+ const vehicleRef = await UpsertVehicleData(socket, ownerRef.ReferenceId);
+ socket.vehicleRef = vehicleRef;
} else {
CdkBase.createLogEvent(
socket,
- "DEBUG",
- `Contact and Vehicle updates disabled. Skipping to accounting data insert.`
+ "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;
}
- CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`);
- CdkBase.createLogEvent(socket, "DEBUG", `Inserting accounting posting data..`);
+ CdkBase.createLogEvent(socket, "INFO", `Inserting accounting posting data..`);
const insertResponse = await InsertAccountPostingData(socket);
if (insertResponse.WasSuccessful) {
- CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
- await MarkJobExported(socket, socket.JobData.id);
+ if (socket.JobData.bodyshop.pbs_configuration.ro_posting) {
+ await CreateRepairOrderInPBS(socket, socket.ownerRef, socket.vehicleRef)
+ }
+ CdkBase.createLogEvent(socket, "INFO", `Marking job as exported.`);
+ await MarkJobExported(socket, socket.JobData.id);
socket.emit("export-success", socket.JobData.id);
} else {
CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
}
} catch (error) {
- CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`);
+ CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
await InsertFailedExportLog(socket, error);
}
};
@@ -112,22 +137,22 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selecte
// Was Successful
async function CheckForErrors(socket, response) {
if (response.WasSuccessful === undefined || response.WasSuccessful === true) {
- CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`);
+ CdkBase.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`);
} else {
CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
- CdkBase.createLogEvent(socket, "SILLY", `Error received from DMS: ${JSON.stringify(response)}`);
+ CdkBase.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`);
}
}
exports.CheckForErrors = CheckForErrors;
async function QueryJobData(socket, jobid) {
- CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
+ CdkBase.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 });
- CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
+ CdkBase.createLogEvent(socket, "DEBUG", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
@@ -247,15 +272,15 @@ async function UpsertContactData(socket, selectedCustomerId) {
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_co_nm,
+ IsBusiness: true
+ }
: {
- LastName: socket.JobData.ownr_ln,
- FirstName: socket.JobData.ownr_fn,
- IsBusiness: false
- }),
+ LastName: socket.JobData.ownr_ln,
+ FirstName: socket.JobData.ownr_fn,
+ IsBusiness: false
+ }),
//Salutation: "String",
//MiddleName: "String",
@@ -332,7 +357,7 @@ async function UpsertVehicleData(socket, ownerRef) {
//FleetNumber: "String",
//Status: "String",
OwnerRef: ownerRef, // "00000000000000000000000000000000",
- ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode,
+ // ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode,
Make: socket.JobData.v_make_desc,
Model: socket.JobData.v_model_desc,
Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode,
@@ -340,7 +365,7 @@ async function UpsertVehicleData(socket, ownerRef) {
Year: socket.JobData.v_model_yr,
Odometer: socket.JobData.kmout,
ExteriorColor: {
- Code: socket.JobData.v_color,
+ // Code: socket.JobData.v_color,
Description: socket.JobData.v_color
}
// InteriorColor: { Code: "String", Description: "String" },
@@ -470,6 +495,57 @@ async function UpsertVehicleData(socket, ownerRef) {
}
}
+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 {
+ CdkBase.createLogEvent(socket, "ERROR", `Error in Getting Vehicle Data - ${Vehicles.length} vehicle(s) found`);
+ }
+ } catch (error) {
+ CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
+ throw new Error(error);
+ }
+}
+
+
async function InsertAccountPostingData(socket) {
try {
const allocations = await CalculateAllocations(socket, socket.JobData.id);
@@ -572,7 +648,7 @@ async function InsertAccountPostingData(socket) {
}
async function MarkJobExported(socket, jobid) {
- CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);
+ CdkBase.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}` })
@@ -618,3 +694,158 @@ async function InsertFailedExportLog(socket, error) {
CdkBase.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;
+ CdkBase.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) {
+ CdkBase.createLogEvent(socket, "INFO", `ROs found for vehicle, but none match. Inserting record.`);
+ const InsertedRepairOrder = await RepairOrderChange(socket)
+ socket.InsertedRepairOrder = InsertedRepairOrder;
+ } else {
+ CdkBase.createLogEvent(socket, "WARN", `Repair order appears to already exist in PBS. ${matchingRo.RepairOrderNumber}`);
+ }
+ }
+ } catch (error) {
+ CdkBase.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) {
+ CdkBase.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) {
+ CdkBase.createLogEvent(socket, "ERROR", `Error in RepairOrderChange - ${error}`);
+ throw new Error(error);
+ }
+}
\ No newline at end of file
diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js
index 9a3bb1d6e..af62e847a 100644
--- a/server/graphql-client/queries.js
+++ b/server/graphql-client/queries.js
@@ -420,6 +420,8 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
v_make_desc
v_color
ca_customer_gst
+ scheduled_completion
+ actual_completion
vehicle {
v_trimcode
v_makecode
@@ -2159,18 +2161,16 @@ exports.UPDATE_OLD_TRANSITION = `mutation UPDATE_OLD_TRANSITION($jobid: uuid!, $
exports.INSERT_NEW_TRANSITION = (
includeOldTransition
-) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${
- includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : ""
-}) {
+) => `mutation INSERT_NEW_TRANSITION($newTransition: transitions_insert_input!, ${includeOldTransition ? `$oldTransitionId: uuid!, $duration: numeric` : ""
+ }) {
insert_transitions_one(object: $newTransition) {
id
}
- ${
- includeOldTransition
- ? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) {
+ ${includeOldTransition
+ ? `update_transitions(where: {id: {_eq: $oldTransitionId}}, _set: {duration: $duration}) {
affected_rows
}`
- : ""
+ : ""
}
}`;