const client = require("../graphql-client/graphql-client").client; const { GET_JOB_BY_PK } = require("../graphql-client/queries"); // ---------- Internals ---------- function digitsOnly(s) { return String(s || "").replace(/[^\d]/g, ""); } function pickJobId(ctx, explicitId) { return explicitId || ctx?.job?.id || ctx?.payload?.job?.id || ctx?.payload?.jobId || ctx?.jobId || null; } function safeVin(job) { // Your schema exposes v_vin on jobs (no vehicle_vin root field). return (job?.v_vin && String(job.v_vin).trim()) || null; } // Combined search helpers expect array-like blocks function blocksFromCombinedSearchResult(res) { const data = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : []; return Array.isArray(data) ? data : []; } // ---------- Public API ---------- /** * Fetch a job by id using the shared Hasura GraphQL client. * Resolution order: * 1) ctx.job * 2) ctx.payload.job * 3) ctx.payload.jobId / ctx.jobId / explicit jobId */ async function QueryJobData(ctx = {}, jobId) { if (ctx?.job) return ctx.job; if (ctx?.payload?.job) return ctx.payload.job; const id = pickJobId(ctx, jobId); if (!id) throw new Error("QueryJobData: jobId required (none found in ctx or args)"); try { const res = await client.request(GET_JOB_BY_PK, { id }); const job = res?.jobs_by_pk; if (!job) throw new Error(`Job ${id} not found`); return job; } catch (e) { const msg = e?.response?.errors?.[0]?.message || e.message || "unknown"; throw new Error(`QueryJobData failed: ${msg}`); } } /** * Build minimal RR RO payload (keys match RR client expectations). * - Requires advisor number and customer number. * - We provide BOTH "customerNo" and "custNo" (and BOTH "advisorNo" and "advNo") * to be compatible with the compiled RR CJS lib which currently requires * "customerNo (or CustNo)". */ function buildRRRepairOrderPayload({ job, selectedCustomer, advisorNo }) { // Resolve customerNo from object or primitive; accept multiple incoming shapes const customerNo = selectedCustomer?.customerNo ? String(selectedCustomer?.customerNo).trim() : null; if (!customerNo) throw new Error("No RR customer selected (customerNo/CustNo missing)"); // Advisor number (accepts advisorNo string, map to both keys) const adv = advisorNo != null && String(advisorNo).trim() !== "" ? String(advisorNo).trim() : null; if (!adv) throw new Error("advisorNo is required for RR export"); // Clean/normalize VIN if present const vinRaw = job?.v_vin; const vin = typeof vinRaw === "string" ? vinRaw .replace(/[^A-Za-z0-9]/g, "") .toUpperCase() .slice(0, 17) || undefined : undefined; // Pick a stable external RO number const ro = job?.ro_number != null ? job.ro_number : job?.id != null ? job.id : null; if (ro == null) throw new Error("Missing repair order identifier (ro_number/job_number/id)"); // Provide superset of keys for maximum compatibility with the RR client return { repairOrderNumber: String(ro), deptType: "B", vin, customerNo: String(customerNo), advisorNo: adv }; } /** * Derive a vehicle search payload from a job. * Prefers VIN; otherwise tries a plate, else null. */ function makeVehicleSearchPayloadFromJob(job) { const vin = safeVin(job); if (vin) return { kind: "vin", vin }; const plate = job?.plate_no; if (plate) return { kind: "license", license: String(plate).trim() }; return null; } /** * Derive a customer search payload from a job. * Prefers phone (digits), then last name/company, then VIN. */ function makeCustomerSearchPayloadFromJob(job) { const phone = job?.ownr_ph1; const d = digitsOnly(phone); if (d.length >= 7) return { kind: "phone", phone: d }; const lastName = job?.ownr_ln; const company = job?.ownr_co_nm; const lnOrCompany = lastName || company; if (lnOrCompany) return { kind: "name", name: { name: String(lnOrCompany).trim() } }; const vin = safeVin(job); if (vin) return { kind: "vin", vin }; return null; } /** * Normalize candidate customers from a RR combined search response. */ function normalizeCustomerCandidates(res) { const blocks = blocksFromCombinedSearchResult(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?.FName, ind?.LName].filter(Boolean).join(" ").trim(); const company = bus?.CompanyName; const name = (personal || company || "").trim(); for (const custNo of custNos) { out.push({ custNo, name: name || `Customer ${custNo}`, _blk: blk }); } } const seen = new Set(); return out.filter((c) => { if (!c.custNo || seen.has(c.custNo)) return false; seen.add(c.custNo); return true; }); } /** * Normalize candidate vehicles from a RR combined search response. */ function normalizeVehicleCandidates(res) { const blocks = blocksFromCombinedSearchResult(res); console.log("Normalized vehicle Candiadates!!!!!!!!!!!!!!!!!!!!!"); console.dir({ res, blocks }, { depth: null }); const out = []; for (const blk of blocks) { const serv = Array.isArray(blk?.ServVehicle) ? blk.ServVehicle : []; for (const sv of serv) { const v = sv?.Vehicle || {}; const vin = v?.Vin || v?.VIN || v?.vin; if (!vin) continue; const year = v?.VehicleYr || v?.ModelYear || v?.Year; const make = v?.VehicleMake || v?.MakeName || v?.Make; const model = v?.MdlNo || v?.ModelDesc || v?.Model; const label = [year, make, model, vin].filter(Boolean).join(" "); out.push({ vin, year, make, model, label, _blk: blk }); } } const seen = new Set(); return out.filter((v) => { if (!v.vin || seen.has(v.vin)) return false; seen.add(v.vin); return true; }); } module.exports = { QueryJobData, buildRRRepairOrderPayload, makeCustomerSearchPayloadFromJob, makeVehicleSearchPayloadFromJob, normalizeCustomerCandidates, normalizeVehicleCandidates };