feature/IO-3205-Paint-Scale-Integrations: Add Verbose XML checking

This commit is contained in:
Dave Richer
2025-04-30 13:47:08 -04:00
parent 90077fc72c
commit 4113a16760
3 changed files with 116 additions and 36 deletions

View File

@@ -3,47 +3,85 @@ 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()) {
log.debug(`Processing input file: ${filePath}`);
if (!stats.isFile()) {
continue;
}
// Get authentication token
const token = (store.get("user") as any)?.stsTokenManager?.accessToken;
if (!token) {
log.error(`No authentication token for file: ${filePath}`);
continue;
}
log.debug(`Processing input file: ${filePath}`);
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 || "");
// 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;
}
const baseURL = store.get("app.isTest")
? import.meta.env.VITE_API_TEST_URL
: import.meta.env.VITE_API_URL;
const finalUrl = `${baseURL}/mixdata/upload`;
// Validate XML structure
let xmlContent : BlobPart;
log.debug(`Uploading file to ${finalUrl}`);
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) {
log.error(`No authentication token for file: ${filePath}`);
continue;
}
// Upload file to API
const formData = new FormData();
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
: 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}`,
@@ -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),
})),
},
},