feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint

This commit is contained in:
Dave
2025-10-07 16:45:06 -04:00
parent c149d457e7
commit 2ffc4b81f4
28 changed files with 2594 additions and 1642 deletions

View File

@@ -1,12 +1,8 @@
// -----------------------------------------------------------------------------
// RR (Reynolds & Reynolds) HTTP routes
// - Mirrors /cdk shape so the UI can switch providers with minimal changes
// - Uses validateFirebaseIdTokenMiddleware + withUserGraphQLClientMiddleware
// - Calls into rr/* modules which wrap MakeRRCall from rr-helpers
//
// TODO:RR — As you wire the real RR endpoints + schemas, adjust the request
// bodies, query params, and response normalization inside rr/* files.
// -----------------------------------------------------------------------------
/**
* @file rrRoutes.js
* @description Express Router for Reynolds & Reynolds (Rome) DMS integration.
* Provides endpoints for lookup, customer management, repair orders, and full job export.
*/
const express = require("express");
const router = express.Router();
@@ -16,143 +12,197 @@ const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLCl
const { RrCombinedSearch, RrGetAdvisors, RrGetParts } = require("../rr/rr-lookup");
const { RrCustomerInsert, RrCustomerUpdate } = require("../rr/rr-customer");
// NOTE: correct filename is rr-repair-orders.js (plural)
const { CreateRepairOrder, UpdateRepairOrder } = require("../rr/rr-repair-orders");
const { ExportJobToRR } = require("../rr/rr-job-export");
const RRLogger = require("../rr/rr-logger");
// Require auth on all RR routes (keep parity with /cdk)
/**
* Apply global middlewares:
* - Firebase token validation (auth)
* - GraphQL client injection (Hasura access)
*/
router.use(validateFirebaseIdTokenMiddleware);
router.use(withUserGraphQLClientMiddleware);
// -----------------------------------------------------------------------------
// Accounting parity / scaffolding
// -----------------------------------------------------------------------------
/**
* Health check / diagnostic route
*/
router.get("/", async (req, res) => {
res.status(200).json({ provider: "Reynolds & Reynolds (Rome)", status: "OK" });
});
// Reuse CDK allocations for now; keep the endpoint name identical to /cdk
router.post("/calculate-allocations", withUserGraphQLClientMiddleware, async (req, res) => {
/**
* Full DMS export for a single job
* POST /rr/job/export
* Body: { JobData: {...} }
*/
router.post("/job/export", async (req, res) => {
try {
const CalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
const result = await CalculateAllocations(req, req.body.jobid, true); // verbose=true (like Fortellis flow)
res.status(200).json({ data: result });
} catch (e) {
req.logger?.log("rr-calc-allocations-route", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
const { JobData } = req.body;
RRLogger(req, "info", "RR /job/export initiated", { jobid: JobData?.id });
const result = await ExportJobToRR({
socket: req,
redisHelpers: req.sessionUtils,
JobData
});
res.status(result.success ? 200 : 500).json(result);
} catch (error) {
RRLogger(req, "error", `RR /job/export failed: ${error.message}`);
res.status(500).json({ error: error.message });
}
});
// Placeholder for a future RR "get vehicles" endpoint to match /cdk/getvehicles
router.post("/getvehicles", withUserGraphQLClientMiddleware, async (_req, res) => {
res.status(501).json({ error: "RR getvehicles not implemented yet" });
});
// -----------------------------------------------------------------------------
// Lookup endpoints
// -----------------------------------------------------------------------------
// GET /rr/lookup/combined?vin=...&lastName=...
router.get("/lookup/combined", async (req, res) => {
try {
const params = Object.entries(req.query); // [["vin","..."], ["lastName","..."]]
const data = await RrCombinedSearch({ socket: req, redisHelpers: req.sessionUtils, jobid: "ad-hoc", params });
res.status(200).json({ data });
} catch (e) {
req.logger?.log("rr-lookup-combined", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
}
});
// GET /rr/advisors?locationId=...
router.get("/advisors", async (req, res) => {
try {
const params = Object.entries(req.query);
const data = await RrGetAdvisors({ socket: req, redisHelpers: req.sessionUtils, jobid: "ad-hoc", params });
res.status(200).json({ data });
} catch (e) {
req.logger?.log("rr-get-advisors", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
}
});
// GET /rr/parts?partNumber=...&make=...
router.get("/parts", async (req, res) => {
try {
const params = Object.entries(req.query);
const data = await RrGetParts({ socket: req, redisHelpers: req.sessionUtils, jobid: "ad-hoc", params });
res.status(200).json({ data });
} catch (e) {
req.logger?.log("rr-get-parts", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
}
});
// -----------------------------------------------------------------------------
// Customer endpoints
// -----------------------------------------------------------------------------
// POST /rr/customer/insert
// Body: { ...JobData-like shape used by rr-mappers }
/**
* Customer insert
* POST /rr/customer/insert
*/
router.post("/customer/insert", async (req, res) => {
try {
const data = await RrCustomerInsert({ socket: req, redisHelpers: req.sessionUtils, JobData: req.body });
res.status(200).json({ data });
} catch (e) {
req.logger?.log("rr-customer-insert", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
const { JobData } = req.body;
const data = await RrCustomerInsert({
socket: req,
redisHelpers: req.sessionUtils,
JobData
});
res.status(200).json({ success: true, data });
} catch (error) {
RRLogger(req, "error", `RR /customer/insert failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
});
// PUT /rr/customer/update/:id
// Body: { JobData, existingCustomer, patch }
/**
* Customer update
* PUT /rr/customer/update/:id
*/
router.put("/customer/update/:id", async (req, res) => {
try {
const { JobData, existingCustomer, patch } = req.body;
const data = await RrCustomerUpdate({
socket: req,
redisHelpers: req.sessionUtils,
JobData: req.body?.JobData,
existingCustomer: req.body?.existingCustomer,
patch: req.body?.patch
JobData,
existingCustomer,
patch
});
res.status(200).json({ data });
} catch (e) {
req.logger?.log("rr-customer-update", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
res.status(200).json({ success: true, data });
} catch (error) {
RRLogger(req, "error", `RR /customer/update failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
});
// -----------------------------------------------------------------------------
// Repair Order endpoints
// -----------------------------------------------------------------------------
// POST /rr/repair-order/create
// Body: { JobData, txEnvelope }
/**
* Create Repair Order
* POST /rr/repair-order/create
*/
router.post("/repair-order/create", async (req, res) => {
try {
const { JobData, txEnvelope } = req.body;
const data = await CreateRepairOrder({
socket: req,
redisHelpers: req.sessionUtils,
JobData: req.body?.JobData,
txEnvelope: req.body?.txEnvelope
JobData,
txEnvelope
});
res.status(200).json({ data });
} catch (e) {
req.logger?.log("rr-ro-create", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
res.status(200).json({ success: true, data });
} catch (error) {
RRLogger(req, "error", `RR /repair-order/create failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
});
// PUT /rr/repair-order/update/:id
// Body: { JobData, txEnvelope }
/**
* Update Repair Order
* PUT /rr/repair-order/update/:id
*/
router.put("/repair-order/update/:id", async (req, res) => {
try {
const { JobData, txEnvelope } = req.body;
const data = await UpdateRepairOrder({
socket: req,
redisHelpers: req.sessionUtils,
JobData: req.body?.JobData,
txEnvelope: req.body?.txEnvelope
JobData,
txEnvelope
});
res.status(200).json({ data });
} catch (e) {
req.logger?.log("rr-ro-update", "ERROR", "api", "rr", { message: e.message, stack: e.stack });
res.status(500).json({ error: e.message });
res.status(200).json({ success: true, data });
} catch (error) {
RRLogger(req, "error", `RR /repair-order/update failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Combined search (customer + service vehicle)
* GET /rr/lookup/combined?vin=XXX&lastname=DOE
*/
router.get("/lookup/combined", async (req, res) => {
try {
const params = Object.entries(req.query);
const data = await RrCombinedSearch({
socket: req,
redisHelpers: req.sessionUtils,
jobid: "ad-hoc",
params
});
res.status(200).json({ success: true, data });
} catch (error) {
RRLogger(req, "error", `RR /lookup/combined failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Get Advisors
* GET /rr/advisors
*/
router.get("/advisors", async (req, res) => {
try {
const params = Object.entries(req.query);
const data = await RrGetAdvisors({
socket: req,
redisHelpers: req.sessionUtils,
jobid: "ad-hoc",
params
});
res.status(200).json({ success: true, data });
} catch (error) {
RRLogger(req, "error", `RR /advisors failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Get Parts
* GET /rr/parts
*/
router.get("/parts", async (req, res) => {
try {
const params = Object.entries(req.query);
const data = await RrGetParts({
socket: req,
redisHelpers: req.sessionUtils,
jobid: "ad-hoc",
params
});
res.status(200).json({ success: true, data });
} catch (error) {
RRLogger(req, "error", `RR /parts failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
});
/**
* Not implemented placeholder (for future expansion)
*/
router.post("/calculate-allocations", async (req, res) => {
res.status(501).json({ error: "RR calculate-allocations not yet implemented" });
});
module.exports = router;