|
|
|
|
@@ -25,7 +25,7 @@ const FALLBACK_DEFAULT_ORDER_STATUS = "Open";
|
|
|
|
|
const getDefaultOrderStatus = async (shopId, logger) => {
|
|
|
|
|
try {
|
|
|
|
|
const { bodyshop_by_pk } = await client.request(GET_BODYSHOP_STATUS, { id: shopId });
|
|
|
|
|
return bodyshop_by_pk?.md_order_statuses?.default_open || FALLBACK_DEFAULT_ORDER_STATUS;
|
|
|
|
|
return bodyshop_by_pk?.md_order_statuses?.default_open || FALLBACK_DEFAULT_ORDER_STATUS; //I think this is intended to be called job status, not order status.
|
|
|
|
|
} catch (err) {
|
|
|
|
|
logger.log("parts-bodyshop-fetch-failed", "warn", shopId, null, { error: err });
|
|
|
|
|
return FALLBACK_DEFAULT_ORDER_STATUS;
|
|
|
|
|
@@ -101,14 +101,14 @@ const extractOwnerData = (rq, shopId) => {
|
|
|
|
|
const personName = personInfo.PersonName || {};
|
|
|
|
|
const address = personInfo.Communications?.Address || {};
|
|
|
|
|
|
|
|
|
|
let ownr_ph1, ownr_ph2, ownr_ea, ownr_alt_ph;
|
|
|
|
|
let ownr_ph1, ownr_ph2, ownr_ea, ownr_alt_ph;
|
|
|
|
|
|
|
|
|
|
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 === "CP") ownr_ph1 = c.CommPhone; //PF: Should document this logic. 1 and 2 don't typically indicate type in EMS. This makes sense, but good to document.
|
|
|
|
|
if (c.CommQualifier === "WP") ownr_ph2 = c.CommPhone;
|
|
|
|
|
if (c.CommQualifier === "EM") ownr_ea = c.CommEmail;
|
|
|
|
|
if (c.CommQualifier === "AL") ownr_alt_ph = c.CommPhone;
|
|
|
|
|
@@ -128,7 +128,7 @@ const extractOwnerData = (rq, shopId) => {
|
|
|
|
|
ownr_ph1,
|
|
|
|
|
ownr_ph2,
|
|
|
|
|
ownr_ea,
|
|
|
|
|
ownr_alt_ph
|
|
|
|
|
ownr_alt_ph //PF: This is not in the DB, if this object is inserted in place, this will fail.
|
|
|
|
|
// ownr_id_qualifier: ownerOrClaimant.IDInfo?.IDQualifierCode || null // New
|
|
|
|
|
// ownr_id_num: ownerOrClaimant.IDInfo?.IDNum || null, // New
|
|
|
|
|
// ownr_preferred_contact: ownerOrClaimant.PreferredContactMethod || null // New
|
|
|
|
|
@@ -166,7 +166,7 @@ const extractAdjusterData = (rq) => {
|
|
|
|
|
: [adjParty.ContactInfo?.Communications || {}];
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
agt_ct_fn: adjParty.PersonInfo?.PersonName?.FirstName || null,
|
|
|
|
|
agt_ct_fn: adjParty.PersonInfo?.PersonName?.FirstName || null, //PF: I dont think we display agt_ct_* fields in app. Have they typically been sending data here?
|
|
|
|
|
agt_ct_ln: adjParty.PersonInfo?.PersonName?.LastName || null,
|
|
|
|
|
agt_ct_ph: adjComms.find((c) => c.CommQualifier === "CP")?.CommPhone || null,
|
|
|
|
|
agt_ea: adjComms.find((c) => c.CommQualifier === "EM")?.CommEmail || null
|
|
|
|
|
@@ -185,7 +185,8 @@ const extractRepairFacilityData = (rq) => {
|
|
|
|
|
: [rfParty.ContactInfo?.Communications || {}];
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
servicing_dealer: rfParty.OrgInfo?.CompanyName || null,
|
|
|
|
|
servicing_dealer: rfParty.OrgInfo?.CompanyName || null, //PF: The servicing dealer fields are a relic from synergy for a few folks
|
|
|
|
|
//PF: I suspect RF data could be ignored since they are the RF.
|
|
|
|
|
servicing_dealer_contact:
|
|
|
|
|
rfComms.find((c) => c.CommQualifier === "WP" || c.CommQualifier === "FX")?.CommPhone || null
|
|
|
|
|
};
|
|
|
|
|
@@ -203,10 +204,10 @@ const extractLossInfo = (rq) => {
|
|
|
|
|
loss_date: loss.LossDateTime || null,
|
|
|
|
|
loss_type: custom.LossTypeCode || null,
|
|
|
|
|
loss_desc: custom.LossTypeDesc || null
|
|
|
|
|
// primary_poi: loss.PrimaryPOI?.POICode || null,
|
|
|
|
|
// primary_poi: loss.PrimaryPOI?.POICode || null, //PF: These map back to area_of_impact.impact_#
|
|
|
|
|
// secondary_poi: loss.SecondaryPOI?.POICode || null,
|
|
|
|
|
// damage_memo: loss.DamageMemo || null, //(maybe ins_memo)
|
|
|
|
|
// total_loss_ind: rq.ClaimInfo?.LossInfo?.TotalLossInd || null // New
|
|
|
|
|
// total_loss_ind: rq.ClaimInfo?.LossInfo?.TotalLossInd || null // New //PF: tlosind i believe is our field.
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -288,9 +289,9 @@ const extractVehicleData = (rq, shopId) => {
|
|
|
|
|
v_color: exterior.Color?.ColorName || null,
|
|
|
|
|
v_bstyle: desc.BodyStyle || null,
|
|
|
|
|
v_engine: desc.EngineDesc || null,
|
|
|
|
|
v_options: desc.SubModelDesc || null,
|
|
|
|
|
v_options: desc.SubModelDesc || null, //PF: Need to confirm with exact data, but this is typically a list of options. Not used AFAIK.
|
|
|
|
|
v_type: desc.FuelType || null,
|
|
|
|
|
v_cond: rq.VehicleInfo?.Condition?.DrivableInd,
|
|
|
|
|
v_cond: rq.VehicleInfo?.Condition?.DrivableInd, //PF: there is a separate driveable flag on the job.
|
|
|
|
|
v_trimcode: desc.TrimCode || null,
|
|
|
|
|
v_tone: exterior.Tone || null,
|
|
|
|
|
v_stage: exterior.RefinishStage || rq.VehicleInfo?.Paint?.RefinishStage || null,
|
|
|
|
|
@@ -342,7 +343,7 @@ const extractJobLines = (rq) => {
|
|
|
|
|
line.ManualLineInd === true ||
|
|
|
|
|
line.ManualLineInd === 1 ||
|
|
|
|
|
line.ManualLineInd === "1" ||
|
|
|
|
|
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y");
|
|
|
|
|
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y"); //PF: manual line tracks manual in IO or not, this woudl presumably always be false
|
|
|
|
|
} else {
|
|
|
|
|
lineOut.manual_line = null;
|
|
|
|
|
}
|
|
|
|
|
@@ -355,8 +356,8 @@ const extractJobLines = (rq) => {
|
|
|
|
|
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
|
|
|
|
|
lineOut.part_type = partInfo.PartType || null ? String(partInfo.PartType).toUpperCase() : null;
|
|
|
|
|
lineOut.part_qty = parseFloat(partInfo.Quantity || 0) || 1;
|
|
|
|
|
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null;
|
|
|
|
|
lineOut.db_price = isNaN(price) ? 0 : price;
|
|
|
|
|
lineOut.oem_partno = partInfo.OEMPartNum || partInfo.PartNum || null; //PF: if aftermarket part, we have alt_part_no to capture.
|
|
|
|
|
lineOut.db_price = isNaN(price) ? 0 : price; //PF: the Db and act price often are different. These should map back to their EMS equivalents.
|
|
|
|
|
lineOut.act_price = isNaN(price) ? 0 : price;
|
|
|
|
|
|
|
|
|
|
// Tax flag from PartInfo.TaxableInd when provided
|
|
|
|
|
@@ -373,7 +374,7 @@ const extractJobLines = (rq) => {
|
|
|
|
|
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y");
|
|
|
|
|
}
|
|
|
|
|
} else if (hasSublet) {
|
|
|
|
|
const amt = parseFloat(subletInfo.SubletAmount || 0);
|
|
|
|
|
const amt = parseFloat(subletInfo.SubletAmount || 0); //PF: Some nuance here. Usually a part and sublet amount shouldnt be on the same line, but they theoretically could. May require additional discussion.
|
|
|
|
|
lineOut.part_type = "PAS"; // Sublet as parts-as-service
|
|
|
|
|
lineOut.part_qty = 1;
|
|
|
|
|
lineOut.act_price = isNaN(amt) ? 0 : amt;
|
|
|
|
|
@@ -389,10 +390,12 @@ const extractJobLines = (rq) => {
|
|
|
|
|
if (hasLabor) {
|
|
|
|
|
lineOut.mod_lbr_ty = laborInfo.LaborType || null;
|
|
|
|
|
lineOut.mod_lb_hrs = isNaN(hrs) ? 0 : hrs;
|
|
|
|
|
lineOut.lbr_op = laborInfo.LaborOperation || null;
|
|
|
|
|
lineOut.lbr_op = laborInfo.LaborOperation || null; //PF: can add lbr_op_desc according to mapping available in new partner.
|
|
|
|
|
lineOut.lbr_amt = isNaN(amt) ? 0 : amt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//PF: what's the BMS logic for this? Body and refinish operations can often happen to the same part, but most systems output a second line for the refinish labor.
|
|
|
|
|
//PF: 2nd line may include a duplicate of the part price, but that can be removed. This is the case for CCC.
|
|
|
|
|
// Refinish labor (if present) recorded on the same line using secondary labor fields
|
|
|
|
|
const rHrs = parseFloat(refinishInfo.LaborHours || 0);
|
|
|
|
|
const rAmt = parseFloat(refinishInfo.LaborAmt || 0);
|
|
|
|
|
@@ -403,9 +406,9 @@ const extractJobLines = (rq) => {
|
|
|
|
|
!isNaN(rAmt) ||
|
|
|
|
|
!!refinishInfo.LaborOperation);
|
|
|
|
|
if (hasRefinish) {
|
|
|
|
|
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR";
|
|
|
|
|
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;
|
|
|
|
|
lineOut.lbr_op_j = refinishInfo.LaborOperation || null;
|
|
|
|
|
lineOut.lbr_typ_j = refinishInfo.LaborType || "LAR"; //PF: _j fields indicate judgement, and are bool type.
|
|
|
|
|
lineOut.lbr_hrs_j = isNaN(rHrs) ? 0 : rHrs;//PF: _j fields indicate judgement, and are bool type.
|
|
|
|
|
lineOut.lbr_op_j = refinishInfo.LaborOperation || null; //PF: _j fields indicate judgement, and are bool type.
|
|
|
|
|
// Aggregate refinish labor amount into the total labor amount for the line
|
|
|
|
|
if (!isNaN(rAmt)) {
|
|
|
|
|
lineOut.lbr_amt = (Number.isFinite(lineOut.lbr_amt) ? lineOut.lbr_amt : 0) + rAmt;
|
|
|
|
|
@@ -474,7 +477,7 @@ const computeLinesTotal = (joblines = []) => {
|
|
|
|
|
labor += jl.lbr_amt;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const total = parts + labor;
|
|
|
|
|
const total = parts + labor; //PF: clm_total is the 100% full amount of the repair including deductible, betterment and taxes. Typically provided by the source system.
|
|
|
|
|
return Number.isFinite(total) && total > 0 ? total : 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -523,7 +526,7 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get default status
|
|
|
|
|
const defaultStatus = await getDefaultOrderStatus(shopId, logger);
|
|
|
|
|
const defaultStatus = await getDefaultOrderStatus(shopId, logger); //This likely should be get default job status, not order.
|
|
|
|
|
|
|
|
|
|
// Extract additional data
|
|
|
|
|
const parts_tax_rates = extractPartsTaxRates(rq.ProfileInfo);
|
|
|
|
|
|