const { DBFFile } = require("dbffile"); const path = require("path"); const _ = require("lodash"); const log = require("electron-log"); const { store } = require("../electron-store"); const { BrowserWindow } = require("electron"); const ipcTypes = require("../../src/ipc.types"); const { NewNotification, } = require("../notification-wrapper/notification-wrapper"); //const Nucleus = require("nucleus-nodejs"); async function ImportJob(path) { const b = BrowserWindow.getAllWindows()[0]; b.webContents.send(ipcTypes.default.estimate.toRenderer.estimateDecodeStart); const newJob = await DecodeEstimate(path); if (newJob && !newJob.ERROR) { b.webContents.send( ipcTypes.default.estimate.toRenderer.estimateDecodeSuccess, newJob ); log.info(`Sent job for upload. ${newJob.clm_no}`); NewNotification({ title: "Job Uploaded", body: "A new job has been uploaded.", }); } else { log.info(`Ignored job. ${newJob.ERROR}`); // Nucleus.track("IGNORE_JOB", { reason: newJob.ERROR }); NewNotification({ title: "Job Ignored", body: newJob.ERROR, }); } } async function DecodeEstimate(filePath, includeFilePathInReturnJob = false) { const parsedFilePath = path.parse(filePath); let extensionlessFilePath = path.join( parsedFilePath.dir, parsedFilePath.name ); const job = { ...(await DecodeAd1File(extensionlessFilePath)), ...(await DecodeVehFile(extensionlessFilePath)), ...(await DecodeTtlFile(extensionlessFilePath)), ...(await DecodeLinFile(extensionlessFilePath)), ...(includeFilePathInReturnJob ? { filePath } : {}), }; const ad2 = await DecodeAd2File(extensionlessFilePath); if (job.OWNR_FN === "" || !job.OWNR_FN) job.OWNR_FN = ad2.CLMT_FN; if (job.OWNR_LN === "" || !job.OWNR_LN) job.OWNR_LN = ad2.CLMT_LN; if (job.OWNR_CO_NM) job.OWNR_LN = `${job.OWNR_LN} ${job.OWNR_CO_NM}`; delete job.OWNR_CO_NM; const accepted_ins_co = store.get("accepted_ins_co"); let returnValue; //Removed as a part of RPS-40. // if (job.V_MILEAGE <= 20000) { // returnValue = { ERROR: "Vehicle mileage is less than 20,000kms." }; // } else if (!accepted_ins_co.includes(job.INS_CO_NM)) { returnValue = { ERROR: `Insurance Company Name is not valid for RPS. (${ job.INS_CO_NM || "No name set" })`, }; } else if (!job.CLM_NO) { log.info("Job ignored. No claim #. "); returnValue = { ERROR: `An unique claim number must be set for all jobs sent to RPS.`, }; } else if (job.CLM_NO.match("02$")) { log.info("Job ignored. This is an -02 claim. Claim #. " + job.CLM_NO); returnValue = { ERROR: "Job ignored. This is an -02 claim. Claim #. " + job.CLM_NO, }; } else { returnValue = _.transform(job, function (result, val, key) { result[key.toLowerCase()] = val; }); } return returnValue; } async function DecodeAd1File(extensionlessFilePath) { let dbf; try { dbf = await DBFFile.open(`${extensionlessFilePath}A.AD1`); } catch (error) { log.error("Error opening AD1 File.", error); dbf = await DBFFile.open(`${extensionlessFilePath}.AD1`); dbf && log.log("Found AD1 file using regular CIECA Id."); } finally { if (!dbf) return {}; let records = await dbf.readRecords(1); return _.pick(records[0], [ // "INS_CO_ID", "INS_CO_NM", // "INS_ADDR1", // "INS_ADDR2", // "INS_CITY", // "INS_ST", // "INS_ZIP", // "INS_CTRY", // "INS_EA", // "POLICY_NO", // "DED_AMT", // "DED_STATUS", // "ASGN_NO", //"ASGN_DATE", // "ASGN_TYPE", "CLM_NO", // "CLM_OFC_ID", // "CLM_OFC_NM", // "CLM_ADDR1", // "CLM_ADDR2", // "CLM_CITY", // "CLM_ST", // "CLM_ZIP", // "CLM_CTRY", // "CLM_PH1", // "CLM_PH1X", // "CLM_PH2", // "CLM_PH2X", // "CLM_FAX", // "CLM_FAXX", // "CLM_CT_LN", // "CLM_CT_FN", // "CLM_TITLE", // "CLM_CT_PH", // "CLM_CT_PHX", // "CLM_EA", // "PAYEE_NMS", // "PAY_TYPE", // "PAY_DATE", // "PAY_CHKNM", // "PAY_AMT", // "AGT_CO_ID", // "AGT_CO_NM", // "AGT_ADDR1", // "AGT_ADDR2", // "AGT_CITY", // "AGT_ST", // "AGT_ZIP", // "AGT_CTRY", // "AGT_PH1", // "AGT_PH1X", // "AGT_PH2", // "AGT_PH2X", // "AGT_FAX", // "AGT_FAXX", // "AGT_CT_LN", // "AGT_CT_FN", // "AGT_CT_PH", // "AGT_CT_PHX", // "AGT_EA", // "AGT_LIC_NO", "LOSS_DATE", // "LOSS_TYPE", // "LOSS_DESC", // "THEFT_IND", // "CAT_NO", // "TLOS_IND", // "CUST_PR", // "INSD_LN", // "INSD_FN", // "INSD_TITLE", // "INSD_CO_NM", // "INSD_ADDR1", // "INSD_ADDR2", // "INSD_CITY", // "INSD_ST", // "INSD_ZIP", // "INSD_CTRY", // "INSD_PH1", // "INSD_PH1X", // "INSD_PH2", // "INSD_PH2X", // "INSD_FAX", // "INSD_FAXX", // "INSD_EA", "OWNR_LN", "OWNR_FN", // "OWNR_TITLE", "OWNR_CO_NM", // "OWNR_ADDR1", // "OWNR_ADDR2", // "OWNR_CITY", // "OWNR_ST", // "OWNR_ZIP", // "OWNR_CTRY", // "OWNR_PH1", // "OWNR_PH1X", // "OWNR_PH2", // "OWNR_PH2X", // "OWNR_FAX", // "OWNR_FAXX", // "OWNR_EA", // "INS_PH1", // "INS_PH1X", // "INS_PH2", // "INS_PH2X", // "INS_FAX", // "INS_FAXX", // "INS_CT_LN", // "INS_CT_FN", // "INS_TITLE", // "INS_CT_PH", // "INS_CT_PHX", // "LOSS_CAT", ]); } } async function DecodeAd2File(extensionlessFilePath) { let dbf; try { dbf = await DBFFile.open(`${extensionlessFilePath}B.AD2`); } catch (error) { log.error("Error opening AD2 File.", error); dbf = await DBFFile.open(`${extensionlessFilePath}.AD2`); dbf && log.log("Found AD2 file using regular CIECA Id."); } finally { if (!dbf) return {}; let records = await dbf.readRecords(1); return _.pick(records[0], ["CLMT_LN", "CLMT_FN"]); } } async function DecodeVehFile(extensionlessFilePath) { let dbf; try { dbf = await DBFFile.open(`${extensionlessFilePath}V.VEH`); } catch (error) { log.error("Error opening VEH File.", error); dbf = await DBFFile.open(`${extensionlessFilePath}.VEH`); dbf && log.log("Found VEH file using regular CIECA Id."); } finally { if (!dbf) return {}; let records = await dbf.readRecords(1); return _.pick(records[0], [ // "IMPACT_1", // "IMPACT_2", // "DB_V_CODE", // "PLATE_NO", // "PLATE_ST", "V_VIN", // "V_COND", // "V_PROD_DT", "V_MODEL_YR", // "V_MAKECODE", "V_MAKEDESC", "V_MODEL", "V_TYPE", "V_MILEAGE", // "V_BSTYLE", // "V_TRIMCODE", // "TRIM_COLOR", // "V_MLDGCODE", // "V_ENGINE", // "V_COLOR", // "V_TONE", // "V_STAGE", // "PAINT_CD1", // "PAINT_CD2", // "PAINT_CD3", ]); } } async function DecodeTtlFile(extensionlessFilePath) { let dbf = await DBFFile.open(`${extensionlessFilePath}.TTL`); let records = await dbf.readRecords(1); return { clm_total: records[0]["G_TTL_AMT"] }; } async function DecodeLinFile(extensionlessFilePath) { let dbf = await DBFFile.open(`${extensionlessFilePath}.LIN`); let records = await dbf.readRecords(); let joblines = records.map((record) => { return _.transform( _.pick(record, [ "LINE_NO", "LINE_IND", "LINE_REF", // "TRAN_CODE", "DB_REF", "UNQ_SEQ", // "WHO_PAYS", "LINE_DESC", "PART_TYPE", // "PART_DESCJ", "PRT_DSMK_M", "OEM_PARTNO", // "PRICE_INC", // "ALT_PART_I", // "TAX_PART", "DB_PRICE", "ACT_PRICE", "PART_QTY", "PRICE_J", "GLASS_FLAG", // "CERT_PART", // "ALT_CO_ID", // "ALT_PARTNO", // "ALT_OVERRD", // "ALT_PARTM", "PRT_DSMK_P", // "MOD_LBR_TY", // "DB_HRS", // "MOD_LB_HRS", // "LBR_INC", // "LBR_OP", // "LBR_HRS_J", // "LBR_TYP_J", // "LBR_OP_J", // "PAINT_STG", // "PAINT_TONE", // "LBR_TAX", // "LBR_AMT", // "MISC_AMT", // "MISC_SUBLT", // "MISC_TAX", // "BETT_TYPE", // "BETT_PCTG", // "BETT_AMT", // "BETT_TAX", ]), function (result, val, key) { //Required because unq_seq gets pulled as a numeric instaed of a string. if (key === "UNQ_SEQ") { result[key.toLowerCase()] = val.toString(); return; } result[key.toLowerCase()] = val; return; } ); }); // .filter( // (jobline) => // jobline.part_type && // !jobline.db_ref.startsWith("900") && // !jobline.line_desc.toLowerCase().startsWith("urethane") && // !jobline.line_desc.toLowerCase().startsWith("wheel") && // !jobline.line_desc.toLowerCase().startsWith("hazardous") && // !jobline.line_desc.toLowerCase().startsWith("detail") && // !jobline.line_desc.toLowerCase().startsWith("clean") && // jobline.part_type.toUpperCase() !== "PAG" && // jobline.part_type.toUpperCase() !== "PAS" && // jobline.part_type.toUpperCase() !== "PASL" && // jobline.part_type.toUpperCase() !== "PAE" && // jobline.glass_flag === false // ) joblines.map((jobline) => { //Removed as a result of conversation with Norm. // Appears you are calculating based on ‘N.A.’ part values in the Mitchell database. // Reminder – if Mitchell DB has $0.00 price updates can be tracked, if it has N.A. it cannot calculate RPS value. //Removed as a part of $0db removal on 12/17/2022. // if ( // (jobline.db_price === null || jobline.db_price === 0) && // !!jobline.act_price && // jobline.act_price > 0 // ) { // // log.info( // // "DB Price null/lower than act price", // // jobline.line_desc, // // jobline.db_price, // // jobline.act_price // // ); // jobline.db_price = jobline.act_price; // } //*****TODO LINE**** //Any PAL, Remanufactured, Recycled, Aftermarket, Used, // If DB Price 0, ignore them. // if ( // (jobline.part_type === "PAL" || // jobline.part_type === "PAM" || // jobline.part_type === "PAR" || // jobline.part_type === "PAA") && // jobline.db_price === 0 // ) { // jobline.ignore = true; // } jobline.ignore = false; //Wheel Repair Pricing PRS-82 //Verified on 08/24/21 if ( jobline.part_type === "PAN" && jobline.line_desc.toLowerCase().includes("wheel") && Math.abs(jobline.prt_dsmk_m) === Math.round((jobline.act_price / 2) * 100) / 100 ) { log.info(`Jobline '${jobline.line_desc}' ignored due to wheel repair.`); jobline.ignore = true; } //RPS-46 Ignore NA Line Items. //Removed on 05/20. We are seeing more $0DB lines than NA lines and they are getting incorrectly ignored. // if ( // jobline.part_type === "PAN" && // jobline.price_j === true && // jobline.db_price === 0 // ) { // jobline.ignore = true; // } //RPS-39 - OEM ON OEM SAVINGS //Verified on 08/24/21 if ( jobline.part_type === "PAN" && jobline.db_price !== jobline.act_price && jobline.db_price !== 0 ) { jobline.db_price = jobline.act_price; } //Remove all $0DB line items 02/17/2022. if ( (jobline.part_type === "PAA" || jobline.part_type === "PAL" || jobline.part_type === "PAN") && jobline.db_price === 0 ) { jobline.ignore = true; } //05/20 //We’ll have to apply a rule that will not count any A/M, Reman or new part price that is manually changed to $0.00. //Only recycled parts that are changed to $0.00 can be counted towards RPS. // This is separate from $0.00 DB prices that need to be updated to the manually entered price. //Verified on 08/24/21 if ( jobline.part_type !== "PAL" && jobline.act_price === 0 && jobline.price_j ) { log.info( `Jobline '${jobline.line_desc}' ignored because it was manually changed to 0..` ); jobline.ignore = true; } //08/24/21 - Norm to take as action item to determine what the final set of rules were per Derek. // if (jobline.glass_flag && jobline.part_type !== "PAL") { // jobline.ignore = true; // } //09/2021 Detect NAGS lines using RegEx for the Part Number if ( jobline.line_desc.toLowerCase().includes("glass") && jobline.oem_partno.match(`[A-Z]{2}[0-9]{5,6}[A-Z]{3}`) ) { console.log(jobline.line_desc, "NAGS Line ignored"); jobline.ignore = true; } //Logic Based Exclusions. //Verified on 08/24/21 if ( !jobline.part_type || jobline.db_ref.startsWith("900") || jobline.line_desc.toLowerCase().startsWith("urethane") || jobline.line_desc.toLowerCase().startsWith("w/shield adhesive") || //jobline.line_desc.toLowerCase().includes("wheel") || Removed as a part of RPS-41 jobline.line_desc.toLowerCase().includes("tire") || jobline.line_desc.toLowerCase().startsWith("hazardous") || jobline.line_desc.toLowerCase().startsWith("detail") || jobline.line_desc.toLowerCase().startsWith("clean") || // jobline.part_type.toUpperCase() === "PAG" ||Removed for RPS-43. jobline.part_type.toUpperCase() === "PAS" || jobline.part_type.toUpperCase() === "PASL" || jobline.part_type.toUpperCase() === "PAE" //jobline.glass_flag === true //Removed for RPS-43. ) { jobline.ignore = true; } //Check to see if this is a discount line i.e. a 900511 if (jobline.db_ref === "900511" && jobline.prt_dsmk_p !== 50) { jobline.ignore = false; //Calculate the discount to be added as a negative. jobline.act_price = jobline.prt_dsmk_m; //Check to see if the parent line has a discount. const parentLine = joblines.find( (r) => parseInt(r.unq_seq) === jobline.line_ref ); if (parentLine && parentLine.ignore) { jobline.ignore = true; } } //RPS-42 Dynamic Inclusion or Exclusion of Lines based on RPS-EXCLUDE or RPS. if (jobline.oem_partno.toLowerCase().includes("/rps-exclude")) { jobline.ignore = true; } else if (jobline.oem_partno.toLowerCase().includes("/rps")) { jobline.ignore = false; } delete jobline.prt_dsmk_m; //Delete price markup for wheel repair delete jobline.prt_dsmk_p; delete jobline.glass_flag; delete jobline.price_j; delete jobline.line_ref; return jobline; }); //Check for discounts that need to be ignored after the fact. return { joblines: { data: joblines } }; } exports.DecodeEstimate = DecodeEstimate; exports.ImportJob = ImportJob;