From 3b992edc21ca7c25ad1158f80f710d34dd4e4a4c Mon Sep 17 00:00:00 2001 From: Patrick Fic <> Date: Tue, 20 Jul 2021 16:25:08 -0700 Subject: [PATCH] IO-594 Create schedulable AH export. --- .platform/nginx/conf.d/proxy.conf | 2 +- bodyshop_translations.babel | 21 +++ .../email-overlay/email-overlay.component.jsx | 4 +- client/src/translations/en_us/common.json | 1 + client/src/translations/es/common.json | 1 + client/src/translations/fr/common.json | 1 + package.json | 1 + server.js | 2 +- server/data/autohouse.js | 124 +++++++++++++----- server/graphql-client/queries.js | 78 +++++++---- yarn.lock | 63 ++++++++- 11 files changed, 233 insertions(+), 65 deletions(-) diff --git a/.platform/nginx/conf.d/proxy.conf b/.platform/nginx/conf.d/proxy.conf index b68f5618f..ae3fb47f6 100644 --- a/.platform/nginx/conf.d/proxy.conf +++ b/.platform/nginx/conf.d/proxy.conf @@ -1 +1 @@ -client_max_body_size 15M; \ No newline at end of file +client_max_body_size 50M; \ No newline at end of file diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 46f5e9f32..e407d53ed 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -11700,6 +11700,27 @@ + + pdfcopywillbeattached + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + preview false diff --git a/client/src/components/email-overlay/email-overlay.component.jsx b/client/src/components/email-overlay/email-overlay.component.jsx index 3f6f8f72c..756f7b45f 100644 --- a/client/src/components/email-overlay/email-overlay.component.jsx +++ b/client/src/components/email-overlay/email-overlay.component.jsx @@ -1,5 +1,5 @@ import { UploadOutlined } from "@ant-design/icons"; -import { Divider, Form, Input, Select, Tabs, Upload } from "antd"; +import { Divider, Form, Input, Select, Tabs, Typography, Upload } from "antd"; import React from "react"; import { useTranslation } from "react-i18next"; import EmailDocumentsComponent from "../email-documents/email-documents.component"; @@ -37,6 +37,8 @@ export default function EmailOverlayComponent({ form, selectedMediaState }) { {t("emails.labels.preview")} + {t("emails.labels.pdfcopywillbeattached")} + {() => { return ( diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 8c444c2cd..0a7420617 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -743,6 +743,7 @@ "attachments": "Attachments", "documents": "Documents", "generatingemail": "Generating email...", + "pdfcopywillbeattached": "A PDF copy of this email will be attached when it is sent.", "preview": "Email Preview" }, "successes": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index d8eb746a4..99407b22c 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -743,6 +743,7 @@ "attachments": "", "documents": "", "generatingemail": "", + "pdfcopywillbeattached": "", "preview": "" }, "successes": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 811471037..003fb315f 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -743,6 +743,7 @@ "attachments": "", "documents": "", "generatingemail": "", + "pdfcopywillbeattached": "", "preview": "" }, "successes": { diff --git a/package.json b/package.json index 3ef5a1d82..0c15fb68e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "phone": "^2.4.20", "soap": "^0.39.0", "socket.io": "^4.1.2", + "ssh2-sftp-client": "^7.0.0", "stripe": "^8.148.0", "twilio": "^3.62.0", "xmlbuilder2": "^2.4.1" diff --git a/server.js b/server.js index 70186a202..d4122df1e 100644 --- a/server.js +++ b/server.js @@ -139,7 +139,7 @@ app.post("/qbo/authorize", qbo.authorize); app.get("/qbo/callback", qbo.callback); var data = require("./server/data/data"); -app.get("/data/ah", data.autohouse); +app.post("/data/ah", data.autohouse); var ioevent = require("./server/ioevent/ioevent"); app.post("/ioevent", ioevent.default); diff --git a/server/data/autohouse.js b/server/data/autohouse.js index 765e787cf..64a7bf6cd 100644 --- a/server/data/autohouse.js +++ b/server/data/autohouse.js @@ -11,42 +11,109 @@ require("dotenv").config({ `.env.${process.env.NODE_ENV || "development"}` ), }); +let Client = require("ssh2-sftp-client"); const client = require("../graphql-client/graphql-client").client; const AHDineroFormat = "0.00"; const AhDateFormat = "MMDDYYYY"; + +const repairOpCodes = ["OP4", "OP9", "OP10"]; +const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"]; + +const ftpSetup = { + host: process.env.AUTOHOUSE_HOST, + port: process.env.AUTOHOUSE_PORT, + username: process.env.AUTOHOUSE_USER, + password: process.env.AUTOHOUSE_PASSWORD, + //debug: console.log, +}; + exports.default = async (req, res) => { - //Get Client Dataset. - const { jobs } = await client.request(queries.AUTOHOUSE_QUERY); + //Query for the List of Bodyshop Clients. + const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); - const erroredJobs = []; + const allxmlsToUpload = []; + const allErrors = []; - const autoHouseObject = { - AutoHouseExport: { - RepairOrder: jobs.map((j) => - CreateRepairOrderTag(j, (job, error) => { - erroredJobs.push({ job, error }); + for (const bodyshop of bodyshops) { + const erroredJobs = []; + try { + const { jobs } = await client.request(queries.AUTOHOUSE_QUERY, { + bodyshopid: bodyshop.id, + }); + + const autoHouseObject = { + AutoHouseExport: { + RepairOrder: jobs.map((j) => + CreateRepairOrderTag({ ...j, bodyshop }, function ({ job, error }) { + erroredJobs.push({ job: job, error: error.toString() }); + }) + ), + }, + }; + + console.log( + "***Number of Failed jobs***: ", + erroredJobs.length, + JSON.stringify(erroredJobs.map((j) => j.job.ro_number)) + ); + + var ret = builder + .create(autoHouseObject, { + version: "1.0", + encoding: "UTF-8", }) - ), - }, - }; + .end({ pretty: true, allowEmptyTags: true }); - console.log( - "***Number of Failed jobs***: ", - erroredJobs.length, - JSON.stringify(erroredJobs.map((x) => x.error)) - ); - var ret = builder - .create(autoHouseObject, { - version: "1.0", - encoding: "UTF-8", - }) - .end({ pretty: true, allowEmptyTags: true }); + allxmlsToUpload.push({ + xml: ret, + filename: `IM_${bodyshop.imexshopid}_${moment().format( + "DDMMYYYY_HHMMSS" + )}.xml`, + }); + } catch (error) { + //Error at the shop level. + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + fatal: true, + errors: [error.toString()], + }); + } finally { + allErrors.push({ + bodyshopid: bodyshop.id, + imexshopid: bodyshop.imexshopid, + errors: erroredJobs, + }); + } + } - //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml - res.type("application/xml"); - //res.sendFile(ret); - res.send(ret); + let sftp = new Client(); + try { + //Connect to the FTP and upload all. + + await sftp.connect(ftpSetup); + + for (const xmlObj of allxmlsToUpload) { + console.log("Uploading", xmlObj.filename); + const uploadResult = await sftp.put( + Buffer.from(xmlObj.xml), + `/${xmlObj.filename}` + ); + console.log( + "🚀 ~ file: autohouse.js ~ line 94 ~ uploadResult", + uploadResult + ); + } + + //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml + } catch (error) { + console.log("Error when connecting to FTP", error); + } finally { + sftp.end(); + } + + res.sendStatus(200); }; const CreateRepairOrderTag = (job, errorCallback) => { @@ -410,7 +477,7 @@ const CreateRepairOrderTag = (job, errorCallback) => { return ret; } catch (error) { console.log("Error calculating job", error); - errorCallback(job, error); + errorCallback({ job, error }); } }; @@ -611,6 +678,3 @@ const generateNullDetailLine = () => { EstimateAmount: null, }; }; - -const repairOpCodes = ["OP4", "OP9", "OP10"]; -const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"]; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 32db12ae6..62a7eb4d9 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -357,8 +357,8 @@ exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employee } }`; -exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { - jobs(where: {_and: [{updated_at: {_gt: $start}}, {bodyshop: {autohouseid: {_is_null: false}}}]}) { +exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshopid: uuid!) { + jobs(where: {_and: [{updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) { id ro_number status @@ -433,10 +433,10 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { md_ro_statuses md_order_statuses autohouseid - md_responsibility_centers - jc_hourly_rates + md_responsibility_centers + jc_hourly_rates } - joblines (where:{removed: {_eq:false}}){ + joblines(where: {removed: {_eq: false}}) { id line_no status @@ -452,40 +452,40 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { part_type oem_partno lbr_op - profitcenter_part - profitcenter_labor - billlines (order_by:{bill:{date:desc_nulls_last}}) { + profitcenter_part + profitcenter_labor + billlines(order_by: {bill: {date: desc_nulls_last}}) { actual_cost actual_price quantity bill { - vendor{ + vendor { name } invoice_number } } - - } bills { - id - federal_tax_rate - local_tax_rate - state_tax_rate - is_credit_memo - billlines { - actual_cost - cost_center - id - quantity - } - } - timetickets { - id - rate + } + bills { + id + federal_tax_rate + local_tax_rate + state_tax_rate + is_credit_memo + billlines { + actual_cost cost_center - actualhrs - productivehrs + id + quantity } + } + timetickets { + id + rate + cost_center + actualhrs + productivehrs + } area_of_damage employee_prep_rel { first_name @@ -507,6 +507,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) { } } } + `; exports.UPDATE_JOB = ` @@ -906,3 +907,24 @@ exports.INSERT_IOEVENT = ` mutation INSERT_IOEVENT($event: ioevents_insert_input } } `; + +exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS { + bodyshops(where: {autohouseid: {_is_null: false}}){ + + id + shopname + address1 + city + state + zip_post + country + phone + md_ro_statuses + md_order_statuses + autohouseid + md_responsibility_centers + jc_hourly_rates + imexshopid + } +} +`; diff --git a/yarn.lock b/yarn.lock index 53ba85566..52ad9c473 100644 --- a/yarn.lock +++ b/yarn.lock @@ -529,7 +529,7 @@ asap@^2.0.0: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asn1@~0.2.3: +asn1@^0.2.4, asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== @@ -644,7 +644,7 @@ batch@^0.6.1: resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= -bcrypt-pbkdf@^1.0.0: +bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= @@ -915,6 +915,16 @@ concat-stream@^1.4.7: readable-stream "^2.2.2" typedarray "^0.0.6" +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + concurrently@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.0.2.tgz#4ecdfc78a72a6f626a3a5d3c2a7a81962f3663e3" @@ -999,6 +1009,13 @@ cors@2.8.5, cors@~2.8.5: object-assign "^4" vary "^1" +cpu-features@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.2.tgz#9f636156f1155fd04bdbaa028bb3c2fbef3cea7a" + integrity sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA== + dependencies: + nan "^2.14.1" + cross-fetch@^3.0.6: version "3.1.4" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" @@ -1345,6 +1362,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2839,6 +2861,11 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +nan@^2.14.1, nan@^2.14.2: + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -3136,6 +3163,14 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + protobufjs@^6.10.2, protobufjs@^6.8.6: version "6.10.2" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" @@ -3347,7 +3382,7 @@ readable-stream@2, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stre string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -3431,7 +3466,7 @@ retry-request@^4.0.0, retry-request@^4.1.1: dependencies: debug "^4.1.1" -retry@0.12.0: +retry@0.12.0, retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= @@ -3766,6 +3801,26 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +ssh2-sftp-client@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ssh2-sftp-client/-/ssh2-sftp-client-7.0.0.tgz#38c3420319156d030a80ac9db1df7459d2ba42a0" + integrity sha512-o++ryEeSbAQ6GzjuXs6BHnST6zsoWUZYt9cLy6XQ4+WdL6jNuU6UjyQzvg1J3IgN4LpCSAI+9EyTeKeIb0AfSQ== + dependencies: + concat-stream "^2.0.0" + promise-retry "^2.0.1" + ssh2 "^1.1.0" + +ssh2@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.1.0.tgz#43dd24930e15e317687f519d6b40270d9cd00d00" + integrity sha512-CidQLG2ZacoT0Z7O6dOyisj4JdrOrLVJ4KbHjVNz9yI1vO08FAYQPcnkXY9BP8zeYo+J/nBgY6Gg4R7w4WFWtg== + dependencies: + asn1 "^0.2.4" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "0.0.2" + nan "^2.14.2" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"