198 lines
6.1 KiB
JavaScript
198 lines
6.1 KiB
JavaScript
/**
|
|
* 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<any>}
|
|
*/
|
|
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/name blocks, including address + owner flag
|
|
* IMPORTANT: If no ServVehicle/CustomerNo exists (e.g. name-only hits), fall back to NameRecId.
|
|
* @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 = [];
|
|
|
|
const pickAddr = (addrArr) => {
|
|
const arr = Array.isArray(addrArr) ? addrArr : addrArr ? [addrArr] : [];
|
|
if (!arr.length) return null;
|
|
|
|
const chosen = arr.find((a) => (a?.Type || a?.type || "").toString().toUpperCase() === "P") || arr[0];
|
|
|
|
// NEW: include County
|
|
const line1 = chosen?.Addr1 ?? chosen?.AddressLine1 ?? chosen?.Line1 ?? chosen?.Street1 ?? undefined;
|
|
const line2 = chosen?.Addr2 ?? chosen?.AddressLine2 ?? chosen?.Line2 ?? chosen?.Street2 ?? undefined;
|
|
const city = chosen?.City ?? chosen?.city ?? undefined;
|
|
const state = chosen?.State ?? chosen?.StateOrProvince ?? chosen?.state ?? undefined;
|
|
const postalCode = chosen?.Zip ?? chosen?.PostalCode ?? chosen?.zip ?? undefined;
|
|
const country = chosen?.Country ?? chosen?.CountryCode ?? chosen?.country ?? undefined;
|
|
const county = chosen?.County ?? chosen?.county ?? undefined; // << added
|
|
|
|
if (!line1 && !city && !state && !postalCode && !country && !county) return null;
|
|
return { line1, line2, city, state, postalCode, country, county };
|
|
};
|
|
|
|
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();
|
|
|
|
const address = pickAddr(nci?.Address);
|
|
|
|
// fallback to NameRecId when no ServVehicle/CustomerNo exists (e.g., pure name search)
|
|
const nameRecIdRaw = nci?.NameId?.NameRecId;
|
|
const nameRecId = nameRecIdRaw != null ? String(nameRecIdRaw).trim() : "";
|
|
|
|
if (Array.isArray(custNos) && custNos.length > 0) {
|
|
for (const custNo of custNos) {
|
|
const cno = String(custNo).trim();
|
|
if (!cno) continue;
|
|
|
|
const item = {
|
|
custNo: cno,
|
|
name: name || `Customer ${cno}`,
|
|
address: address || undefined
|
|
};
|
|
|
|
if (ownersSet && ownersSet.has(cno)) {
|
|
item.isVehicleOwner = true;
|
|
item.vinOwner = true;
|
|
}
|
|
|
|
out.push(item);
|
|
}
|
|
} else if (nameRecId) {
|
|
// Use NameRecId as the identifier
|
|
const cno = nameRecId;
|
|
const item = {
|
|
custNo: cno,
|
|
name: name || `Customer ${cno}`,
|
|
address: address || undefined
|
|
};
|
|
out.push(item);
|
|
}
|
|
}
|
|
|
|
// Deduplicate by custNo, merge owner/address flags
|
|
const byId = new Map();
|
|
for (const c of out) {
|
|
const key = (c.custNo || "").trim();
|
|
if (!key) continue;
|
|
const prev = byId.get(key);
|
|
if (!prev) byId.set(key, c);
|
|
else {
|
|
byId.set(key, {
|
|
...prev,
|
|
isVehicleOwner: prev.isVehicleOwner || c.isVehicleOwner,
|
|
vinOwner: prev.vinOwner || c.vinOwner,
|
|
address: prev.address || c.address
|
|
});
|
|
}
|
|
}
|
|
|
|
return Array.from(byId.values());
|
|
};
|
|
|
|
/**
|
|
* Read advisor number from payload or cached value
|
|
* @param payload
|
|
* @param cached
|
|
* @returns {string|null}
|
|
*/
|
|
const readAdvisorNo = (payload, cached) => {
|
|
const tx = payload?.txEnvelope || payload?.envelope || {};
|
|
|
|
const get = (v) => (v != null && String(v).trim() !== "" ? String(v).trim() : null);
|
|
|
|
let value = get(tx?.advisorNo) || get(payload?.advisorNo) || get(cached) || null;
|
|
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* Cache enum keys for RR session transaction data
|
|
* @type {{txEnvelope: string, JobData: string, SelectedCustomer: string, AdvisorNo: string, VINCandidates: string, SelectedVin: string, ExportResult: string, PendingRO: string}}
|
|
*/
|
|
const RRCacheEnums = {
|
|
txEnvelope: "RR.txEnvelope",
|
|
JobData: "RR.JobData",
|
|
SelectedCustomer: "RR.SelectedCustomer",
|
|
AdvisorNo: "RR.AdvisorNo",
|
|
VINCandidates: "RR.VINCandidates",
|
|
SelectedVin: "RR.SelectedVin",
|
|
ExportResult: "RR.ExportResult",
|
|
PendingRO: "RR.PendingRO" // NEW: cache created RO to finalize later
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
};
|