Files
imexrps/electron/decoder/decoder.js
2021-12-01 14:38:06 -08:00

519 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
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 #. " + job.clm_no);
returnValue = {
ERROR: `An unique claim number must be set for all jobs sent to RPS.`,
};
} 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.
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 = jobline.act_price;
}
//$0DB price for aftermarket and recycled parts. RPS-83
//Verified on 08/24/21
if (
(jobline.part_type === "PAA" || jobline.part_type === "PAL") &&
jobline.db_price === 0 &&
jobline.db_price !== jobline.act_price
) {
jobline.db_price = jobline.act_price;
}
//05/20
//Well 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;