Files
bodyshop/server/data/claimscorp.js
Allan Carr 78678dd3dc IO-3027 Datapumps Refactor
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-15 10:04:03 -08:00

689 lines
27 KiB
JavaScript

const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const CCDineroFormat = "0,0.00";
const AhDateFormat = "MM/DD/YYYY";
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
const ftpSetup = {
host: process.env.CLAIMSCORP_HOST,
port: process.env.CLAIMSCORP_PORT,
username: process.env.CLAIMSCORP_USER,
password: process.env.CLAIMSCORP_PASSWORD,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
algorithms: {
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;
}
// Only process if the appropriate token is provided.
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401);
return;
}
// Send immediate response and continue processing.
res.status(202).json({
success: true,
message: "Processing request ...",
timestamp: new Date().toISOString()
});
try {
logger.log("claimscorp-start", "DEBUG", "api", null, null);
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS); //Query for the List of Bodyshop Clients.
const specificShopIds = req.body.bodyshopIds; // ['uuid];
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
const batchSize = 10;
const shopsToProcess =
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
logger.log("claimscorp-shopsToProcess-generated", "DEBUG", "api", null, null);
if (shopsToProcess.length === 0) {
logger.log("claimscorp-shopsToProcess-empty", "DEBUG", "api", null, null);
return;
}
const batchPromises = [];
for (let i = 0; i < shopsToProcess.length; i += batchSize) {
const batch = shopsToProcess.slice(i, i + batchSize);
const batchPromise = (async () => {
await processBatch(batch, start, end);
if (skipUpload) {
for (const xmlObj of allxmlsToUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
}
} else {
await uploadViaSFTP(allxmlsToUpload);
}
})();
batchPromises.push(batchPromise);
}
await Promise.all(batchPromises);
await sendServerEmail({
subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`,
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })),
null,
2
)}`
});
logger.log("claimscorp-end", "DEBUG", "api", null, null);
} catch (error) {
logger.log("claimscorp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
}
};
async function processBatch(batch, start, end) {
for (const bodyshop of batch) {
const erroredJobs = [];
try {
logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
const { jobs, bodyshops_by_pk } = await client.request(queries.CLAIMSCORP_QUERY, {
bodyshopid: bodyshop.id,
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).endOf("day") })
});
const claimsCorpObject = {
DataFeed: {
ShopInfo: {
ShopID: bodyshops_by_pk.claimscorpid,
ShopName: bodyshops_by_pk.shopname,
RO: 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("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
});
}
const ret = builder.create({}, claimsCorpObject).end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: claimsCorpObject.DataFeed.ShopInfo.RO.length,
xml: ret,
filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml`
});
logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
} catch (error) {
//Error at the shop level.
logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
fatal: true,
errors: [error.toString()]
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number,
jobid: ej.job?.id,
error: ej.error
}))
});
}
}
}
async function uploadViaSFTP(allxmlsToUpload) {
const sftp = new Client();
sftp.on("error", (errors) =>
logger.log("claimscorp-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 {
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename,
result: xmlObj.result
});
} catch (error) {
logger.log("claimscorp-sftp-upload-error", "ERROR", "api", null, {
filename: xmlObj.filename,
error: error.message,
stack: error.stack
});
throw error;
}
}
} catch (error) {
logger.log("claimscorp-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
throw error;
} finally {
sftp.end();
}
}
const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2
if (!job.job_totals) {
errorCallback({
jobid: job.id,
job: job,
ro_number: job.ro_number,
error: { toString: () => "No job totals for RO." }
});
return {};
}
const repairCosts = CreateCosts(job);
//Calculate detail only lines.
const detailAdjustments = job.joblines
.filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty)
.reduce(
(acc, val) => {
return {
hours: acc.hours + val.mod_lb_hrs,
amount: acc.amount.add(
Dinero({
amount: Math.round((job.job_totals.rates[val.mod_lbr_ty.toLowerCase()].rate || 0) * val.mod_lb_hrs * 100)
})
)
};
},
{ hours: 0, amount: Dinero() }
);
try {
const ret = {
RoNumber: job.ro_number,
Customer: {
CustomerZip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "",
CustomerState: job.ownr_st || ""
},
Vehicle: {
Year: job.v_model_yr
? parseInt(job.v_model_yr.match(/\d/g))
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
: ""
: "",
Make: job.v_make_desc || "",
Model: job.v_model_desc || "",
BodyStyle: (job.vehicle && job.vehicle.v_bstyle) || "",
Color: job.v_color || "",
VIN: job.v_vin || ""
},
Carrier: {
UniqueID: job.ins_co_nm || "",
InsuranceCompany: job.ins_co_nm || ""
},
Claim: job.clm_no || "",
Contacts: {
PC: job.employee_csr_rel
? `${
job.employee_csr_rel.last_name ? job.employee_csr_rel.last_name : ""
}${job.employee_csr_rel.last_name ? ", " : ""}${
job.employee_csr_rel.first_name ? job.employee_csr_rel.first_name : ""
}`
: "",
Phone1: "",
Phone2: "",
Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
BodyTechnician: job.employee_body_rel
? `${
job.employee_body_rel.last_name ? job.employee_body_rel.last_name : ""
}${job.employee_body_rel.last_name ? ", " : ""}${
job.employee_body_rel.first_name ? job.employee_body_rel.first_name : ""
}`
: "",
PaintTechnician: job.employee_refinish_rel
? `${
job.employee_refinish_rel.last_name ? job.employee_refinish_rel.last_name : ""
}${job.employee_refinish_rel.last_name ? ", " : ""}${
job.employee_refinish_rel.first_name ? job.employee_refinish_rel.first_name : ""
}`
: ""
},
Dates: {
DateCreated: (job.date_estimated && moment(job.date_estimated).format(AhDateFormat)) || "",
DateLoss: (job.loss_date && moment(job.loss_date).format(AhDateFormat)) || "",
DateFNOL: "",
DateContact: "",
DateEstimated: (job.date_estimated && moment(job.date_estimated).format(AhDateFormat)) || "",
DateScheduled:
(job.scheduled_in && moment(job.scheduled_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "",
DateArrived: (job.actual_in && moment(job.actual_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "",
DateFirstPartsOrdered:
(job.parts_orders &&
job.parts_orders[0] &&
moment(job.parts_orders[0].created_at).tz(job.bodyshop.timezone).format(AhDateFormat)) ||
"",
DateStart: job.date_repairstarted
? (job.date_repairstarted && moment(job.date_repairstarted).tz(job.bodyshop.timezone).format(AhDateFormat)) ||
""
: (job.date_repairstarted && moment(job.actual_in).tz(job.bodyshop.timezone).format(AhDateFormat)) || "",
BodyStart: "",
BodyEnd: "",
FrameStart: "",
FrameEnd: "",
PrepStart: "",
PrepEnd: "",
SprayStart: "",
SprayEnd: "",
DateReady:
(job.actual_completion && moment(job.actual_completion).tz(job.bodyshop.timezone).format(AhDateFormat)) || "",
DateScheduledDelivery:
(job.scheduled_delivery && moment(job.scheduled_delivery).tz(job.bodyshop.timezone).format(AhDateFormat)) ||
"",
DateDelivered:
(job.actual_delivery && moment(job.actual_delivery).tz(job.bodyshop.timezone).format(AhDateFormat)) || "",
DateClosed:
(job.date_invoiced && moment(job.date_invoiced).tz(job.bodyshop.timezone).format(AhDateFormat)) || "",
BilledDate: "",
PaidInFullDate: "",
ROStatus: job.tlos_ind ? "TOT" : StatusMapping(job.status, job.bodyshop.md_ro_statuses)
},
Sales: {
Body: Dinero(job.job_totals.rates.lab.total)
.add(Dinero(job.job_totals.rates.laa.total))
.add(Dinero(job.job_totals.rates.lad.total))
.add(Dinero(job.job_totals.rates.las.total))
.toFormat(CCDineroFormat),
Paint: Dinero(job.job_totals.rates.lar.total).toFormat(CCDineroFormat),
Prep: Dinero().toFormat(CCDineroFormat),
Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat),
Mech: Dinero(job.job_totals.rates.lam.total).toFormat(CCDineroFormat),
Glass: Dinero(job.job_totals.rates.lag.total).toFormat(CCDineroFormat),
Elec: Dinero(job.job_totals.rates.lae.total).toFormat(CCDineroFormat),
Detail: detailAdjustments.amount.toFormat(CCDineroFormat),
Reassem: Dinero().toFormat(CCDineroFormat),
OtherLabor: Dinero(job.job_totals.rates.la1.total)
.add(Dinero(job.job_totals.rates.la2.total))
.add(Dinero(job.job_totals.rates.la3.total))
.add(Dinero(job.job_totals.rates.la4.total))
.add(Dinero(job.job_totals.rates.lau.total))
.subtract(detailAdjustments.amount)
.toFormat(CCDineroFormat),
BMatl: Dinero(job.job_totals.rates.mash.total).toFormat(CCDineroFormat),
PMatl: Dinero(job.job_totals.rates.mapa.total).toFormat(CCDineroFormat),
OEM: Dinero(job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN.total)
.add(Dinero(job.job_totals.parts.parts.list.PAP && job.job_totals.parts.parts.list.PAP.total))
.toFormat(CCDineroFormat),
LKQ: Dinero(job.job_totals.parts.parts.list.PAL && job.job_totals.parts.parts.list.PAL.total).toFormat(
CCDineroFormat
),
AM: Dinero(job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA.total).toFormat(
CCDineroFormat
),
MechParts: Dinero().toFormat(CCDineroFormat),
OtherParts: Dinero(job.job_totals.parts.parts.list.PAO && job.job_totals.parts.parts.list.PAO.total).toFormat(
CCDineroFormat
),
OtherSales: Dinero(job.job_totals.additional.storage).toFormat(CCDineroFormat),
Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat(CCDineroFormat),
Towing: Dinero(job.job_totals.additional.towing).toFormat(CCDineroFormat),
Storage: "0.00",
Rental:
job.job_totals.additional.additionalCostItems.includes("ATS Amount") === true
? Dinero(
job.job_totals.additional.additionalCostItems[
job.job_totals.additional.additionalCostItems.indexOf("ATS Amount")
].total
).toFormat(CCDineroFormat)
: Dinero().toFormat(CCDineroFormat),
HazWaste: Dinero().toFormat(CCDineroFormat),
Discounts: Dinero(job.job_totals.additional.adjustments).toFormat(CCDineroFormat),
Tax: Dinero(job.job_totals.totals.local_tax)
.add(Dinero(job.job_totals.totals.state_tax))
.add(Dinero(job.job_totals.totals.federal_tax))
.add(Dinero(job.job_totals.additional.pvrt))
.toFormat(CCDineroFormat),
NetSaleTotal: Dinero(job.job_totals.totals.subtotal).toFormat(CCDineroFormat),
SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(CCDineroFormat)
},
SaleHours: {
Body: job.job_totals.rates.lab.hours.toFixed(2),
BodyRepairHours: job.joblines
.filter((line) => repairOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
BodyReplacehours: job.joblines
.filter((line) => replaceOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
Paint: job.job_totals.rates.lar.hours.toFixed(2),
Prep: "0.00",
Frame: job.job_totals.rates.laf.hours.toFixed(2),
Mech: job.job_totals.rates.lam.hours.toFixed(2),
Glass: job.job_totals.rates.lag.hours.toFixed(2),
Elec: job.job_totals.rates.lae.hours.toFixed(2),
Detail: detailAdjustments.hours,
Reassem: "0.00",
Other: (
job.job_totals.rates.la1.hours +
job.job_totals.rates.la2.hours +
job.job_totals.rates.la3.hours +
job.job_totals.rates.la4.hours +
job.job_totals.rates.lau.hours -
detailAdjustments.hours
).toFixed(2),
TotalHours: job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0).toFixed(2)
},
Costs: {
Body: repairCosts.BodyLaborTotalCost.toFormat(CCDineroFormat),
Paint: repairCosts.RefinishLaborTotalCost.toFormat(CCDineroFormat),
Prep: Dinero().toFormat(CCDineroFormat),
Frame: repairCosts.FrameLaborTotalCost.toFormat(CCDineroFormat),
Mech: repairCosts.MechanicalLaborTotalCost.toFormat(CCDineroFormat),
Glass: repairCosts.GlassLaborTotalCost.toFormat(CCDineroFormat),
Elec: repairCosts.ElectricalLaborTotalCost.toFormat(CCDineroFormat),
Detail: Dinero().toFormat(CCDineroFormat),
Reassem: Dinero().toFormat(CCDineroFormat),
OtherLabor: repairCosts.LaborMiscTotalCost.toFormat(CCDineroFormat),
Bmatl: repairCosts.BMTotalCost.toFormat(CCDineroFormat),
Pmatl: repairCosts.PMTotalCost.toFormat(CCDineroFormat),
OEM: repairCosts.PartsOemCost.toFormat(CCDineroFormat),
LKQ: repairCosts.PartsRecycledCost.toFormat(CCDineroFormat),
AM: repairCosts.PartsAMCost.toFormat(CCDineroFormat),
MechParts: Dinero().toFormat(CCDineroFormat),
OtherParts: Dinero().toFormat(CCDineroFormat), //Check Synergy
OtherCost: repairCosts.PartsOtherCost.toFormat(CCDineroFormat),
Sublet: repairCosts.SubletTotalCost.toFormat(CCDineroFormat),
Towing: repairCosts.TowingTotalCost.toFormat(CCDineroFormat),
Storage: repairCosts.StorageTotalCost.toFormat(CCDineroFormat),
Rental: Dinero().toFormat(CCDineroFormat),
HazWaste: Dinero().toFormat(CCDineroFormat),
CostTotal: repairCosts.TotalCost.toFormat(CCDineroFormat)
},
CostHours: {
Body: repairCosts.BodyLaborTotalHrs.toFixed(2),
Paint: repairCosts.RefinishLaborTotalHrs.toFixed(2),
Prep: "0.00",
Frame: repairCosts.FrameLaborTotalHrs.toFixed(2),
Mech: repairCosts.MechanicalLaborTotalHrs.toFixed(2),
Glass: repairCosts.GlassLaborTotalHrs.toFixed(2),
Elec: repairCosts.ElectricalLaborTotalHrs.toFixed(2),
Detail: "0.00",
Other: repairCosts.LaborMiscTotalHrs.toFixed(2),
CostTotalHours: repairCosts.TotalHrs.toFixed(2)
}
};
return ret;
} catch (error) {
logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
}
};
const CreateCosts = (job) => {
//Create a mapping based on AH Requirements
//For DMS, the keys in the object below are the CIECA part types.
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
if (!bill_acc[line_val.cost_center]) bill_acc[line_val.cost_center] = Dinero();
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100)
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
return null;
});
return bill_acc;
}, {});
//If the hourly rates for job costing are set, add them in.
if (
job.bodyshop.jc_hourly_rates &&
(job.bodyshop.jc_hourly_rates.mapa ||
typeof job.bodyshop.jc_hourly_rates.mapa === "number" ||
isNaN(job.bodyshop.jc_hourly_rates.mapa) === false)
) {
if (!billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA])
billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero();
if (job.bodyshop.use_paint_scale_data === true) {
if (job.mixdata.length > 0) {
billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = Dinero({
amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100)
});
} else {
billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0)
}).multiply(job.job_totals.rates.mapa.hours)
);
}
} else {
billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MAPA] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa * 100) || 0)
}).multiply(job.job_totals.rates.mapa.hours)
);
}
}
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
if (!billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MASH])
billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MASH] = Dinero();
billTotalsByCostCenters[job.bodyshop.md_responsibility_centers.defaults.costs.MASH] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
].add(
Dinero({
amount: Math.round((job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash * 100) || 0)
}).multiply(job.job_totals.rates.mash.hours)
);
}
//Uses CIECA Labor types.
const ticketTotalsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = Dinero();
ticket_acc[ticket_val.cost_center] = ticket_acc[ticket_val.cost_center].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100)
}).multiply((ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || 0)
);
return ticket_acc;
}, {});
const ticketHrsByCostCenter = job.timetickets.reduce((ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center]) ticket_acc[ticket_val.cost_center] = 0;
ticket_acc[ticket_val.cost_center] =
ticket_acc[ticket_val.cost_center] + (ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) ||
0;
return ticket_acc;
}, {});
//CIECA STANDARD MAPPING OBJECT.
const ciecaObj = {
ATS: "ATS",
LA1: "LA1",
LA2: "LA2",
LA3: "LA3",
LA4: "LA4",
LAA: "LAA",
LAB: "LAB",
LAD: "LAD",
LAE: "LAE",
LAF: "LAF",
LAG: "LAG",
LAM: "LAM",
LAR: "LAR",
LAS: "LAS",
LAU: "LAU",
PAA: "PAA",
PAC: "PAC",
PAG: "PAG",
PAL: "PAL",
PAM: "PAM",
PAN: "PAN",
PAO: "PAO",
PAP: "PAP",
PAR: "PAR",
PAS: "PAS",
TOW: "TOW",
MAPA: "MAPA",
MASH: "MASH",
PASL: "PASL"
};
const defaultCosts =
job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber
? ciecaObj
: job.bodyshop.md_responsibility_centers.defaults.costs;
return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
if (
key !== defaultCosts.PAS &&
key !== defaultCosts.PASL &&
key !== defaultCosts.MAPA &&
key !== defaultCosts.MASH &&
key !== defaultCosts.TOW
)
return acc.add(billTotalsByCostCenters[key]);
return acc;
}, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(
billTotalsByCostCenters[defaultCosts.PAP] || Dinero()
),
PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(),
PartsReconditionedCost: billTotalsByCostCenters[defaultCosts.PAM] || Dinero(),
PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(),
PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
SubletTotalCost:
billTotalsByCostCenters[defaultCosts.PAS] || Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()),
BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(),
BodyLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0,
RefinishLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(),
RefinishLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0,
MechanicalLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
MechanicalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0,
StructuralLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
StructuralLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0,
ElectricalLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(),
ElectricalLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0,
FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(),
FrameLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0,
GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
GlassLaborTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0,
DetailLaborTotalCost: Dinero(),
// ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()),
LaborMiscTotalHrs:
(ticketHrsByCostCenter[defaultCosts.LA1] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA2] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA3] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA4] || 0) +
(ticketHrsByCostCenter[defaultCosts.LAU] || 0),
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(),
StorageTotalCost: Dinero(),
DetailTotal: Dinero(),
DetailTotalCost: Dinero(),
SalesTaxTotalCost: Dinero(),
LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce((acc, key) => {
return acc.add(ticketTotalsByCostCenter[key]);
}, Dinero()),
TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
return acc.add(billTotalsByCostCenters[key]);
}, Dinero()),
TotalHrs: job.timetickets.reduce((acc, ticket_val) => {
return acc + (ticket_val.flat_rate ? ticket_val.productivehrs : ticket_val.actualhrs) || 0;
}, 0)
};
};
const StatusMapping = (status, md_ro_statuses) => {
//Possible return statuses CLO, CAN, OPN
const {
default_imported,
default_open,
default_scheduled,
default_arrived,
default_completed,
default_delivered,
default_invoiced,
default_exported,
default_void
} = md_ro_statuses;
if (
status === default_open ||
status === default_imported ||
status === default_scheduled ||
status === default_arrived ||
status === default_completed ||
status === default_delivered ||
md_ro_statuses.production_statuses.includes(status)
)
return "OPN";
else if (status === default_invoiced || status === default_exported) return "CLO";
else if (status === default_void) return "CAN";
else return "UNDEFINED";
};