diff --git a/package-lock.json b/package-lock.json index 758e375..b920d09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bodyshop-desktop", - "version": "0.0.1-alpha.6", + "version": "0.0.1-alpha.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bodyshop-desktop", - "version": "0.0.1-alpha.6", + "version": "0.0.1-alpha.7", "hasInstallScript": true, "dependencies": { "@apollo/client": "^3.13.6", @@ -14,11 +14,14 @@ "@electron-toolkit/utils": "^4.0.0", "@sentry/electron": "^6.5.0", "@sentry/vite-plugin": "^3.3.1", + "axios": "^1.9.0", "dayjs": "^1.11.13", "electron-log": "^5.3.3", "electron-store": "^8.2.0", "electron-updater": "^6.6.2", - "winax": "^3.6.2" + "node-cron": "^3.0.3", + "winax": "^3.6.2", + "xmlbuilder2": "^3.1.1" }, "devDependencies": { "@ant-design/v5-patch-for-react-19": "^1.0.3", @@ -31,6 +34,7 @@ "@types/express": "^5.0.1", "@types/lodash": "^4.17.16", "@types/node": "^22.14.0", + "@types/node-cron": "^3.0.11", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.3.4", @@ -2695,6 +2699,54 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/@oozcitak/dom": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz", + "integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==", + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "license": "MIT", + "dependencies": { + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -4459,6 +4511,13 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/pg": { "version": "8.6.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", @@ -5627,7 +5686,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -5666,6 +5724,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6138,7 +6207,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6399,7 +6467,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -6943,7 +7010,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -7189,7 +7255,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -7706,7 +7771,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7716,7 +7780,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7754,7 +7817,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -7767,7 +7829,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -8144,6 +8205,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -8528,6 +8602,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -8578,7 +8672,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -8594,7 +8687,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -8604,7 +8696,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8798,7 +8889,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -8823,7 +8913,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -8977,7 +9066,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9117,7 +9205,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -9130,7 +9217,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -10561,7 +10647,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11026,6 +11111,18 @@ "node": ">=10" } }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -14773,6 +14870,15 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -15159,6 +15265,49 @@ "node": ">=8.0" } }, + "node_modules/xmlbuilder2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz", + "integrity": "sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==", + "license": "MIT", + "dependencies": { + "@oozcitak/dom": "1.15.10", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "js-yaml": "3.14.1" + }, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/xmlbuilder2/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/xmlbuilder2/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/xmlbuilder2/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index f751d16..3f6fe04 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,14 @@ "@electron-toolkit/utils": "^4.0.0", "@sentry/electron": "^6.5.0", "@sentry/vite-plugin": "^3.3.1", + "axios": "^1.9.0", "dayjs": "^1.11.13", "electron-log": "^5.3.3", "electron-store": "^8.2.0", "electron-updater": "^6.6.2", - "winax": "^3.6.2" + "node-cron": "^3.0.3", + "winax": "^3.6.2", + "xmlbuilder2": "^3.1.1" }, "devDependencies": { "@ant-design/v5-patch-for-react-19": "^1.0.3", @@ -46,6 +49,7 @@ "@types/express": "^5.0.1", "@types/lodash": "^4.17.16", "@types/node": "^22.14.0", + "@types/node-cron": "^3.0.11", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.3.4", diff --git a/src/main/ipc/ipcMainConfig.ts b/src/main/ipc/ipcMainConfig.ts index e9273bc..830b5cd 100644 --- a/src/main/ipc/ipcMainConfig.ts +++ b/src/main/ipc/ipcMainConfig.ts @@ -1,4 +1,3 @@ -// main/ipcMainConfig.ts import { app, ipcMain } from "electron"; import log from "electron-log/main"; import { autoUpdater } from "electron-updater"; @@ -8,6 +7,14 @@ 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, @@ -15,19 +22,84 @@ import { SettingsWatchedFilePathsRemove, SettingsWatcherPollingGet, SettingsWatcherPollingSet, - SettingEmsOutFilePathSet, - SettingEmsOutFilePathGet, - SettingsPaintScaleInputConfigsGet, - SettingsPaintScaleInputConfigsSet, - SettingsPaintScaleInputPathSet, - SettingsPaintScaleOutputConfigsGet, - SettingsPaintScaleOutputConfigsSet, - SettingsPaintScaleOutputPathSet, } from "./ipcMainHandler.settings"; import { ipcMainHandleAuthStateChanged, ipMainHandleResetPassword, } from "./ipcMainHandler.user"; +import cron from "node-cron"; +import fs from "fs/promises"; +import axios from "axios"; +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<{ + 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; + }>; +} // Log all IPC messages and their payloads const logIpcMessages = (): void => { @@ -39,10 +111,10 @@ const logIpcMessages = (): void => { } ipcMain.on(messageType, (event, payload) => { log.info( - `%c[IPC Main]%c${messageType}`, - "color: red", - "color: green", - payload, + `%c[IPC Main]%c${messageType}`, + "color: red", + "color: green", + payload, ); if (originalHandler) { originalHandler(event, payload); @@ -51,8 +123,354 @@ const logIpcMessages = (): void => { }); }; +// Input handler map +const inputTypeHandlers: Record< + PaintScaleType, + (config: PaintScaleConfig) => Promise +> = { + [PaintScaleType.PPG]: async (config: PaintScaleConfig) => { + 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) { + 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 response = await axios.post( + "https://your-api-base-url/mixdata/upload", + 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 +}; + +// Output handler map +const outputTypeHandlers: Record< + PaintScaleType, + (config: PaintScaleConfig) => Promise +> = { + [PaintScaleType.PPG]: async (config: PaintScaleConfig) => { + try { + log.info(`Generating PPG output for config ${config.id}: ${config.path}`); + + // Ensure output directory exists + await fs.mkdir(config.path!, { recursive: true }); + + // GraphQL query for jobs + const query = ` + query PpgData($today: date!, $todayplus5: date!, $shopid: uuid!) { + bodyshops_by_pk(id: $shopid) { + imexshopid + shopname + } + jobs(where: { + _and: [ + { shop_id: { _eq: $shopid } }, + { _or: [ + { status: { _eq: "Active" } }, + { scheduled_date: { _gte: $today, _lte: $todayplus5 } } + ] } + ] + }) { + ro_number + ownr_ln + ownr_fn + plate_no + v_vin + v_model_yr + v_make_desc + v_model_desc + vehicle { + v_paint_codes { + paint_cd1 + } + } + larhrs_aggregate: estimate_lines_aggregate(where: { line_type: { _eq: "Paint" } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + ins_co_nm + est_ct_ln + est_ct_fn + job_totals { + rates { + mapa { + total { + amount + } + } + } + totals { + subtotal { + amount + } + } + } + rate_mapa + labhrs_aggregate: estimate_lines_aggregate(where: { line_type: { _eq: "Body" } }) { + aggregate { + sum { + mod_lb_hrs + } + } + } + rate_lab + } + } + `; + + const variables = { + today: new Date().toISOString().split("T")[0], + todayplus5: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000) + .toISOString() + .split("T")[0], + shopid: (store.get("app.bodyshop") as BodyShop)?.id, + }; + + const response = (await client.request( + query, + variables, + )) as GraphQLResponse; + + // Generate XML + const doc = create({ version: "1.0" }) + .ele("PPG") + .ele("Header") + .ele("Protocol") + .ele("Message") + .txt("PaintShopInterface") + .up() + .ele("Name") + .txt("PPG") + .up() + .ele("Version") + .txt("1.5.0") + .up() + .up() + .ele("Transaction") + .ele("TransactionID") + .txt("") + .up() + .ele("TransactionDate") + .txt(new Date().toISOString().replace("T", " ").substring(0, 19)) + .up() + .up() + .ele("Product") + .ele("Name") + .txt("ImEX Online") + .up() + .ele("Version") + .txt("") + .up() + .up() + .up() + .ele("DataInterface") + .ele("ROData") + .ele("ShopInfo") + .ele("ShopID") + .txt(response.bodyshops_by_pk?.imexshopid || "") + .up() + .ele("ShopName") + .txt(response.bodyshops_by_pk?.shopname || "") + .up() + .up() + .ele("RepairOrders") + .ele("ROCount") + .txt((response.jobs?.length || 0).toString()) + .up() + .up() + .up() + .up(); + + const repairOrders = (doc.root() as any).findOne("RepairOrders"); + (response as GraphQLResponse).jobs?.forEach((job: any) => { + repairOrders + .ele("RO") + .ele("RONumber") + .txt(job.ro_number || "") + .up() + .ele("ROStatus") + .txt("Open") + .up() + .ele("Customer") + .txt(`${job.ownr_ln || ""}, ${job.ownr_fn || ""}`) + .up() + .ele("ROPainterNotes") + .txt("") + .up() + .ele("LicensePlateNum") + .txt(job.plate_no || "") + .up() + .ele("VIN") + .txt(job.v_vin || "") + .up() + .ele("ModelYear") + .txt(job.v_model_yr || "") + .up() + .ele("MakeDesc") + .txt(job.v_make_desc || "") + .up() + .ele("ModelName") + .txt(job.v_model_desc || "") + .up() + .ele("OEMColorCode") + .txt(job.vehicle?.v_paint_codes?.paint_cd1 || "") + .up() + .ele("RefinishLaborHours") + .txt(job.larhrs_aggregate?.aggregate?.sum?.mod_lb_hrs || 0) + .up() + .ele("InsuranceCompanyName") + .txt(job.ins_co_nm || "") + .up() + .ele("EstimatorName") + .txt(`${job.est_ct_ln || ""}, ${job.est_ct_fn || ""}`) + .up() + .ele("PaintMaterialsRevenue") + .txt( + ((job.job_totals?.rates?.mapa?.total?.amount || 0) / 100).toFixed( + 2, + ), + ) + .up() + .ele("PaintMaterialsRate") + .txt(job.rate_mapa || 0) + .up() + .ele("BodyHours") + .txt(job.labhrs_aggregate?.aggregate?.sum?.mod_lb_hrs || 0) + .up() + .ele("BodyLaborRate") + .txt(job.rate_lab || 0) + .up() + .ele("TotalCostOfRepairs") + .txt( + ((job.job_totals?.totals?.subtotal?.amount || 0) / 100).toFixed(2), + ) + .up(); + }); + + // Save XML to file + const xmlString = doc.end({ prettyPrint: true }); + const outputPath = path.join(config.path!, "PPGPaint.xml"); + await fs.writeFile(outputPath, xmlString); + log.info(`Saved PPG output XML to ${outputPath}`); + } catch (error) { + log.error(`Error generating PPG output for config ${config.id}:`, error); + } + }, + // 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}s`, + ); + }); +}; + +// 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}s`, + ); + }); +}; + +// Existing IPC handlers... + ipcMain.on(ipcTypes.toMain.test, () => - console.log("** Verify that ipcMain is loaded and working."), + console.log("** Verify that ipcMain is loaded and working."), ); // Auth handler @@ -80,63 +498,86 @@ if (import.meta.env.DEV) { // Settings Handlers ipcMain.handle( - ipcTypes.toMain.settings.filepaths.get, - SettingsWatchedFilePathsGet, + ipcTypes.toMain.settings.filepaths.get, + SettingsWatchedFilePathsGet, ); ipcMain.handle( - ipcTypes.toMain.settings.filepaths.add, - SettingsWatchedFilePathsAdd, + ipcTypes.toMain.settings.filepaths.add, + SettingsWatchedFilePathsAdd, ); ipcMain.handle( - ipcTypes.toMain.settings.filepaths.remove, - SettingsWatchedFilePathsRemove, + ipcTypes.toMain.settings.filepaths.remove, + SettingsWatchedFilePathsRemove, ); ipcMain.handle( - ipcTypes.toMain.settings.watcher.getpolling, - SettingsWatcherPollingGet, + ipcTypes.toMain.settings.watcher.getpolling, + SettingsWatcherPollingGet, ); ipcMain.handle( - ipcTypes.toMain.settings.watcher.setpolling, - SettingsWatcherPollingSet, + 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, + ipcTypes.toMain.settings.getEmsOutFilePath, + SettingEmsOutFilePathGet, ); ipcMain.handle( - ipcTypes.toMain.settings.setEmsOutFilePath, - SettingEmsOutFilePathSet, + ipcTypes.toMain.settings.setEmsOutFilePath, + SettingEmsOutFilePathSet, ); // Paint Scale Input Settings Handlers ipcMain.handle( - ipcTypes.toMain.settings.paintScale.getInputConfigs, - SettingsPaintScaleInputConfigsGet, + ipcTypes.toMain.settings.paintScale.getInputConfigs, + SettingsPaintScaleInputConfigsGet, ); ipcMain.handle( - ipcTypes.toMain.settings.paintScale.setInputConfigs, - SettingsPaintScaleInputConfigsSet, + ipcTypes.toMain.settings.paintScale.setInputConfigs, + SettingsPaintScaleInputConfigsSet, ); ipcMain.handle( - ipcTypes.toMain.settings.paintScale.setInputPath, - SettingsPaintScaleInputPathSet, + ipcTypes.toMain.settings.paintScale.setInputPath, + SettingsPaintScaleInputPathSet, ); // Paint Scale Output Settings Handlers ipcMain.handle( - ipcTypes.toMain.settings.paintScale.getOutputConfigs, - SettingsPaintScaleOutputConfigsGet, + ipcTypes.toMain.settings.paintScale.getOutputConfigs, + SettingsPaintScaleOutputConfigsGet, ); ipcMain.handle( - ipcTypes.toMain.settings.paintScale.setOutputConfigs, - SettingsPaintScaleOutputConfigsSet, + ipcTypes.toMain.settings.paintScale.setOutputConfigs, + SettingsPaintScaleOutputConfigsSet, ); ipcMain.handle( - ipcTypes.toMain.settings.paintScale.setOutputPath, - SettingsPaintScaleOutputPathSet, + 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, () => { @@ -145,16 +586,22 @@ ipcMain.handle(ipcTypes.toMain.user.getActiveShop, () => { // Watcher Handlers ipcMain.on(ipcTypes.toMain.watcher.start, () => { - StartWatcher(); + StartWatcher().catch((error) => { + log.error("Error starting watcher:", error); + }); }); ipcMain.on(ipcTypes.toMain.watcher.stop, () => { - StopWatcher(); + StopWatcher().catch((error) => { + log.error("Error stopping watcher:", error); + }); }); ipcMain.on(ipcTypes.toMain.updates.download, () => { log.info("Download update requested from renderer."); - autoUpdater.downloadUpdate(); + autoUpdater.downloadUpdate().catch((error) => { + log.error("Error downloading update:", error); + }); }); logIpcMessages(); \ No newline at end of file diff --git a/src/main/ipc/paintScale.ts b/src/main/ipc/paintScale.ts new file mode 100644 index 0000000..e2bc4f2 --- /dev/null +++ b/src/main/ipc/paintScale.ts @@ -0,0 +1,17 @@ +// src/types/paintScale.ts +export enum PaintScaleType { + PPG = 'PPG', + // Add other types as needed +} + +export interface PaintScaleConfig { + id: string; + path: string | null; + type: PaintScaleType; + pollingInterval: number; +} + +export const paintScaleTypeOptions = [ + { value: PaintScaleType.PPG, label: 'PPG' }, + // Add other options as needed +]; \ No newline at end of file diff --git a/src/renderer/src/components/Settings/PaintScale/usePaintScaleConfig.ts b/src/renderer/src/components/Settings/PaintScale/usePaintScaleConfig.ts index e2859bb..64462e7 100644 --- a/src/renderer/src/components/Settings/PaintScale/usePaintScaleConfig.ts +++ b/src/renderer/src/components/Settings/PaintScale/usePaintScaleConfig.ts @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; -import { PaintScaleConfig, PaintScaleType } from './types'; import ipcTypes from '../../../../../util/ipcTypes.json'; +import {PaintScaleConfig, PaintScaleType} from "./types"; type ConfigType = 'input' | 'output'; @@ -28,7 +28,7 @@ export const usePaintScaleConfig = (configType: ConfigType) => { // Ensure all configs have a pollingInterval and type (for backward compatibility) const updatedConfigs = configs.map(config => ({ ...config, - pollingInterval: config.pollingInterval || 1440, // Default to 1440 seconds if not set + pollingInterval: config.pollingInterval || 1440, // Default to 1440 seconds type: config.type || PaintScaleType.PPG, // Default type if missing })); setPaintScaleConfigs(updatedConfigs || []); @@ -38,10 +38,18 @@ export const usePaintScaleConfig = (configType: ConfigType) => { }); }, [getConfigsMethod]); - // Save configs to store + // 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); }); diff --git a/src/util/ipcTypes.json b/src/util/ipcTypes.json index a910e75..a53cd32 100644 --- a/src/util/ipcTypes.json +++ b/src/util/ipcTypes.json @@ -34,7 +34,9 @@ "setInputPath": "toMain_settings_paintScale_setInputPath", "getOutputConfigs": "toMain_settings_paintScale_getOutputConfigs", "setOutputConfigs": "toMain_settings_paintScale_setOutputConfigs", - "setOutputPath": "toMain_settings_paintScale_setOutputPath" + "setOutputPath": "toMain_settings_paintScale_setOutputPath", + "updateInputCron": "toMain_settings_paintScale_updateInputCron", + "updateOutputCron": "toMain_settings_paintScale_updateOutputCron" } }, "user": {