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 { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl"); const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").default; const CdkBase = require("../../web-sockets/web-socket"); 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); CdkBase.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); 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, "INFO", `Received Job export request for id ${jobid}`); const JobData = await QueryJobData(socket, jobid); socket.JobData = JobData; 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. if (socket.DmsVeh && 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) { CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`); } }; exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) { try { 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, "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; 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, "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, "INFO", `Inserting accounting 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) } 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 PbsSelectedCustomer. ${error}`); await InsertFailedExportLog(socket, error); } }; // Was Successful async function CheckForErrors(socket, response) { if (response.WasSuccessful === undefined || response.WasSuccessful === true) { CdkBase.createLogEvent(socket, "INFO", `Successful response from DMS. ${response.Message || ""}`); } else { CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`); CdkBase.createLogEvent(socket, "DEBUG", `Error received from DMS: ${JSON.stringify(response)}`); } } exports.CheckForErrors = CheckForErrors; async function QueryJobData(socket, 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, "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) { CdkBase.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 && CustomerGetResponse.Contacts; } catch (error) { CdkBase.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 && CustomerGetResponse.Contacts; } catch (error) { CdkBase.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) { CdkBase.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 && 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, //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) { CdkBase.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 { 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); 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, TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z", 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) { CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`); throw new Error(error); } } async function MarkJobExported(socket, 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}` }) .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, { log: { bodyshopid: socket.JobData.bodyshop.id, jobid: socket.JobData.id, successful: false, message: JSON.stringify(error), useremail: socket.user.email } }); return result; } catch (error2) { 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); } }