feature/IO-3066-1-scaffolding: Fix some missing PR Notes around PPG
This commit is contained in:
@@ -2,6 +2,7 @@ import { UUID } from "crypto";
|
|||||||
import { parse, TypedQueryDocumentNode } from "graphql";
|
import { parse, TypedQueryDocumentNode } from "graphql";
|
||||||
import { gql } from "graphql-request";
|
import { gql } from "graphql-request";
|
||||||
import { AvailableJobSchema } from "../decoder/decoder";
|
import { AvailableJobSchema } from "../decoder/decoder";
|
||||||
|
|
||||||
// Define types for the query result and variables
|
// Define types for the query result and variables
|
||||||
export interface ActiveBodyshopQueryResult {
|
export interface ActiveBodyshopQueryResult {
|
||||||
bodyshops: Array<{
|
bodyshops: Array<{
|
||||||
@@ -11,9 +12,8 @@ export interface ActiveBodyshopQueryResult {
|
|||||||
convenient_company: string;
|
convenient_company: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
// No variables needed for this query
|
|
||||||
|
|
||||||
// Transform the string query into a TypedQueryDocumentNode
|
// No variables needed for this query
|
||||||
export const QUERY_ACTIVE_BODYSHOP_TYPED: TypedQueryDocumentNode<
|
export const QUERY_ACTIVE_BODYSHOP_TYPED: TypedQueryDocumentNode<
|
||||||
ActiveBodyshopQueryResult,
|
ActiveBodyshopQueryResult,
|
||||||
Record<never, never>
|
Record<never, never>
|
||||||
@@ -34,9 +34,11 @@ export interface MasterdataQueryResult {
|
|||||||
key: string;
|
key: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MasterdataQueryVariables {
|
interface MasterdataQueryVariables {
|
||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QUERY_MASTERDATA_TYPED: TypedQueryDocumentNode<
|
export const QUERY_MASTERDATA_TYPED: TypedQueryDocumentNode<
|
||||||
MasterdataQueryResult,
|
MasterdataQueryResult,
|
||||||
MasterdataQueryVariables
|
MasterdataQueryVariables
|
||||||
@@ -54,9 +56,11 @@ export interface VehicleQueryResult {
|
|||||||
id: UUID;
|
id: UUID;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VehicleQueryVariables {
|
interface VehicleQueryVariables {
|
||||||
vin: string;
|
vin: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QUERY_VEHICLE_BY_VIN_TYPED: TypedQueryDocumentNode<
|
export const QUERY_VEHICLE_BY_VIN_TYPED: TypedQueryDocumentNode<
|
||||||
VehicleQueryResult,
|
VehicleQueryResult,
|
||||||
VehicleQueryVariables
|
VehicleQueryVariables
|
||||||
@@ -73,9 +77,11 @@ export interface QueryJobByClmNoResult {
|
|||||||
id: UUID;
|
id: UUID;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryJobByClmNoVariables {
|
export interface QueryJobByClmNoVariables {
|
||||||
clm_no: string;
|
clm_no: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QUERY_JOB_BY_CLM_NO_TYPED: TypedQueryDocumentNode<
|
export const QUERY_JOB_BY_CLM_NO_TYPED: TypedQueryDocumentNode<
|
||||||
QueryJobByClmNoResult,
|
QueryJobByClmNoResult,
|
||||||
QueryJobByClmNoVariables
|
QueryJobByClmNoVariables
|
||||||
@@ -92,9 +98,11 @@ export interface InsertAvailableJobResult {
|
|||||||
id: UUID;
|
id: UUID;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InsertAvailableJobVariables {
|
export interface InsertAvailableJobVariables {
|
||||||
jobInput: Array<AvailableJobSchema>;
|
jobInput: Array<AvailableJobSchema>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INSERT_AVAILABLE_JOB_TYPED: TypedQueryDocumentNode<
|
export const INSERT_AVAILABLE_JOB_TYPED: TypedQueryDocumentNode<
|
||||||
InsertAvailableJobResult,
|
InsertAvailableJobResult,
|
||||||
InsertAvailableJobVariables
|
InsertAvailableJobVariables
|
||||||
@@ -125,3 +133,140 @@ export const INSERT_AVAILABLE_JOB_TYPED: TypedQueryDocumentNode<
|
|||||||
InsertAvailableJobResult,
|
InsertAvailableJobResult,
|
||||||
InsertAvailableJobVariables
|
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>;
|
||||||
|
|||||||
@@ -464,27 +464,32 @@ app.whenReady().then(async () => {
|
|||||||
if (url.startsWith(`${protocol}://keep-alive`)) {
|
if (url.startsWith(`${protocol}://keep-alive`)) {
|
||||||
log.info("Keep-alive protocol received, app is already running.");
|
log.info("Keep-alive protocol received, app is already running.");
|
||||||
// Do nothing if already running
|
// Do nothing if already running
|
||||||
return; // Skip openMainWindow to avoid focusing the window
|
return;
|
||||||
} else {
|
} else {
|
||||||
openInExplorer(url);
|
openInExplorer(url);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
openMainWindow(); // Focus window if no URL
|
|
||||||
}
|
}
|
||||||
|
// No action taken if no URL is provided
|
||||||
});
|
});
|
||||||
|
|
||||||
//Dynamically load ipcMain handlers once ready.
|
//Dynamically load ipcMain handlers once ready.
|
||||||
try {
|
try {
|
||||||
const module = await import("./ipc/ipcMainConfig");
|
const { initializeCronTasks } = await import("./ipc/ipcMainConfig");
|
||||||
log.debug("Successfully loaded ipcMainConfig");
|
log.debug("Successfully loaded ipcMainConfig");
|
||||||
|
|
||||||
// Initialize cron tasks after loading ipcMainConfig
|
try {
|
||||||
await module.initializeCronTasks();
|
await initializeCronTasks();
|
||||||
log.info("Cron tasks initialized successfully");
|
log.info("Cron tasks initialized successfully");
|
||||||
|
} catch (error) {
|
||||||
|
log.warn("Non-fatal: Failed to initialize cron tasks", {
|
||||||
|
...ErrorTypeCheck(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to load ipcMainConfig or initialize cron tasks", {
|
log.error("Fatal: Failed to load ipcMainConfig", {
|
||||||
...ErrorTypeCheck(error),
|
...ErrorTypeCheck(error),
|
||||||
});
|
});
|
||||||
|
throw error; // Adjust based on whether the app should continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create Tray
|
//Create Tray
|
||||||
@@ -568,15 +573,10 @@ app.whenReady().then(async () => {
|
|||||||
|
|
||||||
app.on("open-url", (event: Electron.Event, url: string) => {
|
app.on("open-url", (event: Electron.Event, url: string) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
//Don't do anything for now. We just want to open the app.
|
|
||||||
if (url.startsWith(`${protocol}://keep-alive`)) {
|
if (url.startsWith(`${protocol}://keep-alive`)) {
|
||||||
log.info("Keep-alive protocol received.");
|
log.info("Keep-alive protocol received.");
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
// Do nothing, whether app is running or not
|
||||||
isKeepAliveLaunch = true;
|
return;
|
||||||
openMainWindow(); // Launch app if not running
|
|
||||||
}
|
|
||||||
// Do nothing if already running
|
|
||||||
return; // Skip openMainWindow to avoid focusing the window
|
|
||||||
} else {
|
} else {
|
||||||
openInExplorer(url);
|
openInExplorer(url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
} from "../watcher/watcher";
|
} from "../watcher/watcher";
|
||||||
import { PaintScaleConfig } from "../../util/types/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
|
||||||
if (!Store.get("settings.paintScaleInputConfigs")) {
|
if (!Store.get("settings.paintScaleInputConfigs")) {
|
||||||
Store.set("settings.paintScaleInputConfigs", []);
|
Store.set("settings.paintScaleInputConfigs", []);
|
||||||
@@ -35,8 +34,8 @@ const SettingsWatchedFilePathsAdd = async (): Promise<string[]> => {
|
|||||||
|
|
||||||
if (!result.canceled) {
|
if (!result.canceled) {
|
||||||
Store.set(
|
Store.set(
|
||||||
"settings.filepaths",
|
"settings.filepaths",
|
||||||
_.union(result.filePaths, Store.get("settings.filepaths")),
|
_.union(result.filePaths, Store.get("settings.filepaths")),
|
||||||
);
|
);
|
||||||
addWatcherPath(result.filePaths);
|
addWatcherPath(result.filePaths);
|
||||||
}
|
}
|
||||||
@@ -45,12 +44,12 @@ const SettingsWatchedFilePathsAdd = async (): Promise<string[]> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SettingsWatchedFilePathsRemove = async (
|
const SettingsWatchedFilePathsRemove = async (
|
||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
path: string,
|
path: string,
|
||||||
): Promise<string[]> => {
|
): Promise<string[]> => {
|
||||||
Store.set(
|
Store.set(
|
||||||
"settings.filepaths",
|
"settings.filepaths",
|
||||||
_.without(Store.get("settings.filepaths"), path),
|
_.without(Store.get("settings.filepaths"), path),
|
||||||
);
|
);
|
||||||
removeWatcherPath(path);
|
removeWatcherPath(path);
|
||||||
return Store.get("settings.filepaths");
|
return Store.get("settings.filepaths");
|
||||||
@@ -65,16 +64,16 @@ const SettingsWatcherPollingGet = async (): Promise<{
|
|||||||
interval: number;
|
interval: number;
|
||||||
}> => {
|
}> => {
|
||||||
const pollingEnabled: { enabled: boolean; interval: number } =
|
const pollingEnabled: { enabled: boolean; interval: number } =
|
||||||
Store.get("settings.polling");
|
Store.get("settings.polling");
|
||||||
return { enabled: pollingEnabled.enabled, interval: pollingEnabled.interval };
|
return { enabled: pollingEnabled.enabled, interval: pollingEnabled.interval };
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsWatcherPollingSet = async (
|
const SettingsWatcherPollingSet = async (
|
||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
pollingSettings: {
|
pollingSettings: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
interval: number;
|
interval: number;
|
||||||
},
|
},
|
||||||
): Promise<{
|
): Promise<{
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
interval: number;
|
interval: number;
|
||||||
@@ -131,11 +130,13 @@ const SettingEmsOutFilePathSet = async (): Promise<string> => {
|
|||||||
return (Store.get("settings.emsOutFilePath") as string) || "";
|
return (Store.get("settings.emsOutFilePath") as string) || "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPaintScaleInputConfigsGet = async (
|
const SettingsPaintScaleInputConfigsGet = (
|
||||||
_event?: IpcMainInvokeEvent,
|
_event?: IpcMainInvokeEvent,
|
||||||
): Promise<PaintScaleConfig[]> => {
|
): PaintScaleConfig[] => {
|
||||||
try {
|
try {
|
||||||
const configs = Store.get("settings.paintScaleInputConfigs") as PaintScaleConfig[];
|
const configs = Store.get(
|
||||||
|
"settings.paintScaleInputConfigs",
|
||||||
|
) as PaintScaleConfig[];
|
||||||
log.debug("Retrieved paint scale input configs:", configs);
|
log.debug("Retrieved paint scale input configs:", configs);
|
||||||
return configs || [];
|
return configs || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -145,8 +146,8 @@ const SettingsPaintScaleInputConfigsGet = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPaintScaleInputConfigsSet = async (
|
const SettingsPaintScaleInputConfigsSet = async (
|
||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
configs: PaintScaleConfig[],
|
configs: PaintScaleConfig[],
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
Store.set("settings.paintScaleInputConfigs", configs);
|
Store.set("settings.paintScaleInputConfigs", configs);
|
||||||
@@ -159,7 +160,7 @@ const SettingsPaintScaleInputConfigsSet = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPaintScaleInputPathSet = async (
|
const SettingsPaintScaleInputPathSet = async (
|
||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
): Promise<string | null> => {
|
): Promise<string | null> => {
|
||||||
try {
|
try {
|
||||||
const mainWindow = getMainWindow();
|
const mainWindow = getMainWindow();
|
||||||
@@ -183,11 +184,13 @@ const SettingsPaintScaleInputPathSet = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPaintScaleOutputConfigsGet = async (
|
const SettingsPaintScaleOutputConfigsGet = (
|
||||||
_event?: IpcMainInvokeEvent,
|
_event?: IpcMainInvokeEvent,
|
||||||
): Promise<PaintScaleConfig[]> => {
|
): PaintScaleConfig[] => {
|
||||||
try {
|
try {
|
||||||
const configs = Store.get("settings.paintScaleOutputConfigs") as PaintScaleConfig[];
|
const configs = Store.get(
|
||||||
|
"settings.paintScaleOutputConfigs",
|
||||||
|
) as PaintScaleConfig[];
|
||||||
log.debug("Retrieved paint scale output configs:", configs);
|
log.debug("Retrieved paint scale output configs:", configs);
|
||||||
return configs || [];
|
return configs || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -197,8 +200,8 @@ const SettingsPaintScaleOutputConfigsGet = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPaintScaleOutputConfigsSet = async (
|
const SettingsPaintScaleOutputConfigsSet = async (
|
||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
configs: PaintScaleConfig[],
|
configs: PaintScaleConfig[],
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
Store.set("settings.paintScaleOutputConfigs", configs);
|
Store.set("settings.paintScaleOutputConfigs", configs);
|
||||||
@@ -211,7 +214,7 @@ const SettingsPaintScaleOutputConfigsSet = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SettingsPaintScaleOutputPathSet = async (
|
const SettingsPaintScaleOutputPathSet = async (
|
||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
): Promise<string | null> => {
|
): Promise<string | null> => {
|
||||||
try {
|
try {
|
||||||
const mainWindow = getMainWindow();
|
const mainWindow = getMainWindow();
|
||||||
|
|||||||
@@ -5,24 +5,43 @@ import axios from "axios";
|
|||||||
import { create } from "xmlbuilder2";
|
import { create } from "xmlbuilder2";
|
||||||
import { parseStringPromise } from "xml2js";
|
import { parseStringPromise } from "xml2js";
|
||||||
import store from "../../store/store";
|
import store from "../../store/store";
|
||||||
import client from "../../graphql/graphql-client";
|
import client, { getTokenFromRenderer } from "../../graphql/graphql-client";
|
||||||
import { PaintScaleConfig } from "../../../util/types/paintScale";
|
import { PaintScaleConfig } from "../../../util/types/paintScale";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {
|
||||||
|
PPG_DATA_QUERY_TYPED,
|
||||||
|
PpgDataQueryResult,
|
||||||
|
PpgDataQueryVariables,
|
||||||
|
} from "../../graphql/queries";
|
||||||
|
|
||||||
// PPG Input Handler
|
|
||||||
export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||||
try {
|
try {
|
||||||
log.info(
|
log.info(
|
||||||
`Polling input directory for PPG config ${config.id}: ${config.path}`,
|
`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
|
// Ensure archive and error directories exist
|
||||||
const archiveDir = path.join(config.path!, "archive");
|
const archiveDir = path.join(config.path!, "archive");
|
||||||
const errorDir = path.join(config.path!, "error");
|
const errorDir = path.join(config.path!, "error");
|
||||||
await fs.mkdir(archiveDir, { recursive: true });
|
try {
|
||||||
await fs.mkdir(errorDir, { recursive: true });
|
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
|
// Check for files
|
||||||
const files = await fs.readdir(config.path!);
|
const files = await fs.readdir(config.path!);
|
||||||
|
log.debug(`Found ${files.length} files in ${config.path}:`, files);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
// Only process XML files
|
// Only process XML files
|
||||||
if (!file.toLowerCase().endsWith(".xml")) {
|
if (!file.toLowerCase().endsWith(".xml")) {
|
||||||
@@ -30,8 +49,13 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filePath = path.join(config.path!, file);
|
const filePath = path.join(config.path!, file);
|
||||||
const stats = await fs.stat(filePath);
|
try {
|
||||||
if (!stats.isFile()) {
|
const stats = await fs.stat(filePath);
|
||||||
|
if (!stats.isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (statError) {
|
||||||
|
log.warn(`Failed to stat file ${filePath}:`, statError);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,40 +70,61 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate XML structure
|
// Validate XML structure
|
||||||
let xmlContent : BlobPart;
|
let xmlContent: BlobPart;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
xmlContent = await fs.readFile(filePath, "utf8");
|
xmlContent = await fs.readFile(filePath, "utf8");
|
||||||
await parseStringPromise(xmlContent);
|
await parseStringPromise(xmlContent);
|
||||||
|
log.debug(`Successfully validated XML for ${filePath}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Invalid XML in ${filePath}:`, error);
|
log.error(`Invalid XML in ${filePath}:`, error);
|
||||||
const timestamp = Date.now().toString(); // similar to DateTime.Now.Ticks in C#
|
const timestamp = dayjs().format("YYYYMMDD_HHmmss");
|
||||||
const errorPath = path.join(errorDir, `${timestamp}.xml`);
|
const originalFilename = path.basename(file, path.extname(file));
|
||||||
await fs.rename(filePath, errorPath);
|
const errorPath = path.join(
|
||||||
log.debug(`Moved invalid file to error: ${errorPath}`);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get authentication token
|
// Get authentication token
|
||||||
const token = (store.get("user") as any)?.stsTokenManager?.accessToken;
|
let token: string | null;
|
||||||
if (!token) {
|
try {
|
||||||
log.error(`No authentication token for file: ${filePath}`);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload file to API
|
// Upload file to API
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", new Blob([xmlContent]), path.basename(filePath));
|
formData.append("file", new Blob([xmlContent]), path.basename(filePath));
|
||||||
formData.append(
|
const shopId = (store.get("app.bodyshop") as any)?.shopname || "";
|
||||||
"shopId",
|
formData.append("shopId", shopId);
|
||||||
(store.get("app.bodyshop") as any)?.shopname || "",
|
log.debug(`Shop ID: ${shopId}`);
|
||||||
);
|
|
||||||
|
|
||||||
const baseURL = store.get("app.isTest")
|
const baseURL = store.get("app.isTest")
|
||||||
? import.meta.env.VITE_API_TEST_URL
|
? import.meta.env.VITE_API_TEST_URL
|
||||||
: import.meta.env.VITE_API_URL;
|
: import.meta.env.VITE_API_URL;
|
||||||
const finalUrl = `${baseURL}/mixdata/upload`;
|
const finalUrl = `${baseURL}/mixdata/upload`;
|
||||||
|
|
||||||
log.debug(`Uploading file to ${finalUrl}`);
|
log.debug(`Uploading file to ${finalUrl}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -88,23 +133,52 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
|||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
"Content-Type": "multipart/form-data",
|
"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) {
|
if (response.status === 200) {
|
||||||
log.info(`Successful upload of ${filePath}`);
|
log.info(`Successful upload of ${filePath}`);
|
||||||
// Move file to archive
|
// Move file to archive
|
||||||
const timestamp = Date.now().toString(); // generate new timestamp
|
const timestamp = dayjs().format("YYYYMMDD_HHmmss");
|
||||||
const archivePath = path.join(archiveDir, `${timestamp}.xml`);
|
const originalFilename = path.basename(file, path.extname(file));
|
||||||
await fs.rename(filePath, archivePath);
|
const archivePath = path.join(
|
||||||
log.debug(`Moved file to archive: ${archivePath}`);
|
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 {
|
} else {
|
||||||
log.error(
|
log.error(
|
||||||
`Failed to upload ${filePath}: ${response.status} ${response.statusText}`,
|
`Failed to upload ${filePath}: ${response.status} ${response.statusText}`,
|
||||||
response.data,
|
{ responseData: response.data },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
log.error(`Error uploading ${filePath}:`, error);
|
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) {
|
} catch (error) {
|
||||||
@@ -121,70 +195,16 @@ export async function ppgOutputHandler(
|
|||||||
|
|
||||||
await fs.mkdir(config.path!, { recursive: true });
|
await fs.mkdir(config.path!, { recursive: true });
|
||||||
|
|
||||||
const query = `
|
const variables: PpgDataQueryVariables = {
|
||||||
query PpgData($today: timestamptz!, $todayplus5: timestamptz!, $shopid: uuid!) {
|
today: dayjs().toISOString(),
|
||||||
bodyshops_by_pk(id:$shopid) {
|
todayplus5: dayjs().add(5, "day").toISOString(),
|
||||||
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,
|
shopid: (store.get("app.bodyshop") as any)?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = (await client.request(query, variables)) as any;
|
const response = await client.request<
|
||||||
|
PpgDataQueryResult,
|
||||||
|
PpgDataQueryVariables
|
||||||
|
>(PPG_DATA_QUERY_TYPED, variables);
|
||||||
const jobs = response.jobs ?? [];
|
const jobs = response.jobs ?? [];
|
||||||
|
|
||||||
const header = {
|
const header = {
|
||||||
@@ -197,18 +217,10 @@ export async function ppgOutputHandler(
|
|||||||
},
|
},
|
||||||
Transaction: {
|
Transaction: {
|
||||||
TransactionID: "",
|
TransactionID: "",
|
||||||
TransactionDate: (() => {
|
TransactionDate: dayjs().format("YYYY-MM-DD:HH:mm"),
|
||||||
const now = new Date();
|
|
||||||
const year = now.getFullYear();
|
|
||||||
const month = ("0" + (now.getMonth() + 1)).slice(-2);
|
|
||||||
const day = ("0" + now.getDate()).slice(-2);
|
|
||||||
const hours = ("0" + now.getHours()).slice(-2);
|
|
||||||
const minutes = ("0" + now.getMinutes()).slice(-2);
|
|
||||||
return `${year}-${month}-${day}:${hours}:${minutes}`;
|
|
||||||
})(),
|
|
||||||
},
|
},
|
||||||
Product: {
|
Product: {
|
||||||
Name: "ImEX Online",
|
Name: import.meta.env.VITE_COMPANY === "IMEX",
|
||||||
Version: "",
|
Version: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -220,7 +232,7 @@ export async function ppgOutputHandler(
|
|||||||
},
|
},
|
||||||
RepairOrders: {
|
RepairOrders: {
|
||||||
ROCount: jobs.length.toString(),
|
ROCount: jobs.length.toString(),
|
||||||
RO: jobs.map((job: any) => ({
|
RO: jobs.map((job) => ({
|
||||||
RONumber: job.ro_number || "",
|
RONumber: job.ro_number || "",
|
||||||
ROStatus: "Open",
|
ROStatus: "Open",
|
||||||
Customer: `${job.ownr_ln || ""}, ${job.ownr_fn || ""}`,
|
Customer: `${job.ownr_ln || ""}, ${job.ownr_fn || ""}`,
|
||||||
@@ -258,4 +270,3 @@ export async function ppgOutputHandler(
|
|||||||
log.error(`Error generating PPG output for config ${config.id}:`, error);
|
log.error(`Error generating PPG output for config ${config.id}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ const execPromise = promisify(exec);
|
|||||||
|
|
||||||
// Define the interval as a variable (in minutes)
|
// Define the interval as a variable (in minutes)
|
||||||
const KEEP_ALIVE_INTERVAL_MINUTES = 15;
|
const KEEP_ALIVE_INTERVAL_MINUTES = 15;
|
||||||
|
const taskName = "ShopPartnerKeepAlive";
|
||||||
|
|
||||||
export async function setupKeepAliveTask(): Promise<void> {
|
export async function setupKeepAliveTask(): Promise<void> {
|
||||||
const taskName = "ImEXShopPartnerKeepAlive";
|
|
||||||
const protocolUrl = "imexmedia://keep-alive";
|
const protocolUrl = "imexmedia://keep-alive";
|
||||||
// Use rundll32.exe to silently open the URL as a protocol
|
// Use rundll32.exe to silently open the URL as a protocol
|
||||||
const command = `rundll32.exe url.dll,OpenURL "${protocolUrl}"`;
|
const command = `rundll32.exe url.dll,OpenURL "${protocolUrl}"`;
|
||||||
@@ -30,7 +30,6 @@ export async function setupKeepAliveTask(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function isKeepAliveTaskInstalled(): Promise<boolean> {
|
export async function isKeepAliveTaskInstalled(): Promise<boolean> {
|
||||||
const taskName = "ImEXShopPartnerKeepAlive";
|
|
||||||
const maxRetries = 3;
|
const maxRetries = 3;
|
||||||
const retryDelay = 500; // 500ms delay between retries
|
const retryDelay = 500; // 500ms delay between retries
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Table,
|
Table,
|
||||||
Tag,
|
Tag,
|
||||||
|
theme,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { FC, useState } from "react";
|
import { JSX, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
PaintScaleConfig,
|
PaintScaleConfig,
|
||||||
@@ -24,8 +25,10 @@ import {
|
|||||||
} from "../../../../util/types/paintScale";
|
} from "../../../../util/types/paintScale";
|
||||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||||
|
|
||||||
const SettingsPaintScaleInputPaths: FC = () => {
|
const SettingsPaintScaleInputPaths = (): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { token } = theme.useToken(); // Access theme tokens
|
||||||
|
|
||||||
const {
|
const {
|
||||||
paintScaleConfigs,
|
paintScaleConfigs,
|
||||||
handleAddConfig,
|
handleAddConfig,
|
||||||
@@ -87,7 +90,7 @@ const SettingsPaintScaleInputPaths: FC = () => {
|
|||||||
placeholder={t("settings.labels.paintScalePath")}
|
placeholder={t("settings.labels.paintScalePath")}
|
||||||
disabled
|
disabled
|
||||||
style={{
|
style={{
|
||||||
borderColor: isValid ? "#52c41a" : "#d9d9d9",
|
borderColor: isValid ? token.colorSuccess : token.colorError, // Use semantic tokens
|
||||||
}}
|
}}
|
||||||
suffix={
|
suffix={
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -98,9 +101,9 @@ const SettingsPaintScaleInputPaths: FC = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isValid ? (
|
{isValid ? (
|
||||||
<CheckCircleFilled style={{ color: "#52c41a" }} />
|
<CheckCircleFilled style={{ color: token.colorSuccess }} />
|
||||||
) : (
|
) : (
|
||||||
<WarningFilled style={{ color: "#faad14" }} />
|
<WarningFilled style={{ color: token.colorError }} />
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,18 @@ import {
|
|||||||
FolderOpenFilled,
|
FolderOpenFilled,
|
||||||
WarningFilled,
|
WarningFilled,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Button, Card, Input, Modal, Select, Space, Table, Tag } from "antd";
|
import {
|
||||||
import { FC, useState } from "react";
|
Button,
|
||||||
|
Card,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
Select,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
theme,
|
||||||
|
} from "antd";
|
||||||
|
import { JSX, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
PaintScaleConfig,
|
PaintScaleConfig,
|
||||||
@@ -14,7 +24,8 @@ import {
|
|||||||
} from "../../../../util/types/paintScale";
|
} from "../../../../util/types/paintScale";
|
||||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||||
|
|
||||||
const SettingsPaintScaleOutputPaths: FC = () => {
|
const SettingsPaintScaleOutputPaths = (): JSX.Element => {
|
||||||
|
const { token } = theme.useToken();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
paintScaleConfigs,
|
paintScaleConfigs,
|
||||||
@@ -77,13 +88,13 @@ const SettingsPaintScaleOutputPaths: FC = () => {
|
|||||||
placeholder={t("settings.labels.paintScalePath")}
|
placeholder={t("settings.labels.paintScalePath")}
|
||||||
disabled
|
disabled
|
||||||
style={{
|
style={{
|
||||||
borderColor: isValid ? "#52c41a" : "#d9d9d9",
|
borderColor: isValid ? token.colorSuccess : token.colorError,
|
||||||
}}
|
}}
|
||||||
suffix={
|
suffix={
|
||||||
isValid ? (
|
isValid ? (
|
||||||
<CheckCircleFilled style={{ color: "#52c41a" }} />
|
<CheckCircleFilled style={{ color: token.colorSuccess }} />
|
||||||
) : (
|
) : (
|
||||||
<WarningFilled style={{ color: "#faad14" }} />
|
<WarningFilled style={{ color: token.colorError }} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user