feature/IO-3205-Paint-Scale-Integrations: Add Verbose XML checking
This commit is contained in:
48
package-lock.json
generated
48
package-lock.json
generated
@@ -21,6 +21,7 @@
|
||||
"electron-updater": "^6.6.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"winax": "^3.6.2",
|
||||
"xml2js": "^0.6.2",
|
||||
"xmlbuilder2": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -37,6 +38,7 @@
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"antd": "^5.24.6",
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -4374,13 +4376,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||
@@ -4646,6 +4641,16 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/xml2js": {
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
@@ -12948,13 +12953,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz",
|
||||
"integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==",
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
|
||||
"integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"turbo-stream": "2.4.0"
|
||||
@@ -15255,6 +15259,28 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js/node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "15.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"electron-updater": "^6.6.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"winax": "^3.6.2",
|
||||
"xml2js": "^0.6.2",
|
||||
"xmlbuilder2": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,6 +53,7 @@
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"antd": "^5.24.6",
|
||||
"chokidar": "^4.0.3",
|
||||
|
||||
@@ -3,29 +3,62 @@ 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 from "../../graphql/graphql-client";
|
||||
import { PaintScaleConfig, PaintScaleType } from "../../util/types/paintScale";
|
||||
import { PaintScaleConfig } from "../../../util/types/paintScale";
|
||||
|
||||
// PPG Input Handler
|
||||
export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
try {
|
||||
log.info(`Polling input directory for PPG config ${config.id}: ${config.path}`);
|
||||
log.info(
|
||||
`Polling input directory for PPG config ${config.id}: ${config.path}`,
|
||||
);
|
||||
|
||||
// Ensure archive directory exists
|
||||
// Ensure archive and error directories exist
|
||||
const archiveDir = path.join(config.path!, "archive");
|
||||
const errorDir = path.join(config.path!, "error");
|
||||
await fs.mkdir(archiveDir, { recursive: true });
|
||||
await fs.mkdir(errorDir, { recursive: true });
|
||||
|
||||
// Check for files
|
||||
const files = await fs.readdir(config.path!);
|
||||
for (const file of files) {
|
||||
if (!file.toLowerCase().endsWith(".xml")) continue;
|
||||
// Only process XML files
|
||||
if (!file.toLowerCase().endsWith(".xml")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(config.path!, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
if (stats.isFile()) {
|
||||
if (!stats.isFile()) {
|
||||
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);
|
||||
} catch (error) {
|
||||
log.error(`Invalid XML in ${filePath}:`, error);
|
||||
const errorPath = path.join(errorDir, path.basename(filePath));
|
||||
await fs.rename(filePath, errorPath);
|
||||
log.debug(`Moved invalid file to error: ${errorPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get authentication token
|
||||
const token = (store.get("user") as any)?.stsTokenManager?.accessToken;
|
||||
if (!token) {
|
||||
@@ -33,9 +66,13 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Upload file to API
|
||||
const formData = new FormData();
|
||||
formData.append("file", new Blob([await fs.readFile(filePath)]), path.basename(filePath));
|
||||
formData.append("shopId", (store.get("app.bodyshop") as any)?.shopname || "");
|
||||
formData.append("file", new Blob([xmlContent]), path.basename(filePath));
|
||||
formData.append(
|
||||
"shopId",
|
||||
(store.get("app.bodyshop") as any)?.shopname || "",
|
||||
);
|
||||
|
||||
const baseURL = store.get("app.isTest")
|
||||
? import.meta.env.VITE_API_TEST_URL
|
||||
@@ -44,6 +81,7 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
|
||||
log.debug(`Uploading file to ${finalUrl}`);
|
||||
|
||||
try {
|
||||
const response = await axios.post(finalUrl, formData, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
@@ -58,8 +96,13 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
await fs.rename(filePath, archivePath);
|
||||
log.debug(`Moved file to archive: ${archivePath}`);
|
||||
} else {
|
||||
log.error(`Failed to upload ${filePath}: ${response.statusText}`);
|
||||
log.error(
|
||||
`Failed to upload ${filePath}: ${response.status} ${response.statusText}`,
|
||||
response.data,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Error uploading ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -68,7 +111,9 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
}
|
||||
|
||||
// PPG Output Handler
|
||||
export async function ppgOutputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
export async function ppgOutputHandler(
|
||||
config: PaintScaleConfig,
|
||||
): Promise<void> {
|
||||
try {
|
||||
log.info(`Generating PPG output for config ${config.id}: ${config.path}`);
|
||||
|
||||
@@ -150,7 +195,10 @@ export async function ppgOutputHandler(config: PaintScaleConfig): Promise<void>
|
||||
},
|
||||
Transaction: {
|
||||
TransactionID: "",
|
||||
TransactionDate: new Date().toISOString().replace("T", " ").substring(0, 19),
|
||||
TransactionDate: new Date()
|
||||
.toISOString()
|
||||
.replace("T", " ")
|
||||
.substring(0, 19),
|
||||
},
|
||||
Product: {
|
||||
Name: "ImEX Online",
|
||||
@@ -179,11 +227,15 @@ export async function ppgOutputHandler(config: PaintScaleConfig): Promise<void>
|
||||
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),
|
||||
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),
|
||||
TotalCostOfRepairs: (
|
||||
(job.job_totals?.totals?.subtotal?.amount || 0) / 100
|
||||
).toFixed(2),
|
||||
})),
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user