const path = require("path"); require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); 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)}`; //console.log(printable); CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data); return 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); return x; }); 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}`); const JobData = await QueryJobData(socket, jobid); socket.JobData = JobData; CdkBase.createLogEvent(socket, "DEBUG", `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 { if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) { CdkBase.createLogEvent(socket, "DEBUG", `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 || "" } ${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); } else { CdkBase.createLogEvent( socket, "DEBUG", `Contact and Vehicle updates disabled. Skipping to accounting data insert.` ); } CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`); CdkBase.createLogEvent(socket, "DEBUG", `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); socket.emit("export-success", socket.JobData.id); } else { CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`); } } catch (error) { CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`); await InsertFailedExportLog(socket, error); } }; async function CheckForErrors(socket, response) { if (response.WasSuccessful === undefined || response.WasSuccessful === true) { CdkBase.createLogEvent(socket, "DEBUG", `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)}`); } } exports.CheckForErrors = CheckForErrors; async function QueryJobData(socket, jobid) { CdkBase.createLogEvent(socket, "DEBUG", `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)}`); return result.jobs_by_pk; } async function QueryVehicleFromDms(socket) { try { if (!socket.JobData.v_vin) return null; const { data: VehicleGetResponse, request } = 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 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, "DEBUG", `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)}`); } }