feature/Reynolds-and-Reynolds-DMS-API-Integration -Expand
This commit is contained in:
@@ -328,7 +328,6 @@ const RRActions = {
|
|||||||
url: isProduction ? "https://rr.example.com/customer/v1/search" : "https://rr-uat.example.com/customer/v1/search",
|
url: isProduction ? "https://rr.example.com/customer/v1/search" : "https://rr-uat.example.com/customer/v1/search",
|
||||||
type: "get"
|
type: "get"
|
||||||
},
|
},
|
||||||
|
|
||||||
// Combined search (customer + vehicle)
|
// Combined search (customer + vehicle)
|
||||||
CombinedSearch: {
|
CombinedSearch: {
|
||||||
apiName: "RR Combined Search (Customer + Vehicle)",
|
apiName: "RR Combined Search (Customer + Vehicle)",
|
||||||
@@ -337,14 +336,12 @@ const RRActions = {
|
|||||||
: "https://rr-uat.example.com/search/v1/customer-vehicle",
|
: "https://rr-uat.example.com/search/v1/customer-vehicle",
|
||||||
type: "get"
|
type: "get"
|
||||||
},
|
},
|
||||||
|
|
||||||
// Advisors
|
// Advisors
|
||||||
GetAdvisors: {
|
GetAdvisors: {
|
||||||
apiName: "RR Get Advisors",
|
apiName: "RR Get Advisors",
|
||||||
url: isProduction ? "https://rr.example.com/advisors/v1" : "https://rr-uat.example.com/advisors/v1",
|
url: isProduction ? "https://rr.example.com/advisors/v1" : "https://rr-uat.example.com/advisors/v1",
|
||||||
type: "get"
|
type: "get"
|
||||||
},
|
},
|
||||||
|
|
||||||
// Parts
|
// Parts
|
||||||
GetParts: {
|
GetParts: {
|
||||||
apiName: "RR Get Parts",
|
apiName: "RR Get Parts",
|
||||||
|
|||||||
249
server/rr/rr-mappers.js
Normal file
249
server/rr/rr-mappers.js
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// server/rr/rr-mappers.js
|
||||||
|
// Centralized mapping & normalization for Reynolds & Reynolds (RR)
|
||||||
|
//
|
||||||
|
// NOTE: This is scaffolding intended to be completed against the RR XML/JSON
|
||||||
|
// schemas in the Rome RR specs you dropped (Customer Insert/Update, Repair Order,
|
||||||
|
// Service Vehicle, etc.). Field names below are placeholders where noted.
|
||||||
|
// Fill the TODOs with the exact RR element/attribute names.
|
||||||
|
//
|
||||||
|
// Usage expectation from calling code (example you gave):
|
||||||
|
// const { mapCustomerInsert, mapCustomerUpdate } = require("./rr-mappers");
|
||||||
|
// const body = mapCustomerInsert(JobData);
|
||||||
|
// const body = mapCustomerUpdate(existingCustomer, patch);
|
||||||
|
|
||||||
|
const _ = require("lodash");
|
||||||
|
const InstanceMgr = require("../utils/instanceMgr").default;
|
||||||
|
|
||||||
|
// Keep this consistent with other providers
|
||||||
|
const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g;
|
||||||
|
|
||||||
|
function sanitize(value) {
|
||||||
|
if (value === null || value === undefined) return value;
|
||||||
|
return String(value).replace(replaceSpecialRegex, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePostal(raw) {
|
||||||
|
// Match Fortellis/CDK behavior for CA vs US formatting
|
||||||
|
// (Use InstanceMgr profile detection already present in your codebase)
|
||||||
|
return InstanceMgr({
|
||||||
|
imex: raw && String(raw).toUpperCase().replace(/\W/g, "").replace(/(...)/, "$1 "),
|
||||||
|
rome: raw
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function asStringOrNull(value) {
|
||||||
|
const s = sanitize(value);
|
||||||
|
return s && s.length > 0 ? s : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapPhones({ ph1, ph2, mobile }) {
|
||||||
|
// TODO: Update to RR’s final phone structure and codes/types when wiring
|
||||||
|
const out = [];
|
||||||
|
if (ph1) out.push({ number: sanitize(ph1), type: "HOME" });
|
||||||
|
if (ph2) out.push({ number: sanitize(ph2), type: "WORK" });
|
||||||
|
if (mobile) out.push({ number: sanitize(mobile), type: "MOBILE" });
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapEmails({ email }) {
|
||||||
|
// RR often supports multiple emails; start with one.
|
||||||
|
// TODO: Update per RR schema (email flags, preferred, etc.)
|
||||||
|
if (!email) return [];
|
||||||
|
return [{ address: sanitize(email), type: "PERSONAL" }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapPostalAddressFromJob(job) {
|
||||||
|
// Rome job-level owner fields (aligning to prior provider scaffolds)
|
||||||
|
return {
|
||||||
|
addressLine1: asStringOrNull(job.ownr_addr1),
|
||||||
|
addressLine2: asStringOrNull(job.ownr_addr2),
|
||||||
|
city: asStringOrNull(job.ownr_city),
|
||||||
|
state: asStringOrNull(job.ownr_st || job.ownr_state),
|
||||||
|
postalCode: normalizePostal(job.ownr_zip),
|
||||||
|
country: asStringOrNull(job.ownr_ctry) || "USA", // default, adjust as needed
|
||||||
|
province: asStringOrNull(job.ownr_st) // keep both state/province fields for CA cases
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapPhonesFromJob(job) {
|
||||||
|
return mapPhones({
|
||||||
|
ph1: job.ownr_ph1,
|
||||||
|
ph2: job.ownr_ph2,
|
||||||
|
mobile: job.ownr_mobile
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapEmailsFromJob(job) {
|
||||||
|
return mapEmails({ email: job.ownr_ea });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer Insert
|
||||||
|
* Matches your call site:
|
||||||
|
* const body = mapCustomerInsert(JobData)
|
||||||
|
*
|
||||||
|
* Return shape intentionally mirrors Fortellis scaffolding so the same
|
||||||
|
* MakeRRCall pipeline can be reused. Replace placeholders with the RR spec’s
|
||||||
|
* request envelope/element names (e.g., CustomerInsertRq, CustomerRq, etc.).
|
||||||
|
*/
|
||||||
|
function mapCustomerInsert(job) {
|
||||||
|
const isCompany = Boolean(job.ownr_co_nm && job.ownr_co_nm.trim() !== "");
|
||||||
|
|
||||||
|
// Skeleton payload — replace keys under CustomerInsertRq with the actual RR names
|
||||||
|
return {
|
||||||
|
CustomerInsertRq: {
|
||||||
|
// TODO: Confirm RR element/attribute names from spec PDFs
|
||||||
|
customerType: isCompany ? "ORGANIZATION" : "INDIVIDUAL",
|
||||||
|
customerName: {
|
||||||
|
companyName: asStringOrNull(job.ownr_co_nm)?.toUpperCase() || null,
|
||||||
|
firstName: isCompany ? null : asStringOrNull(job.ownr_fn)?.toUpperCase(),
|
||||||
|
lastName: isCompany ? null : asStringOrNull(job.ownr_ln)?.toUpperCase()
|
||||||
|
},
|
||||||
|
postalAddress: mapPostalAddressFromJob(job),
|
||||||
|
contactMethods: {
|
||||||
|
phones: mapPhonesFromJob(job),
|
||||||
|
emailAddresses: mapEmailsFromJob(job)
|
||||||
|
}
|
||||||
|
// Optional / placeholders for future fields in RR spec
|
||||||
|
// taxCode: null,
|
||||||
|
// groupCode: null,
|
||||||
|
// dealerFields: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer Update
|
||||||
|
* Matches your call site:
|
||||||
|
* const body = mapCustomerUpdate(existingCustomer, patch)
|
||||||
|
*
|
||||||
|
* - existingCustomer: prior RR customer payload/shape (from RR Read/Query)
|
||||||
|
* - patch: minimal delta from UI/Job selections to overlay onto the RR model
|
||||||
|
*
|
||||||
|
* We return a merged/normalized payload for RR Update.
|
||||||
|
*/
|
||||||
|
function mapCustomerUpdate(existingCustomer, patch = {}) {
|
||||||
|
// NOTE:
|
||||||
|
// 1) We assume existingCustomer already resembles RR’s stored shape.
|
||||||
|
// 2) We overlay patch fields into that shape, then project to the
|
||||||
|
// RR Update request envelope.
|
||||||
|
// 3) Replace inner keys with exact RR Update schema element names.
|
||||||
|
|
||||||
|
const merged = _.merge({}, existingCustomer || {}, patch || {});
|
||||||
|
const id = merged?.customerId || merged?.id || merged?.CustomerId || merged?.customer?.id || null;
|
||||||
|
|
||||||
|
// Derive a normalized name object from merged data (handles org/person)
|
||||||
|
const isCompany = Boolean(merged?.customerName?.companyName) || Boolean(merged?.companyName) || false;
|
||||||
|
|
||||||
|
const normalizedName = {
|
||||||
|
companyName: asStringOrNull(merged?.customerName?.companyName) || asStringOrNull(merged?.companyName),
|
||||||
|
firstName: isCompany ? null : asStringOrNull(merged?.customerName?.firstName) || asStringOrNull(merged?.firstName),
|
||||||
|
lastName: isCompany ? null : asStringOrNull(merged?.customerName?.lastName) || asStringOrNull(merged?.lastName)
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizedAddress = {
|
||||||
|
addressLine1: asStringOrNull(merged?.postalAddress?.addressLine1) || asStringOrNull(merged?.addressLine1),
|
||||||
|
addressLine2: asStringOrNull(merged?.postalAddress?.addressLine2) || asStringOrNull(merged?.addressLine2),
|
||||||
|
city: asStringOrNull(merged?.postalAddress?.city) || asStringOrNull(merged?.city),
|
||||||
|
state:
|
||||||
|
asStringOrNull(merged?.postalAddress?.state) ||
|
||||||
|
asStringOrNull(merged?.state) ||
|
||||||
|
asStringOrNull(merged?.stateOrProvince) ||
|
||||||
|
asStringOrNull(merged?.province),
|
||||||
|
postalCode: normalizePostal(merged?.postalAddress?.postalCode || merged?.postalCode),
|
||||||
|
country: asStringOrNull(merged?.postalAddress?.country) || asStringOrNull(merged?.country) || "USA",
|
||||||
|
province: asStringOrNull(merged?.postalAddress?.province) || asStringOrNull(merged?.province)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Phones
|
||||||
|
const normalizedPhones = merged?.contactMethods?.phones || merged?.phones || [];
|
||||||
|
|
||||||
|
// Emails
|
||||||
|
const normalizedEmails = merged?.contactMethods?.emailAddresses || merged?.emailAddresses || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
CustomerUpdateRq: {
|
||||||
|
// TODO: Confirm exact RR element/attribute names for update
|
||||||
|
customerId: id,
|
||||||
|
customerType: normalizedName.companyName ? "ORGANIZATION" : "INDIVIDUAL",
|
||||||
|
customerName: normalizedName,
|
||||||
|
postalAddress: normalizedAddress,
|
||||||
|
contactMethods: {
|
||||||
|
phones: normalizedPhones,
|
||||||
|
emailAddresses: normalizedEmails
|
||||||
|
}
|
||||||
|
// Optional change tracking fields, timestamps, etc., per RR spec can go here
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Additional mappers (scaffolding for upcoming work) ===== */
|
||||||
|
|
||||||
|
function mapVehicleInsertFromJob(job, txEnvelope = {}) {
|
||||||
|
// TODO: Replace with RR Service Vehicle Insert schema
|
||||||
|
return {
|
||||||
|
ServiceVehicleInsertRq: {
|
||||||
|
vin: asStringOrNull(job.v_vin),
|
||||||
|
year: job.v_model_yr || null,
|
||||||
|
make: txEnvelope.dms_make || asStringOrNull(job.v_make),
|
||||||
|
model: txEnvelope.dms_model || asStringOrNull(job.v_model),
|
||||||
|
odometer: txEnvelope.kmout || null,
|
||||||
|
licensePlate: job.plate_no && /\w/.test(job.plate_no) ? asStringOrNull(job.plate_no).toUpperCase() : null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapRepairOrderAddFromJob(job) {
|
||||||
|
// TODO: Replace with RR RepairOrder Add schema (headers, lines, taxes)
|
||||||
|
return {
|
||||||
|
RepairOrderAddRq: {
|
||||||
|
customerId: job.customer?.id || null,
|
||||||
|
vehicleId: job.vehicle?.id || null,
|
||||||
|
referenceNumber: asStringOrNull(job.ro_number),
|
||||||
|
openedAt: job.actual_in || null,
|
||||||
|
closedAt: job.invoice_date || null
|
||||||
|
// lines: job.joblines?.map(mapJobLineToRRLine),
|
||||||
|
// taxes: mapTaxes(job),
|
||||||
|
// payments: mapPayments(job)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapRepairOrderChangeFromJob(job) {
|
||||||
|
// TODO: Replace with RR RepairOrder Update schema
|
||||||
|
return {
|
||||||
|
RepairOrderChgRq: {
|
||||||
|
repairOrderId: job.id,
|
||||||
|
referenceNumber: asStringOrNull(job.ro_number)
|
||||||
|
// delta lines, amounts, status, etc.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Example line mapper (placeholder) */
|
||||||
|
function mapJobLineToRRLine(line) {
|
||||||
|
return {
|
||||||
|
// TODO: set RR fields
|
||||||
|
seq: line.sequence || null,
|
||||||
|
opCode: line.opCode || null,
|
||||||
|
description: asStringOrNull(line.description),
|
||||||
|
qty: line.part_qty || null,
|
||||||
|
price: line.price || null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Required by your current calling code:
|
||||||
|
mapCustomerInsert,
|
||||||
|
mapCustomerUpdate,
|
||||||
|
|
||||||
|
// Extra scaffolds we’ll likely use right after:
|
||||||
|
mapVehicleInsertFromJob,
|
||||||
|
mapRepairOrderAddFromJob,
|
||||||
|
mapRepairOrderChangeFromJob,
|
||||||
|
mapJobLineToRRLine,
|
||||||
|
|
||||||
|
// low-level utils (export if you want to reuse in tests)
|
||||||
|
_sanitize: sanitize,
|
||||||
|
_normalizePostal: normalizePostal
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user