175 lines
5.4 KiB
JavaScript
175 lines
5.4 KiB
JavaScript
// server/rr/rr-job-helpers.js
|
||
// Utilities to fetch and map job data into RR payloads using the shared Hasura client.
|
||
|
||
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 your RR client’s expectations).
|
||
* Uses fields that exist in your schema (v_vin, ro_number, owner fields, etc).
|
||
*/
|
||
function buildRRRepairOrderPayload({ job, selectedCustomer, advisorNo }) {
|
||
const custNo =
|
||
(selectedCustomer && (selectedCustomer.custNo || selectedCustomer.customerNo)) ||
|
||
(typeof selectedCustomer === "string" || typeof selectedCustomer === "number" ? String(selectedCustomer) : null);
|
||
|
||
if (!custNo) throw new Error("No RR customer selected (custNo missing)");
|
||
|
||
const vin = safeVin(job);
|
||
// For RR create flows, VIN is typically required; leave null allowed if you gate earlier in your flow.
|
||
return {
|
||
repairOrderNumber: String(job?.ro_number || job?.job_number || job?.id),
|
||
deptType: "B",
|
||
vin: vin || undefined,
|
||
custNo,
|
||
advNo: advisorNo || undefined
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 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);
|
||
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
|
||
};
|