225 lines
6.3 KiB
JavaScript
225 lines
6.3 KiB
JavaScript
const client = require("../graphql-client/graphql-client").client;
|
|
const { GET_JOB_BY_PK } = require("../graphql-client/queries");
|
|
|
|
/**
|
|
* Remove all non-digit characters from a string.
|
|
* @param s
|
|
* @returns {string}
|
|
*/
|
|
const digitsOnly = (s) => String(s || "").replace(/\D/g, "");
|
|
|
|
/**
|
|
* Pick job ID from various possible locations.
|
|
* @param ctx
|
|
* @param explicitId
|
|
* @returns {*|null}
|
|
*/
|
|
const pickJobId = (ctx, explicitId) =>
|
|
explicitId || ctx?.job?.id || ctx?.payload?.job?.id || ctx?.payload?.jobId || ctx?.jobId || null;
|
|
|
|
/**
|
|
* Safely get VIN from job object.
|
|
* @param job
|
|
* @returns {*|string|null}
|
|
*/
|
|
const safeVin = (job) => (job?.v_vin && String(job.v_vin).trim()) || null;
|
|
|
|
/**
|
|
* Extract blocks array from combined search result.
|
|
* @param res
|
|
* @returns {any[]|*[]}
|
|
*/
|
|
const blocksFromCombinedSearchResult = (res) => {
|
|
const data = Array.isArray(res?.data) ? res.data : Array.isArray(res) ? res : [];
|
|
return Array.isArray(data) ? data : [];
|
|
};
|
|
|
|
// ---------- Public API ----------
|
|
|
|
/**
|
|
* Query job data by ID from GraphQL API.
|
|
* @param ctx
|
|
* @param jobId
|
|
* @returns {Promise<*>}
|
|
* @constructor
|
|
*/
|
|
const QueryJobData = async (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 Repair Order payload for RR from job and customer data.
|
|
* @param job
|
|
* @param selectedCustomer
|
|
* @param advisorNo
|
|
* @param story
|
|
* @param makeOverride
|
|
* @returns {{outsdRoNo: string, repairOrderNumber: string, departmentType: string, vin: string, customerNo: string, advisorNo: string, mileageIn: *|null}}
|
|
*/
|
|
const buildRRRepairOrderPayload = ({ job, selectedCustomer, advisorNo, story }) => {
|
|
const customerNo = selectedCustomer?.customerNo
|
|
? String(selectedCustomer.customerNo).trim()
|
|
: selectedCustomer?.custNo
|
|
? String(selectedCustomer.custNo).trim()
|
|
: null;
|
|
|
|
if (!customerNo) throw new Error("No RR customer selected (customerNo/CustNo missing)");
|
|
|
|
const adv = advisorNo != null && String(advisorNo).trim() !== "" ? String(advisorNo).trim() : null;
|
|
|
|
if (!adv) throw new Error("advisorNo is required for RR export");
|
|
|
|
const vinRaw = job?.v_vin;
|
|
const vin =
|
|
typeof vinRaw === "string"
|
|
? vinRaw
|
|
.replace(/[^A-Za-z0-9]/g, "")
|
|
.toUpperCase()
|
|
.slice(0, 17) || undefined
|
|
: undefined;
|
|
|
|
// Use ro_number when present; fallback to job.id
|
|
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/id)");
|
|
|
|
const roStr = String(ro);
|
|
|
|
const output = {
|
|
outsdRoNo: roStr,
|
|
repairOrderNumber: roStr,
|
|
departmentType: "B",
|
|
vin,
|
|
customerNo: String(customerNo),
|
|
advisorNo: adv,
|
|
mileageIn: job.kmin
|
|
};
|
|
|
|
if (story) {
|
|
output.roComment = String(story).trim();
|
|
}
|
|
|
|
return output;
|
|
};
|
|
|
|
/**
|
|
* Make vehicle search payload from job data
|
|
* @param job
|
|
* @returns {{kind: string, license: string}|null|{kind: string, vin: *|string}}
|
|
*/
|
|
const 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;
|
|
};
|
|
|
|
/**
|
|
* Make customer search payload from job data
|
|
* @param job
|
|
* @returns {{kind: string, vin: *|string}|{kind: string, name: {name: string}}|{kind: string, phone: string}|null}
|
|
*/
|
|
const 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 customer candidates from combined search result.
|
|
* @param res
|
|
* @returns {*[]}
|
|
*/
|
|
const 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 vehicle candidates from combined search result.
|
|
* @param res
|
|
* @returns {*[]}
|
|
*/
|
|
const 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
|
|
};
|