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 }` - : "" + : "" } }`;