Add parsing of AD1 files. Memorize window size and location.

This commit is contained in:
Patrick Fic
2025-03-17 14:27:33 -07:00
parent 10368f8f9e
commit c1949eb5f9
33 changed files with 524 additions and 20 deletions

View File

@@ -0,0 +1,147 @@
interface ParsedAD1 {
// 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?: 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;
};
}
interface OwnerRecordInterface {
ownr_ln: string;
ownr_fn: string;
ownr_title: string;
ownr_co_nm: string;
ownr_addr1: string;
ownr_addr2: string;
ownr_city: string;
ownr_st: string;
ownr_zip: string;
ownr_ctry: string;
ownr_ph1: string;
ownr_ph2: string;
ownr_ea: string;
shopid: string;
}

View File

@@ -0,0 +1,219 @@
import { DBFFile } from "dbffile";
import log from "electron-log/main";
import _ from "lodash";
import deepLowerCaseKeys from "../../util/deepLowercaseKeys";
const DecodeAD1 = async (extensionlessFilePath: string): Promise<ParsedAD1> => {
let dbf;
try {
dbf = await DBFFile.open(`${extensionlessFilePath}A.AD1`);
} catch (error) {
log.error("Error opening AD1 File.", error);
dbf = await DBFFile.open(`${extensionlessFilePath}.AD1`);
log.log("Found AD1 file using regular CIECA Id.");
}
if (!dbf) {
log.error(`Could not find any AD1 files at ${extensionlessFilePath}`);
return {
id: 0,
};
}
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 = 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.ownr_ph1) {
rawAd1Data.ownr_ph1 = rawAd1Data.ownr_ph2;
}
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.
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: "UUID", //TODO: Need to add the shop uuid to this set of functions.
};
} 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: "UUID",
};
}
return { ...rawAd1Data, owner: { data: ownerRecord } };
};
export default DecodeAD1;
interface OwnerRecordInterface {
ownr_ln: string;
ownr_fn: string;
ownr_title: string;
ownr_co_nm: string;
ownr_addr1: string;
ownr_addr2: string;
ownr_city: string;
ownr_st: string;
ownr_zip: string;
ownr_ctry: string;
ownr_ph1: string;
ownr_ph2: string;
ownr_ea: string;
shopid: string;
}

View File

@@ -0,0 +1,19 @@
import log from "electron-log/main";
import path from "path";
import DecodeAD1 from "./decode-ad1";
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);
const decodedJob = {};
const ad1: ParsedAD1 = await DecodeAD1(extensionlessFilePath);
log.debug("AD1", ad1);
}
export default ImportJob;

View File

@@ -1,7 +1,7 @@
import { _electron as electron } from "playwright";
import { test, expect } from "@playwright/test";
test("example test", async () => {
test("Basic Electron app compilation.", async () => {
const electronApp = await electron.launch({ args: ["."] });
const isPackaged = await electronApp.evaluate(async ({ app }) => {
// This runs in Electron's main process, parameter here is always
@@ -14,8 +14,6 @@ test("example test", async () => {
// Wait for the first BrowserWindow to open
// and return its Page object
const window = await electronApp.firstWindow();
await window.screenshot({ path: "intro.png" });
// close app
await electronApp.close();
});

View File

@@ -4,13 +4,23 @@ import log from "electron-log/main";
import { join } from "path";
import icon from "../../resources/icon.png?asset";
import ErrorTypeCheck from "../util/errorTypeCheck";
import "./store/store";
import store from "./store/store";
log.initialize();
function createWindow(): void {
// Create the browser window.
const { width, height, x, y } = store.get("app.windowBounds") as {
width: number;
height: number;
x: number | undefined;
y: number | undefined;
};
const mainWindow = new BrowserWindow({
width: 900,
height: 670,
width,
height,
x,
y,
show: false,
autoHideMenuBar: true,
...(process.platform === "linux" ? { icon } : {}),
@@ -21,6 +31,17 @@ function createWindow(): void {
},
});
// Store window properties for later
const storeWindowState = (): void => {
const [width, height] = mainWindow.getSize();
const [x, y] = mainWindow.getPosition();
store.set("app.windowBounds", { width, height, x, y });
};
mainWindow.on("resized", storeWindowState);
mainWindow.on("maximize", storeWindowState);
mainWindow.on("unmaximize", storeWindowState);
mainWindow.on("moved", storeWindowState);
mainWindow.on("ready-to-show", () => {
mainWindow.show();
});

View File

@@ -1,6 +1,8 @@
import { ipcMain } from "electron";
import { app, ipcMain } from "electron";
import log from "electron-log/main";
import path from "path";
import ipcTypes from "../../util/ipcTypes.json";
import ImportJob from "../decoder/decoder";
import { StartWatcher } from "../watcher/watcher";
import {
SettingsWatchedFilePathsAdd,
@@ -10,7 +12,7 @@ import {
import { ipcMainHandleAuthStateChanged } from "./ipcMainHandler.user";
// Log all IPC messages and their payloads
const logIpcMessages = () => {
const logIpcMessages = (): void => {
// Get all message types from ipcTypes.toMain
Object.keys(ipcTypes.toMain).forEach((key) => {
const messageType = ipcTypes.toMain[key];
@@ -42,6 +44,24 @@ ipcMain.on(ipcTypes.toMain.test, (payload: any) =>
//Auth handler
ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged);
//Add debug handlers if in development
if (import.meta.env.DEV) {
log.debug("[IPC Debug Functions] Adding Debug Handlers");
ipcMain.on(
ipcTypes.toMain.debug.decodeEstimate,
async (event, payload): Promise<void> => {
const relativeEmsFilepath = `_reference/ems/MPI_1/3698420.ENV`;
// Get the app's root directory and create an absolute path
const rootDir = app.getAppPath();
const absoluteFilepath = path.join(rootDir, relativeEmsFilepath);
log.debug("[IPC Debug Function] Decode test Estimate", absoluteFilepath);
await ImportJob(absoluteFilepath);
}
);
}
//Settings Handlers
ipcMain.handle(
ipcTypes.toMain.settings.filepaths.get,

View File

@@ -11,7 +11,15 @@ const store = new Store({
pollingInterval: 30000,
},
},
user: null,
app: {
windowBounds: {
width: 800,
height: 600,
x: undefined,
y: undefined,
},
user: null,
},
},
});

View File

@@ -4,8 +4,9 @@ import log from "electron-log/main";
import path from "path";
import errorTypeCheck from "../../util/errorTypeCheck";
import store from "../store/store";
import ImportJob from "../decoder/decoder";
var watcher: FSWatcher;
let watcher: FSWatcher;
async function StartWatcher(): Promise<boolean> {
const filePaths = store.get("settings.filepaths") || [];
@@ -34,8 +35,7 @@ async function StartWatcher(): Promise<boolean> {
watcher = chokidar.watch(filePaths, {
ignored: (filepath, stats) => {
const p = path.parse(filepath);
return !stats?.isFile() && p.ext !== "" && p.ext.toUpperCase() !== ".ENV";
return !stats?.isFile() && p.ext !== "" && p.ext.toUpperCase() !== ".ENV"; //Only watch for .ENV files.
},
usePolling: store.get("settings.polling").enabled || false,
interval: store.get("settings.polling").pollingInterval || 1000,
@@ -77,7 +77,7 @@ async function StartWatcher(): Promise<boolean> {
return true;
}
function onWatcherReady() {
function onWatcherReady(): void {
log.info("Watcher ready!");
// const b = BrowserWindow.getAllWindows()[0];
// b.webContents.send(ipcTypes.default.fileWatcher.toRenderer.startSuccess);
@@ -102,8 +102,8 @@ async function StopWatcher(): Promise<boolean> {
return false;
}
async function HandleNewFile(path) {
//await ImportJob(path);
async function HandleNewFile(path): Promise<void> {
await ImportJob(path);
log.log("Received a new file", path);
}

View File

@@ -11,6 +11,7 @@ import {} from "react-error-boundary";
import { ErrorBoundary } from "react-error-boundary";
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback/ErrorBoundaryFallback";
import Settings from "./components/Settings/Settings";
import Home from "./components/Home/Home";
const App: React.FC = () => {
const [user, setUser] = useState<User | null>(null);
@@ -36,7 +37,7 @@ const App: React.FC = () => {
<>
<NavigationHeader />
<Routes>
<Route path="/" element={<div>AuthHome</div>} />
<Route path="/" element={<Home />} />
<Route path="settings" element={<Settings />} />
</Routes>
</>

View File

@@ -0,0 +1,21 @@
import { Button } from "antd";
import ipcTypes from "../../../../util/ipcTypes.json";
const Home: React.FC = () => {
return (
<div>
<h1>Home</h1>
<Button
onClick={(): void => {
window.electron.ipcRenderer.send(
ipcTypes.toMain.debug.decodeEstimate
);
}}
>
Test Decode Estimate
</Button>
</div>
);
};
export default Home;

View File

@@ -16,7 +16,7 @@ const SettingsWatchedPaths: React.FC = () => {
});
}, []);
const handleAddPath = () => {
const handleAddPath = (): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.filepaths.add)
.then((paths: string[]) => {
@@ -24,7 +24,7 @@ const SettingsWatchedPaths: React.FC = () => {
});
};
const handleRemovePath = (path: string) => {
const handleRemovePath = (path: string): void => {
window.electron.ipcRenderer
.invoke(ipcTypes.toMain.settings.filepaths.remove, path)
.then((paths: string[]) => {

View File

@@ -0,0 +1,32 @@
/**
* Deep renames all keys in an object to lowercase
* @param obj - The object to transform
* @returns A new object with all keys converted to lowercase
*/
function deepLowerCaseKeys<T = any>(obj: any): T {
if (!obj || typeof obj !== "object") {
return obj;
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => deepLowerCaseKeys(item)) as unknown as T;
}
// Handle objects
return Object.keys(obj).reduce(
(result, key) => {
const value = obj[key];
const lowercaseKey = key.toLowerCase();
result[lowercaseKey] =
typeof value === "object" && value !== null
? deepLowerCaseKeys(value)
: value;
return result;
},
{} as Record<string, any>
) as T;
}
export default deepLowerCaseKeys;

View File

@@ -2,6 +2,9 @@
"toMain": {
"test": "toMain_test",
"authStateChanged": "toMain_authStateChanged",
"debug": {
"decodeEstimate": "toMain_debug_decodeEstimate"
},
"watcher": {
"start": "toMain_watcher_start",
"stop": "toMain_watcher_stop"