feature/IO-3205-Paint-Scale-Integrations: Cleanup refactor checkpoint
This commit is contained in:
@@ -28,80 +28,8 @@ import {
|
|||||||
ipMainHandleResetPassword,
|
ipMainHandleResetPassword,
|
||||||
} from "./ipcMainHandler.user";
|
} from "./ipcMainHandler.user";
|
||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
import fs from "fs/promises";
|
import { PaintScaleConfig, PaintScaleType } from "../../util/types/paintScale";
|
||||||
import axios from "axios";
|
import { ppgInputHandler, ppgOutputHandler } from "./paintScaleHandlers/PPG";
|
||||||
import { create } from "xmlbuilder2";
|
|
||||||
import client from "../graphql/graphql-client";
|
|
||||||
import { PaintScaleConfig, PaintScaleType } from "./paintScale";
|
|
||||||
|
|
||||||
// Add these interfaces at the top of your file
|
|
||||||
interface User {
|
|
||||||
stsTokenManager?: {
|
|
||||||
accessToken: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BodyShop {
|
|
||||||
shopname: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initializeCronTasks = async () => {
|
const initializeCronTasks = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -144,237 +72,19 @@ const logIpcMessages = (): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Input handler map
|
// Input handler map
|
||||||
const inputTypeHandlers: Record<
|
const inputTypeHandlers: Partial<
|
||||||
PaintScaleType,
|
Record<PaintScaleType, (config: PaintScaleConfig) => Promise<void>>
|
||||||
(config: PaintScaleConfig) => Promise<void>
|
|
||||||
> = {
|
> = {
|
||||||
[PaintScaleType.PPG]: async (config: PaintScaleConfig) => {
|
[PaintScaleType.PPG]: ppgInputHandler,
|
||||||
try {
|
|
||||||
log.info(
|
|
||||||
`Polling input directory for PPG config ${config.id}: ${config.path}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure archive directory exists
|
|
||||||
const archiveDir = path.join(config.path!, "archive");
|
|
||||||
await fs.mkdir(archiveDir, { recursive: true });
|
|
||||||
|
|
||||||
// Check for files
|
|
||||||
const files = await fs.readdir(config.path!);
|
|
||||||
for (const file of files) {
|
|
||||||
// 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}`);
|
|
||||||
|
|
||||||
// Get authentication token
|
|
||||||
const token = (store.get("user") as User)?.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([await fs.readFile(filePath)]),
|
|
||||||
path.basename(filePath),
|
|
||||||
);
|
|
||||||
formData.append(
|
|
||||||
"shopId",
|
|
||||||
(store.get("app.bodyshop") as BodyShop)?.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}`);
|
|
||||||
|
|
||||||
const response = await axios.post(finalUrl, formData, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
log.info(`Successful upload of ${filePath}`);
|
|
||||||
// Move file to archive
|
|
||||||
const archivePath = path.join(archiveDir, path.basename(filePath));
|
|
||||||
await fs.rename(filePath, archivePath);
|
|
||||||
log.debug(`Moved file to archive: ${archivePath}`);
|
|
||||||
} else {
|
|
||||||
log.error(`Failed to upload ${filePath}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log.error(`Error polling input directory ${config.path}:`, error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Add other input type handlers as needed
|
// Add other input type handlers as needed
|
||||||
};
|
};
|
||||||
|
|
||||||
// Output handler map
|
// Output handler map
|
||||||
const outputTypeHandlers: Record<
|
const outputTypeHandlers: Partial<
|
||||||
PaintScaleType,
|
Record<PaintScaleType, (config: PaintScaleConfig) => Promise<void>>
|
||||||
(config: PaintScaleConfig) => Promise<void>
|
|
||||||
> = {
|
> = {
|
||||||
[PaintScaleType.PPG]: async (config: PaintScaleConfig) => {
|
[PaintScaleType.PPG]: ppgOutputHandler,
|
||||||
try {
|
// Add other output type handlers as needed
|
||||||
log.info(`Generating PPG output for config ${config.id}: ${config.path}`);
|
|
||||||
|
|
||||||
await fs.mkdir(config.path!, { recursive: true });
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const variables = {
|
|
||||||
today: new Date().toISOString(),
|
|
||||||
todayplus5: new Date(Date.now() + 5 * 86400000).toISOString(),
|
|
||||||
shopid: (store.get("app.bodyshop") as BodyShop)?.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = (await client.request(
|
|
||||||
query,
|
|
||||||
variables,
|
|
||||||
)) as GraphQLResponse;
|
|
||||||
|
|
||||||
const jobs = response.jobs ?? [];
|
|
||||||
|
|
||||||
const header = {
|
|
||||||
PPG: {
|
|
||||||
Header: {
|
|
||||||
Protocol: {
|
|
||||||
Message: "PaintShopInterface",
|
|
||||||
Name: "PPG",
|
|
||||||
Version: "1.5.0",
|
|
||||||
},
|
|
||||||
Transaction: {
|
|
||||||
TransactionID: "",
|
|
||||||
TransactionDate: new Date()
|
|
||||||
.toISOString()
|
|
||||||
.replace("T", " ")
|
|
||||||
.substring(0, 19),
|
|
||||||
},
|
|
||||||
Product: {
|
|
||||||
Name: "ImEX Online",
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default handler for unsupported types
|
// Default handler for unsupported types
|
||||||
|
|||||||
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;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
StartWatcher,
|
StartWatcher,
|
||||||
StopWatcher,
|
StopWatcher,
|
||||||
} from "../watcher/watcher";
|
} from "../watcher/watcher";
|
||||||
import { PaintScaleConfig } from "./paintScale";
|
import { PaintScaleConfig } from "../../util/types/paintScale";
|
||||||
|
|
||||||
|
|
||||||
// Initialize paint scale input configs in store if not set
|
// Initialize paint scale input configs in store if not set
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// src/types/paintScale.ts
|
|
||||||
export enum PaintScaleType {
|
|
||||||
PPG = 'PPG',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PaintScaleConfig {
|
|
||||||
id: string;
|
|
||||||
path?: string;
|
|
||||||
type: PaintScaleType;
|
|
||||||
pollingInterval: number;
|
|
||||||
}
|
|
||||||
201
src/main/ipc/paintScaleHandlers/PPG.ts
Normal file
201
src/main/ipc/paintScaleHandlers/PPG.ts
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import log from "electron-log/main";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import axios from "axios";
|
||||||
|
import { create } from "xmlbuilder2";
|
||||||
|
import store from "../../store/store";
|
||||||
|
import client from "../../graphql/graphql-client";
|
||||||
|
import { PaintScaleConfig, PaintScaleType } 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}`);
|
||||||
|
|
||||||
|
// Ensure archive directory exists
|
||||||
|
const archiveDir = path.join(config.path!, "archive");
|
||||||
|
await fs.mkdir(archiveDir, { recursive: true });
|
||||||
|
|
||||||
|
// Check for files
|
||||||
|
const files = await fs.readdir(config.path!);
|
||||||
|
for (const file of 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}`);
|
||||||
|
|
||||||
|
// Get authentication token
|
||||||
|
const token = (store.get("user") as any)?.stsTokenManager?.accessToken;
|
||||||
|
if (!token) {
|
||||||
|
log.error(`No authentication token for file: ${filePath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 || "");
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
|
||||||
|
const response = await axios.post(finalUrl, formData, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
log.info(`Successful upload of ${filePath}`);
|
||||||
|
// Move file to archive
|
||||||
|
const archivePath = path.join(archiveDir, path.basename(filePath));
|
||||||
|
await fs.rename(filePath, archivePath);
|
||||||
|
log.debug(`Moved file to archive: ${archivePath}`);
|
||||||
|
} else {
|
||||||
|
log.error(`Failed to upload ${filePath}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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 query = `
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
today: new Date().toISOString(),
|
||||||
|
todayplus5: new Date(Date.now() + 5 * 86400000).toISOString(),
|
||||||
|
shopid: (store.get("app.bodyshop") as any)?.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = (await client.request(query, variables)) as any;
|
||||||
|
const jobs = response.jobs ?? [];
|
||||||
|
|
||||||
|
const header = {
|
||||||
|
PPG: {
|
||||||
|
Header: {
|
||||||
|
Protocol: {
|
||||||
|
Message: "PaintShopInterface",
|
||||||
|
Name: "PPG",
|
||||||
|
Version: "1.5.0",
|
||||||
|
},
|
||||||
|
Transaction: {
|
||||||
|
TransactionID: "",
|
||||||
|
TransactionDate: new Date().toISOString().replace("T", " ").substring(0, 19),
|
||||||
|
},
|
||||||
|
Product: {
|
||||||
|
Name: "ImEX Online",
|
||||||
|
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: any) => ({
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import ipcTypes from '../../../../../util/ipcTypes.json';
|
import ipcTypes from '../../../../../util/ipcTypes.json';
|
||||||
import { PaintScaleConfig, PaintScaleType } from "./types";
|
import { PaintScaleConfig, PaintScaleType } from '../../../../../util/types/paintScale';
|
||||||
import { message } from "antd";
|
import { message } from "antd";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
@@ -76,7 +76,6 @@ export const usePaintScaleConfig = (configType: ConfigType) => {
|
|||||||
const handleAddConfig = (type: PaintScaleType) => {
|
const handleAddConfig = (type: PaintScaleType) => {
|
||||||
const newConfig: PaintScaleConfig = {
|
const newConfig: PaintScaleConfig = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
path: null,
|
|
||||||
type,
|
type,
|
||||||
pollingInterval: 1440, // Default to 1440 seconds
|
pollingInterval: 1440, // Default to 1440 seconds
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
PaintScaleConfig,
|
PaintScaleConfig,
|
||||||
PaintScaleType,
|
PaintScaleType,
|
||||||
paintScaleTypeOptions,
|
paintScaleTypeOptions,
|
||||||
} from "./PaintScale/types";
|
} from "../../../../util/types/paintScale";
|
||||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||||
|
|
||||||
const SettingsPaintScaleInputPaths: FC = () => {
|
const SettingsPaintScaleInputPaths: FC = () => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
PaintScaleConfig,
|
PaintScaleConfig,
|
||||||
PaintScaleType,
|
PaintScaleType,
|
||||||
paintScaleTypeOptions,
|
paintScaleTypeOptions,
|
||||||
} from "./PaintScale/types";
|
} from "../../../../util/types/paintScale";
|
||||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||||
|
|
||||||
const SettingsPaintScaleOutputPaths: FC = () => {
|
const SettingsPaintScaleOutputPaths: FC = () => {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
export interface PaintScaleConfig {
|
|
||||||
id: string;
|
|
||||||
path: string | null;
|
|
||||||
type: PaintScaleType;
|
|
||||||
pollingInterval: number; // In seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PaintScaleType {
|
export enum PaintScaleType {
|
||||||
PPG = "PPG",
|
PPG = "PPG",
|
||||||
SHERWIN = "SHERWIN",
|
|
||||||
AKZO = "AKZO",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const paintScaleTypeOptions = Object.values(PaintScaleType).map((type) => ({
|
export interface PaintScaleConfig {
|
||||||
value: type,
|
id: string;
|
||||||
label: type,
|
path?: string;
|
||||||
}));
|
type: PaintScaleType;
|
||||||
|
pollingInterval: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const paintScaleTypeOptions = Object.values(PaintScaleType).map(
|
||||||
|
(type) => ({
|
||||||
|
value: type,
|
||||||
|
label: type,
|
||||||
|
}),
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user