Initial copy of shop partner app.
This commit is contained in:
149
src/main/decoder/decode-ad1.interface.ts
Normal file
149
src/main/decoder/decode-ad1.interface.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { UUID } from "crypto";
|
||||
|
||||
export interface DecodedAd1 {
|
||||
// Insurance company information
|
||||
ins_co_id?: string;
|
||||
ins_co_nm?: string;
|
||||
ins_addr1?: string;
|
||||
ins_addr2?: string;
|
||||
ins_city?: string;
|
||||
ins_st?: string;
|
||||
ins_zip?: string;
|
||||
ins_ctry?: string;
|
||||
ins_ea?: string;
|
||||
ins_ph1?: string;
|
||||
ins_ph1x?: string;
|
||||
ins_ph2?: string;
|
||||
ins_ph2x?: string;
|
||||
ins_fax?: string;
|
||||
ins_faxx?: string;
|
||||
ins_ct_ln?: string;
|
||||
ins_ct_fn?: string;
|
||||
ins_title?: string;
|
||||
ins_ct_ph?: string;
|
||||
ins_ct_phx?: string;
|
||||
|
||||
// Policy information
|
||||
policy_no?: string;
|
||||
ded_amt?: string;
|
||||
ded_status?: string;
|
||||
asgn_no?: string;
|
||||
asgn_date?: Date | string;
|
||||
asgn_type?: string;
|
||||
|
||||
// Claim information
|
||||
clm_no?: string;
|
||||
clm_ofc_id?: string;
|
||||
clm_ofc_nm?: string;
|
||||
clm_addr1?: string;
|
||||
clm_addr2?: string;
|
||||
clm_city?: string;
|
||||
clm_st?: string;
|
||||
clm_zip?: string;
|
||||
clm_ctry?: string;
|
||||
clm_ph1?: string;
|
||||
clm_ph1x?: string;
|
||||
clm_ph2?: string;
|
||||
clm_ph2x?: string;
|
||||
clm_fax?: string;
|
||||
clm_faxx?: string;
|
||||
clm_ct_ln?: string;
|
||||
clm_ct_fn?: string;
|
||||
clm_title?: string;
|
||||
clm_ct_ph?: string;
|
||||
clm_ct_phx?: string;
|
||||
clm_ea?: string;
|
||||
|
||||
// Payment information
|
||||
payee_nms?: string;
|
||||
pay_type?: string;
|
||||
pay_date?: string;
|
||||
pay_chknm?: string;
|
||||
pay_amt?: string;
|
||||
|
||||
// Agent information
|
||||
agt_co_id?: string;
|
||||
agt_co_nm?: string;
|
||||
agt_addr1?: string;
|
||||
agt_addr2?: string;
|
||||
agt_city?: string;
|
||||
agt_st?: string;
|
||||
agt_zip?: string;
|
||||
agt_ctry?: string;
|
||||
agt_ph1?: string;
|
||||
agt_ph1x?: string;
|
||||
agt_ph2?: string;
|
||||
agt_ph2x?: string;
|
||||
agt_fax?: string;
|
||||
agt_faxx?: string;
|
||||
agt_ct_ln?: string;
|
||||
agt_ct_fn?: string;
|
||||
agt_ct_ph?: string;
|
||||
agt_ct_phx?: string;
|
||||
agt_ea?: string;
|
||||
agt_lic_no?: string;
|
||||
|
||||
// Loss information
|
||||
loss_date?: string;
|
||||
loss_type?: string;
|
||||
loss_desc?: string;
|
||||
theft_ind?: string;
|
||||
cat_no?: string;
|
||||
tlos_ind?: string;
|
||||
cust_pr?: string;
|
||||
loss_cat?: string;
|
||||
|
||||
// Insured information
|
||||
insd_ln?: string;
|
||||
insd_fn?: string;
|
||||
insd_title?: string;
|
||||
insd_co_nm?: string;
|
||||
insd_addr1?: string;
|
||||
insd_addr2?: string;
|
||||
insd_city?: string;
|
||||
insd_st?: string;
|
||||
insd_zip?: string;
|
||||
insd_ctry?: string;
|
||||
insd_ph1?: string;
|
||||
insd_ph2?: string;
|
||||
insd_fax?: string;
|
||||
insd_faxx?: string;
|
||||
insd_ea?: string;
|
||||
|
||||
// Owner information
|
||||
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;
|
||||
|
||||
// Owner data object - referenced in the code
|
||||
owner: {
|
||||
data: OwnerRecordInterface;
|
||||
};
|
||||
}
|
||||
|
||||
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: UUID;
|
||||
}
|
||||
237
src/main/decoder/decode-ad1.ts
Normal file
237
src/main/decoder/decode-ad1.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import store from "../store/store";
|
||||
import { DecodedAd1, OwnerRecordInterface } from "./decode-ad1.interface";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodeAD1 = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedAd1> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}A.AD1`);
|
||||
} catch {
|
||||
// log.debug("Error opening AD1 File.", errorTypeCheck(error));
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.AD1`);
|
||||
// log.debug("Trying to find AD1 file using regular CIECA Id.");
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any AD1 files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any AD1 files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = ["a.ad1", ".ad1"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any AD1 files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any AD1 files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening AD1 File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//AD1 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
const rawAd1Data: DecodedAd1 = deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"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",
|
||||
]),
|
||||
);
|
||||
|
||||
//Copy specific logic for manipulation.
|
||||
//If ownr_ph1 is missing, use ownr_ph2
|
||||
if (rawAd1Data.asgn_date) {
|
||||
const newAsgnDate = new Date(rawAd1Data.asgn_date);
|
||||
rawAd1Data.asgn_date = newAsgnDate.toISOString().split("T")[0];
|
||||
}
|
||||
|
||||
if (!rawAd1Data.ownr_ph1 || _.isEmpty(rawAd1Data.ownr_ph1)) {
|
||||
rawAd1Data.ownr_ph1 = rawAd1Data.ownr_ph2;
|
||||
}
|
||||
if (rawAd1Data.clm_no === "") {
|
||||
rawAd1Data.clm_no = undefined;
|
||||
}
|
||||
let ownerRecord: OwnerRecordInterface;
|
||||
//Check if the owner information is there. If not, use the insured information as a fallback.
|
||||
if (
|
||||
_.isEmpty(rawAd1Data.ownr_ln) &&
|
||||
_.isEmpty(rawAd1Data.ownr_fn) &&
|
||||
_.isEmpty(rawAd1Data.ownr_co_nm)
|
||||
) {
|
||||
//They're all empty. Using the insured information as a fallback.
|
||||
// Build up the owner record to insert it alongside the job.
|
||||
//TODO: Verify that this should be the insured, and not the claimant.
|
||||
ownerRecord = {
|
||||
ownr_ln: rawAd1Data.insd_ln,
|
||||
ownr_fn: rawAd1Data.insd_fn,
|
||||
ownr_title: rawAd1Data.insd_title,
|
||||
ownr_co_nm: rawAd1Data.insd_co_nm,
|
||||
ownr_addr1: rawAd1Data.insd_addr1,
|
||||
ownr_addr2: rawAd1Data.insd_addr2,
|
||||
ownr_city: rawAd1Data.insd_city,
|
||||
ownr_st: rawAd1Data.insd_st,
|
||||
ownr_zip: rawAd1Data.insd_zip,
|
||||
ownr_ctry: rawAd1Data.insd_ctry,
|
||||
ownr_ph1: rawAd1Data.insd_ph1,
|
||||
ownr_ph2: rawAd1Data.insd_ph2,
|
||||
ownr_ea: rawAd1Data.insd_ea,
|
||||
shopid: store.get("app.bodyshop.id"),
|
||||
};
|
||||
} else {
|
||||
//Use the owner information.
|
||||
ownerRecord = {
|
||||
ownr_ln: rawAd1Data.ownr_ln,
|
||||
ownr_fn: rawAd1Data.ownr_fn,
|
||||
ownr_title: rawAd1Data.ownr_title,
|
||||
ownr_co_nm: rawAd1Data.ownr_co_nm,
|
||||
ownr_addr1: rawAd1Data.ownr_addr1,
|
||||
ownr_addr2: rawAd1Data.ownr_addr2,
|
||||
ownr_city: rawAd1Data.ownr_city,
|
||||
ownr_st: rawAd1Data.ownr_st,
|
||||
ownr_zip: rawAd1Data.ownr_zip,
|
||||
ownr_ctry: rawAd1Data.ownr_ctry,
|
||||
ownr_ph1: rawAd1Data.ownr_ph1,
|
||||
ownr_ph2: rawAd1Data.ownr_ph2,
|
||||
ownr_ea: rawAd1Data.ownr_ea,
|
||||
shopid: store.get("app.bodyshop.id"),
|
||||
};
|
||||
}
|
||||
|
||||
return { ...rawAd1Data, owner: { data: ownerRecord } };
|
||||
};
|
||||
|
||||
export default DecodeAD1;
|
||||
29
src/main/decoder/decode-ad2.interface.ts
Normal file
29
src/main/decoder/decode-ad2.interface.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export interface DecodedAD2 {
|
||||
clmt_ln?: string;
|
||||
clmt_fn?: string;
|
||||
clmt_title?: string;
|
||||
clmt_co_nm?: string;
|
||||
clmt_addr1?: string;
|
||||
clmt_addr2?: string;
|
||||
clmt_city?: string;
|
||||
clmt_st?: string;
|
||||
clmt_zip?: string;
|
||||
clmt_ctry?: string;
|
||||
clmt_ph1?: string;
|
||||
clmt_ph2?: string;
|
||||
clmt_ea?: string;
|
||||
est_co_id?: string;
|
||||
est_co_nm?: string;
|
||||
est_addr1?: string;
|
||||
est_addr2?: string;
|
||||
est_city?: string;
|
||||
est_st?: string;
|
||||
est_zip?: string;
|
||||
est_ctry?: string;
|
||||
est_ph1?: string;
|
||||
est_ct_ln?: string;
|
||||
est_ct_fn?: string;
|
||||
est_ea?: string;
|
||||
date_estimated?: Date; // This is transformed from insp_date
|
||||
insp_date?: Date; // This exists initially but gets deleted
|
||||
}
|
||||
170
src/main/decoder/decode-ad2.ts
Normal file
170
src/main/decoder/decode-ad2.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import { DecodedAD2 } from "./decode-ad2.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodeAD2 = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedAD2> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}B.AD2`);
|
||||
} catch {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.AD2`);
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any AD2 files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any AD2 files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = ["b.ad2", ".ad2"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any AD2 files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any AD2 files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening AD2 File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const rawAd2Data: DecodedAD2 = deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"CLMT_LN", //TODO: This claimant info shouldnt be passed back. Just for the owner info.
|
||||
"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",
|
||||
]),
|
||||
);
|
||||
|
||||
//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 rawAd2Data;
|
||||
};
|
||||
export default DecodeAD2;
|
||||
5
src/main/decoder/decode-env.interface.ts
Normal file
5
src/main/decoder/decode-env.interface.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface DecodedEnv {
|
||||
est_system?: string;
|
||||
estfile_id?: string;
|
||||
ciecaid?: string;
|
||||
}
|
||||
68
src/main/decoder/decode-env.ts
Normal file
68
src/main/decoder/decode-env.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedEnv } from "./decode-env.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodeEnv = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedEnv> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.ENV`);
|
||||
} catch (error) {
|
||||
log.error("Error opening ENV File.", errorTypeCheck(error));
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any ENV files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any ENV files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = [".env"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any ENV files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any ENV files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening ENV File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
|
||||
//TODO: Determine if there's any value to capture the whole ENV file.
|
||||
|
||||
const rawEnvData: DecodedEnv = deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
//TODO: Several of these fields will fail. Should extend schema to capture them.
|
||||
//"EST_SYSTEM",
|
||||
"ESTFILE_ID",
|
||||
]),
|
||||
);
|
||||
rawEnvData.ciecaid = rawEnvData.estfile_id;
|
||||
delete rawEnvData.estfile_id;
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
|
||||
return rawEnvData;
|
||||
};
|
||||
export default DecodeEnv;
|
||||
54
src/main/decoder/decode-lin.interface.ts
Normal file
54
src/main/decoder/decode-lin.interface.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
export interface DecodedLinLine {
|
||||
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;
|
||||
}
|
||||
|
||||
export interface DecodedLin {
|
||||
joblines: {
|
||||
data: DecodedLinLine[];
|
||||
};
|
||||
}
|
||||
120
src/main/decoder/decode-lin.ts
Normal file
120
src/main/decoder/decode-lin.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import store from "../store/store";
|
||||
import { DecodedLin, DecodedLinLine } from "./decode-lin.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodeLin = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedLin> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = ["lin"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any LIN files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any LIN files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening LIN File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const rawDBFRecord = await dbf.readRecords();
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
const opCodeData = store.get("app.masterdata.opcodes"); //TODO: Type the op codes
|
||||
|
||||
const rawLinData: DecodedLinLine[] = rawDBFRecord.map((record) => {
|
||||
const singleLineData: DecodedLinLine = 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 = opCodeData[singleLineData.lbr_op]?.desc;
|
||||
|
||||
return singleLineData;
|
||||
});
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
//We don't have an inspection date, we instead have `date_estimated`
|
||||
|
||||
return { joblines: { data: rawLinData } };
|
||||
};
|
||||
export default DecodeLin;
|
||||
15
src/main/decoder/decode-pfh.interface.ts
Normal file
15
src/main/decoder/decode-pfh.interface.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface DecodedPfh {
|
||||
tax_prethr: number;
|
||||
tax_thr_amt?: number;
|
||||
tax_pstthr?: number;
|
||||
tax_tow_rt: number;
|
||||
tax_str_rt: number;
|
||||
tax_sub_rt: number;
|
||||
tax_lbr_rt: number;
|
||||
federal_tax_rate: number;
|
||||
adj_g_disc?: number;
|
||||
adj_towdis?: number;
|
||||
adj_strdis?: number;
|
||||
tax_predis?: number;
|
||||
tax_gst_rt?: number;
|
||||
}
|
||||
94
src/main/decoder/decode-pfh.ts
Normal file
94
src/main/decoder/decode-pfh.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedPfh } from "./decode-pfh.interface";
|
||||
import { platform } from "os";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodePfh = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedPfh> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFH`);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFH File.", errorTypeCheck(error));
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFH`);
|
||||
log.log("Trying to find PFH file using regular CIECA Id.");
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any PFH files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFH files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = [".pfh"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any PFH files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFH files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFH File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const rawPfhData: DecodedPfh = deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
//TODO: Several of these fields will fail. Should extend schema to capture them.
|
||||
//"ID_PRO_NAM", //Remove
|
||||
"TAX_PRETHR",
|
||||
"TAX_THRAMT",
|
||||
"TAX_PSTTHR",
|
||||
//"TAX_TOW_IN", //Remove
|
||||
"TAX_TOW_RT",
|
||||
//"TAX_STR_IN", //Remove
|
||||
"TAX_STR_RT",
|
||||
//"TAX_SUB_IN", //Remove
|
||||
"TAX_SUB_RT",
|
||||
//"TAX_BTR_IN", //Remove
|
||||
"TAX_LBR_RT",
|
||||
"TAX_GST_RT",
|
||||
//"TAX_GST_IN", //Remove
|
||||
"ADJ_G_DISC",
|
||||
"ADJ_TOWDIS",
|
||||
"ADJ_STRDIS",
|
||||
//"ADJ_BTR_IN", //Remove
|
||||
"TAX_PREDIS",
|
||||
]),
|
||||
);
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
|
||||
//Standardize some of the numbers and divide by 100.
|
||||
|
||||
rawPfhData.tax_prethr = (rawPfhData.tax_prethr ?? 0) / 100;
|
||||
rawPfhData.tax_pstthr = (rawPfhData.tax_pstthr ?? 0) / 100;
|
||||
rawPfhData.tax_tow_rt = (rawPfhData.tax_tow_rt ?? 0) / 100;
|
||||
rawPfhData.tax_str_rt = (rawPfhData.tax_str_rt ?? 0) / 100;
|
||||
rawPfhData.tax_sub_rt = (rawPfhData.tax_sub_rt ?? 0) / 100;
|
||||
rawPfhData.tax_lbr_rt = (rawPfhData.tax_lbr_rt ?? 0) / 100;
|
||||
rawPfhData.federal_tax_rate = (rawPfhData.tax_gst_rt ?? 0) / 100;
|
||||
delete rawPfhData.tax_gst_rt;
|
||||
|
||||
return rawPfhData;
|
||||
};
|
||||
export default DecodePfh;
|
||||
57
src/main/decoder/decode-pfl.interface.ts
Normal file
57
src/main/decoder/decode-pfl.interface.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
//TODO: Clean up this interface. A bit messy as we store the data in very different ways.
|
||||
|
||||
export interface DecodedPflLine {
|
||||
lbr_type: string;
|
||||
lbr_desc: string;
|
||||
lbr_rate: number;
|
||||
lbr_tax_in: boolean;
|
||||
lbr_taxp: number;
|
||||
lbr_adjP: number;
|
||||
lbr_tx_ty1: string;
|
||||
lbr_tx_in1: boolean;
|
||||
lbr_tx_ty2: string;
|
||||
lbr_tx_in2: boolean;
|
||||
lbr_tx_ty3: string;
|
||||
lbr_tx_in3: boolean;
|
||||
lbr_tx_ty4: string;
|
||||
lbr_tx_in4: boolean;
|
||||
lbr_tx_ty5: string;
|
||||
lbr_tx_in5: boolean;
|
||||
}
|
||||
|
||||
export interface JobLaborRateFields {
|
||||
rate_laa: number;
|
||||
rate_lab: number;
|
||||
rate_lad: number;
|
||||
rate_las: number;
|
||||
rate_lar: number;
|
||||
rate_lae: number;
|
||||
rate_lag: number;
|
||||
rate_laf: number;
|
||||
rate_lam: number;
|
||||
rate_lau: number;
|
||||
rate_la1: number;
|
||||
rate_la2: number;
|
||||
rate_la3: number;
|
||||
rate_la4: number;
|
||||
}
|
||||
export interface CiecaPfl {
|
||||
LAA?: DecodedPflLine;
|
||||
LAB?: DecodedPflLine;
|
||||
LAD?: DecodedPflLine;
|
||||
LAS?: DecodedPflLine;
|
||||
LAR?: DecodedPflLine;
|
||||
LAE?: DecodedPflLine;
|
||||
LAG?: DecodedPflLine;
|
||||
LAF?: DecodedPflLine;
|
||||
LAM?: DecodedPflLine;
|
||||
LAU?: DecodedPflLine;
|
||||
LA1?: DecodedPflLine;
|
||||
LA2?: DecodedPflLine;
|
||||
LA3?: DecodedPflLine;
|
||||
LA4?: DecodedPflLine;
|
||||
}
|
||||
|
||||
export interface DecodedPfl extends JobLaborRateFields {
|
||||
cieca_pfl: CiecaPfl;
|
||||
}
|
||||
122
src/main/decoder/decode-pfl.ts
Normal file
122
src/main/decoder/decode-pfl.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import {
|
||||
DecodedPfl,
|
||||
JobLaborRateFields,
|
||||
DecodedPflLine,
|
||||
} from "./decode-pfl.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodePfl = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedPfl> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFL`);
|
||||
} catch (error) {
|
||||
//PFL File only has 1 location.
|
||||
log.error("Error opening PFL File.", errorTypeCheck(error));
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any PFL files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFL files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = [".pfl"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any PFL files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFL files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFL File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const rawDBFRecord = await dbf.readRecords();
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const jobLaborRates: JobLaborRateFields = {
|
||||
rate_laa: 0,
|
||||
rate_lab: 0,
|
||||
rate_lad: 0,
|
||||
rate_las: 0,
|
||||
rate_lar: 0,
|
||||
rate_lae: 0,
|
||||
rate_lag: 0,
|
||||
rate_laf: 0,
|
||||
rate_lam: 0,
|
||||
rate_lau: 0,
|
||||
rate_la1: 0,
|
||||
rate_la2: 0,
|
||||
rate_la3: 0,
|
||||
rate_la4: 0,
|
||||
};
|
||||
|
||||
const rawPflData: DecodedPflLine[] = rawDBFRecord.map((record) => {
|
||||
const singleLineData: DecodedPflLine = deepLowerCaseKeys(
|
||||
_.pick(record, [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"LBR_TYPE",
|
||||
"LBR_DESC",
|
||||
"LBR_RATE",
|
||||
"LBR_TAX_IN",
|
||||
"LBR_TAXP",
|
||||
"LBR_ADJP",
|
||||
"LBR_TX_TY1",
|
||||
"LBR_TX_IN1",
|
||||
"LBR_TX_TY2",
|
||||
"LBR_TX_IN2",
|
||||
"LBR_TX_TY3",
|
||||
"LBR_TX_IN3",
|
||||
"LBR_TX_TY4",
|
||||
"LBR_TX_IN4",
|
||||
"LBR_TX_TY5",
|
||||
"LBR_TX_IN5",
|
||||
]),
|
||||
);
|
||||
//Apply line by line adjustments.
|
||||
//Set the job.rate_<CIECA_TYPE> field based on the value.
|
||||
jobLaborRates[`rate_${singleLineData.lbr_type.toLowerCase()}`] =
|
||||
singleLineData.lbr_rate;
|
||||
|
||||
//For Mitchell, Alum is stored under LA3 instead of LAA. Shift it back over.
|
||||
//The old partner had a check for this, but it always was true. Matching that logic.
|
||||
if (singleLineData.lbr_type === "LA3") {
|
||||
jobLaborRates[`rate_laa`] = singleLineData.lbr_rate;
|
||||
}
|
||||
|
||||
//Also capture the whole object.
|
||||
//This is segmented because the whole object was not previously captured for ImEX as it wasn't needed.
|
||||
//Rome needs the whole object to accurately calculate the tax rates.
|
||||
|
||||
return singleLineData;
|
||||
});
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
//We don't have an inspection date, we instead have `date_estimated`
|
||||
|
||||
const pflObj = _.keyBy(rawPflData, "lbr_type");
|
||||
|
||||
return { ...jobLaborRates, cieca_pfl: pflObj };
|
||||
};
|
||||
|
||||
export default DecodePfl;
|
||||
50
src/main/decoder/decode-pfm.interface.ts
Normal file
50
src/main/decoder/decode-pfm.interface.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
export interface DecodedPfmLine {
|
||||
matl_type?: string;
|
||||
cal_code?: number;
|
||||
cal_desc?: string;
|
||||
cal_maxdlr?: number;
|
||||
cal_prip?: number;
|
||||
cal_secp?: number;
|
||||
mat_calp?: number;
|
||||
cal_prethr?: number;
|
||||
cal_pstthr?: number;
|
||||
cal_thramt?: number;
|
||||
cal_lbrmin?: number;
|
||||
cal_lbrrte?: number;
|
||||
cal_opcode?: string;
|
||||
tax_ind?: boolean;
|
||||
mat_taxp?: number;
|
||||
mat_adjp?: number;
|
||||
mat_tx_ty1?: string;
|
||||
mat_tx_in1?: boolean;
|
||||
mat_tx_ty2?: string;
|
||||
mat_tx_in2?: boolean;
|
||||
mat_tx_ty3?: string;
|
||||
mat_tx_in3?: boolean;
|
||||
mat_tx_ty4?: string;
|
||||
mat_tx_in4?: boolean;
|
||||
mat_tx_ty5?: string;
|
||||
mat_tx_in5?: boolean;
|
||||
}
|
||||
|
||||
export interface JobMaterialRateFields {
|
||||
rate_mapa: number;
|
||||
tax_paint_mat_rt: number;
|
||||
rate_mash: number;
|
||||
tax_shop_mat_rt: number;
|
||||
rate_mahw: number;
|
||||
tax_levies_rt: number;
|
||||
rate_ma2s: number;
|
||||
rate_ma2t: number;
|
||||
rate_ma3s: number;
|
||||
rate_macs: number;
|
||||
rate_mabl: number;
|
||||
}
|
||||
|
||||
export interface DecodedPfm extends JobMaterialRateFields {
|
||||
materials: {
|
||||
MAPA?: DecodedPfmLine;
|
||||
MASH?: DecodedPfmLine;
|
||||
};
|
||||
cieca_pfm?: DecodedPfmLine[];
|
||||
}
|
||||
169
src/main/decoder/decode-pfm.ts
Normal file
169
src/main/decoder/decode-pfm.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import YNBoolConverter from "../../util/ynBoolConverter";
|
||||
import {
|
||||
DecodedPfm,
|
||||
DecodedPfmLine,
|
||||
JobMaterialRateFields,
|
||||
} from "./decode-pfm.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodePfm = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedPfm> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFM`);
|
||||
} catch (error) {
|
||||
//PFM File only has 1 location.
|
||||
log.error("Error opening PFM File.", errorTypeCheck(error));
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any PFM files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFM files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = [".pfm"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any PFM files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFM files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFM File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const rawDBFRecord = await dbf.readRecords();
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const jobMaterialRates: JobMaterialRateFields = {
|
||||
rate_mapa: 0,
|
||||
tax_paint_mat_rt: 0,
|
||||
rate_mash: 0,
|
||||
tax_shop_mat_rt: 0,
|
||||
rate_mahw: 0,
|
||||
tax_levies_rt: 0,
|
||||
rate_ma2s: 0,
|
||||
rate_ma2t: 0,
|
||||
rate_ma3s: 0,
|
||||
rate_macs: 0,
|
||||
rate_mabl: 0,
|
||||
};
|
||||
|
||||
const rawPfmData: DecodedPfmLine[] = rawDBFRecord.map((record) => {
|
||||
const singleLineData: DecodedPfmLine = YNBoolConverter(
|
||||
deepLowerCaseKeys(
|
||||
_.pick(record, [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"MATL_TYPE",
|
||||
"CAL_CODE",
|
||||
"CAL_DESC",
|
||||
"CAL_MAXDLR",
|
||||
"CAL_PRIP",
|
||||
|
||||
"CAL_SECP",
|
||||
"MAT_CALP",
|
||||
"CAL_PRETHR", //Mitchell here
|
||||
"CAL_PSTTHR",
|
||||
"CAL_THRAMT",
|
||||
|
||||
"CAL_LBRMIN",
|
||||
|
||||
"CAL_LBRRTE", //Audatex puts it here
|
||||
"CAL_OPCODE",
|
||||
|
||||
"TAX_IND",
|
||||
"MAT_TAXP",
|
||||
"MAT_ADJP",
|
||||
"MAT_TX_TY1",
|
||||
"MAT_TX_IN1",
|
||||
"MAT_TX_TY2",
|
||||
"MAT_TX_IN2",
|
||||
"MAT_TX_TY3",
|
||||
"MAT_TX_IN3",
|
||||
"MAT_TX_TY4",
|
||||
"MAT_TX_IN4",
|
||||
"MAT_TX_TY5",
|
||||
"MAT_TX_IN5",
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
//Also capture the whole object.
|
||||
//This is segmented because the whole object was not previously captured for ImEX as it wasn't needed.
|
||||
//Rome needs the whole object to accurately calculate the tax rates.
|
||||
|
||||
return singleLineData;
|
||||
});
|
||||
|
||||
//Apply line by line adjustments.
|
||||
const mapaLine: DecodedPfmLine | undefined = rawPfmData.find(
|
||||
(line) => line.matl_type === "MAPA",
|
||||
);
|
||||
if (mapaLine) {
|
||||
jobMaterialRates.rate_mapa =
|
||||
mapaLine.cal_lbrrte || mapaLine.cal_prethr || 0;
|
||||
jobMaterialRates.tax_paint_mat_rt = (mapaLine.mat_taxp ?? 0) / 100;
|
||||
}
|
||||
|
||||
const mashLine: DecodedPfmLine | undefined = rawPfmData.find(
|
||||
(line) => line.matl_type === "MASH",
|
||||
);
|
||||
if (mashLine) {
|
||||
jobMaterialRates.rate_mash =
|
||||
mashLine.cal_lbrrte || mashLine.cal_prethr || 0;
|
||||
jobMaterialRates.tax_shop_mat_rt = (mashLine.mat_taxp ?? 0) / 100;
|
||||
}
|
||||
|
||||
const mahwLine: DecodedPfmLine | undefined = rawPfmData.find(
|
||||
(line) => line.matl_type === "MAHW",
|
||||
);
|
||||
if (mahwLine) {
|
||||
jobMaterialRates.rate_mahw =
|
||||
mahwLine.cal_lbrrte || mahwLine.cal_prethr || 0;
|
||||
jobMaterialRates.tax_levies_rt = (mahwLine.mat_taxp ?? 0) / 100;
|
||||
}
|
||||
|
||||
const additionalMaterials = ["MA2S", "MA2T", "MA3S", "MACS", "MABL"];
|
||||
additionalMaterials.forEach((type) => {
|
||||
const line: DecodedPfmLine | undefined = rawPfmData.find(
|
||||
(line) => line.matl_type === type,
|
||||
);
|
||||
if (line) {
|
||||
jobMaterialRates[`rate_${type.toLowerCase()}`] =
|
||||
line.cal_lbrrte || line.cal_prethr || 0;
|
||||
}
|
||||
});
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
//We don't have an inspection date, we instead have `date_estimated`
|
||||
|
||||
return {
|
||||
...jobMaterialRates,
|
||||
materials: {
|
||||
MASH: mashLine,
|
||||
MAPA: mapaLine, //TODO: Need to verify if more fields are to come in here.
|
||||
},
|
||||
//cieca_pfm: rawPfmData, //TODO: Not currently captured. This may have valu in the future.
|
||||
};
|
||||
};
|
||||
|
||||
export default DecodePfm;
|
||||
32
src/main/decoder/decode-pfo.interface.ts
Normal file
32
src/main/decoder/decode-pfo.interface.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export interface DecodedPfoLine {
|
||||
tx_tow_ty?: string;
|
||||
tow_t_ty1?: string;
|
||||
tow_t_in1?: boolean;
|
||||
tow_t_ty2?: string;
|
||||
tow_t_in2?: boolean;
|
||||
tow_t_ty3?: string;
|
||||
tow_t_in3?: boolean;
|
||||
tow_t_ty4?: string;
|
||||
tow_t_in4?: boolean;
|
||||
tow_t_ty5?: string;
|
||||
tow_t_in5?: boolean;
|
||||
tow_t_ty6?: string;
|
||||
tow_t_in6?: boolean;
|
||||
tx_stor_ty?: string;
|
||||
stor_t_ty1?: string;
|
||||
stor_t_in1?: boolean;
|
||||
stor_t_ty2?: string;
|
||||
stor_t_in2?: boolean;
|
||||
stor_t_ty3?: string;
|
||||
stor_t_in3?: boolean;
|
||||
stor_t_ty4?: string;
|
||||
stor_t_in4?: boolean;
|
||||
stor_t_ty5?: string;
|
||||
stor_t_in5?: boolean;
|
||||
stor_t_ty6?: string;
|
||||
stor_t_in6?: boolean;
|
||||
}
|
||||
|
||||
export interface DecodedPfo {
|
||||
cieca_pfo: DecodedPfoLine;
|
||||
}
|
||||
93
src/main/decoder/decode-pfo.ts
Normal file
93
src/main/decoder/decode-pfo.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import YNBoolConverter from "../../util/ynBoolConverter";
|
||||
import { DecodedPfo, DecodedPfoLine } from "./decode-pfo.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodePfo = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedPfo> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFO`);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFO File.", errorTypeCheck(error));
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFO`);
|
||||
log.log("Trying to find PFO file using regular CIECA Id.");
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any PFO files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFO files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = [".pfo"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any PFO files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFO files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFO File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//PFO will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const rawPfoData: DecodedPfoLine = YNBoolConverter(
|
||||
deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"TX_TOW_TY",
|
||||
"TOW_T_TY1",
|
||||
"TOW_T_IN1",
|
||||
"TOW_T_TY2",
|
||||
"TOW_T_IN2",
|
||||
"TOW_T_TY3",
|
||||
"TOW_T_IN3",
|
||||
"TOW_T_TY4",
|
||||
"TOW_T_IN4",
|
||||
"TOW_T_TY5",
|
||||
"TOW_T_IN5",
|
||||
"TOW_T_TY6",
|
||||
"TOW_T_IN6",
|
||||
"TX_STOR_TY",
|
||||
"STOR_T_TY1",
|
||||
"STOR_T_IN1",
|
||||
"STOR_T_TY2",
|
||||
"STOR_T_IN2",
|
||||
"STOR_T_TY3",
|
||||
"STOR_T_IN3",
|
||||
"STOR_T_TY4",
|
||||
"STOR_T_IN4",
|
||||
"STOR_T_TY5",
|
||||
"STOR_T_IN5",
|
||||
"STOR_T_TY6",
|
||||
"STOR_T_IN6",
|
||||
]),
|
||||
),
|
||||
);
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
|
||||
return { cieca_pfo: rawPfoData };
|
||||
};
|
||||
export default DecodePfo;
|
||||
37
src/main/decoder/decode-pfp.interface.ts
Normal file
37
src/main/decoder/decode-pfp.interface.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export interface DecodedPfpLine {
|
||||
prt_type: string;
|
||||
prt_tax_in: boolean;
|
||||
prt_tax_rt: number;
|
||||
prt_mkupp: number;
|
||||
prt_mktyp: string;
|
||||
prt_discp: number;
|
||||
prt_tx_ty1: string;
|
||||
prt_tx_in1: boolean;
|
||||
prt_tx_ty2: string;
|
||||
prt_tx_in2: boolean;
|
||||
prt_tx_ty3: string;
|
||||
prt_tx_in3: boolean;
|
||||
prt_tx_ty4: string;
|
||||
prt_tx_in4: boolean;
|
||||
prt_tx_ty5: string;
|
||||
prt_tx_in5: boolean;
|
||||
}
|
||||
|
||||
export interface DecodedPfpLinesByType {
|
||||
PAA: DecodedPfpLine;
|
||||
PAC: DecodedPfpLine;
|
||||
PAL: DecodedPfpLine;
|
||||
PAG: DecodedPfpLine;
|
||||
PAM: DecodedPfpLine;
|
||||
PAP: DecodedPfpLine;
|
||||
PAN: DecodedPfpLine;
|
||||
PAO: DecodedPfpLine;
|
||||
PAR: DecodedPfpLine;
|
||||
PAS: DecodedPfpLine;
|
||||
PASL: DecodedPfpLine;
|
||||
PAT: DecodedPfpLine;
|
||||
}
|
||||
|
||||
export interface DecodedPfp {
|
||||
parts_tax_rates: DecodedPfpLinesByType;
|
||||
}
|
||||
98
src/main/decoder/decode-pfp.ts
Normal file
98
src/main/decoder/decode-pfp.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import YNBoolConverter from "../../util/ynBoolConverter";
|
||||
import {
|
||||
DecodedPfp,
|
||||
DecodedPfpLine,
|
||||
DecodedPfpLinesByType,
|
||||
} from "./decode-pfp.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodePfp = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedPfp> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFP`);
|
||||
} catch (error) {
|
||||
//PFP File only has 1 location.
|
||||
log.error("Error opening PFP File.", errorTypeCheck(error));
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any PFP files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFP files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = [".pfp"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any PFP files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFP files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFP File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const rawDBFRecord = await dbf.readRecords();
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const rawPfpData: DecodedPfpLine[] = rawDBFRecord.map((record) => {
|
||||
const singleLineData: DecodedPfpLine = deepLowerCaseKeys(
|
||||
_.pick(record, [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"PRT_TYPE",
|
||||
"PRT_TAX_IN",
|
||||
"PRT_TAX_RT",
|
||||
"PRT_MKUPP",
|
||||
"PRT_MKTYP",
|
||||
"PRT_DISCP",
|
||||
"PRT_TX_TY1",
|
||||
"PRT_TX_IN1",
|
||||
"PRT_TX_TY2",
|
||||
"PRT_TX_IN2",
|
||||
"PRT_TX_TY3",
|
||||
"PRT_TX_IN3",
|
||||
"PRT_TX_TY4",
|
||||
"PRT_TX_IN4",
|
||||
"PRT_TX_TY5",
|
||||
"PRT_TX_IN5",
|
||||
]),
|
||||
);
|
||||
|
||||
singleLineData.prt_tax_rt = singleLineData.prt_tax_rt / 100;
|
||||
return YNBoolConverter(singleLineData);
|
||||
});
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
|
||||
//Convert array of lines to a hash object.
|
||||
const parsedPfpFile: DecodedPfpLinesByType = rawPfpData.reduce(
|
||||
(acc: DecodedPfpLinesByType, line: DecodedPfpLine) => {
|
||||
acc[line.prt_type] = line;
|
||||
return acc;
|
||||
},
|
||||
{} as DecodedPfpLinesByType,
|
||||
);
|
||||
|
||||
return { parts_tax_rates: parsedPfpFile };
|
||||
};
|
||||
|
||||
export default DecodePfp;
|
||||
147
src/main/decoder/decode-pft.interface.ts
Normal file
147
src/main/decoder/decode-pft.interface.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Interface representing decoded data from a PFT file
|
||||
* Contains tax type information with up to 6 tax types and 5 tiers each
|
||||
*/
|
||||
export interface DecodedPftLine {
|
||||
// Tax Type 1
|
||||
tax_type1?: string;
|
||||
ty1_tier1?: number;
|
||||
ty1_thres1?: number;
|
||||
ty1_rate1?: number;
|
||||
ty1_sur1?: number;
|
||||
ty1_tier2?: number;
|
||||
ty1_thres2?: number;
|
||||
ty1_rate2?: number;
|
||||
ty1_sur2?: number;
|
||||
ty1_tier3?: number;
|
||||
ty1_thres3?: number;
|
||||
ty1_rate3?: number;
|
||||
ty1_sur3?: number;
|
||||
ty1_tier4?: number;
|
||||
ty1_thres4?: number;
|
||||
ty1_rate4?: number;
|
||||
ty1_sur4?: number;
|
||||
ty1_tier5?: number;
|
||||
ty1_thres5?: number;
|
||||
ty1_rate5?: number;
|
||||
ty1_sur5?: number;
|
||||
|
||||
// Tax Type 2
|
||||
tax_type2?: string;
|
||||
ty2_tier1?: number;
|
||||
ty2_thres1?: number;
|
||||
ty2_rate1?: number;
|
||||
ty2_sur1?: number;
|
||||
ty2_tier2?: number;
|
||||
ty2_thres2?: number;
|
||||
ty2_rate2?: number;
|
||||
ty2_sur2?: number;
|
||||
ty2_tier3?: number;
|
||||
ty2_thres3?: number;
|
||||
ty2_rate3?: number;
|
||||
ty2_sur3?: number;
|
||||
ty2_tier4?: number;
|
||||
ty2_thres4?: number;
|
||||
ty2_rate4?: number;
|
||||
ty2_sur4?: number;
|
||||
ty2_tier5?: number;
|
||||
ty2_thres5?: number;
|
||||
ty2_rate5?: number;
|
||||
ty2_sur5?: number;
|
||||
|
||||
// Tax Type 3
|
||||
tax_type3?: string;
|
||||
ty3_tier1?: number;
|
||||
ty3_thres1?: number;
|
||||
ty3_rate1?: number;
|
||||
ty3_sur1?: number;
|
||||
ty3_tier2?: number;
|
||||
ty3_thres2?: number;
|
||||
ty3_rate2?: number;
|
||||
ty3_sur2?: number;
|
||||
ty3_tier3?: number;
|
||||
ty3_thres3?: number;
|
||||
ty3_rate3?: number;
|
||||
ty3_sur3?: number;
|
||||
ty3_tier4?: number;
|
||||
ty3_thres4?: number;
|
||||
ty3_rate4?: number;
|
||||
ty3_sur4?: number;
|
||||
ty3_tier5?: number;
|
||||
ty3_thres5?: number;
|
||||
ty3_rate5?: number;
|
||||
ty3_sur5?: number;
|
||||
|
||||
// Tax Type 4
|
||||
tax_type4?: string;
|
||||
ty4_tier1?: number;
|
||||
ty4_thres1?: number;
|
||||
ty4_rate1?: number;
|
||||
ty4_sur1?: number;
|
||||
ty4_tier2?: number;
|
||||
ty4_thres2?: number;
|
||||
ty4_rate2?: number;
|
||||
ty4_sur2?: number;
|
||||
ty4_tier3?: number;
|
||||
ty4_thres3?: number;
|
||||
ty4_rate3?: number;
|
||||
ty4_sur3?: number;
|
||||
ty4_tier4?: number;
|
||||
ty4_thres4?: number;
|
||||
ty4_rate4?: number;
|
||||
ty4_sur4?: number;
|
||||
ty4_tier5?: number;
|
||||
ty4_thres5?: number;
|
||||
ty4_rate5?: number;
|
||||
ty4_sur5?: number;
|
||||
|
||||
// Tax Type 5
|
||||
tax_type5?: string;
|
||||
ty5_tier1?: number;
|
||||
ty5_thres1?: number;
|
||||
ty5_rate1?: number;
|
||||
ty5_sur1?: number;
|
||||
ty5_tier2?: number;
|
||||
ty5_thres2?: number;
|
||||
ty5_rate2?: number;
|
||||
ty5_sur2?: number;
|
||||
ty5_tier3?: number;
|
||||
ty5_thres3?: number;
|
||||
ty5_rate3?: number;
|
||||
ty5_sur3?: number;
|
||||
ty5_tier4?: number;
|
||||
ty5_thres4?: number;
|
||||
ty5_rate4?: number;
|
||||
ty5_sur4?: number;
|
||||
ty5_tier5?: number;
|
||||
ty5_thres5?: number;
|
||||
ty5_rate5?: number;
|
||||
ty5_sur5?: number;
|
||||
|
||||
// Tax Type 6
|
||||
tax_type6?: string;
|
||||
ty6_tier1?: number;
|
||||
ty6_thres1?: number;
|
||||
ty6_rate1?: number;
|
||||
ty6_sur1?: number;
|
||||
ty6_tier2?: number;
|
||||
ty6_thres2?: number;
|
||||
ty6_rate2?: number;
|
||||
ty6_sur2?: number;
|
||||
ty6_tier3?: number;
|
||||
ty6_thres3?: number;
|
||||
ty6_rate3?: number;
|
||||
ty6_sur3?: number;
|
||||
ty6_tier4?: number;
|
||||
ty6_thres4?: number;
|
||||
ty6_rate4?: number;
|
||||
ty6_sur4?: number;
|
||||
ty6_tier5?: number;
|
||||
ty6_thres5?: number;
|
||||
ty6_rate5?: number;
|
||||
ty6_sur5?: number;
|
||||
}
|
||||
|
||||
export interface DecodedPft {
|
||||
cieca_pft: DecodedPftLine;
|
||||
}
|
||||
189
src/main/decoder/decode-pft.ts
Normal file
189
src/main/decoder/decode-pft.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedPft, DecodedPftLine } from "./decode-pft.interface";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodePft = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedPft> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.PFT`);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFH File.", errorTypeCheck(error));
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any PFT files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFT files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = ["pft"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any PFT files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any PFT files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening PFT File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//PFT will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const rawPftData: DecodedPftLine = deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"TAX_TYPE1", //The below is is taken from a CCC estimate. Will require validation to ensure it is also accurate for Audatex/Mitchell
|
||||
"TY1_TIER1",
|
||||
"TY1_THRES1",
|
||||
"TY1_RATE1",
|
||||
"TY1_SUR1",
|
||||
"TY1_TIER2",
|
||||
"TY1_THRES2",
|
||||
"TY1_RATE2",
|
||||
"TY1_SUR2",
|
||||
"TY1_TIER3",
|
||||
"TY1_THRES3",
|
||||
"TY1_RATE3",
|
||||
"TY1_SUR3",
|
||||
"TY1_TIER4",
|
||||
"TY1_THRES4",
|
||||
"TY1_RATE4",
|
||||
"TY1_SUR4",
|
||||
"TY1_TIER5",
|
||||
"TY1_THRES5",
|
||||
"TY1_RATE5",
|
||||
"TY1_SUR5",
|
||||
"TAX_TYPE2",
|
||||
"TY2_TIER1",
|
||||
"TY2_THRES1",
|
||||
"TY2_RATE1",
|
||||
"TY2_SUR1",
|
||||
"TY2_TIER2",
|
||||
"TY2_THRES2",
|
||||
"TY2_RATE2",
|
||||
"TY2_SUR2",
|
||||
"TY2_TIER3",
|
||||
"TY2_THRES3",
|
||||
"TY2_RATE3",
|
||||
"TY2_SUR3",
|
||||
"TY2_TIER4",
|
||||
"TY2_THRES4",
|
||||
"TY2_RATE4",
|
||||
"TY2_SUR4",
|
||||
"TY2_TIER5",
|
||||
"TY2_THRES5",
|
||||
"TY2_RATE5",
|
||||
"TY2_SUR5",
|
||||
"TAX_TYPE3",
|
||||
"TY3_TIER1",
|
||||
"TY3_THRES1",
|
||||
"TY3_RATE1",
|
||||
"TY3_SUR1",
|
||||
"TY3_TIER2",
|
||||
"TY3_THRES2",
|
||||
"TY3_RATE2",
|
||||
"TY3_SUR2",
|
||||
"TY3_TIER3",
|
||||
"TY3_THRES3",
|
||||
"TY3_RATE3",
|
||||
"TY3_SUR3",
|
||||
"TY3_TIER4",
|
||||
"TY3_THRES4",
|
||||
"TY3_RATE4",
|
||||
"TY3_SUR4",
|
||||
"TY3_TIER5",
|
||||
"TY3_THRES5",
|
||||
"TY3_RATE5",
|
||||
"TY3_SUR5",
|
||||
"TAX_TYPE4",
|
||||
"TY4_TIER1",
|
||||
"TY4_THRES1",
|
||||
"TY4_RATE1",
|
||||
"TY4_SUR1",
|
||||
"TY4_TIER2",
|
||||
"TY4_THRES2",
|
||||
"TY4_RATE2",
|
||||
"TY4_SUR2",
|
||||
"TY4_TIER3",
|
||||
"TY4_THRES3",
|
||||
"TY4_RATE3",
|
||||
"TY4_SUR3",
|
||||
"TY4_TIER4",
|
||||
"TY4_THRES4",
|
||||
"TY4_RATE4",
|
||||
"TY4_SUR4",
|
||||
"TY4_TIER5",
|
||||
"TY4_THRES5",
|
||||
"TY4_RATE5",
|
||||
"TY4_SUR5",
|
||||
"TAX_TYPE5",
|
||||
"TY5_TIER1",
|
||||
"TY5_THRES1",
|
||||
"TY5_RATE1",
|
||||
"TY5_SUR1",
|
||||
"TY5_TIER2",
|
||||
"TY5_THRES2",
|
||||
"TY5_RATE2",
|
||||
"TY5_SUR2",
|
||||
"TY5_TIER3",
|
||||
"TY5_THRES3",
|
||||
"TY5_RATE3",
|
||||
"TY5_SUR3",
|
||||
"TY5_TIER4",
|
||||
"TY5_THRES4",
|
||||
"TY5_RATE4",
|
||||
"TY5_SUR4",
|
||||
"TY5_TIER5",
|
||||
"TY5_THRES5",
|
||||
"TY5_RATE5",
|
||||
"TY5_SUR5",
|
||||
"TAX_TYPE6",
|
||||
"TY6_TIER1",
|
||||
"TY6_THRES1",
|
||||
"TY6_RATE1",
|
||||
"TY6_SUR1",
|
||||
"TY6_TIER2",
|
||||
"TY6_THRES2",
|
||||
"TY6_RATE2",
|
||||
"TY6_SUR2",
|
||||
"TY6_TIER3",
|
||||
"TY6_THRES3",
|
||||
"TY6_RATE3",
|
||||
"TY6_SUR3",
|
||||
"TY6_TIER4",
|
||||
"TY6_THRES4",
|
||||
"TY6_RATE4",
|
||||
"TY6_SUR4",
|
||||
"TY6_TIER5",
|
||||
"TY6_THRES5",
|
||||
"TY6_RATE5",
|
||||
"TY6_SUR5",
|
||||
]),
|
||||
);
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
//We don't have an inspection date, we instead have `date_estimated`
|
||||
|
||||
return { cieca_pft: rawPftData };
|
||||
};
|
||||
export default DecodePft;
|
||||
23
src/main/decoder/decode-stl.interface.ts
Normal file
23
src/main/decoder/decode-stl.interface.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface DecodedStlLine {
|
||||
ttl_type?: string;
|
||||
ttl_typecd?: string;
|
||||
t_amt?: number;
|
||||
t_hrs?: number;
|
||||
t_addlbr?: number;
|
||||
t_discamt?: number;
|
||||
t_mkupamt?: number;
|
||||
t_gdiscamt?: number;
|
||||
tax_amt?: number;
|
||||
nt_amt?: number;
|
||||
nt_hrs?: number;
|
||||
nt_addlbr?: number;
|
||||
nt_disc?: number;
|
||||
nt_mkup?: number;
|
||||
nt_gdis?: number;
|
||||
ttl_typamt?: number;
|
||||
ttl_hrs?: number;
|
||||
ttl_amt?: number;
|
||||
}
|
||||
export interface DecodedStl {
|
||||
cieca_stl: { data: DecodedStlLine[] };
|
||||
}
|
||||
85
src/main/decoder/decode-stl.ts
Normal file
85
src/main/decoder/decode-stl.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedStl, DecodedStlLine } from "./decode-stl.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodeStl = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedStl> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.STL`);
|
||||
} catch (error) {
|
||||
log.error("Error opening STL File.", errorTypeCheck(error));
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any STL files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any STL files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = ["stl"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any STL files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any STL files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening STL File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const rawDBFRecord = await dbf.readRecords();
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const rawStlData: DecodedStlLine[] = rawDBFRecord.map((record) => {
|
||||
const singleLineData: DecodedStlLine = deepLowerCaseKeys(
|
||||
_.pick(record, [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"TTL_TYPE",
|
||||
"TTL_TYPECD",
|
||||
"T_AMT",
|
||||
"T_HRS",
|
||||
"T_ADDLBR",
|
||||
"T_DISCAMT",
|
||||
"T_MKUPAMT",
|
||||
"T_GDISCAMT",
|
||||
"TAX_AMT",
|
||||
"NT_AMT",
|
||||
"NT_HRS",
|
||||
"NT_ADDLBR",
|
||||
"NT_DISC",
|
||||
"NT_MKUP",
|
||||
"NT_GDIS",
|
||||
"TTL_TYPAMT",
|
||||
"TTL_HRS",
|
||||
"TTL_AMT",
|
||||
]),
|
||||
);
|
||||
//Apply line by line adjustments.
|
||||
|
||||
return singleLineData;
|
||||
});
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
//We don't have an inspection date, we instead have `date_estimated`
|
||||
|
||||
return { cieca_stl: { data: rawStlData } };
|
||||
};
|
||||
export default DecodeStl;
|
||||
22
src/main/decoder/decode-ttl.interface.ts
Normal file
22
src/main/decoder/decode-ttl.interface.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export interface DecodedTtl {
|
||||
clm_total: number;
|
||||
depreciation_taxes: number;
|
||||
cieca_ttl: { data: DecodedTtlLine };
|
||||
}
|
||||
|
||||
export interface DecodedTtlLine {
|
||||
g_ttl_amt?: number;
|
||||
g_bett_amt?: number;
|
||||
g_rpd_amt?: number;
|
||||
g_ded_amt?: number;
|
||||
g_cust_amt?: number;
|
||||
g_aa_amt?: number;
|
||||
n_ttl_amt?: number;
|
||||
prev_net?: number;
|
||||
supp_amt?: number;
|
||||
n_supp_amt?: number;
|
||||
g_upd_amt?: number;
|
||||
g_ttl_disc?: number;
|
||||
g_tax?: number;
|
||||
gst_amt?: number;
|
||||
}
|
||||
80
src/main/decoder/decode-ttl.ts
Normal file
80
src/main/decoder/decode-ttl.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedTtl, DecodedTtlLine } from "./decode-ttl.interface";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodeTtl = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedTtl> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}.TTL`);
|
||||
} catch (error) {
|
||||
log.error("Error opening TTL File.", errorTypeCheck(error));
|
||||
}
|
||||
|
||||
if (!dbf) {
|
||||
log.error(`Could not find any TTL files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any TTL files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = ["ttl"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any TTL files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any TTL files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath);
|
||||
} catch (error) {
|
||||
log.error("Error opening TTL File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//PFT will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
const rawTtlData: DecodedTtlLine = deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"G_TTL_AMT",
|
||||
"G_BETT_AMT",
|
||||
"G_RPD_AMT",
|
||||
"G_DED_AMT",
|
||||
"G_CUST_AMT",
|
||||
"G_AA_AMT",
|
||||
"N_TTL_AMT",
|
||||
"PREV_NET",
|
||||
"SUPP_AMT",
|
||||
"N_SUPP_AMT", //Previously commented. Possible issue.
|
||||
"G_UPD_AMT",
|
||||
"G_TTL_DISC",
|
||||
"G_TAX",
|
||||
"GST_AMT",
|
||||
]),
|
||||
);
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
|
||||
return {
|
||||
clm_total: rawTtlData.g_ttl_amt || 0,
|
||||
depreciation_taxes: rawTtlData.g_bett_amt || 0, //TODO: Find where this needs to be filled from
|
||||
cieca_ttl: { data: rawTtlData },
|
||||
};
|
||||
};
|
||||
export default DecodeTtl;
|
||||
64
src/main/decoder/decode-veh.interface.ts
Normal file
64
src/main/decoder/decode-veh.interface.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
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;
|
||||
area_of_damage?: {
|
||||
impact1?: string;
|
||||
impact2?: string;
|
||||
};
|
||||
// Complete vehicle data object
|
||||
vehicle?: { data: VehicleRecordInterface };
|
||||
}
|
||||
|
||||
export interface VehicleRecordInterface {
|
||||
// Area of damage information
|
||||
area_of_damage?: {
|
||||
impact1?: string;
|
||||
impact2?: string;
|
||||
};
|
||||
// Paint code information
|
||||
v_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?: number; //TODO: This can sometimes come in as UNK.
|
||||
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;
|
||||
}
|
||||
145
src/main/decoder/decode-veh.ts
Normal file
145
src/main/decoder/decode-veh.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
|
||||
import { DecodedVeh, VehicleRecordInterface } from "./decode-veh.interface";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import store from "../store/store";
|
||||
import typeCaster from "../../util/typeCaster";
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { findFileCaseInsensitive } from "./decoder-utils";
|
||||
|
||||
const DecodeVeh = async (
|
||||
extensionlessFilePath: string,
|
||||
): Promise<DecodedVeh> => {
|
||||
let dbf: DBFFile | null = null;
|
||||
if (platform.isWindows) {
|
||||
try {
|
||||
dbf = await DBFFile.open(`${extensionlessFilePath}V.VEH`);
|
||||
} catch (error) {
|
||||
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 VEH files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any VEH files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const possibleExtensions: string[] = ["v.veh", ".veh"];
|
||||
const filePath = await findFileCaseInsensitive(
|
||||
extensionlessFilePath,
|
||||
possibleExtensions,
|
||||
);
|
||||
try {
|
||||
if (!filePath) {
|
||||
log.error(`Could not find any VEH files at ${extensionlessFilePath}`);
|
||||
throw new Error(
|
||||
`Could not find any VEH files at ${extensionlessFilePath}`,
|
||||
);
|
||||
}
|
||||
dbf = await DBFFile.open(filePath, { readMode: "loose" });
|
||||
} catch (error) {
|
||||
log.error("Error opening VEH File.", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const rawDBFRecord = await dbf.readRecords(1);
|
||||
|
||||
//AD2 will always have only 1 row.
|
||||
//Commented lines have been cross referenced with existing partner fields.
|
||||
|
||||
//typeCaster is required as the previous partner sent some of these values toString, and the database was made accordingly rather than keeping their original type.
|
||||
//Alternative is to change the database schema to match the original type.
|
||||
const rawVehData: VehicleRecordInterface = typeCaster(
|
||||
deepLowerCaseKeys(
|
||||
_.pick(rawDBFRecord[0], [
|
||||
//TODO: Add typings for EMS File Formats.
|
||||
"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",
|
||||
]),
|
||||
),
|
||||
{
|
||||
v_tone: "string",
|
||||
v_stage: "string",
|
||||
},
|
||||
);
|
||||
|
||||
//Apply business logic transfomrations.
|
||||
|
||||
//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.
|
||||
const area_of_damage = {
|
||||
impact1: rawVehData.impact_1 ?? "",
|
||||
impact2: rawVehData.impact_2 ?? "",
|
||||
};
|
||||
delete rawVehData.impact_1;
|
||||
delete rawVehData.impact_2;
|
||||
|
||||
const kmin = rawVehData.v_mileage ?? 0;
|
||||
delete rawVehData.v_mileage;
|
||||
|
||||
//Consolidate Paint Code information.
|
||||
rawVehData.v_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 = store.get("app.bodyshop.id");
|
||||
|
||||
//Aggregate the vehicle data to be stamped onto the job record.
|
||||
const jobVehicleData: DecodedVeh = {
|
||||
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: kmin,
|
||||
area_of_damage: area_of_damage,
|
||||
vehicle: {
|
||||
data: rawVehData,
|
||||
},
|
||||
};
|
||||
|
||||
return jobVehicleData;
|
||||
};
|
||||
|
||||
export default DecodeVeh;
|
||||
47
src/main/decoder/decoder-utils.ts
Normal file
47
src/main/decoder/decoder-utils.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import log from "electron-log/main";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const findFileCaseInsensitive = async (
|
||||
extensionlessFilePath: string,
|
||||
extensions: string[],
|
||||
): Promise<string | null> => {
|
||||
const directory: string = path.dirname(extensionlessFilePath);
|
||||
try {
|
||||
const matchingFiles = fs.readdirSync(directory).filter((file: string) => {
|
||||
return (
|
||||
extensions.some((ext) =>
|
||||
file.toLowerCase().endsWith(ext.toLowerCase()),
|
||||
) &&
|
||||
path
|
||||
.basename(file, path.extname(file))
|
||||
.toLowerCase()
|
||||
.startsWith(path.basename(extensionlessFilePath).toLowerCase())
|
||||
);
|
||||
});
|
||||
const files: string[] = [];
|
||||
matchingFiles.forEach((file) => {
|
||||
const fullPath = path.join(directory, file);
|
||||
files.push(fullPath);
|
||||
});
|
||||
|
||||
// Return the first matching file if needed
|
||||
if (files.length > 0) {
|
||||
return files[0];
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Failed to read directory ${directory}:`, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const getFilePathWithoutExtension = (filePath: string): string => {
|
||||
return path.join(
|
||||
path.dirname(filePath),
|
||||
path.basename(filePath, path.extname(filePath)),
|
||||
);
|
||||
};
|
||||
|
||||
export { findFileCaseInsensitive };
|
||||
431
src/main/decoder/decoder.ts
Normal file
431
src/main/decoder/decoder.ts
Normal file
@@ -0,0 +1,431 @@
|
||||
import { platform } from "@electron-toolkit/utils";
|
||||
import { UUID } from "crypto";
|
||||
import { Notification, shell } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import fs from "fs";
|
||||
import _ from "lodash";
|
||||
import path from "path";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import client from "../graphql/graphql-client";
|
||||
import {
|
||||
INSERT_AVAILABLE_JOB_TYPED,
|
||||
InsertAvailableJobResult,
|
||||
QUERY_JOB_BY_CLM_NO_TYPED,
|
||||
QUERY_VEHICLE_BY_VIN_TYPED,
|
||||
QueryJobByClmNoResult,
|
||||
VehicleQueryResult,
|
||||
} from "../graphql/queries";
|
||||
import store from "../store/store";
|
||||
import DecodeAD1 from "./decode-ad1";
|
||||
import { DecodedAd1 } from "./decode-ad1.interface";
|
||||
import DecodeAD2 from "./decode-ad2";
|
||||
import { DecodedAD2 } from "./decode-ad2.interface";
|
||||
import DecodeEnv from "./decode-env";
|
||||
import { DecodedEnv } from "./decode-env.interface";
|
||||
import DecodeLin from "./decode-lin";
|
||||
import { DecodedLin } from "./decode-lin.interface";
|
||||
import DecodePfh from "./decode-pfh";
|
||||
import { DecodedPfh } from "./decode-pfh.interface";
|
||||
import DecodePfl from "./decode-pfl";
|
||||
import { DecodedPfl } from "./decode-pfl.interface";
|
||||
import DecodePfm from "./decode-pfm";
|
||||
import { DecodedPfm } from "./decode-pfm.interface";
|
||||
import DecodePfo from "./decode-pfo";
|
||||
import { DecodedPfo } from "./decode-pfo.interface";
|
||||
import DecodePfp from "./decode-pfp";
|
||||
import { DecodedPfp } from "./decode-pfp.interface";
|
||||
import DecodePft from "./decode-pft";
|
||||
import { DecodedPft } from "./decode-pft.interface";
|
||||
import DecodeStl from "./decode-stl";
|
||||
import { DecodedStl } from "./decode-stl.interface";
|
||||
import DecodeTtl from "./decode-ttl";
|
||||
import { DecodedTtl } from "./decode-ttl.interface";
|
||||
import DecodeVeh from "./decode-veh";
|
||||
import { DecodedVeh } from "./decode-veh.interface";
|
||||
import setAppProgressbar from "../util/setAppProgressBar";
|
||||
import UploadEmsToS3 from "./emsbackup";
|
||||
|
||||
async function ImportJob(filepath: string): Promise<void> {
|
||||
const parsedFilePath = path.parse(filepath);
|
||||
const extensionlessFilePath = path.join(
|
||||
parsedFilePath.dir,
|
||||
parsedFilePath.name,
|
||||
);
|
||||
log.debug("Importing Job", extensionlessFilePath);
|
||||
|
||||
try {
|
||||
await WaitForAllFiles(extensionlessFilePath, requiredExtensions);
|
||||
|
||||
//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.
|
||||
setAppProgressbar(0.1);
|
||||
const env: DecodedEnv = await DecodeEnv(extensionlessFilePath);
|
||||
setAppProgressbar(0.15);
|
||||
const ad1: DecodedAd1 = await DecodeAD1(extensionlessFilePath);
|
||||
setAppProgressbar(0.2);
|
||||
const ad2: DecodedAD2 = await DecodeAD2(extensionlessFilePath);
|
||||
setAppProgressbar(0.25);
|
||||
const veh: DecodedVeh = await DecodeVeh(extensionlessFilePath);
|
||||
setAppProgressbar(0.3);
|
||||
const lin: DecodedLin = await DecodeLin(extensionlessFilePath);
|
||||
setAppProgressbar(0.35);
|
||||
const pfh: DecodedPfh = await DecodePfh(extensionlessFilePath);
|
||||
setAppProgressbar(0.4);
|
||||
const pfl: DecodedPfl = await DecodePfl(extensionlessFilePath);
|
||||
setAppProgressbar(0.45);
|
||||
const pft: DecodedPft = await DecodePft(extensionlessFilePath);
|
||||
setAppProgressbar(0.5);
|
||||
const pfm: DecodedPfm = await DecodePfm(extensionlessFilePath);
|
||||
setAppProgressbar(0.55);
|
||||
const pfo: DecodedPfo = await DecodePfo(extensionlessFilePath); // TODO: This will be the `cieca_pfo` object
|
||||
setAppProgressbar(0.6);
|
||||
const stl: DecodedStl = await DecodeStl(extensionlessFilePath); // TODO: This will be the `cieca_stl` object
|
||||
setAppProgressbar(0.65);
|
||||
const ttl: DecodedTtl = await DecodeTtl(extensionlessFilePath);
|
||||
setAppProgressbar(0.7);
|
||||
const pfp: DecodedPfp = await DecodePfp(extensionlessFilePath);
|
||||
setAppProgressbar(0.75);
|
||||
|
||||
const jobObjectUncleaned: RawJobDataObject = {
|
||||
...env,
|
||||
...ad1,
|
||||
...ad2,
|
||||
...veh,
|
||||
...lin,
|
||||
...pfh,
|
||||
...pfl,
|
||||
...pft,
|
||||
...pfm,
|
||||
...pfo,
|
||||
...stl,
|
||||
...ttl,
|
||||
...pfp,
|
||||
shopid: store.get("app.bodyshop.id") as UUID,
|
||||
};
|
||||
|
||||
// Replace owner information with claimant information if necessary
|
||||
const jobObject = ReplaceOwnerInfoWithClaimant(jobObjectUncleaned);
|
||||
setAppProgressbar(0.8);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// Save jobObject to a timestamped JSON file
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.replace(/:/g, "-")
|
||||
.replace(/\..+/, "");
|
||||
const fileName = `job_${timestamp}_${parsedFilePath.name}.json`;
|
||||
const logsDir = path.join(process.cwd(), "logs");
|
||||
|
||||
// Create logs directory if it doesn't exist
|
||||
if (!fs.existsSync(logsDir)) {
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const filePath = path.join(logsDir, fileName);
|
||||
fs.writeFileSync(filePath, JSON.stringify(jobObject, null, 2), "utf8");
|
||||
log.info(`Job data saved to: ${filePath}`);
|
||||
}
|
||||
|
||||
const newAvailableJob: AvailableJobSchema = {
|
||||
uploaded_by: store.get("user.email"),
|
||||
bodyshopid: store.get("app.bodyshop.id"),
|
||||
cieca_id: jobObject.ciecaid,
|
||||
est_data: jobObject,
|
||||
ownr_name: `${jobObject.ownr_fn} ${jobObject.ownr_ln} ${jobObject.ownr_co_nm}`,
|
||||
ins_co_nm: jobObject.ins_co_nm,
|
||||
vehicle_info: `${jobObject.v_model_yr} ${jobObject.v_make_desc} ${jobObject.v_model_desc}`,
|
||||
clm_no: jobObject.clm_no,
|
||||
clm_amt: jobObject.clm_total,
|
||||
// source_system: jobObject.source_system, //TODO: Add back source system if needed.
|
||||
issupplement: false,
|
||||
jobid: null,
|
||||
};
|
||||
setAppProgressbar(0.85);
|
||||
|
||||
const existingVehicleRecord: VehicleQueryResult = await client.request(
|
||||
QUERY_VEHICLE_BY_VIN_TYPED,
|
||||
{
|
||||
vin: jobObject.v_vin,
|
||||
},
|
||||
);
|
||||
|
||||
if (existingVehicleRecord.vehicles.length > 0) {
|
||||
delete newAvailableJob.est_data.vehicle;
|
||||
newAvailableJob.est_data.vehicleid = existingVehicleRecord.vehicles[0].id;
|
||||
}
|
||||
|
||||
console.log("Available Job record to upload;", newAvailableJob);
|
||||
|
||||
setAppProgressbar(0.95);
|
||||
if (jobObject.clm_no) {
|
||||
const existingJobRecord: QueryJobByClmNoResult = await client.request(
|
||||
QUERY_JOB_BY_CLM_NO_TYPED,
|
||||
{ clm_no: jobObject.clm_no },
|
||||
);
|
||||
|
||||
if (existingJobRecord.jobs.length > 0) {
|
||||
newAvailableJob.issupplement = true;
|
||||
newAvailableJob.jobid = existingJobRecord.jobs[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
const insertRecordResult: InsertAvailableJobResult = await client.request(
|
||||
INSERT_AVAILABLE_JOB_TYPED,
|
||||
{
|
||||
jobInput: [newAvailableJob],
|
||||
},
|
||||
);
|
||||
setAppProgressbar(-1);
|
||||
const uploadNotification = new Notification({
|
||||
title: "Job Imported",
|
||||
//subtitle: `${newAvailableJob.ownr_name} - ${newAvailableJob.vehicle_info}`,
|
||||
body: `${newAvailableJob.ownr_name} - ${newAvailableJob.vehicle_info}. Click to view.`,
|
||||
actions: [{ text: "View Job", type: "button" }],
|
||||
});
|
||||
uploadNotification.on("click", () => {
|
||||
shell.openExternal(
|
||||
`${
|
||||
store.get("app.isTest")
|
||||
? import.meta.env.VITE_FE_URL_TEST
|
||||
: import.meta.env.VITE_FE_URL
|
||||
}/manage/available`,
|
||||
);
|
||||
});
|
||||
uploadNotification.show();
|
||||
|
||||
log.debug("Job inserted", insertRecordResult);
|
||||
|
||||
UploadEmsToS3({
|
||||
extensionlessFilePath,
|
||||
bodyshopid: newAvailableJob.bodyshopid,
|
||||
ciecaid: jobObject.ciecaid ?? "",
|
||||
clm_no: jobObject.clm_no ?? "",
|
||||
ownr_ln: jobObject.ownr_ln ?? "",
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Error encountered while decoding job. ", errorTypeCheck(error));
|
||||
const uploadNotificationFailure = new Notification({
|
||||
title: "Job Upload Failure",
|
||||
body: errorTypeCheck(error).message, //TODO: Remove after debug.
|
||||
});
|
||||
|
||||
uploadNotificationFailure.show();
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportJob;
|
||||
|
||||
export interface RawJobDataObject
|
||||
extends DecodedEnv,
|
||||
DecodedAd1,
|
||||
DecodedAD2,
|
||||
DecodedVeh,
|
||||
DecodedLin,
|
||||
DecodedPfh,
|
||||
DecodedPfl,
|
||||
DecodedPft,
|
||||
DecodedPfm,
|
||||
DecodedPfo,
|
||||
DecodedStl,
|
||||
DecodedTtl,
|
||||
DecodedPfp {
|
||||
vehicleid?: UUID;
|
||||
shopid: UUID;
|
||||
}
|
||||
|
||||
export interface AvailableJobSchema {
|
||||
uploaded_by: string;
|
||||
bodyshopid: UUID;
|
||||
cieca_id?: string;
|
||||
est_data: RawJobDataObject;
|
||||
ownr_name: string;
|
||||
ins_co_nm?: string;
|
||||
vehicle_info: string;
|
||||
clm_no?: string;
|
||||
clm_amt: number;
|
||||
source_system?: string | null;
|
||||
issupplement: boolean;
|
||||
jobid: UUID | null;
|
||||
}
|
||||
|
||||
async function WaitForAllFiles(
|
||||
baseFilePath: string,
|
||||
requiredExtensions: string[],
|
||||
maxRetries: number = 5,
|
||||
backoffMs: number = 1000,
|
||||
): Promise<void> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
//Get all files in directory if Mac.
|
||||
let filesInDir: string[] = [];
|
||||
if (platform.isMacOS) {
|
||||
const dir: string = path.dirname(baseFilePath);
|
||||
filesInDir = fs.readdirSync(dir).map((file) => file.toLowerCase());
|
||||
}
|
||||
|
||||
const missingFiles = requiredExtensions.filter((ext) => {
|
||||
const filePath: string = `${baseFilePath}.${ext}`;
|
||||
const filePathA: string = `${baseFilePath}A.${ext}`;
|
||||
const filePathB: string = `${baseFilePath}B.${ext}`;
|
||||
const filePathV: string = `${baseFilePath}V.${ext}`;
|
||||
|
||||
if (!platform.isWindows) {
|
||||
// Case-insensitive check for macOS/Linux
|
||||
const baseName: string = path.basename(baseFilePath);
|
||||
|
||||
return !(
|
||||
filesInDir.includes(`${baseName}.${ext}`.toLowerCase()) ||
|
||||
filesInDir.includes(`${baseName}A.${ext}`.toLowerCase()) ||
|
||||
filesInDir.includes(`${baseName}B.${ext}`.toLowerCase()) ||
|
||||
filesInDir.includes(`${baseName}V.${ext}`.toLowerCase())
|
||||
);
|
||||
} else {
|
||||
// Case-sensitive check for other platforms
|
||||
return !(
|
||||
fs.existsSync(filePath) ||
|
||||
fs.existsSync(filePathA) ||
|
||||
fs.existsSync(filePathB) ||
|
||||
fs.existsSync(filePathV)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (missingFiles.length === 0) {
|
||||
return; // All files are present
|
||||
}
|
||||
|
||||
log.debug(
|
||||
`Attempt ${attempt}: Missing files: ${missingFiles.join(", ")}. Retrying in ${backoffMs}ms...`,
|
||||
);
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
||||
backoffMs *= 2; // Exponential backoff
|
||||
} else {
|
||||
throw new Error(
|
||||
`The set of files is not valid. Missing files for CIECA ID ${baseFilePath}: ${missingFiles.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const requiredExtensions = [
|
||||
"env",
|
||||
"ad1",
|
||||
"ad2",
|
||||
"veh",
|
||||
"lin",
|
||||
"pfh",
|
||||
"pfl",
|
||||
"pft",
|
||||
"pfm",
|
||||
"pfo",
|
||||
"stl",
|
||||
"ttl",
|
||||
"pfp",
|
||||
];
|
||||
|
||||
export function ReplaceOwnerInfoWithClaimant<
|
||||
T extends Partial<
|
||||
Pick<
|
||||
RawJobDataObject,
|
||||
| "ownr_ln"
|
||||
| "ownr_fn"
|
||||
| "ownr_co_nm"
|
||||
| "ownr_title"
|
||||
| "ownr_co_nm"
|
||||
| "ownr_addr1"
|
||||
| "ownr_addr2"
|
||||
| "ownr_city"
|
||||
| "ownr_st"
|
||||
| "ownr_zip"
|
||||
| "ownr_ctry"
|
||||
| "ownr_ph1"
|
||||
| "ownr_ph2"
|
||||
| "ownr_ea"
|
||||
| "clmt_ln"
|
||||
| "clmt_fn"
|
||||
| "clmt_title"
|
||||
| "clmt_co_nm"
|
||||
| "clmt_addr1"
|
||||
| "clmt_addr2"
|
||||
| "clmt_city"
|
||||
| "clmt_st"
|
||||
| "clmt_zip"
|
||||
| "clmt_ctry"
|
||||
| "clmt_ph1"
|
||||
| "clmt_ph2"
|
||||
| "clmt_ea"
|
||||
| "insd_ln"
|
||||
| "insd_fn"
|
||||
| "insd_title"
|
||||
| "insd_co_nm"
|
||||
| "insd_addr1"
|
||||
| "insd_addr2"
|
||||
| "insd_city"
|
||||
| "insd_st"
|
||||
| "insd_zip"
|
||||
| "insd_ctry"
|
||||
| "insd_ph1"
|
||||
| "insd_ph2"
|
||||
| "insd_ea"
|
||||
| "owner"
|
||||
>
|
||||
>,
|
||||
>(jobObject: T): T {
|
||||
// Promote claimant data first if owner identity is entirely missing; otherwise fallback to insured data.
|
||||
const identityKeys = ["ln", "fn", "co_nm"] as const; // keys used to determine presence
|
||||
const copyKeys = [
|
||||
"ln",
|
||||
"fn",
|
||||
"title",
|
||||
"co_nm",
|
||||
"addr1",
|
||||
"addr2",
|
||||
"city",
|
||||
"st",
|
||||
"zip",
|
||||
"ctry",
|
||||
"ph1",
|
||||
"ph2",
|
||||
"ea",
|
||||
] as const; // full set of fields to copy/delete
|
||||
|
||||
const ownerMissing = identityKeys.every((k) =>
|
||||
_.isEmpty((jobObject as any)[`ownr_${k}`]),
|
||||
);
|
||||
const claimantHasSome = identityKeys.some(
|
||||
(k) => !_.isEmpty((jobObject as any)[`clmt_${k}`]),
|
||||
);
|
||||
const claimantMissing = identityKeys.every((k) =>
|
||||
_.isEmpty((jobObject as any)[`clmt_${k}`]),
|
||||
);
|
||||
|
||||
const { owner } = jobObject as any; // destructure for optional nested updates
|
||||
|
||||
// Copy helper inline (no extra function as requested)
|
||||
const promote = (sourcePrefix: "clmt" | "insd"): void => {
|
||||
copyKeys.forEach((suffix) => {
|
||||
(jobObject as any)[`ownr_${suffix}`] = (jobObject as any)[
|
||||
`${sourcePrefix}_${suffix}`
|
||||
];
|
||||
if (owner?.data) {
|
||||
owner.data[`ownr_${suffix}`] = (jobObject as any)[
|
||||
`${sourcePrefix}_${suffix}`
|
||||
];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (ownerMissing && claimantHasSome) {
|
||||
promote("clmt");
|
||||
} else if (ownerMissing && claimantMissing) {
|
||||
promote("insd");
|
||||
}
|
||||
|
||||
// Delete the claimant info as it's not needed.
|
||||
copyKeys.forEach((suffix) => delete (jobObject as any)[`clmt_${suffix}`]);
|
||||
// Delete the insured info as it's not needed.
|
||||
copyKeys.forEach((suffix) => delete (jobObject as any)[`insd_${suffix}`]);
|
||||
|
||||
return jobObject;
|
||||
}
|
||||
104
src/main/decoder/emsbackup.ts
Normal file
104
src/main/decoder/emsbackup.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import axios from "axios";
|
||||
import archiver from "archiver";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { UUID } from "crypto";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import stream from "stream";
|
||||
import { getTokenFromRenderer } from "../graphql/graphql-client";
|
||||
import store from "../store/store";
|
||||
|
||||
async function UploadEmsToS3({
|
||||
extensionlessFilePath,
|
||||
bodyshopid,
|
||||
clm_no,
|
||||
ciecaid,
|
||||
ownr_ln,
|
||||
}: {
|
||||
extensionlessFilePath: string;
|
||||
bodyshopid: UUID;
|
||||
clm_no: string;
|
||||
ciecaid: string;
|
||||
ownr_ln: string;
|
||||
}): Promise<boolean> {
|
||||
// This function is a placeholder for the actual upload logic
|
||||
try {
|
||||
const directory = path.dirname(extensionlessFilePath);
|
||||
const baseFilename = path.basename(extensionlessFilePath);
|
||||
|
||||
// Find all files in the directory that start with the base filename
|
||||
const filesToZip = fs
|
||||
.readdirSync(directory)
|
||||
.filter((file) => file.startsWith(baseFilename))
|
||||
.map((file) => path.join(directory, file));
|
||||
|
||||
if (filesToZip.length === 0) {
|
||||
console.error("No files found to zip.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a zip archive in memory
|
||||
const archive = archiver("zip", { zlib: { level: 9 } });
|
||||
const zipBuffer = await new Promise<Buffer>((resolve, reject) => {
|
||||
const buffers: Buffer[] = [];
|
||||
const writableStream = new stream.Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
buffers.push(chunk);
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
writableStream.on("finish", () => resolve(Buffer.concat(buffers)));
|
||||
writableStream.on("error", reject);
|
||||
|
||||
archive.pipe(writableStream);
|
||||
|
||||
// Append files to the archive
|
||||
filesToZip.forEach((file) => {
|
||||
archive.file(file, { name: path.basename(file) });
|
||||
});
|
||||
|
||||
archive.finalize();
|
||||
});
|
||||
|
||||
// Get the presigned URL from the server
|
||||
const presignedUrlResponse = await axios.post(
|
||||
`${
|
||||
store.get("app.isTest")
|
||||
? import.meta.env.VITE_API_TEST_URL
|
||||
: import.meta.env.VITE_API_URL
|
||||
}/emsupload`,
|
||||
{
|
||||
bodyshopid,
|
||||
ciecaid,
|
||||
clm_no,
|
||||
ownr_ln,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await getTokenFromRenderer()}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const presignedUrl = presignedUrlResponse.data?.presignedUrl;
|
||||
if (!presignedUrl) {
|
||||
console.error("Failed to retrieve presigned URL.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Upload the zip file to S3 using the presigned URL
|
||||
await axios.put(presignedUrl, zipBuffer, {
|
||||
headers: {
|
||||
"Content-Type": "application/zip",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error uploading EMS to S3:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // Return true if the upload is successful
|
||||
}
|
||||
|
||||
export default UploadEmsToS3;
|
||||
57
src/main/decoder/folder-scan.ts
Normal file
57
src/main/decoder/folder-scan.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import path from "path";
|
||||
import { GetAllEnvFiles } from "../watcher/watcher";
|
||||
import DecodeAD1 from "./decode-ad1";
|
||||
import DecodeAD2 from "./decode-ad2";
|
||||
import DecodeEnv from "./decode-env";
|
||||
import DecodeVeh from "./decode-veh";
|
||||
import { ReplaceOwnerInfoWithClaimant } from "./decoder";
|
||||
|
||||
const folderScan = async (): Promise<FolderScanResult[]> => {
|
||||
//Get all ENV files for watched paths.
|
||||
const allEnvFiles = GetAllEnvFiles();
|
||||
//Run a simplified decode on them
|
||||
const returnedFiles: FolderScanResult[] = [];
|
||||
|
||||
for (const filepath of allEnvFiles) {
|
||||
const parsedFilePath = path.parse(filepath);
|
||||
const extensionlessFilePath = path.join(
|
||||
parsedFilePath.dir,
|
||||
parsedFilePath.name,
|
||||
);
|
||||
|
||||
const rawJob = {
|
||||
...(await DecodeEnv(extensionlessFilePath)),
|
||||
...(await DecodeAD1(extensionlessFilePath)),
|
||||
...(await DecodeAD2(extensionlessFilePath)),
|
||||
...(await DecodeVeh(extensionlessFilePath)),
|
||||
};
|
||||
const job = ReplaceOwnerInfoWithClaimant(rawJob);
|
||||
|
||||
const scanResult: FolderScanResult = {
|
||||
id: job.ciecaid,
|
||||
filepath: filepath,
|
||||
cieca_id: job.ciecaid,
|
||||
clm_no: job.clm_no,
|
||||
owner: `${job.ownr_fn} ${job.ownr_ln} ${job.ownr_co_nm}`.trim(),
|
||||
vehicle:
|
||||
`${job.vehicle?.data.v_model_yr} ${job.vehicle?.data.v_make_desc} ${job.vehicle?.data.v_model_desc}`.trim(),
|
||||
ins_co_nm: job.ins_co_nm,
|
||||
};
|
||||
|
||||
returnedFiles.push(scanResult);
|
||||
}
|
||||
//Build up the object and return it
|
||||
return returnedFiles;
|
||||
};
|
||||
|
||||
export interface FolderScanResult {
|
||||
id?: string;
|
||||
filepath: string;
|
||||
cieca_id?: string;
|
||||
clm_no?: string;
|
||||
owner: string;
|
||||
ins_co_nm?: string;
|
||||
vehicle: string;
|
||||
}
|
||||
|
||||
export default folderScan;
|
||||
Reference in New Issue
Block a user