Add PFL and PFH files.

This commit is contained in:
Patrick Fic
2025-03-19 07:19:49 -07:00
parent 4f3d8cf754
commit 5ddfe4d86f
9 changed files with 228 additions and 10 deletions

View File

@@ -18,10 +18,6 @@ nsis:
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
azureSignOptions:
endpoint: https://eus.codesigning.azure.net
certificateProfileName: ImEXRPS
codeSigningAccountName: ImEX
mac:
entitlementsInherit: build/entitlements.mac.plist
category: public.app-category.business

View File

@@ -2,8 +2,8 @@ import { DBFFile } from "dbffile";
import log from "electron-log/main";
import _ from "lodash";
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
import { OwnerRecordInterface, DecodedAd1 } from "./decode-ad1.interface";
import errorTypeCheck from "../../util/errorTypeCheck";
import { DecodedAd1, OwnerRecordInterface } from "./decode-ad1.interface";
const DecodeAD1 = async (
extensionlessFilePath: string
@@ -14,7 +14,7 @@ const DecodeAD1 = async (
} catch (error) {
log.error("Error opening AD1 File.", errorTypeCheck(error));
dbf = await DBFFile.open(`${extensionlessFilePath}.AD1`);
log.log("Found AD1 file using regular CIECA Id.");
log.log("Trying to find AD1 file using regular CIECA Id.");
}
if (!dbf) {

View File

@@ -25,5 +25,5 @@ export interface DecodedAD2 {
est_ct_fn?: string;
est_ea?: string;
date_estimated?: Date; // This is transformed from insp_date
//insp_date?: string; // This exists initially but gets deleted
insp_date?: Date; // This exists initially but gets deleted
}

View File

@@ -14,7 +14,7 @@ const DecodeAD2 = async (
} catch (error) {
log.error("Error opening AD2 File.", errorTypeCheck(error));
dbf = await DBFFile.open(`${extensionlessFilePath}.AD2`);
log.log("Found AD2 file using regular CIECA Id.");
log.log("Trying to find AD2 file using regular CIECA Id.");
}
if (!dbf) {
@@ -27,7 +27,7 @@ const DecodeAD2 = async (
//AD2 will always have only 1 row.
//Commented lines have been cross referenced with existing partner fields.
const rawAd2Data = deepLowerCaseKeys(
const rawAd2Data: DecodedAD2 = deepLowerCaseKeys(
_.pick(rawDBFRecord[0], [
//TODO: Add typings for EMS File Formats.
"CLMT_LN",

View 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;
}

View File

@@ -0,0 +1,71 @@
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";
const DecodePfh = async (
extensionlessFilePath: string
): Promise<DecodedPfh> => {
let dbf: DBFFile;
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}`);
}
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", //Remove
"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;

View File

@@ -0,0 +1,41 @@
//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 DecodedPfl extends JobLaborRateFields {
cieca_pfl: DecodedPflLine[];
}

View File

@@ -0,0 +1,89 @@
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";
const DecodePfl = async (
extensionlessFilePath: string
): Promise<DecodedPfl> => {
let dbf: DBFFile;
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}`);
}
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;
//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`
return { ...jobLaborRates, cieca_pfl: rawPflData };
};
export default DecodePfl;

View File

@@ -7,6 +7,10 @@ import DecodeAD2 from "./decode-ad2";
import { DecodedAD2 } from "./decode-ad2.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 DecodeVeh from "./decode-veh";
import { DecodedVeh } from "./decode-veh.interface";
@@ -25,7 +29,9 @@ async function ImportJob(filepath: string): Promise<void> {
const ad2: DecodedAD2 = await DecodeAD2(extensionlessFilePath);
const veh: DecodedVeh = await DecodeVeh(extensionlessFilePath);
const lin: DecodedLin[] = await DecodeLin(extensionlessFilePath);
log.debug("EMS Object", { ad1, ad2, veh, lin });
const pfh: DecodedPfh = await DecodePfh(extensionlessFilePath);
const pfl: DecodedPfl = await DecodePfl(extensionlessFilePath);
log.debug("EMS Object", { ad1, ad2, veh, lin, pfh, pfl });
} catch (error) {
log.error("Error encountered while decoding job. ", errorTypeCheck(error));
}