Files
bodyshop/server/integrations/partsManagement/partsManagementVehicleDamageEstimateAddRq.js

276 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// no-dd-sa:javascript-code-style/assignment-name
// CamelCase is used for GraphQL and database fields.
const xml2js = require("xml2js");
const client = require("../../graphql-client/graphql-client").client;
// GraphQL statements
const INSERT_JOB_WITH_LINES = `
mutation InsertJob($job: jobs_insert_input!) {
insert_jobs_one(object: $job) {
id
joblines { id unq_seq }
}
}
`;
const partsManagementVehicleDamageEstimateAddRq = async (req, res) => {
const { logger } = req;
const xml = req.body;
// ── PARSE XML ────────────────────────────────────────────────────────────────
let payload;
try {
payload = await xml2js.parseStringPromise(xml, {
explicitArray: false,
tagNameProcessors: [xml2js.processors.stripPrefix]
});
logger.log("parts-xml-parse", "debug", null, null, { success: true });
} catch (err) {
logger.log("parts-xml-parse-error", "error", null, null, { error: err });
return res.status(400).send("Invalid XML");
}
const rq = payload.VehicleDamageEstimateAddRq;
if (!rq) {
logger.log("parts-missing-root", "error");
return res.status(400).send("Missing <VehicleDamageEstimateAddRq>");
}
try {
// ── SHOP & CLAIM IDs ────────────────────────────────────────────────────────
const shopId = rq.ShopID || rq.shopId;
if (!shopId) throw { status: 400, message: "Missing <ShopID> in XML" };
const { RefClaimNum } = rq;
// ── DOCUMENT INFO ──────────────────────────────────────────────────────────
const doc = rq.DocumentInfo || {};
const comment = doc.Comment || null;
const date_exported = doc.TransmitDateTime || null;
// capture CIECA ID & totals
const ciecaid = rq.RqUID || null;
const cieca_ttl = parseFloat(rq.Cieca_ttl || 0);
// map DocumentInfo fields to our category/class fields
const cat_no = doc.VendorCode || null;
const category = doc.DocumentType || null;
const classType = doc.DocumentStatus || null;
// ── EVENT INFO ──────────────────────────────────────────────────────────────
const ev = rq.EventInfo || {};
const asgn = ev.AssignmentEvent || {};
const asgn_no = asgn.AssignmentNumber || null;
const asgn_type = asgn.AssignmentType || null;
const asgn_date = asgn.AssignmentDate || null;
const scheduled_in = ev.RepairEvent?.RequestedPickUpDateTime || null;
const scheduled_completion = ev.RepairEvent?.TargetCompletionDateTime || null;
// ── CLAIM & POLICY ──────────────────────────────────────────────────────────
const ci = rq.ClaimInfo || {};
const clm_no = ci.ClaimNum || null;
const status = ci.ClaimStatus || null;
const policy_no = ci.PolicyInfo?.PolicyNum || null;
const ded_amt = parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0);
// ── OWNER ────────────────────────────────────────────────────────────────────
const ownerParty = rq.AdminInfo?.Owner?.Party || {};
const ownr_fn = ownerParty.PersonInfo?.PersonName?.FirstName || null;
const ownr_ln = ownerParty.PersonInfo?.PersonName?.LastName || null;
const ownr_co_nm = ownerParty.OrgInfo?.CompanyName || null;
const adr = ownerParty.PersonInfo?.Communications?.Address || {};
const ownr_addr1 = adr.Address1 || null;
const ownr_addr2 = adr.Address2 || null;
const ownr_city = adr.City || null;
const ownr_st = adr.StateProvince || null;
const ownr_zip = adr.PostalCode || null;
const ownr_ctry = adr.Country || null;
let ownr_ph1;
let ownr_ph2;
let ownr_fax;
let ownr_ea;
(Array.isArray(ownerParty.ContactInfo?.Communications)
? ownerParty.ContactInfo.Communications
: [ownerParty.ContactInfo?.Communications || {}]
).forEach((c) => {
if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone;
if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone;
if (c.CommQualifier === "FX") ownr_fax = c.CommPhone;
if (c.CommQualifier === "EM") ownr_ea = c.CommEmail;
});
// Estimator → map to est_… fields
const estParty = rq.AdminInfo?.Estimator?.Party || {};
// grab raw first/last
const est_fn = estParty.PersonInfo?.PersonName?.FirstName || null;
const est_ln = estParty.PersonInfo?.PersonName?.LastName || null;
// now alias into the GraphQL names
const est_ct_fn = est_fn;
const est_ct_ln = est_ln;
// TODO: SHould be the estimator insurance company name est_co_name
const est_aff = rq.AdminInfo?.Estimator?.Affiliation || null;
const estComms = Array.isArray(estParty.ContactInfo?.Communications)
? estParty.ContactInfo.Communications
: [estParty.ContactInfo?.Communications || {}];
const est_ea = estComms.find((c) => c.CommQualifier === "EM")?.CommEmail || null;
// ── ADJUSTER ────────────────────────────────────────────────────────────────
const adjParty = rq.AdminInfo?.Adjuster?.Party || {};
const agt_ct_fn = adjParty.PersonInfo?.PersonName?.FirstName || null;
const agt_ct_ln = adjParty.PersonInfo?.PersonName?.LastName || null;
const agt_ct_ph =
(Array.isArray(adjParty.ContactInfo?.Communications)
? adjParty.ContactInfo.Communications
: [adjParty.ContactInfo?.Communications || {}]
).find((c) => c.CommQualifier === "CP")?.CommPhone || null;
const agt_ea =
(Array.isArray(adjParty.ContactInfo?.Communications)
? adjParty.ContactInfo.Communications
: [adjParty.ContactInfo?.Communications || {}]
).find((c) => c.CommQualifier === "EM")?.CommEmail || null;
// ── REPAIR FACILITY ─────────────────────────────────────────────────────────
const rfParty = rq.AdminInfo?.RepairFacility?.Party || {};
const servicing_dealer = rfParty.OrgInfo?.CompanyName || null;
const servicing_dealer_contact =
(Array.isArray(rfParty.ContactInfo?.Communications)
? rfParty.ContactInfo.Communications
: [rfParty.ContactInfo?.Communications || {}]
).find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null;
// ── VEHICLE (one-to-one) ─────────────────────────────────────────────────────
const vin = rq.VehicleInfo?.VINInfo?.VINNum || null;
const plate_no = rq.VehicleInfo?.License?.LicensePlateNum || null;
const plate_st = rq.VehicleInfo?.License?.LicensePlateStateProvince || null;
const desc = rq.VehicleInfo?.VehicleDesc || {};
const v_model_yr = desc.ModelYear || null;
const v_make_desc = desc.MakeDesc || null;
const v_model_desc = desc.ModelName || null;
const body_style = desc.BodyStyle || null;
const engine_desc = desc.EngineDesc || null;
const v_options = desc.SubModelDesc || null;
const v_type = desc.FuelType || null;
const v_cond = rq.VehicleInfo?.Condition?.DrivableInd;
const vehicleData = {
shopid: shopId,
v_vin: vin,
plate_no,
plate_st,
v_model_yr,
v_make_desc,
v_model_desc,
v_color: rq.VehicleInfo?.Paint?.Exterior?.ColorName || null,
v_bstyle: body_style,
v_engine: engine_desc,
// prod_dt: production_date,
v_options,
v_type,
v_cond
};
// ── DAMAGE LINES → joblinesData ────────────────────────────────────────────
const damageLines = Array.isArray(rq.DamageLineInfo) ? rq.DamageLineInfo : [rq.DamageLineInfo];
const joblinesData = damageLines.map((line) => ({
line_no: parseInt(line.LineNum, 10),
unq_seq: parseInt(line.UniqueSequenceNum, 10),
status: line.LineStatusCode || null,
line_desc: line.LineDesc || null,
// parts
part_type: line.PartInfo?.PartType || null,
part_qty: parseFloat(line.PartInfo?.Quantity || 0),
oem_partno: line.PartInfo?.OEMPartNum || null,
db_price: parseFloat(line.PartInfo?.PartPrice || 0),
act_price: parseFloat(line.PartInfo?.PartPrice || 0),
// labor
mod_lbr_ty: line.LaborInfo?.LaborType || null,
mod_lb_hrs: parseFloat(line.LaborInfo?.LaborHours || 0),
lbr_op: line.LaborInfo?.LaborOperation || null,
lbr_amt: parseFloat(line.LaborInfo?.LaborAmt || 0),
notes: line.LineMemo || null
}));
// ── BUILD & INSERT THE JOB ──────────────────────────────────────────────────
const jobInput = {
shopid: shopId,
ro_number: RefClaimNum,
// IDs & CIECA metadata
ciecaid,
cieca_ttl,
cat_no,
category,
class: classType,
// claim & policy
clm_no,
// default job: bodyshop.md_status.default_open
status: status || "OPEN",
clm_total: cieca_ttl,
policy_no,
ded_amt,
// document & events
comment,
date_exported,
asgn_no,
asgn_type,
asgn_date,
scheduled_in,
scheduled_completion,
// owner
ownr_fn,
ownr_ln,
ownr_co_nm,
ownr_addr1,
ownr_addr2,
ownr_city,
ownr_st,
ownr_zip,
ownr_ctry,
ownr_ph1,
ownr_ph2,
ownr_fax,
ownr_ea,
// estimator
// est_co_id: est_aff,
est_ct_fn,
est_ct_ln,
est_ea,
// adjuster
agt_ct_fn,
agt_ct_ln,
agt_ct_ph,
agt_ea,
// repair facility
servicing_dealer,
servicing_dealer_contact,
// stash any extra CIECA stuff we didnt map above
production_vars: {},
// nested relationships
vehicle: { data: vehicleData },
joblines: { data: joblinesData }
};
logger.log("parts-insert-job", "debug", null, null, { jobInput });
const { insert_jobs_one: newJob } = await client.request(INSERT_JOB_WITH_LINES, { job: jobInput });
logger.log("parts-job-created", "info", newJob.id, null);
return res.status(200).json({ success: true, jobId: newJob.id });
} catch (err) {
logger.log("parts-route-error", "error", null, null, { error: err });
return res.status(err.status || 500).json({ error: err.message || "Internal error" });
}
};
module.exports = partsManagementVehicleDamageEstimateAddRq;