268 lines
8.0 KiB
JavaScript
268 lines
8.0 KiB
JavaScript
const { RRClient } = require("./lib/index.cjs");
|
|
const { getRRConfigFromBodyshop } = require("./rr-config");
|
|
const CreateRRLogEvent = require("./rr-logger-event");
|
|
const InstanceManager = require("../utils/instanceMgr").default;
|
|
|
|
/**
|
|
* Country code map for normalization
|
|
* @type {{US: string, USA: string, "UNITED STATES": string, CA: string, CAN: string, CANADA: string}}
|
|
*/
|
|
const COUNTRY_MAP = {
|
|
US: "US",
|
|
USA: "US",
|
|
"UNITED STATES": "US",
|
|
CA: "CA",
|
|
CAN: "CA",
|
|
CANADA: "CA"
|
|
};
|
|
|
|
/**
|
|
* Normalize country input to 2-char code
|
|
* @param v
|
|
* @returns {*|string}
|
|
*/
|
|
const toCountry2 = (v) => {
|
|
const s = String(v || "")
|
|
.trim()
|
|
.toUpperCase();
|
|
if (!s) return "US"; // sane default
|
|
if (COUNTRY_MAP[s]) return COUNTRY_MAP[s];
|
|
// fallbacks: prefer 2-char; last resort: take first 2
|
|
return s.length === 2 ? s : s.slice(0, 2);
|
|
};
|
|
|
|
/**
|
|
* Normalize phone number to 10-digit string
|
|
* @param num
|
|
* @returns {string}
|
|
*/
|
|
const normalizePhone = (num) => {
|
|
const d = String(num || "").replace(/\D/g, "");
|
|
const n = d.length === 11 && d.startsWith("1") ? d.slice(1) : d;
|
|
return n.slice(0, 10);
|
|
};
|
|
|
|
/**
|
|
* Normalize postal code based on country
|
|
* @param pc
|
|
* @param country
|
|
* @returns {string}
|
|
*/
|
|
const normalizePostal = (pc, country) => {
|
|
const s = String(pc || "").trim();
|
|
if (country === "US") return s.replace(/[^0-9]/g, "").slice(0, 5);
|
|
if (country === "CA") return s.toUpperCase().replace(/\s+/g, "").slice(0, 6);
|
|
return s;
|
|
};
|
|
|
|
/**
|
|
* Sanitize RR customer payload (addresses, phones, names)
|
|
* @param payload
|
|
* @returns {{}}
|
|
*/
|
|
const sanitizeRRCustomerPayload = (payload = {}) => {
|
|
const out = { ...payload };
|
|
|
|
out.addresses = (payload.addresses || []).map((a) => {
|
|
const country = toCountry2(a.country);
|
|
return {
|
|
...a,
|
|
country,
|
|
state: String(a.state || "")
|
|
.toUpperCase()
|
|
.slice(0, 2),
|
|
postalCode: normalizePostal(a.postalCode, country)
|
|
};
|
|
});
|
|
|
|
out.phones = (payload.phones || []).map((p) => ({
|
|
...p,
|
|
number: normalizePhone(p.number)
|
|
}));
|
|
|
|
// trim names defensively (RR has various max lengths by site config)
|
|
if (out.firstName) out.firstName = String(out.firstName).trim().slice(0, 30);
|
|
if (out.lastName) out.lastName = String(out.lastName).trim().slice(0, 30);
|
|
|
|
return out;
|
|
};
|
|
|
|
/**
|
|
* Build an RR client + common opts from a bodyshop row
|
|
* @param bodyshop
|
|
* @returns {{client: *, opts: {routing: {dealerNumber: *, storeNumber: *, areaNumber: *}, envelope: {sender: {component: string, task: string, referenceId: string, creator: string, senderName: string}}}}}
|
|
*/
|
|
const buildClientAndOpts = (bodyshop) => {
|
|
const cfg = getRRConfigFromBodyshop(bodyshop);
|
|
const client = new RRClient({
|
|
baseUrl: cfg.baseUrl,
|
|
username: cfg.username,
|
|
password: cfg.password,
|
|
timeoutMs: cfg.timeoutMs,
|
|
retries: cfg.retries
|
|
});
|
|
|
|
const opts = {
|
|
routing: cfg.routing,
|
|
envelope: {
|
|
sender: {
|
|
component: "Rome",
|
|
task: "CU",
|
|
referenceId: "Insert",
|
|
creator: "RCI",
|
|
senderName: "RCI"
|
|
}
|
|
}
|
|
};
|
|
return { client, opts };
|
|
};
|
|
|
|
/**
|
|
* Strip all non-digit characters from a string
|
|
* @param s
|
|
* @returns {string}
|
|
*/
|
|
const digitsOnly = (s) => {
|
|
return String(s || "").replace(/\D/g, "");
|
|
};
|
|
|
|
/**
|
|
* Return a new array with only unique values from the input array
|
|
* @param arr
|
|
* @returns {any[]}
|
|
*/
|
|
const uniq = (arr) => {
|
|
return Array.from(new Set(arr));
|
|
};
|
|
|
|
/**
|
|
* Build RR customer payload from job.ownr_* fields, with optional overrides.
|
|
* @param job
|
|
* @param overrides
|
|
* @returns {{ibFlag: string, firstName, lastName, customerName, createdBy, customerType, addresses: [{type, line1: *, line2, city, state, postalCode, country}], phones: {number: *}[], emails: [{address: string}]}}
|
|
*/
|
|
const buildCustomerPayloadFromJob = (job, overrides = {}) => {
|
|
// Pull ONLY from job.ownr_* fields (no job.customer.*)
|
|
const firstName = overrides.firstName ?? job?.ownr_fn ?? undefined;
|
|
const lastName = overrides.lastName ?? job?.ownr_ln ?? undefined;
|
|
const companyName = overrides.companyName ?? overrides.company ?? job?.ownr_co_nm ?? undefined;
|
|
|
|
// Decide Individual vs Business (caller can force via overrides.ibFlag)
|
|
const ibFlag = (overrides.ibFlag || (firstName ? "I" : companyName ? "B" : "I")).toUpperCase();
|
|
|
|
// Email(s)
|
|
const email = overrides.email ?? job?.ownr_ea ?? undefined;
|
|
const emails = email ? [{ address: String(email) }] : undefined;
|
|
|
|
// Phones
|
|
const phoneCandidates = [overrides.phone, job?.ownr_ph1, job?.ownr_ph2]
|
|
.map((v) => digitsOnly(v))
|
|
.filter((v) => v && v.length >= 7);
|
|
|
|
const phones = uniq(phoneCandidates).map((num) => ({ number: num }));
|
|
|
|
// Address (include only if line1 exists; template requires Addr1 if address is present)
|
|
const line1 = overrides.addressLine1 ?? job?.ownr_addr1 ?? undefined;
|
|
|
|
const addresses = line1
|
|
? [
|
|
{
|
|
type: overrides.addressType || "P",
|
|
line1,
|
|
line2: overrides.addressLine2 ?? job?.ownr_addr2 ?? undefined,
|
|
city: overrides.city ?? job?.ownr_city ?? undefined,
|
|
state: overrides.state ?? job?.ownr_st ?? undefined,
|
|
postalCode: overrides.postalCode ?? job?.ownr_zip ?? undefined,
|
|
country: (overrides.country ?? job?.ownr_ctry ?? "CA") || undefined
|
|
}
|
|
]
|
|
: undefined;
|
|
|
|
// Enforce lib requirement: lastName OR customerName
|
|
if (!lastName && !companyName) {
|
|
throw new Error(
|
|
"Cannot build RR customer payload: lastName or companyName is required (no ownr_ln / ownr_co_nm on job)."
|
|
);
|
|
}
|
|
|
|
const payload = {
|
|
ibFlag, // 'I' or 'B'
|
|
firstName: firstName || undefined,
|
|
lastName: lastName || undefined,
|
|
customerName: companyName || undefined,
|
|
createdBy: overrides.createdBy || InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
|
|
customerType: overrides.customerType || "R", // Retail default
|
|
addresses,
|
|
phones,
|
|
emails
|
|
};
|
|
|
|
Object.keys(payload).forEach((k) => payload[k] === undefined && delete payload[k]);
|
|
return payload;
|
|
};
|
|
|
|
/**
|
|
* Create a customer in RR and return { customerNo, raw }.
|
|
* Maps data.dmsRecKey -> customerNo for compatibility with existing callers.
|
|
*/
|
|
const createRRCustomer = async ({ bodyshop, job, overrides = {}, socket }) => {
|
|
const { client, opts } = buildClientAndOpts(bodyshop);
|
|
const payload = buildCustomerPayloadFromJob(job, overrides);
|
|
const safePayload = sanitizeRRCustomerPayload(payload);
|
|
|
|
// Story step: clearly show we are about to hit Reynolds insertCustomer
|
|
CreateRRLogEvent(socket, "DEBUG", "{CU} insertCustomer: begin", {
|
|
ibFlag: safePayload.ibFlag,
|
|
hasAddress: Array.isArray(safePayload.addresses) && safePayload.addresses.length > 0,
|
|
hasPhones: Array.isArray(safePayload.phones) && safePayload.phones.length > 0,
|
|
hasEmails: Array.isArray(safePayload.emails) && safePayload.emails.length > 0
|
|
});
|
|
|
|
let response;
|
|
try {
|
|
response = await client.insertCustomer(safePayload, opts);
|
|
// Very noisy; only show when log level is cranked to SILLY
|
|
CreateRRLogEvent(socket, "SILLY", "{CU} insertCustomer: raw response", { response });
|
|
} catch (e) {
|
|
CreateRRLogEvent(socket, "ERROR", "RR insertCustomer transport error", {
|
|
message: e?.message,
|
|
code: e?.code,
|
|
status: e?.meta?.status || e?.status,
|
|
payload: safePayload
|
|
});
|
|
throw e;
|
|
}
|
|
|
|
const data = response?.data ?? response;
|
|
const trx = response?.statusBlocks?.transaction;
|
|
|
|
let customerNo = data?.dmsRecKey;
|
|
if (!customerNo) {
|
|
CreateRRLogEvent(socket, "ERROR", "RR insertCustomer returned no dmsRecKey/custNo", {
|
|
status: trx?.status,
|
|
statusCode: trx?.statusCode,
|
|
message: trx?.message,
|
|
data
|
|
});
|
|
|
|
throw new Error(
|
|
`RR insertCustomer returned no dmsRecKey (status=${trx?.status ?? "?"} code=${trx?.statusCode ?? "?"}${
|
|
trx?.message ? ` msg=${trx.message}` : ""
|
|
})`
|
|
);
|
|
}
|
|
|
|
const out = { customerNo: String(customerNo), raw: data };
|
|
|
|
CreateRRLogEvent(socket, "INFO", "{CU} insertCustomer: success", {
|
|
customerNo: out.customerNo,
|
|
status: trx || null
|
|
});
|
|
|
|
return out;
|
|
};
|
|
|
|
module.exports = {
|
|
createRRCustomer
|
|
};
|