diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 9b6494e67..6f0fbf098 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -13,6 +13,7 @@ let Client = require("ssh2-sftp-client"); const client = require("../graphql-client/graphql-client").client; const { sendServerEmail } = require("../email/sendemail"); + const AHDineroFormat = "0.00"; const AhDateFormat = "MMDDYYYY"; @@ -26,170 +27,176 @@ const ftpSetup = { password: process.env.AUTOHOUSE_PASSWORD, debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), algorithms: { - serverHostKey: ["ssh-rsa", "ssh-dss"] + serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] } }; +const allxmlsToUpload = []; +const allErrors = []; + exports.default = async (req, res) => { // Only process if in production environment. if (process.env.NODE_ENV !== "production") { res.sendStatus(403); return; } - - //Query for the List of Bodyshop Clients. - logger.log("autohouse-start", "DEBUG", "api", null, null); - const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); - - const specificShopIds = req.body.bodyshopIds; // ['uuid] - const { start, end, skipUpload } = req.body; //YYYY-MM-DD + // Only process if the appropriate token is provided. if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { res.sendStatus(401); return; } - const allxmlsToUpload = []; - const allErrors = []; try { - for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) { - logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname - }); - const erroredJobs = []; - try { - const { jobs, bodyshops_by_pk } = await client.request(queries.AUTOHOUSE_QUERY, { - bodyshopid: bodyshop.id, - start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"), - ...(end && { end: moment(end).endOf("day") }) - }); + //Query for the List of Bodyshop Clients. + logger.log("autohouse-start", "DEBUG", "api", null, null); + const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); + const specificShopIds = req.body.bodyshopIds; // ['uuid]; - const autoHouseObject = { - AutoHouseExport: { - RepairOrder: jobs.map((j) => - CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) { - erroredJobs.push({ job: job, error: error.toString() }); - }) - ) - } - }; + const { start, end, skipUpload } = req.body; //YYYY-MM-DD - if (erroredJobs.length > 0) { - logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, { - count: erroredJobs.length, - jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)) - }); - } + const batchSize = 10; - var ret = builder - .create( - { - // version: "1.0", - // encoding: "UTF-8", - //keepNullNodes: true, - }, - autoHouseObject - ) - .end({ allowEmptyTags: true }); + const shopsToProcess = + specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops; + logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null); - allxmlsToUpload.push({ - count: autoHouseObject.AutoHouseExport.RepairOrder.length, - xml: ret, - filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml` - }); - - logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, { - shopname: bodyshop.shopname - }); - } catch (error) { - //Error at the shop level. - logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, { - ...error - }); - - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - autuhouseid: bodyshop.autuhouseid, - fatal: true, - errors: [error.toString()] - }); - } finally { - allErrors.push({ - bodyshopid: bodyshop.id, - imexshopid: bodyshop.imexshopid, - autohouseid: bodyshop.autohouseid, - errors: erroredJobs.map((ej) => ({ - ro_number: ej.job?.ro_number, - jobid: ej.job?.id, - error: ej.error - })) - }); - } - } - - if (skipUpload) { - for (const xmlObj of allxmlsToUpload) { - fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); - } - - res.json(allxmlsToUpload); - sendServerEmail({ - subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, - text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} - Uploaded: ${JSON.stringify( - allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), - null, - 2 - )} - ` - }); + if (shopsToProcess.length === 0) { + logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null); + res.sendStatus(200); return; } - let sftp = new Client(); - sftp.on("error", (errors) => - logger.log("autohouse-sftp-error", "ERROR", "api", null, { - ...errors - }) - ); + for (let i = 0; i < shopsToProcess.length; i += batchSize) { + const batch = shopsToProcess.slice(i, i + batchSize); + await processBatch(batch, start, end); + + if (skipUpload) { + for (const xmlObj of allxmlsToUpload) { + fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml); + } + } else { + await uploadViaSFTP(allxmlsToUpload); + } + + sendServerEmail({ + subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, + text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} + Uploaded: ${JSON.stringify( + allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })), + null, + 2 + )}` + }); + + logger.log("autohouse-end", "DEBUG", "api", null, null); + res.sendStatus(200); + } + } catch (error) { + logger.log("autohouse-shopsToProcess-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); + res.status(500).json({ error: error.message, stack: error.stack }); + } +}; + +async function processBatch(batch, start, end) { + for (const bodyshop of batch) { + const erroredJobs = []; try { - //Connect to the FTP and upload all. + logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); - await sftp.connect(ftpSetup); + const { jobs, bodyshops_by_pk } = await client.request(queries.AUTOHOUSE_QUERY, { + bodyshopid: bodyshop.id, + start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"), + ...(end && { end: moment(end).endOf("day") }) + }); - for (const xmlObj of allxmlsToUpload) { - logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { - filename: xmlObj.filename - }); + const autoHouseObject = { + AutoHouseExport: { + RepairOrder: jobs.map((j) => + CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) { + erroredJobs.push({ job: job, error: error.toString() }); + }) + ) + } + }; - const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`); - logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, { - uploadResult + if (erroredJobs.length > 0) { + logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, { + count: erroredJobs.length, + jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)) }); } - //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml + const ret = builder.create({}, autoHouseObject).end({ allowEmptyTags: true }); + + allxmlsToUpload.push({ + count: autoHouseObject.AutoHouseExport.RepairOrder.length, + xml: ret, + filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml` + }); + + logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, { + shopname: bodyshop.shopname + }); } catch (error) { - logger.log("autohouse-sftp-error", "ERROR", "api", null, { - ...error + //Error at the shop level. + logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack }); + + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + autuhouseid: bodyshop.autuhouseid, + fatal: true, + errors: [error.toString()] }); } finally { - sftp.end(); + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + autuhouseid: bodyshop.autuhouseid, + errors: erroredJobs.map((ej) => ({ + ro_number: ej.job?.ro_number, + jobid: ej.job?.id, + error: ej.error + })) + }); } - sendServerEmail({ - subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, - text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))} - Uploaded: ${JSON.stringify( - allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })), - null, - 2 - )} - ` - }); - res.sendStatus(200); - } catch (error) { - res.status(200).json(error); } -}; +} + +async function uploadViaSFTP(allxmlsToUpload) { + const sftp = new Client(); + sftp.on("error", (errors) => + logger.log("autohouse-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) + ); + try { + //Connect to the FTP and upload all. + await sftp.connect(ftpSetup); + + for (const xmlObj of allxmlsToUpload) { + try { + logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { filename: xmlObj.filename }); + xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); + logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, { + filename: xmlObj.filename, + result: xmlObj.result + }); + } catch (error) { + logger.log("autohouse-sftp-upload-error", "ERROR", "api", null, { + filename: xmlObj.filename, + error: error.message, + stack: error.stack + }); + throw error; + } + } + } catch (error) { + logger.log("autohouse-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); + throw error; + } finally { + sftp.end(); + } +} const CreateRepairOrderTag = (job, errorCallback) => { //Level 2 @@ -287,8 +294,8 @@ const CreateRepairOrderTag = (job, errorCallback) => { InsuranceCo: job.ins_co_nm || "", CompanyName: job.ins_co_nm || "", Address: job.ins_addr1 || "", - City: job.ins_addr1 || "", - State: job.ins_city || "", + City: job.ins_city || "", + State: job.ins_st || "", Zip: job.ins_zip || "", Phone: job.ins_ph1 || "", Fax: job.ins_fax || "",