// 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 "); } try { // ── SHOP & CLAIM IDs ──────────────────────────────────────────────────────── const shopId = rq.ShopID || rq.shopId; if (!shopId) throw { status: 400, message: "Missing 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; 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 production_date = desc.ProductionDate || 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, status, 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 didn’t map above production_vars: { documentVersions: [] // we’ve flattened these }, // 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;