diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 304423d4e..0c2c9c8e1 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1786,6 +1786,7 @@ }, "jobs": { "actions": { + "addpayer": "Add Payer", "addDocuments": "Add Job Documents", "addNote": "Add Note", "addtopartsqueue": "Add to Parts Queue", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 9fdfa21b6..a73e3d1d6 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1780,6 +1780,7 @@ }, "jobs": { "actions": { + "addpayer": "", "addDocuments": "Agregar documentos de trabajo", "addNote": "AƱadir la nota", "addtopartsqueue": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 2f07205c9..e0c1b29cc 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1780,6 +1780,7 @@ }, "jobs": { "actions": { + "addpayer": "", "addDocuments": "Ajouter des documents de travail", "addNote": "Ajouter une note", "addtopartsqueue": "", diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml index 051a718ef..a4bc41901 100644 --- a/hasura/metadata/tables.yaml +++ b/hasura/metadata/tables.yaml @@ -1164,6 +1164,7 @@ - notification_followers - state - md_order_statuses + - md_ro_statuses retry_conf: interval_sec: 10 num_retries: 0 @@ -1184,7 +1185,8 @@ "new": { "id": {{$body.event.data.new.id}}, "shopname": {{$body.event.data.new.shopname}}, - "md_order_statuses": {{$body.event.data.new.md_order_statuses}} + "md_order_statuses": {{$body.event.data.new.md_order_statuses}}, + "md_ro_statuses": {{$body.event.data.new.md_ro_statuses}} } }, "op": {{$body.event.op}}, diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 56f9b58a8..9e45eabab 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2956,6 +2956,7 @@ exports.GET_BODYSHOP_BY_ID = ` query GET_BODYSHOP_BY_ID($id: uuid!) { bodyshops_by_pk(id: $id) { id + md_ro_statuses md_order_statuses shopname imexshopid diff --git a/server/rr/rr-export-logs.test.js b/server/rr/rr-export-logs.test.js new file mode 100644 index 000000000..5178595cc --- /dev/null +++ b/server/rr/rr-export-logs.test.js @@ -0,0 +1,187 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { createRequire } from "node:module"; + +const require = createRequire(import.meta.url); +const mock = require("mock-require"); + +const graphqlRequestModuleId = require.resolve("graphql-request"); +const queriesModuleId = require.resolve("../graphql-client/queries"); +const rrLoggerModuleId = require.resolve("./rr-logger-event"); +const rrExportLogsModuleId = require.resolve("./rr-export-logs"); + +const loadExportLogs = ({ requests }) => { + mock.stopAll(); + + mock(graphqlRequestModuleId, { + GraphQLClient: class MockGraphQLClient { + constructor(endpoint) { + this.endpoint = endpoint; + this.headers = {}; + } + + setHeaders(headers) { + this.headers = headers; + return this; + } + + async request(query, variables) { + requests.push({ + endpoint: this.endpoint, + headers: this.headers, + query, + variables + }); + return {}; + } + } + }); + + mock(queriesModuleId, { + INSERT_EXPORT_LOG: "INSERT_EXPORT_LOG", + MARK_JOB_EXPORTED: "MARK_JOB_EXPORTED" + }); + + mock(rrLoggerModuleId, () => {}); + + delete require.cache[rrExportLogsModuleId]; + return require(rrExportLogsModuleId); +}; + +const socket = { + data: { authToken: "socket-token" }, + user: { email: "tech@example.com" } +}; + +const job = { + id: "job-1", + bodyshop: { + id: "bodyshop-1", + md_ro_statuses: { + default_exported: "Exported" + } + } +}; + +describe("server/rr/rr-export-logs", () => { + const originalEndpoint = process.env.GRAPHQL_ENDPOINT; + + afterEach(() => { + mock.stopAll(); + delete require.cache[rrExportLogsModuleId]; + process.env.GRAPHQL_ENDPOINT = originalEndpoint; + }); + + it("marks Reynolds full exports as exported using the shared DMS export mutation", async () => { + process.env.GRAPHQL_ENDPOINT = "https://graphql.example.test/v1/graphql"; + const requests = []; + const { markRRExportSuccess } = loadExportLogs({ requests }); + + await markRRExportSuccess({ + socket, + jobId: job.id, + job, + bodyshop: job.bodyshop, + result: { + success: true, + roStatus: { + status: "SUCCESS", + statusCode: "0", + message: "Finalized" + } + } + }); + + expect(requests).toHaveLength(1); + expect(requests[0]).toMatchObject({ + endpoint: "https://graphql.example.test/v1/graphql", + headers: { Authorization: "Bearer socket-token" }, + query: "MARK_JOB_EXPORTED", + variables: { + jobId: "job-1", + job: { + status: "Exported", + date_exported: expect.any(Date) + }, + log: { + bodyshopid: "bodyshop-1", + jobid: "job-1", + successful: true, + useremail: "tech@example.com" + }, + bill: { + exported: true, + exported_at: expect.any(Date) + } + } + }); + }); + + it("uses the separately loaded bodyshop statuses when job.bodyshop is missing", async () => { + process.env.GRAPHQL_ENDPOINT = "https://graphql.example.test/v1/graphql"; + const requests = []; + const { markRRExportSuccess } = loadExportLogs({ requests }); + + await markRRExportSuccess({ + socket, + jobId: job.id, + job: { id: job.id }, + bodyshop: job.bodyshop, + result: { + success: true, + roStatus: { + status: "SUCCESS", + statusCode: "0", + message: "Finalized" + } + } + }); + + expect(requests).toHaveLength(1); + expect(requests[0]).toMatchObject({ + query: "MARK_JOB_EXPORTED", + variables: { + jobId: "job-1", + job: { + status: "Exported", + date_exported: expect.any(Date) + }, + log: { + bodyshopid: "bodyshop-1", + jobid: "job-1", + successful: true, + useremail: "tech@example.com" + } + } + }); + }); + + it("does not mark Reynolds early RO creation as exported", async () => { + process.env.GRAPHQL_ENDPOINT = "https://graphql.example.test/v1/graphql"; + const requests = []; + const { markRRExportSuccess } = loadExportLogs({ requests }); + + await markRRExportSuccess({ + socket, + jobId: job.id, + job, + bodyshop: job.bodyshop, + result: { success: true }, + isEarlyRo: true + }); + + expect(requests).toHaveLength(1); + expect(requests[0]).toMatchObject({ + query: "INSERT_EXPORT_LOG", + variables: { + logs: [ + { + bodyshopid: "bodyshop-1", + jobid: "job-1", + successful: true, + useremail: "tech@example.com" + } + ] + } + }); + }); +});