Improve EMS interfaces and add LIN decoding.

This commit is contained in:
Patrick Fic
2025-03-17 15:45:45 -07:00
parent 8cfc9ec6a6
commit c03cf5a98f
9 changed files with 414 additions and 229 deletions

View File

@@ -1,4 +1,6 @@
export interface ParsedAD1 {
import { UUID } from "crypto";
export interface DecodedAd1 {
// Insurance company information
ins_co_id?: string;
ins_co_nm?: string;
@@ -130,18 +132,18 @@ export interface ParsedAD1 {
}
export interface OwnerRecordInterface {
ownr_ln: string;
ownr_fn: string;
ownr_title: string;
ownr_co_nm: string;
ownr_addr1: string;
ownr_addr2: string;
ownr_city: string;
ownr_st: string;
ownr_zip: string;
ownr_ctry: string;
ownr_ph1: string;
ownr_ph2: string;
ownr_ea: string;
shopid: string;
ownr_ln?: string;
ownr_fn?: string;
ownr_title?: string;
ownr_co_nm?: string;
ownr_addr1?: string;
ownr_addr2?: string;
ownr_city?: string;
ownr_st?: string;
ownr_zip?: string;
ownr_ctry?: string;
ownr_ph1?: string;
ownr_ph2?: string;
ownr_ea?: string;
shopid: UUID;
}

View File

@@ -2,21 +2,24 @@ import { DBFFile } from "dbffile";
import log from "electron-log/main";
import _ from "lodash";
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
import { OwnerRecordInterface, ParsedAD1 } from "./decode-ad1.interface";
import { OwnerRecordInterface, DecodedAd1 } from "./decode-ad1.interface";
import errorTypeCheck from "../../util/errorTypeCheck";
const DecodeAD1 = async (extensionlessFilePath: string): Promise<ParsedAD1> => {
let dbf;
const DecodeAD1 = async (
extensionlessFilePath: string
): Promise<DecodedAd1> => {
let dbf: DBFFile;
try {
dbf = await DBFFile.open(`${extensionlessFilePath}A.AD1`);
} catch (error) {
log.error("Error opening AD1 File.", error);
log.error("Error opening AD1 File.", errorTypeCheck(error));
dbf = await DBFFile.open(`${extensionlessFilePath}.AD1`);
log.log("Found AD1 file using regular CIECA Id.");
}
if (!dbf) {
log.error(`Could not find any AD1 files at ${extensionlessFilePath}`);
return null;
throw new Error(`Could not find any AD1 files at ${extensionlessFilePath}`);
}
const rawDBFRecord = await dbf.readRecords(1);
@@ -24,7 +27,7 @@ const DecodeAD1 = async (extensionlessFilePath: string): Promise<ParsedAD1> => {
//AD1 will always have only 1 row.
//Commented lines have been cross referenced with existing partner fields.
const rawAd1Data = deepLowerCaseKeys(
const rawAd1Data: DecodedAd1 = deepLowerCaseKeys(
_.pick(rawDBFRecord[0], [
//TODO: Add typings for EMS File Formats.
"INS_CO_ID",
@@ -192,7 +195,7 @@ const DecodeAD1 = async (extensionlessFilePath: string): Promise<ParsedAD1> => {
ownr_ph1: rawAd1Data.ownr_ph1,
ownr_ph2: rawAd1Data.ownr_ph2,
ownr_ea: rawAd1Data.ownr_ea,
shopid: "UUID",
shopid: "UUID", //TODO: Need to add the shop uuid to this set of functions.
};
}

View File

@@ -1,4 +1,4 @@
export interface ParsedAD2 {
export interface DecodedAD2 {
clmt_ln?: string;
clmt_fn?: string;
clmt_title?: string;

View File

@@ -2,23 +2,24 @@ import { DBFFile } from "dbffile";
import log from "electron-log/main";
import _ from "lodash";
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
import { ParsedAD2 } from "./decode-ad2.interface";
import { DecodedAD2 } from "./decode-ad2.interface";
import errorTypeCheck from "../../util/errorTypeCheck";
const DecodeAD2 = async (extensionlessFilePath: string): Promise<ParsedAD2> => {
let dbf;
const DecodeAD2 = async (
extensionlessFilePath: string
): Promise<DecodedAD2> => {
let dbf: DBFFile;
try {
dbf = await DBFFile.open(`${extensionlessFilePath}B.AD2`);
} catch (error) {
log.error("Error opening AD2 File.", error);
log.error("Error opening AD2 File.", errorTypeCheck(error));
dbf = await DBFFile.open(`${extensionlessFilePath}.AD2`);
log.log("Found AD2 file using regular CIECA Id.");
}
if (!dbf) {
log.error(`Could not find any AD2 files at ${extensionlessFilePath}`);
return {
id: 0,
};
throw new Error(`Could not find any AD2 files at ${extensionlessFilePath}`);
}
const rawDBFRecord = await dbf.readRecords(1);
@@ -65,75 +66,75 @@ const DecodeAD2 = async (extensionlessFilePath: string): Promise<ParsedAD2> => {
"EST_EA",
//"EST_LIC_NO",
//"EST_FILENO",
// "INSP_CT_LN",
// "INSP_CT_FN",
// "INSP_ADDR1",
// "INSP_ADDR2",
// "INSP_CITY",
// "INSP_ST",
// "INSP_ZIP",
// "INSP_CTRY",
// "INSP_PH1",
// "INSP_PH1X",
// "INSP_PH2",
// "INSP_PH2X",
// "INSP_FAX",
// "INSP_FAXX",
// "INSP_EA",
// "INSP_CODE",
// "INSP_DESC",
//"INSP_CT_LN",
//"INSP_CT_FN",
//"INSP_ADDR1",
//"INSP_ADDR2",
//"INSP_CITY",
//"INSP_ST",
//"INSP_ZIP",
//"INSP_CTRY",
//"INSP_PH1",
//"INSP_PH1X",
//"INSP_PH2",
//"INSP_PH2X",
//"INSP_FAX",
//"INSP_FAXX",
//"INSP_EA",
//"INSP_CODE",
//"INSP_DESC",
"INSP_DATE", //RENAME TO date_estimated
// "INSP_TIME",
// "RF_CO_ID",
// "RF_CO_NM",
// "RF_ADDR1",
// "RF_ADDR2",
// "RF_CITY",
// "RF_ST",
// "RF_ZIP",
// "RF_CTRY",
// "RF_PH1",
// "RF_PH1X",
// "RF_PH2",
// "RF_PH2X",
// "RF_FAX",
// "RF_FAXX",
// "RF_CT_LN",
// "RF_CT_FN",
// "RF_EA",
// "RF_TAX_ID",
// "RF_LIC_NO",
// "RF_BAR_NO",
// "RO_IN_DATE",
// "RO_IN_TIME",
// "TAR_DATE",
// "TAR_TIME",
// "RO_CMPDATE",
// "RO_CMPTIME",
// "DATE_OUT",
// "TIME_OUT",
// "RF_ESTIMTR",
// "MKTG_TYPE",
// "MKTG_SRC",
// "LOC_NM",
// "LOC_ADDR1",
// "LOC_ADDR2",
// "LOC_CITY",
// "LOC_ST",
// "LOC_ZIP",
// "LOC_CTRY",
// "LOC_PH1",
// "LOC_PH1X",
// "LOC_PH2",
// "LOC_PH2X",
// "LOC_FAX",
// "LOC_FAXX",
// "LOC_CT_LN",
// "LOC_CT_FN",
// "LOC_TITLE",
// "LOC_PH",
// "LOC_PHX",
// "LOC_EA",
//"INSP_TIME",
//"RF_CO_ID",
//"RF_CO_NM",
//"RF_ADDR1",
//"RF_ADDR2",
//"RF_CITY",
//"RF_ST",
//"RF_ZIP",
//"RF_CTRY",
//"RF_PH1",
//"RF_PH1X",
//"RF_PH2",
//"RF_PH2X",
//"RF_FAX",
//"RF_FAXX",
//"RF_CT_LN",
//"RF_CT_FN",
//"RF_EA",
//"RF_TAX_ID",
//"RF_LIC_NO",
//"RF_BAR_NO",
//"RO_IN_DATE",
//"RO_IN_TIME",
//"TAR_DATE",
//"TAR_TIME",
//"RO_CMPDATE",
//"RO_CMPTIME",
//"DATE_OUT",
//"TIME_OUT",
//"RF_ESTIMTR",
//"MKTG_TYPE",
//"MKTG_SRC",
//"LOC_NM",
//"LOC_ADDR1",
//"LOC_ADDR2",
//"LOC_CITY",
//"LOC_ST",
//"LOC_ZIP",
//"LOC_CTRY",
//"LOC_PH1",
//"LOC_PH1X",
//"LOC_PH2",
//"LOC_PH2X",
//"LOC_FAX",
//"LOC_FAXX",
//"LOC_CT_LN",
//"LOC_CT_FN",
//"LOC_TITLE",
//"LOC_PH",
//"LOC_PHX",
//"LOC_EA",
])
);
@@ -142,6 +143,6 @@ const DecodeAD2 = async (extensionlessFilePath: string): Promise<ParsedAD2> => {
rawAd2Data.date_estimated = rawAd2Data.insp_date;
delete rawAd2Data.insp_date;
return null;
return rawAd2Data;
};
export default DecodeAD2;

View File

@@ -0,0 +1,48 @@
export interface DecodedLin {
line_no?: string;
line_ind?: string;
line_ref?: string;
tran_code?: string;
db_ref?: string;
unq_seq?: string;
//who_pays?: string;
line_desc?: string;
part_type?: string;
//part_desc_j?: boolean;
glass_flag?: boolean;
oem_partno?: string;
price_inc?: boolean;
alt_part_i?: boolean;
tax_part?: boolean;
db_price?: number;
act_price?: number;
price_j?: boolean;
cert_part?: boolean;
part_qty?: number;
alt_co_id?: string;
alt_partno?: string;
alt_overrd?: boolean;
alt_partm?: string;
prt_dsmk_p?: string;
prt_dsmk_m?: string;
mod_lbr_ty?: string;
db_hrs?: number;
mod_lb_hrs?: number;
lbr_inc?: boolean;
lbr_op?: string;
lbr_hrs_j?: boolean;
lbr_typ_j?: boolean;
lbr_op_j?: boolean;
paint_stg?: string;
paint_tone?: string;
lbr_tax?: boolean;
lbr_amt?: number;
misc_amt?: number;
misc_sublt?: string;
misc_tax?: boolean;
bett_type?: string;
bett_pctg?: string | number;
bett_amt?: number;
bett_tax?: boolean;
op_code_desc?: string;
}

View File

@@ -0,0 +1,104 @@
import { DBFFile } from "dbffile";
import log from "electron-log/main";
import _ from "lodash";
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
import { DecodedLin } from "./decode-lin.interface";
import errorTypeCheck from "../../util/errorTypeCheck";
const DecodeLin = async (
extensionlessFilePath: string
): Promise<DecodedLin[]> => {
let dbf: DBFFile;
try {
dbf = await DBFFile.open(`${extensionlessFilePath}.LIN`);
} catch (error) {
//LIN File only has 1 location.
log.error("Error opening LIN File.", errorTypeCheck(error));
}
if (!dbf) {
log.error(`Could not find any LIN files at ${extensionlessFilePath}`);
throw new Error(`Could not find any LIN files at ${extensionlessFilePath}`);
}
const rawDBFRecord = await dbf.readRecords();
//AD2 will always have only 1 row.
//Commented lines have been cross referenced with existing partner fields.
const rawLinData: DecodedLin[] = rawDBFRecord.map((record) => {
const singleLineData: DecodedLin = deepLowerCaseKeys(
_.pick(record, [
//TODO: Add typings for EMS File Formats.
"LINE_NO",
"LINE_IND",
"LINE_REF",
"TRAN_CODE",
"DB_REF",
"UNQ_SEQ",
// "WHO_PAYS",
"LINE_DESC",
"PART_TYPE",
//TODO: Believe this was previously broken in partner. Need to confirm.
// system == "M" ? "PART_DESCJ" : "PART_DES_J",
//"PART_DESC_J",
//End Check
"GLASS_FLAG",
"OEM_PARTNO",
"PRICE_INC",
"ALT_PART_I",
"TAX_PART",
"DB_PRICE",
"ACT_PRICE",
"PRICE_J",
"CERT_PART",
"PART_QTY",
"ALT_CO_ID",
"ALT_PARTNO",
"ALT_OVERRD",
"ALT_PARTM",
"PRT_DSMK_P",
"PRT_DSMK_M",
"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",
])
);
//Apply line by line adjustments.
singleLineData.op_code_desc = ""; //TODO: Implement the OP Code Lookup.
//Partner previously queried this on login and stored it. Then referenced it and added here.
// Sample Code:
// try
// {
// lin.op_code_desc = Utils.AppMetaData.CiecaOpCodes[lin.lbr_op.Value]["desc"].Value;
// }
// catch (Exception Ex)
// {
// logger.Warn(Ex, "Couldnt find OpCodeDesc from {0} ", lin.lbr_op.Value);
// }
return singleLineData;
});
//Apply business logic transfomrations.
//We don't have an inspection date, we instead have `date_estimated`
return rawLinData;
};
export default DecodeLin;

View File

@@ -1 +1,61 @@
export interface ParsedVeh {}
import { UUID } from "crypto";
export interface DecodedVeh {
// Basic vehicle information
plate_no?: string;
plate_st?: string;
v_vin?: string;
v_model_yr?: string;
v_make_desc?: string;
v_model_desc?: string;
v_color?: string;
kmin?: number;
// Complete vehicle data object
vehicle: { data: VehicleRecordInterface };
}
export interface VehicleRecordInterface {
// Area of damage information
area_of_damage: {
impact1?: string;
impact2?: string;
};
// Paint code information
paint_codes: {
paint_cd1: string;
paint_cd2: string;
paint_cd3: string;
};
// Vehicle information from DBF file
db_v_code?: string;
plate_no?: string;
plate_st?: string;
v_vin?: string;
v_cond: string;
v_prod_dt?: Date;
v_model_yr: string;
v_makecode: string;
v_make_desc?: string;
v_model?: string;
v_model_desc?: string;
v_type: string;
v_bstyle?: string;
v_trimcode?: string;
trim_color?: string;
v_mldgcode?: string;
v_engine?: string;
v_mileage?: string;
v_color?: string;
v_tone?: string;
v_stage?: string;
shopid: UUID;
//These are removed during business logic processing.
v_makedesc?: string;
impact_1?: string;
impact_2?: string;
paint_cd1?: string;
paint_cd2?: string;
paint_cd3?: string;
}

View File

@@ -2,24 +2,24 @@ import { DBFFile } from "dbffile";
import log from "electron-log/main";
import _ from "lodash";
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
import { ParsedAD2 } from "./decode-ad2.interface";
import { ParsedVeh } from "./decode-veh.interface";
import { DecodedVeh, VehicleRecordInterface } from "./decode-veh.interface";
import errorTypeCheck from "../../util/errorTypeCheck";
const DecodeVeh = async (extensionlessFilePath: string): Promise<ParsedVeh> => {
let dbf;
const DecodeVeh = async (
extensionlessFilePath: string
): Promise<DecodedVeh> => {
let dbf: DBFFile;
try {
dbf = await DBFFile.open(`${extensionlessFilePath}B.AD2`);
dbf = await DBFFile.open(`${extensionlessFilePath}V.VEH`);
} catch (error) {
log.error("Error opening AD2 File.", error);
dbf = await DBFFile.open(`${extensionlessFilePath}.AD2`);
log.log("Found AD2 file using regular CIECA Id.");
log.error("Error opening VEH File.", errorTypeCheck(error));
dbf = await DBFFile.open(`${extensionlessFilePath}.VEH`);
log.log("Found VEH file using regular CIECA Id.");
}
if (!dbf) {
log.error(`Could not find any AD2 files at ${extensionlessFilePath}`);
return {
id: 0,
};
log.error(`Could not find any VEH files at ${extensionlessFilePath}`);
throw new Error(`Could not find any VEH files at ${extensionlessFilePath}`);
}
const rawDBFRecord = await dbf.readRecords(1);
@@ -27,122 +27,84 @@ const DecodeVeh = async (extensionlessFilePath: string): Promise<ParsedVeh> => {
//AD2 will always have only 1 row.
//Commented lines have been cross referenced with existing partner fields.
const rawAd2Data = deepLowerCaseKeys(
const rawVehData: VehicleRecordInterface = deepLowerCaseKeys(
_.pick(rawDBFRecord[0], [
//TODO: Add typings for EMS File Formats.
"CLMT_LN",
"CLMT_FN",
"CLMT_TITLE",
"CLMT_CO_NM",
"CLMT_ADDR1",
"CLMT_ADDR2",
"CLMT_CITY",
"CLMT_ST",
"CLMT_ZIP",
"CLMT_CTRY",
"CLMT_PH1",
//"CLMT_PH1X",
"CLMT_PH2",
//"CLMT_PH2X",
//"CLMT_FAX",
//"CLMT_FAXX",
"CLMT_EA",
"EST_CO_ID",
"EST_CO_NM",
"EST_ADDR1",
"EST_ADDR2",
"EST_CITY",
"EST_ST",
"EST_ZIP",
"EST_CTRY",
"EST_PH1",
//"EST_PH1X",
//"EST_PH2",
//"EST_PH2X",
//"EST_FAX",
//"EST_FAXX",
"EST_CT_LN",
"EST_CT_FN",
"EST_EA",
//"EST_LIC_NO",
//"EST_FILENO",
// "INSP_CT_LN",
// "INSP_CT_FN",
// "INSP_ADDR1",
// "INSP_ADDR2",
// "INSP_CITY",
// "INSP_ST",
// "INSP_ZIP",
// "INSP_CTRY",
// "INSP_PH1",
// "INSP_PH1X",
// "INSP_PH2",
// "INSP_PH2X",
// "INSP_FAX",
// "INSP_FAXX",
// "INSP_EA",
// "INSP_CODE",
// "INSP_DESC",
"INSP_DATE", //RENAME TO date_estimated
// "INSP_TIME",
// "RF_CO_ID",
// "RF_CO_NM",
// "RF_ADDR1",
// "RF_ADDR2",
// "RF_CITY",
// "RF_ST",
// "RF_ZIP",
// "RF_CTRY",
// "RF_PH1",
// "RF_PH1X",
// "RF_PH2",
// "RF_PH2X",
// "RF_FAX",
// "RF_FAXX",
// "RF_CT_LN",
// "RF_CT_FN",
// "RF_EA",
// "RF_TAX_ID",
// "RF_LIC_NO",
// "RF_BAR_NO",
// "RO_IN_DATE",
// "RO_IN_TIME",
// "TAR_DATE",
// "TAR_TIME",
// "RO_CMPDATE",
// "RO_CMPTIME",
// "DATE_OUT",
// "TIME_OUT",
// "RF_ESTIMTR",
// "MKTG_TYPE",
// "MKTG_SRC",
// "LOC_NM",
// "LOC_ADDR1",
// "LOC_ADDR2",
// "LOC_CITY",
// "LOC_ST",
// "LOC_ZIP",
// "LOC_CTRY",
// "LOC_PH1",
// "LOC_PH1X",
// "LOC_PH2",
// "LOC_PH2X",
// "LOC_FAX",
// "LOC_FAXX",
// "LOC_CT_LN",
// "LOC_CT_FN",
// "LOC_TITLE",
// "LOC_PH",
// "LOC_PHX",
// "LOC_EA",
"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_BSTYLE",
"V_TRIMCODE",
"TRIM_COLOR",
"V_MLDGCODE",
"V_ENGINE",
"V_MILEAGE",
"V_COLOR",
"V_TONE",
"V_STAGE",
"PAINT_CD1",
"PAINT_CD2",
"PAINT_CD3",
])
);
//Apply business logic transfomrations.
//We don't have an inspection date, we instead have `date_estimated`
rawAd2Data.date_estimated = rawAd2Data.insp_date;
delete rawAd2Data.insp_date;
return null;
//An old error where the column had an extra underscore.
rawVehData.v_make_desc = rawVehData.v_makedesc || rawVehData.v_makecode; //Fallback for US.
delete rawVehData.v_makedesc;
//An old error where the column had an extra underscore.
rawVehData.v_model_desc = rawVehData.v_model;
delete rawVehData.v_model;
//Consolidate Area of Damage.
rawVehData.area_of_damage = {
impact1: rawVehData.impact_1 ?? "",
impact2: rawVehData.impact_2 ?? "",
};
delete rawVehData.impact_1;
delete rawVehData.impact_2;
//Consolidate Paint Code information.
rawVehData.paint_codes = {
paint_cd1: rawVehData.paint_cd1 ?? "",
paint_cd2: rawVehData.paint_cd2 ?? "",
paint_cd3: rawVehData.paint_cd3 ?? "",
};
delete rawVehData.paint_cd1;
delete rawVehData.paint_cd2;
delete rawVehData.paint_cd3;
rawVehData.shopid = "UUID"; //TODO: Pass down the shopid for generation.
//Aggregate the vehicle data to be stamped onto the job record.
const jobVehiclData = {
plate_no: rawVehData.plate_no,
plate_st: rawVehData.plate_st,
v_vin: rawVehData.v_vin,
v_model_yr: rawVehData.v_model_yr,
v_make_desc: rawVehData.v_make_desc,
v_model_desc: rawVehData.v_model_desc,
v_color: rawVehData.v_color,
kmin: rawVehData.v_mileage,
};
return {
...jobVehiclData,
vehicle: {
data: rawVehData,
},
};
};
export default DecodeVeh;

View File

@@ -2,11 +2,13 @@ import log from "electron-log/main";
import path from "path";
import errorTypeCheck from "../../util/errorTypeCheck";
import DecodeAD1 from "./decode-ad1";
import { ParsedAD1 } from "./decode-ad1.interface";
import { DecodedAd1 } from "./decode-ad1.interface";
import DecodeAD2 from "./decode-ad2";
import { ParsedAD2 } from "./decode-ad2.interface";
import { DecodedAD2 } from "./decode-ad2.interface";
import DecodeLin from "./decode-lin";
import { DecodedLin } from "./decode-lin.interface";
import DecodeVeh from "./decode-veh";
import { ParsedVeh } from "./decode-veh.interface";
import { DecodedVeh } from "./decode-veh.interface";
async function ImportJob(filepath: string): Promise<void> {
const parsedFilePath = path.parse(filepath);
@@ -17,10 +19,13 @@ async function ImportJob(filepath: string): Promise<void> {
log.debug("Importing Job", extensionlessFilePath);
try {
const ad1: ParsedAD1 = await DecodeAD1(extensionlessFilePath);
const ad2: ParsedAD2 = await DecodeAD2(extensionlessFilePath);
const veh: ParsedVeh = await DecodeVeh(extensionlessFilePath);
log.debug("EMS Object", { ad1, ad2, veh });
//The below all end up returning parts of the job object.
//Some of them return additional info - e.g. owner or vehicle record data at both the job and corresponding table level.
const ad1: DecodedAd1 = await DecodeAD1(extensionlessFilePath);
const ad2: DecodedAD2 = await DecodeAD2(extensionlessFilePath);
const veh: DecodedVeh = await DecodeVeh(extensionlessFilePath);
const lin: DecodedLin[] = await DecodeLin(extensionlessFilePath);
log.debug("EMS Object", { ad1, ad2, veh, lin });
} catch (error) {
log.error("Error encountered while decoding job. ", errorTypeCheck(error));
}