const GraphQLClient = require("graphql-request").GraphQLClient; const CalculateAllocations = require("../cdk/cdk-calculate-allocations").default; const InstanceMgr = require("../utils/instanceMgr").default; const CreateFortellisLogEvent = require("./fortellis-logger"); const queries = require("../graphql-client/queries"); const { MakeFortellisCall, FortellisActions, getTransactionType, defaultFortellisTTL, FortellisCacheEnums, FortellisApiError } = require("./fortellis-helpers"); const _ = require("lodash"); const moment = require("moment-timezone"); const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g; // Helper function to handle FortellisApiError logging function handleFortellisApiError(socket, error, functionName, additionalDetails = {}) { if (error instanceof FortellisApiError) { CreateFortellisLogEvent(socket, "ERROR", `${functionName} failed: ${error.message}`, { reqId: error.reqId, url: error.url, apiName: error.apiName, errorData: error.errorData, errorStatus: error.errorStatus, errorStatusText: error.errorStatusText, functionName, ...additionalDetails }); CreateFortellisLogEvent( socket, "DEBUG", `${functionName} failed. , ${JSON.stringify({ reqId: error.reqId, url: error.url, apiName: error.apiName, errorData: error.errorData, errorStatus: error.errorStatus, errorStatusText: error.errorStatusText, functionName, ...additionalDetails })}` ); } else { CreateFortellisLogEvent(socket, "ERROR", `Error in ${functionName} - ${error.message}`, { error: error.message, stack: error.stack, functionName, ...additionalDetails }); } } async function FortellisJobExport({ socket, redisHelpers, txEnvelope, jobid }) { const { // setSessionData, // getSessionData, // addUserSocketMapping, // removeUserSocketMapping, // refreshUserSocketTTL, // getUserSocketMappingByBodyshop, setSessionTransactionData // getSessionTransactionData, // clearSessionTransactionData } = redisHelpers; try { CreateFortellisLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.txEnvelope, txEnvelope, defaultFortellisTTL ); const JobData = await QueryJobData({ socket, jobid }); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.JobData, JobData, defaultFortellisTTL ); CreateFortellisLogEvent(socket, "DEBUG", `{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`); const DMSVid = (await CalculateDmsVid({ socket, JobData, redisHelpers }))[0]; await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVid, DMSVid, defaultFortellisTTL ); let DMSVehCustomer; if (!DMSVid.newId) { CreateFortellisLogEvent(socket, "DEBUG", `{2.1} Querying the Vehicle using the DMSVid: ${DMSVid.vehiclesVehId}`); const DMSVeh = await QueryDmsVehicleById({ socket, redisHelpers, JobData, DMSVid }); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVeh, DMSVeh, defaultFortellisTTL ); const DMSVehCustomerFromVehicle = DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT"); if (DMSVehCustomerFromVehicle?.id && DMSVehCustomerFromVehicle.id.value) { CreateFortellisLogEvent( socket, "DEBUG", `{2.2} Querying the Customer using the ID from DMSVeh: ${DMSVehCustomerFromVehicle.id.value}` ); DMSVehCustomer = await QueryDmsCustomerById({ socket, redisHelpers, JobData, CustomerId: DMSVehCustomerFromVehicle.id.value }); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVehCustomer, DMSVehCustomer, defaultFortellisTTL ); } } CreateFortellisLogEvent(socket, "DEBUG", `{2.3} Querying the Customer using the name.`); const DMSCustList = await QueryDmsCustomerByName({ socket, redisHelpers, JobData }); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCustList, DMSCustList, defaultFortellisTTL ); socket.emit("fortellis-select-customer", [ ...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []), ...DMSCustList ]); } catch (error) { CreateFortellisLogEvent(socket, "ERROR", `Error in FortellisJobExport - ${error} `, { error: error.message, stack: error.stack }); } } async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustomerId, jobid }) { const { // setSessionData, // getSessionData, // addUserSocketMapping, // removeUserSocketMapping, // refreshUserSocketTTL, // getUserSocketMappingByBodyshop, setSessionTransactionData, getSessionTransactionData //clearSessionTransactionData } = redisHelpers; await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.selectedCustomerId, selectedCustomerId, defaultFortellisTTL ); const JobData = await getSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.JobData); const txEnvelope = await getSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.txEnvelope ); const DMSVid = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.DMSVid ); try { let DMSCust; if (selectedCustomerId) { CreateFortellisLogEvent(socket, "DEBUG", `{3.1} Querying the Customer using Customer ID: ${selectedCustomerId}`); //Get cust list from Redis. Return the item const DMSCustList = await getSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCustList ); const existingCustomerInDMSCustList = DMSCustList.find((c) => c.customerId === selectedCustomerId); DMSCust = existingCustomerInDMSCustList || { customerId: selectedCustomerId //This is the fall back in case it is the generic customer. }; await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCust, DMSCust, defaultFortellisTTL ); } else { CreateFortellisLogEvent(socket, "DEBUG", `{3.2} Creating new customer.`); const DMSCustomerInsertResponse = await InsertDmsCustomer({ socket, redisHelpers, JobData }); DMSCust = { customerId: DMSCustomerInsertResponse.data }; await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSCust, DMSCust, defaultFortellisTTL ); } let DMSVeh; if (DMSVid.newId) { CreateFortellisLogEvent(socket, "DEBUG", `{4.1} Inserting new vehicle with ID: ID ${DMSVid.vehiclesVehId}`); DMSVeh = await InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMSVid, DMSCust }); } else { DMSVeh = await getSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVeh ) CreateFortellisLogEvent(socket, "DEBUG", `{4.3} Updating Existing Vehicle to associate to owner.`); //Check to see if the vehicle needs to be updated - i.e. the owner is not the selected customer. if (!DMSVeh?.owners.find((o) => o.id.value === DMSCust.customerId && o.id.assigningPartyId === "CURRENT")) { DMSVeh = await UpdateDmsVehicle({ socket, redisHelpers, JobData, DMSVeh, DMSCust, selectedCustomerId: selectedCustomerId || DMSCust.customerId, txEnvelope }); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVeh, DMSVeh, defaultFortellisTTL ); } } // const DMSVehHistory = await InsertServiceVehicleHistory({ socket, redisHelpers, JobData }); // await setSessionTransactionData(socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVehHistory, DMSVehHistory, defaultFortellisTTL); CreateFortellisLogEvent(socket, "DEBUG", `{5} Creating Transaction header with Dms Start WIP`); const DMSTransHeader = await InsertDmsStartWip({ socket, redisHelpers, JobData }); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSTransHeader, DMSTransHeader, defaultFortellisTTL ); CreateFortellisLogEvent(socket, "DEBUG", `{5.1} Creating Transaction with ID ${DMSTransHeader.transID}`); if (DMSTransHeader.rtnCode === "0") { try { CreateFortellisLogEvent(socket, "DEBUG", `{6} Attempting to post Transaction with ID ${DMSTransHeader.transID}`); const DmsBatchTxnPost = await PostDmsBatchWip({ socket, redisHelpers, JobData }); // 2 in 1 call that includes a post and the transactions. await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DmsBatchTxnPost, DmsBatchTxnPost, defaultFortellisTTL ); if (DmsBatchTxnPost.rtnCode === "0") { //TODO: Validate this is a string and not # //something CreateFortellisLogEvent(socket, "DEBUG", `{6} Successfully posted transaction to DMS.`); await MarkJobExported({ socket, jobid: JobData.id, JobData }); try { CreateFortellisLogEvent(socket, "DEBUG", `{7} Updating Service Vehicle History.`); const DMSVehHistory = await InsertServiceVehicleHistory({ socket, redisHelpers, JobData }); await setSessionTransactionData( socket.id, getTransactionType(jobid), FortellisCacheEnums.DMSVehHistory, DMSVehHistory, defaultFortellisTTL ); } catch (error) { CreateFortellisLogEvent(socket, "ERROR", `{7.1} Error posting vehicle service history. ${error.message}`); } socket.emit("export-success", JobData.id); } else { //There was something wrong. Throw an error to trigger clean up. //throw new Error("Error posting DMS Batch Transaction"); } } catch (error) { //Clean up the transaction and insert a faild error code // //Get the error code CreateFortellisLogEvent(socket, "DEBUG", `{6.1} Getting errors for Transaction ID ${DMSTransHeader.transID}`); const DmsError = await QueryDmsErrWip({ socket, redisHelpers, JobData }); // //Delete the transaction CreateFortellisLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${DMSTransHeader.transID}`); await DeleteDmsWip({ socket, redisHelpers, JobData }); DmsError.errMsg.map( (e) => e !== null && e !== "" && CreateFortellisLogEvent(socket, "ERROR", `Error encountered in posting transaction => ${e} `) ); await InsertFailedExportLog({ socket, JobData, error: DmsError.errMsg }); } } } catch (error) { CreateFortellisLogEvent(socket, "ERROR", `Error in FortellisSelectedCustomer - ${error} `, { error: error.message, stack: error.stack, data: error.errorData }); await InsertFailedExportLog({ socket, JobData, error: error.errorData?.issues || [JSON.stringify(error.errorData)] }); } finally { //Ensure we always insert logEvents //GQL to insert logevents. //CdkBase.createLogEvent(socket, "DEBUG", `Capturing log events to database.`); } } exports.FortellisSelectedCustomer = FortellisSelectedCustomer; async function QueryJobData({ socket, 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_CDK_EXPORT, { id: jobid }); return result.jobs_by_pk; } async function CalculateDmsVid({ socket, JobData, redisHelpers }) { try { const result = await MakeFortellisCall({ ...FortellisActions.GetVehicleId, requestPathParams: JobData.v_vin, headers: {}, redisHelpers, socket, jobid: JobData.id, body: {} }); return result; } catch (error) { handleFortellisApiError(socket, error, "CalculateDmsVid", { vin: JobData.v_vin, jobId: JobData.id }); throw error; } } async function QueryDmsVehicleById({ socket, redisHelpers, JobData, DMSVid }) { try { const result = await MakeFortellisCall({ ...FortellisActions.GetVehicleById, requestPathParams: DMSVid.vehiclesVehId, headers: {}, redisHelpers, socket, jobid: JobData.id, body: {} }); return result; } catch (error) { handleFortellisApiError(socket, error, "QueryDmsVehicleById", { vehicleId: DMSVid.vehiclesVehId, jobId: JobData.id }); throw error; } } async function QueryDmsCustomerById({ socket, redisHelpers, JobData, CustomerId }) { try { const result = await MakeFortellisCall({ ...FortellisActions.ReadCustomer, requestPathParams: CustomerId, headers: {}, redisHelpers, socket, jobid: JobData.id, body: {} }); return result.data; } catch (error) { handleFortellisApiError(socket, error, "QueryDmsCustomerById", { customerId: CustomerId, jobId: JobData.id }); throw error; } } async function QueryDmsCustomerByName({ socket, redisHelpers, JobData }) { const ownerName = JobData.ownr_co_nm && JobData.ownr_co_nm.trim() !== "" //? [["firstName", JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase()]] // Commented out until we receive direction. ? [["phone", JobData.ownr_ph1?.replace(replaceSpecialRegex, "")]] : [ ["firstName", JobData.ownr_fn?.replace(/[^a-zA-Z-]/g, "").toUpperCase()], ["lastName", JobData.ownr_ln?.replace(/[^a-zA-Z-]/g, "").toUpperCase()] ]; try { const result = await MakeFortellisCall({ ...FortellisActions.QueryCustomerByName, requestSearchParams: ownerName, headers: {}, redisHelpers, socket, jobid: JobData.id, body: {} }); return result.data; } catch (error) { handleFortellisApiError(socket, error, "QueryDmsCustomerByName", { searchParams: ownerName, jobId: JobData.id }); throw error; } } async function InsertDmsCustomer({ socket, redisHelpers, JobData }) { try { const isBusiness = (JobData.ownr_co_nm && JobData.ownr_co_nm.replace(replaceSpecialRegex, "").trim() !== "") const result = await MakeFortellisCall({ ...FortellisActions.CreateCustomer, headers: {}, redisHelpers, socket, jobid: JobData.id, body: { customerType: isBusiness ? "BUSINESS" : "INDIVIDUAL", ...isBusiness ? { companyName: JobData.ownr_co_nm && JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase(), secondaryCustomerName: { //lastName: JobData.ownr_co_nm && JobData.ownr_co_nm.replace(replaceSpecialRegex, "").toUpperCase() } } : { customerName: { //"suffix": "Mr.", firstName: JobData.ownr_fn && JobData.ownr_fn.replace(/[^a-zA-Z-]/g, "").toUpperCase(), //"middleName": "", lastName: JobData.ownr_ln && JobData.ownr_ln.replace(/[^a-zA-Z-]/g, "").toUpperCase() //"title": "", //"nickName": "" } }, postalAddress: { addressLine1: JobData.ownr_addr1?.replace(replaceSpecialRegex, "").trim().toUpperCase(), addressLine2: JobData.ownr_addr2?.replace(replaceSpecialRegex, "").trim().toUpperCase(), city: JobData.ownr_city?.replace(replaceSpecialRegex, "").trim().toUpperCase(), postalCode: InstanceMgr({ imex: JobData.ownr_zip && JobData.ownr_zip.toUpperCase().replace(/\W/g, "").replace(/(...)/, "$1 "), rome: JobData.ownr_zip }), state: JobData.ownr_st?.replace(replaceSpecialRegex, "").trim().toUpperCase(), country: JobData.ownr_ctry?.replace(replaceSpecialRegex, "").trim().toUpperCase(), //"territory": "" }, // "birthDate": { // "day": "15", // "month": "07", // "year": "1979" // }, //"gender": "M", //"language": "English", contactMethods: { phones: [ { //"uuid": "", number: JobData.ownr_ph1?.replace(replaceSpecialRegex, ""), type: "HOME" // "doNotCallIndicator": true, // "doNotCallIndicatorDate": `null, // "doNotCallRegistrySource": "", // "isOnDoNotCallRegistry": false, // "isPrimary": false, // "isPreferred": false, // "isTextMessageAllowed": false, // "textMessageCarrier": "", // "optInDate": null, // "optInRequestedDate": null, // "preferredDay": "", // "preferredTime": "" } // { // "uuid": "", // "number": "6666666666", // "type": "MOBILE", // "doNotCallIndicator": true, // "doNotCallIndicatorDate": null, // "doNotCallRegistrySource": "", // "isOnDoNotCallRegistry": false, // "isPrimary": true, // "isPreferred": true, // "isTextMessageAllowed": false, // "textMessageCarrier": "", // "optInDate": null, // "optInRequestedDate": null, // "preferredDay": "", // "preferredTime": "" // } ], emailAddresses: [ ...(!_.isEmpty(JobData.ownr_ea) ? [ { //"uuid": "", address: JobData.ownr_ea.toUpperCase(), type: "PERSONAL" // "doNotEmailSource": "", // "doNotEmail": false, // "isPreferred": true, // "transactionEmailNotificationOptIn": false, // "optInRequestDate": null, // "optInDate": null } ] : []) // { // "uuid": "", // "address": "jilldoe@test.com", // "type": "WORK", // "doNotEmailSource": "", // "doNotEmail": false, // "isPreferred": false, // "transactionEmailNotificationOptIn": false, // "optInRequestDate": null, // "optInDate": null // } ] } // // "doNotContact": false, // // "optOutDate": "", // // "optOutTime": "", // // "optOutFlag": false, // // "isDeleteDataFlag": false, // // "deleteDataDate": "", // // "deleteDataTime": "", // // "blockMailFlag": true, // // "dateAdded": "", // // "employer": "employer", // "insurance": { // "policy": { // "effectiveDate": "2022-01-01", // "expirationDate": "2023-01-01", // "number": "12345", // "verifiedBy": "Agent", // "verifiedDate": "2023-01-01", // "insPolicyCollisionDed": "", // "insPolicyComprehensiveDed": "", // "insPolicyFireTheftDed": "" // }, // "insuranceAgency": { // "agencyName": "InsAgency", // "agentName": "agent", // "phoneNumber": "9999999999", // "postalAddress": { // "addressLine1": "999 Main St", // "addressLine2": "Suite 999", // "city": "Austin", // "state": "TX", // "postalCode": "78750", // "county": "Travis", // "country": "USA" // } // }, // "insuranceCompany": { // "name": "InsCompany", // "phoneNumber": "8888888888", // "postalAddress": { // "addressLine1": "888 Main St", // "addressLine2": "Suite 888", // "city": "Austin", // "state": "TX", // "postalCode": "78750", // "county": "Travis", // "country": "USA" // } // } // }, // "specialInstructions": [ // { // "lineNuber": "1", // "specialInstruction": "specialInstruction1" // }, // { // "lineNuber": "2", // "specialInstruction": "specialInstruction2" // } // ], // "groupCode": "PT", // "priceCode": "5", // "roPriceCode": "5", // "taxCode": "3145", // "dealerLoyaltyIndicator": "PN612345", // "delCdeServiceNames": "99", // "deleteCode": "9999", // "fleetFlag": "1", // "dealerFields": [ // { // "lineNumber": null, // "dealerField": "Custom dealer field value 1" // }, // { // "lineNumber": null, // "dealerField": "Custom dealer field value 2" // }, // { // "lineNumber": null, // "dealerField": "Custom dealer field value 3" // } // ] } }); return result; } catch (error) { handleFortellisApiError(socket, error, "InsertDmsCustomer", { customerData: { firstName: JobData.ownr_fn, lastName: JobData.ownr_ln, companyName: JobData.ownr_co_nm }, jobId: JobData.id }); throw error; } } async function InsertDmsVehicle({ socket, redisHelpers, JobData, txEnvelope, DMSVid, DMSCust }) { try { const result = await MakeFortellisCall({ ...FortellisActions.InsertVehicle, requestSearchParams: {}, headers: {}, redisHelpers, socket, jobid: JobData.id, body: { dealer: { //company: JobData.bodyshop.cdk_configuration.srcco || "77", // "dealNumber": "", // "dealerAssignedNumber": "82268", // "dealerDefined1": "2WDSP", // "dealerDefined2": "33732.71", // "dealerDefined3": "", // "dealerDefined4": "G0901", // "dealerDefined5": "", // "dealerDefined6": "", // "dealerDefined7": "", // "dealerDefined8": "", // "dealerNumber": "", ...(txEnvelope.inservicedate && { inServiceDate: txEnvelope.dms_unsold === true ? "" : moment(txEnvelope.inservicedate) //.tz(JobData.bodyshop.timezone) .startOf("day") .toISOString() }), //"lastServiceDate": "2011-11-23", vehicleId: DMSVid.vehiclesVehId // "vehicleLocation": "", // "vehicleSoldDate": "2021-04-06", // "wholesaleVehicleInd": false }, // "manufacturer": { // "name": "", // "plant": "", // "productionNumber": "PZPKM6", // "vehicleProductionDate": "2020-04-06" // }, // "invoice": { // "entryDate": "2012-01-19", // "freight": { // "freightInCharge": 995.95, // "freightOutCharge": 95.95, // "freightTaxCharge": 5.95 // }, // "vehicleAcquisitionDate": "2012-01-18", // "vehicleOrderDate": "2012-01-12", // "vehicleOrderNumber": "", // "vehicleOrderPriority": "", // "vehicleOrderType": "TRE RETAIL - STOCK" // }, vehicle: { // "axleCode": "GU6/3.42 REAR AXLE RATIO", // "axleCount": 2, // "bodyStyle": "PU", // "brakeSystem": "", // "cabType": "", // "certifiedPreownedInd": false, // "certifiedPreownedNumber": "", // "chassis": "", // "color": "", // "dealerBodyStyle": "", deliveryDate: txEnvelope.dms_unsold === true ? "" : moment() // .tz(JobData.bodyshop.timezone) .format("YYYY-MM-DD"), // "deliveryMileage": 4, // "doorsQuantity": 4, // "engineNumber": "", // "engineType": "LMG/VORTEC 5.3L VVT V8 SFI FLEXFUEL", // "exteriorColor": "", // "fleetVehicleId": "", // "frontTireCode": "", // "gmRPOCode": "", // "ignitionKeyNumber": "", // "interiorColor": "EBONY", licensePlateNo: JobData.plate_no === null ? null : String(JobData.plate_no).replace(/([^\w]|_)/g, "").length === 0 ? null : String(JobData.plate_no) .replace(/([^\w]|_)/g, "") .toUpperCase(), make: txEnvelope.dms_make, // "model": "CC10753", modelAbrev: txEnvelope.dms_model, // "modelDescription": "SILVERADO 1500 2WD EXT CAB LT", // "modelType": "T", modelYear: JobData.v_model_yr, // "numberOfEngineCylinders": 4, odometerStatus: txEnvelope.kmout, // "options": [ // { // "optionCategory": "SS", // "optionCode": "A95", // "optionCost": 875.6, // "optionDescription": "FRONT BUCKET SEATS INCLUDING: PWR SEAT ADJUST DRIVER 6-WAY", // "optionPrices": [ // { // "optionPricingType": "RETAIL", // "price": 995 // }, // { // "optionPricingType": "INVOICE", // "price": 875.6 // } // ] // }, // { // "optionCategory": "E", // "optionCode": "LMG", // "optionCost": 0, // "optionDescription": "VORTEC 5.3L V8 SFI ENGINE W/ACTIVE FUEL MANAGEMENT", // "optionPrices": [ // { // "optionPricingType": "RETAIL", // "price": 0 // }, // { // "optionPricingType": "INVOICE", // "price": 0 // } // ] // } // ], // "rearTireCode": "", // "restraintSystem": "", saleClassValue: "MISC", // "sourceCodeValue": "", // "sourceDescription": "", // "standardEquipment": "", // "stickerNumber": "", // "transmissionType": "A", // "transmissionNo": "MYC/ELECTRONIC 6-SPEED AUTOMATIC W/OD", // "trimCode": "", // "vehicleNote": "", // "vehiclePrices": [ // { // "price": 35894, // "vehiclePricingType": "SELLINGPRICE" // }, // { // "price": 33749.87, // "vehiclePricingType": "INVOICE" // }, // { // "price": 36472, // "vehiclePricingType": "RETAIL" // }, // { // "price": 28276.66, // "vehiclePricingType": "BASEINVOICE" // }, // { // "price": 30405, // "vehiclePricingType": "BASERETAIL" // }, // { // "price": 33749.87, // "vehiclePricingType": "COMMISSIONPRICE" // }, // { // "price": 32702.9, // "vehiclePricingType": "DRAFTAMOUNT" // } // ], // "vehicleStatus": "G", // "vehicleStock": "82268", // "vehicleWeight": "6800", vin: JobData.v_vin.toUpperCase() // "warrantyExpDate": "2015-01-12", // "wheelbase": "" }, owners: [ { id: { assigningPartyId: "CURRENT", value: DMSCust.customerId } } ] //"inventoryAccount": "237" } }); return result.data; } catch (error) { handleFortellisApiError(socket, error, "InsertDmsVehicle", { vin: JobData.v_vin, vehicleId: DMSVid.vehiclesVehId, customerId: DMSCust.customerId, jobId: JobData.id }); throw error; } } async function UpdateDmsVehicle({ socket, redisHelpers, JobData, DMSVeh, DMSCust, selectedCustomerId, txEnvelope }) { try { let ids = []; //if it's a generic customer, don't update the vehicle owners. if (selectedCustomerId === JobData.bodyshop.cdk_configuration.generic_customer_number) { ids = DMSVeh?.owners && DMSVeh.owners; } else { const existingOwnerinVeh = DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.value === DMSCust.customerId); if (existingOwnerinVeh) { ids = DMSVeh.owners.map((o) => { return { id: { assigningPartyId: o.id.value === DMSCust.customerId ? "CURRENT" : "PREVIOUS", value: o.id.value } }; }); } else { const oldOwner = DMSVeh?.owners && DMSVeh.owners.find((o) => o.id.assigningPartyId === "CURRENT"); ids = [ { id: { assigningPartyId: "CURRENT", value: DMSCust.customerId } }, ...(oldOwner ? [ { id: { assigningPartyId: "PREVIOUS", value: oldOwner.id.value } } ] : []) ]; } } const DMSVehToSend = _.cloneDeep(DMSVeh); //Remove unsupported fields on the post API. delete DMSVehToSend.dealer.lastActivityDate; delete DMSVehToSend.manufacturer; delete DMSVehToSend.invoice; delete DMSVehToSend.inventoryAccount; const result = await MakeFortellisCall({ ...FortellisActions.UpdateVehicle, requestSearchParams: {}, headers: {}, redisHelpers, socket, jobid: JobData.id, body: { ...DMSVehToSend, dealer: { ...DMSVehToSend.dealer, //TODO: Check why company is blank on a queried record. ...((txEnvelope.inservicedate || DMSVehToSend.dealer.inServiceDate) && { inServiceDate: txEnvelope.dms_unsold === true ? "" : moment(DMSVehToSend.dealer.inServiceDate || txEnvelope.inservicedate) // .tz(JobData.bodyshop.timezone) .toISOString() }) }, vehicle: { ...DMSVehToSend.vehicle, ...(txEnvelope.dms_model_override ? { make: txEnvelope.dms_make, modelAbrev: txEnvelope.dms_model } : {}), deliveryDate: txEnvelope.dms_unsold === true ? "" : moment(DMSVehToSend.vehicle.deliveryDate) //.tz(JobData.bodyshop.timezone) .toISOString() }, owners: ids } }); return result; } catch (error) { handleFortellisApiError(socket, error, "UpdateDmsVehicle", { vin: JobData.v_vin, vehicleId: DMSVeh?.dealer?.vehicleId, customerId: DMSCust?.customerId, selectedCustomerId, jobId: JobData.id }); throw error; } } async function InsertServiceVehicleHistory({ socket, redisHelpers, JobData }) { try { const txEnvelope = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.txEnvelope ); const DMSVid = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.DMSVid ); const result = await MakeFortellisCall({ ...FortellisActions.ServiceHistoryInsert, headers: {}, redisHelpers, socket, jobid: JobData.id, body: { header: { vehId: DMSVid.vehiclesVehId, roNumber: JobData.ro_number.match(/\d+/g)[0], mileage: txEnvelope.kmout, openDate: moment(JobData.actual_in).tz(JobData.bodyshop.timezone).format("YYYY-MM-DD"), openTime: moment(JobData.actual_in).tz(JobData.bodyshop.timezone).format("HH:mm:ss"), closeDate: moment(JobData.invoice_date).tz(JobData.bodyshop.timezone).format("YYYY-MM-DD"), closeTime: moment(JobData.invoice_date).tz(JobData.bodyshop.timezone).format("HH:mm:ss"), comments: txEnvelope.story?.slice(0, 40).toUpperCase(), // has to be between 0 and 40. cashierId: JobData.bodyshop.cdk_configuration.cashierid, referenceNumber: JobData.ro_number.match(/\d+/g)[0] } } }); return result; } catch (error) { handleFortellisApiError(socket, error, "InsertServiceVehicleHistory", { roNumber: JobData.ro_number, jobId: JobData.id }); throw error; } } async function InsertDmsStartWip({ socket, redisHelpers, JobData }) { try { const txEnvelope = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.txEnvelope ); const result = await MakeFortellisCall({ ...FortellisActions.StartWip, headers: {}, redisHelpers, socket, jobid: JobData.id, body: { acctgDate: moment().tz(JobData.bodyshop.timezone).format("YYYY-MM-DD"), desc: txEnvelope.story && txEnvelope.story.replace(replaceSpecialRegex, "").toUpperCase(), docType: "10", m13Flag: "0", refer: JobData.ro_number, // "rtnCode": "", // "sendline": "", // "groupName": "", srcCo: JobData.bodyshop.cdk_configuration.srcco, srcJrnl: txEnvelope.journal, transID: "", userID: JobData.bodyshop.cdk_configuration.cashierid, userName: "IMEX" //Cert Values Below // userID: "partprgm", // userName: "PROGRAM, PARTNER" // acctgDate: "2025-07-07", // desc: "DOCUMENT DESC. OPTIONAL REQUIREMENT", // docType: "3", // m13Flag: "0", // refer: "707MISC01", // rtnCode: "", // sendline: "", // groupName: "", // srcCo: "77", // srcJrnl: "80", // transID: "", // userID: "partprgm", // userName: "PROGRAM, PARTNER" }, }); return result; } catch (error) { handleFortellisApiError(socket, error, "InsertDmsStartWip", { roNumber: JobData.ro_number, jobId: JobData.id }); throw error; } } async function GenerateTransWips({ socket, redisHelpers, JobData }) { //3rd prop sets fortellis to true to maintain logging. const allocations = await CalculateAllocations(socket, JobData.id, true); const wips = []; const DMSTransHeader = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.DMSTransHeader ); allocations.forEach((alloc) => { //Add the sale item from each allocation. if (alloc.sale.getAmount() > 0 && !alloc.tax) { const item = { acct: alloc.profitCenter.dms_acctnumber, cntl: alloc.profitCenter.dms_control_override && alloc.profitCenter.dms_control_override !== null && alloc.profitCenter.dms_control_override !== undefined && alloc.profitCenter.dms_control_override?.trim() !== "" ? alloc.profitCenter.dms_control_override : JobData.ro_number, cntl2: null, credtMemoNo: null, postAmt: alloc.sale.multiply(-1).getAmount(), postDesc: null, prod: null, statCnt: 1, transID: DMSTransHeader.transID, trgtCoID: JobData.bodyshop.cdk_configuration.srcco }; wips.push(item); } //Add the cost Item. if (alloc.cost.getAmount() > 0 && !alloc.tax) { const item = { acct: alloc.costCenter.dms_acctnumber, cntl: alloc.costCenter.dms_control_override && alloc.costCenter.dms_control_override !== null && alloc.costCenter.dms_control_override !== undefined && alloc.costCenter.dms_control_override?.trim() !== "" ? alloc.costCenter.dms_control_override : JobData.ro_number, cntl2: null, credtMemoNo: null, postAmt: alloc.cost.getAmount(), postDesc: null, prod: null, statCnt: 1, transID: DMSTransHeader.transID, trgtCoID: JobData.bodyshop.cdk_configuration.srcco }; wips.push(item); const itemWip = { acct: alloc.costCenter.dms_wip_acctnumber, cntl: alloc.costCenter.dms_control_override && alloc.costCenter.dms_control_override !== null && alloc.costCenter.dms_control_override !== undefined && alloc.costCenter.dms_control_override?.trim() !== "" ? alloc.costCenter.dms_control_override : JobData.ro_number, cntl2: null, credtMemoNo: null, postAmt: alloc.cost.multiply(-1).getAmount(), postDesc: null, prod: null, statCnt: 1, transID: DMSTransHeader.transID, trgtCoID: JobData.bodyshop.cdk_configuration.srcco }; wips.push(itemWip); //Add to the WIP account. } if (alloc.tax) { if (alloc.sale.getAmount() > 0) { const item2 = { acct: alloc.profitCenter.dms_acctnumber, cntl: alloc.profitCenter.dms_control_override && alloc.profitCenter.dms_control_override !== null && alloc.profitCenter.dms_control_override !== undefined && alloc.profitCenter.dms_control_override?.trim() !== "" ? alloc.profitCenter.dms_control_override : JobData.ro_number, cntl2: null, credtMemoNo: null, postAmt: alloc.sale.multiply(-1).getAmount(), postDesc: null, prod: null, statCnt: 1, transID: DMSTransHeader.transID, trgtCoID: JobData.bodyshop.cdk_configuration.srcco }; wips.push(item2); } } }); const txEnvelope = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.txEnvelope ); txEnvelope.payers.forEach((payer) => { const item = { acct: payer.dms_acctnumber, cntl: payer.controlnumber, cntl2: null, credtMemoNo: null, postAmt: Math.round(payer.amount * 100), postDesc: null, prod: null, statCnt: 1, transID: DMSTransHeader.transID, trgtCoID: JobData.bodyshop.cdk_configuration.srcco }; wips.push(item); }); await redisHelpers.setSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.transWips, wips, defaultFortellisTTL ); return wips; } async function PostDmsBatchWip({ socket, redisHelpers, JobData }) { let DMSTransHeader; try { DMSTransHeader = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.DMSTransHeader ); const result = await MakeFortellisCall({ ...FortellisActions.PostBatchWip, headers: {}, redisHelpers, socket, jobid: JobData.id, body: { opCode: "P", transID: DMSTransHeader.transID, transWipReqList: await GenerateTransWips({ socket, redisHelpers, JobData }) }, }); return result; } catch (error) { handleFortellisApiError(socket, error, "PostDmsBatchWip", { transId: DMSTransHeader?.transID, jobId: JobData.id }); throw error; } } async function QueryDmsErrWip({ socket, redisHelpers, JobData }) { let DMSTransHeader; try { DMSTransHeader = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.DMSTransHeader ); const result = await MakeFortellisCall({ ...FortellisActions.QueryErrorWip, headers: {}, redisHelpers, socket, jobid: JobData.id, requestPathParams: DMSTransHeader.transID, body: {}, }); return result; } catch (error) { handleFortellisApiError(socket, error, "QueryDmsErrWip", { transId: DMSTransHeader?.transID, jobId: JobData.id }); throw error; } } async function DeleteDmsWip({ socket, redisHelpers, JobData }) { let DMSTransHeader; try { DMSTransHeader = await redisHelpers.getSessionTransactionData( socket.id, getTransactionType(JobData.id), FortellisCacheEnums.DMSTransHeader ); const result = await MakeFortellisCall({ ...FortellisActions.DeleteTranWip, headers: {}, redisHelpers, socket, jobid: JobData.id, body: { opCode: "D", transID: DMSTransHeader.transID }, }); return result; } catch (error) { handleFortellisApiError(socket, error, "DeleteDmsWip", { transId: DMSTransHeader?.transID, jobId: JobData.id }); throw error; } } async function MarkJobExported({ socket, jobid, JobData }) { CreateFortellisLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const currentToken = (socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token); const result = await client .setHeaders({ Authorization: `Bearer ${currentToken}` }) .request(queries.MARK_JOB_EXPORTED, { jobId: jobid, job: { status: JobData.bodyshop.md_ro_statuses.default_exported || "Exported*", date_exported: new Date() }, log: { bodyshopid: 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, JobData, error }) { try { const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const currentToken = (socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token); const result = await client .setHeaders({ Authorization: `Bearer ${currentToken}` }) .request(queries.INSERT_EXPORT_LOG, { logs: [{ bodyshopid: JobData.bodyshop.id, jobid: JobData.id, successful: false, message: JSON.stringify(error), useremail: socket.user.email }] }); return result; } catch (error2) { CreateFortellisLogEvent(socket, "ERROR", `Error in InsertFailedExportLog - ${error}`, { message: error2.message, stack: error2.stack }); } } exports.getTransactionType = getTransactionType; exports.FortellisJobExport = FortellisJobExport;