feature/IO-3255-simplified-parts-management - Checkpoint

This commit is contained in:
Dave Richer
2025-06-27 13:01:18 -04:00
parent f5ea8719ef
commit 8de92403ee
6 changed files with 373 additions and 323 deletions

View File

@@ -55,6 +55,34 @@ const parseXml = async (xml, logger) => {
}
};
/**
* Recursively strip `xml2js`-style { _: 'value', $: { ... } } nodes into plain strings.
* @param {*} obj - Parsed XML object
* @returns {*} Normalized object
*/
const normalizeXmlObject = (obj) => {
if (Array.isArray(obj)) {
return obj.map(normalizeXmlObject);
}
if (typeof obj === "object" && obj !== null) {
if (Object.keys(obj).length === 2 && "_" in obj && "$" in obj) {
return normalizeXmlObject(obj._); // unwrap {_:"value",$:{...}} to just "value"
}
if (Object.keys(obj).length === 1 && "_" in obj) {
return normalizeXmlObject(obj._); // unwrap {_:"value"}
}
const normalized = {};
for (const key in obj) {
normalized[key] = normalizeXmlObject(obj[key]);
}
return normalized;
}
return obj;
};
/**
* Fetches the default order status for a bodyshop.
* @param {string} shopId - The bodyshop UUID.
@@ -81,7 +109,17 @@ const extractPartsTaxRates = (profile = {}) => {
const partsTaxRates = {};
for (const code of KNOWN_PART_RATE_TYPES) {
const rateInfo = rateInfos.find((r) => (r?.RateType || "").toUpperCase() === code);
const rateInfo = rateInfos.find((r) => {
const rateType =
typeof r?.RateType === "string"
? r.RateType
: typeof r?.RateType === "object" && r?.RateType._ // xml2js sometimes uses _ for text content
? r.RateType._
: "";
return rateType.toUpperCase() === code;
});
if (!rateInfo) {
partsTaxRates[code] = {};
continue;
@@ -111,7 +149,6 @@ const extractPartsTaxRates = (profile = {}) => {
return partsTaxRates;
};
/**
* Extracts job-related data from the XML request.
* @param {object} rq - The VehicleDamageEstimateAddRq object.
@@ -151,31 +188,44 @@ const extractJobData = (rq) => {
* @param {string} shopId - The bodyshop UUID.
* @returns {object} Owner data for insertion and inline use.
*/
/**
* Extracts owner data from the XML request.
* Falls back to Claimant if Owner is missing.
* @param {object} rq - The VehicleDamageEstimateAddRq object.
* @param {string} shopId - The bodyshop UUID.
* @returns {object} Owner data for insertion and inline use.
*/
const extractOwnerData = (rq, shopId) => {
const ownerParty = rq.AdminInfo?.Owner?.Party || {};
const adr = ownerParty.PersonInfo?.Communications?.Address || {};
// Prefer Owner, fallback to Claimant
const ownerOrClaimant = rq.AdminInfo?.Owner?.Party || rq.AdminInfo?.Claimant?.Party || {};
const personInfo = ownerOrClaimant.PersonInfo || {};
const personName = personInfo.PersonName || {};
const address = personInfo.Communications?.Address || {};
let ownr_ph1, ownr_ph2, ownr_ea;
(Array.isArray(ownerParty.ContactInfo?.Communications)
? ownerParty.ContactInfo.Communications
: [ownerParty.ContactInfo?.Communications || {}]
).forEach((c) => {
const comms = Array.isArray(ownerOrClaimant.ContactInfo?.Communications)
? ownerOrClaimant.ContactInfo.Communications
: [ownerOrClaimant.ContactInfo?.Communications || {}];
for (const c of comms) {
if (c.CommQualifier === "CP") ownr_ph1 = c.CommPhone;
if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone;
if (c.CommQualifier === "EM") ownr_ea = c.CommEmail;
});
}
return {
shopid: shopId,
ownr_fn: ownerParty.PersonInfo?.PersonName?.FirstName || null,
ownr_ln: ownerParty.PersonInfo?.PersonName?.LastName || null,
ownr_co_nm: ownerParty.OrgInfo?.CompanyName || null,
ownr_addr1: adr.Address1 || null,
ownr_addr2: adr.Address2 || null,
ownr_city: adr.City || null,
ownr_st: adr.StateProvince || null,
ownr_zip: adr.PostalCode || null,
ownr_ctry: adr.Country || null,
ownr_fn: personName.FirstName || null,
ownr_ln: personName.LastName || null,
ownr_co_nm: ownerOrClaimant.OrgInfo?.CompanyName || null,
ownr_addr1: address.Address1 || null,
ownr_addr2: address.Address2 || null,
ownr_city: address.City || null,
ownr_st: address.StateProvince || null,
ownr_zip: address.PostalCode || null,
ownr_ctry: address.Country || null,
ownr_ph1,
ownr_ph2,
ownr_ea
@@ -341,9 +391,9 @@ const extractVehicleData = (rq, shopId) => {
v_mldgcode: desc.MldgCode || null,
v_makecode: desc.MakeCode || null,
trim_color: interior.ColorName || desc.TrimColor || null,
db_v_code: desc.DatabaseCode || null,
v_model_num: desc.ModelNum || null,
v_odo: desc.OdometerInfo?.OdometerReading || null
db_v_code: desc.DatabaseCode || null
// v_model_num: desc.ModelNum || null
// v_odo: desc.OdometerInfo?.OdometerReading || null
};
};
@@ -436,7 +486,7 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => {
try {
// Parse XML
const payload = await parseXml(req.body, logger);
const rq = payload.VehicleDamageEstimateAddRq;
const rq = normalizeXmlObject(payload.VehicleDamageEstimateAddRq);
if (!rq) {
logger.log("parts-missing-root", "error");
return res.status(400).send("Missing <VehicleDamageEstimateAddRq>");
@@ -534,6 +584,7 @@ const partsManagementVehicleDamageEstimateAddRq = async (req, res) => {
return res.status(200).json({ success: true, jobId: newJob.id });
} catch (err) {
logger.log("parts-route-error", "error", null, null, { error: err });
console.dir({ err });
return res.status(err.status || 500).json({ error: err.message || "Internal error" });
}
};