feature/IO-3255-simplified-parts-management - vehicleDamageEstimateAddRq.js enhancements
This commit is contained in:
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
const client = require("../../../graphql-client/graphql-client").client;
|
const client = require("../../../graphql-client/graphql-client").client;
|
||||||
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
const { parseXml, normalizeXmlObject } = require("../partsManagementUtils");
|
||||||
const { toArray } = require("lodash");
|
|
||||||
|
|
||||||
// GraphQL Queries and Mutations
|
// GraphQL Queries and Mutations
|
||||||
const {
|
const {
|
||||||
@@ -35,6 +34,8 @@ const KNOWN_PART_RATE_TYPES = [
|
|||||||
"CCM",
|
"CCM",
|
||||||
"CCDR"
|
"CCDR"
|
||||||
];
|
];
|
||||||
|
// Config: include labor lines and labor in totals (default false for development ease)
|
||||||
|
const INCLUDE_LABOR = process.env.PARTS_MGMT_INCLUDE_LABOR === "true";
|
||||||
/**
|
/**
|
||||||
* Fetches the default order status for a bodyshop.
|
* Fetches the default order status for a bodyshop.
|
||||||
* @param {string} shopId - The bodyshop UUID.
|
* @param {string} shopId - The bodyshop UUID.
|
||||||
@@ -60,47 +61,61 @@ const extractPartsTaxRates = (profile = {}) => {
|
|||||||
const rateInfos = Array.isArray(profile.RateInfo) ? profile.RateInfo : [profile.RateInfo || {}];
|
const rateInfos = Array.isArray(profile.RateInfo) ? profile.RateInfo : [profile.RateInfo || {}];
|
||||||
const partsTaxRates = {};
|
const partsTaxRates = {};
|
||||||
|
|
||||||
for (const code of KNOWN_PART_RATE_TYPES) {
|
for (const r of rateInfos) {
|
||||||
const rateInfo = rateInfos.find((r) => {
|
const rateTypeRaw =
|
||||||
const rateType =
|
typeof r?.RateType === "string"
|
||||||
typeof r?.RateType === "string"
|
? r.RateType
|
||||||
? r.RateType
|
: typeof r?.RateType === "object" && r?.RateType._
|
||||||
: typeof r?.RateType === "object" && r?.RateType._ // xml2js sometimes uses _ for text content
|
? r.RateType._
|
||||||
? r.RateType._
|
: "";
|
||||||
: "";
|
const rateType = (rateTypeRaw || "").toUpperCase();
|
||||||
|
if (!KNOWN_PART_RATE_TYPES.includes(rateType)) continue;
|
||||||
|
|
||||||
return rateType.toUpperCase() === code;
|
const taxInfo = r.TaxInfo;
|
||||||
});
|
|
||||||
|
|
||||||
if (!rateInfo) {
|
|
||||||
partsTaxRates[code] = {};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const taxInfo = rateInfo.TaxInfo;
|
|
||||||
const taxTier = taxInfo?.TaxTierInfo;
|
const taxTier = taxInfo?.TaxTierInfo;
|
||||||
let percentage = parseFloat(taxTier?.Percentage ?? "NaN");
|
let percentage = parseFloat(taxTier?.Percentage ?? "NaN");
|
||||||
|
|
||||||
if (isNaN(percentage)) {
|
if (isNaN(percentage)) {
|
||||||
const tierRate = Array.isArray(rateInfo.RateTierInfo)
|
const tierRate = Array.isArray(r.RateTierInfo) ? r.RateTierInfo[0]?.Rate : r.RateTierInfo?.Rate;
|
||||||
? rateInfo.RateTierInfo[0]?.Rate
|
|
||||||
: rateInfo.RateTierInfo?.Rate;
|
|
||||||
percentage = parseFloat(tierRate ?? "NaN");
|
percentage = parseFloat(tierRate ?? "NaN");
|
||||||
}
|
}
|
||||||
|
|
||||||
partsTaxRates[code] = isNaN(percentage)
|
if (!isNaN(percentage)) {
|
||||||
? {}
|
partsTaxRates[rateType] = {
|
||||||
: {
|
prt_discp: 0,
|
||||||
prt_discp: 0,
|
prt_mktyp: false,
|
||||||
prt_mktyp: false,
|
prt_mkupp: 0,
|
||||||
prt_mkupp: 0,
|
prt_tax_in: true,
|
||||||
prt_tax_in: true,
|
prt_tax_rt: percentage / 100
|
||||||
prt_tax_rt: percentage / 100
|
};
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return partsTaxRates;
|
return partsTaxRates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an existing vehicle by shopId and VIN.
|
||||||
|
* @param {string} shopId - The bodyshop UUID.
|
||||||
|
* @param {string} v_vin - The vehicle VIN.
|
||||||
|
* @param {object} logger - The logger instance.
|
||||||
|
* @returns {Promise<string|null>} The vehicle ID or null if not found.
|
||||||
|
*/
|
||||||
|
const findExistingVehicle = async (shopId, v_vin, logger) => {
|
||||||
|
if (!v_vin) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { vehicles } = await client.request(GET_VEHICLE_BY_SHOP_VIN, { shopid: shopId, v_vin });
|
||||||
|
if (vehicles?.length > 0) {
|
||||||
|
logger.log("parts-vehicle-found", "info", vehicles[0].id, null, { shopid: shopId, v_vin });
|
||||||
|
return vehicles[0].id;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.log("parts-vehicle-fetch-failed", "warn", null, null, { error: err });
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts job-related data from the XML request.
|
* Extracts job-related data from the XML request.
|
||||||
* @param {object} rq - The VehicleDamageEstimateAddRq object.
|
* @param {object} rq - The VehicleDamageEstimateAddRq object.
|
||||||
@@ -116,12 +131,14 @@ const extractJobData = (rq) => {
|
|||||||
shopId: rq.ShopID || rq.shopId,
|
shopId: rq.ShopID || rq.shopId,
|
||||||
refClaimNum: rq.RefClaimNum,
|
refClaimNum: rq.RefClaimNum,
|
||||||
ciecaid: rq.RqUID || null,
|
ciecaid: rq.RqUID || null,
|
||||||
cieca_ttl: parseFloat(rq.Cieca_ttl || 0),
|
// Pull Cieca_ttl from ClaimInfo per schema/sample
|
||||||
|
cieca_ttl: parseFloat(ci.Cieca_ttl || 0),
|
||||||
cat_no: doc.VendorCode || null,
|
cat_no: doc.VendorCode || null,
|
||||||
category: doc.DocumentType || null,
|
category: doc.DocumentType || null,
|
||||||
classType: doc.DocumentStatus || null,
|
classType: doc.DocumentStatus || null,
|
||||||
comment: doc.Comment || null,
|
comment: doc.Comment || null,
|
||||||
date_exported: doc.TransmitDateTime || null,
|
// TODO: This causes the job to be read only in the UI
|
||||||
|
// date_exported: doc.TransmitDateTime || null,
|
||||||
asgn_no: asgn.AssignmentNumber || null,
|
asgn_no: asgn.AssignmentNumber || null,
|
||||||
asgn_type: asgn.AssignmentType || null,
|
asgn_type: asgn.AssignmentType || null,
|
||||||
asgn_date: asgn.AssignmentDate || null,
|
asgn_date: asgn.AssignmentDate || null,
|
||||||
@@ -130,23 +147,8 @@ const extractJobData = (rq) => {
|
|||||||
scheduled_completion: ev.RepairEvent?.TargetCompletionDateTime || null,
|
scheduled_completion: ev.RepairEvent?.TargetCompletionDateTime || null,
|
||||||
clm_no: ci.ClaimNum || null,
|
clm_no: ci.ClaimNum || null,
|
||||||
status: ci.ClaimStatus || null,
|
status: ci.ClaimStatus || null,
|
||||||
policy_no: ci.PolicyInfo?.PolicyNum || null,
|
policy_no: ci.PolicyInfo?.PolicyInfo?.PolicyNum || ci.PolicyInfo?.PolicyNum || null,
|
||||||
ded_amt: parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0)
|
ded_amt: parseFloat(ci.PolicyInfo?.CoverageInfo?.Coverage?.DeductibleInfo?.DeductibleAmt || 0)
|
||||||
// document_id: doc.DocumentID || null,
|
|
||||||
// bms_version: doc.BMSVer || null,
|
|
||||||
// reference_info:
|
|
||||||
// doc.ReferenceInfo?.OtherReferenceInfo?.map((ref) => ({
|
|
||||||
// name: ref.OtherReferenceName,
|
|
||||||
// number: ref.OtherRefNum
|
|
||||||
// })) || [],
|
|
||||||
// currency_info: doc.CurrencyInfo
|
|
||||||
// ? {
|
|
||||||
// code: doc.CurrencyInfo.CurCode,
|
|
||||||
// base_code: doc.CurrencyInfo.BaseCurCode,
|
|
||||||
// rate: parseFloat(doc.CurrencyInfo.CurRate || 0),
|
|
||||||
// rule: doc.CurrencyInfo.CurConvertRule
|
|
||||||
// }
|
|
||||||
// : null
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -340,7 +342,8 @@ const extractVehicleData = (rq, shopId) => {
|
|||||||
const interior = rq.VehicleInfo?.Paint?.Interior || {};
|
const interior = rq.VehicleInfo?.Paint?.Interior || {};
|
||||||
return {
|
return {
|
||||||
shopid: shopId,
|
shopid: shopId,
|
||||||
v_vin: rq.VehicleInfo?.VINInfo?.VIN?.VINNum || null,
|
// VIN may be either VINInfo.VINNum or VINInfo.VIN.VINNum depending on producer
|
||||||
|
v_vin: rq.VehicleInfo?.VINInfo?.VINNum || rq.VehicleInfo?.VINInfo?.VIN?.VINNum || null,
|
||||||
plate_no: rq.VehicleInfo?.License?.LicensePlateNum || null,
|
plate_no: rq.VehicleInfo?.License?.LicensePlateNum || null,
|
||||||
plate_st: rq.VehicleInfo?.License?.LicensePlateStateProvince || null,
|
plate_st: rq.VehicleInfo?.License?.LicensePlateStateProvince || null,
|
||||||
v_model_yr: desc.ModelYear || null,
|
v_model_yr: desc.ModelYear || null,
|
||||||
@@ -363,8 +366,6 @@ const extractVehicleData = (rq, shopId) => {
|
|||||||
v_makecode: desc.MakeCode || null,
|
v_makecode: desc.MakeCode || null,
|
||||||
trim_color: interior.ColorName || desc.TrimColor || null,
|
trim_color: interior.ColorName || desc.TrimColor || null,
|
||||||
db_v_code: desc.DatabaseCode || null
|
db_v_code: desc.DatabaseCode || null
|
||||||
// v_model_num: desc.ModelNum || null
|
|
||||||
// v_odo: desc.OdometerInfo?.OdometerReading || null
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -374,71 +375,154 @@ const extractVehicleData = (rq, shopId) => {
|
|||||||
* @returns {object[]} Array of job line objects.
|
* @returns {object[]} Array of job line objects.
|
||||||
*/
|
*/
|
||||||
const extractJobLines = (rq) => {
|
const extractJobLines = (rq) => {
|
||||||
const damageLines = toArray(rq.DamageLineInfo);
|
// Normalize to array without lodash toArray (which flattens object values incorrectly)
|
||||||
|
const dl = rq.DamageLineInfo;
|
||||||
|
const damageLines = Array.isArray(dl) ? dl : dl ? [dl] : [];
|
||||||
if (damageLines.length === 0) {
|
if (damageLines.length === 0) {
|
||||||
return []; // Or throw if required
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return damageLines.map((line) => {
|
const out = [];
|
||||||
|
|
||||||
|
for (const line of damageLines) {
|
||||||
const partInfo = line.PartInfo || {};
|
const partInfo = line.PartInfo || {};
|
||||||
const laborInfo = line.LaborInfo || {};
|
const laborInfo = line.LaborInfo || {};
|
||||||
|
const refinishInfo = line.RefinishLaborInfo || {};
|
||||||
const subletInfo = line.SubletInfo || {};
|
const subletInfo = line.SubletInfo || {};
|
||||||
|
|
||||||
let jobLineType = "PART";
|
let jobLineType = "PART";
|
||||||
if (Object.keys(subletInfo).length > 0) jobLineType = "SUBLET";
|
if (Object.keys(subletInfo).length > 0) jobLineType = "SUBLET";
|
||||||
else if (Object.keys(laborInfo).length > 0 && Object.keys(partInfo).length === 0) jobLineType = "LABOR";
|
else if (Object.keys(laborInfo).length > 0 && Object.keys(partInfo).length === 0) jobLineType = "LABOR";
|
||||||
|
|
||||||
const jobLine = {
|
const base = {
|
||||||
line_no: parseInt(line.LineNum || 0, 10),
|
line_no: parseInt(line.LineNum || 0, 10),
|
||||||
unq_seq: parseInt(line.UniqueSequenceNum || 0, 10),
|
unq_seq: parseInt(line.UniqueSequenceNum || 0, 10),
|
||||||
status: line.LineStatusCode || null,
|
status: line.LineStatusCode || null,
|
||||||
line_desc: line.LineDesc || null
|
line_desc: line.LineDesc || null,
|
||||||
// line_type: jobLineType // New field for clarity
|
notes: line.LineMemo || null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (jobLineType === "PART") {
|
if (jobLineType === "PART") {
|
||||||
jobLine.part_type = partInfo.PartType || null;
|
const price = parseFloat(partInfo.PartPrice || partInfo.ListPrice || 0);
|
||||||
jobLine.part_qty = parseFloat(partInfo.Quantity || 0);
|
out.push({
|
||||||
jobLine.oem_partno = partInfo.OEMPartNum || null;
|
...base,
|
||||||
jobLine.db_price = parseFloat(partInfo.PartPrice || 0);
|
part_type: partInfo.PartType || null,
|
||||||
jobLine.act_price = parseFloat(partInfo.PartPrice || 0); // Or NonOEM if selected
|
part_qty: parseFloat(partInfo.Quantity || 0) || 1,
|
||||||
|
oem_partno: partInfo.OEMPartNum || null,
|
||||||
|
db_price: price,
|
||||||
|
act_price: price,
|
||||||
|
// Labor fields if present on same line
|
||||||
|
mod_lbr_ty: laborInfo.LaborType || null,
|
||||||
|
mod_lb_hrs: parseFloat(laborInfo.LaborHours || 0),
|
||||||
|
lbr_op: laborInfo.LaborOperation || null,
|
||||||
|
lbr_amt: INCLUDE_LABOR ? parseFloat(laborInfo.LaborAmt || 0) : 0,
|
||||||
|
// Tax flag from PartInfo.TaxableInd when provided
|
||||||
|
...(partInfo.TaxableInd !== undefined
|
||||||
|
? {
|
||||||
|
tax_part:
|
||||||
|
partInfo.TaxableInd === true ||
|
||||||
|
partInfo.TaxableInd === 1 ||
|
||||||
|
partInfo.TaxableInd === "1" ||
|
||||||
|
(typeof partInfo.TaxableInd === "string" && partInfo.TaxableInd.toUpperCase() === "Y")
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
// Manual line flag coercion
|
||||||
|
...(line.ManualLineInd !== undefined
|
||||||
|
? {
|
||||||
|
manual_line:
|
||||||
|
line.ManualLineInd === true ||
|
||||||
|
line.ManualLineInd === 1 ||
|
||||||
|
line.ManualLineInd === "1" ||
|
||||||
|
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||||
|
}
|
||||||
|
: { manual_line: null })
|
||||||
|
});
|
||||||
} else if (jobLineType === "SUBLET") {
|
} else if (jobLineType === "SUBLET") {
|
||||||
jobLine.part_type = "SUB"; // Custom code for sublet
|
out.push({
|
||||||
jobLine.part_qty = 1; // Default
|
...base,
|
||||||
jobLine.act_price = parseFloat(subletInfo.SubletAmount || 0);
|
part_type: "PAS",
|
||||||
// jobLine.sublet_vendor = subletInfo.SubletVendorName || null; // TODO: Clarify
|
part_qty: 1,
|
||||||
// jobLine.lbr_hrs = parseFloat(subletInfo.SubletLaborHours || 0); // TODO: Clarify
|
act_price: parseFloat(subletInfo.SubletAmount || 0),
|
||||||
} // Labor-only already handled
|
// Manual line flag
|
||||||
|
...(line.ManualLineInd !== undefined
|
||||||
|
? {
|
||||||
|
manual_line:
|
||||||
|
line.ManualLineInd === true ||
|
||||||
|
line.ManualLineInd === 1 ||
|
||||||
|
line.ManualLineInd === "1" ||
|
||||||
|
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||||
|
}
|
||||||
|
: { manual_line: null })
|
||||||
|
});
|
||||||
|
} else if (INCLUDE_LABOR) {
|
||||||
|
// Labor-only line (only when enabled)
|
||||||
|
out.push({
|
||||||
|
...base,
|
||||||
|
mod_lbr_ty: laborInfo.LaborType || null,
|
||||||
|
mod_lb_hrs: parseFloat(laborInfo.LaborHours || 0),
|
||||||
|
lbr_op: laborInfo.LaborOperation || null,
|
||||||
|
lbr_amt: parseFloat(laborInfo.LaborAmt || 0),
|
||||||
|
...(line.ManualLineInd !== undefined
|
||||||
|
? {
|
||||||
|
manual_line:
|
||||||
|
line.ManualLineInd === true ||
|
||||||
|
line.ManualLineInd === 1 ||
|
||||||
|
line.ManualLineInd === "1" ||
|
||||||
|
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||||
|
}
|
||||||
|
: { manual_line: null })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
jobLine.mod_lbr_ty = laborInfo.LaborType || null;
|
// Add a separate refinish labor line if present and enabled
|
||||||
jobLine.mod_lb_hrs = parseFloat(laborInfo.LaborHours || 0);
|
if (INCLUDE_LABOR && Object.keys(refinishInfo).length > 0) {
|
||||||
jobLine.lbr_op = laborInfo.LaborOperation || null;
|
const hrs = parseFloat(refinishInfo.LaborHours || 0);
|
||||||
jobLine.lbr_amt = parseFloat(laborInfo.LaborAmt || 0);
|
const amt = parseFloat(refinishInfo.LaborAmt || 0);
|
||||||
jobLine.notes = line.LineMemo || null;
|
if (!isNaN(hrs) || !isNaN(amt)) {
|
||||||
jobLine.manual_line = line.ManualLineInd || null;
|
out.push({
|
||||||
|
...base,
|
||||||
|
// tweak unq_seq to avoid collisions in later upserts
|
||||||
|
unq_seq: (parseInt(line.UniqueSequenceNum || 0, 10) || 0) + 500000,
|
||||||
|
line_desc: base.line_desc || "Refinish",
|
||||||
|
mod_lbr_ty: "LAR",
|
||||||
|
mod_lb_hrs: isNaN(hrs) ? 0 : hrs,
|
||||||
|
lbr_op: refinishInfo.LaborOperation || null,
|
||||||
|
lbr_amt: isNaN(amt) ? 0 : amt,
|
||||||
|
...(line.ManualLineInd !== undefined
|
||||||
|
? {
|
||||||
|
manual_line:
|
||||||
|
line.ManualLineInd === true ||
|
||||||
|
line.ManualLineInd === 1 ||
|
||||||
|
line.ManualLineInd === "1" ||
|
||||||
|
(typeof line.ManualLineInd === "string" && line.ManualLineInd.toUpperCase() === "Y")
|
||||||
|
}
|
||||||
|
: { manual_line: null })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return jobLine;
|
return out;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// Helper to extract a GRAND TOTAL amount from RepairTotalsInfo
|
||||||
* Finds an existing vehicle by shopId and VIN.
|
const extractGrandTotal = (rq) => {
|
||||||
* @param {string} shopId - The bodyshop UUID.
|
const rti = rq.RepairTotalsInfo;
|
||||||
* @param {string} v_vin - The vehicle VIN.
|
const groups = Array.isArray(rti) ? rti : rti ? [rti] : [];
|
||||||
* @param {object} logger - The logger instance.
|
for (const grp of groups) {
|
||||||
* @returns {Promise<string|null>} The vehicle ID or null if not found.
|
const sums = Array.isArray(grp.SummaryTotalsInfo)
|
||||||
*/
|
? grp.SummaryTotalsInfo
|
||||||
const findExistingVehicle = async (shopId, v_vin, logger) => {
|
: grp.SummaryTotalsInfo
|
||||||
if (!v_vin) return null;
|
? [grp.SummaryTotalsInfo]
|
||||||
|
: [];
|
||||||
try {
|
for (const s of sums) {
|
||||||
const { vehicles } = await client.request(GET_VEHICLE_BY_SHOP_VIN, { shopid: shopId, v_vin });
|
const type = (s.TotalType || "").toString().toUpperCase();
|
||||||
if (vehicles?.length > 0) {
|
const desc = (s.TotalTypeDesc || "").toString().toUpperCase();
|
||||||
logger.log("parts-vehicle-found", "info", vehicles[0].id, null, { shopid: shopId, v_vin });
|
if (type.includes("GRAND") || type === "TOTAL" || desc.includes("GRAND")) {
|
||||||
return vehicles[0].id;
|
const amt = parseFloat(s.TotalAmt ?? "NaN");
|
||||||
|
if (!isNaN(amt)) return amt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
logger.log("parts-vehicle-fetch-failed", "warn", null, null, { error: err });
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -459,6 +543,26 @@ const insertOwner = async (ownerInput, logger) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fallback: compute a naive total from joblines (parts + sublet + labor amounts)
|
||||||
|
const computeLinesTotal = (joblines = []) => {
|
||||||
|
let parts = 0;
|
||||||
|
let labor = 0;
|
||||||
|
for (const jl of joblines) {
|
||||||
|
if (jl && jl.part_type) {
|
||||||
|
const qty = Number.isFinite(jl.part_qty) ? jl.part_qty : 1;
|
||||||
|
const price = Number.isFinite(jl.act_price) ? jl.act_price : 0;
|
||||||
|
parts += price * (qty || 1);
|
||||||
|
} else if (!jl.part_type && Number.isFinite(jl.act_price)) {
|
||||||
|
parts += jl.act_price;
|
||||||
|
}
|
||||||
|
if (INCLUDE_LABOR && Number.isFinite(jl.lbr_amt)) {
|
||||||
|
labor += jl.lbr_amt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const total = parts + labor;
|
||||||
|
return Number.isFinite(total) && total > 0 ? total : 0;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the VehicleDamageEstimateAddRq XML request from parts management.
|
* Handles the VehicleDamageEstimateAddRq XML request from parts management.
|
||||||
* @param {object} req - The HTTP request object.
|
* @param {object} req - The HTTP request object.
|
||||||
@@ -517,6 +621,10 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
const joblinesData = extractJobLines(rq);
|
const joblinesData = extractJobLines(rq);
|
||||||
const insuranceData = extractInsuranceData(rq);
|
const insuranceData = extractInsuranceData(rq);
|
||||||
|
|
||||||
|
// Derive clm_total: prefer RepairTotalsInfo SummaryTotals GRAND TOTAL; else sum from lines
|
||||||
|
const grandTotal = extractGrandTotal(rq);
|
||||||
|
const computedTotal = grandTotal ?? computeLinesTotal(joblinesData);
|
||||||
|
|
||||||
// Find or create relationships
|
// Find or create relationships
|
||||||
const ownerid = await insertOwner(ownerData, logger);
|
const ownerid = await insertOwner(ownerData, logger);
|
||||||
const vehicleid = await findExistingVehicle(shopId, vehicleData.v_vin, logger);
|
const vehicleid = await findExistingVehicle(shopId, vehicleData.v_vin, logger);
|
||||||
@@ -524,6 +632,7 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
// Build job input
|
// Build job input
|
||||||
const jobInput = {
|
const jobInput = {
|
||||||
shopid: shopId,
|
shopid: shopId,
|
||||||
|
converted: true,
|
||||||
ownerid,
|
ownerid,
|
||||||
ro_number: refClaimNum,
|
ro_number: refClaimNum,
|
||||||
ciecaid,
|
ciecaid,
|
||||||
@@ -534,7 +643,7 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
parts_tax_rates,
|
parts_tax_rates,
|
||||||
clm_no,
|
clm_no,
|
||||||
status: status || defaultStatus,
|
status: status || defaultStatus,
|
||||||
clm_total: cieca_ttl,
|
clm_total: computedTotal || null,
|
||||||
policy_no,
|
policy_no,
|
||||||
ded_amt,
|
ded_amt,
|
||||||
comment,
|
comment,
|
||||||
@@ -544,12 +653,13 @@ const vehicleDamageEstimateAddRq = async (req, res) => {
|
|||||||
asgn_date,
|
asgn_date,
|
||||||
scheduled_in,
|
scheduled_in,
|
||||||
scheduled_completion,
|
scheduled_completion,
|
||||||
...insuranceData, // Inline insurance data
|
// Inline insurance/loss/contacts
|
||||||
...lossInfo, // Inline loss information
|
...insuranceData,
|
||||||
...ownerData, // Inline owner data
|
...lossInfo,
|
||||||
...estimatorData, // Inline estimator data
|
...ownerData,
|
||||||
...adjusterData, // Inline adjuster data
|
...estimatorData,
|
||||||
...repairFacilityData, // Inline repair facility data
|
...adjusterData,
|
||||||
|
...repairFacilityData,
|
||||||
// Inline vehicle data
|
// Inline vehicle data
|
||||||
v_vin: vehicleData.v_vin,
|
v_vin: vehicleData.v_vin,
|
||||||
v_model_yr: vehicleData.v_model_yr,
|
v_model_yr: vehicleData.v_model_yr,
|
||||||
|
|||||||
431
server/integrations/partsManagement/sampleData/schemaData.md
Normal file
431
server/integrations/partsManagement/sampleData/schemaData.md
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
Awesome — thanks for the dumps. I pulled the structures directly from the XSDs you uploaded and
|
||||||
|
focused on **`VehicleDamageEstimateAddRq`** and the graph of types it depends on. Below is a
|
||||||
|
developer-grade map you can hand to a coding agent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# What it is & where it lives
|
||||||
|
|
||||||
|
* **Global element**: `VehicleDamageEstimateAddRq`
|
||||||
|
* **Namespace**: `http://www.cieca.com/BMS` (default ns in your files)
|
||||||
|
* **Defined in**: `BMSEstimateMessages_2024R1_V6.9.0.xsd`
|
||||||
|
* **Type**: `EstimateRqType` (from `BMSEstimateCommonTypes_2024R1_V6.9.0.xsd`)
|
||||||
|
* **Service group** (where this message is accepted): `EstimateService` in
|
||||||
|
`BMSEstimateService_2024R1_V6.9.0.xsd`
|
||||||
|
Includes: `PropertyDamageEstimateAddRq/Rs`, `VehicleDamageEstimateAddRq/Rs`,
|
||||||
|
`VehicleDamageEstimateChgRq/Rs`, `VehicleDamagePhotoEstimateAddRq/Rs`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Top-level schema (for `VehicleDamageEstimateAddRq` → `EstimateRqType`)
|
||||||
|
|
||||||
|
`EstimateRqType` **extends** `MessageHeaderType` (from `BMSCommonGlobalTypes_2024R1_V6.9.0.xsd`) and
|
||||||
|
then adds the following sequence. I’ve marked **required** vs *optional* and multiplicity:
|
||||||
|
|
||||||
|
**Header (inherited from `MessageHeaderType`):**
|
||||||
|
|
||||||
|
* **`RqUID`** (UUID) — **required**
|
||||||
|
* `AsyncRqUID` (UUID) — *optional*
|
||||||
|
* `PartnerKey` (Identifier) — *optional*
|
||||||
|
|
||||||
|
**Body (from `EstimateRqType`):**
|
||||||
|
|
||||||
|
* `SvcProviderName` (Identifier) — *optional*
|
||||||
|
* `RefClaimNum` (Char\_50) — *optional*
|
||||||
|
* **`DocumentInfo`** (`DocumentInfoType`) — **required, 1**
|
||||||
|
* **`ApplicationInfo`** (`ApplicationInfoType`) — **required, 1..**\*
|
||||||
|
* `EventInfo` (`EventInfoType`) — *optional*
|
||||||
|
* **`AdminInfo`** (`AdminInfoType`) — **required**
|
||||||
|
* **`EstimatorIDs`** (`EstimatorIDsTypeType`) — **required**
|
||||||
|
* `ClaimInfo` (`ClaimInfoType`) — *optional*
|
||||||
|
* **`VehicleInfo`** (`VehicleInfoType`) **OR** `PropertyInfo` (`PropertyInfoType`) — **choice** →
|
||||||
|
for vehicle, use **`VehicleInfo`**
|
||||||
|
* **`ProfileInfo`** (`ProfileInfoType`) — **required**
|
||||||
|
* **`DamageLineInfo`** (`DamageLineInfoType`) — **required, 1..**\* (line items)
|
||||||
|
* `CalibrationInfo` (`CalibrationInfoType`) — *optional, 0..*
|
||||||
|
* `ScanInfo` (`ScanInfoType`) — *optional, 0..*
|
||||||
|
* `FileAttachment` (`FileAttachmentType`) — *optional*
|
||||||
|
* `NonNewOEMPartInd` (Boolean) — *optional*
|
||||||
|
* `StorageDuration` (Integer\_Range\_0-999) — *optional*
|
||||||
|
* **`RepairTotalsInfo`** (`RepairTotalsInfoType`) — **required, 1..**\*
|
||||||
|
* `RepairTotalsHistory` (`RepairTotalsHistoryType`) — *optional, 0..*
|
||||||
|
* `PaymentInfo` (`PaymentInfoType`) — *optional*
|
||||||
|
* `EstimateMemo` (C) — *optional*
|
||||||
|
* `AdministrativeMemo` (C) — *optional*
|
||||||
|
* `Disclaimers` (C) — *optional*
|
||||||
|
* `CustomMemo` (C) — *optional*
|
||||||
|
* `CustomPrintImage` (C) — *optional*
|
||||||
|
* `OtherMemos` (`OtherMemosType`) — *optional, 0..*
|
||||||
|
|
||||||
|
**Files involved:**
|
||||||
|
`BMSEstimateMessages_2024R1_V6.9.0.xsd`, `BMSEstimateCommonTypes_2024R1_V6.9.0.xsd`,
|
||||||
|
`BMSCommonGlobalTypes_2024R1_V6.9.0.xsd`, `BMSSimpleTypes_2024R1_V6.9.0.xsd` + code lists XSDs for
|
||||||
|
enums.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Key dependent types (immediate children you’ll actually populate)
|
||||||
|
|
||||||
|
Below are the **first-level** structures you’ll typically use. I’ve trimmed to the practical fields;
|
||||||
|
each type has many optional parties and details you can ignore for a minimal AddRq.
|
||||||
|
|
||||||
|
## `DocumentInfoType` (BMSCommonGlobalTypes)
|
||||||
|
|
||||||
|
Typical header metadata:
|
||||||
|
|
||||||
|
* **`BMSVer`** (`BMSVersionClosedEnumType`) — e.g. **`6.9.0`**
|
||||||
|
* **`DocumentType`** (`DocumentTypeClosedEnumType`) — code for message family (e.g. `E` for
|
||||||
|
estimate; codelists provide the letter codes)
|
||||||
|
* `DocumentSubType` (`DocumentSubTypeClosedEnumType`) — e.g. “Original Estimate”, “Copy”, etc.
|
||||||
|
* `DocumentID` (Char\_50) — your ID
|
||||||
|
* `VendorCode` (VendorCodeOpenEnumType) — optional
|
||||||
|
* `DocumentVer` (`DocumentVerType`) — versioning container (0..\*)
|
||||||
|
* **`CreateDateTime`** (DateTime)
|
||||||
|
* `TransmitDateTime` (DateTime)
|
||||||
|
* `ReferenceInfo` (`RefInfoType`) — links to prior docs
|
||||||
|
* `CountryCode`, `CurrencyInfo`, `CultureCode` — optional locale bits
|
||||||
|
|
||||||
|
## `ApplicationInfoType` (BMSCommonGlobalTypes) **(1..\*)**
|
||||||
|
|
||||||
|
* **`ApplicationType`** (`ApplicationTypeClosedEnumType`) — e.g., Estimator, Shop Mgmt, etc.
|
||||||
|
* **`ApplicationName`** (Char\_30)
|
||||||
|
* **`ApplicationVer`** (Char\_12)
|
||||||
|
* `DatabaseVer` (Char\_12)
|
||||||
|
* `DatabaseDateTime` (DateTime)
|
||||||
|
|
||||||
|
## `AdminInfoType` (BMSCommonGlobalTypes)
|
||||||
|
|
||||||
|
Large party/role roster; **all child elements are optional**, but the container itself is required.
|
||||||
|
Common ones:
|
||||||
|
|
||||||
|
* `InsuranceCompany` (`InsuranceCompanyType`)
|
||||||
|
* `PolicyHolder` (`PolicyHolderType`)
|
||||||
|
* `Insured` / `Owner` / `Customer` (`GenericPartyType`)
|
||||||
|
* `Claimant` (`ClaimantType`)
|
||||||
|
* `Estimator` (0..\*) (`EstimatorType`)
|
||||||
|
* `RepairFacility` (`RepairFacilityType`)
|
||||||
|
* `RentalProvider`, `TowCompany`, `Lender`, `Lienholder` (0..\*), etc.
|
||||||
|
(You can send `<AdminInfo/>` if you don’t need parties; it validates.)
|
||||||
|
|
||||||
|
## `EstimatorIDsTypeType` (BMSEstimateCommonTypes)
|
||||||
|
|
||||||
|
* `OriginalEstimatorID` (Char\_40) — optional
|
||||||
|
* `EstimatorHistory` (0..\*) → `EstimatorHistoryType` ⇒ (`DocumentVerCode`, `DocumentVerNum`)
|
||||||
|
|
||||||
|
## `ClaimInfoType` (BMSCommonGlobalTypes) *(optional)*
|
||||||
|
|
||||||
|
* `ClaimNum` (Char\_50)
|
||||||
|
* `PolicyInfo` (0..\*) (`PolicyInfoType`)
|
||||||
|
* `LossInfo` (`LossInfoType`) — details on loss/time/location/coverage
|
||||||
|
* `AdditionalIDInfo` (0..\*) (`IDInfoType`)
|
||||||
|
* `ClaimStatus`, `PreviousPaymentAmt`, `ClaimMemo`, etc.
|
||||||
|
|
||||||
|
## `VehicleInfoType` (BMSCommonGlobalTypes) *(choose this over PropertyInfo)*
|
||||||
|
|
||||||
|
* `VINInfo` (0..\*) (`VINInfoType`) → **choice** of `VINAvailabilityCode` or one or more `VIN` (
|
||||||
|
`VINType`)
|
||||||
|
* `License` (`LicenseType`)
|
||||||
|
* `VehicleDesc` (`VehicleDescType`) — **ModelYear**, **MakeDesc/MakeCode**, **ModelName/ModelNum**,
|
||||||
|
`VehicleType`, etc.
|
||||||
|
* `Paint`, `Body`, `Powertrain`, `Condition`, `Valuation`, `VehicleMemo`
|
||||||
|
* `PolicyVehicleNum`, `LossVehicleNum`
|
||||||
|
* `FileAttachment` (`FileAttachmentType`)
|
||||||
|
* `CustomElement` (0..\*)
|
||||||
|
* `UnitNum` (Char\_20)
|
||||||
|
|
||||||
|
> Note: `VINType` is referenced but its concrete restriction is provided elsewhere in BMS; you can
|
||||||
|
> treat it as a VIN string (17-char typical) and your validator will enforce the real facet.
|
||||||
|
|
||||||
|
## `ProfileInfoType` (BMSEstimateCommonTypes) **required**
|
||||||
|
|
||||||
|
Controls rates, tax, and rules used to compute totals:
|
||||||
|
|
||||||
|
* `ProfileName` (Char\_40)
|
||||||
|
* **`RateInfo`** (1..\*) (`RateInfoType`)
|
||||||
|
|
||||||
|
* `RateType` (`RateTypeClosedEnumType`) — e.g., BODY\_LABOR, PAINT\_LABOR, MECHANICAL\_LABOR,
|
||||||
|
MATERIAL, etc.
|
||||||
|
* `RateTierInfo` / `RateTierHistory` (0..\*)
|
||||||
|
* `TaxableInd`, `TaxRate`, `AdjustmentInfo` (0..*), `TaxInfo` (0..*)
|
||||||
|
* `MaterialCalcSettings` (optional)
|
||||||
|
* `AlternatePartInfo` (0..*), `PartCertification` (0..*), `RefinishCalcSettings`,
|
||||||
|
`PreTaxDiscountRate`, `TaxExemptInfo` (0..\*), `CanadianTax` (for CA specifics)
|
||||||
|
|
||||||
|
## `DamageLineInfoType` (BMSEstimateCommonTypes) **1..**\*
|
||||||
|
|
||||||
|
One per estimate line. Core children:
|
||||||
|
|
||||||
|
* `LineNum`, `UniqueSequenceNum`, `ParentLineNum` (hierarchy)
|
||||||
|
* `ManualLineInd`, `AutomatedEntry`, `LineStatusCode`
|
||||||
|
* `LineDesc`, `LineDescCode`
|
||||||
|
* `SubletInfo` (`SubletInfoType`)
|
||||||
|
* `PartInfo` (0..\*) (`PartInfoType`)
|
||||||
|
* `LaborInfo` (`LaborInfoType`)
|
||||||
|
* `RefinishLaborInfo` (`LaborInfoType`)
|
||||||
|
* `MaterialType`, `OtherChargesInfo`, `WhoPays`
|
||||||
|
* `LineAdjustment`, `AppliedAdjustment`
|
||||||
|
* `PDRInfo`, `LineType`, `LineMemo`, `VendorRefNum` (0..\*)
|
||||||
|
|
||||||
|
**`PartInfoType`** highlights:
|
||||||
|
|
||||||
|
* `PartMaterialCode`, `PartType`, `LineItemCategoryCode`
|
||||||
|
* `PartDesc`, `PartNum`, `OEMPartNum`
|
||||||
|
* `NonOEM` (0..\*) (`NonOEMType`) — alternate sources/quality
|
||||||
|
* `ListPrice`, `PartPrice`, `UnitPartPrice`, `TotalPartPrice`, `OEMPartPrice`
|
||||||
|
* `PriceAdjustment` (0..\*) (`PriceAdjustmentType`)
|
||||||
|
* `TaxableInd`, `AppliedTaxes`
|
||||||
|
* `CertificationType` (0..\*), `AlternatePartInd`, `GlassPartInd`
|
||||||
|
* `Quantity`, `PartStatus`, `Dimensions`, `Glass*`, `QuotedPartList` …
|
||||||
|
|
||||||
|
**`LaborInfoType`** highlights:
|
||||||
|
|
||||||
|
* **`LaborType`** (`LaborTypeClosedEnumType`) — **required**
|
||||||
|
* `LaborOperation`, `LaborHours`, `LaborHourlyRate`, `LaborAmt`
|
||||||
|
* `DatabaseLaborType/Hours/Amt`
|
||||||
|
* `LaborAdjustment` (0..\*)
|
||||||
|
* Judgment/flags (e.g., `LaborAmtJudgmentInd`, `OverlapInd`)
|
||||||
|
* Paint-specific fields (`PaintStagesNum`, `PaintTonesNum`)
|
||||||
|
* `AssemblyLaborCode`
|
||||||
|
|
||||||
|
## `CalibrationInfoType` / `ScanInfoType` (BMSEstimateCommonTypes)
|
||||||
|
|
||||||
|
* **`ScanInfoType`**: `ScanDetailsList` (optional), `FileAttachment` (optional), `ScanTool`,
|
||||||
|
`ScanDateTime` (**required**), flags `CleanScanInd`, `FollowUpInd`, plus `Technician`.
|
||||||
|
* **`CalibrationInfoType`**: optional lists for details & technicians, plus process flags (
|
||||||
|
`PrerequisitesMetInd`, `ProceduresFollowedInd`, `ADASReviewedWithOwnerInd`).
|
||||||
|
|
||||||
|
## `FileAttachmentType` (BMSCommonGlobalTypes)
|
||||||
|
|
||||||
|
* `DocAttachment` (0..\*) (`DocAttachmentType`)
|
||||||
|
|
||||||
|
* `AttachmentType` (open enum)
|
||||||
|
* `AttachmentTitle` **or** `AttachmentMemo`
|
||||||
|
* `AttachmentFileType`, `AttachmentFileName`, `AttachmentLength`
|
||||||
|
* **One of:** `AttachmentURI` **or** `EmbeddedAttachmentType`
|
||||||
|
|
||||||
|
* `EmbeddedAttachmentType` → **choice**: `EmbeddedAttachment` (Binary) **or**
|
||||||
|
`EmbeddedAttachmentText` (C)
|
||||||
|
* `AttachmentIntegrity` (0..\*) (optionally includes Binary integrity blobs)
|
||||||
|
* `AttachmentStatusCode` (open enum)
|
||||||
|
|
||||||
|
## `RepairTotalsInfoType` (BMSEstimateCommonTypes) **1..**\*
|
||||||
|
|
||||||
|
* `LaborTotalsInfo` (0..\*) (`TotalsInfoType`)
|
||||||
|
* `PartsTotalsInfo` (0..\*) (`TotalsInfoType`)
|
||||||
|
* `OtherChargesTotalsInfo` (0..\*) (`TotalsInfoType`)
|
||||||
|
* `NumOfDamageLines` (optional)
|
||||||
|
* **`SummaryTotalsInfo`** (1..\*) (`TotalsInfoType`) — your rolled-up totals
|
||||||
|
* `RepairTotalsType` (`LineTypeClosedEnumType`) — optional (e.g., gross vs. customer-pay segments)
|
||||||
|
|
||||||
|
**`TotalsInfoType`** (BMSCommonGlobalTypes) highlights:
|
||||||
|
|
||||||
|
* **`TotalType`** (`TotalTypeOpenEnumType`) — category (e.g., LABOR, PARTS, TAX, GRAND\_TOTAL,…)
|
||||||
|
* `TotalSubType` (open enum)
|
||||||
|
* **`TotalTypeDesc`** (Char\_30)
|
||||||
|
* Hours quantities & units, item quantity, unit price
|
||||||
|
* Detailed `TotalTaxInfo` / `TotalAdjustmentInfo` (0..\*)
|
||||||
|
* Amounts: `NonTaxableAmt`, `TaxableAmt`, `TaxTotalAmt`, `OtherCharges*`, **`TotalAmt`**,
|
||||||
|
`TotalPct`, `TotalCost`
|
||||||
|
* `AmtDueInfo` (0..\*)
|
||||||
|
|
||||||
|
## `RepairTotalsHistoryType` (BMSEstimateCommonTypes)
|
||||||
|
|
||||||
|
* Version stamp and one or more `HistoryTotalsInfo` entries.
|
||||||
|
|
||||||
|
## `PaymentInfoType` (BMSCommonGlobalTypes) *(optional)*
|
||||||
|
|
||||||
|
* `PayerType`, `PaymentType`
|
||||||
|
* `Payee`/`PayerInfo`/`PayeeInfo`
|
||||||
|
* `PaymentDateTime`, **`PaymentAmt`**
|
||||||
|
* `PaymentID`, `PaymentMemo`, `PaymentAmtType`
|
||||||
|
|
||||||
|
## `OtherMemosType` (BMSCommonGlobalTypes)
|
||||||
|
|
||||||
|
* `OtherMemoRef` (open enum), `OtherMemo` (C)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Minimal, schema-valid XML skeleton (vehicle path)
|
||||||
|
|
||||||
|
> Uses only **required** containers/fields; values shown as **PLACEHOLDER**.
|
||||||
|
> You must add at least one **DamageLineInfo** and one **SummaryTotalsInfo** item, and at least one
|
||||||
|
**RateInfo** inside **ProfileInfo**.
|
||||||
|
> Enumerations are *code lists*; use valid codes from your system.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
|
||||||
|
<VehicleDamageEstimateAddRq xmlns="http://www.cieca.com/BMS">
|
||||||
|
<!-- MessageHeaderType -->
|
||||||
|
<RqUID>00000000-0000-0000-0000-000000000000</RqUID>
|
||||||
|
|
||||||
|
<!-- EstimateRqType sequence -->
|
||||||
|
<DocumentInfo>
|
||||||
|
<BMSVer>6.9.0</BMSVer>
|
||||||
|
<DocumentType>E</DocumentType>
|
||||||
|
<CreateDateTime>2025-08-14T12:00:00Z</CreateDateTime>
|
||||||
|
</DocumentInfo>
|
||||||
|
|
||||||
|
<ApplicationInfo>
|
||||||
|
<ApplicationType>INSERT_APP_TYPE</ApplicationType>
|
||||||
|
<ApplicationName>INSERT_APP_NAME</ApplicationName>
|
||||||
|
<ApplicationVer>INSERT_APP_VER</ApplicationVer>
|
||||||
|
</ApplicationInfo>
|
||||||
|
|
||||||
|
<AdminInfo/> <!-- container required; children optional -->
|
||||||
|
|
||||||
|
<EstimatorIDs/> <!-- container required; children optional -->
|
||||||
|
|
||||||
|
<!-- choice: VehicleInfo OR PropertyInfo -->
|
||||||
|
<VehicleInfo>
|
||||||
|
<!-- minimally empty is allowed; typical payload would include VIN and Year/Make/Model -->
|
||||||
|
<!-- Example:
|
||||||
|
<VINInfo>
|
||||||
|
<VIN>1HGBH41JXMN109186</VIN>
|
||||||
|
</VINInfo>
|
||||||
|
<VehicleDesc>
|
||||||
|
<ModelYear>2020</ModelYear>
|
||||||
|
<MakeDesc>Honda</MakeDesc>
|
||||||
|
<ModelName>Civic</ModelName>
|
||||||
|
</VehicleDesc>
|
||||||
|
-->
|
||||||
|
</VehicleInfo>
|
||||||
|
|
||||||
|
<ProfileInfo>
|
||||||
|
<!-- at least one RateInfo required -->
|
||||||
|
<RateInfo>
|
||||||
|
<RateType>INSERT_RATE_TYPE</RateType>
|
||||||
|
<!-- optional: <RateDesc>Body Labor</RateDesc> <TaxRate>13.00</TaxRate> etc. -->
|
||||||
|
</RateInfo>
|
||||||
|
</ProfileInfo>
|
||||||
|
|
||||||
|
<!-- at least one DamageLineInfo -->
|
||||||
|
<DamageLineInfo>
|
||||||
|
<!-- minimal: include a LaborInfo with required LaborType -->
|
||||||
|
<LaborInfo>
|
||||||
|
<LaborType>INSERT_LABOR_TYPE</LaborType>
|
||||||
|
<!-- optional: <LaborHours>1.0</LaborHours> <LaborHourlyRate>85.00</LaborHourlyRate> -->
|
||||||
|
</LaborInfo>
|
||||||
|
</DamageLineInfo>
|
||||||
|
|
||||||
|
<!-- at least one RepairTotalsInfo with at least one SummaryTotalsInfo -->
|
||||||
|
<RepairTotalsInfo>
|
||||||
|
<SummaryTotalsInfo>
|
||||||
|
<TotalType>INSERT_TOTAL_TYPE</TotalType>
|
||||||
|
<TotalTypeDesc>Grand Total</TotalTypeDesc>
|
||||||
|
<TotalAmt>0.00</TotalAmt>
|
||||||
|
</SummaryTotalsInfo>
|
||||||
|
</RepairTotalsInfo>
|
||||||
|
</VehicleDamageEstimateAddRq>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Implementation notes & gotchas (important)
|
||||||
|
|
||||||
|
1. **Required containers vs. required content**
|
||||||
|
|
||||||
|
* `AdminInfo` and `EstimatorIDs` are **required containers** but their **children are optional**.
|
||||||
|
Empty elements validate.
|
||||||
|
* `ProfileInfo` is required and must include **≥1 `RateInfo`** with a `RateType`.
|
||||||
|
* You must include the **choice** of **`VehicleInfo`** (for this message) instead of `PropertyInfo`.
|
||||||
|
* Include **≥1 `DamageLineInfo`** and **≥1 `RepairTotalsInfo`** each containing *
|
||||||
|
*≥1 `SummaryTotalsInfo`**.
|
||||||
|
|
||||||
|
2. **Header**
|
||||||
|
|
||||||
|
* `RqUID` is required; use a real UUID.
|
||||||
|
|
||||||
|
3. **Enumerations / code lists**
|
||||||
|
|
||||||
|
* Many fields are `ClosedEnumType`/`OpenEnumType` and validated against the BMS code list XSDs you
|
||||||
|
included (e.g., `BMSCodeLists_*.xsd`). Use the exact code values your trading partner expects (
|
||||||
|
e.g., `DocumentType` = `E` for estimates).
|
||||||
|
* `BMSVer` supports `6.9.0`.
|
||||||
|
|
||||||
|
4. **Line hierarchy**
|
||||||
|
|
||||||
|
* For nested kits/assemblies, use `ParentLineNum`; `UniqueSequenceNum` helps ordering. `LineType`
|
||||||
|
can label grouping (e.g., Sublet, Labor, Part, etc.).
|
||||||
|
|
||||||
|
5. **Attachments**
|
||||||
|
|
||||||
|
* You can embed binary (`EmbeddedAttachmentType/EmbeddedAttachment`) **or** provide a URI (
|
||||||
|
`AttachmentURI`). Provide `AttachmentFileType` and `AttachmentFileName` either way.
|
||||||
|
|
||||||
|
6. **Scans & calibrations**
|
||||||
|
|
||||||
|
* If you include `ScanInfo`, it **requires** `ScanTool` and `ScanDateTime`. Calibrations are
|
||||||
|
optional but provide strong ADAS traceability.
|
||||||
|
|
||||||
|
7. **Totals integrity**
|
||||||
|
|
||||||
|
* `RepairTotalsInfo/SummaryTotalsInfo` acts as your roll-up. Ensure it reconciles with the sum of
|
||||||
|
`DamageLineInfo` components and the profile’s rates/taxes so consumers don’t reject on mismatches.
|
||||||
|
|
||||||
|
8. **Currency / numeric facets**
|
||||||
|
|
||||||
|
* Monetary fields use `Currency`. Hours/rates/quantities have explicit facets (e.g.,
|
||||||
|
`Decimal_Range_-999.9-999.9`). Stay within ranges.
|
||||||
|
|
||||||
|
9. **Canada specifics**
|
||||||
|
|
||||||
|
* `DocumentInfo/CountryCode` = `CA`, and `ProfileInfo/CanadianTax` is available for PST/HST/GST
|
||||||
|
modeling if you need to encode tax policy explicitly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Quick field checklist for a typical *valid* “vehicle add” you’ll generate
|
||||||
|
|
||||||
|
* **Header**
|
||||||
|
|
||||||
|
* `RqUID` ✅
|
||||||
|
|
||||||
|
* **Doc header**
|
||||||
|
|
||||||
|
* `DocumentInfo/BMSVer` = `6.9.0` ✅
|
||||||
|
* `DocumentInfo/DocumentType` = `E` ✅
|
||||||
|
* `DocumentInfo/CreateDateTime` ✅
|
||||||
|
|
||||||
|
* **App**
|
||||||
|
|
||||||
|
* `ApplicationInfo[1..*]/(ApplicationType, ApplicationName, ApplicationVer)` ✅
|
||||||
|
|
||||||
|
* **Admin**
|
||||||
|
|
||||||
|
* `<AdminInfo/>` (or populate parties) ✅
|
||||||
|
|
||||||
|
* **EstimatorIDs**
|
||||||
|
|
||||||
|
* `<EstimatorIDs/>` (or add contents) ✅
|
||||||
|
|
||||||
|
* **Vehicle**
|
||||||
|
|
||||||
|
* `VehicleInfo` (VIN + YMM recommended) ✅
|
||||||
|
|
||||||
|
* **Profile & rates**
|
||||||
|
|
||||||
|
* `ProfileInfo/RateInfo[1..*]/RateType` ✅
|
||||||
|
|
||||||
|
* **Lines**
|
||||||
|
|
||||||
|
* `DamageLineInfo[1..*]` with at least one `LaborInfo/LaborType` or `PartInfo` ✅
|
||||||
|
|
||||||
|
* **Totals**
|
||||||
|
|
||||||
|
* `RepairTotalsInfo[1..*]/SummaryTotalsInfo[1..*]/(TotalType, TotalTypeDesc, TotalAmt)` ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pointers to definitions in your bundle (for traceability)
|
||||||
|
|
||||||
|
* `VehicleDamageEstimateAddRq` element → `BMSEstimateMessages_2024R1_V6.9.0.xsd`
|
||||||
|
* `EstimateRqType` → `BMSEstimateCommonTypes_2024R1_V6.9.0.xsd`
|
||||||
|
* `MessageHeaderType`, `DocumentInfoType`, `VehicleInfoType`, `FileAttachmentType`,
|
||||||
|
`PaymentInfoType`, etc. → `BMSCommonGlobalTypes_2024R1_V6.9.0.xsd`
|
||||||
|
* Rates/lines/totals/calibration/scan subtypes → mostly `BMSEstimateCommonTypes_2024R1_V6.9.0.xsd`
|
||||||
|
* Enums/code lists → `BMSCodeLists_ClassicCode_2024R1_V6.9.0.xsd`,
|
||||||
|
`BMSCodeLists_CodeExt_2024R1_V6.9.0.xsd`
|
||||||
|
* Service wrapper (which messages are valid to send/receive) →
|
||||||
|
`BMSEstimateService_2024R1_V6.9.0.xsd`
|
||||||
|
|
||||||
|
---
|
||||||
Reference in New Issue
Block a user