/** * Get last 8 chars of a string, uppercased * @param v * @returns {string} */ const last8 = (v) => { return (String(v || "") || "").slice(-8).toUpperCase(); }; /** * Extract owner customer numbers from VIN-based blocks * @param blocks * @param jobVin * @returns {Set} */ const ownersFromVinBlocks = (blocks = [], jobVin = null) => { const out = new Set(); const want8 = jobVin ? last8(jobVin) : null; for (const blk of Array.isArray(blocks) ? blocks : []) { const serv = Array.isArray(blk?.ServVehicle) ? blk.ServVehicle : []; for (const sv of serv) { const svVin = String(sv?.Vehicle?.Vin || ""); if (want8 && last8(svVin) !== want8) continue; const custNo = sv?.VehicleServInfo?.CustomerNo; if (custNo != null && String(custNo).trim() !== "") { out.add(String(custNo).trim()); } } } return out; }; /** * Make vehicle search payload from job data * @param job * @returns {null|{kind: string, license: string, maxResults: number}|{kind: string, vin: string, maxResults: number}} */ const makeVehicleSearchPayloadFromJob = (job) => { const vin = job?.v_vin; if (vin) return { kind: "vin", vin: String(vin).trim(), maxResults: 50 }; const plate = job?.plate_no; if (plate) return { kind: "license", license: String(plate).trim(), maxResults: 50 }; return null; }; /** * Normalize customer candidates from VIN blocks * @param res * @param ownersSet * @returns {any[]} */ const normalizeCustomerCandidates = (res, { ownersSet = null } = {}) => { const blocks = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : []; const out = []; for (const blk of blocks) { const serv = Array.isArray(blk?.ServVehicle) ? blk.ServVehicle : []; const custNos = serv.map((sv) => sv?.VehicleServInfo?.CustomerNo).filter(Boolean); const nci = blk?.NameContactId; const ind = nci?.NameId?.IndName; const bus = nci?.NameId?.BusName; const personal = [ind?.FirstName || ind?.FName, ind?.LastName || ind?.LName].filter(Boolean).join(" ").trim(); const company = bus?.CompanyName || bus?.BName; const name = (personal || company || "").trim(); for (const custNo of custNos) { const cno = String(custNo).trim(); const item = { custNo: cno, name: name || `Customer ${cno}` }; if (ownersSet && ownersSet.has(cno)) item.isVehicleOwner = true; out.push(item); } } // Dedup by custNo, keep isVehicleOwner if any const seen = new Map(); for (const c of out) { const key = (c.custNo || "").trim(); if (!key) continue; const prev = seen.get(key); if (!prev) seen.set(key, c); else if (c.isVehicleOwner && !prev.isVehicleOwner) seen.set(key, { ...prev, isVehicleOwner: true }); } return Array.from(seen.values()); }; /** * Read advisor number from payload or cached value * @param payload * @param cached * @returns {string|null} */ const readAdvisorNo = (payload, cached) => { const v = (payload?.txEnvelope?.advisorNo != null && String(payload.txEnvelope.advisorNo)) || (payload?.advisorNo != null && String(payload.advisorNo)) || (cached != null && String(cached)) || null; return v && v.trim() !== "" ? v : null; }; /** * Cache enum keys for RR session transaction data * @type {{txEnvelope: string, JobData: string, SelectedCustomer: string, AdvisorNo: string, VINCandidates: string, SelectedVin: string, ExportResult: string}} */ const RRCacheEnums = { txEnvelope: "RR.txEnvelope", JobData: "RR.JobData", SelectedCustomer: "RR.SelectedCustomer", AdvisorNo: "RR.AdvisorNo", VINCandidates: "RR.VINCandidates", SelectedVin: "RR.SelectedVin", ExportResult: "RR.ExportResult" }; /** * Get transaction type string for job ID * @param jobid * @returns {`rr:${string}`} */ const getTransactionType = (jobid) => `rr:${jobid}`; /** * Default RR TTL (1 hour) * @type {number} */ const defaultRRTTL = 60 * 60; module.exports = { RRCacheEnums, defaultRRTTL, getTransactionType, ownersFromVinBlocks, makeVehicleSearchPayloadFromJob, normalizeCustomerCandidates, readAdvisorNo };