Initial copy of shop partner app.
This commit is contained in:
12
src/env.d.ts
vendored
Normal file
12
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
export interface ImportMetaEnv {
|
||||
readonly VITE_FIREBASE_CONFIG: string;
|
||||
readonly VITE_GRAPHQL_ENDPOINT: string;
|
||||
readonly VITE_FIREBASE_CONFIG_TEST: string;
|
||||
readonly VITE_GRAPHQL_ENDPOINT_TEST: string;
|
||||
}
|
||||
|
||||
export interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
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;
|
||||
158
src/main/ems-parts-order/ems-parts-order-generate-ad1.ts
Normal file
158
src/main/ems-parts-order/ems-parts-order-generate-ad1.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { ad1FieldLineDescriptors } from "../util/ems-interface/fielddescriptors/ad1-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGenerateAd1File = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = [
|
||||
{
|
||||
INS_CO_ID: partsOrder.job.ins_co_nm,
|
||||
INS_CO_NM: partsOrder.job.ins_co_nm,
|
||||
INS_ADDR1: partsOrder.job.ins_addr1,
|
||||
INS_ADDR2: partsOrder.job.ins_addr2,
|
||||
INS_CITY: partsOrder.job.ins_city,
|
||||
INS_ST: partsOrder.job.ins_st,
|
||||
INS_ZIP: partsOrder.job.ins_zip,
|
||||
INS_CTRY: partsOrder.job.ins_ctry,
|
||||
INS_PH1: partsOrder.job.ins_ph1,
|
||||
INS_PH1X: partsOrder.job.ins_ph1x,
|
||||
INS_PH2: partsOrder.job.ins_ph2,
|
||||
INS_PH2X: partsOrder.job.ins_ph2x,
|
||||
INS_FAX: partsOrder.job.ins_fax,
|
||||
INS_FAXX: partsOrder.job.ins_faxx,
|
||||
INS_CT_LN: partsOrder.job.ins_ct_ln,
|
||||
INS_CT_FN: partsOrder.job.ins_ct_fn,
|
||||
INS_TITLE: partsOrder.job.ins_title,
|
||||
INS_CT_PH: partsOrder.job.ins_ct_ph,
|
||||
INS_CT_PHX: partsOrder.job.ins_ct_phx,
|
||||
INS_EA: partsOrder.job.ins_ea,
|
||||
INS_MEMO: partsOrder.job.ins_memo,
|
||||
POLICY_NO: partsOrder.job.policy_no,
|
||||
DED_AMT: partsOrder.job.ded_amt,
|
||||
DED_STATUS: partsOrder.job.ded_status,
|
||||
ASGN_NO: partsOrder.job.asgn_no,
|
||||
ASGN_DATE: partsOrder.job.asgn_date
|
||||
? new Date(partsOrder.job.asgn_date)
|
||||
: null,
|
||||
ASGN_TYPE: partsOrder.job.asgn_type,
|
||||
CLM_NO: partsOrder.job.clm_no,
|
||||
CLM_OFC_ID: partsOrder.job.clm_ofc_id,
|
||||
CLM_OFC_NM: partsOrder.job.clm_ofc_nm,
|
||||
CLM_ADDR1: partsOrder.job.clm_addr1,
|
||||
CLM_ADDR2: partsOrder.job.clm_addr2,
|
||||
CLM_CITY: partsOrder.job.clm_city,
|
||||
CLM_ST: partsOrder.job.clm_st,
|
||||
CLM_ZIP: partsOrder.job.clm_zip,
|
||||
CLM_CTRY: partsOrder.job.clm_ctry,
|
||||
CLM_PH1: partsOrder.job.clm_ph1,
|
||||
CLM_PH1X: partsOrder.job.clm_ph1x,
|
||||
CLM_PH2: partsOrder.job.clm_ph2,
|
||||
CLM_PH2X: partsOrder.job.clm_ph2x,
|
||||
CLM_FAX: partsOrder.job.clm_fax,
|
||||
CLM_FAXX: partsOrder.job.clm_faxx,
|
||||
CLM_CT_LN: partsOrder.job.clm_ct_ln,
|
||||
CLM_CT_FN: partsOrder.job.clm_ct_fn,
|
||||
CLM_TITLE: partsOrder.job.clm_title,
|
||||
CLM_CT_PH: partsOrder.job.clm_ct_ph,
|
||||
CLM_CT_PHX: partsOrder.job.clm_ct_phx,
|
||||
CLM_EA: partsOrder.job.clm_ea,
|
||||
PAYEE_NMS: partsOrder.job.payee_nms,
|
||||
PAY_TYPE: partsOrder.job.pay_type,
|
||||
PAY_DATE: partsOrder.job.pay_date,
|
||||
PAY_CHKNM: null, // Explicitly set to null as in original code
|
||||
PAY_AMT: null, // Explicitly set to null as in original code
|
||||
PAY_MEMO: partsOrder.job.pay_memo,
|
||||
AGT_CO_ID: partsOrder.job.agt_co_id,
|
||||
AGT_CO_NM: partsOrder.job.agt_co_nm,
|
||||
AGT_ADDR1: partsOrder.job.agt_addr1,
|
||||
AGT_ADDR2: partsOrder.job.agt_addr2,
|
||||
AGT_CITY: partsOrder.job.agt_city,
|
||||
AGT_ST: partsOrder.job.agt_st,
|
||||
AGT_ZIP: partsOrder.job.agt_zip,
|
||||
AGT_CTRY: partsOrder.job.agt_ctry,
|
||||
AGT_PH1: partsOrder.job.agt_ph1,
|
||||
AGT_PH1X: partsOrder.job.agt_ph1x,
|
||||
AGT_PH2: partsOrder.job.agt_ph2,
|
||||
AGT_PH2X: partsOrder.job.agt_ph2x,
|
||||
AGT_FAX: partsOrder.job.agt_fax,
|
||||
AGT_FAXX: partsOrder.job.agt_faxx,
|
||||
AGT_CT_LN: partsOrder.job.agt_ct_ln,
|
||||
AGT_CT_FN: partsOrder.job.agt_ct_fn,
|
||||
AGT_CT_PH: partsOrder.job.agt_ct_ph,
|
||||
AGT_CT_PHX: partsOrder.job.agt_ct_phx,
|
||||
AGT_EA: partsOrder.job.agt_ea,
|
||||
AGT_LIC_NO: partsOrder.job.agt_lic_no,
|
||||
LOSS_DATE: partsOrder.job.loss_date
|
||||
? new Date(partsOrder.job.loss_date)
|
||||
: null,
|
||||
LOSS_CAT: null, // Explicitly set to null as in original code
|
||||
LOSS_TYPE: null, // Explicitly set to null as in original code
|
||||
LOSS_DESC: partsOrder.job.loss_desc,
|
||||
THEFT_IND: null, // Explicitly set to null as in original code
|
||||
CAT_NO: partsOrder.job.cat_no,
|
||||
TLOS_IND: null, // Explicitly set to null as in original code
|
||||
LOSS_MEMO: partsOrder.job.loss_memo,
|
||||
CUST_PR: partsOrder.job.cust_pr,
|
||||
INSD_LN: partsOrder.job.insd_ln,
|
||||
INSD_FN: partsOrder.job.insd_fn,
|
||||
INSD_TITLE: partsOrder.job.insd_title,
|
||||
INSD_CO_NM: partsOrder.job.insd_co_nm,
|
||||
INSD_ADDR1: partsOrder.job.insd_addr1,
|
||||
INSD_ADDR2: partsOrder.job.insd_addr2,
|
||||
INSD_CITY: partsOrder.job.insd_city,
|
||||
INSD_ST: partsOrder.job.insd_st,
|
||||
INSD_ZIP: partsOrder.job.insd_zip,
|
||||
INSD_CTRY: partsOrder.job.insd_ctry,
|
||||
INSD_PH1: partsOrder.job.insd_ph1,
|
||||
INSD_PH1X: partsOrder.job.insd_ph1x,
|
||||
INSD_PH2: partsOrder.job.insd_ph2,
|
||||
INSD_PH2X: partsOrder.job.insd_ph2x,
|
||||
INSD_FAX: partsOrder.job.insd_fax,
|
||||
INSD_FAXX: partsOrder.job.insd_faxx,
|
||||
INSD_EA: partsOrder.job.insd_ea,
|
||||
OWNR_LN: partsOrder.job.ownr_ln,
|
||||
OWNR_FN: partsOrder.job.ownr_fn,
|
||||
OWNR_TITLE: partsOrder.job.ownr_title,
|
||||
OWNR_CO_NM: partsOrder.job.ownr_co_nm,
|
||||
OWNR_ADDR1: partsOrder.job.ownr_addr1,
|
||||
OWNR_ADDR2: partsOrder.job.ownr_addr2,
|
||||
OWNR_CITY: partsOrder.job.ownr_city,
|
||||
OWNR_ST: partsOrder.job.ownr_st,
|
||||
OWNR_ZIP: partsOrder.job.ownr_zip,
|
||||
OWNR_CTRY: partsOrder.job.ownr_ctry,
|
||||
OWNR_PH1: partsOrder.job.ownr_ph1,
|
||||
OWNR_PH1X: partsOrder.job.ownr_ph1x,
|
||||
OWNR_PH2: partsOrder.job.ownr_ph2,
|
||||
OWNR_PH2X: partsOrder.job.ownr_ph2x,
|
||||
OWNR_FAX: partsOrder.job.ownr_fax,
|
||||
OWNR_FAXX: partsOrder.job.ownr_faxx,
|
||||
OWNR_EA: partsOrder.job.ownr_ea,
|
||||
},
|
||||
];
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.AD1`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.AD1`),
|
||||
ad1FieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} AD1 file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating AD1 file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGenerateAd1File;
|
||||
67
src/main/ems-parts-order/ems-parts-order-generate-ad2.ts
Normal file
67
src/main/ems-parts-order/ems-parts-order-generate-ad2.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { ad2FieldLineDescriptors } from "../util/ems-interface/fielddescriptors/ad2-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGenerateAd2File = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = [
|
||||
{
|
||||
EST_CO_NM: partsOrder.job.est_co_nm,
|
||||
EST_ADDR1: partsOrder.job.est_addr1,
|
||||
EST_ADDR2: partsOrder.job.est_addr2,
|
||||
EST_CITY: partsOrder.job.est_city,
|
||||
EST_ST: partsOrder.job.est_st,
|
||||
EST_ZIP: partsOrder.job.est_zip,
|
||||
EST_CTRY: partsOrder.job.est_ctry,
|
||||
EST_PH1: partsOrder.job.est_ph1,
|
||||
EST_CT_LN: partsOrder.job.est_ct_ln,
|
||||
EST_CT_FN: partsOrder.job.est_ct_fn,
|
||||
EST_EA: partsOrder.job.est_ea,
|
||||
CLMT_ADDR1: partsOrder.job.clm_addr1,
|
||||
CLMT_ADDR2: partsOrder.job.clm_addr2,
|
||||
CLMT_CITY: partsOrder.job.clm_city,
|
||||
CLMT_ST: partsOrder.job.clm_st,
|
||||
CLMT_ZIP: partsOrder.job.clm_zip,
|
||||
CLMT_CTRY: partsOrder.job.clm_ctry,
|
||||
CLMT_PH1: partsOrder.job.clm_ph1,
|
||||
CLMT_PH1X: partsOrder.job.clm_ph1x,
|
||||
CLMT_PH2: partsOrder.job.clm_ph2,
|
||||
CLMT_PH2X: partsOrder.job.clm_ph2x,
|
||||
CLMT_FAX: partsOrder.job.clm_fax,
|
||||
CLMT_FAXX: partsOrder.job.clm_faxx,
|
||||
CLMT_LN: partsOrder.job.clm_ct_ln,
|
||||
CLMT_FN: partsOrder.job.clm_ct_fn,
|
||||
CLMT_TITLE: partsOrder.job.clm_title,
|
||||
CLMT_CT_PH: partsOrder.job.clm_ct_ph,
|
||||
CLMT_CT_PHX: partsOrder.job.clm_ct_phx,
|
||||
CLMT_EA: partsOrder.job.clm_ea,
|
||||
RF_CO_NM: partsOrder.job.bodyshop.shopname,
|
||||
},
|
||||
];
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.AD2`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.AD2`),
|
||||
ad2FieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} AD2 file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating AD2 file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGenerateAd2File;
|
||||
80
src/main/ems-parts-order/ems-parts-order-generate-env.ts
Normal file
80
src/main/ems-parts-order/ems-parts-order-generate-env.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { envFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/env-field-descriptor";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGenerateEnvFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const dateNow = new Date();
|
||||
const formatTime = (date: Date): string =>
|
||||
`${date.getHours().toString().padStart(2, "0")}${date.getMinutes().toString().padStart(2, "0")}${date.getSeconds().toString().padStart(2, "0")}`;
|
||||
|
||||
const {
|
||||
job: { ro_number, ciecaid },
|
||||
} = partsOrder;
|
||||
|
||||
// Find the highest line_ind value
|
||||
const lineInds = partsOrder.parts_order_lines.map(
|
||||
(line) => line.jobline.line_ind,
|
||||
);
|
||||
const getNumber = (str: string): number => {
|
||||
const match = str.match(/(\d+)$/);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
};
|
||||
const highestLineInd = lineInds.reduce(
|
||||
(max, current) => (getNumber(current) > getNumber(max) ? current : max),
|
||||
lineInds[0] || "",
|
||||
);
|
||||
|
||||
const records = [
|
||||
{
|
||||
EST_SYSTEM: "M",
|
||||
SW_VERSION: "25.3",
|
||||
DB_VERSION: "OCT_25_V",
|
||||
DB_DATE: dateNow,
|
||||
RO_ID: ro_number,
|
||||
ESTFILE_ID: ciecaid,
|
||||
SUPP_NO: highestLineInd ? getNumber(highestLineInd).toString() : "1",
|
||||
EST_CTRY: "CAN",
|
||||
TOP_SECRET: "00000000-0000-0000-0000-000000000000",
|
||||
TRANS_TYPE: highestLineInd ? highestLineInd.charAt(0) : "S",
|
||||
STATUS: false,
|
||||
CREATE_DT: dateNow,
|
||||
CREATE_TM: formatTime(dateNow),
|
||||
TRANSMT_DT: dateNow,
|
||||
TRANSMT_TM: formatTime(dateNow),
|
||||
INCL_ADMIN: true,
|
||||
INCL_VEH: true,
|
||||
INCL_EST: true,
|
||||
INCL_PROFL: false,
|
||||
INCL_TOTAL: false,
|
||||
INCL_VENDR: false,
|
||||
EMS_VER: "2.0",
|
||||
},
|
||||
];
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.ENV`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.ENV`),
|
||||
envFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} ENV file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating ENV file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGenerateEnvFile;
|
||||
85
src/main/ems-parts-order/ems-parts-order-generate-lin.ts
Normal file
85
src/main/ems-parts-order/ems-parts-order-generate-lin.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
import { linFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/lin-field-descriptors";
|
||||
|
||||
const EmsPartsOrderGenerateLinFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = partsOrder.parts_order_lines.map((partsOrderLine) => ({
|
||||
LINE_NO: partsOrderLine.jobline?.line_no,
|
||||
LINE_IND: partsOrderLine.jobline?.line_ind,
|
||||
LINE_REF: partsOrderLine.jobline?.line_ref,
|
||||
TRAN_CODE: partsOrderLine.jobline?.tran_code ?? "1",
|
||||
DB_REF: partsOrderLine.jobline?.db_ref,
|
||||
UNQ_SEQ: partsOrderLine.jobline?.unq_seq,
|
||||
PART_DES_J: false,
|
||||
LINE_DESC: partsOrderLine.jobline?.line_desc,
|
||||
PART_TYPE:
|
||||
partsOrderLine.priceChange === true
|
||||
? partsOrderLine.part_type
|
||||
: partsOrderLine.jobline?.part_type,
|
||||
GLASS_FLAG: partsOrderLine.jobline?.glass_flag,
|
||||
OEM_PARTNO: partsOrderLine.jobline?.oem_partno,
|
||||
PRICE_INC: partsOrderLine.jobline?.price_inc,
|
||||
ALT_PART_I: partsOrderLine.jobline?.alt_part_i,
|
||||
TAX_PART: partsOrderLine.jobline?.tax_part,
|
||||
DB_PRICE: partsOrderLine.jobline?.db_price,
|
||||
ACT_PRICE:
|
||||
partsOrderLine.priceChange === true
|
||||
? partsOrderLine.act_price
|
||||
: partsOrderLine.jobline?.act_price,
|
||||
PRICE_J: partsOrderLine.jobline?.price_j,
|
||||
CERT_PART: partsOrderLine.jobline?.cert_part,
|
||||
PART_QTY: partsOrderLine.jobline?.part_qty,
|
||||
ALT_CO_ID: partsOrderLine.jobline?.alt_co_id,
|
||||
ALT_PARTNO: partsOrderLine.jobline?.alt_partno,
|
||||
ALT_OVERRD: partsOrderLine.jobline?.alt_overrd,
|
||||
ALT_PARTM: partsOrderLine.jobline?.alt_partm,
|
||||
PRT_DSMK_P: partsOrderLine.jobline?.prt_dsmk_p,
|
||||
PRT_DSMK_M: partsOrderLine.jobline?.prt_dsmk_m,
|
||||
MOD_LBR_TY: partsOrderLine.jobline?.mod_lbr_ty,
|
||||
DB_HRS: partsOrderLine.jobline?.db_hrs,
|
||||
MOD_LB_HRS: partsOrderLine.jobline?.mod_lb_hrs,
|
||||
LBR_INC: partsOrderLine.jobline?.lbr_inc,
|
||||
LBR_OP: partsOrderLine.jobline?.lbr_op,
|
||||
LBR_HRS_J: partsOrderLine.jobline?.lbr_hrs_j,
|
||||
LBR_TYP_J: partsOrderLine.jobline?.lbr_typ_j,
|
||||
LBR_OP_J: partsOrderLine.jobline?.lbr_op_j,
|
||||
PAINT_STG: partsOrderLine.jobline?.paint_stg,
|
||||
PAINT_TONE: partsOrderLine.jobline?.paint_tone,
|
||||
LBR_TAX: partsOrderLine.jobline?.lbr_tax,
|
||||
LBR_AMT: partsOrderLine.jobline?.lbr_amt,
|
||||
MISC_AMT: partsOrderLine.jobline?.misc_amt,
|
||||
MISC_SUBLT: partsOrderLine.jobline?.misc_sublt,
|
||||
MISC_TAX: partsOrderLine.jobline?.misc_tax,
|
||||
BETT_TYPE: partsOrderLine.jobline?.bett_type,
|
||||
BETT_PCTG: partsOrderLine.jobline?.bett_pctg,
|
||||
BETT_AMT: partsOrderLine.jobline?.bett_amt,
|
||||
BETT_TAX: partsOrderLine.jobline?.bett_tax,
|
||||
}));
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.LIN`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.LIN`),
|
||||
linFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} LIN file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating LIN file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGenerateLinFile;
|
||||
59
src/main/ems-parts-order/ems-parts-order-generate-pfh.ts
Normal file
59
src/main/ems-parts-order/ems-parts-order-generate-pfh.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
import { pfhFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/pfh-field-descriptors";
|
||||
|
||||
const EmsPartsOrderGeneratePfhFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = [
|
||||
{
|
||||
ID_PRO_NAM: "REPAIR FACILITY", // Job.id_pro_nam?.Value
|
||||
TAX_PRETHR: (partsOrder.job.tax_prethr || 0) * 100,
|
||||
TAX_THRAMT: (partsOrder.job.tax_thramt || 0) * 100,
|
||||
TAX_PSTTHR: (partsOrder.job.tax_pstthr || 0) * 100,
|
||||
TAX_TOW_IN: true, // Job.tax_tow_in?.Value
|
||||
TAX_TOW_RT: (partsOrder.job.tax_tow_rt || 0) * 100,
|
||||
TAX_STR_IN: true, // Job.tax_str_in?.Value
|
||||
TAX_STR_RT: (partsOrder.job.tax_str_rt || 0) * 100,
|
||||
TAX_SUB_IN: true, // Job.tax_sub_in?.Value
|
||||
TAX_SUB_RT: (partsOrder.job.tax_sub_rt || 0) * 100,
|
||||
TAX_BTR_IN: true, // Job.tax_btr_in?.Value
|
||||
TAX_LBR_RT:
|
||||
(partsOrder.job.bodyshop?.bill_tax_rates?.state_tax_rate || 0) * 100,
|
||||
TAX_GST_RT:
|
||||
(partsOrder.job.bodyshop?.bill_tax_rates?.federal_tax_rate || 0) *
|
||||
100,
|
||||
TAX_GST_IN: true, // Job.tax_gst_in?.Value
|
||||
ADJ_G_DISC: (partsOrder.job.adj_g_disc || 0) * 100,
|
||||
ADJ_TOWDIS: (partsOrder.job.adj_towdis || 0) * 100,
|
||||
ADJ_STRDIS: (partsOrder.job.adj_strdis || 0) * 100,
|
||||
ADJ_BTR_IN: null, // Job.adj_btr_in?.Value
|
||||
TAX_PREDIS: (partsOrder.job.tax_predis || 0) * 100,
|
||||
},
|
||||
];
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFH`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFH`),
|
||||
pfhFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} PFH file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating PFH file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGeneratePfhFile;
|
||||
302
src/main/ems-parts-order/ems-parts-order-generate-pfl.ts
Normal file
302
src/main/ems-parts-order/ems-parts-order-generate-pfl.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedPflLine } from "../decoder/decode-pfl.interface";
|
||||
import { pflFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/pfl-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import uppercaseObjectKeys from "../util/uppercaseObjectKeys";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
import _ from "lodash";
|
||||
|
||||
const EmsPartsOrderGeneratePflFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
let records;
|
||||
|
||||
if (partsOrder.job.cieca_pfl && !_.isEmpty(partsOrder.job.cieca_pfl)) {
|
||||
records = Object.keys(partsOrder.job.cieca_pfl).map((key) => {
|
||||
const record: DecodedPflLine = partsOrder.job.cieca_pfl[key];
|
||||
return uppercaseObjectKeys(record);
|
||||
});
|
||||
} else {
|
||||
//We don't have the PFL data for an old job, so make it manually.
|
||||
|
||||
records = [
|
||||
{
|
||||
LBR_TYPE: "LAA",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_laa,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAB",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_lab,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
|
||||
{
|
||||
LBR_TYPE: "LAD",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_lad,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAE",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_lae,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAF",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_laf,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAG",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_lag,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAM",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_lam,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAR",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_lar,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAS",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_las,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LAU",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_lau,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LA1",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_la1,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LA2",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_la2,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LA3",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_la3,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
{
|
||||
LBR_TYPE: "LA4",
|
||||
LBR_DESC: "",
|
||||
LBR_RATE: partsOrder.job.rate_la4,
|
||||
LBR_TAX_IN: true,
|
||||
LBR_TAXP: null, // Job.bodyshop.bill_tax_rates.state_tax_rate?.Value ?? 0,
|
||||
LBR_ADJP: 0,
|
||||
LBR_TX_TY1: null, //partsOrder.job.lbr_tx_ty1,
|
||||
LBR_TX_IN1: null, //partsOrder.job.lbr_tx_in1,
|
||||
LBR_TX_TY2: null, //partsOrder.job.lbr_tx_ty2,
|
||||
LBR_TX_IN2: null, //partsOrder.job.lbr_tx_in2,
|
||||
LBR_TX_TY3: null, //partsOrder.job.lbr_tx_ty3,
|
||||
LBR_TX_IN3: null, //partsOrder.job.lbr_tx_in3,
|
||||
LBR_TX_TY4: null, //partsOrder.job.lbr_tx_ty4,
|
||||
LBR_TX_IN4: null, //partsOrder.job.lbr_tx_in4,
|
||||
LBR_TX_TY5: null, //partsOrder.job.lbr_tx_ty5,
|
||||
LBR_TX_IN5: null, //partsOrder.job.lbr_tx_in5,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFL`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFL`),
|
||||
pflFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} PFL file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating PFL file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGeneratePflFile;
|
||||
105
src/main/ems-parts-order/ems-parts-order-generate-pfm.ts
Normal file
105
src/main/ems-parts-order/ems-parts-order-generate-pfm.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import _ from "lodash";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedPfmLine } from "../decoder/decode-pfm.interface";
|
||||
import { pfmFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/pfm-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import uppercaseObjectKeys from "../util/uppercaseObjectKeys";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGeneratePfmFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
let records;
|
||||
if (partsOrder.job.materials && !_.isEmpty(partsOrder.job.materials)) {
|
||||
records = Object.keys(partsOrder.job.materials).map((key) => {
|
||||
const record: DecodedPfmLine = partsOrder.job.materials[key];
|
||||
return uppercaseObjectKeys(record);
|
||||
});
|
||||
} else {
|
||||
//Older records may not have materials, especially for ImEX.
|
||||
records = [
|
||||
{
|
||||
MATL_TYPE: "MAPA",
|
||||
CAL_CODE: null,
|
||||
CAL_DESC: null,
|
||||
CAL_MAXDLR: 0,
|
||||
CAL_PRIP: 0,
|
||||
CAL_SECP: 0,
|
||||
MAT_CALP: 0,
|
||||
CAL_PRETHR: 0,
|
||||
CAL_PSTTHR: 0,
|
||||
CAL_THRAMT: 0,
|
||||
CAL_LBRMIN: 0,
|
||||
CAL_LBRMAX: 0,
|
||||
CAL_LBRRTE: partsOrder.job.rate_mapa,
|
||||
CAL_OPCODE: null,
|
||||
TAX_IND: true,
|
||||
MAT_TAXP: null,
|
||||
MAT_ADJP: null,
|
||||
MAT_TX_TY1: null,
|
||||
MAT_TX_IN1: null,
|
||||
MAT_TX_TY2: null,
|
||||
MAT_TX_IN2: null,
|
||||
MAT_TX_TY3: null,
|
||||
MAT_TX_IN3: null,
|
||||
MAT_TX_TY4: null,
|
||||
MAT_TX_IN4: null,
|
||||
MAT_TX_TY5: null,
|
||||
MAT_TX_IN5: null,
|
||||
},
|
||||
{
|
||||
MATL_TYPE: "MASH",
|
||||
CAL_CODE: null,
|
||||
CAL_DESC: null,
|
||||
CAL_MAXDLR: 0,
|
||||
CAL_PRIP: 0,
|
||||
CAL_SECP: 0,
|
||||
MAT_CALP: 0,
|
||||
CAL_PRETHR: 0,
|
||||
CAL_PSTTHR: 0,
|
||||
CAL_THRAMT: 0,
|
||||
CAL_LBRMIN: 0,
|
||||
CAL_LBRMAX: 0,
|
||||
CAL_LBRRTE: partsOrder.job.rate_mash,
|
||||
CAL_OPCODE: null,
|
||||
TAX_IND: true,
|
||||
MAT_TAXP: null,
|
||||
MAT_ADJP: null,
|
||||
MAT_TX_TY1: null,
|
||||
MAT_TX_IN1: null,
|
||||
MAT_TX_TY2: null,
|
||||
MAT_TX_IN2: null,
|
||||
MAT_TX_TY3: null,
|
||||
MAT_TX_IN3: null,
|
||||
MAT_TX_TY4: null,
|
||||
MAT_TX_IN4: null,
|
||||
MAT_TX_TY5: null,
|
||||
MAT_TX_IN5: null,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFM`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFM`),
|
||||
pfmFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} PFM file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating PFM file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGeneratePfmFile;
|
||||
34
src/main/ems-parts-order/ems-parts-order-generate-pfo.ts
Normal file
34
src/main/ems-parts-order/ems-parts-order-generate-pfo.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { pfoFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/pfo-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGeneratePfoFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = []; //This was kept blank previously as well.
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFO`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFO`),
|
||||
pfoFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} PFO file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating PFO file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGeneratePfoFile;
|
||||
39
src/main/ems-parts-order/ems-parts-order-generate-pfp.ts
Normal file
39
src/main/ems-parts-order/ems-parts-order-generate-pfp.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedPfpLine } from "../decoder/decode-pfp.interface";
|
||||
import { pfpFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/pfp-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import uppercaseObjectKeys from "../util/uppercaseObjectKeys";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGeneratePfpFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = Object.keys(partsOrder.job.parts_tax_rates).map((key) => {
|
||||
const record: DecodedPfpLine = partsOrder.job.parts_tax_rates[key];
|
||||
return uppercaseObjectKeys(record);
|
||||
});
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFP`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFP`),
|
||||
pfpFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} PFP file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating PFP file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGeneratePfpFile;
|
||||
34
src/main/ems-parts-order/ems-parts-order-generate-pft.ts
Normal file
34
src/main/ems-parts-order/ems-parts-order-generate-pft.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { pftFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/pft-field-descriptor";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGeneratePftFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = []; //Left blank intentionally as per previous code.
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFT`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.PFT`),
|
||||
pftFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} PFT file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating PFT file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGeneratePftFile;
|
||||
40
src/main/ems-parts-order/ems-parts-order-generate-stl.ts
Normal file
40
src/main/ems-parts-order/ems-parts-order-generate-stl.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { DecodedStlLine } from "../decoder/decode-stl.interface";
|
||||
import { stlFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/stl-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import uppercaseObjectKeys from "../util/uppercaseObjectKeys";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGenerateStlFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
//TODO: Add CIECA STL to parts order.
|
||||
const records = Object.keys(partsOrder.job.cieca_stl?.data).map((key) => {
|
||||
const record: DecodedStlLine = partsOrder.job.cieca_stl.data[key];
|
||||
return uppercaseObjectKeys(record);
|
||||
});
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.STL`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.STL`),
|
||||
stlFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} STL file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating STL file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGenerateStlFile;
|
||||
36
src/main/ems-parts-order/ems-parts-order-generate-ttl.ts
Normal file
36
src/main/ems-parts-order/ems-parts-order-generate-ttl.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { ttlFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/ttl-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import uppercaseObjectKeys from "../util/uppercaseObjectKeys";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGenerateTtlFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
//TODO: Add CIECA STL to parts order.
|
||||
const records = uppercaseObjectKeys(partsOrder.job.cieca_ttl?.data);
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.TTL`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.TTL`),
|
||||
ttlFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords([records]);
|
||||
console.log(`${records.length} TTL file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating TTL file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGenerateTtlFile;
|
||||
65
src/main/ems-parts-order/ems-parts-order-generate-veh.ts
Normal file
65
src/main/ems-parts-order/ems-parts-order-generate-veh.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import { vehFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/veh-field-descriptors";
|
||||
import {
|
||||
deleteEmsFileIfExists,
|
||||
generateEmsOutFilePath,
|
||||
} from "../util/ems-util";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const EmsPartsOrderGenerateVehFile = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const records = [
|
||||
{
|
||||
IMPACT_1: partsOrder.job.area_of_damage?.impact1 || null,
|
||||
IMPACT_2: partsOrder.job.area_of_damage?.impact2 || null,
|
||||
DMG_MEMO: null,
|
||||
DB_V_CODE: "",
|
||||
PLATE_NO: partsOrder.job.plate_no || null,
|
||||
PLATE_ST: partsOrder.job.plate_st || null,
|
||||
V_VIN: partsOrder.job.v_vin || null,
|
||||
V_COND: "",
|
||||
V_PROD_DT: "",
|
||||
V_MODEL_YR: partsOrder.job.v_model_yr || null,
|
||||
V_MAKECODE: "",
|
||||
V_MAKEDESC: partsOrder.job.v_make_desc || null,
|
||||
V_MODEL: partsOrder.job.v_model_desc || null,
|
||||
V_TYPE: partsOrder.job.vehicle?.v_type || null,
|
||||
V_BSTYLE: partsOrder.job.vehicle?.v_bstyle || null,
|
||||
V_TRIMCODE: partsOrder.job.vehicle?.v_trimcode || null,
|
||||
TRIM_COLOR: partsOrder.job.vehicle?.trim_color || null,
|
||||
V_MLDGCODE: partsOrder.job.vehicle?.v_mldgcode || null,
|
||||
V_ENGINE: partsOrder.job.vehicle?.v_engine || null,
|
||||
V_MILEAGE: partsOrder.job.vehicle?.v_mileage || null,
|
||||
V_OPTIONS: null,
|
||||
V_COLOR: partsOrder.job.vehicle?.v_color || null,
|
||||
V_TONE: Number(partsOrder.job.vehicle?.v_tone) || null,
|
||||
V_STAGE: null,
|
||||
PAINT_CD1: partsOrder.job.vehicle?.v_paint_codes?.paint_cd1 || "",
|
||||
PAINT_CD2: partsOrder.job.vehicle?.v_paint_codes?.paint_cd2 || "",
|
||||
PAINT_CD3: partsOrder.job.vehicle?.v_paint_codes?.paint_cd3 || "",
|
||||
V_MEMO: null,
|
||||
},
|
||||
];
|
||||
|
||||
await deleteEmsFileIfExists(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.VEH`),
|
||||
);
|
||||
|
||||
const dbf: DBFFile = await DBFFile.create(
|
||||
generateEmsOutFilePath(`${partsOrder.job.ciecaid}.VEH`),
|
||||
vehFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} VEH file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating VEH file:", errorTypeCheck(error));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default EmsPartsOrderGenerateVehFile;
|
||||
83
src/main/ems-parts-order/ems-parts-order-handler.ts
Normal file
83
src/main/ems-parts-order/ems-parts-order-handler.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import log from "electron-log/main";
|
||||
import express from "express";
|
||||
import _ from "lodash";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import store from "../store/store";
|
||||
import createdDirectoryIfNotExist from "../util/createDirectoryIfNotExist";
|
||||
import EmsPartsOrderGenerateAd1File from "./ems-parts-order-generate-ad1";
|
||||
import EmsPartsOrderGenerateAd2File from "./ems-parts-order-generate-ad2";
|
||||
import EmsPartsOrderGenerateEnvFile from "./ems-parts-order-generate-env";
|
||||
import EmsPartsOrderGenerateLinFile from "./ems-parts-order-generate-lin";
|
||||
import EmsPartsOrderGeneratePfhFile from "./ems-parts-order-generate-pfh";
|
||||
import EmsPartsOrderGeneratePflFile from "./ems-parts-order-generate-pfl";
|
||||
import EmsPartsOrderGeneratePfmFile from "./ems-parts-order-generate-pfm";
|
||||
import EmsPartsOrderGeneratePfoFile from "./ems-parts-order-generate-pfo";
|
||||
import EmsPartsOrderGeneratePfpFile from "./ems-parts-order-generate-pfp";
|
||||
import EmsPartsOrderGeneratePftFile from "./ems-parts-order-generate-pft";
|
||||
import EmsPartsOrderGenerateStlFile from "./ems-parts-order-generate-stl";
|
||||
import EmsPartsOrderGenerateTtlFile from "./ems-parts-order-generate-ttl";
|
||||
import EmsPartsOrderGenerateVehFile from "./ems-parts-order-generate-veh";
|
||||
import { EmsPartsOrder } from "./ems-parts-order-interfaces";
|
||||
|
||||
const handleEMSPartsOrder = async (
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<void> => {
|
||||
//Route handler here only.
|
||||
|
||||
const partsOrderBody = req.body as EmsPartsOrder;
|
||||
try {
|
||||
await generateEMSPartsOrder(partsOrderBody);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
log.error("Error generating parts price change", errorTypeCheck(error));
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Error generating parts price change.",
|
||||
...errorTypeCheck(error),
|
||||
});
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const generateEMSPartsOrder = async (
|
||||
partsOrder: EmsPartsOrder,
|
||||
): Promise<void> => {
|
||||
log.debug(" Generating parts price change");
|
||||
//Check to make sure that the EMS Output file path exists. If it doesn't, create it. If it's not set, abandon ship.
|
||||
|
||||
const emsOutFilePath: string | null = store.get("settings.emsOutFilePath");
|
||||
if (_.isEmpty(emsOutFilePath) || emsOutFilePath === null) {
|
||||
log.error("EMS Out file path is not set");
|
||||
throw new Error("EMS Out file path is not set");
|
||||
}
|
||||
try {
|
||||
createdDirectoryIfNotExist(emsOutFilePath);
|
||||
|
||||
//Generate all required files: ad1, ad2, veh, lin, pfh, pfl, pfm,pfo, pfp, pft, stl, ttl
|
||||
await EmsPartsOrderGenerateAd1File(partsOrder);
|
||||
await EmsPartsOrderGenerateAd2File(partsOrder);
|
||||
await EmsPartsOrderGenerateVehFile(partsOrder);
|
||||
await EmsPartsOrderGenerateLinFile(partsOrder);
|
||||
await EmsPartsOrderGeneratePfhFile(partsOrder);
|
||||
await EmsPartsOrderGeneratePflFile(partsOrder);
|
||||
await EmsPartsOrderGeneratePfmFile(partsOrder);
|
||||
await EmsPartsOrderGeneratePfoFile(partsOrder);
|
||||
await EmsPartsOrderGeneratePfpFile(partsOrder);
|
||||
await EmsPartsOrderGeneratePftFile(partsOrder);
|
||||
await EmsPartsOrderGenerateStlFile(partsOrder);
|
||||
await EmsPartsOrderGenerateTtlFile(partsOrder);
|
||||
|
||||
await EmsPartsOrderGenerateEnvFile(partsOrder);
|
||||
|
||||
log.info(
|
||||
"EMS Parts Order files generated successfully for " +
|
||||
partsOrder.job.ciecaid,
|
||||
);
|
||||
} catch (error) {
|
||||
log.error("Error generating parts price change", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export { handleEMSPartsOrder };
|
||||
322
src/main/ems-parts-order/ems-parts-order-interfaces.ts
Normal file
322
src/main/ems-parts-order/ems-parts-order-interfaces.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
import { CiecaPfl } from "../decoder/decode-pfl.interface";
|
||||
import { DecodedPfmLine } from "../decoder/decode-pfm.interface";
|
||||
import { DecodedPfpLine } from "../decoder/decode-pfp.interface";
|
||||
import { DecodedStlLine } from "../decoder/decode-stl.interface";
|
||||
import { DecodedTtlLine } from "../decoder/decode-ttl.interface";
|
||||
|
||||
export interface TaxRate {
|
||||
prt_type: string;
|
||||
prt_discp: number;
|
||||
prt_mktyp: boolean;
|
||||
prt_mkupp: number;
|
||||
prt_tax_in: boolean;
|
||||
prt_tax_rt: number;
|
||||
}
|
||||
|
||||
export interface BillTaxRates {
|
||||
local_tax_rate: number;
|
||||
state_tax_rate: number;
|
||||
federal_tax_rate: number;
|
||||
}
|
||||
|
||||
export interface PaintCodes {
|
||||
paint_cd1: string | null;
|
||||
paint_cd2: string | null;
|
||||
paint_cd3: string | null;
|
||||
}
|
||||
|
||||
export interface AreaOfDamage {
|
||||
impact1: string;
|
||||
impact2: string | null;
|
||||
}
|
||||
|
||||
// Jobline export interface
|
||||
export interface Jobline {
|
||||
tran_code: string;
|
||||
act_price: number;
|
||||
db_ref: string;
|
||||
db_price: number;
|
||||
db_hrs: number;
|
||||
glass_flag: boolean;
|
||||
id: string;
|
||||
lbr_amt: number;
|
||||
lbr_hrs_j: boolean;
|
||||
lbr_inc: boolean;
|
||||
lbr_op: string;
|
||||
lbr_op_j: boolean;
|
||||
lbr_tax: boolean;
|
||||
lbr_typ_j: boolean;
|
||||
line_desc: string;
|
||||
line_ind: string;
|
||||
line_no: number;
|
||||
line_ref: number;
|
||||
location: string | null;
|
||||
misc_amt: number;
|
||||
misc_sublt: boolean;
|
||||
misc_tax: boolean;
|
||||
mod_lb_hrs: number;
|
||||
mod_lbr_ty: string;
|
||||
oem_partno: string;
|
||||
op_code_desc: string;
|
||||
paint_stg: number;
|
||||
paint_tone: number;
|
||||
part_qty: number;
|
||||
part_type: string;
|
||||
price_inc: boolean;
|
||||
price_j: boolean;
|
||||
prt_dsmk_m: number;
|
||||
prt_dsmk_p: number;
|
||||
tax_part: boolean;
|
||||
unq_seq: number;
|
||||
alt_co_id: string | null;
|
||||
alt_overrd: boolean;
|
||||
alt_part_i: boolean;
|
||||
alt_partm: string | null;
|
||||
alt_partno: string | null;
|
||||
bett_amt: number;
|
||||
bett_pctg: number;
|
||||
bett_tax: boolean;
|
||||
bett_type: string | null;
|
||||
cert_part: boolean;
|
||||
est_seq: string | null;
|
||||
part_descj: boolean;
|
||||
}
|
||||
|
||||
// Parts Order Line export interface
|
||||
export interface PartsOrderLine {
|
||||
jobline: Jobline;
|
||||
act_price: number;
|
||||
id: string;
|
||||
db_price: number;
|
||||
line_desc: string;
|
||||
quantity: number;
|
||||
part_type: string;
|
||||
priceChange: boolean;
|
||||
}
|
||||
|
||||
// Vehicle export interface
|
||||
export interface Vehicle {
|
||||
v_bstyle: string;
|
||||
v_type: string;
|
||||
v_trimcode: string | null;
|
||||
v_tone: string;
|
||||
v_stage: string;
|
||||
v_prod_dt: string | null;
|
||||
v_options: string | null;
|
||||
v_paint_codes: PaintCodes;
|
||||
v_model_yr: string;
|
||||
v_model_desc: string;
|
||||
v_mldgcode: string | null;
|
||||
v_makecode: string;
|
||||
v_make_desc: string;
|
||||
v_engine: string;
|
||||
v_cond: string;
|
||||
v_color: string | null;
|
||||
trim_color: string | null;
|
||||
shopid: string;
|
||||
plate_no: string;
|
||||
plate_st: string;
|
||||
db_v_code: string;
|
||||
v_vin: string;
|
||||
}
|
||||
|
||||
// Bodyshop export interface
|
||||
export interface Bodyshop {
|
||||
shopname: string;
|
||||
bill_tax_rates: BillTaxRates;
|
||||
}
|
||||
|
||||
// Job export interface
|
||||
export interface Job {
|
||||
bodyshop: Bodyshop;
|
||||
ro_number: string;
|
||||
clm_no: string;
|
||||
asgn_no: string;
|
||||
asgn_date: string;
|
||||
state_tax_rate: number | null;
|
||||
area_of_damage: AreaOfDamage;
|
||||
asgn_type: string | null;
|
||||
ciecaid: string;
|
||||
cieca_pfl: CiecaPfl;
|
||||
clm_addr1: string | null;
|
||||
clm_city: string | null;
|
||||
clm_addr2: string | null;
|
||||
clm_ct_fn: string | null;
|
||||
clm_ct_ln: string | null;
|
||||
clm_ct_ph: string | null;
|
||||
clm_ct_phx: string | null;
|
||||
clm_ctry: string | null;
|
||||
clm_ea: string | null;
|
||||
clm_fax: string | null;
|
||||
clm_faxx: string | null;
|
||||
clm_ofc_id: string | null;
|
||||
clm_ofc_nm: string | null;
|
||||
clm_ph1: string | null;
|
||||
clm_ph1x: string | null;
|
||||
clm_ph2: string | null;
|
||||
clm_ph2x: string | null;
|
||||
clm_st: string | null;
|
||||
clm_title: string | null;
|
||||
clm_total: number;
|
||||
clm_zip: string | null;
|
||||
ded_amt: number;
|
||||
est_addr1: string | null;
|
||||
est_addr2: string | null;
|
||||
est_city: string | null;
|
||||
est_co_nm: string | null;
|
||||
est_ct_fn: string;
|
||||
est_ctry: string | null;
|
||||
est_ct_ln: string;
|
||||
est_ea: string;
|
||||
est_ph1: string | null;
|
||||
est_st: string | null;
|
||||
est_zip: string | null;
|
||||
g_bett_amt: number;
|
||||
id: string;
|
||||
ins_addr1: string | null;
|
||||
ins_city: string | null;
|
||||
ins_addr2: string | null;
|
||||
ins_co_id: string | null;
|
||||
ins_co_nm: string;
|
||||
ins_ct_fn: string | null;
|
||||
ins_ct_ln: string | null;
|
||||
ins_ct_ph: string | null;
|
||||
ins_ct_phx: string | null;
|
||||
ins_ctry: string | null;
|
||||
ins_ea: string | null;
|
||||
ins_fax: string | null;
|
||||
ins_faxx: string | null;
|
||||
ins_memo: string | null;
|
||||
ins_ph1: string | null;
|
||||
ins_ph1x: string | null;
|
||||
ins_ph2: string | null;
|
||||
ins_ph2x: string | null;
|
||||
ins_st: string | null;
|
||||
ins_title: string | null;
|
||||
ins_zip: string | null;
|
||||
insd_addr1: string;
|
||||
insd_addr2: string | null;
|
||||
insd_city: string;
|
||||
insd_co_nm: string | null;
|
||||
insd_ctry: string | null;
|
||||
insd_ea: string | null;
|
||||
insd_fax: string | null;
|
||||
insd_faxx: string | null;
|
||||
insd_fn: string;
|
||||
insd_ln: string;
|
||||
insd_ph1: string;
|
||||
insd_ph1x: string | null;
|
||||
insd_ph2: string;
|
||||
insd_ph2x: string | null;
|
||||
insd_st: string;
|
||||
insd_title: string | null;
|
||||
insd_zip: string;
|
||||
loss_cat: string;
|
||||
loss_date: string;
|
||||
loss_desc: string;
|
||||
loss_of_use: string | null;
|
||||
loss_type: string;
|
||||
ownr_addr1: string;
|
||||
ownr_addr2: string | null;
|
||||
ownr_city: string;
|
||||
ownr_co_nm: string | null;
|
||||
ownr_ctry: string | null;
|
||||
ownr_ea: string | null;
|
||||
ownr_fax: string | null;
|
||||
ownr_faxx: string | null;
|
||||
ownr_ph1: string;
|
||||
ownr_fn: string;
|
||||
ownr_ln: string;
|
||||
ownr_ph1x: string | null;
|
||||
ownr_ph2: string;
|
||||
ownr_ph2x: string | null;
|
||||
ownr_st: string;
|
||||
ownr_title: string | null;
|
||||
ownr_zip: string;
|
||||
parts_tax_rates: Record<string, DecodedPfpLine>;
|
||||
pay_amt: number;
|
||||
pay_date: string | null;
|
||||
pay_type: string | null;
|
||||
pay_chknm: string;
|
||||
payee_nms: string | null;
|
||||
plate_no: string;
|
||||
plate_st: string;
|
||||
po_number: string | null;
|
||||
policy_no: string;
|
||||
tax_lbr_rt: number;
|
||||
tax_levies_rt: number;
|
||||
tax_paint_mat_rt: number;
|
||||
tax_predis: number;
|
||||
tax_prethr: number;
|
||||
tax_pstthr: number;
|
||||
tax_registration_number: string | null;
|
||||
tax_str_rt: number;
|
||||
tax_shop_mat_rt: number;
|
||||
tax_sub_rt: number;
|
||||
tax_thramt: number;
|
||||
tax_tow_rt: number;
|
||||
theft_ind: boolean;
|
||||
tlos_ind: boolean;
|
||||
towin: boolean;
|
||||
v_color: string | null;
|
||||
v_make_desc: string;
|
||||
v_model_desc: string;
|
||||
v_model_yr: string;
|
||||
v_vin: string;
|
||||
vehicle: Vehicle;
|
||||
agt_zip: string | null;
|
||||
agt_st: string | null;
|
||||
agt_ph2x: string | null;
|
||||
agt_ph2: string | null;
|
||||
agt_ph1x: string | null;
|
||||
agt_ph1: string | null;
|
||||
agt_lic_no: string | null;
|
||||
agt_faxx: string | null;
|
||||
agt_fax: string | null;
|
||||
agt_ea: string | null;
|
||||
agt_ctry: string | null;
|
||||
agt_ct_phx: string | null;
|
||||
agt_ct_ph: string | null;
|
||||
agt_ct_ln: string | null;
|
||||
agt_ct_fn: string | null;
|
||||
agt_co_nm: string | null;
|
||||
agt_co_id: string | null;
|
||||
agt_city: string | null;
|
||||
agt_addr1: string | null;
|
||||
agt_addr2: string | null;
|
||||
adj_g_disc: number;
|
||||
rate_matd: number | null;
|
||||
rate_mash: number;
|
||||
rate_mapa: number;
|
||||
rate_mahw: number;
|
||||
rate_macs: number;
|
||||
rate_mabl: number | null;
|
||||
rate_ma3s: number;
|
||||
rate_ma2t: number;
|
||||
rate_ma2s: number;
|
||||
rate_lau: number;
|
||||
rate_las: number;
|
||||
rate_lar: number;
|
||||
rate_lam: number;
|
||||
rate_lag: number;
|
||||
rate_laf: number;
|
||||
rate_lae: number | null;
|
||||
rate_lad: number | null;
|
||||
rate_lab: number;
|
||||
rate_laa: number;
|
||||
rate_la4: number;
|
||||
rate_la3: number;
|
||||
rate_la2: number;
|
||||
rate_la1: number;
|
||||
materials: Record<string, DecodedPfmLine>;
|
||||
cieca_stl: {
|
||||
data: Array<DecodedStlLine>;
|
||||
};
|
||||
cieca_ttl: { data: DecodedTtlLine };
|
||||
}
|
||||
|
||||
// Main Parts Order export interface
|
||||
export interface EmsPartsOrder {
|
||||
parts_order_lines: PartsOrderLine[];
|
||||
job: Job;
|
||||
}
|
||||
53
src/main/graphql/graphql-client.ts
Normal file
53
src/main/graphql/graphql-client.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { BrowserWindow, ipcMain } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import { GraphQLClient, RequestMiddleware } from "graphql-request";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck.js";
|
||||
import ipcTypes from "../../util/ipcTypes.json";
|
||||
import store from "../store/store.js";
|
||||
import getMainWindow from "../../util/getMainWindow.js";
|
||||
|
||||
const requestMiddleware: RequestMiddleware = async (request) => {
|
||||
const token = await getTokenFromRenderer();
|
||||
log.info(
|
||||
`%c[Graphql Request]%c${request.operationName}`,
|
||||
"color: red",
|
||||
"color: green",
|
||||
request,
|
||||
);
|
||||
|
||||
return {
|
||||
...request,
|
||||
headers: { ...request.headers, Authorization: `Bearer ${token}` },
|
||||
};
|
||||
};
|
||||
|
||||
const client: GraphQLClient = new GraphQLClient(
|
||||
store.get("app.isTest") || false
|
||||
? import.meta.env.VITE_GRAPHQL_ENDPOINT_TEST
|
||||
: import.meta.env.VITE_GRAPHQL_ENDPOINT,
|
||||
{
|
||||
requestMiddleware,
|
||||
},
|
||||
);
|
||||
|
||||
export async function getTokenFromRenderer(): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const mainWindow = getMainWindow();
|
||||
//TODO: Verify that this will work if the app is minimized/closed.
|
||||
mainWindow?.webContents.send(ipcTypes.toRenderer.user.getToken);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
"Unable to send request to renderer process for token",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
}
|
||||
|
||||
// Set up one-time listener for the response
|
||||
ipcMain.once(ipcTypes.toMain.user.getTokenResponse, (_, token: string) => {
|
||||
resolve(token);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default client;
|
||||
272
src/main/graphql/queries.ts
Normal file
272
src/main/graphql/queries.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { UUID } from "crypto";
|
||||
import { parse, TypedQueryDocumentNode } from "graphql";
|
||||
import { gql } from "graphql-request";
|
||||
import { AvailableJobSchema } from "../decoder/decoder";
|
||||
|
||||
// Define types for the query result and variables
|
||||
export interface ActiveBodyshopQueryResult {
|
||||
bodyshops: Array<{
|
||||
id: string;
|
||||
shopname: string;
|
||||
region_config: string;
|
||||
convenient_company: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// No variables needed for this query
|
||||
export const QUERY_ACTIVE_BODYSHOP_TYPED: TypedQueryDocumentNode<
|
||||
ActiveBodyshopQueryResult,
|
||||
Record<never, never>
|
||||
> = parse(gql`
|
||||
query QUERY_ACTIVE_BODYSHOP {
|
||||
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
||||
id
|
||||
shopname
|
||||
region_config
|
||||
convenient_company
|
||||
}
|
||||
}
|
||||
`) as TypedQueryDocumentNode<ActiveBodyshopQueryResult, Record<never, never>>;
|
||||
|
||||
export interface MasterdataQueryResult {
|
||||
masterdata: Array<{
|
||||
value: string;
|
||||
key: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface MasterdataQueryVariables {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export const QUERY_MASTERDATA_TYPED: TypedQueryDocumentNode<
|
||||
MasterdataQueryResult,
|
||||
MasterdataQueryVariables
|
||||
> = parse(gql`
|
||||
query QUERY_MASTERDATA($key: String!) {
|
||||
masterdata(where: { key: { _eq: $key } }) {
|
||||
value
|
||||
key
|
||||
}
|
||||
}
|
||||
`) as TypedQueryDocumentNode<MasterdataQueryResult, MasterdataQueryVariables>;
|
||||
|
||||
export interface VehicleQueryResult {
|
||||
vehicles: Array<{
|
||||
id: UUID;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface VehicleQueryVariables {
|
||||
vin: string;
|
||||
}
|
||||
|
||||
export const QUERY_VEHICLE_BY_VIN_TYPED: TypedQueryDocumentNode<
|
||||
VehicleQueryResult,
|
||||
VehicleQueryVariables
|
||||
> = parse(gql`
|
||||
query QUERY_VEHICLE_BY_VIN($vin: String!) {
|
||||
vehicles(where: { v_vin: { _eq: $vin } }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`) as TypedQueryDocumentNode<VehicleQueryResult, VehicleQueryVariables>;
|
||||
|
||||
export interface QueryJobByClmNoResult {
|
||||
jobs: Array<{
|
||||
id: UUID;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface QueryJobByClmNoVariables {
|
||||
clm_no: string;
|
||||
}
|
||||
|
||||
export const QUERY_JOB_BY_CLM_NO_TYPED: TypedQueryDocumentNode<
|
||||
QueryJobByClmNoResult,
|
||||
QueryJobByClmNoVariables
|
||||
> = parse(gql`
|
||||
query QUERY_JOB_BY_CLM_NO($clm_no: String!) {
|
||||
jobs(where: { clm_no: { _eq: $clm_no } }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`) as TypedQueryDocumentNode<QueryJobByClmNoResult, QueryJobByClmNoVariables>;
|
||||
|
||||
export interface InsertAvailableJobResult {
|
||||
returning: Array<{
|
||||
id: UUID;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface InsertAvailableJobVariables {
|
||||
jobInput: Array<AvailableJobSchema>;
|
||||
}
|
||||
|
||||
export const INSERT_AVAILABLE_JOB_TYPED: TypedQueryDocumentNode<
|
||||
InsertAvailableJobResult,
|
||||
InsertAvailableJobVariables
|
||||
> = parse(gql`
|
||||
mutation INSERT_AVAILABLE_JOB($jobInput: [available_jobs_insert_input!]!) {
|
||||
insert_available_jobs(
|
||||
objects: $jobInput
|
||||
on_conflict: {
|
||||
constraint: available_jobs_clm_no_bodyshopid_key
|
||||
update_columns: [
|
||||
clm_amt
|
||||
cieca_id
|
||||
est_data
|
||||
issupplement
|
||||
ownr_name
|
||||
source_system
|
||||
supplement_number
|
||||
vehicle_info
|
||||
]
|
||||
}
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`) as TypedQueryDocumentNode<
|
||||
InsertAvailableJobResult,
|
||||
InsertAvailableJobVariables
|
||||
>;
|
||||
|
||||
// Add PpgData Query
|
||||
export interface PpgDataQueryResult {
|
||||
bodyshops_by_pk: {
|
||||
id: string;
|
||||
shopname: string;
|
||||
imexshopid: string;
|
||||
} | null;
|
||||
jobs: Array<{
|
||||
id: string;
|
||||
ro_number: string;
|
||||
status: string;
|
||||
ownr_fn: string;
|
||||
ownr_ln: string;
|
||||
ownr_co_nm: string;
|
||||
v_vin: string;
|
||||
v_model_yr: string;
|
||||
v_make_desc: string;
|
||||
v_model_desc: string;
|
||||
v_color: string;
|
||||
plate_no: string;
|
||||
ins_co_nm: string;
|
||||
est_ct_fn: string;
|
||||
est_ct_ln: string;
|
||||
rate_mapa: number;
|
||||
rate_lab: number;
|
||||
job_totals: {
|
||||
rates?: {
|
||||
mapa?: {
|
||||
total?: {
|
||||
amount?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
totals?: {
|
||||
subtotal?: {
|
||||
amount?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
vehicle: {
|
||||
v_paint_codes: {
|
||||
paint_cd1?: string;
|
||||
};
|
||||
};
|
||||
labhrs: {
|
||||
aggregate: {
|
||||
sum: {
|
||||
mod_lb_hrs: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
larhrs: {
|
||||
aggregate: {
|
||||
sum: {
|
||||
mod_lb_hrs: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface PpgDataQueryVariables {
|
||||
today: string;
|
||||
todayplus5: string;
|
||||
shopid: string;
|
||||
}
|
||||
|
||||
export const PPG_DATA_QUERY_TYPED: TypedQueryDocumentNode<
|
||||
PpgDataQueryResult,
|
||||
PpgDataQueryVariables
|
||||
> = parse(gql`
|
||||
query PpgData(
|
||||
$today: timestamptz!
|
||||
$todayplus5: timestamptz!
|
||||
$shopid: uuid!
|
||||
) {
|
||||
bodyshops_by_pk(id: $shopid) {
|
||||
id
|
||||
shopname
|
||||
imexshopid
|
||||
}
|
||||
jobs(
|
||||
where: {
|
||||
_or: [
|
||||
{
|
||||
_and: [
|
||||
{ scheduled_in: { _lte: $todayplus5 } }
|
||||
{ scheduled_in: { _gte: $today } }
|
||||
]
|
||||
}
|
||||
{ inproduction: { _eq: true } }
|
||||
]
|
||||
}
|
||||
) {
|
||||
id
|
||||
ro_number
|
||||
status
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
v_vin
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_color
|
||||
plate_no
|
||||
ins_co_nm
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
rate_mapa
|
||||
rate_lab
|
||||
job_totals
|
||||
vehicle {
|
||||
v_paint_codes
|
||||
}
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`) as TypedQueryDocumentNode<PpgDataQueryResult, PpgDataQueryVariables>;
|
||||
210
src/main/http-server/http-server.ts
Normal file
210
src/main/http-server/http-server.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import cors from "cors";
|
||||
import { app } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import express from "express";
|
||||
import http from "http";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import ImportJob from "../decoder/decoder";
|
||||
import folderScan from "../decoder/folder-scan";
|
||||
import { handleEMSPartsOrder } from "../ems-parts-order/ems-parts-order-handler";
|
||||
import { handleShopMetaDataFetch } from "../ipc/ipcMainHandler.user";
|
||||
import { handlePartsPriceChangeRequest } from "../ppc/ppc-handler";
|
||||
import { handleQuickBookRequest } from "../quickbooks-desktop/quickbooks-desktop";
|
||||
|
||||
export default class LocalServer {
|
||||
private readonly app: express.Application;
|
||||
private server: http.Server | null;
|
||||
private PORT = 1337;
|
||||
|
||||
constructor() {
|
||||
this.server = null;
|
||||
this.app = express();
|
||||
this.configureMiddleware();
|
||||
this.configureRoutes();
|
||||
}
|
||||
|
||||
private configureMiddleware(): void {
|
||||
const allowedOrigins = [
|
||||
"http://localhost",
|
||||
"https://localhost",
|
||||
"http://localhost:3000",
|
||||
"https://localhost:3000",
|
||||
"https://test.imex.online",
|
||||
"https://imex.online",
|
||||
"https://test.romeonline.io",
|
||||
"https://romeonline.io",
|
||||
"https://www.test.imex.online",
|
||||
"https://www.imex.online",
|
||||
"https://www.test.romeonline.io",
|
||||
"https://www.romeonline.io",
|
||||
];
|
||||
|
||||
this.app.use(
|
||||
cors({
|
||||
origin: (origin, callback) => {
|
||||
// Allow requests with no origin (like mobile apps, curl requests)
|
||||
if (!origin) return callback(null, true);
|
||||
|
||||
if (allowedOrigins.indexOf(origin) !== -1) {
|
||||
return callback(null, true);
|
||||
} else {
|
||||
return callback(null, false);
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// Parse JSON bodies
|
||||
this.app.use(express.json());
|
||||
this.app.use(express.urlencoded());
|
||||
|
||||
//Add logger Middleware
|
||||
this.app.use((req, res, next) => {
|
||||
const startTime = Date.now();
|
||||
const requestId = Math.random().toString(36).substring(2, 15);
|
||||
|
||||
// Log request details
|
||||
log.info(
|
||||
`[HTTP Server] [${requestId}] Request: ${req.method} ${req.url}`,
|
||||
);
|
||||
log.info(
|
||||
`[HTTP Server] [${requestId}] Headers: ${JSON.stringify(req.headers)}`,
|
||||
);
|
||||
|
||||
// Log request body if it exists
|
||||
if (req.body && Object.keys(req.body).length > 0) {
|
||||
log.info(
|
||||
`[HTTP Server] [${requestId}] Body: ${JSON.stringify(req.body)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Capture the original methods
|
||||
const originalSend = res.send;
|
||||
const originalJson = res.json;
|
||||
|
||||
// Override send method to log response
|
||||
res.send = function (body): express.Response {
|
||||
log.info(`[HTTP Server] [${requestId}] Response body: ${body}`);
|
||||
log.info(
|
||||
`[HTTP Server] [${requestId}] Response time: ${Date.now() - startTime}ms`,
|
||||
);
|
||||
return originalSend.call(this, body);
|
||||
};
|
||||
|
||||
// Override json method to log response
|
||||
res.json = function (body): express.Response {
|
||||
log.info(
|
||||
`[HTTP Server] [${requestId}] Response body: ${JSON.stringify(body)}`,
|
||||
);
|
||||
log.info(
|
||||
`[HTTP Server] [${requestId}] Response time: ${Date.now() - startTime}ms`,
|
||||
);
|
||||
return originalJson.call(this, body);
|
||||
};
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
private configureRoutes(): void {
|
||||
// Basic health check endpoint
|
||||
this.app.get("/health", (_req: express.Request, res: express.Response) => {
|
||||
res.status(200).json({ status: "ok" });
|
||||
});
|
||||
this.app.post("/ping", (_req, res) => {
|
||||
res.status(200).json({
|
||||
appVer: app.getVersion(),
|
||||
qbPath: app.getPath("userData"), //TODO: Resolve to actual QB file path.
|
||||
});
|
||||
});
|
||||
|
||||
this.app.post("/qb", handleQuickBookRequest);
|
||||
this.app.post("/scan", async (_req, res): Promise<void> => {
|
||||
log.debug("[HTTP Server] Scan request received");
|
||||
const files = await folderScan();
|
||||
res.status(200).json(files);
|
||||
return;
|
||||
});
|
||||
this.app.post("/ppc", handlePartsPriceChangeRequest);
|
||||
this.app.post("/oec", handleEMSPartsOrder);
|
||||
this.app.post(
|
||||
"/import",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
log.debug("[HTTP Server] Import request received");
|
||||
const { filepath } = req.body;
|
||||
if (!filepath) {
|
||||
res.status(400).json({ error: "filepath is required" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ImportJob(filepath);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
log.error(
|
||||
"[HTTP Server] Error importing file",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Error importing file",
|
||||
...errorTypeCheck(error),
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
this.app.post(
|
||||
"/refresh",
|
||||
async (_req: express.Request, res: express.Response) => {
|
||||
log.debug("[HTTP Server] Refresh request received");
|
||||
try {
|
||||
await handleShopMetaDataFetch(true);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
log.error(
|
||||
"[HTTP Server] Error refreshing shop metadata",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Error importing file",
|
||||
...errorTypeCheck(error),
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Add more routes as needed
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
try {
|
||||
this.server = http.createServer(this.app);
|
||||
|
||||
this.server.on("error", (error: NodeJS.ErrnoException) => {
|
||||
if (error.code === "EADDRINUSE") {
|
||||
log.error(
|
||||
`[HTTP Server] Port ${this.PORT} is already in use. Please use a different port.`,
|
||||
);
|
||||
} else {
|
||||
log.error(`[HTTP Server] Server error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
this.server.listen(this.PORT, () => {
|
||||
log.info(
|
||||
`[HTTP Server] Local HTTP server running on port ${this.PORT}`,
|
||||
);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
log.error("[HTTP Server] Error starting server", errorTypeCheck(error));
|
||||
}
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
log.info("[HTTP Server] Local HTTP server stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/main/index.test.ts
Normal file
19
src/main/index.test.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { _electron as electron } from "playwright";
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
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
|
||||
// the result of the require('electron') in the main app script.
|
||||
return app.isPackaged;
|
||||
});
|
||||
|
||||
expect(isPackaged).toBe(false);
|
||||
|
||||
// Wait for the first BrowserWindow to open
|
||||
// and return its Page object
|
||||
const window = await electronApp.firstWindow();
|
||||
// close app
|
||||
await electronApp.close();
|
||||
});
|
||||
691
src/main/index.ts
Normal file
691
src/main/index.ts
Normal file
@@ -0,0 +1,691 @@
|
||||
import { is, optimizer, platform } from "@electron-toolkit/utils";
|
||||
import Sentry from "@sentry/electron/main";
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
globalShortcut,
|
||||
ipcMain,
|
||||
Menu,
|
||||
nativeImage,
|
||||
shell,
|
||||
Tray,
|
||||
} from "electron";
|
||||
import log from "electron-log/main";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import path, { join } from "path";
|
||||
import imexAppIcon from "../../resources/icon.png?asset";
|
||||
import romeAppIcon from "../../resources/ro-icon.png?asset";
|
||||
|
||||
import {
|
||||
default as ErrorTypeCheck,
|
||||
default as errorTypeCheck,
|
||||
} from "../util/errorTypeCheck";
|
||||
import ipcTypes from "../util/ipcTypes.json";
|
||||
import ImportJob from "./decoder/decoder";
|
||||
import LocalServer from "./http-server/http-server";
|
||||
import store from "./store/store";
|
||||
import { checkForAppUpdates } from "./util/checkForAppUpdates";
|
||||
import { getMainWindow } from "./util/toRenderer";
|
||||
import { GetAllEnvFiles } from "./watcher/watcher";
|
||||
import {
|
||||
isKeepAliveAgentInstalled,
|
||||
setupKeepAliveAgent,
|
||||
} from "./setup-keep-alive-agent";
|
||||
import {
|
||||
isKeepAliveTaskInstalled,
|
||||
setupKeepAliveTask,
|
||||
} from "./setup-keep-alive-task";
|
||||
import ensureWindowOnScreen from "./util/ensureWindowOnScreen";
|
||||
import ongoingMemoryDump, { dumpMemoryStatsToFile } from "../util/memUsage";
|
||||
|
||||
const appIconToUse =
|
||||
import.meta.env.VITE_COMPANY === "IMEX" ? imexAppIcon : romeAppIcon;
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://ba41d22656999a8c1fd63bcb7df98650@o492140.ingest.us.sentry.io/4509074139447296",
|
||||
});
|
||||
|
||||
log.initialize();
|
||||
|
||||
// Configure log format to include process ID
|
||||
log.transports.file.format =
|
||||
"[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] [PID:{processId}] {text}";
|
||||
log.transports.console.format =
|
||||
"[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] [PID:{processId}] {text}";
|
||||
log.transports.file.maxSize = 50 * 1024 * 1024; // 50 MB
|
||||
const isMac: boolean = process.platform === "darwin";
|
||||
const protocol: string = "imexmedia";
|
||||
let isAppQuitting = false; //Needed on Mac as an override to allow us to fully quit the app.
|
||||
let isKeepAliveLaunch = false; // Track if launched via keep-alive
|
||||
// Initialize the server
|
||||
const localServer = new LocalServer();
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotTheLock) {
|
||||
log.warn(
|
||||
"Another instance is already running and could not obtain mutex lock. Exiting this instance.",
|
||||
);
|
||||
isAppQuitting = true;
|
||||
app.quit(); // Quit the app if another instance is already running
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// Validate window position is on screen
|
||||
const { validX, validY } = ensureWindowOnScreen(x, y, width, height);
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
width,
|
||||
height,
|
||||
x: validX,
|
||||
y: validY,
|
||||
show: false, // Start hidden, show later if not keep-alive
|
||||
minWidth: 600,
|
||||
minHeight: 400,
|
||||
//autoHideMenuBar: true,
|
||||
...(process.platform === "linux"
|
||||
? {
|
||||
icon: appIconToUse,
|
||||
}
|
||||
: {}),
|
||||
title: "Shop Partner",
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "../preload/index.js"),
|
||||
sandbox: false,
|
||||
devTools: true,
|
||||
},
|
||||
});
|
||||
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
// { role: 'appMenu' }
|
||||
// @ts-ignore
|
||||
...(isMac
|
||||
? [
|
||||
{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: "about" },
|
||||
{ type: "separator" },
|
||||
{ role: "services" },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{ role: "hideOthers" },
|
||||
{ role: "unhide" },
|
||||
{ type: "separator" },
|
||||
{ role: "quit" },
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
// { role: 'fileMenu' }
|
||||
{
|
||||
label: "File",
|
||||
submenu: [
|
||||
// @ts-ignore
|
||||
...(!isMac ? [{ role: "about" }] : []),
|
||||
// @ts-ignore
|
||||
isMac ? { role: "close" } : { role: "quit" },
|
||||
],
|
||||
},
|
||||
// { role: 'editMenu' }
|
||||
{
|
||||
label: "Edit",
|
||||
submenu: [
|
||||
{ role: "undo" },
|
||||
{ role: "redo" },
|
||||
{ type: "separator" },
|
||||
{ role: "cut" },
|
||||
{ role: "copy" },
|
||||
{ role: "paste" },
|
||||
// @ts-ignore
|
||||
...(isMac
|
||||
? [
|
||||
{ role: "pasteAndMatchStyle" },
|
||||
{ role: "delete" },
|
||||
{ role: "selectAll" },
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Speech",
|
||||
submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }],
|
||||
},
|
||||
]
|
||||
: [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]),
|
||||
],
|
||||
},
|
||||
// { role: 'viewMenu' }
|
||||
{
|
||||
label: "View",
|
||||
// @ts-ignore
|
||||
submenu: [
|
||||
{ role: "reload" },
|
||||
{ role: "forceReload" },
|
||||
{ role: "toggleDevTools" },
|
||||
{ type: "separator" },
|
||||
{ role: "resetZoom" },
|
||||
{ role: "zoomIn" },
|
||||
{ role: "zoomOut" },
|
||||
{ type: "separator" },
|
||||
{ role: "togglefullscreen" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Application",
|
||||
// @ts-ignore
|
||||
submenu: [
|
||||
{
|
||||
label: "Open on Startup",
|
||||
checked: store.get("app.openOnStartup") as boolean,
|
||||
type: "checkbox",
|
||||
click: (): void => {
|
||||
const currentSetting = store.get("app.openOnStartup") as boolean;
|
||||
store.set("app.openOnStartup", !currentSetting);
|
||||
log.info("Open on startup set to", !currentSetting);
|
||||
if (!import.meta.env.DEV) {
|
||||
app.setLoginItemSettings({
|
||||
enabled: true, //This is a windows only command. Updates the task manager and registry.
|
||||
openAtLogin: !currentSetting,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: `Check for Updates (${app.getVersion()})`,
|
||||
click: (): void => {
|
||||
checkForAppUpdates();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Development",
|
||||
id: "development",
|
||||
visible: import.meta.env.DEV,
|
||||
submenu: [
|
||||
{
|
||||
label: "Connect to Test",
|
||||
checked: store.get("app.isTest") as boolean,
|
||||
type: "checkbox",
|
||||
id: "toggleTest",
|
||||
click: (): void => {
|
||||
const currentSetting = store.get("app.isTest") as boolean;
|
||||
store.set("app.isTest", !currentSetting);
|
||||
log.info("Setting isTest to: ", !currentSetting);
|
||||
app.relaunch(); // Relaunch the app
|
||||
preQuitMethods(); //Quitting handlers aren't called. Manually execute to clean up the app.
|
||||
app.exit(0); // Exit the current instance
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Check for updates",
|
||||
click: (): void => {
|
||||
checkForAppUpdates();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Open Log File",
|
||||
click: (): void => {
|
||||
/* action for item 1 */
|
||||
shell
|
||||
.openPath(log.transports.file.getFile().path)
|
||||
.catch((error) => {
|
||||
log.error(
|
||||
"Failed to open log file:",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Clear Log",
|
||||
click: (): void => {
|
||||
log.transports.file.getFile().clear();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Open Config Folder",
|
||||
click: (): void => {
|
||||
shell.openPath(path.dirname(store.path)).catch((error) => {
|
||||
log.error(
|
||||
"Failed to open config folder:",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Log the Store",
|
||||
click: (): void => {
|
||||
log.debug(
|
||||
"Store Contents" + JSON.stringify(store.store, null, 4),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
{
|
||||
label: "Enable Memory Logging",
|
||||
checked: store.get("settings.enableMemDebug") as boolean,
|
||||
type: "checkbox",
|
||||
click: (): void => {
|
||||
const currentSetting = store.get(
|
||||
"settings.enableMemDebug",
|
||||
) as boolean;
|
||||
store.set("settings.enableMemDebug", !currentSetting);
|
||||
log.info("Enable Memory Logging set to", !currentSetting);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Dump Memory Stats Now",
|
||||
click: (): void => {
|
||||
dumpMemoryStatsToFile();
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
// {
|
||||
// label: "Decode Hardcoded Estimate",
|
||||
// click: (): void => {
|
||||
// ImportJob(`C:\\EMS\\CCC\\9ee762f4.ENV`);
|
||||
// },
|
||||
// },
|
||||
{
|
||||
label: "Install Keep Alive",
|
||||
enabled: true, // Default to enabled, update dynamically
|
||||
click: async (): Promise<void> => {
|
||||
try {
|
||||
if (platform.isWindows) {
|
||||
log.debug("Creating Windows keep-alive task");
|
||||
await setupKeepAliveTask();
|
||||
log.info("Successfully installed Windows keep-alive task");
|
||||
} else if (platform.isMacOS) {
|
||||
log.debug("Creating macOS keep-alive agent");
|
||||
await setupKeepAliveAgent();
|
||||
log.info("Successfully installed macOS keep-alive agent");
|
||||
}
|
||||
// Wait to ensure task/agent is registered
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
// Rebuild menu and update enabled state
|
||||
await updateKeepAliveMenuItem();
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Failed to install keep-alive: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
// Optionally notify user (e.g., via dialog or log)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Add All Estimates in watched directories",
|
||||
click: (): void => {
|
||||
GetAllEnvFiles().forEach((file) => ImportJob(file));
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// { role: 'windowMenu' }
|
||||
{
|
||||
label: "Window",
|
||||
submenu: [
|
||||
{ role: "minimize" },
|
||||
{ role: "zoom" },
|
||||
// @ts-ignore
|
||||
...(isMac
|
||||
? [
|
||||
{ type: "separator" },
|
||||
{ role: "front" },
|
||||
{ type: "separator" },
|
||||
{ role: "window" },
|
||||
]
|
||||
: [{ role: "close" }]),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Dynamically update Install Keep Alive enabled state
|
||||
const updateKeepAliveMenuItem = async (): Promise<void> => {
|
||||
try {
|
||||
const isInstalled = platform.isWindows
|
||||
? await isKeepAliveTaskInstalled()
|
||||
: platform.isMacOS
|
||||
? await isKeepAliveAgentInstalled()
|
||||
: false;
|
||||
const developmentMenu = template
|
||||
.find((item) => item.label === "Application")
|
||||
// @ts-ignore
|
||||
?.submenu?.find((item: { id: string }) => item.id === "development")
|
||||
?.submenu as Electron.MenuItemConstructorOptions[];
|
||||
const keepAliveItem = developmentMenu?.find(
|
||||
(item) => item.label === "Install Keep Alive",
|
||||
);
|
||||
if (keepAliveItem) {
|
||||
keepAliveItem.enabled = !isInstalled; // Enable if not installed, disable if installed
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
log.debug(
|
||||
`Updated Install Keep Alive menu item: enabled=${keepAliveItem.enabled}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const menu: Electron.Menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
// Update menu item enabled state on app start
|
||||
updateKeepAliveMenuItem().catch((error) => {
|
||||
log.error(
|
||||
`Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
});
|
||||
|
||||
// Register a global shortcut to show the hidden item
|
||||
globalShortcut.register("CommandOrControl+Shift+T", () => {
|
||||
console.log("Shortcut pressed! Revealing hidden item.");
|
||||
|
||||
// Update the menu to make the hidden item visible
|
||||
// Find the menu item dynamically by its id
|
||||
const fileMenu = template.find((item) => item.label === "Application");
|
||||
// @ts-ignore
|
||||
const hiddenItem = fileMenu?.submenu?.find(
|
||||
(item: { id: string }) => item.id === "development",
|
||||
);
|
||||
//Adjust the development menu as well.
|
||||
|
||||
if (hiddenItem) {
|
||||
hiddenItem.visible = true; // Update the visibility dynamically
|
||||
const menu: Electron.Menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
});
|
||||
|
||||
// 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", () => {
|
||||
if (!isKeepAliveLaunch) {
|
||||
mainWindow.show(); // Show only if not a keep-alive launch
|
||||
}
|
||||
//Start the HTTP server.
|
||||
// Start the local HTTP server
|
||||
try {
|
||||
localServer.start();
|
||||
} catch (error) {
|
||||
log.error("Failed to start HTTP server:", errorTypeCheck(error));
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on("close", (event: Electron.Event) => {
|
||||
if (!isAppQuitting) {
|
||||
event.preventDefault(); // Prevent the window from closing
|
||||
mainWindow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url).catch((error) => {
|
||||
log.error("Failed to open external URL:", errorTypeCheck(error));
|
||||
});
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch((error) => {
|
||||
log.error("Failed to load URL:", errorTypeCheck(error));
|
||||
});
|
||||
} else {
|
||||
mainWindow
|
||||
.loadFile(join(__dirname, "../renderer/index.html"))
|
||||
.catch((error) => {
|
||||
log.error("Failed to load file:", errorTypeCheck(error));
|
||||
});
|
||||
}
|
||||
if (import.meta.env.DEV) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(async () => {
|
||||
// Default open or close DevTools by F12 in development
|
||||
// and ignore CommandOrControl + R in production.
|
||||
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
|
||||
log.debug("App is ready, initializing shortcuts and protocol handlers.");
|
||||
|
||||
if (platform.isWindows) {
|
||||
app.setAppUserModelId("Shop Partner");
|
||||
}
|
||||
|
||||
app.on("browser-window-created", (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window);
|
||||
});
|
||||
|
||||
let isDefaultProtocolClient: boolean;
|
||||
|
||||
// remove so we can register each time as we run the app.
|
||||
app.removeAsDefaultProtocolClient(protocol);
|
||||
|
||||
// If we are running a non-packaged version of the app && on windows
|
||||
if (process.env.NODE_ENV === "development" && process.platform === "win32") {
|
||||
// Set the path of electron.exe and your app.
|
||||
// These two additional parameters are only available on windows.
|
||||
isDefaultProtocolClient = app.setAsDefaultProtocolClient(
|
||||
protocol,
|
||||
process.execPath,
|
||||
[path.resolve(process.argv[1])],
|
||||
);
|
||||
} else {
|
||||
isDefaultProtocolClient = app.setAsDefaultProtocolClient(protocol);
|
||||
}
|
||||
if (isDefaultProtocolClient) {
|
||||
log.info("Protocol handler registered successfully.");
|
||||
} else {
|
||||
log.warn("Failed to register protocol handler.");
|
||||
}
|
||||
|
||||
//Dynamically load ipcMain handlers once ready.
|
||||
try {
|
||||
const { initializeCronTasks } = await import("./ipc/ipcMainConfig");
|
||||
log.debug("Successfully loaded ipcMainConfig");
|
||||
|
||||
try {
|
||||
await initializeCronTasks();
|
||||
log.info("Cron tasks initialized successfully");
|
||||
} catch (error) {
|
||||
log.warn("Non-fatal: Failed to initialize cron tasks", {
|
||||
...ErrorTypeCheck(error),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Fatal: Failed to load ipcMainConfig", {
|
||||
...ErrorTypeCheck(error),
|
||||
});
|
||||
throw error; // Adjust based on whether the app should continue
|
||||
}
|
||||
|
||||
//Create Tray
|
||||
const trayicon = nativeImage.createFromPath(appIconToUse);
|
||||
const tray = new Tray(trayicon.resize({ width: 16 }));
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: "Show App",
|
||||
click: (): void => {
|
||||
openMainWindow();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Quit",
|
||||
click: (): void => {
|
||||
app.quit(); // actually quit the app.
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
tray.on("double-click", () => {
|
||||
openMainWindow();
|
||||
});
|
||||
|
||||
tray.setContextMenu(contextMenu);
|
||||
|
||||
//Check for app updates.
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.allowDowngrade = true;
|
||||
// if (import.meta.env.DEV) {
|
||||
// // Useful for some dev/debugging tasks, but download can
|
||||
// // not be validated because dev app is not signed
|
||||
// autoUpdater.channel = "alpha";
|
||||
// autoUpdater.updateConfigPath = path.join(
|
||||
// __dirname,
|
||||
// "../../dev-app-update.yml",
|
||||
// );
|
||||
// autoUpdater.forceDevUpdateConfig = true;
|
||||
// //autoUpdater.autoDownload = false;
|
||||
// }
|
||||
|
||||
autoUpdater.on("checking-for-update", () => {
|
||||
log.info("Checking for update...");
|
||||
const mainWindow = getMainWindow();
|
||||
mainWindow?.webContents.send(ipcTypes.toRenderer.updates.checking);
|
||||
});
|
||||
autoUpdater.on("update-available", (info) => {
|
||||
log.info("Update available.", info);
|
||||
const mainWindow = getMainWindow();
|
||||
mainWindow?.webContents.send(ipcTypes.toRenderer.updates.available, info);
|
||||
});
|
||||
autoUpdater.on("download-progress", (progress) => {
|
||||
log.info(`Download speed: ${progress.bytesPerSecond}`);
|
||||
log.info(`Downloaded ${progress.percent}%`);
|
||||
log.info(`Total downloaded ${progress.transferred}/${progress.total}`);
|
||||
const mainWindow = getMainWindow();
|
||||
mainWindow?.webContents.send(
|
||||
ipcTypes.toRenderer.updates.downloading,
|
||||
progress,
|
||||
);
|
||||
});
|
||||
autoUpdater.on("update-downloaded", (info) => {
|
||||
log.info("Update downloaded", info);
|
||||
const mainWindow = getMainWindow();
|
||||
mainWindow?.webContents.send(ipcTypes.toRenderer.updates.downloaded, info);
|
||||
});
|
||||
|
||||
// Check if launched with keep-alive protocol (Windows)
|
||||
const args = process.argv.slice(1);
|
||||
if (args.some((arg) => arg.startsWith(`${protocol}://keep-alive`))) {
|
||||
isKeepAliveLaunch = true;
|
||||
}
|
||||
|
||||
//The update itself will run when the bodyshop record is queried to know what release channel to use.
|
||||
openMainWindow();
|
||||
ongoingMemoryDump();
|
||||
|
||||
app.on("activate", function () {
|
||||
openMainWindow();
|
||||
});
|
||||
});
|
||||
|
||||
app.on("open-url", (event: Electron.Event, url: string) => {
|
||||
event.preventDefault();
|
||||
if (url.startsWith(`${protocol}://keep-alive`)) {
|
||||
log.info("Keep-alive protocol received.");
|
||||
// Do nothing, whether app is running or not
|
||||
return;
|
||||
} else {
|
||||
openInExplorer(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Add this event handler for second instance
|
||||
app.on("second-instance", (_event: Electron.Event, argv: string[]) => {
|
||||
const url = argv.find((arg) => arg.startsWith(`${protocol}://`));
|
||||
if (url) {
|
||||
if (url.startsWith(`${protocol}://keep-alive`)) {
|
||||
log.info(
|
||||
"Keep-alive protocol received, app is already running. Nothing to do.",
|
||||
);
|
||||
// Do nothing if already running
|
||||
return;
|
||||
} else {
|
||||
log.info("Received Media URL: ", url);
|
||||
openInExplorer(url);
|
||||
}
|
||||
}
|
||||
// No action taken if no URL is provided
|
||||
});
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit(); //Disable the quit.
|
||||
}
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
preQuitMethods();
|
||||
});
|
||||
|
||||
//We need to hit the prequit methods from here as well to ensure the app quits and restarts.
|
||||
ipcMain.on(ipcTypes.toMain.updates.apply, () => {
|
||||
log.info("Applying update from renderer.");
|
||||
preQuitMethods();
|
||||
setImmediate(() => {
|
||||
app.removeAllListeners("window-all-closed");
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow) mainWindow.close();
|
||||
autoUpdater.quitAndInstall(false);
|
||||
});
|
||||
});
|
||||
|
||||
function preQuitMethods(): void {
|
||||
localServer.stop();
|
||||
const currentSetting = store.get("app.openOnStartup") as boolean;
|
||||
if (!import.meta.env.DEV) {
|
||||
app.setLoginItemSettings({
|
||||
enabled: true, //This is a windows only command. Updates the task manager and registry.
|
||||
openAtLogin: !currentSetting,
|
||||
});
|
||||
}
|
||||
globalShortcut.unregisterAll();
|
||||
isAppQuitting = true;
|
||||
}
|
||||
|
||||
function openMainWindow(): void {
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
} else {
|
||||
createWindow();
|
||||
}
|
||||
}
|
||||
|
||||
function openInExplorer(url: string): void {
|
||||
const folderPath: string = decodeURIComponent(url.split(`${protocol}://`)[1]);
|
||||
log.info("Opening folder in explorer", folderPath);
|
||||
shell.openPath(folderPath).catch((error) => {
|
||||
log.error("Failed to open folder in explorer:", errorTypeCheck(error));
|
||||
});
|
||||
}
|
||||
278
src/main/ipc/ipcMainConfig.ts
Normal file
278
src/main/ipc/ipcMainConfig.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { app, ipcMain } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import path from "path";
|
||||
import ipcTypes from "../../util/ipcTypes.json";
|
||||
import ImportJob from "../decoder/decoder";
|
||||
import store from "../store/store";
|
||||
import { StartWatcher, StopWatcher } from "../watcher/watcher";
|
||||
import {
|
||||
SettingEmsOutFilePathGet,
|
||||
SettingEmsOutFilePathSet,
|
||||
SettingsPaintScaleInputConfigsGet,
|
||||
SettingsPaintScaleInputConfigsSet,
|
||||
SettingsPaintScaleInputPathSet,
|
||||
SettingsPaintScaleOutputConfigsGet,
|
||||
SettingsPaintScaleOutputConfigsSet,
|
||||
SettingsPaintScaleOutputPathSet,
|
||||
SettingsPpcFilePathGet,
|
||||
SettingsPpcFilePathSet,
|
||||
SettingsWatchedFilePathsAdd,
|
||||
SettingsWatchedFilePathsGet,
|
||||
SettingsWatchedFilePathsRemove,
|
||||
SettingsWatcherPollingGet,
|
||||
SettingsWatcherPollingSet,
|
||||
} from "./ipcMainHandler.settings";
|
||||
import {
|
||||
ipcMainHandleAuthStateChanged,
|
||||
ipMainHandleResetPassword,
|
||||
} from "./ipcMainHandler.user";
|
||||
import cron from "node-cron";
|
||||
import { PaintScaleConfig, PaintScaleType } from "../../util/types/paintScale";
|
||||
import { ppgInputHandler, ppgOutputHandler } from "./paintScaleHandlers/PPG";
|
||||
|
||||
const initializeCronTasks = async () => {
|
||||
try {
|
||||
// Fetch input and output configurations
|
||||
const inputConfigs = await SettingsPaintScaleInputConfigsGet();
|
||||
const outputConfigs = await SettingsPaintScaleOutputConfigsGet();
|
||||
|
||||
// Start input cron tasks
|
||||
await handlePaintScaleInputCron(inputConfigs);
|
||||
log.info("Initialized input cron tasks on app startup");
|
||||
|
||||
// Start output cron tasks
|
||||
await handlePaintScaleOutputCron(outputConfigs);
|
||||
log.info("Initialized output cron tasks on app startup");
|
||||
} catch (error) {
|
||||
log.error("Error initializing cron tasks on app startup:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Log all IPC messages and their payloads
|
||||
const logIpcMessages = (): void => {
|
||||
Object.keys(ipcTypes.toMain).forEach((key) => {
|
||||
const messageType = ipcTypes.toMain[key];
|
||||
const originalHandler = ipcMain.listeners(messageType)?.[0];
|
||||
if (originalHandler) {
|
||||
ipcMain.removeAllListeners(messageType);
|
||||
}
|
||||
ipcMain.on(messageType, (event, payload) => {
|
||||
log.info(
|
||||
`%c[IPC Main]%c${messageType}`,
|
||||
"color: red",
|
||||
"color: green",
|
||||
payload,
|
||||
);
|
||||
if (originalHandler) {
|
||||
originalHandler(event, payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Input handler map
|
||||
const inputTypeHandlers: Partial<
|
||||
Record<PaintScaleType, (config: PaintScaleConfig) => Promise<void>>
|
||||
> = {
|
||||
[PaintScaleType.PPG]: ppgInputHandler,
|
||||
// Add other input type handlers as needed
|
||||
};
|
||||
|
||||
// Output handler map
|
||||
const outputTypeHandlers: Partial<
|
||||
Record<PaintScaleType, (config: PaintScaleConfig) => Promise<void>>
|
||||
> = {
|
||||
[PaintScaleType.PPG]: ppgOutputHandler,
|
||||
// Add other output type handlers as needed
|
||||
};
|
||||
|
||||
// Default handler for unsupported types
|
||||
const defaultHandler = async (config: PaintScaleConfig) => {
|
||||
log.debug(
|
||||
`No handler defined for type ${config.type} in config ${config.id}`,
|
||||
);
|
||||
};
|
||||
|
||||
// Input cron job management
|
||||
let inputCronTasks: { [id: string]: cron.ScheduledTask } = {};
|
||||
|
||||
const handlePaintScaleInputCron = async (configs: PaintScaleConfig[]) => {
|
||||
Object.values(inputCronTasks).forEach((task) => task.stop());
|
||||
inputCronTasks = {};
|
||||
|
||||
const validConfigs = configs.filter(
|
||||
(config) => config.path && config.path.trim() !== "",
|
||||
);
|
||||
|
||||
validConfigs.forEach((config) => {
|
||||
const cronExpression = `*/${config.pollingInterval} * * * *`;
|
||||
inputCronTasks[config.id] = cron.schedule(cronExpression, async () => {
|
||||
const handler = inputTypeHandlers[config.type] || defaultHandler;
|
||||
await handler(config);
|
||||
});
|
||||
log.info(
|
||||
`Started input cron task for config ${config.id} (type: ${config.type}) with interval ${config.pollingInterval}m`,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Output cron job management
|
||||
let outputCronTasks: { [id: string]: cron.ScheduledTask } = {};
|
||||
|
||||
const handlePaintScaleOutputCron = async (configs: PaintScaleConfig[]) => {
|
||||
Object.values(outputCronTasks).forEach((task) => task.stop());
|
||||
outputCronTasks = {};
|
||||
|
||||
const validConfigs = configs.filter(
|
||||
(config) => config.path && config.path.trim() !== "",
|
||||
);
|
||||
|
||||
validConfigs.forEach((config) => {
|
||||
const cronExpression = `*/${config.pollingInterval} * * * *`;
|
||||
outputCronTasks[config.id] = cron.schedule(cronExpression, async () => {
|
||||
const handler = outputTypeHandlers[config.type] || defaultHandler;
|
||||
await handler(config);
|
||||
});
|
||||
log.info(
|
||||
`Started output cron task for config ${config.id} (type: ${config.type}) with interval ${config.pollingInterval}m`,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Existing IPC handlers...
|
||||
|
||||
ipcMain.on(ipcTypes.toMain.test, () =>
|
||||
console.log("** Verify that ipcMain is loaded and working."),
|
||||
);
|
||||
|
||||
// Auth handler
|
||||
ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged);
|
||||
ipcMain.on(ipcTypes.toMain.user.resetPassword, ipMainHandleResetPassword);
|
||||
|
||||
// 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 (): Promise<void> => {
|
||||
const relativeEmsFilepath = `_reference/ems/MPI_1/3698420.ENV`;
|
||||
const rootDir = app.getAppPath();
|
||||
const absoluteFilepath = path.join(rootDir, relativeEmsFilepath);
|
||||
|
||||
log.debug("[IPC Debug Function] Decode test Estimate", absoluteFilepath);
|
||||
await ImportJob(absoluteFilepath);
|
||||
|
||||
const job2 = `/Users/pfic/Downloads/12285264/2285264.ENV`;
|
||||
const job3 = `/Users/pfic/Downloads/14033376/4033376.ENV`;
|
||||
await ImportJob(job2);
|
||||
await ImportJob(job3);
|
||||
});
|
||||
}
|
||||
|
||||
// Settings Handlers
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.filepaths.get,
|
||||
SettingsWatchedFilePathsGet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.filepaths.add,
|
||||
SettingsWatchedFilePathsAdd,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.filepaths.remove,
|
||||
SettingsWatchedFilePathsRemove,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.watcher.getpolling,
|
||||
SettingsWatcherPollingGet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.watcher.setpolling,
|
||||
SettingsWatcherPollingSet,
|
||||
);
|
||||
|
||||
ipcMain.handle(ipcTypes.toMain.settings.getPpcFilePath, SettingsPpcFilePathGet);
|
||||
ipcMain.handle(ipcTypes.toMain.settings.setPpcFilePath, SettingsPpcFilePathSet);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.getEmsOutFilePath,
|
||||
SettingEmsOutFilePathGet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.setEmsOutFilePath,
|
||||
SettingEmsOutFilePathSet,
|
||||
);
|
||||
|
||||
// Paint Scale Input Settings Handlers
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.getInputConfigs,
|
||||
SettingsPaintScaleInputConfigsGet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setInputConfigs,
|
||||
SettingsPaintScaleInputConfigsSet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setInputPath,
|
||||
SettingsPaintScaleInputPathSet,
|
||||
);
|
||||
|
||||
// Paint Scale Output Settings Handlers
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.getOutputConfigs,
|
||||
SettingsPaintScaleOutputConfigsGet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setOutputConfigs,
|
||||
SettingsPaintScaleOutputConfigsSet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setOutputPath,
|
||||
SettingsPaintScaleOutputPathSet,
|
||||
);
|
||||
|
||||
// IPC handlers for updating paint scale cron
|
||||
ipcMain.on(
|
||||
ipcTypes.toMain.settings.paintScale.updateInputCron,
|
||||
(_event, configs: PaintScaleConfig[]) => {
|
||||
handlePaintScaleInputCron(configs).catch((error) => {
|
||||
log.error(`Error handling paint scale input cron for configs: ${error}`);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.on(
|
||||
ipcTypes.toMain.settings.paintScale.updateOutputCron,
|
||||
(_event, configs: PaintScaleConfig[]) => {
|
||||
handlePaintScaleOutputCron(configs).catch((error) => {
|
||||
log.error(`Error handling paint scale output cron for configs: ${error}`);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle(ipcTypes.toMain.user.getActiveShop, () => {
|
||||
return store.get("app.bodyshop.shopname");
|
||||
});
|
||||
|
||||
// Watcher Handlers
|
||||
ipcMain.on(ipcTypes.toMain.watcher.start, () => {
|
||||
StartWatcher().catch((error) => {
|
||||
log.error("Error starting watcher:", error);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.toMain.watcher.stop, () => {
|
||||
StopWatcher().catch((error) => {
|
||||
log.error("Error stopping watcher:", error);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.toMain.updates.download, () => {
|
||||
log.info("Download update requested from renderer.");
|
||||
autoUpdater.downloadUpdate().catch((error) => {
|
||||
log.error("Error downloading update:", error);
|
||||
});
|
||||
});
|
||||
|
||||
export { initializeCronTasks };
|
||||
|
||||
logIpcMessages();
|
||||
67
src/main/ipc/ipcMainConfig.types.ts
Normal file
67
src/main/ipc/ipcMainConfig.types.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
export interface User {
|
||||
stsTokenManager?: {
|
||||
accessToken: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BodyShop {
|
||||
shopname: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface GraphQLResponse {
|
||||
bodyshops_by_pk?: {
|
||||
imexshopid: string;
|
||||
shopname: string;
|
||||
};
|
||||
jobs?: Array<{
|
||||
labhrs: any;
|
||||
larhrs: any;
|
||||
ro_number: string;
|
||||
ownr_ln: string;
|
||||
ownr_fn: string;
|
||||
plate_no: string;
|
||||
v_vin: string;
|
||||
v_model_yr: string;
|
||||
v_make_desc: string;
|
||||
v_model_desc: string;
|
||||
vehicle?: {
|
||||
v_paint_codes?: {
|
||||
paint_cd1: string;
|
||||
};
|
||||
};
|
||||
larhrs_aggregate?: {
|
||||
aggregate?: {
|
||||
sum?: {
|
||||
mod_lb_hrs: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
ins_co_nm: string;
|
||||
est_ct_ln: string;
|
||||
est_ct_fn: string;
|
||||
job_totals?: {
|
||||
rates?: {
|
||||
mapa?: {
|
||||
total?: {
|
||||
amount: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
totals?: {
|
||||
subtotal?: {
|
||||
amount: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
rate_mapa: number;
|
||||
labhrs_aggregate?: {
|
||||
aggregate?: {
|
||||
sum?: {
|
||||
mod_lb_hrs: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
rate_lab: number;
|
||||
}>;
|
||||
}
|
||||
257
src/main/ipc/ipcMainHandler.settings.ts
Normal file
257
src/main/ipc/ipcMainHandler.settings.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
// main/ipcMainHandler.settings.ts
|
||||
import { dialog, IpcMainInvokeEvent } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import Store from "../store/store";
|
||||
import { getMainWindow } from "../util/toRenderer";
|
||||
import {
|
||||
addWatcherPath,
|
||||
removeWatcherPath,
|
||||
StartWatcher,
|
||||
StopWatcher,
|
||||
} from "../watcher/watcher";
|
||||
import { PaintScaleConfig } from "../../util/types/paintScale";
|
||||
|
||||
// Initialize paint scale input configs in store if not set
|
||||
if (!Store.get("settings.paintScaleInputConfigs")) {
|
||||
Store.set("settings.paintScaleInputConfigs", []);
|
||||
}
|
||||
|
||||
// Initialize paint scale output configs in store if not set
|
||||
if (!Store.get("settings.paintScaleOutputConfigs")) {
|
||||
Store.set("settings.paintScaleOutputConfigs", []);
|
||||
}
|
||||
|
||||
const SettingsWatchedFilePathsAdd = async (): Promise<string[]> => {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return [];
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
Store.set(
|
||||
"settings.filepaths",
|
||||
_.union(result.filePaths, Store.get("settings.filepaths")),
|
||||
);
|
||||
addWatcherPath(result.filePaths);
|
||||
}
|
||||
|
||||
return Store.get("settings.filepaths");
|
||||
};
|
||||
|
||||
const SettingsWatchedFilePathsRemove = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
path: string,
|
||||
): Promise<string[]> => {
|
||||
Store.set(
|
||||
"settings.filepaths",
|
||||
_.without(Store.get("settings.filepaths"), path),
|
||||
);
|
||||
removeWatcherPath(path);
|
||||
return Store.get("settings.filepaths");
|
||||
};
|
||||
|
||||
const SettingsWatchedFilePathsGet = async (): Promise<string[]> => {
|
||||
return Store.get("settings.filepaths") || [];
|
||||
};
|
||||
|
||||
const SettingsWatcherPollingGet = async (): Promise<{
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
}> => {
|
||||
const pollingEnabled: { enabled: boolean; interval: number } =
|
||||
Store.get("settings.polling");
|
||||
return { enabled: pollingEnabled.enabled, interval: pollingEnabled.interval };
|
||||
};
|
||||
|
||||
const SettingsWatcherPollingSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
pollingSettings: {
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
},
|
||||
): Promise<{
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
}> => {
|
||||
log.info("Polling set", pollingSettings);
|
||||
const { enabled, interval } = pollingSettings;
|
||||
Store.set("settings.polling", { enabled, interval });
|
||||
|
||||
await StopWatcher();
|
||||
await StartWatcher();
|
||||
|
||||
return { enabled, interval };
|
||||
};
|
||||
|
||||
const SettingsPpcFilePathGet = async (): Promise<string> => {
|
||||
return Store.get("settings.ppcFilePath");
|
||||
};
|
||||
|
||||
const SettingsPpcFilePathSet = async (): Promise<string> => {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return "";
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
Store.set("settings.ppcFilePath", result.filePaths[0]);
|
||||
}
|
||||
|
||||
return (Store.get("settings.ppcFilePath") as string) || "";
|
||||
};
|
||||
|
||||
const SettingEmsOutFilePathGet = async (): Promise<string> => {
|
||||
return Store.get("settings.emsOutFilePath");
|
||||
};
|
||||
|
||||
const SettingEmsOutFilePathSet = async (): Promise<string> => {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return "";
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
Store.set("settings.emsOutFilePath", result.filePaths[0]);
|
||||
}
|
||||
|
||||
return (Store.get("settings.emsOutFilePath") as string) || "";
|
||||
};
|
||||
|
||||
const SettingsPaintScaleInputConfigsGet = (
|
||||
_event?: IpcMainInvokeEvent,
|
||||
): PaintScaleConfig[] => {
|
||||
try {
|
||||
const configs = Store.get(
|
||||
"settings.paintScaleInputConfigs",
|
||||
) as PaintScaleConfig[];
|
||||
log.debug("Retrieved paint scale input configs:", configs);
|
||||
return configs || [];
|
||||
} catch (error) {
|
||||
log.error("Error getting paint scale input configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleInputConfigsSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
configs: PaintScaleConfig[],
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
Store.set("settings.paintScaleInputConfigs", configs);
|
||||
log.debug("Saved paint scale input configs:", configs);
|
||||
return true;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale input configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleInputPathSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
): Promise<string | null> => {
|
||||
try {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return null;
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
if (result.canceled) {
|
||||
log.debug("Paint scale input path selection canceled");
|
||||
return null;
|
||||
}
|
||||
const path = result.filePaths[0];
|
||||
log.debug("Selected paint scale input path:", path);
|
||||
return path;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale input path:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleOutputConfigsGet = (
|
||||
_event?: IpcMainInvokeEvent,
|
||||
): PaintScaleConfig[] => {
|
||||
try {
|
||||
const configs = Store.get(
|
||||
"settings.paintScaleOutputConfigs",
|
||||
) as PaintScaleConfig[];
|
||||
log.debug("Retrieved paint scale output configs:", configs);
|
||||
return configs || [];
|
||||
} catch (error) {
|
||||
log.error("Error getting paint scale output configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleOutputConfigsSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
configs: PaintScaleConfig[],
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
Store.set("settings.paintScaleOutputConfigs", configs);
|
||||
log.debug("Saved paint scale output configs:", configs);
|
||||
return true;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale output configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleOutputPathSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
): Promise<string | null> => {
|
||||
try {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return null;
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
if (result.canceled) {
|
||||
log.debug("Paint scale output path selection canceled");
|
||||
return null;
|
||||
}
|
||||
const path = result.filePaths[0];
|
||||
log.debug("Selected paint scale output path:", path);
|
||||
return path;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale output path:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
SettingsPpcFilePathGet,
|
||||
SettingsPpcFilePathSet,
|
||||
SettingsWatchedFilePathsAdd,
|
||||
SettingsWatchedFilePathsGet,
|
||||
SettingsWatchedFilePathsRemove,
|
||||
SettingsWatcherPollingGet,
|
||||
SettingsWatcherPollingSet,
|
||||
SettingEmsOutFilePathGet,
|
||||
SettingEmsOutFilePathSet,
|
||||
SettingsPaintScaleInputConfigsGet,
|
||||
SettingsPaintScaleInputConfigsSet,
|
||||
SettingsPaintScaleInputPathSet,
|
||||
SettingsPaintScaleOutputConfigsGet,
|
||||
SettingsPaintScaleOutputConfigsSet,
|
||||
SettingsPaintScaleOutputPathSet,
|
||||
};
|
||||
102
src/main/ipc/ipcMainHandler.user.ts
Normal file
102
src/main/ipc/ipcMainHandler.user.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { IpcMainEvent, shell } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { User } from "firebase/auth";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import ipcTypes from "../../util/ipcTypes.json";
|
||||
import client from "../graphql/graphql-client";
|
||||
import {
|
||||
ActiveBodyshopQueryResult,
|
||||
MasterdataQueryResult,
|
||||
QUERY_ACTIVE_BODYSHOP_TYPED,
|
||||
QUERY_MASTERDATA_TYPED,
|
||||
} from "../graphql/queries";
|
||||
import { default as Store, default as store } from "../store/store";
|
||||
import { checkForAppUpdatesContinuously } from "../util/checkForAppUpdates";
|
||||
import { getMainWindow, sendIpcToRenderer } from "../util/toRenderer";
|
||||
|
||||
const ipcMainHandleAuthStateChanged = async (
|
||||
_event: IpcMainEvent,
|
||||
user: User | null,
|
||||
): Promise<void> => {
|
||||
Store.set("user", user);
|
||||
log.debug("Received authentication state change from Renderer.", user);
|
||||
await setReleaseChannel();
|
||||
checkForAppUpdatesContinuously();
|
||||
};
|
||||
|
||||
async function setReleaseChannel() {
|
||||
try {
|
||||
//Need to query the currently active shop, and store the metadata as well.
|
||||
//Also need to query the OP Codes for decoding reference.
|
||||
await handleShopMetaDataFetch();
|
||||
//Check for updates
|
||||
const bodyshop = Store.get("app.bodyshop");
|
||||
if (bodyshop?.convenient_company?.toLowerCase() === "alpha") {
|
||||
autoUpdater.channel = "alpha";
|
||||
log.debug("Setting update channel to ALPHA channel.");
|
||||
} else if (bodyshop?.convenient_company?.toLowerCase() === "beta") {
|
||||
autoUpdater.channel = "beta";
|
||||
log.debug("Setting update channel to BETA channel.");
|
||||
} else {
|
||||
log.debug("Setting update channel to LATEST channel.");
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
"Error while querying active bodyshop or master data",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
sendIpcToRenderer(
|
||||
ipcTypes.toRenderer.general.showErrorMessage,
|
||||
"Error connecting to ImEX Online servers to get shop data. Please try again.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const handleShopMetaDataFetch = async (
|
||||
reloadWindow?: boolean,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
log.debug("Requery shop information & master data.");
|
||||
const activeBodyshop: ActiveBodyshopQueryResult = await client.request(
|
||||
QUERY_ACTIVE_BODYSHOP_TYPED,
|
||||
);
|
||||
|
||||
Store.set("app.bodyshop", activeBodyshop.bodyshops[0]);
|
||||
|
||||
const OpCodes: MasterdataQueryResult = await client.request(
|
||||
QUERY_MASTERDATA_TYPED,
|
||||
{
|
||||
key: `${activeBodyshop.bodyshops[0].region_config}_ciecaopcodes`,
|
||||
},
|
||||
);
|
||||
Store.set(
|
||||
"app.masterdata.opcodes",
|
||||
JSON.parse(OpCodes.masterdata[0]?.value),
|
||||
);
|
||||
if (reloadWindow) {
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow) {
|
||||
mainWindow.reload();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Error while fetching shop metadata", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const ipMainHandleResetPassword = async (): Promise<void> => {
|
||||
shell.openExternal(
|
||||
store.get("app.isTest")
|
||||
? `${import.meta.env.VITE_FE_URL_TEST}/resetpassword`
|
||||
: `${import.meta.env.VITE_FE_URL}/resetpassword`,
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
handleShopMetaDataFetch,
|
||||
ipcMainHandleAuthStateChanged,
|
||||
ipMainHandleResetPassword,
|
||||
setReleaseChannel,
|
||||
};
|
||||
272
src/main/ipc/paintScaleHandlers/PPG.ts
Normal file
272
src/main/ipc/paintScaleHandlers/PPG.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import log from "electron-log/main";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import axios from "axios";
|
||||
import { create } from "xmlbuilder2";
|
||||
import { parseStringPromise } from "xml2js";
|
||||
import store from "../../store/store";
|
||||
import client, { getTokenFromRenderer } from "../../graphql/graphql-client";
|
||||
import { PaintScaleConfig } from "../../../util/types/paintScale";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
PPG_DATA_QUERY_TYPED,
|
||||
PpgDataQueryResult,
|
||||
PpgDataQueryVariables,
|
||||
} from "../../graphql/queries";
|
||||
|
||||
export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
try {
|
||||
log.info(
|
||||
`Polling input directory for PPG config ${config.id}: ${config.path}`,
|
||||
);
|
||||
|
||||
log.debug(
|
||||
`Archive dir: ${path.join(config.path!, "archive")}, Error dir: ${path.join(config.path!, "error")}`,
|
||||
);
|
||||
|
||||
// Ensure archive and error directories exist
|
||||
const archiveDir = path.join(config.path!, "archive");
|
||||
const errorDir = path.join(config.path!, "error");
|
||||
try {
|
||||
await fs.mkdir(archiveDir, { recursive: true });
|
||||
await fs.mkdir(errorDir, { recursive: true });
|
||||
log.debug(
|
||||
`Archive and error directories ensured: ${archiveDir}, ${errorDir}`,
|
||||
);
|
||||
} catch (dirError) {
|
||||
log.error(`Failed to create directories for ${config.path}:`, dirError);
|
||||
throw dirError;
|
||||
}
|
||||
|
||||
// Check for files
|
||||
const files = await fs.readdir(config.path!);
|
||||
log.debug(`Found ${files.length} files in ${config.path}:`, files);
|
||||
|
||||
for (const file of files) {
|
||||
// Only process XML files
|
||||
if (!file.toLowerCase().endsWith(".xml")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(config.path!, file);
|
||||
try {
|
||||
const stats = await fs.stat(filePath);
|
||||
if (!stats.isFile()) {
|
||||
continue;
|
||||
}
|
||||
} catch (statError) {
|
||||
log.warn(`Failed to stat file ${filePath}:`, statError);
|
||||
continue;
|
||||
}
|
||||
|
||||
log.debug(`Processing input file: ${filePath}`);
|
||||
|
||||
// Check file accessibility (e.g., not locked)
|
||||
try {
|
||||
await fs.access(filePath, fs.constants.R_OK);
|
||||
} catch (error) {
|
||||
log.warn(`File ${filePath} is inaccessible, skipping:`, error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate XML structure
|
||||
let xmlContent: BlobPart;
|
||||
try {
|
||||
xmlContent = await fs.readFile(filePath, "utf8");
|
||||
await parseStringPromise(xmlContent);
|
||||
log.debug(`Successfully validated XML for ${filePath}`);
|
||||
} catch (error) {
|
||||
log.error(`Invalid XML in ${filePath}:`, error);
|
||||
const timestamp = dayjs().format("YYYYMMDD_HHmmss");
|
||||
const originalFilename = path.basename(file, path.extname(file));
|
||||
const errorPath = path.join(
|
||||
errorDir,
|
||||
`${originalFilename}-${timestamp}.xml`,
|
||||
);
|
||||
try {
|
||||
await fs.rename(filePath, errorPath);
|
||||
log.debug(`Moved invalid file to error: ${errorPath}`);
|
||||
} catch (moveError) {
|
||||
log.error(
|
||||
`Failed to move invalid file to error directory ${errorPath}:`,
|
||||
moveError,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get authentication token
|
||||
let token: string | null;
|
||||
try {
|
||||
token = await getTokenFromRenderer();
|
||||
if (!token) {
|
||||
log.error(`No authentication token for file: ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
log.debug(
|
||||
`Obtained authentication token for ${filePath}: ${token.slice(0, 10)}...`,
|
||||
);
|
||||
} catch (tokenError) {
|
||||
log.error(
|
||||
`Failed to obtain authentication token for ${filePath}:`,
|
||||
tokenError,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Upload file to API
|
||||
const formData = new FormData();
|
||||
formData.append("file", new Blob([xmlContent]), path.basename(filePath));
|
||||
const shopId = (store.get("app.bodyshop") as any)?.shopname || "";
|
||||
formData.append("shopId", shopId);
|
||||
log.debug(`Shop ID: ${shopId}`);
|
||||
|
||||
const baseURL = store.get("app.isTest")
|
||||
? import.meta.env.VITE_API_TEST_URL
|
||||
: import.meta.env.VITE_API_URL;
|
||||
const finalUrl = `${baseURL}/mixdata/upload`;
|
||||
log.debug(`Uploading file to ${finalUrl}`);
|
||||
|
||||
try {
|
||||
const response = await axios.post(finalUrl, formData, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
timeout: 10000, // 10-second timeout
|
||||
});
|
||||
|
||||
log.info(`Upload response for ${filePath}:`, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: response.data,
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
log.info(`Successful upload of ${filePath}`);
|
||||
// Move file to archive
|
||||
const timestamp = dayjs().format("YYYYMMDD_HHmmss");
|
||||
const originalFilename = path.basename(file, path.extname(file));
|
||||
const archivePath = path.join(
|
||||
archiveDir,
|
||||
`${originalFilename}-${timestamp}.xml`,
|
||||
);
|
||||
try {
|
||||
await fs.access(archiveDir, fs.constants.W_OK); // Verify archiveDir is writable
|
||||
await fs.rename(filePath, archivePath);
|
||||
log.info(`Moved file to archive: ${archivePath}`);
|
||||
} catch (moveError) {
|
||||
log.error(
|
||||
`Failed to move file to archive directory ${archivePath}:`,
|
||||
moveError,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log.error(
|
||||
`Failed to upload ${filePath}: ${response.status} ${response.statusText}`,
|
||||
{ responseData: response.data },
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(`Error uploading ${filePath}:`, {
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
response: error.response
|
||||
? {
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
data: error.response.data,
|
||||
}
|
||||
: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Error polling input directory ${config.path}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// PPG Output Handler
|
||||
export async function ppgOutputHandler(
|
||||
config: PaintScaleConfig,
|
||||
): Promise<void> {
|
||||
try {
|
||||
log.info(`Generating PPG output for config ${config.id}: ${config.path}`);
|
||||
|
||||
await fs.mkdir(config.path!, { recursive: true });
|
||||
|
||||
const variables: PpgDataQueryVariables = {
|
||||
today: dayjs().toISOString(),
|
||||
todayplus5: dayjs().add(5, "day").toISOString(),
|
||||
shopid: (store.get("app.bodyshop") as any)?.id,
|
||||
};
|
||||
|
||||
const response = await client.request<
|
||||
PpgDataQueryResult,
|
||||
PpgDataQueryVariables
|
||||
>(PPG_DATA_QUERY_TYPED, variables);
|
||||
const jobs = response.jobs ?? [];
|
||||
|
||||
const header = {
|
||||
PPG: {
|
||||
Header: {
|
||||
Protocol: {
|
||||
Message: "PaintShopInterface",
|
||||
Name: "PPG",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
Transaction: {
|
||||
TransactionID: "",
|
||||
TransactionDate: dayjs().format("YYYY-MM-DD:HH:mm"),
|
||||
},
|
||||
Product: {
|
||||
Name: import.meta.env.VITE_COMPANY === "IMEX",
|
||||
Version: "",
|
||||
},
|
||||
},
|
||||
DataInterface: {
|
||||
ROData: {
|
||||
ShopInfo: {
|
||||
ShopID: response.bodyshops_by_pk?.imexshopid || "",
|
||||
ShopName: response.bodyshops_by_pk?.shopname || "",
|
||||
},
|
||||
RepairOrders: {
|
||||
ROCount: jobs.length.toString(),
|
||||
RO: jobs.map((job) => ({
|
||||
RONumber: job.ro_number || "",
|
||||
ROStatus: "Open",
|
||||
Customer: `${job.ownr_ln || ""}, ${job.ownr_fn || ""}`,
|
||||
ROPainterNotes: "",
|
||||
LicensePlateNum: job.plate_no || "",
|
||||
VIN: job.v_vin || "",
|
||||
ModelYear: job.v_model_yr || "",
|
||||
MakeDesc: job.v_make_desc || "",
|
||||
ModelName: job.v_model_desc || "",
|
||||
OEMColorCode: job.vehicle?.v_paint_codes?.paint_cd1 || "",
|
||||
RefinishLaborHours: job.larhrs?.aggregate?.sum?.mod_lb_hrs || 0,
|
||||
InsuranceCompanyName: job.ins_co_nm || "",
|
||||
EstimatorName: `${job.est_ct_ln || ""}, ${job.est_ct_fn || ""}`,
|
||||
PaintMaterialsRevenue: (
|
||||
(job.job_totals?.rates?.mapa?.total?.amount || 0) / 100
|
||||
).toFixed(2),
|
||||
PaintMaterialsRate: job.rate_mapa || 0,
|
||||
BodyHours: job.labhrs?.aggregate?.sum?.mod_lb_hrs || 0,
|
||||
BodyLaborRate: job.rate_lab || 0,
|
||||
TotalCostOfRepairs: (
|
||||
(job.job_totals?.totals?.subtotal?.amount || 0) / 100
|
||||
).toFixed(2),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const xml = create({ version: "1.0" }, header).end({ prettyPrint: true });
|
||||
const outputPath = path.join(config.path!, `PPGPaint.xml`);
|
||||
await fs.writeFile(outputPath, xml);
|
||||
log.info(`Saved PPG output XML to ${outputPath}`);
|
||||
} catch (error) {
|
||||
log.error(`Error generating PPG output for config ${config.id}:`, error);
|
||||
}
|
||||
}
|
||||
34
src/main/ppc/ppc-generate-env.ts
Normal file
34
src/main/ppc/ppc-generate-env.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import { envFieldLineDescriptors } from "../util/ems-interface/fielddescriptors/env-field-descriptor";
|
||||
import { deleteEmsFileIfExists, generatePpcFilePath } from "../util/ems-util";
|
||||
import { PpcJob } from "./ppc-handler";
|
||||
|
||||
const GenerateEnvFile = async (job: PpcJob): Promise<boolean> => {
|
||||
const records = [
|
||||
{
|
||||
EST_SYSTEM: "C",
|
||||
RO_ID: job.ro_number,
|
||||
ESTFILE_ID: job.ciecaid,
|
||||
STATUS: false,
|
||||
INCL_ADMIN: true,
|
||||
INCL_VEH: true,
|
||||
INCL_EST: true,
|
||||
INCL_PROFL: true,
|
||||
INCL_TOTAL: true,
|
||||
INCL_VENDR: false,
|
||||
},
|
||||
];
|
||||
|
||||
await deleteEmsFileIfExists(generatePpcFilePath(`${job.ciecaid}.ENV`));
|
||||
|
||||
const dbf = await DBFFile.create(
|
||||
generatePpcFilePath(`${job.ciecaid}.ENV`),
|
||||
envFieldLineDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} LIN file records added.`);
|
||||
return true;
|
||||
};
|
||||
|
||||
export default GenerateEnvFile;
|
||||
34
src/main/ppc/ppc-generate-lin.ts
Normal file
34
src/main/ppc/ppc-generate-lin.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DBFFile } from "dbffile";
|
||||
import { linFieldDescriptors } from "../util/ems-interface/fielddescriptors/lin-field-descriptor";
|
||||
import { deleteEmsFileIfExists, generatePpcFilePath } from "../util/ems-util";
|
||||
import { PpcJob } from "./ppc-handler";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
|
||||
const GenerateLinFile = async (job: PpcJob): Promise<boolean> => {
|
||||
try {
|
||||
const records = job.joblines.map((line) => {
|
||||
return {
|
||||
//TODO: There are missing types here. May require server side updates, but we are missing things like LINE_NO, LINE_IND, etc.
|
||||
TRAN_CODE: "2",
|
||||
UNQ_SEQ: line.unq_seq,
|
||||
ACT_PRICE: line.act_price,
|
||||
};
|
||||
});
|
||||
|
||||
await deleteEmsFileIfExists(generatePpcFilePath(`${job.ciecaid}.LIN`));
|
||||
|
||||
const dbf = await DBFFile.create(
|
||||
generatePpcFilePath(`${job.ciecaid}.LIN`),
|
||||
linFieldDescriptors,
|
||||
);
|
||||
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} LIN file records added.`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error generating PPC LIN file", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default GenerateLinFile;
|
||||
67
src/main/ppc/ppc-handler.ts
Normal file
67
src/main/ppc/ppc-handler.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { UUID } from "crypto";
|
||||
import log from "electron-log/main";
|
||||
import express from "express";
|
||||
import _ from "lodash";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import store from "../store/store";
|
||||
import createdDirectoryIfNotExist from "../util/createDirectoryIfNotExist";
|
||||
import GenerateEnvFile from "./ppc-generate-env";
|
||||
import GenerateLinFile from "./ppc-generate-lin";
|
||||
|
||||
const handlePartsPriceChangeRequest = async (
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<void> => {
|
||||
//Route handler here only.
|
||||
|
||||
const job = req.body as PpcJob;
|
||||
try {
|
||||
await generatePartsPriceChange(job);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
log.error("Error generating parts price change", errorTypeCheck(error));
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Error generating parts price change.",
|
||||
...errorTypeCheck(error),
|
||||
});
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const generatePartsPriceChange = async (job: PpcJob): Promise<void> => {
|
||||
log.debug(" Generating parts price change");
|
||||
//Check to make sure that the PPC Output file path exists. If it doesn't, create it. If it's not set, abandon ship.
|
||||
|
||||
const ppcOutFilePath: string | null = store.get("settings.ppcFilePath");
|
||||
if (_.isEmpty(ppcOutFilePath) || ppcOutFilePath === null) {
|
||||
log.error("PPC file path is not set");
|
||||
throw new Error("PPC file path is not set");
|
||||
}
|
||||
try {
|
||||
createdDirectoryIfNotExist(ppcOutFilePath);
|
||||
|
||||
await GenerateLinFile(job);
|
||||
await GenerateEnvFile(job);
|
||||
} catch (error) {
|
||||
log.error("Error generating parts price change", errorTypeCheck(error));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export interface PpcJob {
|
||||
id: UUID;
|
||||
ciecaid: string;
|
||||
ro_number: string;
|
||||
joblines: {
|
||||
removed: boolean;
|
||||
act_price_before_ppc: number | null;
|
||||
id: string;
|
||||
act_price: number;
|
||||
unq_seq: string; //TODO: Might be a number.
|
||||
}[];
|
||||
bodyshop: {
|
||||
timezone: string;
|
||||
};
|
||||
}
|
||||
|
||||
export { handlePartsPriceChangeRequest };
|
||||
30
src/main/quickbooks-desktop/QuickbooksConnector.cs
Normal file
30
src/main/quickbooks-desktop/QuickbooksConnector.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Interop.QBFC16; // Ensure this matches your DLL version
|
||||
|
||||
public class QuickBooksConnector
|
||||
{
|
||||
public string ProcessQBXML(string qbxmlRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
QBSessionManager sessionManager = new QBSessionManager();
|
||||
sessionManager.OpenConnection("", "YourAppName");
|
||||
sessionManager.BeginSession("", ENOpenMode.omDontCare);
|
||||
|
||||
IMsgSetRequest requestMsgSet = sessionManager.CreateMsgSetRequest("US", 13, 0);
|
||||
requestMsgSet.AppendXML(qbxmlRequest);
|
||||
|
||||
IMsgSetResponse responseMsgSet = sessionManager.DoRequests(requestMsgSet);
|
||||
string qbxmlResponse = responseMsgSet.ToXMLString();
|
||||
|
||||
sessionManager.EndSession();
|
||||
sessionManager.CloseConnection();
|
||||
|
||||
return qbxmlResponse;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/main/quickbooks-desktop/quickbooks-desktop.ts
Normal file
130
src/main/quickbooks-desktop/quickbooks-desktop.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import log from "electron-log/main";
|
||||
|
||||
import { UUID } from "crypto";
|
||||
import { Request, Response } from "express";
|
||||
import _ from "lodash";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let Winax: any; // Declare Winax as any to avoid TypeScript errors on non-Windows platforms
|
||||
|
||||
if (process.platform === "win32") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
Winax = require("winax");
|
||||
}
|
||||
|
||||
export async function handleQuickBookRequest(
|
||||
req: Request,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
if (process.platform !== "win32") {
|
||||
res.status(500).json({
|
||||
error: "QuickBooks Desktop integration is only available on Windows",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const QbFilePath: string = `C:\\Users\\PatrickFic\\Development\\FRODO COLLISION.QBW`;
|
||||
// ||
|
||||
// (store.get("settings.qbFilePath") as string) F
|
||||
|
||||
if (_.isEmpty(QbFilePath)) {
|
||||
res.status(400).json({ error: "Quickbooks file path not set" });
|
||||
return;
|
||||
}
|
||||
|
||||
const qbxmlRequestList = req.body as Array<{
|
||||
id: UUID;
|
||||
okStatusCodes: Array<string>;
|
||||
qbxml: string;
|
||||
}>;
|
||||
|
||||
const returnResponse: Array<{
|
||||
Id: UUID;
|
||||
Success: boolean;
|
||||
ErrorMessage: string;
|
||||
}> = [];
|
||||
|
||||
//Connect to the QuickBooks File
|
||||
let requestProcessor;
|
||||
try {
|
||||
requestProcessor = new Winax.Object("QBXMLRP2.RequestProcessor.2");
|
||||
requestProcessor.OpenConnection(QbFilePath, "ShopPartnerActualRequest");
|
||||
} catch (error) {
|
||||
log.error(
|
||||
"Error instnatiating QuickBooks Request Processor",
|
||||
QbFilePath,
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
res.status(500).json({ error: "Error connecting to QuickBooks" });
|
||||
return;
|
||||
}
|
||||
|
||||
const ticket = requestProcessor.BeginSession(QbFilePath, 2); //2 indicated qbFileOpenModeDoNotCare
|
||||
log.info("Quickbooks Ticket", ticket);
|
||||
for (const qbxmlRequest of qbxmlRequestList) {
|
||||
try {
|
||||
//TODO: Refactor to not create a new connection every time.
|
||||
const QuickBooksResponse = requestProcessor.ProcessRequest(
|
||||
ticket,
|
||||
qbxmlRequest.qbxml,
|
||||
);
|
||||
log.info("QuickBooks Raw Response: ", QuickBooksResponse);
|
||||
returnResponse.push({
|
||||
Id: qbxmlRequest.id,
|
||||
Success:
|
||||
QuickBooksResponse.StatusCode === "0" ||
|
||||
qbxmlRequest.okStatusCodes.includes(QuickBooksResponse.StatusCode),
|
||||
ErrorMessage: QuickBooksResponse,
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(
|
||||
"Error running transaction",
|
||||
ticket,
|
||||
qbxmlRequest,
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
}
|
||||
}
|
||||
requestProcessor.EndSession(ticket);
|
||||
requestProcessor.CloseConnection();
|
||||
res.json(qbxmlRequestList);
|
||||
}
|
||||
|
||||
//This set of functions works.
|
||||
export function TestQB(): void {
|
||||
if (process.platform !== "win32") {
|
||||
log.warn("TestQB is only available on Windows");
|
||||
return;
|
||||
}
|
||||
let requestProcessor, ticket;
|
||||
try {
|
||||
requestProcessor = new Winax.Object("QBXMLRP.RequestProcessor.1");
|
||||
requestProcessor.OpenConnection("", "ShopPartnerOneoFf");
|
||||
|
||||
ticket = requestProcessor.BeginSession("", 2); //2 indicated qbFileOOpenModeDoNotCare
|
||||
|
||||
requestProcessor.ProcessRequest(
|
||||
ticket,
|
||||
`<?qbxml version="16.0"?>
|
||||
<QBXML>
|
||||
<QBXMLMsgsRq onError="stopOnError">
|
||||
<AccountQueryRq requestID="1"> </AccountQueryRq>
|
||||
</QBXMLMsgsRq>
|
||||
</QBXML>`,
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
"Error instnatiating QuickBooks Request Processor",
|
||||
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log.log("Ticket", ticket);
|
||||
requestProcessor.EndSession(ticket);
|
||||
requestProcessor.CloseConnection();
|
||||
return;
|
||||
}
|
||||
77
src/main/setup-keep-alive-agent.ts
Normal file
77
src/main/setup-keep-alive-agent.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { promises as fs } from "fs";
|
||||
import { join } from "path";
|
||||
import { homedir } from "os";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import log from "electron-log/main";
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
// Define the interval as a variable (in seconds)
|
||||
const KEEP_ALIVE_INTERVAL_SECONDS = 15 * 60; // 15 minutes
|
||||
|
||||
export async function setupKeepAliveAgent(): Promise<void> {
|
||||
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.convenientbrands.bodyshop-desktop.keepalive</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>Shop Partner Keep Alive</string>
|
||||
<string>imexmedia://keep-alive</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StartInterval</key>
|
||||
<integer>${KEEP_ALIVE_INTERVAL_SECONDS}</integer>
|
||||
</dict>
|
||||
</plist>`;
|
||||
|
||||
const plistPath = join(
|
||||
homedir(),
|
||||
"/Library/LaunchAgents/com.convenientbrands.bodyshop-desktop.keepalive.plist",
|
||||
);
|
||||
|
||||
try {
|
||||
await fs.writeFile(plistPath, plistContent);
|
||||
const { stdout, stderr } = await execPromise(`launchctl load ${plistPath}`);
|
||||
log.info(`Launch agent created and loaded: ${stdout}`);
|
||||
if (stderr) log.warn(`Launch agent stderr: ${stderr}`);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Error setting up launch agent: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
throw error; // Rethrow to allow caller to handle
|
||||
}
|
||||
}
|
||||
|
||||
export async function isKeepAliveAgentInstalled(): Promise<boolean> {
|
||||
const plistPath = join(
|
||||
homedir(),
|
||||
"/Library/LaunchAgents/com.convenientbrands.bodyshop-desktop.keepalive.plist",
|
||||
);
|
||||
const maxRetries = 3;
|
||||
const retryDelay = 500; // 500ms delay between retries
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
await fs.access(plistPath, fs.constants.F_OK);
|
||||
const { stdout } = await execPromise(
|
||||
`launchctl list | grep com.convenientbrands.bodyshop-desktop.keepalive`,
|
||||
);
|
||||
return !!stdout; // Return true if plist exists and agent is loaded
|
||||
} catch (error) {
|
||||
log.debug(
|
||||
`Launch agent not found (attempt ${attempt}/${maxRetries}): ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
if (attempt === maxRetries) {
|
||||
return false; // Return false after all retries fail
|
||||
}
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
return false; // Fallback return
|
||||
}
|
||||
52
src/main/setup-keep-alive-task.ts
Normal file
52
src/main/setup-keep-alive-task.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import log from "electron-log/main";
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
// Define the interval as a variable (in minutes)
|
||||
const KEEP_ALIVE_INTERVAL_MINUTES = 15;
|
||||
const taskName = "ShopPartnerKeepAlive";
|
||||
|
||||
export async function setupKeepAliveTask(): Promise<void> {
|
||||
const protocolUrl = "imexmedia://keep-alive";
|
||||
// Use rundll32.exe to silently open the URL as a protocol
|
||||
const command = `rundll32.exe url.dll,OpenURL "${protocolUrl}"`;
|
||||
// Escape quotes for schtasks /tr parameter
|
||||
const escapedCommand = command.replace(/"/g, '\\"');
|
||||
|
||||
const schtasksCommand = `schtasks /create /tn "${taskName}" /tr "${escapedCommand}" /sc minute /mo ${KEEP_ALIVE_INTERVAL_MINUTES} /f`;
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execPromise(schtasksCommand);
|
||||
log.info(`Scheduled task created: ${stdout}`);
|
||||
if (stderr) log.warn(`Scheduled task stderr: ${stderr}`);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Error creating scheduled task: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
throw error; // Rethrow to allow caller to handle
|
||||
}
|
||||
}
|
||||
|
||||
export async function isKeepAliveTaskInstalled(): Promise<boolean> {
|
||||
const maxRetries = 3;
|
||||
const retryDelay = 500; // 500ms delay between retries
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const { stdout } = await execPromise(`schtasks /query /tn "${taskName}"`);
|
||||
return !!stdout; // Return true if task exists
|
||||
} catch (error) {
|
||||
log.debug(
|
||||
`Scheduled task ${taskName} not found (attempt ${attempt}/${maxRetries}): ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
if (attempt === maxRetries) {
|
||||
return false; // Return false after all retries fail
|
||||
}
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
return false; // Fallback return
|
||||
}
|
||||
40
src/main/store/store.ts
Normal file
40
src/main/store/store.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import Store from "electron-store";
|
||||
|
||||
const store = new Store({
|
||||
defaults: {
|
||||
settings: {
|
||||
runOnStartup: true,
|
||||
filepaths: [],
|
||||
ppcFilePath: null,
|
||||
emsOutFilePath: null,
|
||||
qbFilePath: "",
|
||||
runWatcherOnStartup: true,
|
||||
enableMemDebug: false,
|
||||
polling: {
|
||||
enabled: false,
|
||||
interval: 30000,
|
||||
},
|
||||
},
|
||||
app: {
|
||||
windowBounds: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
},
|
||||
user: null,
|
||||
isTest: false,
|
||||
bodyshop: {},
|
||||
masterdata: {
|
||||
opcodes: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// store.onDidAnyChange((newValue, oldValue) => {
|
||||
// const mainWindow = BrowserWindow.getAllWindows()[0];
|
||||
// mainWindow?.webContents.send(ipcTypes.toRenderer.store.didChange, newValue);
|
||||
// });
|
||||
|
||||
export default store;
|
||||
23
src/main/util/checkForAppUpdates.ts
Normal file
23
src/main/util/checkForAppUpdates.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { setReleaseChannel } from "../ipc/ipcMainHandler.user";
|
||||
|
||||
let continuousUpdatesTriggered = false;
|
||||
|
||||
async function checkForAppUpdatesContinuously(): Promise<void> {
|
||||
if (!continuousUpdatesTriggered) {
|
||||
continuousUpdatesTriggered = true;
|
||||
checkForAppUpdates();
|
||||
setInterval(
|
||||
() => {
|
||||
checkForAppUpdates();
|
||||
},
|
||||
1000 * 60 * 30,
|
||||
);
|
||||
}
|
||||
}
|
||||
async function checkForAppUpdates(): Promise<void> {
|
||||
await setReleaseChannel();
|
||||
autoUpdater.checkForUpdates();
|
||||
}
|
||||
|
||||
export { checkForAppUpdates, checkForAppUpdatesContinuously };
|
||||
21
src/main/util/createDirectoryIfNotExist.ts
Normal file
21
src/main/util/createDirectoryIfNotExist.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import log from "electron-log/main";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
|
||||
const createdDirectoryIfNotExist = async (dirPath: string) => {
|
||||
try {
|
||||
const directoryPath = path.dirname(dirPath);
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
log.info(`Directory does not exist. Creating: ${directoryPath}`);
|
||||
fs.mkdirSync(directoryPath, { recursive: true });
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Error creating directory as needed", errorTypeCheck(error));
|
||||
throw new Error(
|
||||
"Error creating directory: " + errorTypeCheck(error).message,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default createdDirectoryIfNotExist;
|
||||
@@ -0,0 +1,706 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const ad1FieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "INS_CO_ID",
|
||||
type: "C",
|
||||
size: 5,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_CO_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_CT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_CT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_TITLE",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_CT_PH",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_CT_PHX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INS_MEMO",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "POLICY_NO",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DED_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "DED_STATUS",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ASGN_NO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ASGN_DATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ASGN_TYPE",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_NO",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_OFC_ID",
|
||||
type: "C",
|
||||
size: 5,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_OFC_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_CT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_CT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_TITLE",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_CT_PH",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_CT_PHX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLM_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAYEE_NMS",
|
||||
type: "C",
|
||||
size: 85,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAY_TYPE",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAY_DATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAY_CHKNM",
|
||||
type: "C",
|
||||
size: 16,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAY_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PAY_MEMO",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CO_ID",
|
||||
type: "C",
|
||||
size: 5,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CO_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CT_PH",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_CT_PHX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "AGT_LIC_NO",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOSS_DATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOSS_CAT",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOSS_TYPE",
|
||||
type: "C",
|
||||
size: 7,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOSS_DESC",
|
||||
type: "C",
|
||||
size: 38,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "THEFT_IND",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CAT_NO",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TLOS_IND",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOSS_MEMO",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CUST_PR",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_TITLE",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_CO_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSD_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_TITLE",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_CO_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OWNR_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,640 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const ad2FieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "CLMT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_TITLE",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_CO_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CLMT_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_CO_ID",
|
||||
type: "C",
|
||||
size: 5,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_CO_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_CT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_CT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_LIC_NO",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_FILENO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_CT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_CT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_CODE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_DESC",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_DATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INSP_TIME",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_CO_ID",
|
||||
type: "C",
|
||||
size: 5,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_CO_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_CT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_CT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_TAX_ID",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_LIC_NO",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_BAR_NO",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RO_IN_DATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RO_IN_TIME",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RO_AUTH",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAR_DATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAR_TIME",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RO_CMPDATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RO_CMPTIME",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DATE_OUT",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TIME_OUT",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RF_ESTIMTR",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MKTG_TYPE",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MKTG_SRC",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_NM",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_ADDR1",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_ADDR2",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_CITY",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_ZIP",
|
||||
type: "C",
|
||||
size: 11,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_PH1",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_PH1X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_PH2",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_PH2X",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_FAX",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_FAXX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_CT_LN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_CT_FN",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_TITLE",
|
||||
type: "C",
|
||||
size: 35,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_PH",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_PHX",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LOC_EA",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,154 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const envFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "EST_SYSTEM",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "SW_VERSION",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_VERSION",
|
||||
type: "C",
|
||||
size: 12,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_DATE",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "UNQFILE_ID",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "RO_ID",
|
||||
type: "C",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ESTFILE_ID",
|
||||
type: "C",
|
||||
size: 38,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "SUPP_NO",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EST_CTRY",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOP_SECRET",
|
||||
type: "C",
|
||||
size: 80,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "H_TRANS_ID",
|
||||
type: "C",
|
||||
size: 9,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "H_CTRL_NO",
|
||||
type: "C",
|
||||
size: 9,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TRANS_TYPE",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STATUS",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CREATE_DT",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CREATE_TM",
|
||||
type: "C",
|
||||
size: 6,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TRANSMT_DT",
|
||||
type: "D",
|
||||
size: 8,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TRANSMT_TM",
|
||||
type: "C",
|
||||
size: 6,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INCL_ADMIN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INCL_VEH",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INCL_EST",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INCL_PROFL",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INCL_TOTAL",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "INCL_VENDR",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "EMS_VER",
|
||||
type: "C",
|
||||
size: 5,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,274 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const linFieldDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "LINE_NO",
|
||||
type: "N",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_IND",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_REF",
|
||||
type: "N",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TRAN_CODE",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_REF",
|
||||
type: "C",
|
||||
size: 7,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "UNQ_SEQ",
|
||||
type: "N",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "WHO_PAYS",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_DESC",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_DES_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "GLASS_FLAG",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OEM_PARTNO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRICE_INC",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PART_I",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_PART",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_PRICE",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "ACT_PRICE",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PRICE_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CERT_PART",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_QTY",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_CO_ID",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PARTNO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_OVERRD",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PARTM",
|
||||
type: "C",
|
||||
size: 45,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_DSMK_P",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PRT_DSMK_M",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MOD_LBR_TY",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "MOD_LB_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "LBR_INC",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_OP",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_HRS_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TYP_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_OP_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_STG",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_TONE",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MISC_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MISC_SUBLT",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MISC_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "BETT_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "BETT_PCTG",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "BETT_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "BETT_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,274 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const linFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "LINE_NO",
|
||||
type: "N",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_IND",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_REF",
|
||||
type: "N",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TRAN_CODE",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_REF",
|
||||
type: "C",
|
||||
size: 7,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "UNQ_SEQ",
|
||||
type: "N",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "WHO_PAYS",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_DESC",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_DES_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "GLASS_FLAG",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OEM_PARTNO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRICE_INC",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PART_I",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_PART",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_PRICE",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "ACT_PRICE",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PRICE_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CERT_PART",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_QTY",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_CO_ID",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PARTNO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_OVERRD",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PARTM",
|
||||
type: "C",
|
||||
size: 45,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_DSMK_P",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PRT_DSMK_M",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MOD_LBR_TY",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "MOD_LB_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "LBR_INC",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_OP",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_HRS_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TYP_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_OP_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_STG",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_TONE",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MISC_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MISC_SUBLT",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MISC_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "BETT_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "BETT_PCTG",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "BETT_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "BETT_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,118 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const pfhFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "ID_PRO_NAM",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_PRETHR",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TAX_THRAMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TAX_PSTTHR",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TAX_TOW_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_TOW_RT",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TAX_STR_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_STR_RT",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TAX_SUB_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_SUB_RT",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TAX_BTR_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_LBR_RT",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TAX_GST_RT",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TAX_GST_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ADJ_G_DISC",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "ADJ_TOWDIS",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "ADJ_STRDIS",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "ADJ_BTR_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_PREDIS",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,100 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const pflFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "LBR_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_DESC",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_RATE",
|
||||
type: "N",
|
||||
size: 6,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "LBR_TAX_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TAXP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "LBR_ADJP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_TY1",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_IN1",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_TY2",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_IN2",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_TY3",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_IN3",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_TY4",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_IN4",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_TY5",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TX_IN5",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,166 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const pfmFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "MATL_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CAL_CODE",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CAL_DESC",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CAL_MAXDLR",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "CAL_PRIP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "CAL_SECP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "MAT_CALP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "CAL_PRETHR",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "CAL_PSTTHR",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "CAL_THRAMT",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "CAL_LBRMIN",
|
||||
type: "N",
|
||||
size: 4,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "CAL_LBRMAX",
|
||||
type: "N",
|
||||
size: 4,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "CAL_LBRRTE",
|
||||
type: "N",
|
||||
size: 6,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "CAL_OPCODE",
|
||||
type: "C",
|
||||
size: 48,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_IND",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TAXP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "MAT_ADJP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_TY1",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_IN1",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_TY2",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_IN2",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_TY3",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_IN3",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_TY4",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_IN4",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_TY5",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MAT_TX_IN5",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,160 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const pfoFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "TX_TOW_TY",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_TY1",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_IN1",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_TY2",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_IN2",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_TY3",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_IN3",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_TY4",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_IN4",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_TY5",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_IN5",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_TY6",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TOW_T_IN6",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TX_STOR_TY",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_TY1",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_IN1",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_TY2",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_IN2",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_TY3",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_IN3",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_TY4",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_IN4",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_TY5",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_IN5",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_TY6",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "STOR_T_IN6",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,100 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const pfpFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "PRT_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TAX_IN",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TAX_RT",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "PRT_MKUPP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "PRT_MKTYP",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_DISCP",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_TY1",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_IN1",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_TY2",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_IN2",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_TY3",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_IN3",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_TY4",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_IN4",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_TY5",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_TX_IN5",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,760 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const pftFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "TAX_TYPE1",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY1_TIER1",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY1_THRES1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_RATE1",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY1_SUR1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_TIER2",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY1_THRES2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_RATE2",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY1_SUR2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_TIER3",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY1_THRES3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_RATE3",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY1_SUR3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_TIER4",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY1_THRES4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_RATE4",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY1_SUR4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_TIER5",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY1_THRES5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY1_RATE5",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY1_SUR5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TAX_TYPE2",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY2_TIER1",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY2_THRES1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_RATE1",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY2_SUR1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_TIER2",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY2_THRES2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_RATE2",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY2_SUR2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_TIER3",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY2_THRES3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_RATE3",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY2_SUR3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_TIER4",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY2_THRES4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_RATE4",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY2_SUR4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_TIER5",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY2_THRES5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY2_RATE5",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY2_SUR5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TAX_TYPE3",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY3_TIER1",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY3_THRES1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_RATE1",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY3_SUR1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_TIER2",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY3_THRES2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_RATE2",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY3_SUR2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_TIER3",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY3_THRES3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_RATE3",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY3_SUR3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_TIER4",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY3_THRES4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_RATE4",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY3_SUR4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_TIER5",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY3_THRES5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY3_RATE5",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY3_SUR5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TAX_TYPE4",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY4_TIER1",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY4_THRES1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_RATE1",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY4_SUR1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_TIER2",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY4_THRES2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_RATE2",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY4_SUR2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_TIER3",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY4_THRES3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_RATE3",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY4_SUR3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_TIER4",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY4_THRES4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_RATE4",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY4_SUR4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_TIER5",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY4_THRES5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY4_RATE5",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY4_SUR5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TAX_TYPE5",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY5_TIER1",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY5_THRES1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_RATE1",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY5_SUR1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_TIER2",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY5_THRES2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_RATE2",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY5_SUR2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_TIER3",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY5_THRES3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_RATE3",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY5_SUR3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_TIER4",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY5_THRES4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_RATE4",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY5_SUR4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_TIER5",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY5_THRES5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY5_RATE5",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY5_SUR5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TAX_TYPE6",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY6_TIER1",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY6_THRES1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_RATE1",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY6_SUR1",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_TIER2",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY6_THRES2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_RATE2",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY6_SUR2",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_TIER3",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY6_THRES3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_RATE3",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY6_SUR3",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_TIER4",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY6_THRES4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_RATE4",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY6_SUR4",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_TIER5",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TY6_THRES5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TY6_RATE5",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "TY6_SUR5",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,112 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const stlFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "TTL_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TTL_TYPECD",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "T_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "T_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "T_ADDLBR",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "T_DISCAMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "T_MKUPAMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "T_GDISCAMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TAX_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "NT_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "NT_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "NT_ADDLBR",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "NT_DISC",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "NT_MKUP",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "NT_GDIS",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TTL_TYPAMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "TTL_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "TTL_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,88 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const ttlFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "G_TTL_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_BETT_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_RPD_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_DED_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_CUST_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_AA_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "N_TTL_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PREV_NET",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "SUPP_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "N_SUPP_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_UPD_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_TTL_DISC",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "G_TAX",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "GST_AMT",
|
||||
type: "N",
|
||||
size: 10,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,172 @@
|
||||
import { FieldDescriptor } from "dbffile";
|
||||
|
||||
export const vehFieldLineDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "IMPACT_1",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "IMPACT_2",
|
||||
type: "C",
|
||||
size: 30,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DMG_MEMO",
|
||||
type: "C", // Changed from "M" to "C" to allow writing, need to verify if this still works.
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_V_CODE",
|
||||
type: "C",
|
||||
size: 7,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PLATE_NO",
|
||||
type: "C",
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PLATE_ST",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_VIN",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_COND",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_PROD_DT",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_MODEL_YR",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_MAKECODE",
|
||||
type: "C",
|
||||
size: 12,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_MAKEDESC",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_MODEL",
|
||||
type: "C",
|
||||
size: 50,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_TYPE",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_BSTYLE",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_TRIMCODE",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TRIM_COLOR",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_MLDGCODE",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_ENGINE",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_MILEAGE",
|
||||
type: "C",
|
||||
size: 6,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_OPTIONS",
|
||||
type: "C", // Changed from "M" to "C" to allow writing, need to verify if this still works.
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_COLOR",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_TONE",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_STAGE",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_CD1",
|
||||
type: "C",
|
||||
size: 15,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_CD2",
|
||||
type: "C",
|
||||
size: 15,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_CD3",
|
||||
type: "C",
|
||||
size: 15,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "V_MEMO", //dbffile does not support writing to a memo field.
|
||||
type: "C", // Changed from "M" to "C" to allow writing, need to verify if this still works.
|
||||
size: 10,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
36
src/main/util/ems-util.ts
Normal file
36
src/main/util/ems-util.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import path from "path";
|
||||
import store from "../store/store";
|
||||
import fs from "fs";
|
||||
|
||||
const generatePpcFilePath = (filename: string): string => {
|
||||
const ppcOutFilePath: string | null = store.get("settings.ppcFilePath");
|
||||
if (!ppcOutFilePath) {
|
||||
throw new Error("PPC file path is not set");
|
||||
}
|
||||
return path.resolve(ppcOutFilePath, filename);
|
||||
};
|
||||
|
||||
const generateEmsOutFilePath = (filename: string): string => {
|
||||
const emsOutFilePath: string | null = store.get("settings.emsOutFilePath");
|
||||
if (!emsOutFilePath) {
|
||||
throw new Error("EMS Out file path is not set");
|
||||
}
|
||||
return path.resolve(emsOutFilePath, filename);
|
||||
};
|
||||
|
||||
const deleteEmsFileIfExists = async (filename: string): Promise<void> => {
|
||||
// Check if the file exists and delete it if it does
|
||||
try {
|
||||
await fs.promises.access(filename); // Check if the file exists
|
||||
await fs.promises.unlink(filename); // Delete the file
|
||||
console.log(`Existing file at ${filename} deleted.`);
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
||||
// If the error is not "file not found", rethrow it
|
||||
throw err;
|
||||
}
|
||||
console.log(`No existing file found at ${filename}.`);
|
||||
}
|
||||
};
|
||||
|
||||
export { generatePpcFilePath, generateEmsOutFilePath, deleteEmsFileIfExists };
|
||||
109
src/main/util/ensureWindowOnScreen.ts
Normal file
109
src/main/util/ensureWindowOnScreen.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { screen } from "electron";
|
||||
|
||||
function ensureWindowOnScreen(
|
||||
x: number | undefined,
|
||||
y: number | undefined,
|
||||
windowWidth: number,
|
||||
windowHeight: number,
|
||||
): { validX: number | undefined; validY: number | undefined } {
|
||||
// If no coordinates stored, let Electron position window automatically
|
||||
if (x === undefined || y === undefined) {
|
||||
return { validX: undefined, validY: undefined };
|
||||
}
|
||||
|
||||
const displays = screen.getAllDisplays();
|
||||
|
||||
// Minimum visible pixels required on each edge to be considered "visible enough"
|
||||
const MIN_VISIBLE = 50; // Ensure at least 50px from each edge is visible
|
||||
|
||||
// Try to find a display where the window would be almost fully visible
|
||||
for (const display of displays) {
|
||||
const { bounds } = display;
|
||||
|
||||
// Check if window is mostly within this display
|
||||
if (
|
||||
x + MIN_VISIBLE >= bounds.x &&
|
||||
x + windowWidth - MIN_VISIBLE <= bounds.x + bounds.width &&
|
||||
y + MIN_VISIBLE >= bounds.y &&
|
||||
y + windowHeight - MIN_VISIBLE <= bounds.y + bounds.height
|
||||
) {
|
||||
// Window is adequately visible on this display
|
||||
return { validX: x, validY: y };
|
||||
}
|
||||
}
|
||||
|
||||
// If window isn't adequately visible on any display, try to adjust it to fit the closest display
|
||||
const closestDisplay = findClosestDisplay(displays, x, y);
|
||||
const { bounds } = closestDisplay;
|
||||
|
||||
// Adjust position to ensure window is fully on screen
|
||||
let adjustedX = x;
|
||||
let adjustedY = y;
|
||||
|
||||
// Adjust horizontal position if needed
|
||||
if (x < bounds.x) {
|
||||
adjustedX = bounds.x;
|
||||
} else if (x + windowWidth > bounds.x + bounds.width) {
|
||||
adjustedX = bounds.x + bounds.width - windowWidth;
|
||||
}
|
||||
|
||||
// Adjust vertical position if needed
|
||||
if (y < bounds.y) {
|
||||
adjustedY = bounds.y;
|
||||
} else if (y + windowHeight > bounds.y + bounds.height) {
|
||||
adjustedY = bounds.y + bounds.height - windowHeight;
|
||||
}
|
||||
|
||||
// If adjustments keep window on screen, use adjusted position
|
||||
if (
|
||||
adjustedX >= bounds.x &&
|
||||
adjustedX + windowWidth <= bounds.x + bounds.width &&
|
||||
adjustedY >= bounds.y &&
|
||||
adjustedY + windowHeight <= bounds.y + bounds.height
|
||||
) {
|
||||
return { validX: adjustedX, validY: adjustedY };
|
||||
}
|
||||
|
||||
// If all else fails, center on primary display
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
const primaryBounds = primaryDisplay.bounds;
|
||||
|
||||
return {
|
||||
validX: Math.floor(
|
||||
primaryBounds.x + (primaryBounds.width - windowWidth) / 2,
|
||||
),
|
||||
validY: Math.floor(
|
||||
primaryBounds.y + (primaryBounds.height - windowHeight) / 2,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to find the closest display to a point
|
||||
function findClosestDisplay(
|
||||
displays: Electron.Display[],
|
||||
x: number,
|
||||
y: number,
|
||||
): Electron.Display {
|
||||
let closestDisplay = displays[0];
|
||||
let shortestDistance = Number.MAX_VALUE;
|
||||
|
||||
for (const display of displays) {
|
||||
const { bounds } = display;
|
||||
// Calculate distance to center of display
|
||||
const displayCenterX = bounds.x + bounds.width / 2;
|
||||
const displayCenterY = bounds.y + bounds.height / 2;
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(x - displayCenterX, 2) + Math.pow(y - displayCenterY, 2),
|
||||
);
|
||||
|
||||
if (distance < shortestDistance) {
|
||||
shortestDistance = distance;
|
||||
closestDisplay = display;
|
||||
}
|
||||
}
|
||||
|
||||
return closestDisplay;
|
||||
}
|
||||
|
||||
export default ensureWindowOnScreen;
|
||||
9
src/main/util/setAppProgressBar.ts
Normal file
9
src/main/util/setAppProgressBar.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getMainWindow } from "./toRenderer";
|
||||
|
||||
const setAppProgressbar = (progress: number): void => {
|
||||
const mainWindow = getMainWindow();
|
||||
if (mainWindow) {
|
||||
mainWindow.setProgressBar(progress);
|
||||
}
|
||||
};
|
||||
export default setAppProgressbar;
|
||||
20
src/main/util/toRenderer.ts
Normal file
20
src/main/util/toRenderer.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {BrowserWindow} from "electron";
|
||||
import log from "electron-log/main";
|
||||
|
||||
const getMainWindow = (): Electron.BrowserWindow => {
|
||||
return BrowserWindow.getAllWindows()[0];
|
||||
};
|
||||
|
||||
const sendIpcToRenderer = (ipcMessage: string, ...args: any[]): void => {
|
||||
const window = getMainWindow();
|
||||
if (window) {
|
||||
window.webContents.send(ipcMessage, ...args);
|
||||
} else {
|
||||
log.error(
|
||||
"Unable to find main window. Cannot send IPC message.",
|
||||
ipcMessage,
|
||||
args,
|
||||
);
|
||||
}
|
||||
};
|
||||
export { getMainWindow, sendIpcToRenderer };
|
||||
24
src/main/util/uppercaseObjectKeys.ts
Normal file
24
src/main/util/uppercaseObjectKeys.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Converts all keys of an object to uppercase
|
||||
* @param obj The object whose keys need to be converted to uppercase
|
||||
* @returns A new object with all keys converted to uppercase
|
||||
*/
|
||||
function uppercaseObjectKeys<T extends Record<string, any>>(
|
||||
obj: T,
|
||||
): Record<string, any> {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
return Object.entries(obj).reduce(
|
||||
(result, [key, value]) => {
|
||||
const uppercaseKey = key.toUpperCase();
|
||||
result[uppercaseKey] = typeof value === "object" && value !== null
|
||||
? uppercaseObjectKeys(value)
|
||||
: value;
|
||||
return result;
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
);
|
||||
}
|
||||
export default uppercaseObjectKeys;
|
||||
170
src/main/watcher/watcher.ts
Normal file
170
src/main/watcher/watcher.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import chokidar, { FSWatcher } from "chokidar";
|
||||
import { BrowserWindow, Notification } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import ipcTypes from "../../util/ipcTypes.json";
|
||||
import ImportJob from "../decoder/decoder";
|
||||
import store from "../store/store";
|
||||
import getMainWindow from "../../util/getMainWindow";
|
||||
let watcher: FSWatcher | null;
|
||||
|
||||
async function StartWatcher(): Promise<boolean> {
|
||||
const filePaths: string[] = store.get("settings.filepaths") || [];
|
||||
|
||||
if (filePaths.length === 0) {
|
||||
new Notification({
|
||||
//TODO: Add Translations
|
||||
title: "Watcher cannot start",
|
||||
body: "Please set the appropriate file paths and try again.",
|
||||
}).show();
|
||||
log.warn("Cannot start watcher. No file paths set.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (watcher) {
|
||||
try {
|
||||
log.info("Trying to close watcher - it already existed.");
|
||||
await watcher.close();
|
||||
|
||||
log.info("Watcher closed successfully!");
|
||||
} catch (error) {
|
||||
log.error("Error trying to close Watcher.", error);
|
||||
}
|
||||
}
|
||||
|
||||
const pollingSettings =
|
||||
(store.get("settings.polling") as {
|
||||
enabled?: boolean;
|
||||
interval?: number;
|
||||
}) || {};
|
||||
|
||||
watcher = chokidar.watch(filePaths, {
|
||||
ignored: (filepath, stats) => {
|
||||
const p = path.parse(filepath);
|
||||
return (
|
||||
(!stats?.isFile() && p.ext !== "" && p.ext.toUpperCase() !== ".ENV") ||
|
||||
p.name?.toUpperCase() === ".DS_STORE"
|
||||
); //Only watch for .ENV files.
|
||||
},
|
||||
usePolling: pollingSettings.enabled || false,
|
||||
interval: pollingSettings.interval || 30000,
|
||||
persistent: true,
|
||||
ignoreInitial: true,
|
||||
awaitWriteFinish: {
|
||||
pollInterval: 500,
|
||||
stabilityThreshold: 2000,
|
||||
},
|
||||
});
|
||||
|
||||
watcher
|
||||
.on("add", async function (path) {
|
||||
console.log("File", path, "has been added");
|
||||
HandleNewFile(path);
|
||||
})
|
||||
// .on("addDir", function (path) {
|
||||
// console.log("Directory", path, "has been added");
|
||||
// })
|
||||
.on("change", async function (path) {
|
||||
console.log("File", path, "has been changed");
|
||||
HandleNewFile(path);
|
||||
})
|
||||
// .on("unlink", function (path) {
|
||||
// console.log("File", path, "has been removed");
|
||||
// })
|
||||
// .on("unlinkDir", function (path) {
|
||||
// console.log("Directory", path, "has been removed");
|
||||
// })
|
||||
.on("error", function (error) {
|
||||
log.error("Error in Watcher", errorTypeCheck(error));
|
||||
// mainWindow.webContents.send(
|
||||
// ipcTypes.toRenderer.watcher.error,
|
||||
// errorTypeCheck(error)
|
||||
// );
|
||||
})
|
||||
.on("ready", onWatcherReady);
|
||||
// .on("raw", function (event, path, details) {
|
||||
// // This event should be triggered everytime something happens.
|
||||
// // console.log("Raw event info:", event, path, details);
|
||||
// });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function removeWatcherPath(path: string): void {
|
||||
if (watcher) {
|
||||
watcher.unwatch(path);
|
||||
log.debug(`Stopped watching path: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
function addWatcherPath(path: string | string[]): void {
|
||||
if (watcher) {
|
||||
watcher.add(path);
|
||||
log.debug(`Started watching path: ${path}`);
|
||||
}
|
||||
}
|
||||
|
||||
function onWatcherReady(): void {
|
||||
if (watcher) {
|
||||
const mainWindow = getMainWindow();
|
||||
new Notification({
|
||||
title: "Watcher Started",
|
||||
body: "Newly exported estimates will be automatically uploaded.",
|
||||
}).show();
|
||||
log.info("Confirmed watched paths:", watcher.getWatched());
|
||||
mainWindow?.webContents.send(ipcTypes.toRenderer.watcher.started);
|
||||
}
|
||||
}
|
||||
|
||||
async function StopWatcher(): Promise<boolean> {
|
||||
const mainWindow = getMainWindow();
|
||||
|
||||
if (watcher) {
|
||||
await watcher.close();
|
||||
log.info("Watcher stopped.");
|
||||
mainWindow?.webContents.send(ipcTypes.toRenderer.watcher.stopped);
|
||||
|
||||
new Notification({
|
||||
title: "Watcher Stopped",
|
||||
body: "Estimates will not be automatically uploaded.",
|
||||
}).show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function HandleNewFile(path): Promise<void> {
|
||||
log.log("Received a new file", path);
|
||||
await ImportJob(path);
|
||||
}
|
||||
|
||||
function GetAllEnvFiles(): string[] {
|
||||
const directories = store.get("settings.filepaths") as string[];
|
||||
const files: string[] = [];
|
||||
directories.forEach((directory) => {
|
||||
try {
|
||||
const envFiles = fs
|
||||
.readdirSync(directory)
|
||||
.filter((file: string) => file.toLowerCase().endsWith(".env"));
|
||||
envFiles.forEach((file) => {
|
||||
const fullPath = path.join(directory, file);
|
||||
files.push(fullPath);
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(`Failed to read directory ${directory}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
export {
|
||||
addWatcherPath,
|
||||
GetAllEnvFiles,
|
||||
removeWatcherPath,
|
||||
StartWatcher,
|
||||
StopWatcher,
|
||||
watcher,
|
||||
};
|
||||
8
src/preload/index.d.ts
vendored
Normal file
8
src/preload/index.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ElectronAPI } from "@electron-toolkit/preload";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: ElectronAPI;
|
||||
api: unknown;
|
||||
}
|
||||
}
|
||||
30
src/preload/index.ts
Normal file
30
src/preload/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { contextBridge } from "electron";
|
||||
import { electronAPI } from "@electron-toolkit/preload";
|
||||
import "electron-log/preload";
|
||||
import store from "../main/store/store";
|
||||
|
||||
// Custom APIs for renderer
|
||||
interface Api {
|
||||
isTest: () => boolean;
|
||||
}
|
||||
|
||||
const api: Api = {
|
||||
isTest: (): boolean => store.get("app.isTest") || false,
|
||||
};
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
// renderer only if context isolation is enabled, otherwise
|
||||
// just add to the DOM global.
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
contextBridge.exposeInMainWorld("electron", electronAPI);
|
||||
contextBridge.exposeInMainWorld("api", api);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
// @ts-ignore (define in dts)
|
||||
window.electron = electronAPI;
|
||||
// @ts-ignore (define in dts)
|
||||
window.api = api;
|
||||
}
|
||||
17
src/renderer/index.html
Normal file
17
src/renderer/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Shop Partner</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<!-- <meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||
/> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
97
src/renderer/src/App.test.tsx
Normal file
97
src/renderer/src/App.test.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
// src/renderer/src/App.test.tsx
|
||||
|
||||
// Mock data
|
||||
const mockUser = {
|
||||
uid: "test123",
|
||||
email: "test@example.com",
|
||||
displayName: "Test User",
|
||||
toJSON: () => ({
|
||||
uid: "test123",
|
||||
email: "test@example.com",
|
||||
displayName: "Test User",
|
||||
}),
|
||||
};
|
||||
|
||||
test.describe("App Component", () => {
|
||||
let page: Page;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
|
||||
// Mock Firebase Auth
|
||||
await page.addInitScript(() => {
|
||||
window.mockAuthState = null;
|
||||
|
||||
// Mock the firebase auth module
|
||||
jest.mock("./util/firebase", () => ({
|
||||
auth: {
|
||||
onAuthStateChanged: (callback) => {
|
||||
callback(window.mockAuthState);
|
||||
// Return mock unsubscribe function
|
||||
return () => {};
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock electron IPC
|
||||
window.electron = {
|
||||
ipcRenderer: {
|
||||
send: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
});
|
||||
|
||||
test("should show SignInForm when user is not authenticated", async () => {
|
||||
await page.evaluate(() => {
|
||||
window.mockAuthState = null;
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Check if SignInForm is visible
|
||||
const signInForm = await page
|
||||
.locator("form")
|
||||
.filter({ hasText: "Sign In" });
|
||||
await expect(signInForm).toBeVisible();
|
||||
});
|
||||
|
||||
test("should show routes when user is authenticated", async () => {
|
||||
await page.evaluate((user) => {
|
||||
window.mockAuthState = user;
|
||||
}, mockUser);
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Check if AuthHome is visible
|
||||
const authHome = await page.locator('div:text("AuthHome")');
|
||||
await expect(authHome).toBeVisible();
|
||||
|
||||
// Check that electron IPC was called with auth state
|
||||
await expect(
|
||||
page.evaluate(() => {
|
||||
return window.electron.ipcRenderer.send.mock.calls.length > 0;
|
||||
}),
|
||||
).resolves.toBe(true);
|
||||
});
|
||||
|
||||
test("should navigate to settings page when authenticated", async () => {
|
||||
await page.evaluate((user) => {
|
||||
window.mockAuthState = user;
|
||||
}, mockUser);
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Navigate to settings
|
||||
await page.click('a[href="/settings"]');
|
||||
|
||||
// Check if Settings page is visible
|
||||
const settingsPage = await page.locator('div:text("Settings")');
|
||||
await expect(settingsPage).toBeVisible();
|
||||
});
|
||||
});
|
||||
88
src/renderer/src/App.tsx
Normal file
88
src/renderer/src/App.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import "@ant-design/v5-patch-for-react-19";
|
||||
import { Layout, Skeleton, ConfigProvider, Badge } from "antd";
|
||||
import { User } from "firebase/auth";
|
||||
import { useEffect, useState, FC } from "react";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { Provider } from "react-redux";
|
||||
import { HashRouter, Route, Routes } from "react-router";
|
||||
import ipcTypes from "../../util/ipcTypes.json";
|
||||
import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback/ErrorBoundaryFallback";
|
||||
import Settings from "./components/Settings/Settings";
|
||||
import SignInForm from "./components/SignInForm/SignInForm";
|
||||
import UpdateAvailable from "./components/UpdateAvailable/UpdateAvailable";
|
||||
import reduxStore from "./redux/redux-store";
|
||||
import { auth } from "./util/firebase";
|
||||
import { NotificationProvider } from "./util/notificationContext";
|
||||
|
||||
const App: FC = () => {
|
||||
const [user, setUser] = useState<User | boolean | null>(false);
|
||||
useEffect(() => {
|
||||
// Only set up the listener once when component mounts
|
||||
if (auth.currentUser) {
|
||||
setUser(auth.currentUser);
|
||||
} else {
|
||||
setUser(false);
|
||||
}
|
||||
const unsubscribe = auth.onAuthStateChanged((user: User | null) => {
|
||||
setUser(user);
|
||||
//Send back to the main process so that it knows we are authenticated.
|
||||
if (user) {
|
||||
window.electron.ipcRenderer.send(
|
||||
ipcTypes.toMain.authStateChanged,
|
||||
user.toJSON(),
|
||||
);
|
||||
window.electron.ipcRenderer.send(ipcTypes.toMain.watcher.start);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up the listener when component unmounts
|
||||
return (): void => unsubscribe();
|
||||
}, []);
|
||||
|
||||
const isTest = window.api.isTest();
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {},
|
||||
components: {
|
||||
Card: {
|
||||
borderRadius: 8,
|
||||
colorBgBase: "#ffaacc",
|
||||
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Provider store={reduxStore}>
|
||||
<HashRouter>
|
||||
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
|
||||
<NotificationProvider>
|
||||
<Skeleton loading={user === false} active>
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
{!user ? (
|
||||
<SignInForm />
|
||||
) : (
|
||||
<Badge.Ribbon
|
||||
text={isTest && "Connected to Test"}
|
||||
color={isTest ? "red" : undefined}
|
||||
>
|
||||
<Layout.Content style={{ padding: "0 24px" }}>
|
||||
<UpdateAvailable />
|
||||
<Routes>
|
||||
<Route path="/" element={<Settings />} />
|
||||
</Routes>
|
||||
</Layout.Content>
|
||||
</Badge.Ribbon>
|
||||
)}
|
||||
</Layout>
|
||||
</Skeleton>
|
||||
</NotificationProvider>
|
||||
</ErrorBoundary>
|
||||
</HashRouter>
|
||||
</Provider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { FC } from "react";
|
||||
import { Button, Result } from "antd";
|
||||
import { FallbackProps } from "react-error-boundary";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ErrorBoundaryFallback: FC<FallbackProps> = ({
|
||||
error,
|
||||
resetErrorBoundary,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Result
|
||||
status={"500"}
|
||||
title={t("errors.errorboundary")}
|
||||
subTitle={error?.message}
|
||||
extra={[
|
||||
<Button key="try-again" onClick={resetErrorBoundary}>
|
||||
Try again
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorBoundaryFallback;
|
||||
11
src/renderer/src/components/Home/Home.tsx
Normal file
11
src/renderer/src/components/Home/Home.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { FC } from "react";
|
||||
|
||||
const Home: FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Home</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,164 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import ipcTypes from "../../../../../util/ipcTypes.json";
|
||||
import {
|
||||
PaintScaleConfig,
|
||||
PaintScaleType,
|
||||
} from "../../../../../util/types/paintScale";
|
||||
import { message } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type ConfigType = "input" | "output";
|
||||
|
||||
export const usePaintScaleConfig = (configType: ConfigType) => {
|
||||
const [paintScaleConfigs, setPaintScaleConfigs] = useState<
|
||||
PaintScaleConfig[]
|
||||
>([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Get the appropriate IPC methods based on config type
|
||||
const getConfigsMethod =
|
||||
configType === "input"
|
||||
? ipcTypes.toMain.settings.paintScale.getInputConfigs
|
||||
: ipcTypes.toMain.settings.paintScale.getOutputConfigs;
|
||||
|
||||
const setConfigsMethod =
|
||||
configType === "input"
|
||||
? ipcTypes.toMain.settings.paintScale.setInputConfigs
|
||||
: ipcTypes.toMain.settings.paintScale.setOutputConfigs;
|
||||
|
||||
const setPathMethod =
|
||||
configType === "input"
|
||||
? ipcTypes.toMain.settings.paintScale.setInputPath
|
||||
: ipcTypes.toMain.settings.paintScale.setOutputPath;
|
||||
|
||||
// Load paint scale configs on mount
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(getConfigsMethod)
|
||||
.then((configs: PaintScaleConfig[]) => {
|
||||
// Ensure all configs have a pollingInterval and type (for backward compatibility)
|
||||
const defaultPolling = configType === "input" ? 1440 : 60;
|
||||
const updatedConfigs = configs.map((config) => ({
|
||||
...config,
|
||||
pollingInterval: config.pollingInterval || defaultPolling, // Default to 1440 for input, 60 for output
|
||||
type: config.type || PaintScaleType.PPG, // Default type if missing
|
||||
}));
|
||||
setPaintScaleConfigs(updatedConfigs || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
`Failed to load paint scale ${configType} configs:`,
|
||||
error,
|
||||
);
|
||||
});
|
||||
}, [getConfigsMethod]);
|
||||
|
||||
// Save configs to store and notify main process of config changes
|
||||
const saveConfigs = (configs: PaintScaleConfig[]) => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(setConfigsMethod, configs)
|
||||
.then(() => {
|
||||
// Notify main process to update cron job
|
||||
if (configType === "input") {
|
||||
window.electron.ipcRenderer.send(
|
||||
ipcTypes.toMain.settings.paintScale.updateInputCron,
|
||||
configs,
|
||||
);
|
||||
} else if (configType === "output") {
|
||||
window.electron.ipcRenderer.send(
|
||||
ipcTypes.toMain.settings.paintScale.updateOutputCron,
|
||||
configs,
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
`Failed to save paint scale ${configType} configs:`,
|
||||
error,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// New helper to check if a path is unique across input and output configs
|
||||
const checkPathUnique = async (newPath: string): Promise<boolean> => {
|
||||
try {
|
||||
const inputConfigs: PaintScaleConfig[] =
|
||||
await window.electron.ipcRenderer.invoke(
|
||||
ipcTypes.toMain.settings.paintScale.getInputConfigs,
|
||||
);
|
||||
const outputConfigs: PaintScaleConfig[] =
|
||||
await window.electron.ipcRenderer.invoke(
|
||||
ipcTypes.toMain.settings.paintScale.getOutputConfigs,
|
||||
);
|
||||
const allConfigs = [...inputConfigs, ...outputConfigs];
|
||||
// Allow updating the current config even if its current value equals newPath.
|
||||
return !allConfigs.some((config) => config.path === newPath);
|
||||
} catch (error) {
|
||||
console.error("Failed to check unique path:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle adding a new paint scale config
|
||||
const handleAddConfig = (type: PaintScaleType) => {
|
||||
const defaultPolling = configType === "input" ? 1440 : 60;
|
||||
const newConfig: PaintScaleConfig = {
|
||||
id: Date.now().toString(),
|
||||
type,
|
||||
pollingInterval: defaultPolling, // Default to 1440 for input, 60 for output
|
||||
};
|
||||
const updatedConfigs = [...paintScaleConfigs, newConfig];
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
// Handle removing a config
|
||||
const handleRemoveConfig = (id: string) => {
|
||||
const updatedConfigs = paintScaleConfigs.filter(
|
||||
(config) => config.id !== id,
|
||||
);
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
// Handle path selection (modified to check directory uniqueness)
|
||||
const handlePathChange = async (id: string) => {
|
||||
try {
|
||||
const path: string | null = await window.electron.ipcRenderer.invoke(
|
||||
setPathMethod,
|
||||
id,
|
||||
);
|
||||
if (path) {
|
||||
const isUnique = await checkPathUnique(path);
|
||||
if (!isUnique) {
|
||||
message.error(t("settings.errors.duplicatePath"));
|
||||
return;
|
||||
}
|
||||
const updatedConfigs = paintScaleConfigs.map((config) =>
|
||||
config.id === id ? { ...config, path } : config,
|
||||
);
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to set paint scale ${configType} path:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle polling interval change
|
||||
const handlePollingIntervalChange = (id: string, pollingInterval: number) => {
|
||||
const updatedConfigs = paintScaleConfigs.map((config) =>
|
||||
config.id === id ? { ...config, pollingInterval } : config,
|
||||
);
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
return {
|
||||
paintScaleConfigs,
|
||||
handleAddConfig,
|
||||
handleRemoveConfig,
|
||||
handlePathChange,
|
||||
handlePollingIntervalChange,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { FolderOpenFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Input, Space } from "antd";
|
||||
import { useEffect, useState, FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ipcTypes from "../../../../util/ipcTypes.json";
|
||||
|
||||
const SettingsEmsOutFilePath: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [emsFilePath, setEmsFilePath] = useState<string | null>(null);
|
||||
|
||||
const getPollingStateFromStore = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.getEmsOutFilePath)
|
||||
.then((filePath: string | null) => {
|
||||
setEmsFilePath(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
//Get state first time it renders.
|
||||
useEffect(() => {
|
||||
getPollingStateFromStore();
|
||||
}, []);
|
||||
|
||||
const handlePathChange = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.setEmsOutFilePath)
|
||||
.then((filePath: string | null) => {
|
||||
setEmsFilePath(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title={t("settings.labels.emsOutFilePath")}>
|
||||
<Space wrap>
|
||||
<Input
|
||||
value={emsFilePath || ""}
|
||||
placeholder={t("settings.labels.emsOutFilePath")}
|
||||
disabled
|
||||
/>
|
||||
<Button onClick={handlePathChange} icon={<FolderOpenFilled />} />
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default SettingsEmsOutFilePath;
|
||||
@@ -0,0 +1,183 @@
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
FileAddFilled,
|
||||
FolderOpenFilled,
|
||||
WarningFilled,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
theme,
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import { JSX, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
PaintScaleConfig,
|
||||
PaintScaleType,
|
||||
paintScaleTypeOptions,
|
||||
} from "../../../../util/types/paintScale";
|
||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||
|
||||
const SettingsPaintScaleInputPaths = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const { token } = theme.useToken(); // Access theme tokens
|
||||
|
||||
const {
|
||||
paintScaleConfigs,
|
||||
handleAddConfig,
|
||||
handleRemoveConfig,
|
||||
handlePathChange,
|
||||
handlePollingIntervalChange,
|
||||
} = usePaintScaleConfig("output");
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<PaintScaleType | null>(null);
|
||||
|
||||
// Show modal when adding a new path
|
||||
const showAddPathModal = () => {
|
||||
setSelectedType(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
// Handle modal confirmation
|
||||
const handleModalOk = () => {
|
||||
if (selectedType) {
|
||||
handleAddConfig(selectedType);
|
||||
setIsModalVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle modal cancellation
|
||||
const handleModalCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
// Table columns for paint scale configs
|
||||
const columns = [
|
||||
{
|
||||
title: t("settings.labels.paintScaleType"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: PaintScaleType) => {
|
||||
const typeOption = paintScaleTypeOptions.find(
|
||||
(option) => option.value === type,
|
||||
);
|
||||
const label = typeOption ? typeOption.label : type;
|
||||
const colorMap: Partial<Record<PaintScaleType, string>> = {
|
||||
[PaintScaleType.PPG]: "blue",
|
||||
// Add other types and colors as needed
|
||||
};
|
||||
return <Tag color={colorMap[type] || "default"}>{label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.paintScalePath"),
|
||||
dataIndex: "path",
|
||||
key: "path",
|
||||
render: (path: string | null, record: PaintScaleConfig) => {
|
||||
const isValid = path && path.trim() !== "";
|
||||
return (
|
||||
<Space>
|
||||
<Input
|
||||
value={path || ""}
|
||||
placeholder={t("settings.labels.paintScalePath")}
|
||||
disabled
|
||||
style={{
|
||||
borderColor: isValid ? token.colorSuccess : token.colorError, // Use semantic tokens
|
||||
}}
|
||||
suffix={
|
||||
<Tooltip
|
||||
title={
|
||||
isValid
|
||||
? t("settings.labels.validPath")
|
||||
: t("settings.labels.invalidPath")
|
||||
}
|
||||
>
|
||||
{isValid ? (
|
||||
<CheckCircleFilled style={{ color: token.colorSuccess }} />
|
||||
) : (
|
||||
<WarningFilled style={{ color: token.colorError }} />
|
||||
)}
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handlePathChange(record.id)}
|
||||
icon={<FolderOpenFilled />}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.pollingInterval"),
|
||||
dataIndex: "pollingInterval",
|
||||
key: "pollingInterval",
|
||||
render: (pollingInterval: number, record: PaintScaleConfig) => (
|
||||
<Input
|
||||
type="number"
|
||||
value={pollingInterval}
|
||||
onChange={(e) =>
|
||||
handlePollingIntervalChange(record.id, Number(e.target.value))
|
||||
}
|
||||
style={{ width: 100 }}
|
||||
placeholder={t("settings.labels.pollingInterval")}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.actions"),
|
||||
key: "actions",
|
||||
render: (_: any, record: PaintScaleConfig) => (
|
||||
<Button danger onClick={() => handleRemoveConfig(record.id)}>
|
||||
{t("settings.labels.remove")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
title={t("settings.labels.paintScaleSettingsInput")}
|
||||
extra={
|
||||
<Button onClick={showAddPathModal} icon={<FileAddFilled />}>
|
||||
{t("settings.actions.addpath")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
dataSource={paintScaleConfigs}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={t("settings.labels.selectPaintScaleType")}
|
||||
open={isModalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
okButtonProps={{ disabled: !selectedType }}
|
||||
>
|
||||
<Select
|
||||
value={selectedType}
|
||||
options={paintScaleTypeOptions}
|
||||
onChange={(value) => setSelectedType(value)}
|
||||
style={{ width: "100%" }}
|
||||
placeholder={t("settings.labels.selectPaintScaleType")}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPaintScaleInputPaths;
|
||||
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
FileAddFilled,
|
||||
FolderOpenFilled,
|
||||
WarningFilled,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
theme,
|
||||
} from "antd";
|
||||
import { JSX, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
PaintScaleConfig,
|
||||
PaintScaleType,
|
||||
paintScaleTypeOptions,
|
||||
} from "../../../../util/types/paintScale";
|
||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||
|
||||
const SettingsPaintScaleOutputPaths = (): JSX.Element => {
|
||||
const { token } = theme.useToken();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
paintScaleConfigs,
|
||||
handleAddConfig,
|
||||
handleRemoveConfig,
|
||||
handlePathChange,
|
||||
handlePollingIntervalChange,
|
||||
} = usePaintScaleConfig("input");
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<PaintScaleType | null>(null);
|
||||
|
||||
// Show modal when adding a new path
|
||||
const showAddPathModal = () => {
|
||||
setSelectedType(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
// Handle modal confirmation
|
||||
const handleModalOk = () => {
|
||||
if (selectedType) {
|
||||
handleAddConfig(selectedType);
|
||||
setIsModalVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle modal cancellation
|
||||
const handleModalCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
// Table columns for paint scale configs
|
||||
const columns = [
|
||||
{
|
||||
title: t("settings.labels.paintScaleType"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: PaintScaleType) => {
|
||||
const typeOption = paintScaleTypeOptions.find(
|
||||
(option) => option.value === type,
|
||||
);
|
||||
const label = typeOption ? typeOption.label : type;
|
||||
const colorMap: Partial<Record<PaintScaleType, string>> = {
|
||||
[PaintScaleType.PPG]: "blue",
|
||||
// Add other types and colors as needed
|
||||
};
|
||||
return <Tag color={colorMap[type] || "default"}>{label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.paintScalePath"),
|
||||
dataIndex: "path",
|
||||
key: "path",
|
||||
render: (path: string | null, record: PaintScaleConfig) => {
|
||||
const isValid = path && path.trim() !== "";
|
||||
return (
|
||||
<Space>
|
||||
<Input
|
||||
value={path || ""}
|
||||
placeholder={t("settings.labels.paintScalePath")}
|
||||
disabled
|
||||
style={{
|
||||
borderColor: isValid ? token.colorSuccess : token.colorError,
|
||||
}}
|
||||
suffix={
|
||||
isValid ? (
|
||||
<CheckCircleFilled style={{ color: token.colorSuccess }} />
|
||||
) : (
|
||||
<WarningFilled style={{ color: token.colorError }} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handlePathChange(record.id)}
|
||||
icon={<FolderOpenFilled />}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.pollingInterval"),
|
||||
dataIndex: "pollingInterval",
|
||||
key: "pollingInterval",
|
||||
render: (pollingInterval: number, record: PaintScaleConfig) => (
|
||||
<Input
|
||||
type="number"
|
||||
value={pollingInterval}
|
||||
onChange={(e) =>
|
||||
handlePollingIntervalChange(record.id, Number(e.target.value))
|
||||
}
|
||||
style={{ width: 100 }}
|
||||
placeholder={t("settings.labels.pollingInterval")}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.actions"),
|
||||
key: "actions",
|
||||
render: (_: any, record: PaintScaleConfig) => (
|
||||
<Button danger onClick={() => handleRemoveConfig(record.id)}>
|
||||
{t("settings.labels.remove")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
title={t("settings.labels.paintScaleSettingsOutput")}
|
||||
extra={
|
||||
<Button onClick={showAddPathModal} icon={<FileAddFilled />}>
|
||||
{t("settings.actions.addpath")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
dataSource={paintScaleConfigs}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={t("settings.labels.selectPaintScaleType")}
|
||||
open={isModalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
okButtonProps={{ disabled: !selectedType }}
|
||||
>
|
||||
<Select
|
||||
value={selectedType}
|
||||
options={paintScaleTypeOptions}
|
||||
onChange={(value) => setSelectedType(value)}
|
||||
style={{ width: "100%" }}
|
||||
placeholder={t("settings.labels.selectPaintScaleType")}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPaintScaleOutputPaths;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { FolderOpenFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Input, Space } from "antd";
|
||||
import { useEffect, useState, FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ipcTypes from "../../../../util/ipcTypes.json";
|
||||
|
||||
const SettingsPpcFilepath: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [ppcFilePath, setPpcFilePath] = useState<string | null>(null);
|
||||
|
||||
const getPollingStateFromStore = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.getPpcFilePath)
|
||||
.then((filePath: string | null) => {
|
||||
setPpcFilePath(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
//Get state first time it renders.
|
||||
useEffect(() => {
|
||||
getPollingStateFromStore();
|
||||
}, []);
|
||||
|
||||
const handlePathChange = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.setPpcFilePath)
|
||||
.then((filePath: string | null) => {
|
||||
setPpcFilePath(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title={t("settings.labels.ppcfilepath")}>
|
||||
<Space wrap>
|
||||
<Input
|
||||
value={ppcFilePath || ""}
|
||||
placeholder={t("settings.labels.ppcfilepath")}
|
||||
disabled
|
||||
/>
|
||||
<Button onClick={handlePathChange} icon={<FolderOpenFilled />} />
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default SettingsPpcFilepath;
|
||||
@@ -0,0 +1,64 @@
|
||||
import { DeleteFilled, FileAddFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Space, Timeline } from "antd";
|
||||
import { useEffect, useState, FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ipcTypes from "../../../../util/ipcTypes.json";
|
||||
|
||||
const SettingsWatchedPaths: FC = () => {
|
||||
const [watchedPaths, setWatchedPaths] = useState<string[]>([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.filepaths.get)
|
||||
.then((paths: string[]) => {
|
||||
setWatchedPaths(paths);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleAddPath = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.filepaths.add)
|
||||
.then((paths: string[]) => {
|
||||
setWatchedPaths(paths);
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemovePath = (path: string): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.filepaths.remove, path)
|
||||
.then((paths: string[]) => {
|
||||
setWatchedPaths(paths);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("settings.labels.watchedpaths")}
|
||||
extra={
|
||||
<Button onClick={handleAddPath} icon={<FileAddFilled />}>
|
||||
{t("settings.actions.addpath")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Timeline
|
||||
items={watchedPaths.map((path, index) => ({
|
||||
key: index,
|
||||
children: (
|
||||
<Space align="baseline">
|
||||
{path}
|
||||
<Button
|
||||
size="small"
|
||||
danger
|
||||
type="text"
|
||||
icon={<DeleteFilled />}
|
||||
onClick={() => handleRemovePath(path)}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default SettingsWatchedPaths;
|
||||
157
src/renderer/src/components/Settings/Settings.Watcher.tsx
Normal file
157
src/renderer/src/components/Settings/Settings.Watcher.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import { useAppSelector } from "@renderer/redux/reduxHooks";
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
PlayCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
selectWatcherError,
|
||||
selectWatcherStatus,
|
||||
} from "@renderer/redux/app.slice";
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
InputNumber,
|
||||
Row,
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ipcTypes from "../../../../util/ipcTypes.json";
|
||||
|
||||
const colSpans = {
|
||||
md: 12,
|
||||
sm: 24,
|
||||
};
|
||||
|
||||
const SettingsWatcher: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const isWatcherStarted = useAppSelector(selectWatcherStatus);
|
||||
const watcherError = useAppSelector(selectWatcherError);
|
||||
|
||||
const [pollingState, setPollingState] = useState<{
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
}>({
|
||||
enabled: false,
|
||||
interval: 0,
|
||||
});
|
||||
|
||||
const getPollingStateFromStore = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.watcher.getpolling)
|
||||
.then((storePollingState: { enabled: boolean; interval: number }) => {
|
||||
setPollingState(storePollingState);
|
||||
});
|
||||
};
|
||||
|
||||
//Get state first time it renders.
|
||||
useEffect(() => {
|
||||
getPollingStateFromStore();
|
||||
}, []);
|
||||
|
||||
const handleStart = (): void => {
|
||||
window.electron.ipcRenderer.send(ipcTypes.toMain.watcher.start);
|
||||
};
|
||||
|
||||
const handleStop = (): void => {
|
||||
window.electron.ipcRenderer.send(ipcTypes.toMain.watcher.stop);
|
||||
};
|
||||
|
||||
const toggleWatcherMode = (checked: boolean): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.watcher.setpolling, {
|
||||
enabled: !checked,
|
||||
interval: pollingState.interval,
|
||||
})
|
||||
.then((storePollingState: { enabled: boolean; interval: number }) => {
|
||||
setPollingState(storePollingState);
|
||||
});
|
||||
};
|
||||
|
||||
const handlePollingIntervalChange = (value: number | null): void => {
|
||||
if (value) {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.watcher.setpolling, {
|
||||
enabled: pollingState.enabled,
|
||||
interval: value,
|
||||
})
|
||||
.then((storePollingState: { enabled: boolean; interval: number }) => {
|
||||
setPollingState(storePollingState);
|
||||
});
|
||||
}
|
||||
getPollingStateFromStore();
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge.Ribbon
|
||||
text={
|
||||
isWatcherStarted ? (
|
||||
<Space>
|
||||
<CheckCircleFilled />
|
||||
{t("settings.labels.started")}
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
<ExclamationCircleFilled />
|
||||
{t("settings.labels.stopped")}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
color={isWatcherStarted ? "green" : "red"}
|
||||
>
|
||||
<Card title={t("settings.labels.watcherstatus")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...colSpans}>
|
||||
{isWatcherStarted ? (
|
||||
<Button
|
||||
danger
|
||||
icon={<PauseCircleOutlined />}
|
||||
onClick={handleStop}
|
||||
>
|
||||
{t("settings.actions.stopwatcher")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlayCircleOutlined />}
|
||||
onClick={handleStart}
|
||||
>
|
||||
{t("settings.actions.startwatcher")}
|
||||
</Button>
|
||||
)}
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<Space direction="vertical" wrap>
|
||||
<Switch
|
||||
checked={!pollingState.enabled}
|
||||
onChange={toggleWatcherMode}
|
||||
checkedChildren={t("settings.labels.watchermoderealtime")}
|
||||
unCheckedChildren={t("settings.labels.watchermodepolling")}
|
||||
/>
|
||||
{pollingState.enabled && (
|
||||
<Space size="small" direction="vertical" wrap>
|
||||
<span>{t("settings.labels.pollinginterval")}</span>
|
||||
<InputNumber
|
||||
title={t("settings.labels.pollinginterval")}
|
||||
disabled={!pollingState.enabled}
|
||||
min={1000}
|
||||
value={pollingState.interval}
|
||||
onChange={handlePollingIntervalChange}
|
||||
/>
|
||||
</Space>
|
||||
)}
|
||||
{watcherError && <Alert message={watcherError} />}
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Badge.Ribbon>
|
||||
);
|
||||
};
|
||||
export default SettingsWatcher;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user