const { GraphQLClient } = require("graphql-request"); const queries = require("../graphql-client/queries"); const CreateRRLogEvent = require("./rr-logger-event"); /** Get bearer token from the socket (same approach used elsewhere) */ const getAuthToken = (socket) => (socket?.data && socket.data.authToken) || (socket?.handshake?.auth && socket.handshake.auth.token) || null; /** Compact metadata for RR */ const buildRRExportMeta = ({ result, extra = {} }) => { const tx = result?.statusBlocks?.transaction; const rawRoStatus = result?.roStatus || result?.data?.roStatus || null; const roStatus = rawRoStatus || (tx ? { status: tx.status ?? tx.Status, statusCode: tx.statusCode ?? tx.StatusCode, message: tx.message ?? tx.Message } : null); return { provider: "rr", success: Boolean( result?.success || (roStatus && String(roStatus.status || roStatus.Status).toUpperCase() === "SUCCESS") ), customerNo: result?.customerNo, svId: result?.svId, roStatus: roStatus && { status: roStatus.status ?? roStatus.Status, statusCode: roStatus.statusCode ?? roStatus.StatusCode, message: roStatus.message ?? roStatus.Message }, statusBlocks: result?.statusBlocks || undefined, xml: result?.xml, parsed: result?.parsed, ...extra }; }; /** Build a stringified JSON array for the `message` text column */ const buildMessageJSONString = ({ error, classification, result, fallback }) => { const msgs = []; const clean = (v) => { if (v == null) return null; try { const s = String(v).replace(/\s+/g, " ").trim(); return s.length ? s : null; } catch { return null; } }; const push = (v) => { const s = clean(v); if (s && !msgs.includes(s)) msgs.push(s); }; // Friendly first push(classification?.friendlyMessage); push(classification?.title); // Error text if (error instanceof Error) push(error.message); else if (typeof error === "string") push(error); else if (error?.message) push(error.message); // RR status message push( result?.roStatus?.message ?? result?.roStatus?.Message ?? result?.statusBlocks?.transaction?.message ?? result?.statusBlocks?.transaction?.Message ); // Fallback push(fallback || "RR export failed"); const arr = msgs.length ? msgs : ["RR export failed"]; return JSON.stringify(arr); }; /** * Success: mark job exported + (optionally) insert a success log. * Uses queries.MARK_JOB_EXPORTED (same shape as Fortellis/PBS). * @param {boolean} isEarlyRo - If true, only logs success but does NOT change job status (for early RO creation) */ const markRRExportSuccess = async ({ socket, jobId, job, bodyshop, result, metaExtra = {}, isEarlyRo = false }) => { const endpoint = process.env.GRAPHQL_ENDPOINT; if (!endpoint) throw new Error("GRAPHQL_ENDPOINT not configured"); const token = getAuthToken(socket); if (!token) throw new Error("Auth token missing on socket"); const client = new GraphQLClient(endpoint, {}); client.setHeaders({ Authorization: `Bearer ${token}` }); const meta = buildRRExportMeta({ result, extra: metaExtra }); // For early RO, we only insert a log but do NOT change job status or mark as exported if (isEarlyRo) { try { await client.request(queries.INSERT_EXPORT_LOG, { logs: [ { bodyshopid: bodyshop?.id || job?.bodyshop?.id, jobid: jobId, successful: true, useremail: socket?.user?.email || null, metadata: meta, message: buildMessageJSONString({ result, fallback: "RR early RO created" }) } ] }); CreateRRLogEvent(socket, "INFO", "RR early RO: success log inserted (job status unchanged)", { jobId }); } catch (e) { CreateRRLogEvent(socket, "ERROR", "RR early RO: failed to insert success log", { jobId, error: e?.message }); } return; } // Full export: mark job as exported and insert success log const exportedStatus = job?.bodyshop?.md_ro_statuses?.default_exported || bodyshop?.md_ro_statuses?.default_exported || "Exported*"; try { await client.request(queries.MARK_JOB_EXPORTED, { jobId, job: { status: exportedStatus, date_exported: new Date() }, log: { bodyshopid: bodyshop?.id || job?.bodyshop?.id, jobid: jobId, successful: true, useremail: socket?.user?.email || null, metadata: meta, message: buildMessageJSONString({ result, fallback: "RR export succeeded" }) }, bill: { exported: true, exported_at: new Date() } }); CreateRRLogEvent(socket, "INFO", "RR export: job marked exported + success log inserted", { jobId, exportedStatus }); } catch (e) { CreateRRLogEvent(socket, "ERROR", "RR export: failed to persist success markers/log", { jobId, error: e?.message }); } }; /** * Failure: insert failure ExportsLog with `message` as JSON **string** (text column). * Uses queries.INSERT_EXPORT_LOG($logs: [exportlog_insert_input!]!). */ const insertRRFailedExportLog = async ({ socket, jobId, job, bodyshop, error, classification, result }) => { const endpoint = process.env.GRAPHQL_ENDPOINT; if (!endpoint) throw new Error("GRAPHQL_ENDPOINT not configured"); const token = getAuthToken(socket); if (!token) throw new Error("Auth token missing on socket"); const client = new GraphQLClient(endpoint, {}); client.setHeaders({ Authorization: `Bearer ${token}` }); const meta = buildRRExportMeta({ result, extra: { error: error?.message || String(error), classification: classification || undefined } }); const message = buildMessageJSONString({ error, classification, result, fallback: "RR export failed" }); const entry = { bodyshopid: bodyshop?.id || job?.bodyshop?.id, jobid: jobId, successful: false, message, // stringified JSON array useremail: socket?.user?.email || null, metadata: meta }; try { // Your mutation expects $logs (array). Keep to that signature. await client.request(queries.INSERT_EXPORT_LOG, { logs: [entry] }); CreateRRLogEvent(socket, "INFO", "RR export: failure log inserted", { jobId }); } catch (e) { CreateRRLogEvent(socket, "ERROR", "RR export: failed to insert failure log", { jobId, error: e?.message }); } }; module.exports = { markRRExportSuccess, insertRRFailedExportLog };