Merged in feature/IO-3386-carfax-rps (pull request #2606)
Feature/IO-3386 carfax rps Approved-by: Patrick Fic
This commit is contained in:
@@ -207,6 +207,9 @@ services:
|
|||||||
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key
|
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key
|
||||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rome-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rps-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
"
|
"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ services:
|
|||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-job-totals --create-bucket-configuration LocationConstraint=ca-central-1
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-job-totals --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket parts-estimates --create-bucket-configuration LocationConstraint=ca-central-1
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket parts-estimates --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rome-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket rps-carfax-uploads --create-bucket-configuration LocationConstraint=ca-central-1
|
||||||
"
|
"
|
||||||
# Node App: The Main IMEX API
|
# Node App: The Main IMEX API
|
||||||
node-app:
|
node-app:
|
||||||
|
|||||||
@@ -15,6 +15,15 @@
|
|||||||
- name: x-imex-auth
|
- name: x-imex-auth
|
||||||
value_from_env: DATAPUMP_AUTH
|
value_from_env: DATAPUMP_AUTH
|
||||||
comment: Project Mexico
|
comment: Project Mexico
|
||||||
|
- name: CARFAX RPS Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/carfaxrps'
|
||||||
|
schedule: 15 7 * * 0
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
comment: Project Mexico
|
||||||
- name: Chatter Data Pump
|
- name: Chatter Data Pump
|
||||||
webhook: '{{HASURA_API_URL}}/data/chatter'
|
webhook: '{{HASURA_API_URL}}/data/chatter'
|
||||||
schedule: 45 5 * * *
|
schedule: 45 5 * * *
|
||||||
|
|||||||
441
server/data/carfax-rps.js
Normal file
441
server/data/carfax-rps.js
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
const queries = require("../graphql-client/queries");
|
||||||
|
const moment = require("moment-timezone");
|
||||||
|
const logger = require("../utils/logger");
|
||||||
|
const fs = require("fs");
|
||||||
|
const client = require("../graphql-client/graphql-client").rpsClient;
|
||||||
|
const { sendServerEmail, sendMexicoBillingEmail } = require("../email/sendemail");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const { ftpSetup, uploadToS3 } = require("./carfax");
|
||||||
|
let Client = require("ssh2-sftp-client");
|
||||||
|
|
||||||
|
const AHDateFormat = "YYYY-MM-DD";
|
||||||
|
|
||||||
|
const NON_ASCII_REGEX = /[^\x20-\x7E]/g;
|
||||||
|
|
||||||
|
const S3_BUCKET_NAME = "rps-carfax-uploads";
|
||||||
|
|
||||||
|
const carfaxExportRps = async (req, res) => {
|
||||||
|
// Only process if in production environment.
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
return res.sendStatus(403);
|
||||||
|
}
|
||||||
|
// Only process if the appropriate token is provided.
|
||||||
|
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
||||||
|
return res.sendStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send immediate response and continue processing.
|
||||||
|
res.status(202).json({
|
||||||
|
success: true,
|
||||||
|
message: "Processing request ...",
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.log("CARFAX-RPS-start", "DEBUG", "api", null, null);
|
||||||
|
const allJSONResults = [];
|
||||||
|
const allErrors = [];
|
||||||
|
|
||||||
|
const { bodyshops } = await client.request(queries.GET_CARFAX_RPS_SHOPS); //Query for the List of Bodyshop Clients.
|
||||||
|
const specificShopIds = req.body.bodyshopIds; // ['uuid];
|
||||||
|
const { start, end, skipUpload, ignoreDateFilter } = req.body; //YYYY-MM-DD
|
||||||
|
|
||||||
|
const shopsToProcess =
|
||||||
|
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
|
||||||
|
logger.log("CARFAX-RPS-shopsToProcess-generated", "DEBUG", "api", null, null);
|
||||||
|
|
||||||
|
if (shopsToProcess.length === 0) {
|
||||||
|
logger.log("CARFAX-RPS-shopsToProcess-empty", "DEBUG", "api", null, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await processShopData(shopsToProcess, start, end, skipUpload, ignoreDateFilter, allJSONResults, allErrors);
|
||||||
|
|
||||||
|
await sendServerEmail({
|
||||||
|
subject: `Project Mexico RPS Report ${moment().format("MM-DD-YY")}`,
|
||||||
|
text: `Total Count: ${allJSONResults.reduce((a, v) => a + v.count, 0)}\nErrors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
||||||
|
allJSONResults.map((x) => ({
|
||||||
|
imexshopid: x.imexshopid,
|
||||||
|
filename: x.filename,
|
||||||
|
count: x.count,
|
||||||
|
result: x.result
|
||||||
|
})),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`,
|
||||||
|
to: ["bradley.rhoades@convenient-brands.com"]
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log("CARFAX-RPS-end", "DEBUG", "api", null, null);
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("CARFAX-RPS-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDateFilter, allJSONResults, allErrors) {
|
||||||
|
for (const bodyshop of shopsToProcess) {
|
||||||
|
const shopid = bodyshop.shopname.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||||
|
const erroredJobs = [];
|
||||||
|
try {
|
||||||
|
logger.log("CARFAX-RPS-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
|
shopname: bodyshop.shopname
|
||||||
|
});
|
||||||
|
|
||||||
|
const { jobs, bodyshops_by_pk } = await client.request(queries.CARFAX_RPS_QUERY, {
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
...(ignoreDateFilter
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
starttz: start ? moment(start).startOf("day") : moment().subtract(7, "days").startOf("day"),
|
||||||
|
...(end && { endtz: moment(end).endOf("day") }),
|
||||||
|
start: start
|
||||||
|
? moment(start).startOf("day").format(AHDateFormat)
|
||||||
|
: moment().subtract(7, "days").startOf("day").format(AHDateFormat),
|
||||||
|
...(end && { endtz: moment(end).endOf("day").format(AHDateFormat) })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const carfaxObject = {
|
||||||
|
shopid: shopid,
|
||||||
|
shop_name: bodyshop.shopname,
|
||||||
|
job: jobs.map((j) =>
|
||||||
|
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
|
||||||
|
erroredJobs.push({ job: job, error: error.toString() });
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (erroredJobs.length > 0) {
|
||||||
|
logger.log("CARFAX-RPS-failed-jobs", "ERROR", "api", bodyshop.id, {
|
||||||
|
count: erroredJobs.length,
|
||||||
|
jobs: JSON.stringify(erroredJobs.map((j) => j.job.id))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonObj = {
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: shopid,
|
||||||
|
json: JSON.stringify(carfaxObject, null, 2),
|
||||||
|
filename: `${shopid}_${moment().format("DDMMYYYY_HHMMss")}.json`,
|
||||||
|
count: carfaxObject.job.length
|
||||||
|
};
|
||||||
|
|
||||||
|
if (skipUpload) {
|
||||||
|
fs.writeFileSync(`./logs/${jsonObj.filename}`, jsonObj.json);
|
||||||
|
uploadToS3(jsonObj, S3_BUCKET_NAME);
|
||||||
|
} else {
|
||||||
|
await uploadViaSFTP(jsonObj);
|
||||||
|
|
||||||
|
await sendMexicoBillingEmail({
|
||||||
|
subject: `${shopid.replace(/_/g, "").toUpperCase()}_MexicoRPS_${moment().format("MMDDYYYY")} ROs ${jsonObj.count} Error ${errorCode(jsonObj)}`,
|
||||||
|
text: `Errors:\n${JSON.stringify(
|
||||||
|
erroredJobs.map((ej) => ({
|
||||||
|
jobid: ej.job?.id,
|
||||||
|
error: ej.error
|
||||||
|
})),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}\n\nUploaded:\n${JSON.stringify(
|
||||||
|
{
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: shopid,
|
||||||
|
count: jsonObj.count,
|
||||||
|
filename: jsonObj.filename,
|
||||||
|
result: jsonObj.result
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
allJSONResults.push({
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: shopid,
|
||||||
|
count: jsonObj.count,
|
||||||
|
filename: jsonObj.filename,
|
||||||
|
result: jsonObj.result
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log("CARFAX-RPS-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||||
|
shopname: bodyshop.shopname
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
//Error at the shop level.
|
||||||
|
logger.log("CARFAX-RPS-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
|
||||||
|
|
||||||
|
allErrors.push({
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: shopid,
|
||||||
|
CARFAXid: bodyshop.CARFAXid,
|
||||||
|
fatal: true,
|
||||||
|
errors: [error.toString()]
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
allErrors.push({
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
imexshopid: shopid,
|
||||||
|
CARFAXid: bodyshop.CARFAXid,
|
||||||
|
errors: erroredJobs.map((ej) => ({
|
||||||
|
jobid: ej.job?.id,
|
||||||
|
error: ej.error
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadViaSFTP(jsonObj) {
|
||||||
|
const sftp = new Client();
|
||||||
|
sftp.on("error", (errors) =>
|
||||||
|
logger.log("CARFAX-RPS-sftp-connection-error", "ERROR", "api", jsonObj.bodyshopid, {
|
||||||
|
error: errors.message,
|
||||||
|
stack: errors.stack
|
||||||
|
})
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
// Upload to S3 first.
|
||||||
|
uploadToS3(jsonObj, S3_BUCKET_NAME);
|
||||||
|
|
||||||
|
//Connect to the FTP and upload all.
|
||||||
|
await sftp.connect(ftpSetup);
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsonObj.result = await sftp.put(Buffer.from(jsonObj.json), `${jsonObj.filename}`);
|
||||||
|
logger.log("CARFAX-RPS-sftp-upload", "DEBUG", "api", jsonObj.bodyshopid, {
|
||||||
|
imexshopid: jsonObj.imexshopid,
|
||||||
|
filename: jsonObj.filename,
|
||||||
|
result: jsonObj.result
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("CARFAX-RPS-sftp-upload-error", "ERROR", "api", jsonObj.bodyshopid, {
|
||||||
|
filename: jsonObj.filename,
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("CARFAX-RPS-sftp-error", "ERROR", "api", jsonObj.bodyshopid, {
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
sftp.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateRepairOrderTag = (job, errorCallback) => {
|
||||||
|
try {
|
||||||
|
const subtotalEntry = job.totals.find((total) => total.TTL_TYPECD === "");
|
||||||
|
const subtotal = subtotalEntry ? subtotalEntry.T_AMT : 0;
|
||||||
|
|
||||||
|
const ret = {
|
||||||
|
ro_number: crypto.createHash("md5").update(job.id, "utf8").digest("hex"),
|
||||||
|
v_vin: job.v_vin || "",
|
||||||
|
v_year: job.v_model_yr
|
||||||
|
? parseInt(job.v_model_yr.match(/\d/g))
|
||||||
|
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
|
||||||
|
: ""
|
||||||
|
: "",
|
||||||
|
v_make: job.v_makedesc || "",
|
||||||
|
v_model: job.v_model || "",
|
||||||
|
|
||||||
|
date_estimated: moment(job.created_at).tz("America/Winnipeg").format(AHDateFormat) || "",
|
||||||
|
data_opened: moment(job.created_at).tz("America/Winnipeg").format(AHDateFormat) || "",
|
||||||
|
date_invoiced: [job.close_date, job.created_at].find((date) => date)
|
||||||
|
? moment([job.close_date, job.created_at].find((date) => date))
|
||||||
|
.tz("America/Winnipeg")
|
||||||
|
.format(AHDateFormat)
|
||||||
|
: "",
|
||||||
|
loss_date: job.loss_date ? moment(job.loss_date).format(AHDateFormat) : "",
|
||||||
|
|
||||||
|
ins_co_nm: job.ins_co_nm || "",
|
||||||
|
loss_desc: job.loss_desc || "",
|
||||||
|
theft_ind: job.theft_ind,
|
||||||
|
tloss_ind: job.tlos_ind,
|
||||||
|
|
||||||
|
subtotal: subtotal,
|
||||||
|
|
||||||
|
areaofdamage: {
|
||||||
|
impact1: generateAreaOfDamage(job.impact_1 || ""),
|
||||||
|
impact2: generateAreaOfDamage(job.impact_2 || "")
|
||||||
|
},
|
||||||
|
|
||||||
|
jobLines: job.joblines.length > 0 ? job.joblines.map((jl) => GenerateDetailLines(jl)) : [generateNullDetailLine()]
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("CARFAX-RPS-job-data-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||||
|
errorCallback({ jobid: job.id, error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenerateDetailLines = (line) => {
|
||||||
|
const ret = {
|
||||||
|
line_desc: line.line_desc ? line.line_desc.replace(NON_ASCII_REGEX, "") : null,
|
||||||
|
oem_partno: line.oem_partno ? line.oem_partno.replace(NON_ASCII_REGEX, "") : null,
|
||||||
|
alt_partno: line.alt_partno ? line.alt_partno.replace(NON_ASCII_REGEX, "") : null,
|
||||||
|
op_code_desc: generateOpCodeDescription(line.lbr_op),
|
||||||
|
lbr_ty: generateLaborType(line.mod_lbr_ty),
|
||||||
|
lbr_hrs: line.mod_lb_hrs || 0,
|
||||||
|
part_qty: line.part_qty || 0,
|
||||||
|
part_type: generatePartType(line.part_type),
|
||||||
|
act_price: line.act_price || 0
|
||||||
|
};
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateNullDetailLine = () => {
|
||||||
|
return {
|
||||||
|
line_desc: null,
|
||||||
|
oem_partno: null,
|
||||||
|
alt_partno: null,
|
||||||
|
lbr_ty: null,
|
||||||
|
part_qty: 0,
|
||||||
|
part_type: null,
|
||||||
|
act_price: 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateAreaOfDamage = (loc) => {
|
||||||
|
const areaMap = {
|
||||||
|
"01": "Right Front Corner",
|
||||||
|
"02": "Right Front Side",
|
||||||
|
"03": "Right Side",
|
||||||
|
"04": "Right Rear Side",
|
||||||
|
"05": "Right Rear Corner",
|
||||||
|
"06": "Rear",
|
||||||
|
"07": "Left Rear Corner",
|
||||||
|
"08": "Left Rear Side",
|
||||||
|
"09": "Left Side",
|
||||||
|
10: "Left Front Side",
|
||||||
|
11: "Left Front Corner",
|
||||||
|
12: "Front",
|
||||||
|
13: "Rollover",
|
||||||
|
14: "Uknown",
|
||||||
|
15: "Total Loss",
|
||||||
|
16: "Non-Collision",
|
||||||
|
19: "All Over",
|
||||||
|
25: "Hood",
|
||||||
|
26: "Deck Lid",
|
||||||
|
27: "Roof",
|
||||||
|
28: "Undercarriage",
|
||||||
|
34: "All Over"
|
||||||
|
};
|
||||||
|
return areaMap[loc] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateLaborType = (type) => {
|
||||||
|
const laborTypeMap = {
|
||||||
|
laa: "Aluminum",
|
||||||
|
lab: "Body",
|
||||||
|
lad: "Diagnostic",
|
||||||
|
lae: "Electrical",
|
||||||
|
laf: "Frame",
|
||||||
|
lag: "Glass",
|
||||||
|
lam: "Mechanical",
|
||||||
|
lar: "Refinish",
|
||||||
|
las: "Structural",
|
||||||
|
lau: "Other - LAU",
|
||||||
|
la1: "Other - LA1",
|
||||||
|
la2: "Other - LA2",
|
||||||
|
la3: "Other - LA3",
|
||||||
|
la4: "Other - LA4",
|
||||||
|
null: "Other",
|
||||||
|
mapa: "Paint Materials",
|
||||||
|
mash: "Shop Materials",
|
||||||
|
rates_subtotal: "Labor Total",
|
||||||
|
"timetickets.labels.shift": "Shift",
|
||||||
|
"timetickets.labels.amshift": "Morning Shift",
|
||||||
|
"timetickets.labels.ambreak": "Morning Break",
|
||||||
|
"timetickets.labels.pmshift": "Afternoon Shift",
|
||||||
|
"timetickets.labels.pmbreak": "Afternoon Break",
|
||||||
|
"timetickets.labels.lunch": "Lunch"
|
||||||
|
};
|
||||||
|
|
||||||
|
return laborTypeMap[type?.toLowerCase()] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatePartType = (type) => {
|
||||||
|
const partTypeMap = {
|
||||||
|
paa: "Aftermarket",
|
||||||
|
pae: "Existing",
|
||||||
|
pag: "Glass",
|
||||||
|
pal: "LKQ",
|
||||||
|
pan: "OEM",
|
||||||
|
pao: "Other",
|
||||||
|
pas: "Sublet",
|
||||||
|
pasl: "Sublet",
|
||||||
|
ccc: "CC Cleaning",
|
||||||
|
ccd: "CC Damage Waiver",
|
||||||
|
ccdr: "CC Daily Rate",
|
||||||
|
ccf: "CC Refuel",
|
||||||
|
ccm: "CC Mileage",
|
||||||
|
prt_dsmk_total: "Line Item Adjustment"
|
||||||
|
};
|
||||||
|
|
||||||
|
return partTypeMap[type?.toLowerCase()] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateOpCodeDescription = (type) => {
|
||||||
|
const opCodeMap = {
|
||||||
|
OP0: "REMOVE / REPLACE PARTIAL",
|
||||||
|
OP1: "REFINISH / REPAIR",
|
||||||
|
OP10: "REPAIR , PARTIAL",
|
||||||
|
OP100: "REPLACE PRE-PRICED",
|
||||||
|
OP101: "REMOVE/REPLACE RECYCLED PART",
|
||||||
|
OP103: "REMOVE / REPLACE PARTIAL",
|
||||||
|
OP104: "REMOVE / REPLACE PARTIAL LABOUR",
|
||||||
|
OP105: "!!ADJUST MANUALLY!!",
|
||||||
|
OP106: "REPAIR , PARTIAL",
|
||||||
|
OP107: "CHIPGUARD",
|
||||||
|
OP108: "MULTI TONE",
|
||||||
|
OP109: "REPLACE PRE-PRICED",
|
||||||
|
OP11: "REMOVE / REPLACE",
|
||||||
|
OP110: "REFINISH / REPAIR",
|
||||||
|
OP111: "REMOVE / REPLACE",
|
||||||
|
OP112: "REMOVE / REPLACE",
|
||||||
|
OP113: "REPLACE PRE-PRICED",
|
||||||
|
OP114: "REPLACE PRE-PRICED",
|
||||||
|
OP12: "REMOVE / REPLACE PARTIAL",
|
||||||
|
OP120: "REPAIR , PARTIAL",
|
||||||
|
OP13: "ADDITIONAL COSTS",
|
||||||
|
OP14: "ADDITIONAL OPERATIONS",
|
||||||
|
OP15: "BLEND",
|
||||||
|
OP16: "SUBLET",
|
||||||
|
OP17: "POLICY LIMIT ADJUSTMENT",
|
||||||
|
OP18: "APPEAR ALLOWANCE",
|
||||||
|
OP2: "REMOVE / INSTALL",
|
||||||
|
OP24: "CHIPGUARD",
|
||||||
|
OP25: "TWO TONE",
|
||||||
|
OP26: "PAINTLESS DENT REPAIR",
|
||||||
|
OP260: "SUBLET",
|
||||||
|
OP3: "ADDITIONAL LABOR",
|
||||||
|
OP4: "ALIGNMENT",
|
||||||
|
OP5: "OVERHAUL",
|
||||||
|
OP6: "REFINISH",
|
||||||
|
OP7: "INSPECT",
|
||||||
|
OP8: "CHECK / ADJUST",
|
||||||
|
OP9: "REPAIR"
|
||||||
|
};
|
||||||
|
|
||||||
|
return opCodeMap[type?.toUpperCase()] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorCode = ({ count, filename, results }) => {
|
||||||
|
if (count === 0) return 1;
|
||||||
|
if (!filename) return 3;
|
||||||
|
const sftpErrorCode = results?.sftpError?.code;
|
||||||
|
if (sftpErrorCode && ["ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "ECONNRESET"].includes(sftpErrorCode)) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (sftpErrorCode) return 7;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
default: carfaxExportRps,
|
||||||
|
ftpSetup
|
||||||
|
};
|
||||||
@@ -37,12 +37,12 @@ const S3_BUCKET_NAME = InstanceManager({
|
|||||||
const region = InstanceManager.InstanceRegion;
|
const region = InstanceManager.InstanceRegion;
|
||||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||||
|
|
||||||
const uploadToS3 = (jsonObj) => {
|
const uploadToS3 = (jsonObj, bucketName = S3_BUCKET_NAME) => {
|
||||||
const webPath = isLocal
|
const webPath = isLocal
|
||||||
? `https://${S3_BUCKET_NAME}.s3.localhost.localstack.cloud:4566/${jsonObj.filename}`
|
? `https://${bucketName}.s3.localhost.localstack.cloud:4566/${jsonObj.filename}`
|
||||||
: `https://${S3_BUCKET_NAME}.s3.${region}.amazonaws.com/${jsonObj.filename}`;
|
: `https://${bucketName}.s3.${region}.amazonaws.com/${jsonObj.filename}`;
|
||||||
|
|
||||||
uploadFileToS3({ bucketName: S3_BUCKET_NAME, key: jsonObj.filename, content: jsonObj.json })
|
uploadFileToS3({ bucketName: bucketName, key: jsonObj.filename, content: jsonObj.json })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.log("CARFAX-s3-upload", "DEBUG", "api", jsonObj.bodyshopid, {
|
logger.log("CARFAX-s3-upload", "DEBUG", "api", jsonObj.bodyshopid, {
|
||||||
imexshopid: jsonObj.imexshopid,
|
imexshopid: jsonObj.imexshopid,
|
||||||
@@ -61,7 +61,7 @@ const uploadToS3 = (jsonObj) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
const carfaxExport = async (req, res) => {
|
||||||
// Only process if in production environment.
|
// Only process if in production environment.
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
return res.sendStatus(403);
|
return res.sendStatus(403);
|
||||||
@@ -80,7 +80,7 @@ exports.default = async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log("CARFAX-start", "DEBUG", "api", null, null);
|
logger.log("CARFAX-start", "DEBUG", "api", null, null);
|
||||||
const allXMLResults = [];
|
const allJSONResults = [];
|
||||||
const allErrors = [];
|
const allErrors = [];
|
||||||
|
|
||||||
const { bodyshops } = await client.request(queries.GET_CARFAX_SHOPS); //Query for the List of Bodyshop Clients.
|
const { bodyshops } = await client.request(queries.GET_CARFAX_SHOPS); //Query for the List of Bodyshop Clients.
|
||||||
@@ -96,12 +96,12 @@ exports.default = async (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await processShopData(shopsToProcess, start, end, skipUpload, ignoreDateFilter, allXMLResults, allErrors);
|
await processShopData(shopsToProcess, start, end, skipUpload, ignoreDateFilter, allJSONResults, allErrors);
|
||||||
|
|
||||||
await sendServerEmail({
|
await sendServerEmail({
|
||||||
subject: `Project Mexico Report ${moment().format("MM-DD-YY")}`,
|
subject: `Project Mexico Report ${moment().format("MM-DD-YY")}`,
|
||||||
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
text: `Total Count: ${allJSONResults.reduce((a, v) => a + v.count, 0)}\nErrors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
||||||
allXMLResults.map((x) => ({
|
allJSONResults.map((x) => ({
|
||||||
imexshopid: x.imexshopid,
|
imexshopid: x.imexshopid,
|
||||||
filename: x.filename,
|
filename: x.filename,
|
||||||
count: x.count,
|
count: x.count,
|
||||||
@@ -109,7 +109,8 @@ exports.default = async (req, res) => {
|
|||||||
})),
|
})),
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
)}`
|
)}`,
|
||||||
|
to: ["bradley.rhoades@convenient-brands.com"]
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("CARFAX-end", "DEBUG", "api", null, null);
|
logger.log("CARFAX-end", "DEBUG", "api", null, null);
|
||||||
@@ -118,7 +119,7 @@ exports.default = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDateFilter, allXMLResults, allErrors) {
|
async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDateFilter, allJSONResults, allErrors) {
|
||||||
for (const bodyshop of shopsToProcess) {
|
for (const bodyshop of shopsToProcess) {
|
||||||
const shopid = bodyshop.imexshopid?.toLowerCase() || bodyshop.shopname.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
const shopid = bodyshop.imexshopid?.toLowerCase() || bodyshop.shopname.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||||
const erroredJobs = [];
|
const erroredJobs = [];
|
||||||
@@ -195,7 +196,7 @@ async function processShopData(shopsToProcess, start, end, skipUpload, ignoreDat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
allXMLResults.push({
|
allJSONResults.push({
|
||||||
bodyshopid: bodyshop.id,
|
bodyshopid: bodyshop.id,
|
||||||
imexshopid: shopid,
|
imexshopid: shopid,
|
||||||
count: jsonObj.count,
|
count: jsonObj.count,
|
||||||
@@ -447,3 +448,9 @@ const errorCode = ({ count, filename, results }) => {
|
|||||||
if (sftpErrorCode) return 7;
|
if (sftpErrorCode) return 7;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
default: carfaxExport,
|
||||||
|
ftpSetup,
|
||||||
|
uploadToS3
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ exports.usageReport = require("./usageReport").default;
|
|||||||
exports.podium = require("./podium").default;
|
exports.podium = require("./podium").default;
|
||||||
exports.emsUpload = require("./emsUpload").default;
|
exports.emsUpload = require("./emsUpload").default;
|
||||||
exports.carfax = require("./carfax").default;
|
exports.carfax = require("./carfax").default;
|
||||||
|
exports.carfaxRps = require("./carfax-rps").default;
|
||||||
exports.vehicletype = require("./vehicletype/vehicletype").default;
|
exports.vehicletype = require("./vehicletype/vehicletype").default;
|
||||||
@@ -44,8 +44,14 @@ const logEmail = async (req, email) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendServerEmail = async ({ subject, text }) => {
|
const sendServerEmail = async ({ subject, text, to = [] }) => {
|
||||||
if (process.env.NODE_ENV === undefined) return;
|
if (process.env.NODE_ENV === undefined) return;
|
||||||
|
|
||||||
|
let sentTo = ["support@imexsystems.ca"];
|
||||||
|
if (to?.length) {
|
||||||
|
sentTo = [...sentTo, ...to];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mailer.sendMail(
|
mailer.sendMail(
|
||||||
{
|
{
|
||||||
@@ -53,7 +59,7 @@ const sendServerEmail = async ({ subject, text }) => {
|
|||||||
imex: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
imex: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
||||||
rome: `Rome Online API - ${process.env.NODE_ENV} <noreply@romeonline.io>`
|
rome: `Rome Online API - ${process.env.NODE_ENV} <noreply@romeonline.io>`
|
||||||
}),
|
}),
|
||||||
to: ["support@thinkimex.com"],
|
to: sentTo,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
text: text,
|
text: text,
|
||||||
ses: {
|
ses: {
|
||||||
@@ -68,7 +74,7 @@ const sendServerEmail = async ({ subject, text }) => {
|
|||||||
},
|
},
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
logger.log("server-email-failure", err ? "error" : "debug", null, null, {
|
logger.log("server-email-send", err ? "error" : "debug", null, null, {
|
||||||
message: err?.message,
|
message: err?.message,
|
||||||
stack: err?.stack
|
stack: err?.stack
|
||||||
});
|
});
|
||||||
@@ -103,7 +109,7 @@ const sendMexicoBillingEmail = async ({ subject, text }) => {
|
|||||||
},
|
},
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
logger.log("server-email-failure", err ? "error" : "debug", null, null, {
|
logger.log("server-email-send", err ? "error" : "debug", null, null, {
|
||||||
message: err?.message,
|
message: err?.message,
|
||||||
stack: err?.stack
|
stack: err?.stack
|
||||||
});
|
});
|
||||||
@@ -258,7 +264,10 @@ const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachmen
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
// (message, type, user, record, meta
|
// (message, type, user, record, meta
|
||||||
logger.log("server-email", err ? "error" : "debug", null, null, { message: err?.message, stack: err?.stack });
|
logger.log("server-email-send", err ? "error" : "debug", null, null, {
|
||||||
|
message: err?.message,
|
||||||
|
stack: err?.stack
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
const logger = require("../utils/logger");
|
||||||
|
|
||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||||
|
|
||||||
//New bug introduced with Graphql Request.
|
//New bug introduced with Graphql Request.
|
||||||
@@ -11,9 +13,24 @@ const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rpsClient =
|
||||||
|
process.env.RPS_GRAPHQL_ENDPOINT && process.env.RPS_HASURA_ADMIN_SECRET ?
|
||||||
|
new GraphQLClient(process.env.RPS_GRAPHQL_ENDPOINT, {
|
||||||
|
headers: {
|
||||||
|
"x-hasura-admin-secret": process.env.RPS_HASURA_ADMIN_SECRET
|
||||||
|
}
|
||||||
|
}) : null;
|
||||||
|
|
||||||
|
if (!rpsClient) {
|
||||||
|
//System log to disable RPS functions
|
||||||
|
logger.log(`RPS secrets are not set. Client is not configured.`, "WARN", "redis", "api", {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const unauthorizedClient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT);
|
const unauthorizedClient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
client,
|
client,
|
||||||
|
rpsClient,
|
||||||
unauthorizedClient
|
unauthorizedClient
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -921,6 +921,41 @@ exports.CARFAX_QUERY = `query CARFAX_EXPORT($start: timestamptz, $bodyshopid: uu
|
|||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
exports.CARFAX_RPS_QUERY = `query CARFAX_RPS_EXPORT($starttz: timestamptz, $endtz: timestamptz,$start: date, $end: date, $bodyshopid: uuid!) {
|
||||||
|
bodyshops_by_pk(id: $bodyshopid) {
|
||||||
|
id
|
||||||
|
shopname
|
||||||
|
}
|
||||||
|
jobs(where: {_and: [{_or: [{close_date: {_gt: $start, _lte: $end}}, {created_at: {_gt: $starttz, _lte: $endtz}, close_date: {_is_null: true}}]}, {_not: {_and: [{close_date: {_is_null: true}}, {created_at: {_is_null: true}}]}}, {bodyshopid: {_eq: $bodyshopid}}, {v_vin: {_is_null: false}}]}) {
|
||||||
|
close_date
|
||||||
|
created_at
|
||||||
|
id
|
||||||
|
ins_co_nm
|
||||||
|
impact_1
|
||||||
|
impact_2
|
||||||
|
joblines {
|
||||||
|
act_price
|
||||||
|
alt_partno
|
||||||
|
line_desc
|
||||||
|
mod_lb_hrs
|
||||||
|
mod_lbr_ty
|
||||||
|
oem_partno
|
||||||
|
lbr_op
|
||||||
|
part_type
|
||||||
|
part_qty
|
||||||
|
}
|
||||||
|
loss_date
|
||||||
|
loss_desc
|
||||||
|
theft_ind
|
||||||
|
tlos_ind
|
||||||
|
totals
|
||||||
|
v_makedesc
|
||||||
|
v_model
|
||||||
|
v_model_yr
|
||||||
|
v_vin
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
exports.CLAIMSCORP_QUERY = `query CLAIMSCORP_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
|
exports.CLAIMSCORP_QUERY = `query CLAIMSCORP_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
|
||||||
bodyshops_by_pk(id: $bodyshopid){
|
bodyshops_by_pk(id: $bodyshopid){
|
||||||
id
|
id
|
||||||
@@ -1867,6 +1902,13 @@ exports.GET_CARFAX_SHOPS = `query GET_CARFAX_SHOPS {
|
|||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
exports.GET_CARFAX_RPS_SHOPS = `query GET_CARFAX_RPS_SHOPS {
|
||||||
|
bodyshops(where: {carfax_exclude: {_neq: "true"}}){
|
||||||
|
id
|
||||||
|
shopname
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS {
|
exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS {
|
||||||
bodyshops(where: {claimscorpid: {_is_null: false}, _or: {claimscorpid: {_neq: ""}}}){
|
bodyshops(where: {claimscorpid: {_is_null: false}, _or: {claimscorpid: {_neq: ""}}}){
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { autohouse, claimscorp, chatter, kaizen, usageReport, podium, carfax } = require("../data/data");
|
const { autohouse, claimscorp, chatter, kaizen, usageReport, podium, carfax, carfaxRps } = require("../data/data");
|
||||||
|
|
||||||
router.post("/ah", autohouse);
|
router.post("/ah", autohouse);
|
||||||
router.post("/cc", claimscorp);
|
router.post("/cc", claimscorp);
|
||||||
@@ -9,5 +9,6 @@ router.post("/kaizen", kaizen);
|
|||||||
router.post("/usagereport", usageReport);
|
router.post("/usagereport", usageReport);
|
||||||
router.post("/podium", podium);
|
router.post("/podium", podium);
|
||||||
router.post("/carfax", carfax);
|
router.post("/carfax", carfax);
|
||||||
|
router.post("/carfaxrps", carfaxRps);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user