@machinemetrics/mm-erp-sdk 0.1.9-beta.5 → 0.1.9-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"mm-erp-sdk.js","sources":["../src/types/erp-types.ts","../src/services/erp-api-services/rest/get-query-params.ts","../src/services/mm-api-service/types/receive-types.ts","../src/utils/http-client.ts","../src/services/mm-api-service/token-mgr.ts","../src/services/erp-api-services/errors.ts","../src/services/mm-api-service/mm-api-service.ts","../src/services/mm-api-service/types/send-types.ts","../src/services/mm-api-service/company-info.ts","../src/utils/uniqueRows.ts","../src/utils/removeExtraneousFields.ts","../src/utils/removeIdFieldFromPayload.ts","../src/utils/trimObjectValues.ts","../src/utils/cleanup-numbers.ts","../src/utils/data-transformation.ts","../src/utils/timezone.ts","../src/utils/time-utils.ts","../src/services/caching-service/record-tracking-manager.ts","../src/services/caching-service/batch-cache-manager.ts","../src/utils/mm-labor-ticket-helpers.ts","../src/utils/standard-process-drivers/labor-ticket-erp-synchronizer.ts","../src/services/mm-api-service/types/entity-transformer.ts","../src/utils/standard-process-drivers/error-processor.ts","../src/utils/standard-process-drivers/mm-entity-processor.ts","../src/utils/standard-process-drivers/standard-process-drivers.ts","../src/services/erp-api-services/rest/rest-api-service.ts","../src/utils/erp-type-from-entity.ts","../src/utils/error-utils.ts","../src/utils/error-formatter.ts","../src/utils/connector-log/mm-connector-logger.ts","../src/utils/connector-log/log-deduper.ts","../src/utils/application-initializer.ts","../src/services/erp-api-services/graphql/graphql-service.ts","../src/services/erp-api-services/oauth-client.ts","../src/services/nats-service/nats-service.ts","../src/services/data-sync-service/nats-labor-ticket-listener.ts","../src/services/data-sync-service/data-sync-service.ts","../src/services/sql-server-erp-service/internal/sql-server-config.ts","../src/services/sql-server-erp-service/sql-server-service.ts","../src/services/sql-server-erp-service/internal/sql-transaction-manager.ts","../src/services/sql-server-erp-service/internal/sql-labor-ticket-operations.ts","../src/services/sql-server-erp-service/sql-server-helpers.ts","../src/services/psql-erp-service/psql-service.ts","../src/services/psql-erp-service/psql-helpers.ts"],"sourcesContent":["export enum ERPObjType {\n RESOURCES,\n PARTS,\n PART_OPERATION,\n WORK_ORDERS,\n WORK_ORDER_OPERATIONS,\n PERSONS,\n REASONS,\n LABOR_TICKETS,\n // TO DO: TIMESHEETS support\n}\n\nexport interface ERPPagination {\n count: number;\n limit: number;\n nextPage: string | undefined;\n previousPage: string | undefined;\n offset?: number;\n}\n\nexport interface ERPResponse {\n data: Record<string, string>[];\n paging: ERPPagination;\n}\n\nexport type ERPObject = Record<string, string>;\n","/**\n * Types and utilities for handling GET query parameters and responses\n */\n\nexport enum Operator {\n eq = \"eq\",\n lt = \"lt\",\n lte = \"lte\",\n gt = \"gt\",\n gte = \"gte\",\n}\n\nexport type LogicalCondition = {\n field: string;\n value: string;\n operator: Operator;\n};\n\nexport type InCondition = { field: string; value: Array<string> };\n\n/**\n * QueryParams is specifically used for the get API, defining pagination and filters\n */\nexport interface QueryParams {\n limit?: number;\n nextPage?: string;\n previousPage?: string;\n filter?: {\n in?: InCondition[];\n and?: LogicalCondition[];\n or?: LogicalCondition[];\n };\n}\n\nexport interface Pagination {\n count: number;\n limit: number;\n nextPage?: string;\n previousPage?: string;\n}\n\nexport interface PaginatedAPIResponse<T> {\n data: T[];\n paging: Pagination;\n}\n\n/**\n * Builds a logical condition string from an array of conditions\n * @param conditions Array of logical conditions\n * @param operator The logical operator to use ('and' or 'or')\n * @returns A string representation of the logical conditions\n */\nexport const buildLogicalCondition = (\n conditions: LogicalCondition[],\n operator: \"and\" | \"or\"\n): string => {\n return conditions\n .map(\n (condition) =>\n `${condition.field} ${condition.operator} '${condition.value}'`\n )\n .join(` ${operator} `);\n};\n","//#region MMReceiveLaborTicket\n/**\n * Represents a labor ticket as received from the MM API /erp/v1/labor-tickets/export/updates\n */\nexport class MMReceiveLaborTicket {\n constructor(data: Partial<MMReceiveLaborTicket>) {\n Object.assign(this, data);\n }\n\n // Returns the closed date if it is a valid date, otherwise returns null\n get workOrderOperationClosedDate(): string | null {\n const closedDate = this.workOrderOperation?.closedDate;\n return typeof closedDate === \"string\" &&\n closedDate !== \"\" &&\n !isNaN(Date.parse(closedDate))\n ? closedDate\n : null;\n }\n\n set workOrderOperationClosedDate(value: string | null) {\n if (this.workOrderOperation) {\n this.workOrderOperation.closedDate = value;\n }\n }\n\n // Returns 1 if the work order operation is closed (has a valid closed date), 0 otherwise\n get workOrderOpIsClosed(): number {\n return this.workOrderOperationClosedDate !== null ? 1 : 0;\n }\n\n /*Descriptions from machinemetrics.atlassian.net/wiki/spaces/DEVE/pages/842661901/MM+ERP+Schema+Descriptions*/\n\n badParts!: number; // The number of parts produced that do not meet quality standards\n clockIn!: string | null; // The timestamp when the work on this ticket started\n clockOut!: string | null; // The timestamp when the work on this ticket ended\n comment!: string | null;\n createdAt!: string | null;\n goodParts!: number; // The number of parts produced that meet quality standards\n laborTicketId!: string; // The unique identifier for the labor ticket in the ERP\n laborTicketRef!: string; // The unique identifier for the labor ticket in MM\n lot!: string; // Refers to a material batch. If not available in the ERP, default to an empty string\n personId!: string; // The person who did the work\n reasons?: MMReceiveLaborTicketReason[];\n resourceId!: string; // The physical resource that did the work\n sequenceNumber!: number; // Which sequence this operation is for in the order, associates back to a part_operation with the part_number from the work_order, and sequence_number from the op\n split!: string; // Batch number if order split for staggered delivery. If not available in the ERP, default to an empty string\n state!: string; // The current state of the labor ticket (e.g., \"CLOSED/OPEN\")\n type!: string; // If the labor is production, setup, or indirect\n syncedAt!: string | null;\n updatedAt!: string | null;\n workOrderId!: string; // The order this labor ticket corresponds to\n workOrderOperation?: MMReceiveLaborTicketWorkOrderOperation;\n sub!: string; // Batch number for parts that had to be rerun. If not available in the ERP, default to an empty string\n transactionDate!: string | null;\n}\n\n/**\n * Represents a reason in the MMApiReceiveLaborTicket object as received from MM\n */\nexport interface MMReceiveLaborTicketReason {\n reason: {\n annotationTypeRef: string | null;\n category: string; // Parent group if applicable (optional)\n code: string; // The functional name of the label\n companyId: string;\n createdAt: string;\n description: string;\n entityType: string; // Whether it represents machine downtime or rejected parts context (downtime/quality)\n reasonId: string; // Unique identifier in the ERP\n reasonRef: number;\n rejectReasonRef: number;\n updatedAt: string | null;\n };\n}\n\n/**\n * Represents a work order operation in the MMApiReceiveLaborTicket object, incomplete when\n * pulling updated labor tickets from MM\n */\nexport interface MMReceiveLaborTicketWorkOrderOperation {\n closedDate: string | null; // non-null and a valid date if the work order operation is closed\n}\n//#endregion MMReceiveLaborTicket\n\n//#region MMReceiveTimesheet\n\n// MLW TODO\n\n// #endregion MMReceiveTimesheet\n","import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from \"axios\";\nimport logger from \"../services/reporting-service/logger\";\n\nexport class HTTPError extends Error {\n constructor(\n message: string,\n public status: number,\n public code?: string,\n public data?: unknown\n ) {\n super(message);\n this.name = \"HTTPError\";\n }\n}\n\nexport type HTTPMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\nexport interface HTTPRequestConfig {\n method: HTTPMethod;\n url: string;\n headers?: Record<string, string>;\n data?: unknown;\n params?: Record<string, string>;\n}\n\nexport interface HTTPResponse<T = unknown> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\nexport interface HTTPClient {\n request<T = unknown>(config: HTTPRequestConfig): Promise<HTTPResponse<T>>;\n handleError(error: unknown): HTTPError;\n destroy(): Promise<void>;\n}\n\nexport interface HTTPClientConfig {\n baseUrl: string;\n retryAttempts: number;\n}\n\n/**\n * This is a factory class for creating HTTPClient instances, at least for now\n * just a wrapper around AxiosClient\n */\nexport class HTTPClientFactory {\n static getInstance(config: HTTPClientConfig): HTTPClient {\n return new AxiosClient(config.baseUrl, config.retryAttempts);\n }\n}\n\nclass AxiosClient implements HTTPClient {\n private client: AxiosInstance | null = null;\n private retryAttempts: number;\n private isDestroyed: boolean = false;\n private inFlightControllers: Set<AbortController> = new Set();\n private pendingTimeouts: Set<ReturnType<typeof setTimeout>> = new Set();\n private pendingSleepResolvers: Set<() => void> = new Set();\n\n /**\n * Note regarding baseURL, from https://github.com/axios/axios\n * `baseURL` will be prepended to `url` unless `url` is absolute and option `allowAbsoluteUrls` is set to true (the default).\n * It can be convenient to set `baseURL` for an instance of axios to pass relative URLs to methods of that instance.\n */\n constructor(baseUrl: string, retryAttempts: number) {\n this.client = axios.create({\n baseURL: baseUrl,\n timeout: 30000,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n this.retryAttempts = retryAttempts;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n if (this.isDestroyed) {\n resolve();\n return;\n }\n const timeout = setTimeout(() => {\n this.pendingTimeouts.delete(timeout);\n this.pendingSleepResolvers.delete(resolve);\n resolve();\n }, ms);\n this.pendingTimeouts.add(timeout);\n this.pendingSleepResolvers.add(resolve);\n });\n }\n\n async request<T = unknown>(\n config: HTTPRequestConfig\n ): Promise<HTTPResponse<T>> {\n if (this.isDestroyed || !this.client) {\n throw new HTTPError(\"HTTP client has been destroyed\", 500);\n }\n\n const controller = new AbortController();\n this.inFlightControllers.add(controller);\n\n const axiosConfig: AxiosRequestConfig = {\n method: config.method,\n url: config.url,\n headers: config.headers,\n data: config.data,\n params: config.params,\n signal: controller.signal,\n };\n\n logger.info(\"HTTP request starting\", { \n url: config.url, \n method: config.method, \n baseURL: this.client.defaults.baseURL,\n retryAttempts: this.retryAttempts \n });\n \n // Extra console.log for debugging\n console.log(\"=== FULL URL DEBUG ===\");\n console.log(\"baseURL:\", this.client.defaults.baseURL);\n console.log(\"relative url:\", config.url);\n console.log(\"full constructed URL:\", (this.client.defaults.baseURL || \"\") + config.url);\n console.log(\"method:\", config.method);\n\n let lastError: unknown;\n try {\n for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {\n try {\n logger.info(`HTTP request attempt ${attempt + 1}/${this.retryAttempts + 1}`);\n const response = await this.client.request<T>(axiosConfig);\n logger.info(\"HTTP request succeeded\", { status: response.status });\n return {\n data: response.data,\n status: response.status,\n headers: response.headers as Record<string, string>,\n };\n } catch (error) {\n lastError = error;\n\n const isAxiosErr = error instanceof AxiosError;\n const code = isAxiosErr ? error.code : undefined;\n const status = isAxiosErr ? error.response?.status : undefined;\n const errorConstructor = error instanceof Error ? error.constructor.name : undefined;\n const message = error instanceof Error ? error.message : String(error);\n \n logger.info(`HTTP request attempt ${attempt + 1} failed`, {\n errorType: typeof error,\n errorConstructor,\n isAxiosError: isAxiosErr,\n message,\n code,\n status,\n });\n\n // Don't retry on 4xx errors (client errors)\n if (\n error instanceof AxiosError &&\n error.response?.status &&\n error.response.status >= 400 &&\n error.response.status < 500\n ) {\n logger.info(\"Not retrying due to 4xx client error\");\n break;\n }\n\n // Don't retry canceled/aborted requests\n if (error instanceof AxiosError && error.code === \"ERR_CANCELED\") {\n break;\n }\n\n // If this was the last attempt, don't wait\n if (attempt < this.retryAttempts) {\n const waitTime = Math.pow(2, attempt) * 1000;\n logger.info(`Waiting ${waitTime}ms before retry`);\n await this.sleep(waitTime);\n if (this.isDestroyed) {\n throw new HTTPError(\"HTTP client has been destroyed\", 500);\n }\n }\n }\n }\n } finally {\n this.inFlightControllers.delete(controller);\n }\n logger.info(\"HTTP request failed after all retries, throwing error\");\n throw this.handleError(lastError, config);\n }\n\n handleError(error: unknown, requestConfig?: HTTPRequestConfig): HTTPError {\n if (error instanceof AxiosError) {\n // Build a more descriptive error message that includes the URL and method\n const baseUrl = this.client?.defaults.baseURL || \"\";\n const fullUrl = requestConfig\n ? `${baseUrl}${requestConfig.url}`\n : \"Unknown URL\";\n const method = requestConfig?.method || \"Unknown method\";\n\n const errorMessage = `${error.message} (${method} ${fullUrl})`;\n\n return new HTTPError(\n errorMessage,\n error.response?.status || 500,\n error.code,\n error.response?.data\n );\n }\n return new HTTPError(\n error instanceof Error ? error.message : \"Unknown error occurred\",\n 500\n );\n }\n\n async destroy(): Promise<void> {\n if (this.isDestroyed) return;\n this.isDestroyed = true;\n\n // Abort any in-flight requests\n for (const c of this.inFlightControllers) {\n try { c.abort(); } catch { /* ignore: abort may throw in some environments */ }\n }\n this.inFlightControllers.clear();\n\n // Cancel any pending retry waits\n for (const t of this.pendingTimeouts) {\n clearTimeout(t);\n }\n this.pendingTimeouts.clear();\n for (const resolve of this.pendingSleepResolvers) {\n try { resolve(); } catch { /* ignore: resolver threw; cleanup proceeds */ }\n }\n this.pendingSleepResolvers.clear();\n\n // Drop axios instance reference\n this.client = null;\n }\n}\n","import {\n getCachedMMToken,\n setCachedMMToken,\n} from \"../../utils/local-data-store/jobs-shared-data\";\nimport { HTTPClient, HTTPClientFactory } from \"../../utils/http-client\";\nimport { CoreConfiguration } from \"../data-sync-service/configuration-manager\";\nimport { logger } from \"../reporting-service\";\n\n/**\n * Manages the MM API token with caching and expiration checking\n * Tokens are cached across jobs and job instances in local-data-store/jobs-shared-data.ts\n * @jest-environment node\n */\nexport class MMTokenManager {\n private token: string;\n private tokenExpiration: Date | null;\n private baseUrl: string;\n private api: HTTPClient;\n\n constructor() {\n const cached = getCachedMMToken();\n this.token = cached?.token || \"\";\n this.tokenExpiration = cached?.expiration\n ? new Date(cached.expiration)\n : null;\n const config = CoreConfiguration.inst();\n console.log(\"=== MMTOKEN MANAGER DEBUG ===\");\n console.log(\"config.mmApiBaseUrl:\", config.mmApiBaseUrl);\n console.log(\"config object keys:\", Object.keys(config));\n this.baseUrl = config.mmApiBaseUrl;\n console.log(\"this.baseUrl set to:\", this.baseUrl);\n console.log(\"=== END MMTOKEN MANAGER DEBUG ===\");\n this.api = HTTPClientFactory.getInstance({\n baseUrl: this.baseUrl,\n retryAttempts: config.mmApiRetryAttempts,\n });\n }\n\n /**\n * Checks if the current token is expired or will expire within the next minute\n * @returns boolean indicating if token needs refresh\n */\n private isTokenExpired(): boolean {\n if (!this.tokenExpiration) return true;\n\n // Add 1 minute buffer to ensure we refresh before actual expiration\n const bufferTimeMS = 60_000;\n const isExpired =\n this.tokenExpiration.getTime() - bufferTimeMS <= Date.now();\n if (isExpired) {\n logger.info(\"isTokenExpired: the api token from MM has expired\");\n }\n return isExpired;\n }\n\n /**\n * Safely extracts expiration time from JWT token\n * @param token JWT token string\n * @returns Date object representing token expiration, or null if cannot be determined\n */\n private getTokenExpiration(token: string): Date | null {\n try {\n // Split token into parts\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n console.warn(\"Invalid JWT format: token does not have 3 parts\");\n return null;\n }\n\n // Decode payload (second part)\n const payload = Buffer.from(parts[1], \"base64\").toString(\"utf-8\");\n const decodedPayload = JSON.parse(payload);\n\n // Check if exp claim exists\n if (typeof decodedPayload.exp !== \"number\") {\n console.warn(\"JWT does not contain expiration time (exp claim)\");\n return null;\n }\n\n // Convert Unix timestamp (seconds) to Date\n return new Date(decodedPayload.exp * 1000);\n } catch (error) {\n console.warn(\"Error parsing JWT token:\", error);\n return null;\n }\n }\n\n /**\n * Gets a valid token, either from cache or by requesting a new one\n * @returns Promise resolving to a valid token string\n */\n public async getToken(): Promise<string> {\n if (this.token && !this.isTokenExpired()) {\n return this.token;\n }\n\n const config = CoreConfiguration.inst();\n const serviceToken = config.mmApiAuthToken;\n const tokenResponse = await this.api.request<string>({\n url: \"/user-token\",\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${serviceToken}`,\n },\n });\n\n this.token = tokenResponse.data;\n this.tokenExpiration = this.getTokenExpiration(this.token);\n\n // Cache the new token and its expiration\n setCachedMMToken({\n token: this.token,\n expiration: this.tokenExpiration?.getTime() || null,\n });\n\n logger.info(\"getToken: the api token from MM has been refreshed\");\n return this.token;\n }\n\n /**\n * Invalidates the current token, forcing a fresh fetch on next getToken() call\n */\n public invalidateToken(): void {\n this.token = \"\";\n this.tokenExpiration = null;\n setCachedMMToken({ token: \"\", expiration: null });\n }\n}\n","import { AxiosError } from \"axios\";\nimport logger from \"../reporting-service/logger\";\nimport { GraphQLServerError } from \"./graphql/types\";\nimport { HTTPError } from \"../../utils/http-client\";\n\n/**\n * Represents GraphQL specific errors (validation, missing fields, etc.)\n */\nexport class GraphQLError extends Error {\n constructor(\n message: string,\n public errors: GraphQLServerError[]\n ) {\n super(message);\n this.name = \"GraphQLError\";\n }\n}\n\n/**\n * Centralized error handling for API requests.\n * Transforms various error types into standardized HTTPError or GraphQLError.\n */\nexport class ErrorHandler {\n /**\n * Transforms any error into a standardized HTTPError or GraphQLError and throws it.\n * Never returns - always throws an error.\n *\n * @throws {HTTPError|GraphQLError} Standardized error with appropriate details\n */\n static handle(error: unknown): never {\n // Handle HTTPError (already processed by http-client)\n if (error instanceof HTTPError) {\n // For GraphQL responses with errors in the data\n if (\n error.data &&\n typeof error.data === \"object\" &&\n \"errors\" in error.data\n ) {\n const graphqlError = new GraphQLError(\n \"GraphQL Error\",\n (error.data as { errors: GraphQLServerError[] }).errors\n );\n\n // Log the error\n logger.error(\"Error>>GraphQL\", {\n message: graphqlError.message,\n errors: graphqlError.errors,\n });\n\n throw graphqlError;\n }\n\n // Log HTTP error with generic response data\n logger.error(\"Error>>HTTP\", {\n status: error.status,\n message: error.message,\n code: error.code,\n data: error.data,\n });\n\n throw error; // Re-throw the original HTTPError\n }\n\n // Handle raw AxiosError (direct usage, not through http-client)\n if (error instanceof AxiosError) {\n // For GraphQL responses with errors\n if (error.response?.data?.errors) {\n const graphqlError = new GraphQLError(\n \"GraphQL Error\",\n error.response.data.errors as GraphQLServerError[]\n );\n\n // Log the error\n logger.error(\"Error>>GraphQL\", {\n message: graphqlError.message,\n errors: graphqlError.errors,\n });\n\n throw graphqlError;\n }\n\n // For HTTP errors - create HTTPError and log with response data\n const httpError = new HTTPError(\n error.message,\n error.response?.status || 500,\n error.code,\n error.response?.data\n );\n\n // Log HTTP error with generic response data\n logger.error(\"Error>>HTTP\", {\n status: httpError.status,\n message: httpError.message,\n code: httpError.code,\n data: httpError.data,\n });\n\n throw httpError;\n }\n\n // For other errors\n const httpError = new HTTPError(\n error instanceof Error ? error.message : \"An unknown error occurred\",\n 500\n );\n\n // Log the error\n logger.error(\"Error>>Unknown\", {\n message: httpError.message,\n status: httpError.status,\n });\n\n throw httpError;\n }\n}\n","import logger from \"../reporting-service/logger\";\nimport { ERPObjType } from \"../../types/erp-types\";\nimport { MMReceiveLaborTicket } from \"./types/receive-types\";\nimport { MMTokenManager } from \"./token-mgr\";\nimport * as MM_SEND_TYPES from \"./types/send-types\";\nimport {\n HTTPClient,\n HTTPMethod,\n HTTPRequestConfig,\n HTTPClientFactory,\n} from \"../../utils/http-client\";\nimport { Checkpoint } from \"./types/checkpoint\";\nimport { CoreConfiguration } from \"../data-sync-service/configuration-manager\";\nimport { ErrorHandler } from \"../erp-api-services/errors\";\nimport {\n MMApiBaseResponse,\n MMGraphQLResourceResponse,\n MMMachineGroupsResponse,\n} from \"./types/mm-response-interfaces\";\n\nconst MAPPING = \"MAPPING\";\nconst CHECKPOINTS = \"CHECKPOINTS\";\nconst MACHINE_GROUPS = \"MACHINE_GROUPS\";\nconst GRAPHQL = \"GRAPHQL\";\n\nenum UrlBase {\n ErpApiSvcBase = \"ErpApiSvcBase\",\n ApiBase = \"ApiBase\",\n}\n\n/**\n * Class to manage interactions with the MM Mapping Service\n * Integrates an MM Token Manager to handle the API token.\n */\nexport class MMApiClient {\n tokenMgr: MMTokenManager;\n api: HTTPClient;\n\n private baseUrls: Record<UrlBase, string>;\n\n resourceURLs = {\n [ERPObjType.RESOURCES]: \"/erp/v1/resources\",\n [ERPObjType.PARTS]: \"/erp/v1/parts\",\n [ERPObjType.PART_OPERATION]: \"/erp/v1/part-operations\",\n [ERPObjType.WORK_ORDERS]: \"/erp/v1/production-orders\",\n [ERPObjType.WORK_ORDER_OPERATIONS]: \"/erp/v1/production-order-operations\",\n [ERPObjType.PERSONS]: \"/erp/v1/persons\",\n [ERPObjType.REASONS]: \"/erp/v1/reasons\",\n [ERPObjType.LABOR_TICKETS]: \"/erp/v1/labor-tickets\",\n [MAPPING]: \"/erp/v1/mapping\",\n [CHECKPOINTS]: \"/erp/v1/checkpoints\",\n [MACHINE_GROUPS]: \"/machine-groups\",\n [GRAPHQL]: \"/proxy/graphql\",\n };\n\n constructor() {\n this.tokenMgr = new MMTokenManager();\n this.baseUrls = {\n [UrlBase.ErpApiSvcBase]: CoreConfiguration.inst().mmERPSvcApiBaseUrl,\n [UrlBase.ApiBase]: CoreConfiguration.inst().mmApiBaseUrl,\n };\n this.api = HTTPClientFactory.getInstance({\n baseUrl: '',\n retryAttempts: CoreConfiguration.inst().mmApiRetryAttempts,\n });\n }\n\n //#region Private Methods\n private async executeWithAuthRetry<T>(\n operation: (token: string) => Promise<T>,\n options: { token?: string } = {}\n ): Promise<T> {\n let token = options.token || (await this.tokenMgr.getToken());\n\n try {\n return await operation(token);\n } catch (error: unknown) {\n // Type guard to check if error has status property\n const hasStatus = (err: unknown): err is { status: number } => {\n return typeof err === \"object\" && err !== null && \"status\" in err;\n };\n\n // Check if this looks like an auth error - retry on auth status codes or JWT expiration errors\n const isAuthError =\n hasStatus(error) && (error.status === 401 || error.status === 403) ||\n (hasStatus(error) && error.status === 500 && \n typeof (error as any).data?.error === 'string' && \n (error as any).data.error.includes('JWT'));\n\n if (isAuthError && !options.token) {\n // Only retry if we used cached token\n logger.info(\"Retrying request with fresh token due to auth error\");\n this.tokenMgr.invalidateToken();\n token = await this.tokenMgr.getToken();\n return await operation(token); // Single retry\n }\n\n throw error; // Re-throw if not auth error or already retried\n }\n }\n\n /**\n * Fetch data from the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n * Flags:\n */\n private async getData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n queryParams: { [key: string]: string } = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...queryParams,\n ...(baseUrl === UrlBase.ErpApiSvcBase ? { raw: \"true\" } : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const result = await this.api.request<object>({\n url: fullUrl,\n method: \"GET\" as HTTPMethod,\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n });\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Send data to the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n * Flags:\n * - 'raw' = true and 'dedupe' = true will be set for MM's ERP API url\n */\n private async postData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n data: object = {},\n queryParams: { [key: string]: string } = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...queryParams,\n ...(baseUrl === UrlBase.ErpApiSvcBase\n ? { dedupe: \"true\", raw: \"true\" }\n : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const request: HTTPRequestConfig = {\n url: fullUrl,\n method: \"POST\",\n data: JSON.stringify(data),\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n };\n\n const result = await this.api.request<object>(request);\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Delete data from the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n */\n private async deleteData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n data: object = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...(baseUrl === UrlBase.ErpApiSvcBase ? { raw: \"true\" } : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const result = await this.api.request<object>({\n url: fullUrl,\n method: \"DELETE\",\n data,\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n });\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Patch data to the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n */\n private async patchData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n data: object = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...(baseUrl === UrlBase.ErpApiSvcBase ? { raw: \"true\" } : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const request: HTTPRequestConfig = {\n url: fullUrl,\n method: \"PATCH\",\n data,\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n };\n\n const result = await this.api.request<object>(request);\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n private async graphqlRequest(\n operationName: string,\n query: string\n ): Promise<MMApiBaseResponse> {\n try {\n return await this.postData(\n this.resourceURLs[GRAPHQL],\n { operationName, query },\n {},\n {\n baseUrl: UrlBase.ApiBase,\n token: CoreConfiguration.inst().mmApiAuthToken,\n headers: { \"Content-Type\": \"application/json\" },\n }\n );\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Fetch a checkpoint for a specific system, table, and checkpoint type\n * @param checkpoint The checkpoint to fetch\n * @returns Promise with the checkpoint data\n */\n private async fetchCheckpoint(\n checkpoint: Checkpoint\n ): Promise<MMApiBaseResponse> {\n const { system, table, checkpointType } = checkpoint;\n return this.getData(this.resourceURLs[CHECKPOINTS], {\n system,\n table,\n checkpointType,\n });\n }\n\n //#endregion private methods\n\n //#region Public Methods\n /**\n * Send resources to the MM API\n * @param resources Array of resources to send\n * @returns Promise with the API response\n */\n async sendResourcesToMM(\n resources: MM_SEND_TYPES.MMSendResource[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.RESOURCES], {\n data: resources.map((resource) => resource.toRESTApiObject()),\n });\n }\n\n /**\n * Send parts to the MM API\n * @param parts Array of parts to send\n * @returns Promise with the API response\n */\n async sendPartsToMM(\n parts: MM_SEND_TYPES.MMSendPart[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.PARTS], {\n data: parts.map((part) => part.toRESTApiObject()),\n });\n }\n\n /**\n * Send part operations to the MM API\n * @param operations Array of part operations to send\n * @returns Promise with the API response\n */\n async sendPartOperationsToMM(\n operations: MM_SEND_TYPES.MMSendPartOperation[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.PART_OPERATION], {\n data: operations.map((operation) => operation.toRESTApiObject()),\n });\n }\n\n /**\n * Send work orders to the MM API\n * @param workOrders Array of work orders to send\n * @returns Promise with the API response\n */\n async sendWorkOrdersToMM(\n workOrders: MM_SEND_TYPES.MMSendWorkOrder[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.WORK_ORDERS], {\n data: workOrders.map((workOrder) => workOrder.toRESTApiObject()),\n });\n }\n\n /**\n * Send work order operations to the MM API\n * @param workOrderOperations Array of work order operations to send\n * @returns Promise with the API response\n */\n async sendWorkOrderOperationsToMM(\n workOrderOperations: MM_SEND_TYPES.MMSendWorkOrderOperation[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(\n this.resourceURLs[ERPObjType.WORK_ORDER_OPERATIONS],\n {\n data: workOrderOperations.map((operation) =>\n operation.toRESTApiObject()\n ),\n }\n );\n }\n\n /**\n * Send persons to the MM API\n * @param persons Array of persons to send\n * @returns Promise with the API response\n */\n async sendPersonsToMM(\n persons: MM_SEND_TYPES.MMSendPerson[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.PERSONS], {\n data: persons.map((person) => person.toRESTApiObject()),\n });\n }\n\n /**\n * Send reasons to the MM API\n * @param reasons Array of reasons to send\n * @returns Promise with the API response\n */\n async sendReasonsToMM(\n reasons: MM_SEND_TYPES.MMSendReason[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.REASONS], {\n data: reasons.map((reason) => reason.toRESTApiObject()),\n });\n }\n\n /**\n * Send labor tickets to the MM API\n * @param laborTickets Array of labor tickets to send\n * @returns Promise with the API response\n */\n async sendLaborTicketsToMM(\n laborTickets: MM_SEND_TYPES.MMSendLaborTicket[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.LABOR_TICKETS], {\n data: laborTickets.map((ticket) => ticket.toRESTApiObject()),\n });\n }\n\n /**\n * Delete labor tickets from the MM API\n * @param laborTickets Array of labor ticket records to delete\n * @returns Promise with the API response\n */\n async deleteLaborTickets(\n laborTickets: Record<string, string>[]\n ): Promise<MMApiBaseResponse> {\n logger.debug(\"deleteLaborTickets:\", { laborTickets });\n return await this.deleteData(this.resourceURLs[ERPObjType.LABOR_TICKETS], {\n laborTickets,\n });\n }\n\n /**\n * Fetch transformed labor tickets from the MM API\n * @returns Promise with the transformed labor tickets\n */\n async fetchTransformedLaborTickets(): Promise<MMApiBaseResponse> {\n return await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/export`\n );\n }\n\n /**\n * Fetch labor ticket updates from the MM API\n * @param query Query parameters for filtering updates\n * @returns Promise with an array of labor ticket updates\n */\n async fetchLaborTicketUpdates(\n query: Record<string, string>\n ): Promise<MMReceiveLaborTicket[]> {\n const updates = (await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/export/updates`,\n query\n )) as { data: MMReceiveLaborTicket[] };\n return updates.data.map((ticket) => new MMReceiveLaborTicket(ticket));\n }\n\n /**\n * Fetch a single labor ticket by reference from the MM API\n * @param laborTicketRef The labor ticket reference to fetch\n * @returns Promise with the labor ticket data\n */\n async fetchLaborTicketByRef(\n laborTicketRef: string\n ): Promise<MMReceiveLaborTicket> {\n const response = (await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/${laborTicketRef}`\n )) as { data: MMReceiveLaborTicket };\n return new MMReceiveLaborTicket(response.data);\n }\n\n /**\n * Fetch a checkpoint for a specific system, table, and checkpoint type\n * @param checkpoint The checkpoint to fetch\n * @returns Promise with the checkpoint data\n */\n async getCheckpoint(checkpoint: Checkpoint): Promise<MMApiBaseResponse> {\n return await this.fetchCheckpoint(checkpoint);\n }\n\n /**\n * Save a checkpoint to the MM API\n * @param checkpoint The checkpoint to save\n * @returns Promise with the API response\n */\n async saveCheckpoint(checkpoint: Checkpoint): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[CHECKPOINTS], checkpoint);\n }\n\n /**\n * Initialize a checkpoint for a specific system, table, and checkpoint type\n * @param checkpoint The checkpoint to initialize\n * @returns Promise with the checkpoint data\n */\n async initializeCheckpoint(checkpoint: Checkpoint): Promise<void> {\n const currentCheckpoint = await this.fetchCheckpoint(checkpoint);\n\n logger.info(\"currentCheckpoint:\", { currentCheckpoint });\n logger.info(\"currentCheckpoint keys:\", {\n keys: Object.keys(currentCheckpoint || {}),\n });\n\n // Check if we have a meaningful checkpoint response\n // The API returns either:\n // - Empty: just { httpStatus: 200 }\n // - With data: { timestamp: \"2025-08-08T18:53:14.295Z\", httpStatus: 200 }\n const checkpointResponse = currentCheckpoint as any;\n const hasCheckpointData =\n checkpointResponse && checkpointResponse.timestamp;\n\n if (!hasCheckpointData) {\n logger.info(\"No checkpoint data found, initializing checkpoint\");\n const saveCheckpoint = await this.saveCheckpoint(checkpoint);\n logger.info(\"saveCheckpoint:\", { saveCheckpoint });\n } else {\n logger.info(\"Existing checkpoint found:\", {\n timestamp: checkpointResponse.timestamp,\n });\n }\n }\n\n /**\n * Send data to MM API based on the object type\n * @param type The type of ERP object to send\n * @param objects Array of objects to send\n * @returns Promise with the API response including affected rows count\n */\n public async sendDataToMMByType(\n type: ERPObjType,\n objects: MM_SEND_TYPES.IToRESTApiObject[]\n ): Promise<MMApiBaseResponse> {\n switch (type) {\n case ERPObjType.PERSONS:\n return await this.sendPersonsToMM(\n objects as MM_SEND_TYPES.MMSendPerson[]\n );\n case ERPObjType.RESOURCES:\n return await this.sendResourcesToMM(\n objects as MM_SEND_TYPES.MMSendResource[]\n );\n case ERPObjType.PARTS:\n return await this.sendPartsToMM(objects as MM_SEND_TYPES.MMSendPart[]);\n case ERPObjType.PART_OPERATION:\n return await this.sendPartOperationsToMM(\n objects as MM_SEND_TYPES.MMSendPartOperation[]\n );\n case ERPObjType.WORK_ORDERS:\n return await this.sendWorkOrdersToMM(\n objects as MM_SEND_TYPES.MMSendWorkOrder[]\n );\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return await this.sendWorkOrderOperationsToMM(\n objects as MM_SEND_TYPES.MMSendWorkOrderOperation[]\n );\n case ERPObjType.REASONS:\n return await this.sendReasonsToMM(\n objects as MM_SEND_TYPES.MMSendReason[]\n );\n case ERPObjType.LABOR_TICKETS:\n return await this.sendLaborTicketsToMM(\n objects as MM_SEND_TYPES.MMSendLaborTicket[]\n );\n default:\n throw new Error(`Unsupported type: ${ERPObjType[type]}`);\n }\n }\n\n /**\n * Update the labor ticket ERP ID by laborTicketRef\n * @param laborTicketRef The MM API labor ticket unique ID\n * @param erpObjectId The ERP labor ticket ID to update\n * @returns The updated labor ticket\n */\n async updateLaborTicketIdByRef(\n laborTicketRef: string,\n laborTicketId: string\n ): Promise<MMApiBaseResponse> {\n return await this.patchData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/${laborTicketRef}`,\n { laborTicketId }\n );\n }\n\n /**\n * Add failed labor ticket refs to the MM API\n * @param failedTicketRefs The failed labor ticket references\n * @returns The response from the MM API\n */\n async addFailedLaborTicketRefs(\n system: string,\n failedTicketRefs: string[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/failed`,\n { failedTicketRefs },\n { system }\n );\n }\n\n async fetchFailedLaborTickets(\n system: string\n ): Promise<MMReceiveLaborTicket[]> {\n const failedTickets = (await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/failed`,\n { system }\n )) as { erpLaborTickets: MMReceiveLaborTicket[] };\n return failedTickets.erpLaborTickets.map(\n (ticket) => new MMReceiveLaborTicket(ticket)\n );\n }\n\n /**\n * Send connector logs to the MM API\n * @param logEntry Single log entry to send\n * @returns Promise with the API response\n */\n async sendConnectorLog(logEntry: { source: string; level: string; message: string }): Promise<MMApiBaseResponse> {\n return await this.postData(\n '/connector/logs',\n logEntry,\n {},\n { baseUrl: UrlBase.ApiBase }\n );\n }\n\n /**\n * Send bulk connector logs to the MM API\n * @param logs Array of log entries to send\n * @returns Promise with the API response\n */\n async sendBulkConnectorLogs(logs: { source: string; level: string; message: string }[]): Promise<MMApiBaseResponse> {\n return await this.postData(\n '/connector/logs',\n { logs },\n {},\n { baseUrl: UrlBase.ApiBase }\n );\n }\n\n async deleteFailedLaborTicketIds(\n system: string,\n laborTicketRefs: string[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/failed/remove`,\n { laborTicketRefs },\n { system }\n );\n }\n\n /**\n * Fetch machine groups from the MM API\n * @returns Promise with the machine groups data\n */\n async fetchMachineGroups(): Promise<MMMachineGroupsResponse> {\n return (await this.getData(\n this.resourceURLs[MACHINE_GROUPS],\n {},\n { baseUrl: UrlBase.ApiBase }\n )) as unknown as MMMachineGroupsResponse;\n }\n\n /**\n * Fetch resources using GraphQL\n * @returns Promise with the resources data\n */\n async fetchResourcesFromGraphQL(): Promise<MMGraphQLResourceResponse> {\n return (await this.graphqlRequest(\n \"getErpResources\",\n \"query getErpResources { machineResource: erpResources { machineRef machineGroupId resourceId }}\"\n )) as unknown as MMGraphQLResourceResponse;\n }\n\n /**\n * Cleanup all HTTP connections and resources\n * Call this when the service is no longer needed\n */\n async destroy(): Promise<void> {\n await this.api.destroy();\n // Note: MMTokenManager doesn't currently need explicit cleanup\n // but if it ever acquires resources that need cleanup, add it here\n }\n\n //#endregion public methods\n}\n","/**\n * These represent types of objects that can be sent to the MM API\n * Note that each class also implements a static method to convert from a plain\n * object to a REST API object: e.g.\n * MMSendPerson::fromPlainObject(data: Record<string, string | null>): MMSendPerson\n */\n\nexport interface IToRESTApiObject {\n toRESTApiObject(): Record<string, string | null>;\n}\n\n/**\n * Interface for static factory methods that can reconstruct typed objects\n * from plain data, without validation.\n */\n\nexport class MMSendPerson implements IToRESTApiObject {\n constructor(\n public personId: string,\n public firstName: string,\n public lastName: string,\n public isActive: boolean\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n personId: this.personId,\n firstName: this.firstName,\n lastName: this.lastName,\n isActive: this.isActive ? \"1\" : \"0\",\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendPerson {\n return new MMSendPerson(\n data.personId || \"\",\n data.firstName || \"\",\n data.lastName || \"\",\n data.isActive === \"1\"\n );\n }\n}\n\nexport class MMSendResource implements IToRESTApiObject {\n constructor(\n public resourceId: string, // Unique identifier that the ERP uses to describe a resource\n public name: string, // A colloquial name for the resource, sometimes also just the resource_id\n public isResourceGroup: boolean, // Whether or not this record represents an individual machine/piece of hardware, or a group/type of machines\n public description: string, // Text description of the resource (optional)\n public type: string, // The type of resource (optional)\n public productionBurdenRateHourly: number, // The cost associated with running this machine in production/hour (optional)\n public setupBurdenRateHourly: number // The cost associated with running this machine in setup/hour (optional)\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n resourceId: this.resourceId,\n name: this.name,\n isResourceGroup: this.isResourceGroup ? \"1\" : \"0\",\n description: this.description,\n type: this.type,\n productionBurdenRateHourly: this.productionBurdenRateHourly.toString(),\n setupBurdenRateHourly: this.setupBurdenRateHourly.toString(),\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendResource {\n return new MMSendResource(\n data.resourceId || \"\",\n data.name || \"\",\n data.isResourceGroup === \"1\",\n data.description || \"\",\n data.type || \"\",\n parseFloat(data.productionBurdenRateHourly || \"0\"),\n parseFloat(data.setupBurdenRateHourly || \"0\")\n );\n }\n}\n\nexport class MMSendPart implements IToRESTApiObject {\n constructor(\n public partNumber: string,\n public partRevision: string,\n public method: string\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n partNumber: this.partNumber,\n partRevision: this.partRevision,\n method: this.method,\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendPart {\n return new MMSendPart(\n data.partNumber || \"\",\n data.partRevision || \"\",\n data.method || \"\"\n );\n }\n}\n\nexport class MMSendPartOperation implements IToRESTApiObject {\n constructor(\n public partNumber: string,\n public partRevision: string,\n public method: string,\n public sequenceNumber: string,\n public resourceId: string,\n public cycleTimeMs: number,\n public setupTimeMs: number,\n public description: string,\n public quantityPerPart: number\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n partNumber: this.partNumber,\n partRevision: this.partRevision,\n method: this.method,\n sequenceNumber: this.sequenceNumber,\n resourceId: this.resourceId,\n cycleTimeMs: this.cycleTimeMs.toString(),\n setupTimeMs: this.setupTimeMs.toString(),\n description: this.description,\n quantityPerPart: this.quantityPerPart.toString(),\n };\n }\n\n static fromPlainObject(\n data: Record<string, string | null>\n ): MMSendPartOperation {\n return new MMSendPartOperation(\n data.partNumber || \"\",\n data.partRevision || \"\",\n data.method || \"\",\n data.sequenceNumber || \"\",\n data.resourceId || \"\",\n parseInt(data.cycleTimeMs || \"0\"),\n parseInt(data.setupTimeMs || \"0\"),\n data.description || \"\",\n parseFloat(data.quantityPerPart || \"1\")\n );\n }\n}\n\nexport class MMSendWorkOrder implements IToRESTApiObject {\n constructor(\n public workOrderId: string,\n public lot: string,\n public split: string,\n public sub: string,\n public status: string,\n public dueDate: string | null,\n public description: string,\n public scheduledStartDate: string | null,\n public scheduledEndDate: string | null,\n public closedDate: string | null,\n public quantityRequired: number,\n public partNumber: string,\n public partRevision: string,\n public method: string\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n workOrderId: this.workOrderId,\n lot: this.lot,\n split: this.split,\n sub: this.sub,\n status: this.status,\n dueDate: this.dueDate,\n description: this.description,\n scheduledStartDate: this.scheduledStartDate,\n scheduledEndDate: this.scheduledEndDate,\n closedDate: this.closedDate,\n quantityRequired: this.quantityRequired.toString(),\n partNumber: this.partNumber,\n partRevision: this.partRevision,\n method: this.method,\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendWorkOrder {\n return new MMSendWorkOrder(\n data.workOrderId || \"\",\n data.lot || \"\",\n data.split || \"\",\n data.sub || \"\",\n data.status || \"\",\n data.dueDate ?? null,\n data.description || \"\",\n data.scheduledStartDate ?? null,\n data.scheduledEndDate ?? null,\n data.closedDate ?? null,\n parseFloat(data.quantityRequired || \"0\"),\n data.partNumber || \"\",\n data.partRevision || \"\",\n data.method || \"\"\n );\n }\n}\n\nexport class MMSendWorkOrderOperation implements IToRESTApiObject {\n constructor(\n public workOrderId: string,\n public lot: string,\n public split: string,\n public sub: string,\n public sequenceNumber: string,\n public resourceId: string,\n public startQuantity: number,\n public finishQuantity: number,\n public expectedRejectRate: number,\n public scheduledStartDate: string | null,\n public scheduledFinishDate: string | null,\n public closedDate: string | null,\n public cycleTimeMs: number,\n public setupTimeMs: number,\n public productionburdenRateHourly: number,\n public setupburdenRatehourly: number,\n public operationType: string,\n public quantityPerPart: number,\n public status: string\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n workOrderId: this.workOrderId,\n lot: this.lot,\n split: this.split,\n sub: this.sub,\n sequenceNumber: this.sequenceNumber,\n resourceId: this.resourceId,\n startQuantity: this.startQuantity.toString(),\n finishQuantity: this.finishQuantity.toString(),\n expectedRejectRate: this.expectedRejectRate.toString(),\n scheduledStartDate: this.scheduledStartDate,\n scheduledFinishDate: this.scheduledFinishDate,\n closedDate: this.closedDate,\n cycleTimeMs: this.cycleTimeMs.toString(),\n setupTimeMs: this.setupTimeMs.toString(),\n productionburdenRateHourly: this.productionburdenRateHourly.toString(),\n setupburdenRatehourly: this.setupburdenRatehourly.toString(),\n operationType: this.operationType,\n quantityPerPart: this.quantityPerPart.toString(),\n status: this.status,\n };\n }\n\n static fromPlainObject(\n data: Record<string, string | null>\n ): MMSendWorkOrderOperation {\n return new MMSendWorkOrderOperation(\n data.workOrderId || \"\",\n data.lot || \"\",\n data.split || \"\",\n data.sub || \"\",\n data.sequenceNumber || \"\",\n data.resourceId || \"\",\n parseFloat(data.startQuantity || \"0\"),\n parseFloat(data.finishQuantity || \"0\"),\n parseFloat(data.expectedRejectRate || \"0\"),\n data.scheduledStartDate ?? null,\n data.scheduledFinishDate ?? null,\n data.closedDate ?? null,\n parseInt(data.cycleTimeMs || \"0\"),\n parseInt(data.setupTimeMs || \"0\"),\n parseFloat(data.productionburdenRateHourly || \"0\"),\n parseFloat(data.setupburdenRatehourly || \"0\"),\n data.operationType || \"\",\n parseFloat(data.quantityPerPart || \"1\"),\n data.status || \"\"\n );\n }\n}\n\nexport class MMSendReason implements IToRESTApiObject {\n constructor(\n public reasonId: string,\n public category: string,\n public code: string,\n public description: string,\n public entityType: string\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n reasonId: this.reasonId,\n category: this.category,\n code: this.code,\n description: this.description,\n entityType: this.entityType,\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendReason {\n return new MMSendReason(\n data.reasonId || \"\",\n data.category || \"\",\n data.code || \"\",\n data.description || \"\",\n data.entityType || \"\"\n );\n }\n}\n\nexport class MMSendLaborTicket implements IToRESTApiObject {\n constructor(\n public workOrderId: string,\n public lot: string,\n public split: string,\n public sub: string,\n public sequenceNumber: string,\n public resourceId: string,\n public personId: string,\n public laborTicketId: string,\n public transactionDate: string | null,\n public clockIn: string | null,\n public clockOut: string | null,\n public goodParts: number,\n public badParts: number,\n public type: string,\n public comment: string,\n public state: string\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n workOrderId: this.workOrderId,\n lot: this.lot,\n split: this.split,\n sub: this.sub,\n sequenceNumber: this.sequenceNumber,\n resourceId: this.resourceId,\n personId: this.personId,\n laborTicketId: this.laborTicketId,\n transactionDate: this.transactionDate,\n clockIn: this.clockIn,\n clockOut: this.clockOut,\n goodParts: this.goodParts.toString(),\n badParts: this.badParts.toString(),\n type: this.type,\n comment: this.comment,\n state: this.state,\n };\n }\n\n static fromPlainObject(\n data: Record<string, string | null>\n ): MMSendLaborTicket {\n return new MMSendLaborTicket(\n data.workOrderId || \"\",\n data.lot || \"\",\n data.split || \"\",\n data.sub || \"\",\n data.sequenceNumber || \"\",\n data.resourceId || \"\",\n data.personId || \"\",\n data.laborTicketId || \"\",\n data.transactionDate ?? null,\n data.clockIn ?? null,\n data.clockOut ?? null,\n parseInt(data.goodParts || \"0\"),\n parseInt(data.badParts || \"0\"),\n data.type || \"\",\n data.comment || \"\",\n data.state || \"\"\n );\n }\n}\n\n/**\n * Flattened interface for Work Order related entities that maintains referential integrity\n * by processing parts, part operations, work orders, and work order operations together\n */\nexport interface MMSendWorkOrderBatch {\n parts: MMSendPart[];\n partOperations: MMSendPartOperation[];\n workOrders: MMSendWorkOrder[];\n workOrderOperations: MMSendWorkOrderOperation[];\n}\n","import { CoreConfiguration } from \"../data-sync-service/configuration-manager\";\nimport { HTTPClientFactory } from \"../../utils/http-client\";\nimport { logger } from \"../reporting-service\";\n\n// Cache for company info to avoid repeated API calls\nlet companyInfoCache: {\n timezone: string;\n locationRef: string;\n companyId: string;\n} | null = null;\n\nexport interface CompanyInfo {\n timezone: string;\n locationRef: string;\n companyId: string;\n}\n\n/**\n * Fetches company information from /accounts/current endpoint\n * Results are cached to avoid repeated API calls\n * @returns Promise<CompanyInfo> Company information including timezone, locationRef, and companyId\n * @throws Error if unable to fetch company information\n */\nexport const getCompanyInfo = async (): Promise<CompanyInfo> => {\n // Return cached value if available\n if (companyInfoCache) {\n return companyInfoCache;\n }\n\n try {\n // Get the configuration\n const config = CoreConfiguration.inst();\n const apiUrl = config.mmApiBaseUrl;\n const authToken = config.mmApiAuthToken;\n\n if (!apiUrl || !authToken) {\n throw new Error(\"Missing required configuration for company info fetch\");\n }\n\n // Create HTTP client\n const client = HTTPClientFactory.getInstance({\n baseUrl: apiUrl,\n retryAttempts: config.mmApiRetryAttempts,\n });\n\n // Fetch user/company information from the API\n const response = await client.request<{\n company: {\n timezone: string;\n id: string;\n };\n locationRef: number;\n }>({\n url: \"/accounts/current?includeLocation=true\",\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${authToken}`,\n },\n });\n\n const userInfo = response.data;\n if (!userInfo?.company) {\n throw new Error(\"Unable to retrieve company information from API\");\n }\n\n // Debug: Log what we actually got from the API\n logger.info(\"Fetched company info from /accounts/current\", {\n locationRef: userInfo.locationRef,\n companyId: userInfo.company.id,\n timezone: userInfo.company.timezone,\n });\n\n // Cache and return company info - locationRef is at top level, not in company\n companyInfoCache = {\n timezone: userInfo.company.timezone,\n locationRef: String(userInfo.locationRef), // Convert number to string\n companyId: userInfo.company.id,\n };\n\n return companyInfoCache;\n } catch (error) {\n throw new Error(\n `Failed to get company info: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n};\n\n","/**\n * Extracts unique rows from data based on specified fields and sorts by up to two fields\n * @param data Array of data objects\n * @param fields Array of field names to use as a unique keys\n * @param sortFields Optional array of field names to sort by (up to two fields)\n * @returns Array of unique objects based on the specified fields, or an empty array if data is empty\n */\nexport function getUniqueRows<T extends Record<string, unknown>>(\n data: T[],\n fields: (keyof T)[],\n sortFields?: (keyof T)[]\n): T[] {\n const createCompositeKey = (item: T): string => {\n return fields.map((field) => String(item[field])).join(\"|\");\n };\n\n const uniqueKeys = new Set<string>();\n const uniqueRows: T[] = [];\n\n data.forEach((item) => {\n const key = createCompositeKey(item);\n if (!uniqueKeys.has(key)) {\n uniqueKeys.add(key);\n uniqueRows.push(item);\n }\n });\n\n if (sortFields && sortFields.length > 0) {\n uniqueRows.sort((a, b) => {\n for (let i = 0; i < sortFields.length; i++) {\n const field = sortFields[i];\n if (a[field] < b[field]) return -1;\n if (a[field] > b[field]) return 1;\n }\n return 0;\n });\n }\n\n return uniqueRows || [];\n}\n","import { ERPObject } from \"../types/erp-types\";\n\n/**\n * Retain only the fields listed in retainFields[] in the payload\n */\nexport const removeExtraneousFields = (\n recordset: ERPObject[],\n retainFields: string[]\n): ERPObject[] => {\n const data: ERPObject[] =\n recordset.map((row) => {\n const transformedRow: ERPObject = {};\n Object.keys(row).forEach((key) => {\n if (retainFields.includes(key)) {\n transformedRow[key] = row[key]?.toString() || \"\";\n }\n });\n return transformedRow;\n }) || [];\n\n return data;\n};\n","import { ERPObject } from \"../types/erp-types\";\n\n/**\n * Remove all fields listed in idField[] the from the payload\n */\nexport const getPayloadWithoutIDField = (\n recordset: ERPObject[],\n idField: string[]\n): ERPObject[] => {\n const data: ERPObject[] =\n recordset.map((row) => {\n const transformedRow: ERPObject = {};\n Object.keys(row).forEach((key) => {\n if (!idField.includes(key)) {\n transformedRow[key] = row[key]?.toString() || \"\";\n }\n });\n return transformedRow;\n }) || [];\n\n return data;\n};\n","/**\n * Trims all string values in an object while preserving the object's structure\n */\nexport const trimObjectValues = <T extends object>(obj: T): T => {\n return Object.keys(obj).reduce((acc, key) => {\n const value = obj[key as keyof T];\n acc[key as keyof T] = (\n typeof value === \"string\" ? value.trim() : value\n ) as T[keyof T];\n return acc;\n }, {} as T);\n};\n","export const cleanupNumbers = (data: string) => {\n // eslint-disable-next-line no-useless-escape\n data = data.toString().replace(/[^0-9\\.]+/g, \"\");\n return data;\n};\n","/**\n * Add a new field to the data from an external source by matching on the same field name\n * @param data The data to add the new field to\n * @param externalSource The external source to get the new field from\n * @param externalSourceFieldName The field in both data and external source to match on\n * @param newFieldName The name of the new field to add\n * @param defaultValue The default value to use if there is no match\n */\nexport const addNewFieldFromExternalSource = (\n data: Record<string, string>[],\n externalSource: Record<string, string>[],\n externalSourceFieldName: string,\n newFieldName: string,\n defaultValue: string = \"-\"\n): Record<string, string>[] => {\n return data.map((current) => {\n const cacheEntry = externalSource.find(\n (existing) =>\n existing[externalSourceFieldName] === current[externalSourceFieldName]\n );\n const newFieldValue = cacheEntry?.[newFieldName] || defaultValue;\n\n return {\n ...current,\n [newFieldName]: newFieldValue,\n };\n });\n};\n\n/**\n * Add a new field to the data from an external source by matching on different field names\n * @param data The data to add the new field to\n * @param externalSource The external source to get the new field from\n * @param lookupField The field in the external source to match on\n * @param currentField The field in the current data to match on\n * @param newFieldName The name of the new field to add\n * @param defaultValue The default value to use if there is no match\n */\nexport const addNewFieldFromLookupField = (\n data: Record<string, string>[],\n externalSource: Record<string, string>[],\n lookupField: string,\n currentField: string,\n newFieldName: string,\n defaultValue: string = \"-\"\n): Record<string, string>[] => {\n return data.map((current) => {\n const cacheEntry = externalSource.find(\n (existing) => existing[lookupField] === current[currentField]\n );\n const newFieldValue = cacheEntry?.[currentField] || defaultValue;\n\n return {\n ...current,\n [newFieldName]: newFieldValue,\n };\n });\n};\n","import { CoreConfiguration } from \"../services/data-sync-service/configuration-manager\";\nimport { HTTPClientFactory } from \"./http-client\";\n\n/**\n * Gets the timezone offset in hours and timezone name for the company's timezone\n * @returns Promise<{ offset: number; timezone: string }> The timezone offset in hours and timezone name\n * @throws Error if unable to fetch timezone information\n */\nexport const getTimezoneOffset = async (): Promise<{ offset: number; timezone: string }> => {\n try {\n // Get the timezone from configuration\n const config = CoreConfiguration.inst();\n const apiUrl = config.mmApiBaseUrl;\n const authToken = config.mmApiAuthToken;\n\n if (!apiUrl || !authToken) {\n throw new Error(\"Missing required configuration for timezone fetch\");\n }\n\n // Create HTTP client\n const client = HTTPClientFactory.getInstance({\n baseUrl: apiUrl,\n retryAttempts: config.mmApiRetryAttempts,\n });\n\n // Fetch user/company information from the API\n const response = await client.request<{ company: { timezone: string } }>({\n url: \"/accounts/current\",\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${authToken}`,\n },\n });\n\n const userInfo = response.data;\n if (!userInfo?.company?.timezone) {\n throw new Error(\"Unable to retrieve company timezone from API\");\n }\n\n const timezone = userInfo.company.timezone;\n\n // Calculate the timezone offset\n const date = new Date();\n const utcDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"UTC\" }));\n const tzDate = new Date(\n date.toLocaleString(\"en-US\", { timeZone: timezone })\n );\n\n // Return offset in hours and timezone name\n const offset = (tzDate.getTime() - utcDate.getTime()) / 3600000;\n return { offset, timezone };\n } catch (error) {\n throw new Error(\n `Failed to get timezone offset: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n};\n\n/**\n * Converts a UTC/Zulu time to local time based on the provided timezone offset\n * @param zuluTime The UTC/Zulu time to convert\n * @param timezoneOffset The timezone offset in hours\n * @returns The converted local time, or undefined if input is null/undefined\n */\nexport const convertToLocalTime = (\n zuluTime: string | Date | null | undefined,\n timezoneOffset: number\n): Date | undefined => {\n if (!zuluTime) return undefined;\n const date = new Date(zuluTime);\n return new Date(date.getTime() + timezoneOffset * 60 * 60 * 1000);\n};\n\n/**\n * Formats a date with the timezone offset in ISO format with offset\n * @param date The date string in format YYYY-MM-DD HH:mm:ss or YYYY-MM-DDTHH:mm:ss\n * @param timezoneOffset The timezone offset in hours\n * @returns The formatted date string in format: YYYY-MM-DDTHH:mm:ss+/-HH:MM\n */\nexport const formatDateWithTZOffset = (\n date: string | null | undefined,\n timezoneOffset: number\n): string | undefined => {\n if (!date) return undefined;\n\n // Replace space with T if present\n const isoDate = date.replace(\" \", \"T\");\n\n // Calculate offset components\n const sign = timezoneOffset >= 0 ? \"+\" : \"-\";\n const absOffset = Math.abs(timezoneOffset);\n const hours = Math.floor(absOffset).toString().padStart(2, \"0\");\n const minutes = Math.floor((absOffset % 1) * 60)\n .toString()\n .padStart(2, \"0\");\n\n // Append the timezone offset\n return `${isoDate}${sign}${hours}:${minutes}`;\n};\n\n/**\n * Formats a Date object as an ISO string with the specified timezone offset\n * @param date The Date object to format (should be a Date that has been shifted by convertToLocalTime)\n * @param timezoneOffset The timezone offset in hours\n * @returns ISO string with offset in format: YYYY-MM-DDTHH:mm:ss.SSS±HH:MM\n */\nexport const toISOWithOffset = (date: Date, timezoneOffset: number): string => {\n const sign = timezoneOffset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(timezoneOffset);\n const hours = Math.floor(abs);\n const minutes = Math.round((abs - hours) * 60);\n const pad2 = (n: number) => n.toString().padStart(2, \"0\");\n const pad3 = (n: number) => n.toString().padStart(3, \"0\");\n\n // Use UTC getters since convertToLocalTime shifts the Date's internal timestamp\n // The UTC components of the shifted Date represent the local wall time\n const yyyy = date.getUTCFullYear();\n const MM = pad2(date.getUTCMonth() + 1);\n const dd = pad2(date.getUTCDate());\n const HH = pad2(date.getUTCHours());\n const mm = pad2(date.getUTCMinutes());\n const ss = pad2(date.getUTCSeconds());\n const SSS = pad3(date.getUTCMilliseconds());\n const off = `${sign}${pad2(hours)}:${pad2(minutes)}`;\n\n return `${yyyy}-${MM}-${dd}T${HH}:${mm}:${ss}.${SSS}${off}`;\n};\n","import logger from \"../services/reporting-service/logger\";\nimport {\n setTimezoneOffsetInCache,\n setTimezoneNameInCache,\n} from \"./local-data-store/jobs-shared-data\";\nimport {\n convertToLocalTime,\n formatDateWithTZOffset,\n getTimezoneOffset,\n} from \"./timezone\";\n\n/**\n * Calculates the difference in hours between two timestamps\n * @param startTime ISO timestamp string\n * @param endTime ISO timestamp string\n * @param timezoneOffset timezone offset in hours\n * @returns number representing hours difference, rounded to 3 decimal places\n */\nexport function calculateTimeDifferenceInHours(\n startTime: string | null | undefined,\n endTime: string | null | undefined,\n timezoneOffset: number\n): number {\n if (!startTime || !endTime) return 0;\n\n const localStartTime = convertToLocalTime(startTime, timezoneOffset);\n const localEndTime = convertToLocalTime(endTime, timezoneOffset);\n\n if (!localStartTime || !localEndTime) return 0;\n\n // Get hours, minutes, seconds from the Date objects\n const startHours = localStartTime.getHours();\n const startMinutes = localStartTime.getMinutes();\n const startSeconds = localStartTime.getSeconds();\n\n const endHours = localEndTime.getHours();\n const endMinutes = localEndTime.getMinutes();\n const endSeconds = localEndTime.getSeconds();\n\n // Calculate total seconds for each time\n const startTotalSeconds =\n startHours * 3600 + startMinutes * 60 + startSeconds;\n const endTotalSeconds = endHours * 3600 + endMinutes * 60 + endSeconds;\n\n if (endTotalSeconds > startTotalSeconds) {\n return Number(((endTotalSeconds - startTotalSeconds) / 3600).toFixed(3));\n }\n\n return 0;\n}\n\nexport const getSecondsOfDay = (timestamp: string | null) => {\n if (!timestamp) return null;\n const time = new Date(timestamp);\n\n const utcHours = time.getUTCHours();\n const utcMinutes = time.getUTCMinutes();\n const utcSeconds = time.getUTCSeconds();\n\n return (utcHours * 3600 + utcMinutes * 60 + utcSeconds).toString();\n};\n\n/**\n * Apply timezone offsets to specified datetime fields in an object\n * @param item The object to transform\n * @param fields The fields to apply timezone offset to\n * @param timezoneOffset The timezone offset to apply\n * @returns The transformed object\n */\n\nexport const applyTimezoneOffsetsToFields = (\n item: Record<string, string>,\n fields: string[],\n timezoneOffset: number\n): Record<string, string> => {\n const transformed = { ...item };\n fields.forEach((field) => {\n if (transformed[field] && transformed[field].trim()) {\n const formattedDate = formatDateWithTZOffset(\n transformed[field],\n timezoneOffset\n );\n transformed[field] = formattedDate || \"\";\n } else {\n transformed[field] = \"\";\n }\n });\n return transformed;\n};\n\ninterface TimezoneOffsetParams {\n maxRetries: number;\n retryIntervalMs: number;\n}\n\n/**\n * Gets the timezone offset and timezone name for the company and sets them in the cache\n * The cached offset can be acquired from getCachedTimezoneOffset() in \"./local-data-store/jobs-shared-data\"\n * The cached timezone name can be acquired from getCachedTimezoneName() in \"./local-data-store/jobs-shared-data\"\n *\n * @param params.maxRetries Maximum number of retry attempts\n * @param params.retryIntervalMs Time to wait between retries in milliseconds\n */\nexport const getTimezoneOffsetAndPersist = async (\n params: TimezoneOffsetParams = {\n maxRetries: 36000, // Retry for 10 hours before giving up.\n retryIntervalMs: 10_000,\n }\n): Promise<void> => {\n let success = false;\n let retries = 0;\n logger.info(\n \"Acquiring the timezone offset and timezone name from MachineMetrics and storing in cache\"\n );\n while (!success && retries < params.maxRetries) {\n try {\n const { offset, timezone } = await getTimezoneOffset();\n logger.info(`Timezone offset: ${offset} hours, timezone: ${timezone}`);\n setTimezoneOffsetInCache(offset);\n setTimezoneNameInCache(timezone);\n success = true;\n } catch (error) {\n logger.error(\"Error getting timezone offset:\", error);\n logger.info(`Retrying in ${params.retryIntervalMs / 1000} seconds...`);\n await new Promise((resolve) =>\n setTimeout(resolve, params.retryIntervalMs)\n );\n retries++;\n }\n }\n if (!success) {\n throw new Error(\n `Failed to get the timezone offset after ${params.maxRetries} retries`\n );\n }\n};\n","import knex, { Knex } from \"knex\";\nimport config from \"../../knexfile\";\nimport { ERPObjType } from \"../../types/erp-types\";\n\nconst DEFAULT_RECORD_TRACKING_TABLE_NAME = \"record_tracking\";\n\nexport type RecordTrackingObject = {\n entityType?: ERPObjType;\n lastValue: string; // Typically date/time string\n recordId: string; // A value to disambiguate records with the same lastValue\n};\n\nexport class RecordTrackingManager {\n db: Knex;\n\n constructor() {\n this.db = knex(config.local);\n }\n\n async updateRecord(record: RecordTrackingObject) {\n if (record.lastValue && record.recordId) {\n const recordUpdated = await this.db(DEFAULT_RECORD_TRACKING_TABLE_NAME)\n .where({ entityType: record.entityType })\n .update(record);\n\n if (recordUpdated < 1) {\n await this.db(DEFAULT_RECORD_TRACKING_TABLE_NAME).insert(record);\n }\n }\n }\n\n async getTrackingRecord(type: ERPObjType) {\n return await this.db(DEFAULT_RECORD_TRACKING_TABLE_NAME)\n .select(\"*\")\n .where({ entityType: type.toString() });\n }\n\n async destroy() {\n return this.db.destroy();\n }\n}\n","import { HashedCacheManager } from \"./hashed-cache-manager\";\nimport { ERPObjType } from \"../../types/erp-types\";\n\nexport type BatchResult<T> = {\n duplicates: T[];\n nonDuplicates: T[];\n};\n\ntype BatchCacheManagerOptions = {\n ttl?: number;\n tableName?: string;\n};\n\nexport class BatchCacheManager {\n private cacheManager: HashedCacheManager;\n\n constructor(options?: BatchCacheManagerOptions) {\n this.cacheManager = new HashedCacheManager({\n ttl: options?.ttl,\n tableName: options?.tableName,\n });\n }\n\n /**\n * Checks a batch of objects against the cache and separates them into duplicates and non-duplicates\n * @param type The type of objects\n * @param objects Array of objects to check\n * @returns Object containing arrays of duplicate and non-duplicate objects\n */\n async dedupeBatch(\n type: ERPObjType,\n objects: object[]\n ): Promise<BatchResult<object>> {\n const result: BatchResult<object> = {\n duplicates: [],\n nonDuplicates: [],\n };\n\n // Check each object individually\n for (const object of objects) {\n const hasChanged = await this.cacheManager.hasChanged(type, object);\n\n if (hasChanged) {\n result.nonDuplicates.push(object);\n } else {\n result.duplicates.push(object);\n }\n }\n\n return result;\n }\n\n /**\n * Stores a batch of objects in the cache\n * @param type The type of objects\n * @param objects Array of objects to store\n */\n async storeBatch(\n type: ERPObjType,\n objects: object[]\n ): Promise<{ totalInserted: number }> {\n // Store each object individually\n let totalInserted = 0;\n for (const object of objects) {\n if (await this.cacheManager.store(type, object)) {\n totalInserted++;\n }\n }\n return { totalInserted };\n }\n\n /**\n * Removes objects from the cache\n * @param type The type of objects\n * @param objects Array of objects to remove\n */\n async removeObjects(type: ERPObjType, objects: object[]): Promise<void> {\n // Remove each object individually\n for (const object of objects) {\n await this.cacheManager.removeRecord(type, object);\n }\n }\n\n /**\n * Gets all records of a specific type\n */\n async getRecordsByType(type: ERPObjType): Promise<string[]> {\n return this.cacheManager.getRecordsByType(type);\n }\n\n /**\n * Removes all records of a specific type\n */\n async removeRecordsByType(type: ERPObjType): Promise<void> {\n return this.cacheManager.removeRecordsByType(type);\n }\n\n /**\n * Clears all records from the cache\n */\n async clear(): Promise<void> {\n return this.cacheManager.clear();\n }\n\n /**\n * Cleans up resources\n */\n async destroy(): Promise<void> {\n return this.cacheManager.destroy();\n }\n}\n","import { convertToLocalTime, toISOWithOffset } from \"./timezone\";\nimport { MMReceiveLaborTicket } from \"../services/mm-api-service/types/receive-types\";\n\n/**\n * Apply timezone offsets to datetime fields specifically for the MMApiReceiveLaborTicket object from the Zulu-based MM API\n * (presumably) before sending to the ERP\n * @param laborTicket The MMApiReceiveLaborTicket object to convert\n * @param timezoneOffset The timezone offset to apply\n */\nexport function convertLaborTicketToLocalTimezone(\n laborTicket: MMReceiveLaborTicket,\n timezoneOffset: number\n): MMReceiveLaborTicket {\n const timeFields = [\n \"clockIn\",\n \"clockOut\",\n \"transactionDate\",\n \"createdAt\",\n \"updatedAt\",\n \"workOrderOperationClosedDate\",\n ] as const;\n\n timeFields.forEach((field) => {\n const localTime = convertToLocalTime(laborTicket[field], timezoneOffset);\n laborTicket[field] = localTime ? toISOWithOffset(localTime, timezoneOffset) : null;\n });\n return laborTicket;\n}\n","import { IERPLaborTicketHandler } from \"../../types/erp-connector\";\nimport { MMApiClient } from \"../../services/mm-api-service/mm-api-service\";\nimport { MMReceiveLaborTicket } from \"../../services/mm-api-service/types/receive-types\";\nimport { convertLaborTicketToLocalTimezone } from \"../mm-labor-ticket-helpers\";\nimport { getCachedTimezoneOffset } from \"../local-data-store/jobs-shared-data\";\nimport logger from \"../../services/reporting-service/logger\";\n\n/**\n * Handles synchronization of labor tickets between MachineMetrics and ERP systems\n */\nexport class LaborTicketERPSynchronizer {\n /**\n * Synchronizes updated labor tickets from MachineMetrics to an ERP system\n */\n static async syncToERP(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n try {\n const mmApiClient = new MMApiClient();\n const failedLaborTicketRefs: string[] = [];\n\n await mmApiClient.initializeCheckpoint({\n system: connectorType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: new Date().toISOString(),\n },\n });\n\n const fallbackTimestamp = new Date().toISOString();\n const laborTicketsUpdates = await mmApiClient.fetchLaborTicketUpdates({\n system: connectorType,\n checkpointType: \"export\",\n });\n\n if (laborTicketsUpdates.length === 0) {\n logger.info(\"syncLaborTicketsToERP:No updated labor tickets found\");\n return;\n }\n\n logger.info(\n `ToERP: Found ${laborTicketsUpdates.length} Labor Ticket Ids and Refs to process`,\n {\n laborTickets: laborTicketsUpdates.map(\n (ticket: MMReceiveLaborTicket) => ({\n ref: ticket.laborTicketRef,\n id: ticket.laborTicketId,\n })\n ),\n }\n );\n\n // Find the most recent updatedAt timestamp from labor tickets. This will be used to update\n // the checkpoint to ensure there is no gap of time for the next sync.\n const mostRecentUpdate = laborTicketsUpdates.reduce(\n (latest: string | null, ticket: MMReceiveLaborTicket) => {\n if (!latest || !ticket.updatedAt) return latest;\n return new Date(ticket.updatedAt) > new Date(latest)\n ? ticket.updatedAt\n : latest;\n },\n null as string | null\n );\n\n await Promise.all(\n laborTicketsUpdates.map(async (laborTicket: MMReceiveLaborTicket) => {\n if (!laborTicket.laborTicketRef) {\n logger.error(\n \"syncLaborTicketsToERP: laborTicketRef is not set for laborTicket pulled from MM:\",\n { laborTicket }\n );\n return undefined;\n }\n\n try {\n return await this.processLaborTicket(\n connector,\n mmApiClient,\n laborTicket\n );\n } catch (error) {\n failedLaborTicketRefs.push(laborTicket.laborTicketRef);\n logger.error(\n `syncLaborTicketsToERP: Error processing laborTicketRef ${laborTicket.laborTicketRef}:`,\n { error }\n );\n return undefined;\n }\n })\n );\n\n logger.info(\n `syncLaborTicketsToERP: ${failedLaborTicketRefs.length} failed labor ticket ids`\n );\n if (failedLaborTicketRefs.length > 0) {\n logger.info(\n `syncLaborTicketsToERP: Reporting ${failedLaborTicketRefs.length} labor ticket failures:`,\n {\n failedLaborTicketRefs,\n }\n );\n const addFailedResult = await mmApiClient.addFailedLaborTicketRefs(\n connectorType,\n failedLaborTicketRefs\n );\n logger.info(\"syncLaborTicketsToERP: addFailedResult:\", {\n addFailedResult,\n });\n }\n\n mmApiClient.saveCheckpoint({\n system: connectorType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: mostRecentUpdate || fallbackTimestamp,\n },\n });\n } catch (error) {\n logger.error(\"syncLaborTicketsToERP: Error:\", error);\n }\n }\n\n /**\n * Retries labor tickets that have failed to be created or updated in the ERP during the sync\n */\n static async retryFailed(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n try {\n const mmApiClient = new MMApiClient();\n const successLaborTicketIds: string[] = [];\n\n const laborTickets =\n await mmApiClient.fetchFailedLaborTickets(connectorType);\n if (laborTickets.length === 0) {\n logger.info(\"retryFailedLaborTickets: No failed labor tickets found\");\n return;\n }\n logger.info(\n \"retryFailedLaborTickets: Failed Labor Tickets count:\" +\n laborTickets.length\n );\n\n await Promise.all(\n laborTickets.map(async (laborTicket: MMReceiveLaborTicket) => {\n if (!laborTicket.laborTicketRef) {\n logger.error(\n \"retryFailedLaborTickets: laborTicketRef is not set for laborTicket pulled from MM:\",\n { laborTicket }\n );\n return undefined;\n }\n\n try {\n const laborTicketResult = await this.processLaborTicket(\n connector,\n mmApiClient,\n laborTicket\n );\n successLaborTicketIds.push(laborTicket.laborTicketRef);\n return laborTicketResult;\n } catch (error) {\n logger.error(\n \"retryFailedLaborTickets: Error processing laborTicketRef:\",\n { laborTicketRef: laborTicket.laborTicketRef, error }\n );\n return undefined;\n }\n })\n );\n\n if (successLaborTicketIds.length > 0) {\n logger.info(\"Deleting failed labor ticket ids:\", {\n successLaborTicketIds,\n });\n const deleteFailedResult = await mmApiClient.deleteFailedLaborTicketIds(\n connectorType,\n successLaborTicketIds\n );\n logger.info(\"deleteFailedResult:\", { deleteFailedResult });\n }\n } catch (error) {\n logger.error(\"retryFailedLaborTickets: Error:\", error);\n }\n }\n\n // ============================================================================\n // PRIVATE HELPER METHODS\n // ============================================================================\n\n private static async writeLaborTicketIdToMM(\n mmApiClient: MMApiClient,\n laborTicket: MMReceiveLaborTicket,\n laborTicketResult: MMReceiveLaborTicket\n ): Promise<void> {\n const updateRefAPIResponse = await mmApiClient.updateLaborTicketIdByRef(\n laborTicket.laborTicketRef,\n laborTicketResult.laborTicketId\n );\n logger.info(\n `Updated laborTicketId ${laborTicketResult.laborTicketId} for laborTicketRef ${laborTicket.laborTicketRef} in MM:`,\n { updateRefAPIResponse }\n );\n }\n\n private static async processLaborTicket(\n connector: IERPLaborTicketHandler,\n mmApiClient: MMApiClient,\n laborTicket: MMReceiveLaborTicket\n ): Promise<MMReceiveLaborTicket> {\n let laborTicketResult: MMReceiveLaborTicket;\n\n laborTicketResult = convertLaborTicketToLocalTimezone(\n laborTicket,\n getCachedTimezoneOffset()\n );\n\n logger.info(\n `processing laborTicket, id=${laborTicket.laborTicketId}, ref=${laborTicket.laborTicketRef}`\n );\n logger.debug({ laborTicket });\n\n // MLW TODO: Should we always swap out the resource ID for the machine group ID if it is found?\n // Or use a flag? What about the default resource ID as used by syteline? A Rutherford consultation is in order.\n /**\n * Swapping out the resource ID for the group id corresponding to the\n * resource ID in the ERP would be done like so:\n * const machineGroupId = await mmApiClient.getResourceERPGroupId(laborTicket.resourceId);\n * if(machineGroupId) {\n * laborTicket.resourceId = machineGroupId;\n * }\n */\n\n if (!laborTicket.laborTicketId) {\n const { laborTicket: laborTicketResult, erpUid } =\n await connector.createLaborTicketInERP(laborTicket);\n laborTicketResult.laborTicketId = erpUid;\n await this.writeLaborTicketIdToMM(\n mmApiClient,\n laborTicket,\n laborTicketResult\n );\n } else {\n laborTicketResult = await connector.updateLaborTicketInERP(laborTicket);\n }\n\n logger.info(\"ToERP: laborTicket update result:\", {\n laborTicketResult:\n laborTicketResult || \"Failed to create/update labor ticket\",\n laborTicketRef: laborTicket.laborTicketRef,\n operation: laborTicket.laborTicketId ? \"update\" : \"create\",\n });\n\n return laborTicketResult;\n }\n}\n","import { ERPObjType } from \"../../../types/erp-types\";\nimport { IToRESTApiObject } from \"..\";\nimport {\n MMSendPerson,\n MMSendResource,\n MMSendPart,\n MMSendPartOperation,\n MMSendWorkOrder,\n MMSendWorkOrderOperation,\n MMSendLaborTicket,\n MMSendReason,\n} from \"..\";\n\n/**\n * Utility class for transforming entities to/from non-typed and typed objects\n * for interacting with the MachineMetrics API..\n */\nexport class EntityTransformer {\n // ============================================================================\n // PUBLIC INTERFACE METHODS\n // ============================================================================\n\n /**\n * Extracts the primary key from a record based on entity type\n * @param entityType The type of entity being processed\n * @param record The record to extract primary key from\n * @returns A string representation of the primary key\n * @throws Error if primary key fields are missing or invalid\n */\n static extractPrimaryKey(\n entityType: ERPObjType,\n record: IToRESTApiObject\n ): string {\n switch (entityType) {\n case ERPObjType.PERSONS:\n return this.extractPersonKey(record);\n case ERPObjType.RESOURCES:\n return this.extractResourceKey(record);\n case ERPObjType.PARTS:\n return this.extractPartKey(record);\n case ERPObjType.PART_OPERATION:\n return this.extractPartOperationKey(record);\n case ERPObjType.WORK_ORDERS:\n return this.extractWorkOrderKey(record);\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return this.extractWorkOrderOperationKey(record);\n case ERPObjType.LABOR_TICKETS:\n return this.extractLaborTicketKey(record);\n case ERPObjType.REASONS:\n return this.extractReasonKey(record);\n default:\n throw new Error(\n `Unsupported entity type for primary key extraction: ${entityType}`\n );\n }\n }\n\n /**\n * Reconstructs a typed object from plain data based on entity type\n * @param entityType The type of entity being processed\n * @param plainData The plain data object containing entity fields\n * @returns A properly typed IToRESTApiObject instance\n * @throws Error if the entity type is not supported\n */\n static reconstructFromPlainData(\n entityType: ERPObjType,\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n switch (entityType) {\n case ERPObjType.PERSONS:\n return this.reconstructPerson(plainData);\n case ERPObjType.RESOURCES:\n return this.reconstructResource(plainData);\n case ERPObjType.PARTS:\n return this.reconstructPart(plainData);\n case ERPObjType.PART_OPERATION:\n return this.reconstructPartOperation(plainData);\n case ERPObjType.WORK_ORDERS:\n return this.reconstructWorkOrder(plainData);\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return this.reconstructWorkOrderOperation(plainData);\n case ERPObjType.LABOR_TICKETS:\n return this.reconstructLaborTicket(plainData);\n case ERPObjType.REASONS:\n return this.reconstructReason(plainData);\n default:\n throw new Error(\n `Unsupported entity type for reconstruction: ${entityType}`\n );\n }\n }\n\n /**\n * Reconstructs multiple typed objects from plain data based on entity type\n * @param entityType The type of entity being processed\n * @param plainDataArray Array of plain data objects containing entity fields\n * @returns Array of properly typed IToRESTApiObject instances\n */\n static reconstructMultipleFromPlainData(\n entityType: ERPObjType,\n plainDataArray: Record<string, string | null>[]\n ): IToRESTApiObject[] {\n return plainDataArray.map((plainData) =>\n this.reconstructFromPlainData(entityType, plainData)\n );\n }\n\n // ============================================================================\n // PRIVATE ENTITY-SPECIFIC KEY EXTRACTION METHODS\n // ============================================================================\n\n private static extractPersonKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(restApiObject, [\"personId\"], \"PERSONS\", [\n \"personId\",\n ]);\n }\n\n private static extractResourceKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"resourceId\"],\n \"RESOURCES\",\n [\"resourceId\"]\n );\n }\n\n private static extractPartKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"partNumber\", \"partRevision\", \"method\"],\n \"PARTS\",\n [\"partNumber\"]\n );\n }\n\n private static extractPartOperationKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"partNumber\", \"partRevision\", \"method\", \"sequenceNumber\"],\n \"PART_OPERATION\",\n [\"partNumber\", \"sequenceNumber\"]\n );\n }\n\n private static extractWorkOrderKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"workOrderId\", \"lot\", \"split\", \"sub\"],\n \"WORK_ORDERS\",\n [\"workOrderId\"]\n );\n }\n\n private static extractWorkOrderOperationKey(\n record: IToRESTApiObject\n ): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"workOrderId\", \"lot\", \"split\", \"sub\", \"sequenceNumber\"],\n \"WORK_ORDER_OPERATIONS\",\n [\"workOrderId\", \"sequenceNumber\"]\n );\n }\n\n private static extractLaborTicketKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n\n // Labor tickets can use laborTicketId if present, otherwise use operational context\n if (restApiObject.laborTicketId) {\n return this.validateAndExtractKey(\n restApiObject,\n [\"laborTicketId\"],\n \"LABOR_TICKETS\",\n [\"laborTicketId\"]\n );\n } else {\n return this.validateAndExtractKey(\n restApiObject,\n [\n \"workOrderId\",\n \"lot\",\n \"split\",\n \"sub\",\n \"sequenceNumber\",\n \"resourceId\",\n \"personId\",\n ],\n \"LABOR_TICKETS\",\n [\"workOrderId\", \"sequenceNumber\", \"resourceId\", \"personId\"]\n );\n }\n }\n\n private static extractReasonKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(restApiObject, [\"reasonId\"], \"REASONS\", [\n \"reasonId\",\n ]);\n }\n\n // ============================================================================\n // PRIVATE ENTITY-SPECIFIC RECONSTRUCTION METHODS\n // ============================================================================\n\n private static reconstructPerson(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendPerson.fromPlainObject(plainData);\n }\n\n private static reconstructResource(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendResource.fromPlainObject(plainData);\n }\n\n private static reconstructPart(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendPart.fromPlainObject(plainData);\n }\n\n private static reconstructPartOperation(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendPartOperation.fromPlainObject(plainData);\n }\n\n private static reconstructWorkOrder(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendWorkOrder.fromPlainObject(plainData);\n }\n\n private static reconstructWorkOrderOperation(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendWorkOrderOperation.fromPlainObject(plainData);\n }\n\n private static reconstructLaborTicket(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendLaborTicket.fromPlainObject(plainData);\n }\n\n private static reconstructReason(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendReason.fromPlainObject(plainData);\n }\n\n // ============================================================================\n // SHARED UTILITY METHODS\n // ============================================================================\n\n /**\n * Helper method to validate and extract primary key components\n * @param restApiObject The REST API object representation\n * @param keyFields The fields that make up the primary key\n * @param entityTypeName The entity type name for error messages\n * @param requiredFields The fields that must be non-empty (others can be empty)\n * @returns Combined primary key string\n * @throws Error if any required field is missing or null\n */\n private static validateAndExtractKey(\n restApiObject: Record<string, string | null>,\n keyFields: string[],\n entityTypeName: string,\n requiredFields: string[]\n ): string {\n const keyValues: string[] = [];\n\n for (const field of keyFields) {\n const value = restApiObject[field];\n\n // Check if this field is required\n if (requiredFields.includes(field)) {\n if (value === null || value === undefined || value === \"\") {\n throw new Error(\n `Primary key field '${field}' is missing or empty for entity type ${entityTypeName}`\n );\n }\n }\n\n // Use the value if present, otherwise use empty string\n keyValues.push(value || \"\");\n }\n\n return keyValues.join(\"|\");\n }\n}\n","import { ERPObjType } from \"../../types/erp-types\";\nimport { BatchCacheManager } from \"../../services/caching-service/batch-cache-manager\";\nimport {\n IToRESTApiObject,\n MM207NonLaborTicketResponse,\n MM207LaborTicketResponse,\n} from \"../../services/mm-api-service\";\nimport { EntityTransformer } from \"../../services/mm-api-service/types/entity-transformer\";\nimport logger from \"../../services/reporting-service/logger\";\n\n/**\n * Handles error processing and record management utilities for MM API operations\n */\nexport class ErrorProcessor {\n /**\n * Creates a set of primary keys for all failed records from batch errors\n * @param entityType The type of entity being processed\n * @param batchErrors Array of batch errors containing failed entities\n * @returns Set of primary keys for failed records\n */\n static createFailedRecordKeySet(\n entityType: ERPObjType,\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>\n ): Set<string> {\n const failedKeySet = new Set<string>();\n\n batchErrors.forEach((batchError) => {\n batchError.affectedEntities.forEach((affectedEntity) => {\n try {\n const primaryKey = EntityTransformer.extractPrimaryKey(\n entityType,\n affectedEntity\n );\n failedKeySet.add(primaryKey);\n } catch (error) {\n logger.warn(\n `Failed to extract primary key from error entity: ${error}`\n );\n // Continue processing other records even if one fails\n }\n });\n });\n\n return failedKeySet;\n }\n\n /**\n * Filters out failed records, returning only successful ones\n * @param entityType The type of entity being processed\n * @param allRecords All records (typed objects) that were sent to the API\n * @param failedKeySet Set of primary keys for records that failed\n * @returns Array of records that succeeded\n */\n static filterSuccessfulRecords(\n entityType: ERPObjType,\n allRecords: IToRESTApiObject[],\n failedKeySet: Set<string>\n ): IToRESTApiObject[] {\n const successfulRecords: IToRESTApiObject[] = [];\n\n allRecords.forEach((record) => {\n try {\n const primaryKey = EntityTransformer.extractPrimaryKey(\n entityType,\n record\n );\n if (!failedKeySet.has(primaryKey)) {\n successfulRecords.push(record);\n }\n } catch (error) {\n logger.warn(\n `Failed to extract primary key from record during filtering: ${error}`\n );\n // If we can't extract the key, we can't determine if it failed, so skip it\n }\n });\n\n return successfulRecords;\n }\n\n /**\n * Orchestrates the caching of successful records on partial failure\n * @param entityType The type of entity being processed\n * @param toProcess All records that were sent to the API (all are now guaranteed to be typed objects)\n * @param batchErrors Array of batch errors containing failed entities\n * @param batchCacheManager The cache manager instance\n */\n static async cacheSuccessfulRecordsOnPartialFailure(\n entityType: ERPObjType,\n toProcess: IToRESTApiObject[],\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>,\n batchCacheManager: BatchCacheManager\n ): Promise<void> {\n // Create a set of failed record keys for efficient lookup\n const failedKeySet = this.createFailedRecordKeySet(entityType, batchErrors);\n\n // Filter to get only successful records\n const successfulRecords = this.filterSuccessfulRecords(\n entityType,\n toProcess,\n failedKeySet\n );\n\n logger.info(\n `Caching ${successfulRecords.length} successful records out of ${toProcess.length} total records`\n );\n\n // All records are now guaranteed to be typed objects, so we can cache them directly\n if (successfulRecords.length > 0) {\n await batchCacheManager.storeBatch(entityType, successfulRecords);\n }\n }\n\n /**\n * Extracts error count and batch errors from MM API response for partial failures (HTTP 207)\n * This supports all entities, including the slightly different format for labor tickets.\n * In case of labor tickets, the updateErrors and insertErrors arrays are combined into affectedEntities.\n * @param mmApiResponse The full MM API response object\n * @param entityType The type of entity being processed (determines response structure)\n * @returns Object containing errorCount and batchErrors\n * See MM207NonLaborTicketResponse and MM207LaborTicketResponse for response structure details\n */\n static extractErrorDetails(\n mmApiResponse: MM207NonLaborTicketResponse | MM207LaborTicketResponse,\n entityType: ERPObjType\n ): {\n errorCount: number;\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n } {\n // Type the data property with the expected structure for HTTP 207 responses\n const data = mmApiResponse.data as\n | {\n errors?: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }>;\n updateErrors?: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }>;\n insertErrors?: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }>;\n }\n | undefined;\n\n let allErrors: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }> = [];\n\n if (entityType === ERPObjType.LABOR_TICKETS) {\n // Labor tickets: combine updateErrors and insertErrors\n const updateErrors = data?.updateErrors || [];\n const insertErrors = data?.insertErrors || [];\n\n // Defensive validation with actionable warnings\n if (!data?.updateErrors && !data?.insertErrors) {\n logger.warn(\n \"Labor tickets partial success response missing both updateErrors and insertErrors arrays\"\n );\n }\n\n allErrors = [...updateErrors, ...insertErrors];\n } else {\n // Regular entities: use errors array\n const errors = data?.errors || [];\n\n // Defensive validation\n if (!data?.errors) {\n logger.warn(\n `${entityType} partial success response missing errors array`\n );\n }\n\n allErrors = errors;\n }\n\n const batchErrors = allErrors\n .filter(\n (error: {\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }) => {\n return error.batchData && error.batchData.length > 0;\n }\n )\n .map(\n (error: {\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }) => {\n // Reconstruct typed objects from plain data\n const typedErrorEntities = (error.batchData || []).map((entity) => {\n if (\n typeof entity === \"object\" &&\n entity !== null &&\n \"toRESTApiObject\" in entity &&\n typeof entity.toRESTApiObject === \"function\"\n ) {\n // It's already a typed object, return as-is\n return entity as IToRESTApiObject;\n } else {\n // It's plain data, reconstruct as typed object\n return EntityTransformer.reconstructFromPlainData(\n entityType,\n entity as Record<string, string | null>\n );\n }\n });\n\n return {\n message: error.message,\n affectedEntities: typedErrorEntities,\n };\n }\n );\n\n const errorCount = batchErrors.reduce((total: number, batchError) => {\n return total + batchError.affectedEntities.length;\n }, 0);\n\n return {\n errorCount,\n batchErrors,\n };\n }\n\n /**\n * Extracts error details from a 500 HTTP exception when it contains structured error data\n * @param exception The caught exception from MM API call\n * @param entityType The type of entity being processed\n * @returns Object containing errorCount and batchErrors, or null if not a structured 500 error\n * See MM500NonLaborTicketException and MM500LaborTicketException for exception structure details\n */\n static extractErrorDetailsFrom500Exception(\n exception: unknown,\n entityType: ERPObjType\n ): {\n errorCount: number;\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n } | null {\n try {\n // Cast exception to expected structure for easier access\n const ex = exception as Record<string, unknown>;\n const data = ex?.data as Record<string, unknown>;\n\n // Add diagnostic logging to understand the 500 error structure\n logger.info(\n \"writeEntitiesToMM: Analyzing 500 exception structure for diagnostic purposes\",\n {\n status: ex?.status,\n code: ex?.code,\n hasResponseData: !!data,\n responseDataKeys: data ? Object.keys(data) : [],\n errorMessage: data?.error,\n hasMessageObject: !!data?.message,\n messageObjectKeys: data?.message\n ? Object.keys(data.message as Record<string, unknown>)\n : [],\n entityType,\n exceptionType: typeof exception,\n exceptionKeys: ex ? Object.keys(ex) : [],\n }\n );\n\n // Check if this is a structured 500 error\n if (\n ex?.status !== 500 ||\n typeof data?.error !== \"string\" ||\n !data.error.startsWith(\"Failed to import\")\n ) {\n logger.info(\n \"writeEntitiesToMM: Not a structured 500 error - will re-throw exception as-is\",\n {\n status: ex?.status,\n errorMessage: data?.error,\n expectedStatus: 500,\n expectedMessagePrefix: \"Failed to import\",\n }\n );\n return null;\n }\n\n logger.info(\n \"writeEntitiesToMM: Detected structured 500 error - extracting error details\"\n );\n\n const messageObject = data?.message as Record<string, unknown>;\n if (!messageObject) {\n logger.warn(\n \"writeEntitiesToMM: Structured 500 error missing message object\"\n );\n return null;\n }\n\n let allErrors: unknown[] = [];\n\n if (entityType === ERPObjType.LABOR_TICKETS) {\n // Labor tickets: combine updateErrors and insertErrors\n const updateErrors = Array.isArray(messageObject?.updateErrors)\n ? messageObject.updateErrors\n : [];\n const insertErrors = Array.isArray(messageObject?.insertErrors)\n ? messageObject.insertErrors\n : [];\n\n logger.info(\"writeEntitiesToMM: Processing labor tickets 500 error\", {\n updateErrorsCount: updateErrors.length,\n insertErrorsCount: insertErrors.length,\n });\n\n if (updateErrors.length === 0 && insertErrors.length === 0) {\n logger.warn(\n \"writeEntitiesToMM: Labor tickets 500 error missing both updateErrors and insertErrors arrays\"\n );\n }\n\n allErrors = [...updateErrors, ...insertErrors];\n } else {\n // Regular entities: use errors array\n const errors = Array.isArray(messageObject?.errors)\n ? messageObject.errors\n : [];\n\n logger.info(\"writeEntitiesToMM: Processing regular entity 500 error\", {\n errorsCount: errors.length,\n });\n\n if (errors.length === 0) {\n logger.warn(\n `writeEntitiesToMM: ${entityType} 500 error missing errors array`\n );\n }\n\n allErrors = errors;\n }\n\n const batchErrors = allErrors\n .filter((error) => {\n const err = error as Record<string, unknown>;\n return Array.isArray(err?.batchData) && err.batchData.length > 0;\n })\n .map((error) => {\n const err = error as Record<string, unknown>;\n const batchData = err?.batchData as (\n | IToRESTApiObject\n | Record<string, string | null>\n )[];\n\n // Reconstruct typed objects from plain data\n const typedErrorEntities = (batchData || []).map((entity) => {\n if (\n typeof entity === \"object\" &&\n entity !== null &&\n \"toRESTApiObject\" in entity &&\n typeof entity.toRESTApiObject === \"function\"\n ) {\n // It's already a typed object, return as-is\n return entity as IToRESTApiObject;\n } else {\n // It's plain data, reconstruct as typed object\n return EntityTransformer.reconstructFromPlainData(\n entityType,\n entity as Record<string, string | null>\n );\n }\n });\n\n return {\n message:\n typeof err?.message === \"string\" ? err.message : \"Unknown error\",\n affectedEntities: typedErrorEntities,\n };\n });\n\n const errorCount = batchErrors.reduce((total: number, batchError) => {\n return total + batchError.affectedEntities.length;\n }, 0);\n\n logger.info(\"writeEntitiesToMM: Extracted 500 error details\", {\n batchErrorsCount: batchErrors.length,\n totalErrorCount: errorCount,\n entityType,\n });\n\n return {\n errorCount,\n batchErrors,\n };\n } catch (error) {\n // If we can't even parse the structure safely, log what we can and return null\n logger.error(\n \"writeEntitiesToMM: Failed to parse 500 exception structure safely\",\n {\n error: error instanceof Error ? error.message : String(error),\n exceptionType: typeof exception,\n entityType,\n }\n );\n return null;\n }\n }\n}\n","import { ERPObjType } from \"../../types/erp-types\";\nimport { BatchCacheManager } from \"../../services/caching-service/batch-cache-manager\";\nimport {\n IToRESTApiObject,\n MMSendPerson,\n MMSendResource,\n MMSendPart,\n MMSendPartOperation,\n MMSendWorkOrder,\n MMSendWorkOrderOperation,\n MMSendLaborTicket,\n MMSendReason,\n MMApiBaseResponse,\n MM200NonLaborTicketResponse,\n MM200LaborTicketResponse,\n MM207NonLaborTicketResponse,\n MM207LaborTicketResponse,\n} from \"../../services/mm-api-service\";\nimport { MMApiClient } from \"../../services/mm-api-service/mm-api-service\";\nimport { HTTPError } from \"../http-client\";\nimport {\n WriteEntitiesToMMResult,\n MMBatchValidationError,\n} from \"./standard-process-drivers\";\nimport { ErrorProcessor } from \"./error-processor\";\n\n/**\n * Handles processing of entities to the MachineMetrics API\n */\nexport class MMEntityProcessor {\n /**\n * Writes entities to MM API with deduplication and caching\n */\n static async writeEntities(\n entityType: ERPObjType,\n mmRecords: IToRESTApiObject[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<WriteEntitiesToMMResult> {\n const { toProcess, result } = await this._prepareAndDedupe(\n entityType,\n mmRecords,\n batchCacheManager\n );\n\n if (toProcess.length === 0) {\n result.message =\n \"All records were deduplicated locally - no records sent to MM API\";\n return result;\n }\n\n let mmApiResponse: MMApiBaseResponse;\n try {\n mmApiResponse = await this._sendToAPI(entityType, toProcess);\n } catch (exception) {\n this._handleException(exception, entityType, result);\n }\n\n return await this._handleResponse(\n mmApiResponse,\n entityType,\n toProcess,\n result,\n batchCacheManager\n );\n }\n\n // ============================================================================\n // PRIVATE HELPER METHODS\n // ============================================================================\n\n /**\n * Deduplicates records against cache\n * Returns the records to process and the result object\n * If the batchCacheManager is provided, the records are deduplicated against the cache\n * If the batchCacheManager is not provided, the records are not deduplicated\n */\n private static async _prepareAndDedupe(\n entityType: ERPObjType,\n mmRecords: IToRESTApiObject[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<{\n toProcess: IToRESTApiObject[];\n result: WriteEntitiesToMMResult;\n }> {\n let toProcess: IToRESTApiObject[] = [];\n const result: WriteEntitiesToMMResult = {\n message: \"\",\n upsertedEntities: 0,\n localDedupeCount: 0,\n apiDedupeCount: 0,\n };\n\n if (batchCacheManager) {\n const { nonDuplicates, duplicates } =\n (await batchCacheManager.dedupeBatch(entityType, mmRecords)) as {\n nonDuplicates: IToRESTApiObject[];\n duplicates: IToRESTApiObject[];\n };\n toProcess = nonDuplicates;\n result.localDedupeCount = duplicates.length;\n } else {\n toProcess = mmRecords;\n }\n\n return { toProcess, result };\n }\n\n /**\n * Sends records to MM API\n */\n private static async _sendToAPI(\n entityType: ERPObjType,\n toProcess: IToRESTApiObject[]\n ): Promise<MMApiBaseResponse> {\n const mmApiClient = new MMApiClient();\n\n switch (entityType) {\n case ERPObjType.PERSONS:\n return await mmApiClient.sendPersonsToMM(toProcess as MMSendPerson[]);\n case ERPObjType.RESOURCES:\n return await mmApiClient.sendResourcesToMM(\n toProcess as MMSendResource[]\n );\n case ERPObjType.PARTS:\n return await mmApiClient.sendPartsToMM(toProcess as MMSendPart[]);\n case ERPObjType.PART_OPERATION:\n return await mmApiClient.sendPartOperationsToMM(\n toProcess as MMSendPartOperation[]\n );\n case ERPObjType.WORK_ORDERS:\n return await mmApiClient.sendWorkOrdersToMM(\n toProcess as MMSendWorkOrder[]\n );\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return await mmApiClient.sendWorkOrderOperationsToMM(\n toProcess as MMSendWorkOrderOperation[]\n );\n case ERPObjType.LABOR_TICKETS:\n return await mmApiClient.sendLaborTicketsToMM(\n toProcess as MMSendLaborTicket[]\n );\n case ERPObjType.REASONS:\n return await mmApiClient.sendReasonsToMM(toProcess as MMSendReason[]);\n default:\n throw new Error(`Unknown entity type: ${entityType}`);\n }\n }\n\n /**\n * Handles exceptions from API calls, converting structured 500 errors to MMBatchValidationError\n * @throws MMBatchValidationError for structured 500 errors\n * @throws The original exception for all other errors\n */\n private static _handleException(\n exception: unknown,\n entityType: ERPObjType,\n result: WriteEntitiesToMMResult\n ): never {\n // Handle structured 500 errors that may be appropriate for partial failure\n const structuredCompleteErrorSet =\n ErrorProcessor.extractErrorDetailsFrom500Exception(exception, entityType);\n\n if (structuredCompleteErrorSet) {\n const ex = exception as Record<string, unknown>;\n const data = ex?.data as Record<string, unknown>;\n const errorMessage =\n typeof data?.error === \"string\"\n ? data.error\n : \"All entities failed to import\";\n\n throw new MMBatchValidationError({\n message: errorMessage,\n upsertedEntities: 0,\n localDedupeCount: result.localDedupeCount,\n apiDedupeCount: 0,\n errorCount: structuredCompleteErrorSet.errorCount,\n httpStatus: (exception as HTTPError).status,\n batchErrors: structuredCompleteErrorSet.batchErrors,\n });\n }\n\n // For all other exceptions, re-throw as-is\n throw exception;\n }\n\n /**\n * Handles the MM API response based on HTTP status code, caching successful records\n * and throwing exceptions for partial failures\n *\n * @returns The final result for success cases\n * @throws MMBatchValidationError for partial success cases\n * @throws Error for unknown status codes\n */\n private static async _handleResponse(\n mmApiResponse: MMApiBaseResponse,\n entityType: ERPObjType,\n toProcess: IToRESTApiObject[],\n result: WriteEntitiesToMMResult,\n batchCacheManager: BatchCacheManager | null\n ): Promise<WriteEntitiesToMMResult> {\n if (mmApiResponse.httpStatus === 200) {\n // Complete success - cache all records\n if (batchCacheManager) {\n await batchCacheManager.storeBatch(entityType, toProcess);\n }\n\n if (entityType === ERPObjType.LABOR_TICKETS) {\n const success = mmApiResponse as MM200LaborTicketResponse;\n result.message = success.message || \"Entities processed successfully\";\n result.upsertedEntities =\n (success.updated || 0) + (success.inserted || 0);\n } else {\n const success = mmApiResponse as MM200NonLaborTicketResponse;\n result.message = success.message || \"Entities processed successfully\";\n result.upsertedEntities = success.affectedRows || 0;\n }\n result.apiDedupeCount = toProcess.length - result.upsertedEntities;\n\n return result;\n } else if (mmApiResponse.httpStatus === 207) {\n // Partial success - cache only successful records before throwing exception\n const partialResponse = mmApiResponse as\n | MM207NonLaborTicketResponse\n | MM207LaborTicketResponse;\n const { errorCount, batchErrors } = ErrorProcessor.extractErrorDetails(\n partialResponse,\n entityType\n );\n\n if (batchCacheManager) {\n await ErrorProcessor.cacheSuccessfulRecordsOnPartialFailure(\n entityType,\n toProcess,\n batchErrors,\n batchCacheManager\n );\n }\n\n let upsertedEntities = 0;\n if (entityType === ERPObjType.LABOR_TICKETS) {\n const partial = partialResponse as MM207LaborTicketResponse;\n upsertedEntities =\n (partial.data.updated || 0) + (partial.data.inserted || 0);\n } else {\n const partial = partialResponse as MM207NonLaborTicketResponse;\n upsertedEntities = partial.data.affectedRows || 0;\n }\n\n throw new MMBatchValidationError({\n message:\n partialResponse.message || \"Entities processed with partial failures\",\n upsertedEntities: upsertedEntities,\n localDedupeCount: result.localDedupeCount,\n apiDedupeCount: toProcess.length - upsertedEntities - errorCount,\n errorCount: errorCount,\n httpStatus: mmApiResponse.httpStatus,\n batchErrors: batchErrors,\n });\n } else {\n throw new Error(\n `writeEntitiesToMM: Unknown HTTP status code: ${mmApiResponse.httpStatus}. An exception was expected.`\n );\n }\n }\n}\n","import { ERPObjType } from \"../../types/erp-types\";\nimport { IERPLaborTicketHandler } from \"../../types/erp-connector\";\nimport { BatchCacheManager } from \"../../services/caching-service/batch-cache-manager\";\nimport {\n IToRESTApiObject,\n MMSendWorkOrderBatch,\n MMSendPart,\n MMSendPartOperation,\n MMSendWorkOrder,\n MMSendWorkOrderOperation,\n} from \"../../services/mm-api-service\";\nimport { LaborTicketERPSynchronizer } from \"./labor-ticket-erp-synchronizer\";\nimport { MMEntityProcessor } from \"./mm-entity-processor\";\n\n/**\n * The result of writing entities to the MachineMetrics API on complete success.\n */\nexport interface WriteEntitiesToMMResult {\n message: string;\n upsertedEntities: number;\n localDedupeCount: number;\n apiDedupeCount: number;\n}\n\nexport class MMBatchValidationError extends Error {\n public readonly upsertedEntities: number;\n public readonly localDedupeCount: number;\n public readonly apiDedupeCount: number;\n public readonly errorCount: number;\n public readonly httpStatus: number;\n public readonly batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n\n constructor(options: {\n message: string;\n upsertedEntities: number;\n localDedupeCount: number;\n apiDedupeCount: number;\n errorCount: number;\n httpStatus: number;\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n }) {\n super(options.message);\n this.name = \"MMBatchValidationError\";\n this.upsertedEntities = options.upsertedEntities;\n this.localDedupeCount = options.localDedupeCount;\n this.apiDedupeCount = options.apiDedupeCount;\n this.errorCount = options.errorCount;\n this.httpStatus = options.httpStatus;\n this.batchErrors = options.batchErrors;\n }\n}\n\n/**\n * A collection of standardized process drivers that orchestrate various synchronization\n * and integration processes between systems. Use of these drivers is optional,\n * and useful as a reference implementation for how to implement the various processes.\n */\nexport class StandardProcessDrivers {\n /**\n * Synchronizes updated labor tickets from MachineMetrics to an ERP system:\n *\n * Initializes a checkpoint that tracks the last time labor tickets were synced,\n * fetches labor tickets from MM and converts them to the company timezone,\n * creates or updates the labor ticket in the ERP via connector-specific implementations,\n * updates the labor ticket id in MM on newly created labor tickets,\n * and saves the checkpoint\n */\n static async syncLaborTicketsToERP(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n return LaborTicketERPSynchronizer.syncToERP(connectorType, connector);\n }\n\n /**\n * Retries labor tickets that have failed to be created or updated in the ERP during the sync:\n *\n * Fetches failed labor tickets from MM, processes them, and updates the labor ticket id in MM.\n * If the labor ticket is successfully created or updated, it is added to the list of successful labor ticket ids,\n * which are then deleted from the list of failed labor ticket ids.\n */\n static async retryFailedLaborTickets(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n return LaborTicketERPSynchronizer.retryFailed(connectorType, connector);\n }\n\n /**\n * Writes a batch of records to MM for a specific entity type by:\n * 1. Deduplicating against cached records (localDedupeCount)\n * 2. Sending non-duplicate records to the MM API (apiDedupeCount)\n * 3. Storing successful records in cache\n *\n * An example usage pattern including comprehensive error handling:\n * ```\n * try {\n * const result = await StandardProcessDrivers.writeEntitiesToMM(\n * 'WorkOrders',\n * workOrderRecords,\n * batchCacheManager\n * );\n *\n * // HTTP 200 - Complete success\n * console.log(`✅ Success: ${result.message}`);\n * console.log(`📊 Metrics: ${result.upsertedEntities} upserted, ${result.localDedupeCount} locally deduplicated, ${result.apiDedupeCount} API deduplicated`);\n *\n * } catch (error) {\n * if (error instanceof MMBatchValidationError) {\n * // HTTP 207 - Partial success with some batches failing due to validation errors\n * // HTTP 500 - A specific type representing complete failure due to validation issues;\n * // other 500 types represent failure due to other issues and are not converted to MMBatchValidationError\n * // Note: Each batch error contains ALL entities from the failing batch, not necessarily just the failed ones\n *\n * console.log(`⚠️ Batch processing completed with errors (HTTP ${error.httpStatus})`);\n * console.log(`📊 Metrics: ${error.upsertedEntities} upserted, ${error.localDedupeCount} locally deduplicated, ${error.apiDedupeCount} API deduplicated`);\n * console.log(`❌ Error count: ${error.errorCount}`);\n *\n * // Process specific batch errors for retry or logging\n * error.batchErrors.forEach((batchError, index) => {\n * console.log(`Batch ${index + 1} error: ${batchError.message}`);\n * console.log(`All entities in failing batch:`, batchError.affectedEntities);\n *\n * // Example: Queue entire failing batch for retry (contains both successful and failed entities)\n * await queueForRetry(batchError.affectedEntities);\n * });\n *\n * // Decide whether to continue or halt based on httpStatus\n * if (error.httpStatus === 207) {\n * // Partial success - some batches processed successfully, others failed\n * console.log('⚠️ Continuing with partial success');\n * } else if (error.httpStatus === 500) {\n * // Complete failure - all batches failed due to validation issues\n * console.log('🛑 Complete failure - no records processed');\n * throw error; // Re-throw if complete failure should halt the process\n * }\n *\n * } else {\n * // Other underlying errors (network issues, authentication, etc.)\n * console.error('🚨 Unexpected error during MM API call:', error);\n *\n * // Example: Check for specific error types\n * if (error.message?.includes('authentication')) {\n * console.error('🔐 Authentication issue - check MM API credentials');\n * } else if (error.message?.includes('network') || error.code === 'ECONNREFUSED') {\n * console.error('🌐 Network connectivity issue - check MM API endpoint');\n * }\n *\n * throw error; // Re-throw for upstream handling\n * }\n * }\n * ```\n *\n * @param entityType The type of entity being processed\n * @param mmRecords The records to process\n * @param batchCacheManager The batch cache manager instance; pass in null if caching is not desired\n *\n * @returns WriteEntitiesToMMResult on complete success (HTTP 200)\n * @throws MMBatchValidationError on partial success (HTTP 207) or complete failure (HTTP 500) with structured error details\n * @throws Error on other underlying issues (network, authentication, etc.)\n */\n static async writeEntitiesToMM(\n entityType: ERPObjType,\n mmRecords: IToRESTApiObject[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<WriteEntitiesToMMResult> {\n return MMEntityProcessor.writeEntities(\n entityType,\n mmRecords,\n batchCacheManager\n );\n }\n\n /**\n * Writes a batch of Work Order related entities to MM maintaining referential integrity by:\n * 1. Processing parts first (base entities)\n * 2. Processing part operations (depends on parts)\n * 3. Processing work orders (depends on parts)\n * 4. Processing work order operations (depends on work orders and part operations)\n *\n * This ensures all foreign key constraints are satisfied and prevents referential integrity errors.\n *\n * @param workOrderBatch The batch containing all related entities\n * @param batchCacheManager The batch cache manager instance; pass in null if caching is not desired\n *\n * @returns Combined results from all entity processing\n * @throws MMBatchValidationError on partial success or complete failure with structured error details\n * @throws Error on other underlying issues (network, authentication, etc.)\n */\n static async writeWorkOrderBatchToMM(\n workOrderBatch: MMSendWorkOrderBatch,\n batchCacheManager: BatchCacheManager | null\n ): Promise<{\n parts: WriteEntitiesToMMResult;\n partOperations: WriteEntitiesToMMResult;\n workOrders: WriteEntitiesToMMResult;\n workOrderOperations: WriteEntitiesToMMResult;\n }> {\n // Process entities in order to maintain referential integrity\n\n // 1. Parts first (no dependencies)\n const partsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PARTS,\n workOrderBatch.parts,\n batchCacheManager\n );\n\n // 2. Part Operations (depends on parts)\n const partOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PART_OPERATION,\n workOrderBatch.partOperations,\n batchCacheManager\n );\n\n // 3. Work Orders (depends on parts)\n const workOrdersResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDERS,\n workOrderBatch.workOrders,\n batchCacheManager\n );\n\n // 4. Work Order Operations (depends on work orders and part operations)\n const workOrderOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDER_OPERATIONS,\n workOrderBatch.workOrderOperations,\n batchCacheManager\n );\n\n return {\n parts: partsResult,\n partOperations: partOperationsResult,\n workOrders: workOrdersResult,\n workOrderOperations: workOrderOperationsResult,\n };\n }\n\n /**\n * Processes flattened work order data and syncs it to MM maintaining referential integrity.\n *\n * This method expects flattened data where each row contains both work order and operation information\n * with camelCase field names matching the GraphQL schema. It will automatically extract and deduplicate\n * parts, part operations, work orders, and work order operations, then process them in the correct order\n * to maintain referential integrity.\n *\n * @param flattenedData Array of flattened rows containing both work order and operation data (camelCase fields)\n * @param batchCacheManager The batch cache manager instance; pass in null if caching is not desired\n *\n * @returns Combined results from all entity processing with detailed logging information\n * @throws MMBatchValidationError on partial success or complete failure with structured error details\n * @throws Error on other underlying issues (network, authentication, etc.)\n */\n static async syncWorkOrderBatchFromFlattened(\n flattenedData: any[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<{\n parts: WriteEntitiesToMMResult;\n partOperations: WriteEntitiesToMMResult;\n workOrders: WriteEntitiesToMMResult;\n workOrderOperations: WriteEntitiesToMMResult;\n }> {\n if (!flattenedData || flattenedData.length === 0) {\n throw new Error(\"No flattened work order data provided\");\n }\n\n // Process the flattened data - each row contains both work order and operation info\n const uniqueParts = new Map();\n const uniquePartOperations = new Map();\n const uniqueWorkOrders = new Map();\n const workOrderOperations = [];\n\n // Process each flattened row to extract entities\n for (const row of flattenedData) {\n // Extract unique parts\n const partKey = `${row.partNumber}|${row.partRevision}|${row.method}`;\n if (!uniqueParts.has(partKey)) {\n uniqueParts.set(partKey, {\n partNumber: row.partNumber, // → partNumber\n partRevision: row.partRevision, // → partRevision\n method: row.method, // → method\n });\n }\n\n // Extract unique part operations\n const partOpKey = `${row.partNumber}|${row.partRevision}|${row.method}|${row.sequenceNumber}`;\n if (!uniquePartOperations.has(partOpKey)) {\n uniquePartOperations.set(partOpKey, {\n partNumber: row.partNumber, // → partNumber\n partRevision: row.partRevision, // → partRevision\n method: row.method, // → method\n sequenceNumber: row.sequenceNumber, // → sequenceNumber\n resourceId: row.resourceId, // → resourceId\n cycleTimeMs: row.cycleTimeMs, // → cycleTimeMs\n setupTimeMs: row.setupTimeMs, // → setupTimeMs\n description: row.operationDescription || \"\", // → description\n quantityPerPart: row.quantityPerPart || 1, // → quantityPerPart\n });\n }\n\n // Extract unique work orders\n const workOrderKey = row.workOrderId;\n if (!uniqueWorkOrders.has(workOrderKey)) {\n uniqueWorkOrders.set(workOrderKey, {\n workOrderId: row.workOrderId, // → workOrderId\n lot: row.lot, // → lot\n split: row.split, // → split\n sub: row.sub, // → sub\n status: row.status, // → status\n dueDate: row.dueDate, // → dueDate\n description: row.description, // → description\n scheduledStartDate: row.scheduledStartDate, // → scheduledStartDate\n scheduledEndDate: row.scheduledEndDate, // → scheduledEndDate\n closedDate: row.closedDate, // → closedDate\n quantityRequired: row.quantityRequired, // → quantityRequired\n partNumber: row.partNumber, // → partNumber\n partRevision: row.partRevision, // → partRevision\n method: row.method, // → method\n });\n }\n\n // Add each work order operation (these are not deduplicated since each operation is unique)\n workOrderOperations.push({\n workOrderId: row.workOrderId, // → workOrderId\n lot: row.lot, // → lot\n split: row.split, // → split\n sub: row.sub, // → sub\n sequenceNumber: row.sequenceNumber, // → sequenceNumber\n resourceId: row.resourceId, // → resourceId\n startQuantity: row.startQuantity, // → startQuantity\n finishQuantity: row.finishQuantity, // → finishQuantity\n expectedRejectRate: row.expectedRejectRate, // → expectedRejectRate\n scheduledStartDate: row.opScheduledStartDate, // → scheduledStartDate\n scheduledFinishDate: row.opScheduledFinishDate, // → scheduledFinishDate\n closedDate: row.opClosedDate, // → closedDate\n cycleTimeMs: row.cycleTimeMs, // → cycleTimeMs\n setupTimeMs: row.setupTimeMs, // → setupTimeMs\n productionburdenRateHourly: row.productionburdenRateHourly, // → productionburdenRateHourly\n setupburdenRatehourly: row.setupburdenRatehourly, // → setupburdenRatehourly\n operationType: row.operationType, // → operationType\n quantityPerPart: row.quantityPerPart, // → quantityPerPart\n status: row.opStatus || row.status, // → status\n });\n }\n\n // Transform all the data directly to MM entities\n const parts = Array.from(uniqueParts.values()).map(\n (item) =>\n new MMSendPart(\n item.partNumber || \"\", // partNumber\n item.partRevision || \"\", // partRevision\n item.method || \"Standard\" // method\n )\n );\n\n const partOperations = Array.from(uniquePartOperations.values()).map(\n (item) =>\n new MMSendPartOperation(\n item.partNumber || \"\", // partNumber\n item.partRevision || \"\", // partRevision\n item.method || \"Standard\", // method\n item.sequenceNumber?.toString() || \"\", // sequenceNumber\n item.resourceId?.toString() || \"\", // resourceId\n item.cycleTimeMs || 0, // cycleTimeMs\n item.setupTimeMs || 0, // setupTimeMs\n item.description || \"\", // description\n item.quantityPerPart || 1 // quantityPerPart\n )\n );\n\n const transformedWorkOrders = Array.from(uniqueWorkOrders.values()).map(\n (item) =>\n new MMSendWorkOrder(\n item.workOrderId?.toString() || \"\", // workOrderId\n item.lot || \"\", // lot\n item.split || \"\", // split\n item.sub || \"\", // sub\n item.status || \"Open\", // status\n item.dueDate ? new Date(item.dueDate).toISOString() : null, // dueDate\n item.description || \"\", // description\n item.scheduledStartDate\n ? new Date(item.scheduledStartDate).toISOString()\n : null, // scheduledStartDate\n item.scheduledEndDate\n ? new Date(item.scheduledEndDate).toISOString()\n : null, // scheduledEndDate\n item.closedDate ? new Date(item.closedDate).toISOString() : null, // closedDate\n item.quantityRequired || 0, // quantityRequired\n item.partNumber || \"\", // partNumber\n item.partRevision || \"\", // partRevision\n item.method || \"Standard\" // method\n )\n );\n\n const transformedWorkOrderOperations = workOrderOperations.map(\n (item) =>\n new MMSendWorkOrderOperation(\n item.workOrderId?.toString() || \"\", // workOrderId\n item.lot || \"\", // lot\n item.split || \"\", // split\n item.sub || \"\", // sub\n item.sequenceNumber?.toString() || \"\", // sequenceNumber\n item.resourceId?.toString() || \"\", // resourceId\n item.startQuantity || 0, // startQuantity\n item.finishQuantity || 0, // finishQuantity\n item.expectedRejectRate || 0, // expectedRejectRate\n item.scheduledStartDate\n ? new Date(item.scheduledStartDate).toISOString()\n : null, // scheduledStartDate\n item.scheduledFinishDate\n ? new Date(item.scheduledFinishDate).toISOString()\n : null, // scheduledFinishDate\n item.closedDate ? new Date(item.closedDate).toISOString() : null, // closedDate\n item.cycleTimeMs || 0, // cycleTimeMs\n item.setupTimeMs || 0, // setupTimeMs\n parseFloat(item.productionburdenRateHourly || \"0\"), // productionburdenRateHourly\n parseFloat(item.setupburdenRatehourly || \"0\"), // setupburdenRatehourly\n item.operationType || \"Production\", // operationType\n item.quantityPerPart || 1, // quantityPerPart\n item.status || \"Open\" // status\n )\n );\n\n // Send each entity type individually in the correct order for referential integrity\n const partsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PARTS,\n parts,\n batchCacheManager\n );\n\n const partOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PART_OPERATION,\n partOperations,\n batchCacheManager\n );\n\n const workOrdersResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDERS,\n transformedWorkOrders,\n batchCacheManager\n );\n\n const workOrderOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDER_OPERATIONS,\n transformedWorkOrderOperations,\n batchCacheManager\n );\n\n return {\n parts: partsResult,\n partOperations: partOperationsResult,\n workOrders: workOrdersResult,\n workOrderOperations: workOrderOperationsResult,\n };\n }\n}\n","import { HTTPClient, HTTPClientFactory } from \"../../../utils/http-client\";\nimport {\n QueryParams,\n buildLogicalCondition,\n Pagination,\n} from \"./get-query-params\";\nimport { ErrorHandler } from \"../errors\";\nimport { APIResponse, ERPApiConfig } from \"../types\";\n\nconst DEFAULT_RETRY_ATTEMPTS = 0; // One try only, no retries\nconst DEFAULT_PAGINATION_LIMIT = 100; // Reasonable default for most APIs\n\n/**\n * RestAPIService is a utility class for making REST API requests to an ERP system.\n * Provides methods for GET, PATCH, and POST requests, with built-in pagination support.\n *\n * @param config - Configuration for the ERP API connection\n * @param retryAttempts - Optional number of retry attempts for failed requests. Defaults to 0 (no retries)\n * @param paginationLimit - Optional default limit for paginated requests. Defaults to 100 items per page\n *\n * NOTE: assumes bearertoken-based authentication.\n */\nexport class RestAPIService {\n private client: HTTPClient;\n private config: ERPApiConfig;\n private paginationLimit: number;\n\n constructor(\n config: ERPApiConfig,\n retryAttempts?: number,\n paginationLimit?: number\n ) {\n this.config = config;\n this.paginationLimit = paginationLimit ?? DEFAULT_PAGINATION_LIMIT;\n this.client = HTTPClientFactory.getInstance({\n baseUrl: config.apiUrl,\n retryAttempts: retryAttempts ?? DEFAULT_RETRY_ATTEMPTS,\n });\n }\n\n //#region Get Request Helpers\n private async getHeaders(): Promise<Record<string, string>> {\n const token = await this.config.getAuthToken();\n return {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n };\n }\n\n private buildQueryParams(query: QueryParams = {}): URLSearchParams {\n const { limit, nextPage, filter } = query;\n const queryParams: Record<string, string> = {};\n\n if (limit) {\n queryParams.take = limit.toString();\n }\n if (nextPage) {\n queryParams.skip = nextPage;\n }\n\n if (filter) {\n if (filter.in) {\n filter.in.forEach((filterIn) => {\n const query = filterIn.value.join(\"|\");\n queryParams[`${filterIn.field}[in]`] = query;\n });\n }\n\n if (filter.and) {\n queryParams[\"filter[and]\"] = buildLogicalCondition(filter.and, \"and\");\n }\n\n if (filter.or) {\n queryParams[\"filter[or]\"] = buildLogicalCondition(filter.or, \"or\");\n }\n }\n\n return new URLSearchParams(queryParams);\n }\n\n /**\n * Builds pagination metadata for API responses\n * @param limit - Optional limit for this specific request. If not provided, uses the service's default pagination limit\n * @param nextPage - Optional next page token\n * @param previousPage - Optional previous page token\n * @param dataLength - Number of items in the current response\n * @param totalCount - Total number of items available\n * @returns Pagination metadata including count, limit, and page tokens\n */\n private buildPagination(\n limit?: number,\n nextPage?: string,\n previousPage?: string,\n dataLength: number = 0,\n totalCount?: number\n ): Pagination {\n const paging: Pagination = {\n count: totalCount || 0,\n limit: limit || this.paginationLimit,\n nextPage: undefined,\n previousPage,\n };\n\n if (limit) {\n paging.limit = limit;\n const currentSkip = parseInt(nextPage || \"0\");\n\n // Only set nextPage if:\n // 1. We have data in this response\n // 2. We haven't reached the end (current skip + limit < total count)\n if (dataLength > 0 && (!totalCount || currentSkip + limit < totalCount)) {\n paging.nextPage = `${currentSkip + limit}`;\n }\n }\n\n return paging;\n }\n\n private buildUrl(endpoint: string, queryParams?: URLSearchParams): string {\n const pathParts = [this.config.apiUrl || \"\"];\n pathParts.push(endpoint);\n\n const path = pathParts.filter(Boolean).join(\"/\");\n return queryParams ? `${path}?${queryParams.toString()}` : path;\n }\n //#endregion get request helpers\n\n async get<T>(\n endpoint: string,\n query: QueryParams = {}\n ): Promise<APIResponse<T[]>> {\n try {\n const params = this.buildQueryParams(query);\n const url = this.buildUrl(endpoint, params);\n const headers = await this.getHeaders();\n\n const response = await this.client.request<{\n Data: T[];\n TotalCount: number;\n }>({\n method: \"GET\",\n url,\n headers,\n });\n\n const paging = this.buildPagination(\n query.limit,\n query.nextPage,\n query.previousPage,\n response.data.Data?.length,\n response.data.TotalCount\n );\n\n return {\n data: response.data.Data || [],\n metadata: {\n pagination: paging,\n },\n };\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n async patch<T>(\n endpoint: string,\n id: string,\n data: Partial<T>\n ): Promise<APIResponse<T>> {\n try {\n const url = this.buildUrl(`${endpoint}/${id}`);\n const headers = await this.getHeaders();\n\n const response = await this.client.request<T>({\n method: \"PATCH\",\n url,\n data,\n headers,\n });\n\n return {\n data: response.data,\n };\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n async post<TRequest, TResponse = TRequest>(\n endpoint: string,\n data: TRequest\n ): Promise<APIResponse<TResponse>> {\n try {\n const url = this.buildUrl(endpoint);\n const headers = await this.getHeaders();\n\n const response = await this.client.request<TResponse>({\n method: \"POST\",\n url,\n data,\n headers,\n });\n\n return {\n data: response.data,\n };\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Cleanup all HTTP connections and resources\n * Call this when the service is no longer needed\n */\n async destroy(): Promise<void> {\n await this.client.destroy();\n }\n}\n","import { ERPObjType } from \"../types/erp-types\";\n\nexport function getERPAPITypeFromEntity(\n entity: string,\n entityMap: Record<ERPObjType, { entity: string }>\n): ERPObjType | undefined {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const entry = Object.entries(entityMap).find(\n ([_, value]) => value.entity === entity\n );\n return entry ? (Number(entry[0]) as ERPObjType) : undefined;\n}\n","/** A utility to get the type of an error, particularly\n * useful for the purpose of error deduplication\n * \n * Note: prefers 'code' over 'name' in the expectation that this better\n * represents the error type\n */\nconst isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;\n\nexport function getErrorType(error: unknown): string {\n if (error && typeof error === 'object') {\n const o = error as { code?: unknown; name?: unknown; constructor?: { name?: unknown } };\n if (isNonEmptyString(o.code)) return o.code;\n if (isNonEmptyString(o.name)) return o.name;\n const ctorName = o.constructor?.name;\n if (isNonEmptyString(ctorName) && ctorName !== 'Object') return ctorName;\n }\n return 'Error';\n}\n","/**\n * Error formatter utility for standardizing error messages across connectors\n * Extracts human-readable messages from various error types (axios, custom errors, etc.)\n */\n\nexport interface FormattedError {\n message: string;\n code: string;\n httpStatus?: number;\n metadata?: Record<string, any>;\n}\n\n/**\n * Extract meaningful error message from various error types\n */\nexport function formatError(error: any): FormattedError {\n if (!error) {\n return {\n message: 'Unknown error occurred',\n code: 'UNKNOWN_ERROR',\n };\n }\n\n // Handle axios errors FIRST (before checking for FormattedError)\n // because axios errors also have message and code properties\n if (error.isAxiosError || error.name === 'AxiosError') {\n return formatAxiosError(error);\n }\n\n // If it's already a FormattedError, return as-is\n // Check for httpStatus or metadata to distinguish from axios errors\n if (error.message && error.code && typeof error.message === 'string' && typeof error.code === 'string' && !error.config) {\n return error as FormattedError;\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n return {\n message: error.message || 'An error occurred',\n code: 'ERROR',\n metadata: {\n name: error.name,\n },\n };\n }\n\n // Handle string errors\n if (typeof error === 'string') {\n return {\n message: error,\n code: 'ERROR',\n };\n }\n\n // Last resort: try to extract any useful info\n const message = error.message || error.toString?.() || 'Unknown error occurred';\n return {\n message: typeof message === 'string' ? message : JSON.stringify(message),\n code: error.code || 'UNKNOWN_ERROR',\n };\n}\n\n/**\n * Format axios-specific errors with detailed context\n */\nfunction formatAxiosError(error: any): FormattedError {\n const httpStatus = error.response?.status;\n const method = error.config?.method?.toUpperCase();\n const url = error.config?.url;\n \n // First check if connector already extracted the message\n let message = error._extractedMessage;\n \n // If not, try to extract from response data\n if (!message) {\n const extractedMessage = extractErrorMessage(error.response?.data);\n message = extractedMessage || error.response?.statusText || error.message || 'Request failed';\n }\n\n // Determine error code based on status\n const code = categorizeHttpError(httpStatus);\n\n const metadata: Record<string, any> = {\n method,\n url,\n };\n\n // Add additional context for specific error types\n if (httpStatus === 401 || httpStatus === 403) {\n metadata.hint = 'Check authentication credentials';\n } else if (httpStatus === 404) {\n metadata.hint = 'Resource not found - check endpoint URL';\n } else if (httpStatus && httpStatus >= 500) {\n metadata.hint = 'ERP system may be temporarily unavailable';\n }\n\n return {\n message,\n code,\n httpStatus,\n metadata,\n };\n}\n\n/**\n * Extract meaningful error message from response data\n * Handles various ERP-specific response formats\n */\nfunction extractErrorMessage(data: any): string | null {\n if (!data) return null;\n\n // Common error message fields across different ERPs\n const possibleFields = [\n 'ErrorMessage', // Epicor\n 'error.message', // Common REST format\n 'error', // Simple format\n 'Message', // .NET style\n 'message', // JavaScript style\n 'errorMessage', // Camel case\n 'error_message', // Snake case\n 'errors[0].message', // Array of errors\n 'title', // Problem details format\n 'detail', // Problem details format\n ];\n\n for (const field of possibleFields) {\n const value = getNestedValue(data, field);\n if (value && typeof value === 'string') {\n return value;\n }\n }\n\n // If data is a string, return it\n if (typeof data === 'string') {\n // Try to parse as JSON first\n try {\n const parsed = JSON.parse(data);\n return extractErrorMessage(parsed);\n } catch {\n return data;\n }\n }\n\n return null;\n}\n\n/**\n * Get nested value from object using dot notation\n */\nfunction getNestedValue(obj: any, path: string): any {\n const parts = path.split('.');\n let current = obj;\n\n for (const part of parts) {\n // Handle array notation like 'errors[0]'\n const arrayMatch = part.match(/(\\w+)\\[(\\d+)\\]/);\n if (arrayMatch) {\n const [, key, index] = arrayMatch;\n current = current?.[key]?.[parseInt(index, 10)];\n } else {\n current = current?.[part];\n }\n\n if (current === undefined || current === null) {\n return null;\n }\n }\n\n return current;\n}\n\n/**\n * Categorize HTTP errors into standard codes\n */\nfunction categorizeHttpError(status?: number): string {\n if (!status) return 'NETWORK_ERROR';\n\n if (status === 400) return 'VALIDATION_ERROR';\n if (status === 401) return 'AUTHENTICATION_ERROR';\n if (status === 403) return 'AUTHORIZATION_ERROR';\n if (status === 404) return 'NOT_FOUND';\n if (status === 409) return 'CONFLICT';\n if (status === 422) return 'VALIDATION_ERROR';\n if (status === 429) return 'RATE_LIMIT';\n if (status >= 500) return 'ERP_SERVER_ERROR';\n if (status >= 400) return 'CLIENT_ERROR';\n\n return 'HTTP_ERROR';\n}\n\n/**\n * Format error for logging (shorter, cleaner version)\n */\nexport function formatErrorForLogging(error: any): string {\n const formatted = formatError(error);\n \n let message = `[${formatted.code}] ${formatted.message}`;\n \n if (formatted.httpStatus) {\n message = `[${formatted.httpStatus}] ${message}`;\n }\n \n return message;\n}\n\n","import { MMApiClient } from '../../services/mm-api-service/mm-api-service';\nimport logger from '../../services/reporting-service/logger';\nimport type { LogDeduper } from './log-deduper';\n\nexport type LogLevelString = 'info' | 'warn' | 'error';\n\n/**\n * dedupeKey - key used to deduplicate log entries when a LogDeduper is injected into the MMConnectorLogger\n * \n * A recommended practice is to use a meaningful unique identifier for this log entry type.\n * Examples: SyncFromERPCompleted\n * SyncFromERPError:HTTPError\n */\nexport class LogEntry {\n level: LogLevelString;\n message: string;\n dedupeKey: string;\n eventTime: number;\n\n constructor(params: { level: LogLevelString; message: string; dedupeKey: string}) {\n this.level = params.level;\n this.message = params.message;\n this.dedupeKey = params.dedupeKey;\n this.eventTime = Date.now();\n }\n}\n\n/**\n * The response from the MM connector logging API\n */\nexport interface LogResponse {\n message: string;\n processed?: number;\n}\n\n/**\n * Helper function to check if a value is a LogResponse\n */\nfunction isLogResponse(value: unknown): value is LogResponse {\n if (value === null || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.message !== 'string') return false;\n if ('processed' in v && typeof v.processed !== 'number') return false;\n return true;\n}\n\n/**\n * MM Connector Logger for sending progress and error updates to the MM cloud\n * \n * This class provides helper methods to send individual logs to the\n * MM connector logging API, providing support for validation, retries, and error handling\n * according to the API specification. \n * \n * By injecting a LogDeduper, the log entries are deduplicated across jobs over a specified time window\n */\nexport class MMConnectorLogger {\n private readonly MAX_MSG_LEN = 2000;\n \n private mmApiClient: MMApiClient;\n private readonly deduper?: LogDeduper;\n private readonly source: string;\n\n constructor(source: string, deduper?: LogDeduper) {\n if (source.length < 1 || source.length > 64) {\n throw new Error('source must be 1-64 characters');\n }\n\n this.mmApiClient = new MMApiClient();\n this.deduper = deduper;\n this.source = source;\n }\n\n // Deduplication helpers are delegated to injected FileLogDeduper\n\n /**\n * Send a single log entry to the MM cloud with deduplication.\n * \n * The deduplication is handled by the injected LogDeduper.\n * If no deduper is injected, the log entry is sent without deduplication.\n * \n * The standard deduper, FileLogDeduper, stores the deduplication state in a file, \n * allowing deduplication across jobs, \n * \n * @param logEntry - The log entry to send\n * @returns Promise resolving to the API response or null if suppressed\n * @throws HTTPError if the request fails or Error if the log entry is invalid\n */\n async sendLog(logEntry: LogEntry): Promise<LogResponse | null> {\n this.validateLogEntry(logEntry);\n \n const now = Date.now();\n let messageToSend = logEntry.message;\n if (this.deduper) {\n const decision = await this.deduper.decide(logEntry, now);\n if (decision === null) return null; // suppressed\n messageToSend = decision;\n }\n\n try {\n const logEntryToSend = {\n source: this.source,\n level: logEntry.level,\n message: messageToSend\n };\n\n const response = await this.mmApiClient.sendConnectorLog(logEntryToSend);\n if (this.deduper) {\n await this.deduper.onSuccess(logEntry, now);\n }\n\n if (!isLogResponse(response)) {\n logger.warn('Unexpected success response format from MM API for connector log', { response });\n return { message: 'Unexpected success response format when sending log' };\n }\n return { message: response.message };\n\n } catch (error) {\n logger.error('Failed to send log to MM cloud', { \n level: logEntry.level,\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n throw error;\n }\n }\n\n /**\n * @throws Error if validation fails\n */\n private validateLogEntry(logEntry: LogEntry): void {\n const allowedLevels: ReadonlyArray<LogLevelString> = ['info', 'warn', 'error'];\n if (!logEntry.level || !allowedLevels.includes(logEntry.level)) {\n throw new Error(`level must be one of: ${allowedLevels.join(', ')}`);\n }\n\n if (!logEntry.message || typeof logEntry.message !== 'string') {\n throw new Error('message is required and must be a string');\n }\n\n // Truncate message to MAX_MSG_LEN characters\n logEntry.message = logEntry.message.slice(0, this.MAX_MSG_LEN);\n\n if (!logEntry.dedupeKey || typeof logEntry.dedupeKey !== 'string') {\n throw new Error('dedupeKey is required and must be a string');\n }\n\n if (logEntry.dedupeKey.trim().length < 1) {\n throw new Error('dedupeKey must be a non-empty string');\n }\n }\n\n /**\n * Retry all failed transmissions silently\n * This method attempts to retry all messages that failed to transmit\n * and removes them from the failed list if successful, else leaves them for the client to retry\n * \n * Expected usage is by a client to call this as part of its own retry mechanism\n */\n async retryFailedTransmissions(): Promise<void> {\n if (!this.deduper || !this.deduper.retryFailedTransmissions) {\n return;\n }\n await this.deduper.retryFailedTransmissions(async (entry, message) => {\n await this.mmApiClient.sendConnectorLog({\n source: this.source,\n level: entry.level,\n message\n });\n });\n }\n\n /**\n * Clean up resources\n */\n async destroy(): Promise<void> {\n await this.mmApiClient.destroy();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport logger from '../../services/reporting-service/logger';\nimport type { LogEntry, LogLevelString } from './mm-connector-logger';\n\n/**\n * Implement a custom LogDeduper or use the provided FileLogDeduper\n */\nexport interface LogDeduper {\n decide(entry: LogEntry, now: number): Promise<string | null>;\n onSuccess(entry: LogEntry, now: number): Promise<void>;\n retryFailedTransmissions?(send: (entry: LogEntry, message: string) => Promise<void>): Promise<void>;\n}\n\ninterface LogDedupeEntry {\n lastTransmitted: number;\n suppressedCount: number;\n firstUnsentEventTs: number; // Earliest unsent occurrence time for the current unsent batch (if any)\n lastEventTs: number; // Most recent occurrence time (allowed or suppressed)\n level: LogLevelString;\n message: string;\n}\n\n/**\n * LogDedupeStore is the store for of the most recent log entries for each dedupeKey\n */\ninterface LogDedupeStore {\n [key: string]: LogDedupeEntry;\n}\n\n/**\n * FileLogDeduper implements the LogDeduper interface to deduplicate\n * logs by storing them in a file; this allows deduping across jobs.\n */\nexport class FileLogDeduper implements LogDeduper {\n private readonly storeFilePath: string;\n private readonly windowMs: number;\n private readonly ttlMs: number;\n private readonly sweepIntervalMs: number;\n private lastSweepTsMs: number;\n\n private readonly DEFAULT_WINDOW_TEN_MINS = 600;\n private readonly DEFAULT_TTL_ONE_HOUR = 3600;\n private readonly DEFAULT_SWEEP_INTERVAL_FIVE_MINS = 300;\n private readonly DEFAULT_STORE_FILE_PATH = path.join('/tmp', 'log-deduplication.json');\n\n /**\n * Ctor.\n * @param storeFilePath: The path to the file where the deduplication store is stored; recommended is to use the default\n * @param windowSeconds: Suppression window. Duplicates within this period are suppressed.\n * @param ttlSeconds: Eviction TTL. Store entries for keys inactive beyond this are removed. Enforced to be ≥ windowSeconds.\n * @param sweepIntervalSeconds: Efficiency parameter. How often (min interval) to run opportunistic eviction; retry always sweeps\n * The sweep is lazy, used only when the store is accessed\n */\n constructor({\n storeFilePath = this.DEFAULT_STORE_FILE_PATH,\n windowSeconds = this.DEFAULT_WINDOW_TEN_MINS,\n ttlSeconds = this.DEFAULT_TTL_ONE_HOUR,\n sweepIntervalSeconds = this.DEFAULT_SWEEP_INTERVAL_FIVE_MINS\n } : {\n storeFilePath?: string;\n windowSeconds?: number;\n ttlSeconds?: number;\n sweepIntervalSeconds?: number;\n } = {}) {\n\n this.storeFilePath = storeFilePath;\n this.windowMs = Math.max(1, windowSeconds) * 1000;\n this.ttlMs = Math.max(this.windowMs, Math.max(1, ttlSeconds) * 1000);\n this.sweepIntervalMs = Math.max(1, sweepIntervalSeconds) * 1000;\n this.lastSweepTsMs = 0;\n\n this.ensureStoreFileExists();\n }\n\n /** \n * Deduplication gating function\n * Returns the formatted message to send, or null to suppress\n * Decision is based on the dedupeKey and the time of the entry\n */\n async decide(entry: LogEntry, now: number): Promise<string | null> {\n if (!entry.dedupeKey || typeof entry.dedupeKey !== 'string' || entry.dedupeKey.trim().length === 0) {\n throw new Error('dedupeKey is required and must be a non-empty string');\n }\n const key = entry.dedupeKey;\n\n return this.withLock(async () => {\n const store = this.readStore();\n\n // Opportunistic eviction gated by sweep interval\n if (now - this.lastSweepTsMs >= this.sweepIntervalMs) {\n this.evictExpiredInStore(store, now);\n this.lastSweepTsMs = now;\n this.writeStore(store);\n }\n const existing = store[key];\n\n if (existing) {\n const withinWindow = existing.lastTransmitted > 0 && (existing.lastTransmitted + this.windowMs) > now;\n if (withinWindow) {\n // Suppress and increment count\n store[key] = {\n ...existing,\n suppressedCount: existing.suppressedCount + 1,\n firstUnsentEventTs: existing.suppressedCount === 0 ? (entry.eventTime ?? now) : existing.firstUnsentEventTs,\n lastEventTs: entry.eventTime ?? now,\n level: entry.level,\n message: entry.message\n };\n this.writeStore(store);\n return null;\n }\n\n // Outside window: allow, include timestamp and prior suppression count\n const messageToSend = this.formatMessage(entry.message, entry.eventTime ?? now, existing.suppressedCount, existing.firstUnsentEventTs);\n store[key] = {\n ...existing,\n suppressedCount: 0,\n firstUnsentEventTs: 0,\n lastEventTs: entry.eventTime ?? now,\n level: entry.level,\n message: entry.message\n };\n this.writeStore(store);\n return messageToSend;\n }\n\n // New entry: allow, timestamp, no suppression count\n const messageToSend = this.formatMessage(entry.message, entry.eventTime ?? now, 0);\n store[key] = {\n lastTransmitted: 0,\n suppressedCount: 0,\n firstUnsentEventTs: entry.eventTime ?? now,\n lastEventTs: entry.eventTime ?? now,\n level: entry.level,\n message: entry.message\n };\n this.writeStore(store);\n return messageToSend;\n });\n }\n\n async onSuccess(entry: LogEntry, now: number): Promise<void> {\n if (!entry.dedupeKey || typeof entry.dedupeKey !== 'string' || entry.dedupeKey.trim().length === 0) {\n throw new Error('dedupeKey is required and must be a non-empty string');\n }\n const key = entry.dedupeKey;\n\n await this.withLock(async () => {\n const store = this.readStore();\n const existing = store[key];\n if (existing) {\n store[key] = {\n ...existing,\n lastTransmitted: now,\n firstUnsentEventTs: 0,\n suppressedCount: 0\n };\n this.writeStore(store);\n }\n });\n }\n\n async retryFailedTransmissions(send: (entry: LogEntry, message: string) => Promise<void>): Promise<void> {\n // Best-effort retry: first sweep expired entries, then iterate over entries with lastTransmitted === 0\n const now = Date.now();\n const entries = await this.withLock(async () => {\n const store = this.readStore();\n // Always sweep on retry\n this.evictExpiredInStore(store, now);\n this.lastSweepTsMs = now;\n this.writeStore(store); \n return Object.entries(store)\n .filter(([, rec]) => rec.lastTransmitted === 0)\n .map(([key, rec]) => ({ key, rec }));\n });\n\n for (const { key, rec } of entries) {\n try {\n const message = this.formatMessage(rec.message, rec.lastEventTs, rec.suppressedCount, rec.firstUnsentEventTs);\n await send({ level: rec.level, message: rec.message, dedupeKey: key, eventTime: rec.lastEventTs }, message);\n // Mark success\n await this.withLock(async () => {\n const store = this.readStore();\n const current = store[key];\n if (current) {\n store[key] = {\n ...current,\n lastTransmitted: Date.now(),\n suppressedCount: 0\n };\n this.writeStore(store);\n }\n });\n } catch (err) {\n // Leave entry for future retries; do not throw\n logger.error('Failed to retry failed transmission', { key, rec, error: err });\n return;\n }\n }\n }\n\n // --- Internals ---\n\n private ensureStoreFileExists(): void {\n try {\n if (!fs.existsSync(this.storeFilePath)) {\n fs.writeFileSync(this.storeFilePath, JSON.stringify({}), 'utf-8');\n }\n } catch {\n // ignore\n }\n }\n\n private readStore(): LogDedupeStore {\n try {\n if (!fs.existsSync(this.storeFilePath)) return {};\n const content = fs.readFileSync(this.storeFilePath, 'utf-8');\n return content ? (JSON.parse(content) as LogDedupeStore) : {};\n } catch {\n return {};\n }\n }\n\n private writeStore(store: LogDedupeStore): void {\n try {\n fs.writeFileSync(this.storeFilePath, JSON.stringify(store, null, 2), 'utf-8');\n } catch {\n // ignore\n }\n }\n\n private formatMessage(message: string, eventTs: number, suppressedCount: number, firstUnsentEventTs?: number): string {\n const timestamp = new Date(eventTs).toISOString();\n const base = `${timestamp} | ${message}`;\n if (suppressedCount > 0) {\n const since = firstUnsentEventTs && firstUnsentEventTs > 0 ? ` since ${new Date(firstUnsentEventTs).toISOString()}` : '';\n return `${base} (${suppressedCount} suppressed${since})`;\n }\n return base;\n }\n\n private async withLock<T>(fn: () => Promise<T>): Promise<T> {\n const lockPath = `${this.storeFilePath}.lock`;\n const start = Date.now();\n\n // Try to acquire the lock with retries (best effort)\n // Avoid infinite waits; cap at ~3 seconds\n while (true) {\n try {\n const fd = fs.openSync(lockPath, 'wx');\n try {\n const result = await fn();\n return result;\n } finally {\n try { fs.closeSync(fd); } catch {/* ignore */}\n try { fs.unlinkSync(lockPath); } catch {/* ignore */}\n }\n } catch {\n // Lock exists\n if (Date.now() - start > 3000) {\n // Timed out; proceed without lock to avoid deadlock\n return await fn();\n }\n await new Promise(resolve => setTimeout(resolve, 50));\n }\n }\n }\n\n /**\n * Evict expired entries from the store based on the TTL and the key's last transmitted time\n */\n private evictExpiredInStore(store: LogDedupeStore, now: number): void {\n const keys = Object.keys(store);\n if (keys.length === 0) return;\n for (const key of keys) {\n const rec = store[key];\n const referenceTs = rec.lastTransmitted > 0 ? rec.lastTransmitted : rec.lastEventTs;\n if (now - referenceTs > this.ttlMs) {\n delete store[key];\n }\n }\n }\n}\n","import logger from \"../services/reporting-service/logger\";\nimport { CoreConfiguration } from \"../services/data-sync-service/configuration-manager\";\nimport { SQLiteCoordinator } from \"../services/sqlite-service\";\nimport { getTimezoneOffsetAndPersist } from \"./time-utils\";\nimport knex from \"knex\";\nimport config from \"../knexfile\";\n\n/**\n * Utility class to handle application initialization tasks\n */\nexport class ApplicationInitializer {\n /**\n * Performs all necessary application initialization tasks\n * This should be called before starting any services or jobs\n */\n public static async initialize(): Promise<void> {\n try {\n logger.info(\n \"\\n================================INITIALIZING APPLICATION================================\\n\"\n );\n\n // Load and validate core configuration\n const coreConfig = CoreConfiguration.inst();\n logger.info(\"Core Configuration loaded:\", coreConfig.toSafeLogObject());\n\n // Perform database startup checks\n logger.info(\"Performing database startup checks...\");\n await SQLiteCoordinator.performStartupCheck();\n logger.info(\"Database startup checks completed successfully\");\n\n // Run database migrations to ensure all tables exist\n logger.info(\"Running database migrations...\");\n await ApplicationInitializer.runMigrations();\n logger.info(\"Database migrations completed successfully\");\n\n // Required for most connectors\n await getTimezoneOffsetAndPersist();\n\n logger.info(\n \"\\n================================APPLICATION INITIALIZATION COMPLETED================================\\n\"\n );\n } catch (error) {\n logger.error(\"Critical initialization failure. Exiting.\", error);\n process.exitCode = 1;\n }\n }\n\n /**\n * Runs database migrations to ensure all required tables exist\n */\n private static async runMigrations(): Promise<void> {\n const db = knex(config.local);\n try {\n await db.migrate.latest();\n } catch (error) {\n logger.error(\"Error running migrations:\", error);\n throw error;\n } finally {\n await db.destroy();\n }\n }\n}\n","import {\n HTTPClient,\n HTTPClientFactory,\n HTTPResponse,\n} from \"../../../utils/http-client\";\nimport { GraphQLResponse } from \"./types\";\nimport { ErrorHandler } from \"../errors\";\nimport { APIResponse } from \"../types\";\nimport { ERPApiConfig } from \"../types\";\n\nconst DEFAULT_RETRY_ATTEMPTS = 0; // One try only, no retries\n\nexport class GraphQLService {\n private client: HTTPClient;\n private config: ERPApiConfig;\n private endpoint: string;\n\n constructor(config: ERPApiConfig, endpoint: string, retryAttempts?: number) {\n this.config = config;\n this.endpoint = endpoint\n ? endpoint.startsWith(\"/\")\n ? endpoint\n : `/${endpoint}`\n : \"\";\n this.client = HTTPClientFactory.getInstance({\n baseUrl: config.apiUrl,\n retryAttempts: retryAttempts ?? DEFAULT_RETRY_ATTEMPTS,\n });\n }\n\n private async getHeaders(): Promise<Record<string, string>> {\n const token = await this.config.getAuthToken();\n return {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n };\n }\n\n /**\n * Handles a GraphQL response, properly handling both successful responses\n * and GraphQL-level errors (which come in a 200 HTTP response)\n * \n * From the GraphQL specification (https://spec.graphql.org/draft/#sec-Errors):\n> * \"When a GraphQL server encounters an error, it should return a response with a \n> * top-level \"errors\" field containing the error information. The response may still contain\n * a partial result in the \"data\" field if the error occurred after some data was already resolved.\"\n * \n * And specifically about HTTP status codes:\n * \"A server should return a 200 status code when a GraphQL operation successfully executes, including when \n * the operation returns errors. A server should return a 400 status code when a GraphQL operation fails to execute.\"\n */\n private handleGraphQLResponse<T>(\n response: HTTPResponse<GraphQLResponse<T>>\n ): APIResponse<T> {\n return {\n data: response.data.data as T,\n metadata: {\n errors: response.data.errors,\n extensions: response.data.extensions,\n },\n };\n }\n\n /**\n * Executes a GraphQL query\n * @param query The GraphQL query string\n * @param variables Optional variables for the query\n * @returns The query result\n * @throws {HTTPError} For HTTP/network errors only\n */\n async query<T>(\n query: string,\n variables?: Record<string, unknown>\n ): Promise<APIResponse<T>> {\n try {\n const headers = await this.getHeaders();\n const response = await this.client.request<GraphQLResponse<T>>({\n method: \"POST\",\n url: this.endpoint,\n data: { query, variables },\n headers,\n });\n\n return this.handleGraphQLResponse(response);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Executes a GraphQL mutation\n * @param mutation The GraphQL mutation string\n * @param variables Optional variables for the mutation\n * @returns The mutation result\n * @throws {HTTPError} For HTTP/network errors only\n */\n async mutate<T>(\n mutation: string,\n variables?: Record<string, unknown>\n ): Promise<APIResponse<T>> {\n try {\n const headers = await this.getHeaders();\n const response = await this.client.request<GraphQLResponse<T>>({\n method: \"POST\",\n url: this.endpoint,\n data: { query: mutation, variables },\n headers,\n });\n\n return this.handleGraphQLResponse(response);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Cleanup all HTTP connections and resources\n * Call this when the service is no longer needed\n */\n async destroy(): Promise<void> {\n await this.client.destroy();\n }\n}\n","/**\n * Configuration for OAuth client credentials flow\n */\nexport interface OAuthConfig {\n /** The URL to request the OAuth token from */\n authUrl: string;\n /** The client ID for OAuth authentication */\n clientId: string;\n /** The client secret for OAuth authentication */\n clientSecret: string;\n /** The scope for the OAuth token. Use empty string if no scope is needed. */\n scope: string;\n}\n\n/**\n * Standard OAuth 2.0 token response format as defined in RFC 6749\n */\nexport interface OAuthTokenResponse {\n /** The access token issued by the authorization server */\n access_token: string;\n /** The type of token issued (typically \"Bearer\") */\n token_type: string;\n /** The lifetime in seconds of the access token */\n expires_in: number;\n /** The refresh token, which can be used to obtain new access tokens (optional) */\n refresh_token?: string;\n /** The scope of the access token (optional) */\n scope?: string;\n}\n\n/**\n * A utility class for handling OAuth client credentials flow.\n * This is specifically for the client credentials grant type,\n * which is used for server-to-server authentication.\n */\nexport class OAuthClient {\n constructor(private config: OAuthConfig) {}\n\n /**\n * Requests an OAuth token using the client credentials flow.\n * The request is sent as application/x-www-form-urlencoded.\n * @returns A promise that resolves to the token response containing access_token and expires_in\n * @throws Error if the token request fails\n */\n async getToken(): Promise<OAuthTokenResponse> {\n const formData = new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n scope: this.config.scope,\n });\n\n const response = await fetch(this.config.authUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`OAuth token request failed: ${response.statusText}`);\n }\n\n const data = (await response.json()) as OAuthTokenResponse;\n if (data.token_type !== \"Bearer\") {\n throw new Error(`Unexpected token type: ${data.token_type}`);\n }\n\n return data;\n }\n}\n","/**\n * NATS Service - Central connection and subscription management\n * Allows connectors to register handlers for different NATS subjects\n */\n\nimport { connect, NatsConnection, StringCodec, Subscription } from \"nats\";\nimport { logger } from \"../reporting-service\";\n\nconst sc = StringCodec();\n\nexport interface NatsMessageHandler {\n /**\n * Handler function for NATS messages\n * - Return a value to reply (if msg has reply subject)\n * - Return void/undefined for pub-sub (no reply)\n */\n handle: (data: any, subject: string) => Promise<any | void>;\n}\n\nexport interface NatsHandlerRegistration {\n /**\n * Subject pattern to subscribe to (supports wildcards: *, >)\n * Examples:\n * - \"mm.14.epic01.labor-ticket.*\" (single level wildcard)\n * - \"mm.14.epic01.erp.>\" (multi-level wildcard)\n */\n subject: string;\n\n /**\n * Handler for this subject\n */\n handler: NatsMessageHandler;\n\n /**\n * Optional description for logging/debugging\n */\n description?: string;\n}\n\nexport interface NatsServiceConfig {\n /**\n * NATS server URLs\n */\n servers: string | string[];\n\n /**\n * Connection name (for monitoring)\n */\n name: string;\n\n /**\n * Location reference (for subject namespacing)\n */\n locationRef: string;\n\n /**\n * ERP type (epicor, infor, etc.)\n */\n erpType: string;\n\n /**\n * Enable/disable NATS\n */\n enabled: boolean;\n\n /**\n * Auto-reconnect settings\n */\n reconnect?: boolean;\n maxReconnectAttempts?: number;\n reconnectTimeWait?: number;\n}\n\nexport class NatsService {\n private connection: NatsConnection | null = null;\n private subscriptions: Map<string, Subscription> = new Map();\n private config: NatsServiceConfig;\n private handlers: NatsHandlerRegistration[] = [];\n private statusPublishTimer: NodeJS.Timeout | null = null;\n\n constructor(config: NatsServiceConfig) {\n this.config = config;\n }\n\n /**\n * Register a handler for a specific subject pattern\n */\n registerHandler(registration: NatsHandlerRegistration): void {\n logger.info(\"Registering NATS handler\", {\n subject: registration.subject,\n description: registration.description,\n });\n this.handlers.push(registration);\n }\n\n /**\n * Connect to NATS and start all registered handlers\n */\n async connect(): Promise<void> {\n if (!this.config.enabled) {\n logger.info(\"NATS is disabled, skipping connection\");\n return;\n }\n\n try {\n logger.info(\"Connecting to NATS...\", {\n servers: this.config.servers,\n name: this.config.name,\n });\n\n this.connection = await connect({\n servers: this.config.servers,\n name: this.config.name,\n reconnect: this.config.reconnect ?? true,\n maxReconnectAttempts: this.config.maxReconnectAttempts ?? -1,\n reconnectTimeWait: this.config.reconnectTimeWait ?? 2000,\n });\n\n logger.info(\"Connected to NATS\", {\n server: this.connection.getServer(),\n clientId: this.connection.info?.client_id,\n });\n\n // Start all registered handlers\n for (const registration of this.handlers) {\n await this.startHandler(registration);\n }\n\n // Start automatic status publishing\n this.startStatusPublishing();\n\n // Monitor connection status\n this.monitorConnection();\n\n // Graceful shutdown\n this.setupShutdown();\n } catch (error) {\n logger.error(\"Failed to connect to NATS\", { error });\n throw error;\n }\n }\n\n /**\n * Start a single handler (subscribe to its subject)\n */\n private async startHandler(registration: NatsHandlerRegistration): Promise<void> {\n if (!this.connection) {\n throw new Error(\"NATS connection not established\");\n }\n\n const sub = this.connection.subscribe(registration.subject);\n this.subscriptions.set(registration.subject, sub);\n\n logger.info(\"Started NATS handler\", {\n subject: registration.subject,\n description: registration.description,\n });\n\n // Process messages\n (async () => {\n for await (const msg of sub) {\n try {\n const data = sc.decode(msg.data);\n \n logger.info(\"Received NATS message\", {\n subject: msg.subject,\n hasReply: !!msg.reply,\n });\n\n // Parse JSON if possible\n let parsedData: any;\n try {\n parsedData = JSON.parse(data);\n } catch {\n parsedData = data;\n }\n\n // Call handler\n const response = await registration.handler.handle(parsedData, msg.subject);\n \n // If there's a reply subject and handler returned something, reply\n if (msg.reply && response !== undefined) {\n const responseStr = JSON.stringify(response);\n msg.respond(sc.encode(responseStr));\n logger.info(\"Sent reply\", { replySubject: msg.reply });\n }\n } catch (error) {\n logger.error(\"Error handling NATS message\", {\n subject: msg.subject,\n error,\n });\n\n // Send error response if reply expected\n if (msg.reply) {\n const errorResponse = {\n status: \"error\",\n error: {\n message: error instanceof Error ? error.message : \"Unknown error\",\n code: \"HANDLER_ERROR\",\n },\n };\n msg.respond(sc.encode(JSON.stringify(errorResponse)));\n }\n }\n }\n })();\n }\n\n /**\n * Publish a message to a subject (for pub/sub)\n */\n async publish(subject: string, data: any): Promise<void> {\n if (!this.connection) {\n throw new Error(\"NATS connection not established\");\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n this.connection.publish(subject, sc.encode(message));\n\n logger.info(\"Published NATS message\", { subject });\n }\n\n /**\n * Send a request and wait for reply (for request-reply)\n */\n async request(subject: string, data: any, timeoutMs: number = 30000): Promise<any> {\n if (!this.connection) {\n throw new Error(\"NATS connection not established\");\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n const response = await this.connection.request(\n subject,\n sc.encode(message),\n { timeout: timeoutMs }\n );\n\n const responseData = sc.decode(response.data);\n \n try {\n return JSON.parse(responseData);\n } catch {\n return responseData;\n }\n }\n\n /**\n * Check if connected to NATS\n */\n isConnected(): boolean {\n return this.connection !== null && !this.connection.isClosed();\n }\n\n /**\n * Start automatic status publishing (every 30 seconds)\n */\n private startStatusPublishing(): void {\n logger.info(\"Starting status publishing (every 30 seconds)\");\n\n // Publish immediately on start\n this.publishStatus();\n\n // Then publish every 30 seconds\n this.statusPublishTimer = setInterval(() => {\n this.publishStatus();\n }, 30000);\n }\n\n /**\n * Publish connector status\n */\n private async publishStatus(): Promise<void> {\n try {\n const status = {\n timestamp: new Date().toISOString(),\n locationRef: this.config.locationRef,\n erpType: this.config.erpType,\n natsConnected: this.isConnected(),\n };\n\n await this.publish(\n `mm.14.${this.config.locationRef}.erp.status`,\n status\n );\n\n logger.debug(\"Published connector status\");\n } catch (error) {\n logger.error(\"Failed to publish status\", { error });\n }\n }\n\n /**\n * Monitor connection status\n */\n private monitorConnection(): void {\n if (!this.connection) return;\n\n (async () => {\n for await (const status of this.connection!.status()) {\n // Only log important events, skip pingTimer\n if (status.type !== \"pingTimer\") {\n logger.info(\"NATS connection status\", {\n type: status.type,\n data: status.data,\n });\n }\n }\n })();\n }\n\n /**\n * Setup graceful shutdown\n */\n private setupShutdown(): void {\n const shutdown = async () => {\n logger.info(\"Shutting down NATS service...\");\n await this.disconnect();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n }\n\n /**\n * Disconnect from NATS\n */\n async disconnect(): Promise<void> {\n // Stop status publishing\n if (this.statusPublishTimer) {\n clearInterval(this.statusPublishTimer);\n this.statusPublishTimer = null;\n }\n\n // Close connection\n if (this.connection) {\n await this.connection.drain();\n this.connection = null;\n this.subscriptions.clear();\n logger.info(\"Disconnected from NATS\");\n }\n }\n\n /**\n * Get the location reference\n */\n getLocationRef(): string {\n return this.config.locationRef;\n }\n}\n\n","import logger from \"../reporting-service/logger\";\nimport { formatError } from \"../../utils/error-formatter\";\nimport { NatsService } from \"../nats-service/nats-service\";\nimport { MMReceiveLaborTicket, MMApiClient, getCompanyInfo } from \"../mm-api-service\";\nimport { SQLiteCoordinator } from \"../sqlite-service\";\n\n/**\n * Manages NATS-based real-time labor ticket processing\n */\nexport class NatsLaborTicketListener {\n private connector: any;\n private natsService?: NatsService;\n\n constructor(connector: any) {\n this.connector = connector;\n }\n\n /**\n * Start listening for labor ticket events via NATS\n */\n async start(): Promise<void> {\n try {\n // Fetch company info from MM API (includes locationRef)\n const companyInfo = await getCompanyInfo();\n const erpType = this.connector.type || \"unknown\";\n\n logger.info(\"Starting NATS listener for labor tickets\", {\n locationRef: companyInfo.locationRef,\n companyId: companyInfo.companyId,\n erpType,\n servers: process.env.NATS_SERVERS || \"nats://localhost:4222\",\n });\n\n this.natsService = new NatsService({\n servers: process.env.NATS_SERVERS || \"nats://localhost:4222\",\n name: `${erpType}-connector`,\n locationRef: companyInfo.locationRef,\n erpType,\n enabled: true,\n reconnect: true,\n maxReconnectAttempts: -1,\n reconnectTimeWait: 2000,\n });\n\n // Register health check handler\n this.registerHealthCheckHandler(companyInfo.locationRef, erpType);\n\n // Register labor ticket handler\n this.registerLaborTicketHandler(companyInfo.locationRef, erpType);\n\n // Connect to NATS\n await this.natsService.connect();\n\n logger.info(\"NATS listener started successfully\", {\n subject: `mm.14.${companyInfo.locationRef}.labor-ticket.*`,\n });\n } catch (error) {\n logger.error(\"Failed to start NATS listener\", { error });\n // Don't throw - allow REST polling to continue\n }\n }\n\n /**\n * Register health check handler - responds immediately to let Dashboard know connector is online\n */\n private registerHealthCheckHandler(locationRef: string, erpType: string): void {\n if (!this.natsService) return;\n\n this.natsService.registerHandler({\n subject: `mm.14.${locationRef}.erp.health`,\n description: \"Health check - responds immediately to indicate connector is online\",\n handler: {\n handle: async () => {\n logger.debug(\"Health check received, sending pong\");\n return {\n status: \"online\",\n timestamp: new Date().toISOString(),\n locationRef,\n erpType,\n };\n },\n },\n });\n }\n\n /**\n * Register labor ticket processing handler\n */\n private registerLaborTicketHandler(locationRef: string, erpType: string): void {\n if (!this.natsService) return;\n\n this.natsService.registerHandler({\n subject: `mm.14.${locationRef}.labor-ticket.*`,\n description: \"Process labor tickets in real-time from NATS\",\n handler: {\n handle: async ({ data }: any, subject: string) => {\n const action = subject.split(\".\").pop();\n const { actionPayload } = data;\n const startTime = Date.now();\n const { laborTicketRef } = actionPayload;\n\n logger.info(\"Received labor ticket via NATS\", {\n action,\n requestId: data.requestId,\n laborTicketRef,\n });\n\n // Use the same lock as to-erp job to prevent concurrent processing\n return await SQLiteCoordinator.executeWithLock(\"to-erp\", async () => {\n try {\n let laborTicketData;\n\n if (laborTicketRef) {\n // Fetch the full labor ticket data using the laborTicketRef\n const mmApiClient = new MMApiClient();\n const laborTicket = await mmApiClient.fetchLaborTicketByRef(laborTicketRef);\n\n logger.info(\"Fetched labor ticket data from MM API\", {\n laborTicketRef,\n laborTicketId: laborTicket.laborTicketId,\n });\n\n // Merge changed fields from actionPayload to ensure we process the actual changes\n laborTicketData = {\n ...laborTicket,\n ...actionPayload,\n };\n } else {\n // No laborTicketRef provided, use the actionPayload directly\n logger.info(\"No laborTicketRef provided, using actionPayload directly\", {\n requestId: data.requestId,\n });\n\n laborTicketData = actionPayload;\n }\n\n const mergedLaborTicket = new MMReceiveLaborTicket(laborTicketData);\n\n // Process labor ticket: ERP operation first, then create MM entities with ERP ID\n const result = await this.processLaborTicket(\n mergedLaborTicket,\n action || \"unknown\"\n );\n\n // Update checkpoint to prevent to-erp polling job from reprocessing this ticket\n await this.updateCheckpoint(erpType, result);\n\n // Return response with full labor ticket data for immediate frontend state update\n return {\n status: \"success\",\n requestId: data.requestId,\n action,\n timestamp: new Date().toISOString(),\n processingTimeMs: Date.now() - startTime,\n laborTicketRef: result.laborTicketRef,\n laborTicket: result.laborTicket,\n };\n } catch (error) {\n // Check if error was already formatted in inner catch block\n const formattedError = (error as any)?._formatted || formatError(error);\n\n // Debug logging\n logger.debug(\"Error details\", {\n hasFormatted: !!(error as any)?._formatted,\n isAxiosError: (error as any)?.isAxiosError,\n errorMessage: (error as any)?.message,\n responseStatus: (error as any)?.response?.status,\n responseData: (error as any)?.response?.data,\n });\n\n logger.error(\"Error handling labor ticket from NATS\", {\n error: formattedError.message,\n code: formattedError.code,\n requestId: data.requestId,\n laborTicketRef,\n });\n\n return {\n status: \"error\",\n requestId: data.requestId,\n action,\n timestamp: new Date().toISOString(),\n processingTimeMs: Date.now() - startTime,\n error: {\n message: formattedError.message,\n code: formattedError.code,\n httpStatus: formattedError.httpStatus,\n metadata: formattedError.metadata,\n laborTicketRef,\n },\n };\n }\n });\n },\n },\n });\n }\n\n /**\n * Process labor ticket in ERP and then create MM entities with ERP ID attached\n */\n private async processLaborTicket(\n laborTicket: MMReceiveLaborTicket,\n action: string\n ): Promise<{ laborTicketRef: string; laborTicket: any }> {\n const { MMEntityProcessor } = await import(\"../../utils/standard-process-drivers/mm-entity-processor\");\n const { ERPObjType } = await import(\"../../types/erp-types\");\n const { MMSendLaborTicket } = await import(\"../mm-api-service\");\n\n logger.info(\"Processing labor ticket: ERP first, then MM creation\", {\n laborTicketRef: laborTicket.laborTicketRef,\n action,\n hasLaborTicketId: !!laborTicket.laborTicketId,\n });\n\n try {\n let erpResult;\n\n if (action === \"create\" || !laborTicket.laborTicketId) {\n // Create new labor ticket in ERP\n erpResult = await this.connector.createLaborTicketInERP(laborTicket);\n logger.info(\"Successfully created labor ticket in ERP\", {\n laborTicketRef: laborTicket.laborTicketRef,\n erpUid: erpResult.erpUid,\n });\n } else {\n // Update existing labor ticket in ERP\n erpResult = { laborTicket: await this.connector.updateLaborTicketInERP(laborTicket) };\n logger.info(\"Successfully updated labor ticket in ERP\", {\n laborTicketRef: laborTicket.laborTicketRef,\n });\n }\n\n // Now update MM entities with ERP ID attached\n const laborTicketForMM = { ...laborTicket };\n if (erpResult.erpUid) {\n laborTicketForMM.laborTicketId = erpResult.erpUid;\n \n // If this was a create (no initial laborTicketId) with a laborTicketRef,\n // patch the existing orphaned MM record with the new ERP ID first\n if (!laborTicket.laborTicketId && laborTicketForMM.laborTicketRef) {\n const mmApiClient = new MMApiClient();\n await mmApiClient.updateLaborTicketIdByRef(\n laborTicketForMM.laborTicketRef,\n erpResult.erpUid\n );\n \n logger.info(\"Patched existing MM labor ticket with new ERP ID\", {\n laborTicketRef: laborTicketForMM.laborTicketRef,\n laborTicketId: erpResult.erpUid,\n });\n }\n }\n\n // Set state based on clockOut before writing to MM\n laborTicketForMM.state = laborTicketForMM.clockOut ? \"CLOSED\" : \"OPEN\";\n\n // Write all fields to MM (this updates the existing record now that it has laborTicketId)\n const mmLaborTicket = MMSendLaborTicket.fromPlainObject(laborTicketForMM as any);\n\n const mmResult = await MMEntityProcessor.writeEntities(\n ERPObjType.LABOR_TICKETS,\n [mmLaborTicket],\n null // No caching for real-time operations\n );\n\n logger.info(\"Successfully updated MM entities after ERP operation\", {\n laborTicketRef: laborTicketForMM.laborTicketRef,\n laborTicketId: laborTicketForMM.laborTicketId,\n entitiesCreated: mmResult.upsertedEntities,\n });\n\n return {\n laborTicketRef: laborTicketForMM.laborTicketRef!,\n laborTicket: laborTicketForMM,\n };\n } catch (error) {\n // Format error to preserve meaningful message\n const formattedError = formatError(error);\n\n logger.error(\"Failed to process labor ticket with MM creation\", {\n laborTicketRef: laborTicket.laborTicketRef,\n action,\n error: formattedError.message,\n code: formattedError.code,\n });\n\n // Create enhanced error to propagate\n const enhancedError: any = error;\n enhancedError._formatted = formattedError;\n throw enhancedError;\n }\n }\n\n /**\n * Update checkpoint to prevent to-erp polling job from reprocessing this ticket\n * Only updates if the new timestamp is later than the current checkpoint (prevents moving backwards)\n */\n private async updateCheckpoint(\n erpType: string,\n result: { laborTicketRef: string; laborTicket: any }\n ): Promise<void> {\n const mmApiClient = new MMApiClient();\n const currentCheckpoint = await mmApiClient.getCheckpoint({\n system: erpType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: \"\",\n },\n });\n\n const currentTimestamp = (currentCheckpoint as any)?.timestamp;\n const newTimestamp = result.laborTicket.updatedAt || new Date().toISOString();\n\n // Only update checkpoint if new timestamp is later than current (prevents moving backwards)\n if (!currentTimestamp || new Date(newTimestamp) > new Date(currentTimestamp)) {\n await mmApiClient.saveCheckpoint({\n system: erpType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: newTimestamp,\n },\n });\n\n logger.debug(\"Updated export checkpoint after NATS processing\", {\n laborTicketRef: result.laborTicketRef,\n previousCheckpoint: currentTimestamp,\n newCheckpoint: newTimestamp,\n });\n } else {\n logger.debug(\"Skipped checkpoint update (timestamp not newer)\", {\n laborTicketRef: result.laborTicketRef,\n currentCheckpoint: currentTimestamp,\n ticketTimestamp: newTimestamp,\n });\n }\n }\n}\n\n","import Bree, { Job } from \"bree\";\nimport Graceful from \"@ladjs/graceful\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { CoreConfiguration } from \"./configuration-manager\";\nimport logger from \"../reporting-service/logger\";\nimport { createConnectorFromPath } from \"../../utils/connector-factory\";\nimport { NatsLaborTicketListener } from \"./nats-labor-ticket-listener\";\n\nexport const runDataSyncService = async (connectorPath: string) => {\n const config = CoreConfiguration.inst();\n\n try {\n // Create connector instance\n const connector = await createConnectorFromPath(connectorPath);\n\n // Start NATS listener if enabled\n if (process.env.NATS_ENABLED === \"true\") {\n const natsListener = new NatsLaborTicketListener(connector);\n await natsListener.start();\n }\n // Find the SDK's node_modules directory and jobs path using ES module import\n const currentFileUrl = import.meta.url;\n const currentFilePath = fileURLToPath(currentFileUrl);\n\n // Navigate from current file to the SDK root and then to jobs\n // Current: /path/to/sdk/dist/mm-erp-sdk.js.js\n // Target: /path/to/sdk/dist/services/data-sync-service/jobs\n const sdkDistPath = path.dirname(currentFilePath);\n const jobsPath = path.join(\n sdkDistPath,\n \"services\",\n \"data-sync-service\",\n \"jobs\"\n );\n\n const bree = new Bree({\n root: jobsPath,\n logger,\n worker: {\n env: {\n CONNECTOR_PATH: connectorPath,\n // Pass through all required environment variables\n MM_MAPPING_SERVICE_URL: process.env.MM_MAPPING_SERVICE_URL,\n MM_MAPPING_AUTH_SERVICE_URL: process.env.MM_MAPPING_AUTH_SERVICE_URL,\n MM_MAPPING_SERVICE_TOKEN: process.env.MM_MAPPING_SERVICE_TOKEN,\n ERP_SYSTEM: process.env.ERP_SYSTEM,\n LOG_LEVEL: process.env.LOG_LEVEL,\n NODE_ENV: process.env.NODE_ENV,\n RETRY_ATTEMPTS: process.env.RETRY_ATTEMPTS,\n CACHE_TTL: process.env.CACHE_TTL,\n // Pass through all other environment variables that might be needed\n ...process.env,\n },\n },\n jobs: [\n // {\n // name: 'run-migrations', // Running this once on startup will create the tables in the sqlite database\n // },\n {\n name: \"from-erp\",\n timeout: \"10s\",\n interval: config.fromErpInterval,\n },\n {\n name: \"to-erp\",\n //timeout: '3s', // Use timeout during development to see the job in action quickly\n interval: config.toErpInterval,\n },\n {\n name: \"retry-failed-labor-tickets\",\n interval: config.retryLaborTicketsInterval,\n },\n {\n name: \"clean-up-expired-cache\",\n interval: config.cacheExpirationCheckInterval,\n },\n ],\n });\n\n logger.info(\n \"\\n================================INITIATING DATA SYNC CYCLES (Bree)================================\\n\"\n );\n const jobsConfig = bree.config.jobs.map((job: Job) => ({\n name: job.name,\n interval: job.interval,\n timeout: job.timeout,\n }));\n logger.info(\"JOBS CONFIGURATION:\", { jobs: jobsConfig });\n\n const graceful = new Graceful({ brees: [bree] });\n graceful.listen();\n\n (async () => {\n await bree.start();\n })();\n\n bree.on(\"jobStarted\", (job) => {\n console.log(\"Job \" + job.name + \" started\");\n });\n\n bree.on(\"jobCompleted\", (job) => {\n if (job.error) {\n console.error(\"Job \" + job.name + \" failed:\", job.error);\n } else {\n console.log(\"Job \" + job.name + \" completed successfully!\");\n }\n });\n\n bree.on(\"error\", (error) => {\n console.error(\"Bree error:\", error);\n });\n\n // Exit and let the jobs take over\n logger.info(\n \"\\n================================DATA SYNC CYCLES INITIATION COMPLETED================================\\n\"\n );\n } catch (error) {\n logger.error(\"startUp: Error initiating data sync cycles:\", error);\n }\n};\n","import { z } from \"zod\";\nexport const SQLServerConfigSchema = z.object({\n user: z.string().nonempty(\"User is required.\"),\n password: z.string().nonempty(\"Password is required.\"),\n database: z.string().nonempty(\"Database name is required.\"),\n server: z.string().nonempty(\"Server is required.\"),\n port: z.coerce.number().int().positive(\"Port must be a positive integer.\").default(1433),\n connectionTimeout: z.coerce\n .number()\n .int()\n .positive(\"Connection timeout must be a positive integer.\")\n .default(30000),\n requestTimeout: z.coerce\n .number()\n .int()\n .positive(\"Request timeout must be a positive integer.\")\n .default(60000),\n pool: z.object({\n max: z.coerce\n .number()\n .int()\n .positive(\"Max pool size must be a positive integer.\")\n .default(10),\n min: z.coerce\n .number()\n .int()\n .nonnegative(\"Min pool size must be a non-negative integer.\")\n .default(0),\n idleTimeoutMillis: z.coerce\n .number()\n .int()\n .nonnegative(\"Idle timeout must be a non-negative integer.\")\n .default(30000),\n }),\n options: z.object({\n encrypt: z.coerce.boolean().default(false),\n trustServerCertificate: z.coerce.boolean().default(false),\n }),\n});\n","import sql, { ConnectionPool, IRecordSet } from \"mssql\";\nimport { SQLServerConfigSchema } from \"./internal/sql-server-config\";\nimport logger from \"../reporting-service/logger\";\nimport { ERPResponse } from \"../../types/erp-types\";\nimport { SQLServerConfiguration } from \"./configuration\";\n\ntype PagingParams = {\n limit?: number;\n offset?: number;\n};\n\nexport class SqlServerService {\n private connection: ConnectionPool | null = null;\n private config: SQLServerConfiguration;\n\n constructor(config: SQLServerConfiguration) {\n this.config = config;\n }\n\n async dispose() {\n await this.closeConnection();\n }\n\n /**\n * Execute a prepared statement and return the results\n *\n * @param statementToPrepare The SQL query to prepare\n * @param params The parameters to bind to the prepared statement\n * @param paging Optional paging parameters\n * @returns The entities fetched from the database, along with paging information\n */\n public async executePreparedStatement(\n statementToPrepare: string,\n params: Record<string, string>,\n paging?: PagingParams\n ): Promise<ERPResponse | undefined> {\n const connection = await this.openConnection();\n\n const preparedStatement = new sql.PreparedStatement(connection);\n\n // Add all parameters as inputs\n Object.keys(params).forEach((key) => {\n preparedStatement.input(key, sql.VarChar);\n });\n\n // If limit is specified, try SQL paging\n const finalQuery =\n paging?.limit !== undefined\n ? `${statementToPrepare} OFFSET ${paging.offset || 0} ROWS FETCH NEXT ${paging.limit} ROWS ONLY`\n : statementToPrepare;\n\n await preparedStatement.prepare(finalQuery);\n\n let records;\n try {\n records = await preparedStatement.execute(params);\n } catch (error) {\n const errorInfo = error as Error;\n const errorMessage =\n \"Error fetching data from ERP -> \" +\n (errorInfo.cause ? `Cause: ${errorInfo.cause}. ` : \"\") +\n `Name: ${errorInfo.name}. ` +\n `Message: ${errorInfo.message}. ` +\n `Stack: ${errorInfo.stack}`;\n\n throw new Error(errorMessage);\n } finally {\n try {\n await preparedStatement.unprepare();\n } catch (unprepareError) {\n logger.error(\"Error during unprepare:\", unprepareError);\n }\n }\n\n const allRecords = SqlServerService.recordsetToRecords(records?.recordset);\n const rowsFetched = records?.rowsAffected[0] || 0;\n\n // If SQL paging failed or we need to handle offset, apply in-memory paging\n const pagedData =\n paging?.offset !== undefined\n ? allRecords.slice(\n paging.offset,\n (paging.offset || 0) + (paging.limit || allRecords.length)\n )\n : allRecords;\n\n return {\n data: pagedData,\n paging: {\n count: rowsFetched,\n limit: paging?.limit || 0,\n offset: paging?.offset || 0,\n nextPage: (() => {\n const currentPageEnd = (paging?.offset || 0) + (paging?.limit || 0);\n return paging?.limit && currentPageEnd < rowsFetched\n ? String(currentPageEnd)\n : undefined;\n })(),\n previousPage: paging?.offset\n ? String(Math.max(0, (paging.offset || 0) - (paging.limit || 10)))\n : undefined,\n },\n };\n }\n\n /**\n * Opens a connection to SQL Server\n * Caches the connection so that it can be reused.\n * On failure to connect, throws\n */\n async openConnection(): Promise<ConnectionPool> {\n // If we have a connection and it's still connected, reuse it\n if (this.connection?.connected) {\n logger.debug(\"Reusing existing SQL Server connection\");\n return this.connection;\n }\n\n // If we have a connection but it's not connected, just null it out\n if (this.connection) {\n logger.debug(\"Clearing disconnected SQL Server connection\");\n this.connection = null;\n }\n\n try {\n logger.info(\"Opening new SQL Server connection\");\n const sqlConfig = SQLServerConfigSchema.parse({\n user: this.config.username,\n password: this.config.password,\n database: this.config.database,\n server: this.config.host,\n port: parseInt(this.config.port),\n connectionTimeout: parseInt(this.config.connectionTimeout),\n requestTimeout: parseInt(this.config.requestTimeout),\n pool: {\n max: parseInt(this.config.poolMax),\n min: parseInt(this.config.poolMin),\n idleTimeoutMillis: parseInt(this.config.idleTimeoutMillis),\n },\n options: {\n encrypt: this.config.encrypt,\n trustServerCertificate: this.config.trustServer,\n },\n });\n\n this.connection = await sql.connect(sqlConfig);\n return this.connection;\n } catch (error) {\n logger.error(\"SqlServerService>>openConnection>> Connection failed\", {\n error,\n });\n throw new Error(\"SQL Server connection failed\");\n }\n }\n\n /**\n * Transform a IRecordSet from a SQL query into an array of Record<string, string> instances.\n * @param recordset IRecordSet from a SQL query\n * @returns array of Record<string, string> instances\n */\n public static recordsetToRecords(\n recordset?: IRecordSet<Record<string, unknown>>\n ): Record<string, string>[] {\n const data: Record<string, string>[] =\n recordset?.map((row) => {\n const transformedRow: Record<string, string> = {};\n Object.keys(row).forEach((key) => {\n transformedRow[key] = row[key]?.toString() || \"\";\n });\n return transformedRow;\n }) || [];\n\n return data;\n }\n\n private async closeConnection(): Promise<void> {\n if (this.connection?.connected) {\n logger.info(\"Closing SQL Server connection\");\n try {\n await this.connection.close();\n } catch (error) {\n logger.error(\n \"SqlServerService::closeConnection: Error closing connection\",\n {\n error,\n }\n );\n }\n this.connection = null;\n }\n }\n}\n","import sql from \"mssql\";\nimport logger from \"../../reporting-service/logger\";\n\nexport interface SQLTransactionConfig {\n isolationLevel?: sql.IIsolationLevel;\n}\n\nexport interface SQLTransactionOperation<T, R> {\n execute(transaction: sql.Transaction, data: T): Promise<R>;\n}\n\nexport class SQLTransactionManager {\n constructor(private connection: sql.ConnectionPool) {}\n /**\n * Executes a transaction with the given operation and data, manages rollbacks\n * @param operation The SQLTransactionOperation to execute\n * @param data The sql input parameters to pass to the operation\n * @param config The configuration for the transaction\n * @returns The result of the operation; throws exceptions on failures\n */\n async executeTransaction<T, R>(\n operation: SQLTransactionOperation<T, R>,\n data: T,\n config: SQLTransactionConfig = {}\n ): Promise<R | undefined> {\n const transaction = new sql.Transaction(this.connection);\n const isolationLevel =\n config.isolationLevel || sql.ISOLATION_LEVEL.READ_COMMITTED;\n\n try {\n await transaction.begin(isolationLevel);\n const result = await operation.execute(transaction, data);\n await transaction.commit();\n return result;\n } catch (error) {\n logger.error(\"Transaction error:\", error);\n try {\n await transaction.rollback();\n } catch (rollbackError) {\n logger.error(\"Rollback error:\", rollbackError);\n }\n throw error;\n }\n }\n}\n","import sql from \"mssql\";\nimport { SQLInput } from \"../types/sql-input-param\";\nimport { SQLTransactionOperation } from \"./sql-transaction-manager\";\n\n/**\n * Insert a new labor ticket into the database.\n * Create a query named laborTicketsInsert in the sql_toERP folder, add its inputs to the SQLInput array\n * The laborTicketsInsert query is expected to return the NewRowUniqueId of the inserted labor ticket\n *\n * @param transaction The SQL transaction object\n * @param inputs The SQL input parameters\n * @returns The NewRowUniqueId of the inserted labor ticket\n */\nexport class SQLLaborTicketInsertOperation\n implements SQLTransactionOperation<SQLInput[], number>\n{\n constructor(private readonly sqlQuery: string) {}\n\n async execute(\n transaction: sql.Transaction,\n inputs: SQLInput[]\n ): Promise<number> {\n const request = transaction.request();\n inputs.forEach((input) => {\n request.input(input.name, input.type, input.value);\n });\n\n const result = await request.query(this.sqlQuery);\n\n if (!result.recordset[0].NewRowUniqueId) {\n throw new Error(\n \"No LaborTicketId generated by the labor ticket insert query: \" +\n JSON.stringify(request.parameters, null, 2)\n );\n }\n\n return result.recordset[0].NewRowUniqueId;\n }\n}\n\n/**\n * Update an existing labor ticket in the database.\n * Create a query named laborTicketsUpdate in the sql_toERP folder, add its inputs to the SQLInput array\n *\n * @param transaction The SQL transaction object\n * @param inputs The SQL input parameters\n * @returns The number of rows affected\n */\nexport class SQLLaborTicketUpdateOperation\n implements SQLTransactionOperation<SQLInput[], number>\n{\n constructor(private readonly sqlQuery: string) {}\n\n async execute(\n transaction: sql.Transaction,\n inputs: SQLInput[]\n ): Promise<number> {\n const request = transaction.request();\n inputs.forEach((input) => {\n request.input(input.name, input.type, input.value);\n });\n\n const result = await request.query(this.sqlQuery);\n return result.rowsAffected[0];\n }\n}\n","import { SQLTransactionManager } from \"./internal/sql-transaction-manager\";\nimport {\n SQLLaborTicketInsertOperation,\n SQLLaborTicketUpdateOperation,\n} from \"./internal/sql-labor-ticket-operations\";\nimport { MMReceiveLaborTicket } from \"../mm-api-service/types/receive-types\";\nimport { SqlServerService } from \"./sql-server-service\";\nimport logger from \"../reporting-service/logger\";\nimport { SQLInput } from \"./types/sql-input-param\";\n\nexport class SqlServerHelper {\n /**\n * Creates a new labor ticket in the SQL Server database\n * Opens but does not close the connection\n *\n * @param sqlServerService The SQL Server service to use for connections\n * @param laborTicket The labor ticket to create\n * @param sqlInputs The SQL query parameters\n * @param insertQuery The SQL query to execute for insertion\n *\n * @returns The processed labor ticket, with the ERP's laborTicketId set\n * Throws exceptions on failures, including if the query does not return a laborTicketId\n */\n static async createLaborTicket(\n sqlServerService: SqlServerService,\n laborTicket: MMReceiveLaborTicket,\n sqlInputs: SQLInput[],\n insertQuery: string\n ): Promise<MMReceiveLaborTicket> {\n const connection = await sqlServerService.openConnection();\n if (!connection) {\n throw new Error(\n \"Unable to establish database connection to create labor ticket.\"\n );\n }\n\n const transactionManager = new SQLTransactionManager(connection);\n const laborTicketId = await transactionManager.executeTransaction(\n new SQLLaborTicketInsertOperation(insertQuery),\n sqlInputs\n );\n\n if (laborTicketId) {\n laborTicket.laborTicketId = laborTicketId.toString();\n return laborTicket;\n } else {\n throw new Error(\n `The query did not return the labor ticket id for labor ticket ref: ${laborTicket.laborTicketRef}`\n );\n }\n }\n\n /**\n * Updates an existing labor ticket in the SQL Server database\n * Opens but does not close the connection\n * @param sqlServerService The SQL Server service to use for connections\n * @param laborTicket The labor ticket to update\n * @param sqlInputs The SQL query parameters\n * @param updateQuery The SQL query to execute for update\n *\n * @returns The processed labor ticket\n * Throws exceptions on failures\n */\n static async updateLaborTicket(\n sqlServerService: SqlServerService,\n laborTicket: MMReceiveLaborTicket,\n sqlInputs: SQLInput[],\n updateQuery: string\n ): Promise<MMReceiveLaborTicket> {\n const connection = await sqlServerService.openConnection();\n if (!connection) {\n throw new Error(\n \"Unable to establish database connection to update labor ticket.\"\n );\n }\n\n const transactionManager = new SQLTransactionManager(connection);\n await transactionManager.executeTransaction(\n new SQLLaborTicketUpdateOperation(updateQuery),\n sqlInputs\n );\n\n return laborTicket;\n }\n\n /**\n * Logs the SQL input parameters for debugging\n */\n static logQueryInputs(inputs: SQLInput[]): void {\n logger.info(\"SQL Input Variables:\");\n logger.info(\"----------------------------------------\");\n logger.info(\"Variable Name | Value\");\n logger.info(\"----------------------------------------\");\n inputs.forEach((input) => {\n logger.info(`${input.name.padEnd(20)} | ${input.value}`);\n });\n logger.info(\"----------------------------------------\");\n }\n}\n","import { PsqlConfiguration } from \"./configuration\";\nimport { ERPResponse } from \"../../types/erp-types\";\nimport { OdbcErrorResponse } from \"./internal/types/psql-types\";\nimport logger from \"../reporting-service/logger\";\n\ntype PagingParams = {\n limit?: number;\n offset?: number;\n};\n\n/**\n * ODBC connection interface for type safety\n */\ninterface OdbcConnection {\n query(sql: string): Promise<any[]>;\n close(): Promise<void>;\n}\n\n/**\n * ODBC module interface\n */\ninterface OdbcModule {\n connect(connectionString: string): Promise<OdbcConnection>;\n}\n\nexport class PsqlService {\n private config: PsqlConfiguration;\n private static odbcModule: OdbcModule | null = null;\n private static odbcLoadError: Error | null = null;\n\n constructor(config: PsqlConfiguration) {\n this.config = config;\n }\n\n /**\n * Dynamically load the ODBC module with lazy initialization and caching\n * @throws Error with helpful message if ODBC package is not installed\n */\n private static async getOdbc(): Promise<OdbcModule> {\n // If we've already tried and failed, throw the cached error\n if (this.odbcLoadError) {\n throw this.odbcLoadError;\n }\n\n // If already loaded, return cached module\n if (this.odbcModule) {\n return this.odbcModule;\n }\n\n try {\n // Dynamic import - only loads when actually needed\n // @ts-ignore - odbc is an optional dependency, may not be installed at build time\n const odbcImport = await import(\"odbc\");\n // Handle both default export and named export patterns\n const odbc = odbcImport.default || odbcImport;\n this.odbcModule = odbc as OdbcModule;\n return this.odbcModule;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.odbcLoadError = new Error(\n `ODBC package is required for PSQL service but is not installed or failed to load.\\n` +\n `Install it with: npm install odbc\\n` +\n `Also install OS-level dependencies, e.g. on Alpine Linux:\\n` +\n ` apk add --no-cache unixodbc unixodbc-dev python3 make g++\\n` +\n `For other Linux distributions, install unixodbc and unixodbc-dev packages.\\n` +\n `Original error: ${errorMessage}`\n );\n throw this.odbcLoadError;\n }\n }\n\n // REMOVED: dispose() method - not needed anymore\n // REMOVED: connection property - not needed anymore\n // REMOVED: openConnection() method - not needed anymore\n // REMOVED: closeConnection() method - not needed anymore\n\n /**\n * Build PSQL ODBC connection string\n * CRITICAL: ServerName must use IP.PORT format (e.g., 10.4.0.11.1583)\n */\n private buildConnectionString(): string {\n const serverName = `${this.config.host}.${this.config.port}`;\n\n return (\n [\n \"Driver={Pervasive ODBC Interface}\",\n `ServerName=${serverName}`,\n `DBQ=${this.config.database}`,\n `UID=${this.config.username}`,\n `PWD=${this.config.password}`,\n \"AutoDoubleQuote=0\",\n ].join(\";\") + \";\"\n );\n }\n\n /**\n * Execute a query and return the results\n * Creates a fresh connection for each query to avoid handle corruption\n *\n * SECURITY WARNING: This method executes the provided SQL string as-is.\n * - Parameter binding is NOT implemented; the `params` argument is currently ignored.\n * - Never concatenate untrusted/user-supplied input into `query`.\n * - Doing so can result in SQL injection vulnerabilities and data exposure.\n * If dynamic values are required, ensure they are strictly validated/escaped\n * or implement proper parameterized execution before accepting untrusted input.\n *\n * @param query The SQL query to execute\n * @param params Query parameters (currently unused for PSQL read operations)\n * @param paging Optional paging parameters\n * @returns The entities fetched from the database, along with paging information\n */\n public async executePreparedStatement(\n query: string,\n params: Record<string, string> = {},\n paging?: PagingParams\n ): Promise<ERPResponse | undefined> {\n // Dynamically load ODBC module (will throw helpful error if not installed)\n const odbc = await PsqlService.getOdbc();\n let connection: OdbcConnection | null = null;\n\n try {\n // Create fresh connection for THIS query only\n const connStr = this.buildConnectionString();\n logger.debug(\"Creating fresh PSQL connection for query\");\n connection = await odbc.connect(connStr);\n\n if (Object.keys(params).length > 0) {\n logger.warn(\n \"PsqlService: Query parameters provided but parameter binding not yet implemented. \" +\n \"Using direct query execution.\"\n );\n }\n\n const records = await connection.query(query);\n const allRecords = PsqlService.recordsetToRecords(records);\n const rowsFetched = allRecords.length;\n\n // Apply paging if requested\n const pagedData =\n paging?.offset !== undefined || paging?.limit !== undefined\n ? allRecords.slice(\n paging.offset || 0,\n (paging.offset || 0) + (paging.limit || allRecords.length)\n )\n : allRecords;\n\n return {\n data: pagedData,\n paging: {\n count: rowsFetched,\n limit: paging?.limit || 0,\n offset: paging?.offset || 0,\n nextPage:\n paging?.limit && (paging.offset || 0) + paging.limit < rowsFetched\n ? String((paging.offset || 0) + paging.limit)\n : undefined,\n previousPage: paging?.offset\n ? String(Math.max(0, (paging.offset || 0) - (paging.limit || 10)))\n : undefined,\n },\n };\n } catch (error) {\n // If this is an ODBC load error (from getOdbc), re-throw it as-is\n // since it already has a helpful error message\n if (error instanceof Error && error.message.includes(\"ODBC package is required\")) {\n throw error;\n }\n\n // Otherwise, handle as ODBC runtime error\n const errorInfo = error as OdbcErrorResponse;\n logger.error(\"Error fetching data from PSQL\", {\n error: errorInfo.message,\n odbcErrors: errorInfo.odbcErrors,\n query: query.substring(0, 200), // Log first 200 chars of query\n });\n\n throw this.handleOdbcError(errorInfo);\n } finally {\n // CRITICAL: Always close connection, even on error\n if (connection) {\n try {\n await connection.close();\n logger.debug(\"PSQL connection closed successfully\");\n } catch (err) {\n // Don't throw on close errors, just log\n logger.warn(\"Error closing PSQL connection (non-fatal)\", {\n error: err,\n });\n }\n }\n }\n }\n\n /**\n * Transform ODBC result set to array of Record<string, string> instances.\n * IMPORTANT: PSQL CHAR fields are often padded with spaces - we trim them\n */\n public static recordsetToRecords(recordset: any[]): Record<string, string>[] {\n if (!Array.isArray(recordset)) {\n return [];\n }\n\n const data: Record<string, string>[] = recordset.map((row) => {\n const transformedRow: Record<string, string> = {};\n Object.keys(row).forEach((key) => {\n const value = row[key];\n transformedRow[key] =\n value !== null && value !== undefined ? String(value).trim() : \"\";\n });\n return transformedRow;\n });\n\n return data;\n }\n\n /**\n * Handle ODBC errors and provide meaningful messages\n */\n private handleOdbcError(error: OdbcErrorResponse): Error {\n const odbcError = error.odbcErrors?.[0];\n const errorCode = odbcError?.state;\n const message = odbcError?.message || error.message;\n\n switch (errorCode) {\n case \"08S01\":\n return new Error(\n \"PSQL connection failed. Check: \" +\n \"1) PVSW environment variable set to /usr/local/psql/etc/pvsw.ini, \" +\n \"2) Network connectivity to ports 1583/3351, \" +\n \"3) ODBC configuration files in /usr/local/psql/etc/ and /etc/. \" +\n `Original error: ${message}`\n );\n case \"28000\":\n return new Error(\n `PSQL authentication failed. Check username/password. Original error: ${message}`\n );\n case \"42000\":\n return new Error(`PSQL SQL syntax error. Original error: ${message}`);\n case \"42S02\":\n return new Error(\n `PSQL table or view not found. Check table names in query. Original error: ${message}`\n );\n default:\n return new Error(`PSQL error (${errorCode || \"unknown\"}): ${message}`);\n }\n }\n}\n","/**\n * Helper functions for PSQL/Pervasive database operations\n */\n\n/**\n * Formats a date from PSQL YYMMDD format to ISO date string\n * @param psqlDate Date in YYMMDD format (e.g., \"250105\" for Jan 5, 2025)\n * @returns ISO date string (e.g., \"2025-01-05\") or null if invalid\n */\nexport function formatPsqlDate(psqlDate: string): string | null {\n if (!psqlDate || psqlDate.trim().length !== 6 || psqlDate === \"000000\") { \n return null; \n } \n\n try {\n const year = parseInt(psqlDate.substring(0, 2), 10);\n const month = parseInt(psqlDate.substring(2, 4), 10);\n const day = parseInt(psqlDate.substring(4, 6), 10);\n\n // Convert 2-digit year to 4-digit (assuming 2000s)\n const fullYear = year + 2000;\n\n // Basic validation\n if (month < 1 || month > 12 || day < 1 || day > 31) {\n return null;\n }\n\n const date = new Date(fullYear, month - 1, day);\n // Validate that the constructed date matches the input values\n if (\n date.getFullYear() !== fullYear ||\n date.getMonth() !== (month - 1) ||\n date.getDate() !== day\n ) {\n return null;\n }\n return date.toISOString().split(\"T\")[0];\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Formats a time from PSQL HHMM format to HH:MM:SS\n * @param psqlTime Time in HHMM format (e.g., \"1430\" for 2:30 PM)\n * @returns Time string in HH:MM:SS format or null if invalid\n */\nexport function formatPsqlTime(psqlTime: string): string | null {\n if (!psqlTime) {\n return null;\n }\n const trimmed = psqlTime.trim();\n if (trimmed.length !== 4) {\n return null;\n }\n try {\n const hours = parseInt(trimmed.substring(0, 2), 10);\n const minutes = parseInt(trimmed.substring(2, 4), 10);\n if (\n isNaN(hours) ||\n isNaN(minutes) ||\n hours < 0 ||\n hours > 23 ||\n minutes < 0 ||\n minutes > 59\n ) {\n return null;\n }\n // Pad hours and minutes to 2 digits\n const hoursStr = hours.toString().padStart(2, \"0\");\n const minutesStr = minutes.toString().padStart(2, \"0\");\n return `${hoursStr}:${minutesStr}:00`;\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Combines PSQL date and time into ISO datetime string\n * @param psqlDate Date in YYMMDD format\n * @param psqlTime Time in HHMM format\n * @returns ISO datetime string or null if invalid\n */\nexport function combinePsqlDateTime(\n psqlDate: string,\n psqlTime: string\n): string | null {\n const date = formatPsqlDate(psqlDate);\n const time = formatPsqlTime(psqlTime);\n\n if (!date || !time) {\n return null;\n }\n\n return `${date}T${time}`;\n}\n\n/**\n * Helper to check if a PSQL date is \"empty\" (000000 or blank)\n */\nexport function isPsqlDateEmpty(psqlDate: string): boolean {\n return !psqlDate || psqlDate === \"000000\" || psqlDate.trim() === \"\";\n}\n\n/**\n * Clean and trim PSQL CHAR field (removes trailing spaces)\n * PSQL CHAR fields are fixed-width and padded with spaces\n */\nexport function cleanPsqlCharField(value: string | null | undefined): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n return String(value).trim();\n}\n"],"names":["ERPObjType","Operator","config","httpError","UrlBase","path","a","laborTicketResult","DEFAULT_RETRY_ATTEMPTS","query","index","messageToSend","MMEntityProcessor","MMSendLaborTicket"],"mappings":"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"}
1
+ {"version":3,"file":"mm-erp-sdk.js","sources":["../src/types/erp-types.ts","../src/services/erp-api-services/rest/get-query-params.ts","../src/services/mm-api-service/types/receive-types.ts","../src/utils/http-client.ts","../src/services/mm-api-service/token-mgr.ts","../src/services/erp-api-services/errors.ts","../src/services/mm-api-service/mm-api-service.ts","../src/services/mm-api-service/types/send-types.ts","../src/services/mm-api-service/company-info.ts","../src/utils/uniqueRows.ts","../src/utils/removeExtraneousFields.ts","../src/utils/removeIdFieldFromPayload.ts","../src/utils/trimObjectValues.ts","../src/utils/cleanup-numbers.ts","../src/utils/data-transformation.ts","../src/utils/timezone.ts","../src/utils/time-utils.ts","../src/services/caching-service/record-tracking-manager.ts","../src/services/caching-service/batch-cache-manager.ts","../src/utils/mm-labor-ticket-helpers.ts","../src/utils/standard-process-drivers/labor-ticket-erp-synchronizer.ts","../src/services/mm-api-service/types/entity-transformer.ts","../src/utils/standard-process-drivers/error-processor.ts","../src/utils/standard-process-drivers/mm-entity-processor.ts","../src/utils/standard-process-drivers/standard-process-drivers.ts","../src/services/erp-api-services/rest/rest-api-service.ts","../src/utils/erp-type-from-entity.ts","../src/utils/error-utils.ts","../src/utils/error-formatter.ts","../src/utils/connector-log/mm-connector-logger.ts","../src/utils/connector-log/log-deduper.ts","../src/utils/application-initializer.ts","../src/services/erp-api-services/graphql/graphql-service.ts","../src/services/erp-api-services/oauth-client.ts","../src/services/nats-service/nats-service.ts","../src/services/data-sync-service/nats-labor-ticket-listener.ts","../src/services/data-sync-service/data-sync-service.ts","../src/services/sql-server-erp-service/internal/sql-server-config.ts","../src/services/sql-server-erp-service/sql-server-service.ts","../src/services/sql-server-erp-service/internal/sql-transaction-manager.ts","../src/services/sql-server-erp-service/internal/sql-labor-ticket-operations.ts","../src/services/sql-server-erp-service/sql-server-helpers.ts","../src/services/psql-erp-service/psql-service.ts","../src/services/psql-erp-service/psql-helpers.ts"],"sourcesContent":["export enum ERPObjType {\n RESOURCES,\n PARTS,\n PART_OPERATION,\n WORK_ORDERS,\n WORK_ORDER_OPERATIONS,\n PERSONS,\n REASONS,\n LABOR_TICKETS,\n // TO DO: TIMESHEETS support\n}\n\nexport interface ERPPagination {\n count: number;\n limit: number;\n nextPage: string | undefined;\n previousPage: string | undefined;\n offset?: number;\n}\n\nexport interface ERPResponse {\n data: Record<string, string>[];\n paging: ERPPagination;\n}\n\nexport type ERPObject = Record<string, string>;\n","/**\n * Types and utilities for handling GET query parameters and responses\n */\n\nexport enum Operator {\n eq = \"eq\",\n lt = \"lt\",\n lte = \"lte\",\n gt = \"gt\",\n gte = \"gte\",\n}\n\nexport type LogicalCondition = {\n field: string;\n value: string;\n operator: Operator;\n};\n\nexport type InCondition = { field: string; value: Array<string> };\n\n/**\n * QueryParams is specifically used for the get API, defining pagination and filters\n */\nexport interface QueryParams {\n limit?: number;\n nextPage?: string;\n previousPage?: string;\n filter?: {\n in?: InCondition[];\n and?: LogicalCondition[];\n or?: LogicalCondition[];\n };\n}\n\nexport interface Pagination {\n count: number;\n limit: number;\n nextPage?: string;\n previousPage?: string;\n}\n\nexport interface PaginatedAPIResponse<T> {\n data: T[];\n paging: Pagination;\n}\n\n/**\n * Builds a logical condition string from an array of conditions\n * @param conditions Array of logical conditions\n * @param operator The logical operator to use ('and' or 'or')\n * @returns A string representation of the logical conditions\n */\nexport const buildLogicalCondition = (\n conditions: LogicalCondition[],\n operator: \"and\" | \"or\"\n): string => {\n return conditions\n .map(\n (condition) =>\n `${condition.field} ${condition.operator} '${condition.value}'`\n )\n .join(` ${operator} `);\n};\n","//#region MMReceiveLaborTicket\n/**\n * Represents a labor ticket as received from the MM API /erp/v1/labor-tickets/export/updates\n */\nexport class MMReceiveLaborTicket {\n constructor(data: Partial<MMReceiveLaborTicket>) {\n Object.assign(this, data);\n }\n\n // Returns the closed date if it is a valid date, otherwise returns null\n get workOrderOperationClosedDate(): string | null {\n const closedDate = this.workOrderOperation?.closedDate;\n return typeof closedDate === \"string\" &&\n closedDate !== \"\" &&\n !isNaN(Date.parse(closedDate))\n ? closedDate\n : null;\n }\n\n set workOrderOperationClosedDate(value: string | null) {\n if (this.workOrderOperation) {\n this.workOrderOperation.closedDate = value;\n }\n }\n\n // Returns 1 if the work order operation is closed (has a valid closed date), 0 otherwise\n get workOrderOpIsClosed(): number {\n return this.workOrderOperationClosedDate !== null ? 1 : 0;\n }\n\n /*Descriptions from machinemetrics.atlassian.net/wiki/spaces/DEVE/pages/842661901/MM+ERP+Schema+Descriptions*/\n\n badParts!: number; // The number of parts produced that do not meet quality standards\n clockIn!: string | null; // The timestamp when the work on this ticket started\n clockOut!: string | null; // The timestamp when the work on this ticket ended\n comment!: string | null;\n createdAt!: string | null;\n goodParts!: number; // The number of parts produced that meet quality standards\n laborTicketId!: string; // The unique identifier for the labor ticket in the ERP\n laborTicketRef!: string; // The unique identifier for the labor ticket in MM\n lot!: string; // Refers to a material batch. If not available in the ERP, default to an empty string\n personId!: string; // The person who did the work\n reasons?: MMReceiveLaborTicketReason[];\n resourceId!: string; // The physical resource that did the work\n sequenceNumber!: number; // Which sequence this operation is for in the order, associates back to a part_operation with the part_number from the work_order, and sequence_number from the op\n split!: string; // Batch number if order split for staggered delivery. If not available in the ERP, default to an empty string\n state!: string; // The current state of the labor ticket (e.g., \"CLOSED/OPEN\")\n type!: string; // If the labor is production, setup, or indirect\n syncedAt!: string | null;\n updatedAt!: string | null;\n workOrderId!: string; // The order this labor ticket corresponds to\n workOrderOperation?: MMReceiveLaborTicketWorkOrderOperation;\n sub!: string; // Batch number for parts that had to be rerun. If not available in the ERP, default to an empty string\n transactionDate!: string | null;\n}\n\n/**\n * Represents a reason in the MMApiReceiveLaborTicket object as received from MM\n */\nexport interface MMReceiveLaborTicketReason {\n reason: {\n annotationTypeRef: string | null;\n category: string; // Parent group if applicable (optional)\n code: string; // The functional name of the label\n companyId: string;\n createdAt: string;\n description: string;\n entityType: string; // Whether it represents machine downtime or rejected parts context (downtime/quality)\n reasonId: string; // Unique identifier in the ERP\n reasonRef: number;\n rejectReasonRef: number;\n updatedAt: string | null;\n };\n}\n\n/**\n * Represents a work order operation in the MMApiReceiveLaborTicket object, incomplete when\n * pulling updated labor tickets from MM\n */\nexport interface MMReceiveLaborTicketWorkOrderOperation {\n closedDate: string | null; // non-null and a valid date if the work order operation is closed\n}\n//#endregion MMReceiveLaborTicket\n\n//#region MMReceiveTimesheet\n\n// MLW TODO\n\n// #endregion MMReceiveTimesheet\n","import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from \"axios\";\nimport logger from \"../services/reporting-service/logger\";\n\nexport class HTTPError extends Error {\n constructor(\n message: string,\n public status: number,\n public code?: string,\n public data?: unknown\n ) {\n super(message);\n this.name = \"HTTPError\";\n }\n}\n\nexport type HTTPMethod = \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\nexport interface HTTPRequestConfig {\n method: HTTPMethod;\n url: string;\n headers?: Record<string, string>;\n data?: unknown;\n params?: Record<string, string>;\n}\n\nexport interface HTTPResponse<T = unknown> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\nexport interface HTTPClient {\n request<T = unknown>(config: HTTPRequestConfig): Promise<HTTPResponse<T>>;\n handleError(error: unknown): HTTPError;\n destroy(): Promise<void>;\n}\n\nexport interface HTTPClientConfig {\n baseUrl: string;\n retryAttempts: number;\n}\n\n/**\n * This is a factory class for creating HTTPClient instances, at least for now\n * just a wrapper around AxiosClient\n */\nexport class HTTPClientFactory {\n static getInstance(config: HTTPClientConfig): HTTPClient {\n return new AxiosClient(config.baseUrl, config.retryAttempts);\n }\n}\n\nclass AxiosClient implements HTTPClient {\n private client: AxiosInstance | null = null;\n private retryAttempts: number;\n private isDestroyed: boolean = false;\n private inFlightControllers: Set<AbortController> = new Set();\n private pendingTimeouts: Set<ReturnType<typeof setTimeout>> = new Set();\n private pendingSleepResolvers: Set<() => void> = new Set();\n\n /**\n * Note regarding baseURL, from https://github.com/axios/axios\n * `baseURL` will be prepended to `url` unless `url` is absolute and option `allowAbsoluteUrls` is set to true (the default).\n * It can be convenient to set `baseURL` for an instance of axios to pass relative URLs to methods of that instance.\n */\n constructor(baseUrl: string, retryAttempts: number) {\n const timeout = parseInt(process.env.MM_API_TIMEOUT || \"30000\");\n this.client = axios.create({\n baseURL: baseUrl,\n timeout: timeout,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n this.retryAttempts = retryAttempts;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n if (this.isDestroyed) {\n resolve();\n return;\n }\n const timeout = setTimeout(() => {\n this.pendingTimeouts.delete(timeout);\n this.pendingSleepResolvers.delete(resolve);\n resolve();\n }, ms);\n this.pendingTimeouts.add(timeout);\n this.pendingSleepResolvers.add(resolve);\n });\n }\n\n async request<T = unknown>(\n config: HTTPRequestConfig\n ): Promise<HTTPResponse<T>> {\n if (this.isDestroyed || !this.client) {\n throw new HTTPError(\"HTTP client has been destroyed\", 500);\n }\n\n const controller = new AbortController();\n this.inFlightControllers.add(controller);\n\n const axiosConfig: AxiosRequestConfig = {\n method: config.method,\n url: config.url,\n headers: config.headers,\n data: config.data,\n params: config.params,\n signal: controller.signal,\n };\n\n logger.info(\"HTTP request starting\", { \n url: config.url, \n method: config.method, \n baseURL: this.client.defaults.baseURL,\n retryAttempts: this.retryAttempts \n });\n \n // Extra console.log for debugging\n console.log(\"=== FULL URL DEBUG ===\");\n console.log(\"baseURL:\", this.client.defaults.baseURL);\n console.log(\"relative url:\", config.url);\n console.log(\"full constructed URL:\", (this.client.defaults.baseURL || \"\") + config.url);\n console.log(\"method:\", config.method);\n\n let lastError: unknown;\n try {\n for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {\n try {\n logger.info(`HTTP request attempt ${attempt + 1}/${this.retryAttempts + 1}`);\n const response = await this.client.request<T>(axiosConfig);\n logger.info(\"HTTP request succeeded\", { status: response.status });\n return {\n data: response.data,\n status: response.status,\n headers: response.headers as Record<string, string>,\n };\n } catch (error) {\n lastError = error;\n\n const isAxiosErr = error instanceof AxiosError;\n const code = isAxiosErr ? error.code : undefined;\n const status = isAxiosErr ? error.response?.status : undefined;\n const errorConstructor = error instanceof Error ? error.constructor.name : undefined;\n const message = error instanceof Error ? error.message : String(error);\n \n logger.info(`HTTP request attempt ${attempt + 1} failed`, {\n errorType: typeof error,\n errorConstructor,\n isAxiosError: isAxiosErr,\n message,\n code,\n status,\n });\n\n // Don't retry on 4xx errors (client errors)\n if (\n error instanceof AxiosError &&\n error.response?.status &&\n error.response.status >= 400 &&\n error.response.status < 500\n ) {\n logger.info(\"Not retrying due to 4xx client error\");\n break;\n }\n\n // Don't retry canceled/aborted requests\n if (error instanceof AxiosError && error.code === \"ERR_CANCELED\") {\n break;\n }\n\n // If this was the last attempt, don't wait\n if (attempt < this.retryAttempts) {\n const waitTime = Math.pow(2, attempt) * 1000;\n logger.info(`Waiting ${waitTime}ms before retry`);\n await this.sleep(waitTime);\n if (this.isDestroyed) {\n throw new HTTPError(\"HTTP client has been destroyed\", 500);\n }\n }\n }\n }\n } finally {\n this.inFlightControllers.delete(controller);\n }\n logger.info(\"HTTP request failed after all retries, throwing error\");\n throw this.handleError(lastError, config);\n }\n\n handleError(error: unknown, requestConfig?: HTTPRequestConfig): HTTPError {\n if (error instanceof AxiosError) {\n // Build a more descriptive error message that includes the URL and method\n const baseUrl = this.client?.defaults.baseURL || \"\";\n const fullUrl = requestConfig\n ? `${baseUrl}${requestConfig.url}`\n : \"Unknown URL\";\n const method = requestConfig?.method || \"Unknown method\";\n\n const errorMessage = `${error.message} (${method} ${fullUrl})`;\n\n return new HTTPError(\n errorMessage,\n error.response?.status || 500,\n error.code,\n error.response?.data\n );\n }\n return new HTTPError(\n error instanceof Error ? error.message : \"Unknown error occurred\",\n 500\n );\n }\n\n async destroy(): Promise<void> {\n if (this.isDestroyed) return;\n this.isDestroyed = true;\n\n // Abort any in-flight requests\n for (const c of this.inFlightControllers) {\n try { c.abort(); } catch { /* ignore: abort may throw in some environments */ }\n }\n this.inFlightControllers.clear();\n\n // Cancel any pending retry waits\n for (const t of this.pendingTimeouts) {\n clearTimeout(t);\n }\n this.pendingTimeouts.clear();\n for (const resolve of this.pendingSleepResolvers) {\n try { resolve(); } catch { /* ignore: resolver threw; cleanup proceeds */ }\n }\n this.pendingSleepResolvers.clear();\n\n // Drop axios instance reference\n this.client = null;\n }\n}\n","import {\n getCachedMMToken,\n setCachedMMToken,\n} from \"../../utils/local-data-store/jobs-shared-data\";\nimport { HTTPClient, HTTPClientFactory } from \"../../utils/http-client\";\nimport { CoreConfiguration } from \"../data-sync-service/configuration-manager\";\nimport { logger } from \"../reporting-service\";\n\n/**\n * Manages the MM API token with caching and expiration checking\n * Tokens are cached across jobs and job instances in local-data-store/jobs-shared-data.ts\n * @jest-environment node\n */\nexport class MMTokenManager {\n private token: string;\n private tokenExpiration: Date | null;\n private baseUrl: string;\n private api: HTTPClient;\n\n constructor() {\n const cached = getCachedMMToken();\n this.token = cached?.token || \"\";\n this.tokenExpiration = cached?.expiration\n ? new Date(cached.expiration)\n : null;\n const config = CoreConfiguration.inst();\n console.log(\"=== MMTOKEN MANAGER DEBUG ===\");\n console.log(\"config.mmApiBaseUrl:\", config.mmApiBaseUrl);\n console.log(\"config object keys:\", Object.keys(config));\n this.baseUrl = config.mmApiBaseUrl;\n console.log(\"this.baseUrl set to:\", this.baseUrl);\n console.log(\"=== END MMTOKEN MANAGER DEBUG ===\");\n this.api = HTTPClientFactory.getInstance({\n baseUrl: this.baseUrl,\n retryAttempts: config.mmApiRetryAttempts,\n });\n }\n\n /**\n * Checks if the current token is expired or will expire within the next minute\n * @returns boolean indicating if token needs refresh\n */\n private isTokenExpired(): boolean {\n if (!this.tokenExpiration) return true;\n\n // Add 1 minute buffer to ensure we refresh before actual expiration\n const bufferTimeMS = 60_000;\n const isExpired =\n this.tokenExpiration.getTime() - bufferTimeMS <= Date.now();\n if (isExpired) {\n logger.info(\"isTokenExpired: the api token from MM has expired\");\n }\n return isExpired;\n }\n\n /**\n * Safely extracts expiration time from JWT token\n * @param token JWT token string\n * @returns Date object representing token expiration, or null if cannot be determined\n */\n private getTokenExpiration(token: string): Date | null {\n try {\n // Split token into parts\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n console.warn(\"Invalid JWT format: token does not have 3 parts\");\n return null;\n }\n\n // Decode payload (second part)\n const payload = Buffer.from(parts[1], \"base64\").toString(\"utf-8\");\n const decodedPayload = JSON.parse(payload);\n\n // Check if exp claim exists\n if (typeof decodedPayload.exp !== \"number\") {\n console.warn(\"JWT does not contain expiration time (exp claim)\");\n return null;\n }\n\n // Convert Unix timestamp (seconds) to Date\n return new Date(decodedPayload.exp * 1000);\n } catch (error) {\n console.warn(\"Error parsing JWT token:\", error);\n return null;\n }\n }\n\n /**\n * Gets a valid token, either from cache or by requesting a new one\n * @returns Promise resolving to a valid token string\n */\n public async getToken(): Promise<string> {\n if (this.token && !this.isTokenExpired()) {\n return this.token;\n }\n\n const config = CoreConfiguration.inst();\n const serviceToken = config.mmApiAuthToken;\n const tokenResponse = await this.api.request<string>({\n url: \"/user-token\",\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${serviceToken}`,\n },\n });\n\n this.token = tokenResponse.data;\n this.tokenExpiration = this.getTokenExpiration(this.token);\n\n // Cache the new token and its expiration\n setCachedMMToken({\n token: this.token,\n expiration: this.tokenExpiration?.getTime() || null,\n });\n\n logger.info(\"getToken: the api token from MM has been refreshed\");\n return this.token;\n }\n\n /**\n * Invalidates the current token, forcing a fresh fetch on next getToken() call\n */\n public invalidateToken(): void {\n this.token = \"\";\n this.tokenExpiration = null;\n setCachedMMToken({ token: \"\", expiration: null });\n }\n}\n","import { AxiosError } from \"axios\";\nimport logger from \"../reporting-service/logger\";\nimport { GraphQLServerError } from \"./graphql/types\";\nimport { HTTPError } from \"../../utils/http-client\";\n\n/**\n * Represents GraphQL specific errors (validation, missing fields, etc.)\n */\nexport class GraphQLError extends Error {\n constructor(\n message: string,\n public errors: GraphQLServerError[]\n ) {\n super(message);\n this.name = \"GraphQLError\";\n }\n}\n\n/**\n * Centralized error handling for API requests.\n * Transforms various error types into standardized HTTPError or GraphQLError.\n */\nexport class ErrorHandler {\n /**\n * Transforms any error into a standardized HTTPError or GraphQLError and throws it.\n * Never returns - always throws an error.\n *\n * @throws {HTTPError|GraphQLError} Standardized error with appropriate details\n */\n static handle(error: unknown): never {\n // Handle HTTPError (already processed by http-client)\n if (error instanceof HTTPError) {\n // For GraphQL responses with errors in the data\n if (\n error.data &&\n typeof error.data === \"object\" &&\n \"errors\" in error.data\n ) {\n const graphqlError = new GraphQLError(\n \"GraphQL Error\",\n (error.data as { errors: GraphQLServerError[] }).errors\n );\n\n // Log the error\n logger.error(\"Error>>GraphQL\", {\n message: graphqlError.message,\n errors: graphqlError.errors,\n });\n\n throw graphqlError;\n }\n\n // Log HTTP error with generic response data\n logger.error(\"Error>>HTTP\", {\n status: error.status,\n message: error.message,\n code: error.code,\n data: error.data,\n });\n\n throw error; // Re-throw the original HTTPError\n }\n\n // Handle raw AxiosError (direct usage, not through http-client)\n if (error instanceof AxiosError) {\n // For GraphQL responses with errors\n if (error.response?.data?.errors) {\n const graphqlError = new GraphQLError(\n \"GraphQL Error\",\n error.response.data.errors as GraphQLServerError[]\n );\n\n // Log the error\n logger.error(\"Error>>GraphQL\", {\n message: graphqlError.message,\n errors: graphqlError.errors,\n });\n\n throw graphqlError;\n }\n\n // For HTTP errors - create HTTPError and log with response data\n const httpError = new HTTPError(\n error.message,\n error.response?.status || 500,\n error.code,\n error.response?.data\n );\n\n // Log HTTP error with generic response data\n logger.error(\"Error>>HTTP\", {\n status: httpError.status,\n message: httpError.message,\n code: httpError.code,\n data: httpError.data,\n });\n\n throw httpError;\n }\n\n // For other errors\n const httpError = new HTTPError(\n error instanceof Error ? error.message : \"An unknown error occurred\",\n 500\n );\n\n // Log the error\n logger.error(\"Error>>Unknown\", {\n message: httpError.message,\n status: httpError.status,\n });\n\n throw httpError;\n }\n}\n","import logger from \"../reporting-service/logger\";\nimport { ERPObjType } from \"../../types/erp-types\";\nimport { MMReceiveLaborTicket } from \"./types/receive-types\";\nimport { MMTokenManager } from \"./token-mgr\";\nimport * as MM_SEND_TYPES from \"./types/send-types\";\nimport {\n HTTPClient,\n HTTPMethod,\n HTTPRequestConfig,\n HTTPClientFactory,\n} from \"../../utils/http-client\";\nimport { Checkpoint } from \"./types/checkpoint\";\nimport { CoreConfiguration } from \"../data-sync-service/configuration-manager\";\nimport { ErrorHandler } from \"../erp-api-services/errors\";\nimport {\n MMApiBaseResponse,\n MMGraphQLResourceResponse,\n MMMachineGroupsResponse,\n} from \"./types/mm-response-interfaces\";\n\nconst MAPPING = \"MAPPING\";\nconst CHECKPOINTS = \"CHECKPOINTS\";\nconst MACHINE_GROUPS = \"MACHINE_GROUPS\";\nconst GRAPHQL = \"GRAPHQL\";\n\nenum UrlBase {\n ErpApiSvcBase = \"ErpApiSvcBase\",\n ApiBase = \"ApiBase\",\n}\n\n/**\n * Class to manage interactions with the MM Mapping Service\n * Integrates an MM Token Manager to handle the API token.\n */\nexport class MMApiClient {\n tokenMgr: MMTokenManager;\n api: HTTPClient;\n\n private baseUrls: Record<UrlBase, string>;\n\n resourceURLs = {\n [ERPObjType.RESOURCES]: \"/erp/v1/resources\",\n [ERPObjType.PARTS]: \"/erp/v1/parts\",\n [ERPObjType.PART_OPERATION]: \"/erp/v1/part-operations\",\n [ERPObjType.WORK_ORDERS]: \"/erp/v1/production-orders\",\n [ERPObjType.WORK_ORDER_OPERATIONS]: \"/erp/v1/production-order-operations\",\n [ERPObjType.PERSONS]: \"/erp/v1/persons\",\n [ERPObjType.REASONS]: \"/erp/v1/reasons\",\n [ERPObjType.LABOR_TICKETS]: \"/erp/v1/labor-tickets\",\n [MAPPING]: \"/erp/v1/mapping\",\n [CHECKPOINTS]: \"/erp/v1/checkpoints\",\n [MACHINE_GROUPS]: \"/machine-groups\",\n [GRAPHQL]: \"/proxy/graphql\",\n };\n\n constructor() {\n this.tokenMgr = new MMTokenManager();\n this.baseUrls = {\n [UrlBase.ErpApiSvcBase]: CoreConfiguration.inst().mmERPSvcApiBaseUrl,\n [UrlBase.ApiBase]: CoreConfiguration.inst().mmApiBaseUrl,\n };\n this.api = HTTPClientFactory.getInstance({\n baseUrl: '',\n retryAttempts: CoreConfiguration.inst().mmApiRetryAttempts,\n });\n }\n\n //#region Private Methods\n private async executeWithAuthRetry<T>(\n operation: (token: string) => Promise<T>,\n options: { token?: string } = {}\n ): Promise<T> {\n let token = options.token || (await this.tokenMgr.getToken());\n\n try {\n return await operation(token);\n } catch (error: unknown) {\n // Type guard to check if error has status property\n const hasStatus = (err: unknown): err is { status: number } => {\n return typeof err === \"object\" && err !== null && \"status\" in err;\n };\n\n // Check if this looks like an auth error - retry on auth status codes or JWT expiration errors\n const isAuthError =\n hasStatus(error) && (error.status === 401 || error.status === 403) ||\n (hasStatus(error) && error.status === 500 && \n typeof (error as any).data?.error === 'string' && \n (error as any).data.error.includes('JWT'));\n\n if (isAuthError && !options.token) {\n // Only retry if we used cached token\n logger.info(\"Retrying request with fresh token due to auth error\");\n this.tokenMgr.invalidateToken();\n token = await this.tokenMgr.getToken();\n return await operation(token); // Single retry\n }\n\n throw error; // Re-throw if not auth error or already retried\n }\n }\n\n /**\n * Fetch data from the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n * Flags:\n */\n private async getData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n queryParams: { [key: string]: string } = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...queryParams,\n ...(baseUrl === UrlBase.ErpApiSvcBase ? { raw: \"true\" } : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const result = await this.api.request<object>({\n url: fullUrl,\n method: \"GET\" as HTTPMethod,\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n });\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Send data to the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n * Flags:\n * - 'raw' = true and 'dedupe' = true will be set for MM's ERP API url\n */\n private async postData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n data: object = {},\n queryParams: { [key: string]: string } = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...queryParams,\n ...(baseUrl === UrlBase.ErpApiSvcBase\n ? { dedupe: \"true\", raw: \"true\" }\n : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const request: HTTPRequestConfig = {\n url: fullUrl,\n method: \"POST\",\n data: JSON.stringify(data),\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n };\n\n const result = await this.api.request<object>(request);\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Delete data from the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n */\n private async deleteData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n data: object = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...(baseUrl === UrlBase.ErpApiSvcBase ? { raw: \"true\" } : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const result = await this.api.request<object>({\n url: fullUrl,\n method: \"DELETE\",\n data,\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n });\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Patch data to the MM API, with an auth retry mechanism\n * Auth Retry Level (executeWithAuthRetry)\n * - 401 (Unauthorized) and 403 (Forbidden) get special treatment\n * - These trigger a token refresh and one retry attempt\n * - If the retry still fails, the error is re-thrown\n *\n * HTTP Status Codes behavior:\n * - 2xx - Success, returns normally\n * - 4xx - Client errors, throws immediately\n * - 5xx - Server errors, retries then throws\n *\n */\n private async patchData<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(\n path: string,\n data: object = {},\n options: {\n baseUrl?: UrlBase;\n token?: string;\n headers?: Record<string, string>;\n } = {}\n ): Promise<T & MMApiBaseResponse> {\n try {\n return await this.executeWithAuthRetry(async (token) => {\n const baseUrl = options.baseUrl || UrlBase.ErpApiSvcBase;\n const params = new URLSearchParams({\n ...(baseUrl === UrlBase.ErpApiSvcBase ? { raw: \"true\" } : {}),\n });\n const url = params.size > 0 ? `${path}?${params.toString()}` : path;\n const fullUrl = this.baseUrls[baseUrl] + url;\n const request: HTTPRequestConfig = {\n url: fullUrl,\n method: \"PATCH\",\n data,\n headers: {\n Authorization: `Bearer ${token}`,\n ...options.headers,\n },\n };\n\n const result = await this.api.request<object>(request);\n\n // Always include HTTP status in the response\n return {\n ...(result.data as T),\n httpStatus: result.status,\n } as T & MMApiBaseResponse;\n }, options);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n private async graphqlRequest(\n operationName: string,\n query: string\n ): Promise<MMApiBaseResponse> {\n try {\n return await this.postData(\n this.resourceURLs[GRAPHQL],\n { operationName, query },\n {},\n {\n baseUrl: UrlBase.ApiBase,\n token: CoreConfiguration.inst().mmApiAuthToken,\n headers: { \"Content-Type\": \"application/json\" },\n }\n );\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Fetch a checkpoint for a specific system, table, and checkpoint type\n * @param checkpoint The checkpoint to fetch\n * @returns Promise with the checkpoint data\n */\n private async fetchCheckpoint(\n checkpoint: Checkpoint\n ): Promise<MMApiBaseResponse> {\n const { system, table, checkpointType } = checkpoint;\n return this.getData(this.resourceURLs[CHECKPOINTS], {\n system,\n table,\n checkpointType,\n });\n }\n\n //#endregion private methods\n\n //#region Public Methods\n /**\n * Send resources to the MM API\n * @param resources Array of resources to send\n * @returns Promise with the API response\n */\n async sendResourcesToMM(\n resources: MM_SEND_TYPES.MMSendResource[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.RESOURCES], {\n data: resources.map((resource) => resource.toRESTApiObject()),\n });\n }\n\n /**\n * Send parts to the MM API\n * @param parts Array of parts to send\n * @returns Promise with the API response\n */\n async sendPartsToMM(\n parts: MM_SEND_TYPES.MMSendPart[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.PARTS], {\n data: parts.map((part) => part.toRESTApiObject()),\n });\n }\n\n /**\n * Send part operations to the MM API\n * @param operations Array of part operations to send\n * @returns Promise with the API response\n */\n async sendPartOperationsToMM(\n operations: MM_SEND_TYPES.MMSendPartOperation[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.PART_OPERATION], {\n data: operations.map((operation) => operation.toRESTApiObject()),\n });\n }\n\n /**\n * Send work orders to the MM API\n * @param workOrders Array of work orders to send\n * @returns Promise with the API response\n */\n async sendWorkOrdersToMM(\n workOrders: MM_SEND_TYPES.MMSendWorkOrder[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.WORK_ORDERS], {\n data: workOrders.map((workOrder) => workOrder.toRESTApiObject()),\n });\n }\n\n /**\n * Send work order operations to the MM API\n * @param workOrderOperations Array of work order operations to send\n * @returns Promise with the API response\n */\n async sendWorkOrderOperationsToMM(\n workOrderOperations: MM_SEND_TYPES.MMSendWorkOrderOperation[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(\n this.resourceURLs[ERPObjType.WORK_ORDER_OPERATIONS],\n {\n data: workOrderOperations.map((operation) =>\n operation.toRESTApiObject()\n ),\n }\n );\n }\n\n /**\n * Send persons to the MM API\n * @param persons Array of persons to send\n * @returns Promise with the API response\n */\n async sendPersonsToMM(\n persons: MM_SEND_TYPES.MMSendPerson[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.PERSONS], {\n data: persons.map((person) => person.toRESTApiObject()),\n });\n }\n\n /**\n * Send reasons to the MM API\n * @param reasons Array of reasons to send\n * @returns Promise with the API response\n */\n async sendReasonsToMM(\n reasons: MM_SEND_TYPES.MMSendReason[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.REASONS], {\n data: reasons.map((reason) => reason.toRESTApiObject()),\n });\n }\n\n /**\n * Send labor tickets to the MM API\n * @param laborTickets Array of labor tickets to send\n * @returns Promise with the API response\n */\n async sendLaborTicketsToMM(\n laborTickets: MM_SEND_TYPES.MMSendLaborTicket[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[ERPObjType.LABOR_TICKETS], {\n data: laborTickets.map((ticket) => ticket.toRESTApiObject()),\n });\n }\n\n /**\n * Delete labor tickets from the MM API\n * @param laborTickets Array of labor ticket records to delete\n * @returns Promise with the API response\n */\n async deleteLaborTickets(\n laborTickets: Record<string, string>[]\n ): Promise<MMApiBaseResponse> {\n logger.debug(\"deleteLaborTickets:\", { laborTickets });\n return await this.deleteData(this.resourceURLs[ERPObjType.LABOR_TICKETS], {\n laborTickets,\n });\n }\n\n /**\n * Fetch transformed labor tickets from the MM API\n * @returns Promise with the transformed labor tickets\n */\n async fetchTransformedLaborTickets(): Promise<MMApiBaseResponse> {\n return await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/export`\n );\n }\n\n /**\n * Fetch labor ticket updates from the MM API\n * @param query Query parameters for filtering updates\n * @returns Promise with an array of labor ticket updates\n */\n async fetchLaborTicketUpdates(\n query: Record<string, string>\n ): Promise<MMReceiveLaborTicket[]> {\n const updates = (await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/export/updates`,\n query\n )) as { data: MMReceiveLaborTicket[] };\n return updates.data.map((ticket) => new MMReceiveLaborTicket(ticket));\n }\n\n /**\n * Fetch a single labor ticket by reference from the MM API\n * @param laborTicketRef The labor ticket reference to fetch\n * @returns Promise with the labor ticket data\n */\n async fetchLaborTicketByRef(\n laborTicketRef: string\n ): Promise<MMReceiveLaborTicket> {\n const response = (await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/${laborTicketRef}`\n )) as { data: MMReceiveLaborTicket };\n return new MMReceiveLaborTicket(response.data);\n }\n\n /**\n * Fetch a checkpoint for a specific system, table, and checkpoint type\n * @param checkpoint The checkpoint to fetch\n * @returns Promise with the checkpoint data\n */\n async getCheckpoint(checkpoint: Checkpoint): Promise<MMApiBaseResponse> {\n return await this.fetchCheckpoint(checkpoint);\n }\n\n /**\n * Save a checkpoint to the MM API\n * @param checkpoint The checkpoint to save\n * @returns Promise with the API response\n */\n async saveCheckpoint(checkpoint: Checkpoint): Promise<MMApiBaseResponse> {\n return await this.postData(this.resourceURLs[CHECKPOINTS], checkpoint);\n }\n\n /**\n * Initialize a checkpoint for a specific system, table, and checkpoint type\n * @param checkpoint The checkpoint to initialize\n * @returns Promise with the checkpoint data\n */\n async initializeCheckpoint(checkpoint: Checkpoint): Promise<void> {\n const currentCheckpoint = await this.fetchCheckpoint(checkpoint);\n\n logger.info(\"currentCheckpoint:\", { currentCheckpoint });\n logger.info(\"currentCheckpoint keys:\", {\n keys: Object.keys(currentCheckpoint || {}),\n });\n\n // Check if we have a meaningful checkpoint response\n // The API returns either:\n // - Empty: just { httpStatus: 200 }\n // - With data: { timestamp: \"2025-08-08T18:53:14.295Z\", httpStatus: 200 }\n const checkpointResponse = currentCheckpoint as any;\n const hasCheckpointData =\n checkpointResponse && checkpointResponse.timestamp;\n\n if (!hasCheckpointData) {\n logger.info(\"No checkpoint data found, initializing checkpoint\");\n const saveCheckpoint = await this.saveCheckpoint(checkpoint);\n logger.info(\"saveCheckpoint:\", { saveCheckpoint });\n } else {\n logger.info(\"Existing checkpoint found:\", {\n timestamp: checkpointResponse.timestamp,\n });\n }\n }\n\n /**\n * Send data to MM API based on the object type\n * @param type The type of ERP object to send\n * @param objects Array of objects to send\n * @returns Promise with the API response including affected rows count\n */\n public async sendDataToMMByType(\n type: ERPObjType,\n objects: MM_SEND_TYPES.IToRESTApiObject[]\n ): Promise<MMApiBaseResponse> {\n switch (type) {\n case ERPObjType.PERSONS:\n return await this.sendPersonsToMM(\n objects as MM_SEND_TYPES.MMSendPerson[]\n );\n case ERPObjType.RESOURCES:\n return await this.sendResourcesToMM(\n objects as MM_SEND_TYPES.MMSendResource[]\n );\n case ERPObjType.PARTS:\n return await this.sendPartsToMM(objects as MM_SEND_TYPES.MMSendPart[]);\n case ERPObjType.PART_OPERATION:\n return await this.sendPartOperationsToMM(\n objects as MM_SEND_TYPES.MMSendPartOperation[]\n );\n case ERPObjType.WORK_ORDERS:\n return await this.sendWorkOrdersToMM(\n objects as MM_SEND_TYPES.MMSendWorkOrder[]\n );\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return await this.sendWorkOrderOperationsToMM(\n objects as MM_SEND_TYPES.MMSendWorkOrderOperation[]\n );\n case ERPObjType.REASONS:\n return await this.sendReasonsToMM(\n objects as MM_SEND_TYPES.MMSendReason[]\n );\n case ERPObjType.LABOR_TICKETS:\n return await this.sendLaborTicketsToMM(\n objects as MM_SEND_TYPES.MMSendLaborTicket[]\n );\n default:\n throw new Error(`Unsupported type: ${ERPObjType[type]}`);\n }\n }\n\n /**\n * Update the labor ticket ERP ID by laborTicketRef\n * @param laborTicketRef The MM API labor ticket unique ID\n * @param erpObjectId The ERP labor ticket ID to update\n * @returns The updated labor ticket\n */\n async updateLaborTicketIdByRef(\n laborTicketRef: string,\n laborTicketId: string\n ): Promise<MMApiBaseResponse> {\n return await this.patchData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/${laborTicketRef}`,\n { laborTicketId }\n );\n }\n\n /**\n * Add failed labor ticket refs to the MM API\n * @param failedTicketRefs The failed labor ticket references\n * @returns The response from the MM API\n */\n async addFailedLaborTicketRefs(\n system: string,\n failedTicketRefs: string[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/failed`,\n { failedTicketRefs },\n { system }\n );\n }\n\n async fetchFailedLaborTickets(\n system: string\n ): Promise<MMReceiveLaborTicket[]> {\n const failedTickets = (await this.getData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/failed`,\n { system }\n )) as { erpLaborTickets: MMReceiveLaborTicket[] };\n return failedTickets.erpLaborTickets.map(\n (ticket) => new MMReceiveLaborTicket(ticket)\n );\n }\n\n /**\n * Send connector logs to the MM API\n * @param logEntry Single log entry to send\n * @returns Promise with the API response\n */\n async sendConnectorLog(logEntry: { source: string; level: string; message: string }): Promise<MMApiBaseResponse> {\n return await this.postData(\n '/connector/logs',\n logEntry,\n {},\n { baseUrl: UrlBase.ApiBase }\n );\n }\n\n /**\n * Send bulk connector logs to the MM API\n * @param logs Array of log entries to send\n * @returns Promise with the API response\n */\n async sendBulkConnectorLogs(logs: { source: string; level: string; message: string }[]): Promise<MMApiBaseResponse> {\n return await this.postData(\n '/connector/logs',\n { logs },\n {},\n { baseUrl: UrlBase.ApiBase }\n );\n }\n\n async deleteFailedLaborTicketIds(\n system: string,\n laborTicketRefs: string[]\n ): Promise<MMApiBaseResponse> {\n return await this.postData(\n `${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/failed/remove`,\n { laborTicketRefs },\n { system }\n );\n }\n\n /**\n * Fetch machine groups from the MM API\n * @returns Promise with the machine groups data\n */\n async fetchMachineGroups(): Promise<MMMachineGroupsResponse> {\n return (await this.getData(\n this.resourceURLs[MACHINE_GROUPS],\n {},\n { baseUrl: UrlBase.ApiBase }\n )) as unknown as MMMachineGroupsResponse;\n }\n\n /**\n * Fetch resources using GraphQL\n * @returns Promise with the resources data\n */\n async fetchResourcesFromGraphQL(): Promise<MMGraphQLResourceResponse> {\n return (await this.graphqlRequest(\n \"getErpResources\",\n \"query getErpResources { machineResource: erpResources { machineRef machineGroupId resourceId }}\"\n )) as unknown as MMGraphQLResourceResponse;\n }\n\n /**\n * Cleanup all HTTP connections and resources\n * Call this when the service is no longer needed\n */\n async destroy(): Promise<void> {\n await this.api.destroy();\n // Note: MMTokenManager doesn't currently need explicit cleanup\n // but if it ever acquires resources that need cleanup, add it here\n }\n\n //#endregion public methods\n}\n","/**\n * These represent types of objects that can be sent to the MM API\n * Note that each class also implements a static method to convert from a plain\n * object to a REST API object: e.g.\n * MMSendPerson::fromPlainObject(data: Record<string, string | null>): MMSendPerson\n */\n\nexport interface IToRESTApiObject {\n toRESTApiObject(): Record<string, string | null>;\n}\n\n/**\n * Interface for static factory methods that can reconstruct typed objects\n * from plain data, without validation.\n */\n\nexport class MMSendPerson implements IToRESTApiObject {\n constructor(\n public personId: string,\n public firstName: string,\n public lastName: string,\n public isActive: boolean\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n personId: this.personId,\n firstName: this.firstName,\n lastName: this.lastName,\n isActive: this.isActive ? \"1\" : \"0\",\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendPerson {\n return new MMSendPerson(\n data.personId || \"\",\n data.firstName || \"\",\n data.lastName || \"\",\n data.isActive === \"1\"\n );\n }\n}\n\nexport class MMSendResource implements IToRESTApiObject {\n constructor(\n public resourceId: string, // Unique identifier that the ERP uses to describe a resource\n public name: string, // A colloquial name for the resource, sometimes also just the resource_id\n public isResourceGroup: boolean, // Whether or not this record represents an individual machine/piece of hardware, or a group/type of machines\n public description: string, // Text description of the resource (optional)\n public type: string, // The type of resource (optional)\n public productionBurdenRateHourly: number, // The cost associated with running this machine in production/hour (optional)\n public setupBurdenRateHourly: number // The cost associated with running this machine in setup/hour (optional)\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n resourceId: this.resourceId,\n name: this.name,\n isResourceGroup: this.isResourceGroup ? \"1\" : \"0\",\n description: this.description,\n type: this.type,\n productionBurdenRateHourly: this.productionBurdenRateHourly.toString(),\n setupBurdenRateHourly: this.setupBurdenRateHourly.toString(),\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendResource {\n return new MMSendResource(\n data.resourceId || \"\",\n data.name || \"\",\n data.isResourceGroup === \"1\",\n data.description || \"\",\n data.type || \"\",\n parseFloat(data.productionBurdenRateHourly || \"0\"),\n parseFloat(data.setupBurdenRateHourly || \"0\")\n );\n }\n}\n\nexport class MMSendPart implements IToRESTApiObject {\n constructor(\n public partNumber: string,\n public partRevision: string,\n public method: string\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n partNumber: this.partNumber,\n partRevision: this.partRevision,\n method: this.method,\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendPart {\n return new MMSendPart(\n data.partNumber || \"\",\n data.partRevision || \"\",\n data.method || \"\"\n );\n }\n}\n\nexport class MMSendPartOperation implements IToRESTApiObject {\n constructor(\n public partNumber: string,\n public partRevision: string,\n public method: string,\n public sequenceNumber: string,\n public resourceId: string,\n public cycleTimeMs: number,\n public setupTimeMs: number,\n public description: string,\n public quantityPerPart: number\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n partNumber: this.partNumber,\n partRevision: this.partRevision,\n method: this.method,\n sequenceNumber: this.sequenceNumber,\n resourceId: this.resourceId,\n cycleTimeMs: this.cycleTimeMs.toString(),\n setupTimeMs: this.setupTimeMs.toString(),\n description: this.description,\n quantityPerPart: this.quantityPerPart.toString(),\n };\n }\n\n static fromPlainObject(\n data: Record<string, string | null>\n ): MMSendPartOperation {\n return new MMSendPartOperation(\n data.partNumber || \"\",\n data.partRevision || \"\",\n data.method || \"\",\n data.sequenceNumber || \"\",\n data.resourceId || \"\",\n parseInt(data.cycleTimeMs || \"0\"),\n parseInt(data.setupTimeMs || \"0\"),\n data.description || \"\",\n parseFloat(data.quantityPerPart || \"1\")\n );\n }\n}\n\nexport class MMSendWorkOrder implements IToRESTApiObject {\n constructor(\n public workOrderId: string,\n public lot: string,\n public split: string,\n public sub: string,\n public status: string,\n public dueDate: string | null,\n public description: string,\n public scheduledStartDate: string | null,\n public scheduledEndDate: string | null,\n public closedDate: string | null,\n public quantityRequired: number,\n public partNumber: string,\n public partRevision: string,\n public method: string\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n workOrderId: this.workOrderId,\n lot: this.lot,\n split: this.split,\n sub: this.sub,\n status: this.status,\n dueDate: this.dueDate,\n description: this.description,\n scheduledStartDate: this.scheduledStartDate,\n scheduledEndDate: this.scheduledEndDate,\n closedDate: this.closedDate,\n quantityRequired: this.quantityRequired.toString(),\n partNumber: this.partNumber,\n partRevision: this.partRevision,\n method: this.method,\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendWorkOrder {\n return new MMSendWorkOrder(\n data.workOrderId || \"\",\n data.lot || \"\",\n data.split || \"\",\n data.sub || \"\",\n data.status || \"\",\n data.dueDate ?? null,\n data.description || \"\",\n data.scheduledStartDate ?? null,\n data.scheduledEndDate ?? null,\n data.closedDate ?? null,\n parseFloat(data.quantityRequired || \"0\"),\n data.partNumber || \"\",\n data.partRevision || \"\",\n data.method || \"\"\n );\n }\n}\n\nexport class MMSendWorkOrderOperation implements IToRESTApiObject {\n constructor(\n public workOrderId: string,\n public lot: string,\n public split: string,\n public sub: string,\n public sequenceNumber: string,\n public resourceId: string,\n public startQuantity: number,\n public finishQuantity: number,\n public expectedRejectRate: number,\n public scheduledStartDate: string | null,\n public scheduledFinishDate: string | null,\n public closedDate: string | null,\n public cycleTimeMs: number,\n public setupTimeMs: number,\n public productionburdenRateHourly: number,\n public setupburdenRatehourly: number,\n public operationType: string,\n public quantityPerPart: number,\n public status: string\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n workOrderId: this.workOrderId,\n lot: this.lot,\n split: this.split,\n sub: this.sub,\n sequenceNumber: this.sequenceNumber,\n resourceId: this.resourceId,\n startQuantity: this.startQuantity.toString(),\n finishQuantity: this.finishQuantity.toString(),\n expectedRejectRate: this.expectedRejectRate.toString(),\n scheduledStartDate: this.scheduledStartDate,\n scheduledFinishDate: this.scheduledFinishDate,\n closedDate: this.closedDate,\n cycleTimeMs: this.cycleTimeMs.toString(),\n setupTimeMs: this.setupTimeMs.toString(),\n productionburdenRateHourly: this.productionburdenRateHourly.toString(),\n setupburdenRatehourly: this.setupburdenRatehourly.toString(),\n operationType: this.operationType,\n quantityPerPart: this.quantityPerPart.toString(),\n status: this.status,\n };\n }\n\n static fromPlainObject(\n data: Record<string, string | null>\n ): MMSendWorkOrderOperation {\n return new MMSendWorkOrderOperation(\n data.workOrderId || \"\",\n data.lot || \"\",\n data.split || \"\",\n data.sub || \"\",\n data.sequenceNumber || \"\",\n data.resourceId || \"\",\n parseFloat(data.startQuantity || \"0\"),\n parseFloat(data.finishQuantity || \"0\"),\n parseFloat(data.expectedRejectRate || \"0\"),\n data.scheduledStartDate ?? null,\n data.scheduledFinishDate ?? null,\n data.closedDate ?? null,\n parseInt(data.cycleTimeMs || \"0\"),\n parseInt(data.setupTimeMs || \"0\"),\n parseFloat(data.productionburdenRateHourly || \"0\"),\n parseFloat(data.setupburdenRatehourly || \"0\"),\n data.operationType || \"\",\n parseFloat(data.quantityPerPart || \"1\"),\n data.status || \"\"\n );\n }\n}\n\nexport class MMSendReason implements IToRESTApiObject {\n constructor(\n public reasonId: string,\n public category: string,\n public code: string,\n public description: string,\n public entityType: string\n ) {}\n\n toRESTApiObject(): Record<string, string> {\n return {\n reasonId: this.reasonId,\n category: this.category,\n code: this.code,\n description: this.description,\n entityType: this.entityType,\n };\n }\n\n static fromPlainObject(data: Record<string, string | null>): MMSendReason {\n return new MMSendReason(\n data.reasonId || \"\",\n data.category || \"\",\n data.code || \"\",\n data.description || \"\",\n data.entityType || \"\"\n );\n }\n}\n\nexport class MMSendLaborTicket implements IToRESTApiObject {\n constructor(\n public workOrderId: string,\n public lot: string,\n public split: string,\n public sub: string,\n public sequenceNumber: string,\n public resourceId: string,\n public personId: string,\n public laborTicketId: string,\n public transactionDate: string | null,\n public clockIn: string | null,\n public clockOut: string | null,\n public goodParts: number,\n public badParts: number,\n public type: string,\n public comment: string,\n public state: string\n ) {}\n\n toRESTApiObject(): Record<string, string | null> {\n return {\n workOrderId: this.workOrderId,\n lot: this.lot,\n split: this.split,\n sub: this.sub,\n sequenceNumber: this.sequenceNumber,\n resourceId: this.resourceId,\n personId: this.personId,\n laborTicketId: this.laborTicketId,\n transactionDate: this.transactionDate,\n clockIn: this.clockIn,\n clockOut: this.clockOut,\n goodParts: this.goodParts.toString(),\n badParts: this.badParts.toString(),\n type: this.type,\n comment: this.comment,\n state: this.state,\n };\n }\n\n static fromPlainObject(\n data: Record<string, string | null>\n ): MMSendLaborTicket {\n return new MMSendLaborTicket(\n data.workOrderId || \"\",\n data.lot || \"\",\n data.split || \"\",\n data.sub || \"\",\n data.sequenceNumber || \"\",\n data.resourceId || \"\",\n data.personId || \"\",\n data.laborTicketId || \"\",\n data.transactionDate ?? null,\n data.clockIn ?? null,\n data.clockOut ?? null,\n parseInt(data.goodParts || \"0\"),\n parseInt(data.badParts || \"0\"),\n data.type || \"\",\n data.comment || \"\",\n data.state || \"\"\n );\n }\n}\n\n/**\n * Flattened interface for Work Order related entities that maintains referential integrity\n * by processing parts, part operations, work orders, and work order operations together\n */\nexport interface MMSendWorkOrderBatch {\n parts: MMSendPart[];\n partOperations: MMSendPartOperation[];\n workOrders: MMSendWorkOrder[];\n workOrderOperations: MMSendWorkOrderOperation[];\n}\n","import { CoreConfiguration } from \"../data-sync-service/configuration-manager\";\nimport { HTTPClientFactory } from \"../../utils/http-client\";\nimport { logger } from \"../reporting-service\";\n\n// Cache for company info to avoid repeated API calls\nlet companyInfoCache: {\n timezone: string;\n locationRef: string;\n companyId: string;\n} | null = null;\n\nexport interface CompanyInfo {\n timezone: string;\n locationRef: string;\n companyId: string;\n}\n\n/**\n * Fetches company information from /accounts/current endpoint\n * Results are cached to avoid repeated API calls\n * @returns Promise<CompanyInfo> Company information including timezone, locationRef, and companyId\n * @throws Error if unable to fetch company information\n */\nexport const getCompanyInfo = async (): Promise<CompanyInfo> => {\n // Return cached value if available\n if (companyInfoCache) {\n return companyInfoCache;\n }\n\n try {\n // Get the configuration\n const config = CoreConfiguration.inst();\n const apiUrl = config.mmApiBaseUrl;\n const authToken = config.mmApiAuthToken;\n\n if (!apiUrl || !authToken) {\n throw new Error(\"Missing required configuration for company info fetch\");\n }\n\n // Create HTTP client\n const client = HTTPClientFactory.getInstance({\n baseUrl: apiUrl,\n retryAttempts: config.mmApiRetryAttempts,\n });\n\n // Fetch user/company information from the API\n const response = await client.request<{\n company: {\n timezone: string;\n id: string;\n };\n locationRef: number;\n }>({\n url: \"/accounts/current?includeLocation=true\",\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${authToken}`,\n },\n });\n\n const userInfo = response.data;\n if (!userInfo?.company) {\n throw new Error(\"Unable to retrieve company information from API\");\n }\n\n // Debug: Log what we actually got from the API\n logger.info(\"Fetched company info from /accounts/current\", {\n locationRef: userInfo.locationRef,\n companyId: userInfo.company.id,\n timezone: userInfo.company.timezone,\n });\n\n // Cache and return company info - locationRef is at top level, not in company\n companyInfoCache = {\n timezone: userInfo.company.timezone,\n locationRef: String(userInfo.locationRef), // Convert number to string\n companyId: userInfo.company.id,\n };\n\n return companyInfoCache;\n } catch (error) {\n throw new Error(\n `Failed to get company info: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n};\n\n","/**\n * Extracts unique rows from data based on specified fields and sorts by up to two fields\n * @param data Array of data objects\n * @param fields Array of field names to use as a unique keys\n * @param sortFields Optional array of field names to sort by (up to two fields)\n * @returns Array of unique objects based on the specified fields, or an empty array if data is empty\n */\nexport function getUniqueRows<T extends Record<string, unknown>>(\n data: T[],\n fields: (keyof T)[],\n sortFields?: (keyof T)[]\n): T[] {\n const createCompositeKey = (item: T): string => {\n return fields.map((field) => String(item[field])).join(\"|\");\n };\n\n const uniqueKeys = new Set<string>();\n const uniqueRows: T[] = [];\n\n data.forEach((item) => {\n const key = createCompositeKey(item);\n if (!uniqueKeys.has(key)) {\n uniqueKeys.add(key);\n uniqueRows.push(item);\n }\n });\n\n if (sortFields && sortFields.length > 0) {\n uniqueRows.sort((a, b) => {\n for (let i = 0; i < sortFields.length; i++) {\n const field = sortFields[i];\n if (a[field] < b[field]) return -1;\n if (a[field] > b[field]) return 1;\n }\n return 0;\n });\n }\n\n return uniqueRows || [];\n}\n","import { ERPObject } from \"../types/erp-types\";\n\n/**\n * Retain only the fields listed in retainFields[] in the payload\n */\nexport const removeExtraneousFields = (\n recordset: ERPObject[],\n retainFields: string[]\n): ERPObject[] => {\n const data: ERPObject[] =\n recordset.map((row) => {\n const transformedRow: ERPObject = {};\n Object.keys(row).forEach((key) => {\n if (retainFields.includes(key)) {\n transformedRow[key] = row[key]?.toString() || \"\";\n }\n });\n return transformedRow;\n }) || [];\n\n return data;\n};\n","import { ERPObject } from \"../types/erp-types\";\n\n/**\n * Remove all fields listed in idField[] the from the payload\n */\nexport const getPayloadWithoutIDField = (\n recordset: ERPObject[],\n idField: string[]\n): ERPObject[] => {\n const data: ERPObject[] =\n recordset.map((row) => {\n const transformedRow: ERPObject = {};\n Object.keys(row).forEach((key) => {\n if (!idField.includes(key)) {\n transformedRow[key] = row[key]?.toString() || \"\";\n }\n });\n return transformedRow;\n }) || [];\n\n return data;\n};\n","/**\n * Trims all string values in an object while preserving the object's structure\n */\nexport const trimObjectValues = <T extends object>(obj: T): T => {\n return Object.keys(obj).reduce((acc, key) => {\n const value = obj[key as keyof T];\n acc[key as keyof T] = (\n typeof value === \"string\" ? value.trim() : value\n ) as T[keyof T];\n return acc;\n }, {} as T);\n};\n","export const cleanupNumbers = (data: string) => {\n // eslint-disable-next-line no-useless-escape\n data = data.toString().replace(/[^0-9\\.]+/g, \"\");\n return data;\n};\n","/**\n * Add a new field to the data from an external source by matching on the same field name\n * @param data The data to add the new field to\n * @param externalSource The external source to get the new field from\n * @param externalSourceFieldName The field in both data and external source to match on\n * @param newFieldName The name of the new field to add\n * @param defaultValue The default value to use if there is no match\n */\nexport const addNewFieldFromExternalSource = (\n data: Record<string, string>[],\n externalSource: Record<string, string>[],\n externalSourceFieldName: string,\n newFieldName: string,\n defaultValue: string = \"-\"\n): Record<string, string>[] => {\n return data.map((current) => {\n const cacheEntry = externalSource.find(\n (existing) =>\n existing[externalSourceFieldName] === current[externalSourceFieldName]\n );\n const newFieldValue = cacheEntry?.[newFieldName] || defaultValue;\n\n return {\n ...current,\n [newFieldName]: newFieldValue,\n };\n });\n};\n\n/**\n * Add a new field to the data from an external source by matching on different field names\n * @param data The data to add the new field to\n * @param externalSource The external source to get the new field from\n * @param lookupField The field in the external source to match on\n * @param currentField The field in the current data to match on\n * @param newFieldName The name of the new field to add\n * @param defaultValue The default value to use if there is no match\n */\nexport const addNewFieldFromLookupField = (\n data: Record<string, string>[],\n externalSource: Record<string, string>[],\n lookupField: string,\n currentField: string,\n newFieldName: string,\n defaultValue: string = \"-\"\n): Record<string, string>[] => {\n return data.map((current) => {\n const cacheEntry = externalSource.find(\n (existing) => existing[lookupField] === current[currentField]\n );\n const newFieldValue = cacheEntry?.[currentField] || defaultValue;\n\n return {\n ...current,\n [newFieldName]: newFieldValue,\n };\n });\n};\n","import { CoreConfiguration } from \"../services/data-sync-service/configuration-manager\";\nimport { HTTPClientFactory } from \"./http-client\";\n\n/**\n * Gets the timezone offset in hours and timezone name for the company's timezone\n * @returns Promise<{ offset: number; timezone: string }> The timezone offset in hours and timezone name\n * @throws Error if unable to fetch timezone information\n */\nexport const getTimezoneOffset = async (): Promise<{ offset: number; timezone: string }> => {\n try {\n // Get the timezone from configuration\n const config = CoreConfiguration.inst();\n const apiUrl = config.mmApiBaseUrl;\n const authToken = config.mmApiAuthToken;\n\n if (!apiUrl || !authToken) {\n throw new Error(\"Missing required configuration for timezone fetch\");\n }\n\n // Create HTTP client\n const client = HTTPClientFactory.getInstance({\n baseUrl: apiUrl,\n retryAttempts: config.mmApiRetryAttempts,\n });\n\n // Fetch user/company information from the API\n const response = await client.request<{ company: { timezone: string } }>({\n url: \"/accounts/current\",\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${authToken}`,\n },\n });\n\n const userInfo = response.data;\n if (!userInfo?.company?.timezone) {\n throw new Error(\"Unable to retrieve company timezone from API\");\n }\n\n const timezone = userInfo.company.timezone;\n\n // Calculate the timezone offset\n const date = new Date();\n const utcDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"UTC\" }));\n const tzDate = new Date(\n date.toLocaleString(\"en-US\", { timeZone: timezone })\n );\n\n // Return offset in hours and timezone name\n const offset = (tzDate.getTime() - utcDate.getTime()) / 3600000;\n return { offset, timezone };\n } catch (error) {\n throw new Error(\n `Failed to get timezone offset: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n};\n\n/**\n * Converts a UTC/Zulu time to local time based on the provided timezone offset\n * @param zuluTime The UTC/Zulu time to convert\n * @param timezoneOffset The timezone offset in hours\n * @returns The converted local time, or undefined if input is null/undefined\n */\nexport const convertToLocalTime = (\n zuluTime: string | Date | null | undefined,\n timezoneOffset: number\n): Date | undefined => {\n if (!zuluTime) return undefined;\n const date = new Date(zuluTime);\n return new Date(date.getTime() + timezoneOffset * 60 * 60 * 1000);\n};\n\n/**\n * Formats a date with the timezone offset in ISO format with offset\n * @param date The date string in format YYYY-MM-DD HH:mm:ss or YYYY-MM-DDTHH:mm:ss\n * @param timezoneOffset The timezone offset in hours\n * @returns The formatted date string in format: YYYY-MM-DDTHH:mm:ss+/-HH:MM\n */\nexport const formatDateWithTZOffset = (\n date: string | null | undefined,\n timezoneOffset: number\n): string | undefined => {\n if (!date) return undefined;\n\n // Replace space with T if present\n const isoDate = date.replace(\" \", \"T\");\n\n // Calculate offset components\n const sign = timezoneOffset >= 0 ? \"+\" : \"-\";\n const absOffset = Math.abs(timezoneOffset);\n const hours = Math.floor(absOffset).toString().padStart(2, \"0\");\n const minutes = Math.floor((absOffset % 1) * 60)\n .toString()\n .padStart(2, \"0\");\n\n // Append the timezone offset\n return `${isoDate}${sign}${hours}:${minutes}`;\n};\n\n/**\n * Formats a Date object as an ISO string with the specified timezone offset\n * @param date The Date object to format (should be a Date that has been shifted by convertToLocalTime)\n * @param timezoneOffset The timezone offset in hours\n * @returns ISO string with offset in format: YYYY-MM-DDTHH:mm:ss.SSS±HH:MM\n */\nexport const toISOWithOffset = (date: Date, timezoneOffset: number): string => {\n const sign = timezoneOffset >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(timezoneOffset);\n const hours = Math.floor(abs);\n const minutes = Math.round((abs - hours) * 60);\n const pad2 = (n: number) => n.toString().padStart(2, \"0\");\n const pad3 = (n: number) => n.toString().padStart(3, \"0\");\n\n // Use UTC getters since convertToLocalTime shifts the Date's internal timestamp\n // The UTC components of the shifted Date represent the local wall time\n const yyyy = date.getUTCFullYear();\n const MM = pad2(date.getUTCMonth() + 1);\n const dd = pad2(date.getUTCDate());\n const HH = pad2(date.getUTCHours());\n const mm = pad2(date.getUTCMinutes());\n const ss = pad2(date.getUTCSeconds());\n const SSS = pad3(date.getUTCMilliseconds());\n const off = `${sign}${pad2(hours)}:${pad2(minutes)}`;\n\n return `${yyyy}-${MM}-${dd}T${HH}:${mm}:${ss}.${SSS}${off}`;\n};\n","import logger from \"../services/reporting-service/logger\";\nimport {\n setTimezoneOffsetInCache,\n setTimezoneNameInCache,\n} from \"./local-data-store/jobs-shared-data\";\nimport {\n convertToLocalTime,\n formatDateWithTZOffset,\n getTimezoneOffset,\n} from \"./timezone\";\n\n/**\n * Calculates the difference in hours between two timestamps\n * @param startTime ISO timestamp string\n * @param endTime ISO timestamp string\n * @param timezoneOffset timezone offset in hours\n * @returns number representing hours difference, rounded to 3 decimal places\n */\nexport function calculateTimeDifferenceInHours(\n startTime: string | null | undefined,\n endTime: string | null | undefined,\n timezoneOffset: number\n): number {\n if (!startTime || !endTime) return 0;\n\n const localStartTime = convertToLocalTime(startTime, timezoneOffset);\n const localEndTime = convertToLocalTime(endTime, timezoneOffset);\n\n if (!localStartTime || !localEndTime) return 0;\n\n // Get hours, minutes, seconds from the Date objects\n const startHours = localStartTime.getHours();\n const startMinutes = localStartTime.getMinutes();\n const startSeconds = localStartTime.getSeconds();\n\n const endHours = localEndTime.getHours();\n const endMinutes = localEndTime.getMinutes();\n const endSeconds = localEndTime.getSeconds();\n\n // Calculate total seconds for each time\n const startTotalSeconds =\n startHours * 3600 + startMinutes * 60 + startSeconds;\n const endTotalSeconds = endHours * 3600 + endMinutes * 60 + endSeconds;\n\n if (endTotalSeconds > startTotalSeconds) {\n return Number(((endTotalSeconds - startTotalSeconds) / 3600).toFixed(3));\n }\n\n return 0;\n}\n\nexport const getSecondsOfDay = (timestamp: string | null) => {\n if (!timestamp) return null;\n const time = new Date(timestamp);\n\n const utcHours = time.getUTCHours();\n const utcMinutes = time.getUTCMinutes();\n const utcSeconds = time.getUTCSeconds();\n\n return (utcHours * 3600 + utcMinutes * 60 + utcSeconds).toString();\n};\n\n/**\n * Apply timezone offsets to specified datetime fields in an object\n * @param item The object to transform\n * @param fields The fields to apply timezone offset to\n * @param timezoneOffset The timezone offset to apply\n * @returns The transformed object\n */\n\nexport const applyTimezoneOffsetsToFields = (\n item: Record<string, string>,\n fields: string[],\n timezoneOffset: number\n): Record<string, string> => {\n const transformed = { ...item };\n fields.forEach((field) => {\n if (transformed[field] && transformed[field].trim()) {\n const formattedDate = formatDateWithTZOffset(\n transformed[field],\n timezoneOffset\n );\n transformed[field] = formattedDate || \"\";\n } else {\n transformed[field] = \"\";\n }\n });\n return transformed;\n};\n\ninterface TimezoneOffsetParams {\n maxRetries: number;\n retryIntervalMs: number;\n}\n\n/**\n * Gets the timezone offset and timezone name for the company and sets them in the cache\n * The cached offset can be acquired from getCachedTimezoneOffset() in \"./local-data-store/jobs-shared-data\"\n * The cached timezone name can be acquired from getCachedTimezoneName() in \"./local-data-store/jobs-shared-data\"\n *\n * @param params.maxRetries Maximum number of retry attempts\n * @param params.retryIntervalMs Time to wait between retries in milliseconds\n */\nexport const getTimezoneOffsetAndPersist = async (\n params: TimezoneOffsetParams = {\n maxRetries: 36000, // Retry for 10 hours before giving up.\n retryIntervalMs: 10_000,\n }\n): Promise<void> => {\n let success = false;\n let retries = 0;\n logger.info(\n \"Acquiring the timezone offset and timezone name from MachineMetrics and storing in cache\"\n );\n while (!success && retries < params.maxRetries) {\n try {\n const { offset, timezone } = await getTimezoneOffset();\n logger.info(`Timezone offset: ${offset} hours, timezone: ${timezone}`);\n setTimezoneOffsetInCache(offset);\n setTimezoneNameInCache(timezone);\n success = true;\n } catch (error) {\n logger.error(\"Error getting timezone offset:\", error);\n logger.info(`Retrying in ${params.retryIntervalMs / 1000} seconds...`);\n await new Promise((resolve) =>\n setTimeout(resolve, params.retryIntervalMs)\n );\n retries++;\n }\n }\n if (!success) {\n throw new Error(\n `Failed to get the timezone offset after ${params.maxRetries} retries`\n );\n }\n};\n","import knex, { Knex } from \"knex\";\nimport config from \"../../knexfile\";\nimport { ERPObjType } from \"../../types/erp-types\";\n\nconst DEFAULT_RECORD_TRACKING_TABLE_NAME = \"record_tracking\";\n\nexport type RecordTrackingObject = {\n entityType?: ERPObjType;\n lastValue: string; // Typically date/time string\n recordId: string; // A value to disambiguate records with the same lastValue\n};\n\nexport class RecordTrackingManager {\n db: Knex;\n\n constructor() {\n this.db = knex(config.local);\n }\n\n async updateRecord(record: RecordTrackingObject) {\n if (record.lastValue && record.recordId) {\n const recordUpdated = await this.db(DEFAULT_RECORD_TRACKING_TABLE_NAME)\n .where({ entityType: record.entityType })\n .update(record);\n\n if (recordUpdated < 1) {\n await this.db(DEFAULT_RECORD_TRACKING_TABLE_NAME).insert(record);\n }\n }\n }\n\n async getTrackingRecord(type: ERPObjType) {\n return await this.db(DEFAULT_RECORD_TRACKING_TABLE_NAME)\n .select(\"*\")\n .where({ entityType: type.toString() });\n }\n\n async destroy() {\n return this.db.destroy();\n }\n}\n","import { HashedCacheManager } from \"./hashed-cache-manager\";\nimport { ERPObjType } from \"../../types/erp-types\";\n\nexport type BatchResult<T> = {\n duplicates: T[];\n nonDuplicates: T[];\n};\n\ntype BatchCacheManagerOptions = {\n ttl?: number;\n tableName?: string;\n};\n\nexport class BatchCacheManager {\n private cacheManager: HashedCacheManager;\n\n constructor(options?: BatchCacheManagerOptions) {\n this.cacheManager = new HashedCacheManager({\n ttl: options?.ttl,\n tableName: options?.tableName,\n });\n }\n\n /**\n * Checks a batch of objects against the cache and separates them into duplicates and non-duplicates\n * @param type The type of objects\n * @param objects Array of objects to check\n * @returns Object containing arrays of duplicate and non-duplicate objects\n */\n async dedupeBatch(\n type: ERPObjType,\n objects: object[]\n ): Promise<BatchResult<object>> {\n const result: BatchResult<object> = {\n duplicates: [],\n nonDuplicates: [],\n };\n\n // Check each object individually\n for (const object of objects) {\n const hasChanged = await this.cacheManager.hasChanged(type, object);\n\n if (hasChanged) {\n result.nonDuplicates.push(object);\n } else {\n result.duplicates.push(object);\n }\n }\n\n return result;\n }\n\n /**\n * Stores a batch of objects in the cache\n * @param type The type of objects\n * @param objects Array of objects to store\n */\n async storeBatch(\n type: ERPObjType,\n objects: object[]\n ): Promise<{ totalInserted: number }> {\n // Store each object individually\n let totalInserted = 0;\n for (const object of objects) {\n if (await this.cacheManager.store(type, object)) {\n totalInserted++;\n }\n }\n return { totalInserted };\n }\n\n /**\n * Removes objects from the cache\n * @param type The type of objects\n * @param objects Array of objects to remove\n */\n async removeObjects(type: ERPObjType, objects: object[]): Promise<void> {\n // Remove each object individually\n for (const object of objects) {\n await this.cacheManager.removeRecord(type, object);\n }\n }\n\n /**\n * Gets all records of a specific type\n */\n async getRecordsByType(type: ERPObjType): Promise<string[]> {\n return this.cacheManager.getRecordsByType(type);\n }\n\n /**\n * Removes all records of a specific type\n */\n async removeRecordsByType(type: ERPObjType): Promise<void> {\n return this.cacheManager.removeRecordsByType(type);\n }\n\n /**\n * Clears all records from the cache\n */\n async clear(): Promise<void> {\n return this.cacheManager.clear();\n }\n\n /**\n * Cleans up resources\n */\n async destroy(): Promise<void> {\n return this.cacheManager.destroy();\n }\n}\n","import { convertToLocalTime, toISOWithOffset } from \"./timezone\";\nimport { MMReceiveLaborTicket } from \"../services/mm-api-service/types/receive-types\";\n\n/**\n * Apply timezone offsets to datetime fields specifically for the MMApiReceiveLaborTicket object from the Zulu-based MM API\n * (presumably) before sending to the ERP\n * @param laborTicket The MMApiReceiveLaborTicket object to convert\n * @param timezoneOffset The timezone offset to apply\n */\nexport function convertLaborTicketToLocalTimezone(\n laborTicket: MMReceiveLaborTicket,\n timezoneOffset: number\n): MMReceiveLaborTicket {\n const timeFields = [\n \"clockIn\",\n \"clockOut\",\n \"transactionDate\",\n \"createdAt\",\n \"updatedAt\",\n \"workOrderOperationClosedDate\",\n ] as const;\n\n timeFields.forEach((field) => {\n const localTime = convertToLocalTime(laborTicket[field], timezoneOffset);\n laborTicket[field] = localTime ? toISOWithOffset(localTime, timezoneOffset) : null;\n });\n return laborTicket;\n}\n","import { IERPLaborTicketHandler } from \"../../types/erp-connector\";\nimport { MMApiClient } from \"../../services/mm-api-service/mm-api-service\";\nimport { MMReceiveLaborTicket } from \"../../services/mm-api-service/types/receive-types\";\nimport { convertLaborTicketToLocalTimezone } from \"../mm-labor-ticket-helpers\";\nimport { getCachedTimezoneOffset } from \"../local-data-store/jobs-shared-data\";\nimport logger from \"../../services/reporting-service/logger\";\n\n/**\n * Handles synchronization of labor tickets between MachineMetrics and ERP systems\n */\nexport class LaborTicketERPSynchronizer {\n /**\n * Synchronizes updated labor tickets from MachineMetrics to an ERP system\n */\n static async syncToERP(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n try {\n const mmApiClient = new MMApiClient();\n const failedLaborTicketRefs: string[] = [];\n\n await mmApiClient.initializeCheckpoint({\n system: connectorType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: new Date().toISOString(),\n },\n });\n\n const fallbackTimestamp = new Date().toISOString();\n const laborTicketsUpdates = await mmApiClient.fetchLaborTicketUpdates({\n system: connectorType,\n checkpointType: \"export\",\n });\n\n if (laborTicketsUpdates.length === 0) {\n logger.info(\"syncLaborTicketsToERP:No updated labor tickets found\");\n return;\n }\n\n logger.info(\n `ToERP: Found ${laborTicketsUpdates.length} Labor Ticket Ids and Refs to process`,\n {\n laborTickets: laborTicketsUpdates.map(\n (ticket: MMReceiveLaborTicket) => ({\n ref: ticket.laborTicketRef,\n id: ticket.laborTicketId,\n })\n ),\n }\n );\n\n // Find the most recent updatedAt timestamp from labor tickets. This will be used to update\n // the checkpoint to ensure there is no gap of time for the next sync.\n const mostRecentUpdate = laborTicketsUpdates.reduce(\n (latest: string | null, ticket: MMReceiveLaborTicket) => {\n if (!latest || !ticket.updatedAt) return latest;\n return new Date(ticket.updatedAt) > new Date(latest)\n ? ticket.updatedAt\n : latest;\n },\n null as string | null\n );\n\n await Promise.all(\n laborTicketsUpdates.map(async (laborTicket: MMReceiveLaborTicket) => {\n if (!laborTicket.laborTicketRef) {\n logger.error(\n \"syncLaborTicketsToERP: laborTicketRef is not set for laborTicket pulled from MM:\",\n { laborTicket }\n );\n return undefined;\n }\n\n try {\n return await this.processLaborTicket(\n connector,\n mmApiClient,\n laborTicket\n );\n } catch (error) {\n failedLaborTicketRefs.push(laborTicket.laborTicketRef);\n logger.error(\n `syncLaborTicketsToERP: Error processing laborTicketRef ${laborTicket.laborTicketRef}:`,\n { error }\n );\n return undefined;\n }\n })\n );\n\n logger.info(\n `syncLaborTicketsToERP: ${failedLaborTicketRefs.length} failed labor ticket ids`\n );\n if (failedLaborTicketRefs.length > 0) {\n logger.info(\n `syncLaborTicketsToERP: Reporting ${failedLaborTicketRefs.length} labor ticket failures:`,\n {\n failedLaborTicketRefs,\n }\n );\n const addFailedResult = await mmApiClient.addFailedLaborTicketRefs(\n connectorType,\n failedLaborTicketRefs\n );\n logger.info(\"syncLaborTicketsToERP: addFailedResult:\", {\n addFailedResult,\n });\n }\n\n mmApiClient.saveCheckpoint({\n system: connectorType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: mostRecentUpdate || fallbackTimestamp,\n },\n });\n } catch (error) {\n logger.error(\"syncLaborTicketsToERP: Error:\", error);\n }\n }\n\n /**\n * Retries labor tickets that have failed to be created or updated in the ERP during the sync\n */\n static async retryFailed(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n try {\n const mmApiClient = new MMApiClient();\n const successLaborTicketIds: string[] = [];\n\n const laborTickets =\n await mmApiClient.fetchFailedLaborTickets(connectorType);\n if (laborTickets.length === 0) {\n logger.info(\"retryFailedLaborTickets: No failed labor tickets found\");\n return;\n }\n logger.info(\n \"retryFailedLaborTickets: Failed Labor Tickets count:\" +\n laborTickets.length\n );\n\n await Promise.all(\n laborTickets.map(async (laborTicket: MMReceiveLaborTicket) => {\n if (!laborTicket.laborTicketRef) {\n logger.error(\n \"retryFailedLaborTickets: laborTicketRef is not set for laborTicket pulled from MM:\",\n { laborTicket }\n );\n return undefined;\n }\n\n try {\n const laborTicketResult = await this.processLaborTicket(\n connector,\n mmApiClient,\n laborTicket\n );\n successLaborTicketIds.push(laborTicket.laborTicketRef);\n return laborTicketResult;\n } catch (error) {\n logger.error(\n \"retryFailedLaborTickets: Error processing laborTicketRef:\",\n { laborTicketRef: laborTicket.laborTicketRef, error }\n );\n return undefined;\n }\n })\n );\n\n if (successLaborTicketIds.length > 0) {\n logger.info(\"Deleting failed labor ticket ids:\", {\n successLaborTicketIds,\n });\n const deleteFailedResult = await mmApiClient.deleteFailedLaborTicketIds(\n connectorType,\n successLaborTicketIds\n );\n logger.info(\"deleteFailedResult:\", { deleteFailedResult });\n }\n } catch (error) {\n logger.error(\"retryFailedLaborTickets: Error:\", error);\n }\n }\n\n // ============================================================================\n // PRIVATE HELPER METHODS\n // ============================================================================\n\n private static async writeLaborTicketIdToMM(\n mmApiClient: MMApiClient,\n laborTicket: MMReceiveLaborTicket,\n laborTicketResult: MMReceiveLaborTicket\n ): Promise<void> {\n const updateRefAPIResponse = await mmApiClient.updateLaborTicketIdByRef(\n laborTicket.laborTicketRef,\n laborTicketResult.laborTicketId\n );\n logger.info(\n `Updated laborTicketId ${laborTicketResult.laborTicketId} for laborTicketRef ${laborTicket.laborTicketRef} in MM:`,\n { updateRefAPIResponse }\n );\n }\n\n private static async processLaborTicket(\n connector: IERPLaborTicketHandler,\n mmApiClient: MMApiClient,\n laborTicket: MMReceiveLaborTicket\n ): Promise<MMReceiveLaborTicket> {\n let laborTicketResult: MMReceiveLaborTicket;\n\n laborTicketResult = convertLaborTicketToLocalTimezone(\n laborTicket,\n getCachedTimezoneOffset()\n );\n\n logger.info(\n `processing laborTicket, id=${laborTicket.laborTicketId}, ref=${laborTicket.laborTicketRef}`\n );\n logger.debug({ laborTicket });\n\n // MLW TODO: Should we always swap out the resource ID for the machine group ID if it is found?\n // Or use a flag? What about the default resource ID as used by syteline? A Rutherford consultation is in order.\n /**\n * Swapping out the resource ID for the group id corresponding to the\n * resource ID in the ERP would be done like so:\n * const machineGroupId = await mmApiClient.getResourceERPGroupId(laborTicket.resourceId);\n * if(machineGroupId) {\n * laborTicket.resourceId = machineGroupId;\n * }\n */\n\n if (!laborTicket.laborTicketId) {\n const { laborTicket: laborTicketResult, erpUid } =\n await connector.createLaborTicketInERP(laborTicket);\n laborTicketResult.laborTicketId = erpUid;\n await this.writeLaborTicketIdToMM(\n mmApiClient,\n laborTicket,\n laborTicketResult\n );\n } else {\n laborTicketResult = await connector.updateLaborTicketInERP(laborTicket);\n }\n\n logger.info(\"ToERP: laborTicket update result:\", {\n laborTicketResult:\n laborTicketResult || \"Failed to create/update labor ticket\",\n laborTicketRef: laborTicket.laborTicketRef,\n operation: laborTicket.laborTicketId ? \"update\" : \"create\",\n });\n\n return laborTicketResult;\n }\n}\n","import { ERPObjType } from \"../../../types/erp-types\";\nimport { IToRESTApiObject } from \"..\";\nimport {\n MMSendPerson,\n MMSendResource,\n MMSendPart,\n MMSendPartOperation,\n MMSendWorkOrder,\n MMSendWorkOrderOperation,\n MMSendLaborTicket,\n MMSendReason,\n} from \"..\";\n\n/**\n * Utility class for transforming entities to/from non-typed and typed objects\n * for interacting with the MachineMetrics API..\n */\nexport class EntityTransformer {\n // ============================================================================\n // PUBLIC INTERFACE METHODS\n // ============================================================================\n\n /**\n * Extracts the primary key from a record based on entity type\n * @param entityType The type of entity being processed\n * @param record The record to extract primary key from\n * @returns A string representation of the primary key\n * @throws Error if primary key fields are missing or invalid\n */\n static extractPrimaryKey(\n entityType: ERPObjType,\n record: IToRESTApiObject\n ): string {\n switch (entityType) {\n case ERPObjType.PERSONS:\n return this.extractPersonKey(record);\n case ERPObjType.RESOURCES:\n return this.extractResourceKey(record);\n case ERPObjType.PARTS:\n return this.extractPartKey(record);\n case ERPObjType.PART_OPERATION:\n return this.extractPartOperationKey(record);\n case ERPObjType.WORK_ORDERS:\n return this.extractWorkOrderKey(record);\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return this.extractWorkOrderOperationKey(record);\n case ERPObjType.LABOR_TICKETS:\n return this.extractLaborTicketKey(record);\n case ERPObjType.REASONS:\n return this.extractReasonKey(record);\n default:\n throw new Error(\n `Unsupported entity type for primary key extraction: ${entityType}`\n );\n }\n }\n\n /**\n * Reconstructs a typed object from plain data based on entity type\n * @param entityType The type of entity being processed\n * @param plainData The plain data object containing entity fields\n * @returns A properly typed IToRESTApiObject instance\n * @throws Error if the entity type is not supported\n */\n static reconstructFromPlainData(\n entityType: ERPObjType,\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n switch (entityType) {\n case ERPObjType.PERSONS:\n return this.reconstructPerson(plainData);\n case ERPObjType.RESOURCES:\n return this.reconstructResource(plainData);\n case ERPObjType.PARTS:\n return this.reconstructPart(plainData);\n case ERPObjType.PART_OPERATION:\n return this.reconstructPartOperation(plainData);\n case ERPObjType.WORK_ORDERS:\n return this.reconstructWorkOrder(plainData);\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return this.reconstructWorkOrderOperation(plainData);\n case ERPObjType.LABOR_TICKETS:\n return this.reconstructLaborTicket(plainData);\n case ERPObjType.REASONS:\n return this.reconstructReason(plainData);\n default:\n throw new Error(\n `Unsupported entity type for reconstruction: ${entityType}`\n );\n }\n }\n\n /**\n * Reconstructs multiple typed objects from plain data based on entity type\n * @param entityType The type of entity being processed\n * @param plainDataArray Array of plain data objects containing entity fields\n * @returns Array of properly typed IToRESTApiObject instances\n */\n static reconstructMultipleFromPlainData(\n entityType: ERPObjType,\n plainDataArray: Record<string, string | null>[]\n ): IToRESTApiObject[] {\n return plainDataArray.map((plainData) =>\n this.reconstructFromPlainData(entityType, plainData)\n );\n }\n\n // ============================================================================\n // PRIVATE ENTITY-SPECIFIC KEY EXTRACTION METHODS\n // ============================================================================\n\n private static extractPersonKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(restApiObject, [\"personId\"], \"PERSONS\", [\n \"personId\",\n ]);\n }\n\n private static extractResourceKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"resourceId\"],\n \"RESOURCES\",\n [\"resourceId\"]\n );\n }\n\n private static extractPartKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"partNumber\", \"partRevision\", \"method\"],\n \"PARTS\",\n [\"partNumber\"]\n );\n }\n\n private static extractPartOperationKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"partNumber\", \"partRevision\", \"method\", \"sequenceNumber\"],\n \"PART_OPERATION\",\n [\"partNumber\", \"sequenceNumber\"]\n );\n }\n\n private static extractWorkOrderKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"workOrderId\", \"lot\", \"split\", \"sub\"],\n \"WORK_ORDERS\",\n [\"workOrderId\"]\n );\n }\n\n private static extractWorkOrderOperationKey(\n record: IToRESTApiObject\n ): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(\n restApiObject,\n [\"workOrderId\", \"lot\", \"split\", \"sub\", \"sequenceNumber\"],\n \"WORK_ORDER_OPERATIONS\",\n [\"workOrderId\", \"sequenceNumber\"]\n );\n }\n\n private static extractLaborTicketKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n\n // Labor tickets can use laborTicketId if present, otherwise use operational context\n if (restApiObject.laborTicketId) {\n return this.validateAndExtractKey(\n restApiObject,\n [\"laborTicketId\"],\n \"LABOR_TICKETS\",\n [\"laborTicketId\"]\n );\n } else {\n return this.validateAndExtractKey(\n restApiObject,\n [\n \"workOrderId\",\n \"lot\",\n \"split\",\n \"sub\",\n \"sequenceNumber\",\n \"resourceId\",\n \"personId\",\n ],\n \"LABOR_TICKETS\",\n [\"workOrderId\", \"sequenceNumber\", \"resourceId\", \"personId\"]\n );\n }\n }\n\n private static extractReasonKey(record: IToRESTApiObject): string {\n const restApiObject = record.toRESTApiObject();\n return this.validateAndExtractKey(restApiObject, [\"reasonId\"], \"REASONS\", [\n \"reasonId\",\n ]);\n }\n\n // ============================================================================\n // PRIVATE ENTITY-SPECIFIC RECONSTRUCTION METHODS\n // ============================================================================\n\n private static reconstructPerson(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendPerson.fromPlainObject(plainData);\n }\n\n private static reconstructResource(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendResource.fromPlainObject(plainData);\n }\n\n private static reconstructPart(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendPart.fromPlainObject(plainData);\n }\n\n private static reconstructPartOperation(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendPartOperation.fromPlainObject(plainData);\n }\n\n private static reconstructWorkOrder(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendWorkOrder.fromPlainObject(plainData);\n }\n\n private static reconstructWorkOrderOperation(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendWorkOrderOperation.fromPlainObject(plainData);\n }\n\n private static reconstructLaborTicket(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendLaborTicket.fromPlainObject(plainData);\n }\n\n private static reconstructReason(\n plainData: Record<string, string | null>\n ): IToRESTApiObject {\n return MMSendReason.fromPlainObject(plainData);\n }\n\n // ============================================================================\n // SHARED UTILITY METHODS\n // ============================================================================\n\n /**\n * Helper method to validate and extract primary key components\n * @param restApiObject The REST API object representation\n * @param keyFields The fields that make up the primary key\n * @param entityTypeName The entity type name for error messages\n * @param requiredFields The fields that must be non-empty (others can be empty)\n * @returns Combined primary key string\n * @throws Error if any required field is missing or null\n */\n private static validateAndExtractKey(\n restApiObject: Record<string, string | null>,\n keyFields: string[],\n entityTypeName: string,\n requiredFields: string[]\n ): string {\n const keyValues: string[] = [];\n\n for (const field of keyFields) {\n const value = restApiObject[field];\n\n // Check if this field is required\n if (requiredFields.includes(field)) {\n if (value === null || value === undefined || value === \"\") {\n throw new Error(\n `Primary key field '${field}' is missing or empty for entity type ${entityTypeName}`\n );\n }\n }\n\n // Use the value if present, otherwise use empty string\n keyValues.push(value || \"\");\n }\n\n return keyValues.join(\"|\");\n }\n}\n","import { ERPObjType } from \"../../types/erp-types\";\nimport { BatchCacheManager } from \"../../services/caching-service/batch-cache-manager\";\nimport {\n IToRESTApiObject,\n MM207NonLaborTicketResponse,\n MM207LaborTicketResponse,\n} from \"../../services/mm-api-service\";\nimport { EntityTransformer } from \"../../services/mm-api-service/types/entity-transformer\";\nimport logger from \"../../services/reporting-service/logger\";\n\n/**\n * Handles error processing and record management utilities for MM API operations\n */\nexport class ErrorProcessor {\n /**\n * Creates a set of primary keys for all failed records from batch errors\n * @param entityType The type of entity being processed\n * @param batchErrors Array of batch errors containing failed entities\n * @returns Set of primary keys for failed records\n */\n static createFailedRecordKeySet(\n entityType: ERPObjType,\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>\n ): Set<string> {\n const failedKeySet = new Set<string>();\n\n batchErrors.forEach((batchError) => {\n batchError.affectedEntities.forEach((affectedEntity) => {\n try {\n const primaryKey = EntityTransformer.extractPrimaryKey(\n entityType,\n affectedEntity\n );\n failedKeySet.add(primaryKey);\n } catch (error) {\n logger.warn(\n `Failed to extract primary key from error entity: ${error}`\n );\n // Continue processing other records even if one fails\n }\n });\n });\n\n return failedKeySet;\n }\n\n /**\n * Filters out failed records, returning only successful ones\n * @param entityType The type of entity being processed\n * @param allRecords All records (typed objects) that were sent to the API\n * @param failedKeySet Set of primary keys for records that failed\n * @returns Array of records that succeeded\n */\n static filterSuccessfulRecords(\n entityType: ERPObjType,\n allRecords: IToRESTApiObject[],\n failedKeySet: Set<string>\n ): IToRESTApiObject[] {\n const successfulRecords: IToRESTApiObject[] = [];\n\n allRecords.forEach((record) => {\n try {\n const primaryKey = EntityTransformer.extractPrimaryKey(\n entityType,\n record\n );\n if (!failedKeySet.has(primaryKey)) {\n successfulRecords.push(record);\n }\n } catch (error) {\n logger.warn(\n `Failed to extract primary key from record during filtering: ${error}`\n );\n // If we can't extract the key, we can't determine if it failed, so skip it\n }\n });\n\n return successfulRecords;\n }\n\n /**\n * Orchestrates the caching of successful records on partial failure\n * @param entityType The type of entity being processed\n * @param toProcess All records that were sent to the API (all are now guaranteed to be typed objects)\n * @param batchErrors Array of batch errors containing failed entities\n * @param batchCacheManager The cache manager instance\n */\n static async cacheSuccessfulRecordsOnPartialFailure(\n entityType: ERPObjType,\n toProcess: IToRESTApiObject[],\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>,\n batchCacheManager: BatchCacheManager\n ): Promise<void> {\n // Create a set of failed record keys for efficient lookup\n const failedKeySet = this.createFailedRecordKeySet(entityType, batchErrors);\n\n // Filter to get only successful records\n const successfulRecords = this.filterSuccessfulRecords(\n entityType,\n toProcess,\n failedKeySet\n );\n\n logger.info(\n `Caching ${successfulRecords.length} successful records out of ${toProcess.length} total records`\n );\n\n // All records are now guaranteed to be typed objects, so we can cache them directly\n if (successfulRecords.length > 0) {\n await batchCacheManager.storeBatch(entityType, successfulRecords);\n }\n }\n\n /**\n * Extracts error count and batch errors from MM API response for partial failures (HTTP 207)\n * This supports all entities, including the slightly different format for labor tickets.\n * In case of labor tickets, the updateErrors and insertErrors arrays are combined into affectedEntities.\n * @param mmApiResponse The full MM API response object\n * @param entityType The type of entity being processed (determines response structure)\n * @returns Object containing errorCount and batchErrors\n * See MM207NonLaborTicketResponse and MM207LaborTicketResponse for response structure details\n */\n static extractErrorDetails(\n mmApiResponse: MM207NonLaborTicketResponse | MM207LaborTicketResponse,\n entityType: ERPObjType\n ): {\n errorCount: number;\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n } {\n // Type the data property with the expected structure for HTTP 207 responses\n const data = mmApiResponse.data as\n | {\n errors?: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }>;\n updateErrors?: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }>;\n insertErrors?: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }>;\n }\n | undefined;\n\n let allErrors: Array<{\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }> = [];\n\n if (entityType === ERPObjType.LABOR_TICKETS) {\n // Labor tickets: combine updateErrors and insertErrors\n const updateErrors = data?.updateErrors || [];\n const insertErrors = data?.insertErrors || [];\n\n // Defensive validation with actionable warnings\n if (!data?.updateErrors && !data?.insertErrors) {\n logger.warn(\n \"Labor tickets partial success response missing both updateErrors and insertErrors arrays\"\n );\n }\n\n allErrors = [...updateErrors, ...insertErrors];\n } else {\n // Regular entities: use errors array\n const errors = data?.errors || [];\n\n // Defensive validation\n if (!data?.errors) {\n logger.warn(\n `${entityType} partial success response missing errors array`\n );\n }\n\n allErrors = errors;\n }\n\n const batchErrors = allErrors\n .filter(\n (error: {\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }) => {\n return error.batchData && error.batchData.length > 0;\n }\n )\n .map(\n (error: {\n message: string;\n batchData?: (IToRESTApiObject | Record<string, string | null>)[];\n }) => {\n // Reconstruct typed objects from plain data\n const typedErrorEntities = (error.batchData || []).map((entity) => {\n if (\n typeof entity === \"object\" &&\n entity !== null &&\n \"toRESTApiObject\" in entity &&\n typeof entity.toRESTApiObject === \"function\"\n ) {\n // It's already a typed object, return as-is\n return entity as IToRESTApiObject;\n } else {\n // It's plain data, reconstruct as typed object\n return EntityTransformer.reconstructFromPlainData(\n entityType,\n entity as Record<string, string | null>\n );\n }\n });\n\n return {\n message: error.message,\n affectedEntities: typedErrorEntities,\n };\n }\n );\n\n const errorCount = batchErrors.reduce((total: number, batchError) => {\n return total + batchError.affectedEntities.length;\n }, 0);\n\n return {\n errorCount,\n batchErrors,\n };\n }\n\n /**\n * Extracts error details from a 500 HTTP exception when it contains structured error data\n * @param exception The caught exception from MM API call\n * @param entityType The type of entity being processed\n * @returns Object containing errorCount and batchErrors, or null if not a structured 500 error\n * See MM500NonLaborTicketException and MM500LaborTicketException for exception structure details\n */\n static extractErrorDetailsFrom500Exception(\n exception: unknown,\n entityType: ERPObjType\n ): {\n errorCount: number;\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n } | null {\n try {\n // Cast exception to expected structure for easier access\n const ex = exception as Record<string, unknown>;\n const data = ex?.data as Record<string, unknown>;\n\n // Add diagnostic logging to understand the 500 error structure\n logger.info(\n \"writeEntitiesToMM: Analyzing 500 exception structure for diagnostic purposes\",\n {\n status: ex?.status,\n code: ex?.code,\n hasResponseData: !!data,\n responseDataKeys: data ? Object.keys(data) : [],\n errorMessage: data?.error,\n hasMessageObject: !!data?.message,\n messageObjectKeys: data?.message\n ? Object.keys(data.message as Record<string, unknown>)\n : [],\n entityType,\n exceptionType: typeof exception,\n exceptionKeys: ex ? Object.keys(ex) : [],\n }\n );\n\n // Check if this is a structured 500 error\n if (\n ex?.status !== 500 ||\n typeof data?.error !== \"string\" ||\n !data.error.startsWith(\"Failed to import\")\n ) {\n logger.info(\n \"writeEntitiesToMM: Not a structured 500 error - will re-throw exception as-is\",\n {\n status: ex?.status,\n errorMessage: data?.error,\n expectedStatus: 500,\n expectedMessagePrefix: \"Failed to import\",\n }\n );\n return null;\n }\n\n logger.info(\n \"writeEntitiesToMM: Detected structured 500 error - extracting error details\"\n );\n\n const messageObject = data?.message as Record<string, unknown>;\n if (!messageObject) {\n logger.warn(\n \"writeEntitiesToMM: Structured 500 error missing message object\"\n );\n return null;\n }\n\n let allErrors: unknown[] = [];\n\n if (entityType === ERPObjType.LABOR_TICKETS) {\n // Labor tickets: combine updateErrors and insertErrors\n const updateErrors = Array.isArray(messageObject?.updateErrors)\n ? messageObject.updateErrors\n : [];\n const insertErrors = Array.isArray(messageObject?.insertErrors)\n ? messageObject.insertErrors\n : [];\n\n logger.info(\"writeEntitiesToMM: Processing labor tickets 500 error\", {\n updateErrorsCount: updateErrors.length,\n insertErrorsCount: insertErrors.length,\n });\n\n if (updateErrors.length === 0 && insertErrors.length === 0) {\n logger.warn(\n \"writeEntitiesToMM: Labor tickets 500 error missing both updateErrors and insertErrors arrays\"\n );\n }\n\n allErrors = [...updateErrors, ...insertErrors];\n } else {\n // Regular entities: use errors array\n const errors = Array.isArray(messageObject?.errors)\n ? messageObject.errors\n : [];\n\n logger.info(\"writeEntitiesToMM: Processing regular entity 500 error\", {\n errorsCount: errors.length,\n });\n\n if (errors.length === 0) {\n logger.warn(\n `writeEntitiesToMM: ${entityType} 500 error missing errors array`\n );\n }\n\n allErrors = errors;\n }\n\n const batchErrors = allErrors\n .filter((error) => {\n const err = error as Record<string, unknown>;\n return Array.isArray(err?.batchData) && err.batchData.length > 0;\n })\n .map((error) => {\n const err = error as Record<string, unknown>;\n const batchData = err?.batchData as (\n | IToRESTApiObject\n | Record<string, string | null>\n )[];\n\n // Reconstruct typed objects from plain data\n const typedErrorEntities = (batchData || []).map((entity) => {\n if (\n typeof entity === \"object\" &&\n entity !== null &&\n \"toRESTApiObject\" in entity &&\n typeof entity.toRESTApiObject === \"function\"\n ) {\n // It's already a typed object, return as-is\n return entity as IToRESTApiObject;\n } else {\n // It's plain data, reconstruct as typed object\n return EntityTransformer.reconstructFromPlainData(\n entityType,\n entity as Record<string, string | null>\n );\n }\n });\n\n return {\n message:\n typeof err?.message === \"string\" ? err.message : \"Unknown error\",\n affectedEntities: typedErrorEntities,\n };\n });\n\n const errorCount = batchErrors.reduce((total: number, batchError) => {\n return total + batchError.affectedEntities.length;\n }, 0);\n\n logger.info(\"writeEntitiesToMM: Extracted 500 error details\", {\n batchErrorsCount: batchErrors.length,\n totalErrorCount: errorCount,\n entityType,\n });\n\n return {\n errorCount,\n batchErrors,\n };\n } catch (error) {\n // If we can't even parse the structure safely, log what we can and return null\n logger.error(\n \"writeEntitiesToMM: Failed to parse 500 exception structure safely\",\n {\n error: error instanceof Error ? error.message : String(error),\n exceptionType: typeof exception,\n entityType,\n }\n );\n return null;\n }\n }\n}\n","import { ERPObjType } from \"../../types/erp-types\";\nimport { BatchCacheManager } from \"../../services/caching-service/batch-cache-manager\";\nimport {\n IToRESTApiObject,\n MMSendPerson,\n MMSendResource,\n MMSendPart,\n MMSendPartOperation,\n MMSendWorkOrder,\n MMSendWorkOrderOperation,\n MMSendLaborTicket,\n MMSendReason,\n MMApiBaseResponse,\n MM200NonLaborTicketResponse,\n MM200LaborTicketResponse,\n MM207NonLaborTicketResponse,\n MM207LaborTicketResponse,\n} from \"../../services/mm-api-service\";\nimport { MMApiClient } from \"../../services/mm-api-service/mm-api-service\";\nimport { HTTPError } from \"../http-client\";\nimport {\n WriteEntitiesToMMResult,\n MMBatchValidationError,\n} from \"./standard-process-drivers\";\nimport { ErrorProcessor } from \"./error-processor\";\n\n/**\n * Handles processing of entities to the MachineMetrics API\n */\nexport class MMEntityProcessor {\n /**\n * Writes entities to MM API with deduplication and caching\n */\n static async writeEntities(\n entityType: ERPObjType,\n mmRecords: IToRESTApiObject[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<WriteEntitiesToMMResult> {\n const { toProcess, result } = await this._prepareAndDedupe(\n entityType,\n mmRecords,\n batchCacheManager\n );\n\n if (toProcess.length === 0) {\n result.message =\n \"All records were deduplicated locally - no records sent to MM API\";\n return result;\n }\n\n let mmApiResponse: MMApiBaseResponse;\n try {\n mmApiResponse = await this._sendToAPI(entityType, toProcess);\n } catch (exception) {\n this._handleException(exception, entityType, result);\n }\n\n return await this._handleResponse(\n mmApiResponse,\n entityType,\n toProcess,\n result,\n batchCacheManager\n );\n }\n\n // ============================================================================\n // PRIVATE HELPER METHODS\n // ============================================================================\n\n /**\n * Deduplicates records against cache\n * Returns the records to process and the result object\n * If the batchCacheManager is provided, the records are deduplicated against the cache\n * If the batchCacheManager is not provided, the records are not deduplicated\n */\n private static async _prepareAndDedupe(\n entityType: ERPObjType,\n mmRecords: IToRESTApiObject[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<{\n toProcess: IToRESTApiObject[];\n result: WriteEntitiesToMMResult;\n }> {\n let toProcess: IToRESTApiObject[] = [];\n const result: WriteEntitiesToMMResult = {\n message: \"\",\n upsertedEntities: 0,\n localDedupeCount: 0,\n apiDedupeCount: 0,\n };\n\n if (batchCacheManager) {\n const { nonDuplicates, duplicates } =\n (await batchCacheManager.dedupeBatch(entityType, mmRecords)) as {\n nonDuplicates: IToRESTApiObject[];\n duplicates: IToRESTApiObject[];\n };\n toProcess = nonDuplicates;\n result.localDedupeCount = duplicates.length;\n } else {\n toProcess = mmRecords;\n }\n\n return { toProcess, result };\n }\n\n /**\n * Sends records to MM API\n */\n private static async _sendToAPI(\n entityType: ERPObjType,\n toProcess: IToRESTApiObject[]\n ): Promise<MMApiBaseResponse> {\n const mmApiClient = new MMApiClient();\n\n switch (entityType) {\n case ERPObjType.PERSONS:\n return await mmApiClient.sendPersonsToMM(toProcess as MMSendPerson[]);\n case ERPObjType.RESOURCES:\n return await mmApiClient.sendResourcesToMM(\n toProcess as MMSendResource[]\n );\n case ERPObjType.PARTS:\n return await mmApiClient.sendPartsToMM(toProcess as MMSendPart[]);\n case ERPObjType.PART_OPERATION:\n return await mmApiClient.sendPartOperationsToMM(\n toProcess as MMSendPartOperation[]\n );\n case ERPObjType.WORK_ORDERS:\n return await mmApiClient.sendWorkOrdersToMM(\n toProcess as MMSendWorkOrder[]\n );\n case ERPObjType.WORK_ORDER_OPERATIONS:\n return await mmApiClient.sendWorkOrderOperationsToMM(\n toProcess as MMSendWorkOrderOperation[]\n );\n case ERPObjType.LABOR_TICKETS:\n return await mmApiClient.sendLaborTicketsToMM(\n toProcess as MMSendLaborTicket[]\n );\n case ERPObjType.REASONS:\n return await mmApiClient.sendReasonsToMM(toProcess as MMSendReason[]);\n default:\n throw new Error(`Unknown entity type: ${entityType}`);\n }\n }\n\n /**\n * Handles exceptions from API calls, converting structured 500 errors to MMBatchValidationError\n * @throws MMBatchValidationError for structured 500 errors\n * @throws The original exception for all other errors\n */\n private static _handleException(\n exception: unknown,\n entityType: ERPObjType,\n result: WriteEntitiesToMMResult\n ): never {\n // Handle structured 500 errors that may be appropriate for partial failure\n const structuredCompleteErrorSet =\n ErrorProcessor.extractErrorDetailsFrom500Exception(exception, entityType);\n\n if (structuredCompleteErrorSet) {\n const ex = exception as Record<string, unknown>;\n const data = ex?.data as Record<string, unknown>;\n const errorMessage =\n typeof data?.error === \"string\"\n ? data.error\n : \"All entities failed to import\";\n\n throw new MMBatchValidationError({\n message: errorMessage,\n upsertedEntities: 0,\n localDedupeCount: result.localDedupeCount,\n apiDedupeCount: 0,\n errorCount: structuredCompleteErrorSet.errorCount,\n httpStatus: (exception as HTTPError).status,\n batchErrors: structuredCompleteErrorSet.batchErrors,\n });\n }\n\n // For all other exceptions, re-throw as-is\n throw exception;\n }\n\n /**\n * Handles the MM API response based on HTTP status code, caching successful records\n * and throwing exceptions for partial failures\n *\n * @returns The final result for success cases\n * @throws MMBatchValidationError for partial success cases\n * @throws Error for unknown status codes\n */\n private static async _handleResponse(\n mmApiResponse: MMApiBaseResponse,\n entityType: ERPObjType,\n toProcess: IToRESTApiObject[],\n result: WriteEntitiesToMMResult,\n batchCacheManager: BatchCacheManager | null\n ): Promise<WriteEntitiesToMMResult> {\n if (mmApiResponse.httpStatus === 200) {\n // Complete success - cache all records\n if (batchCacheManager) {\n await batchCacheManager.storeBatch(entityType, toProcess);\n }\n\n if (entityType === ERPObjType.LABOR_TICKETS) {\n const success = mmApiResponse as MM200LaborTicketResponse;\n result.message = success.message || \"Entities processed successfully\";\n result.upsertedEntities =\n (success.updated || 0) + (success.inserted || 0);\n } else {\n const success = mmApiResponse as MM200NonLaborTicketResponse;\n result.message = success.message || \"Entities processed successfully\";\n result.upsertedEntities = success.affectedRows || 0;\n }\n result.apiDedupeCount = toProcess.length - result.upsertedEntities;\n\n return result;\n } else if (mmApiResponse.httpStatus === 207) {\n // Partial success - cache only successful records before throwing exception\n const partialResponse = mmApiResponse as\n | MM207NonLaborTicketResponse\n | MM207LaborTicketResponse;\n const { errorCount, batchErrors } = ErrorProcessor.extractErrorDetails(\n partialResponse,\n entityType\n );\n\n if (batchCacheManager) {\n await ErrorProcessor.cacheSuccessfulRecordsOnPartialFailure(\n entityType,\n toProcess,\n batchErrors,\n batchCacheManager\n );\n }\n\n let upsertedEntities = 0;\n if (entityType === ERPObjType.LABOR_TICKETS) {\n const partial = partialResponse as MM207LaborTicketResponse;\n upsertedEntities =\n (partial.data.updated || 0) + (partial.data.inserted || 0);\n } else {\n const partial = partialResponse as MM207NonLaborTicketResponse;\n upsertedEntities = partial.data.affectedRows || 0;\n }\n\n throw new MMBatchValidationError({\n message:\n partialResponse.message || \"Entities processed with partial failures\",\n upsertedEntities: upsertedEntities,\n localDedupeCount: result.localDedupeCount,\n apiDedupeCount: toProcess.length - upsertedEntities - errorCount,\n errorCount: errorCount,\n httpStatus: mmApiResponse.httpStatus,\n batchErrors: batchErrors,\n });\n } else {\n throw new Error(\n `writeEntitiesToMM: Unknown HTTP status code: ${mmApiResponse.httpStatus}. An exception was expected.`\n );\n }\n }\n}\n","import { ERPObjType } from \"../../types/erp-types\";\nimport { IERPLaborTicketHandler } from \"../../types/erp-connector\";\nimport { BatchCacheManager } from \"../../services/caching-service/batch-cache-manager\";\nimport {\n IToRESTApiObject,\n MMSendWorkOrderBatch,\n MMSendPart,\n MMSendPartOperation,\n MMSendWorkOrder,\n MMSendWorkOrderOperation,\n} from \"../../services/mm-api-service\";\nimport { LaborTicketERPSynchronizer } from \"./labor-ticket-erp-synchronizer\";\nimport { MMEntityProcessor } from \"./mm-entity-processor\";\n\n/**\n * The result of writing entities to the MachineMetrics API on complete success.\n */\nexport interface WriteEntitiesToMMResult {\n message: string;\n upsertedEntities: number;\n localDedupeCount: number;\n apiDedupeCount: number;\n}\n\nexport class MMBatchValidationError extends Error {\n public readonly upsertedEntities: number;\n public readonly localDedupeCount: number;\n public readonly apiDedupeCount: number;\n public readonly errorCount: number;\n public readonly httpStatus: number;\n public readonly batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n\n constructor(options: {\n message: string;\n upsertedEntities: number;\n localDedupeCount: number;\n apiDedupeCount: number;\n errorCount: number;\n httpStatus: number;\n batchErrors: Array<{\n message: string;\n affectedEntities: IToRESTApiObject[]; // All entities in the failing batch\n }>;\n }) {\n super(options.message);\n this.name = \"MMBatchValidationError\";\n this.upsertedEntities = options.upsertedEntities;\n this.localDedupeCount = options.localDedupeCount;\n this.apiDedupeCount = options.apiDedupeCount;\n this.errorCount = options.errorCount;\n this.httpStatus = options.httpStatus;\n this.batchErrors = options.batchErrors;\n }\n}\n\n/**\n * A collection of standardized process drivers that orchestrate various synchronization\n * and integration processes between systems. Use of these drivers is optional,\n * and useful as a reference implementation for how to implement the various processes.\n */\nexport class StandardProcessDrivers {\n /**\n * Synchronizes updated labor tickets from MachineMetrics to an ERP system:\n *\n * Initializes a checkpoint that tracks the last time labor tickets were synced,\n * fetches labor tickets from MM and converts them to the company timezone,\n * creates or updates the labor ticket in the ERP via connector-specific implementations,\n * updates the labor ticket id in MM on newly created labor tickets,\n * and saves the checkpoint\n */\n static async syncLaborTicketsToERP(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n return LaborTicketERPSynchronizer.syncToERP(connectorType, connector);\n }\n\n /**\n * Retries labor tickets that have failed to be created or updated in the ERP during the sync:\n *\n * Fetches failed labor tickets from MM, processes them, and updates the labor ticket id in MM.\n * If the labor ticket is successfully created or updated, it is added to the list of successful labor ticket ids,\n * which are then deleted from the list of failed labor ticket ids.\n */\n static async retryFailedLaborTickets(\n connectorType: string,\n connector: IERPLaborTicketHandler\n ): Promise<void> {\n return LaborTicketERPSynchronizer.retryFailed(connectorType, connector);\n }\n\n /**\n * Writes a batch of records to MM for a specific entity type by:\n * 1. Deduplicating against cached records (localDedupeCount)\n * 2. Sending non-duplicate records to the MM API (apiDedupeCount)\n * 3. Storing successful records in cache\n *\n * An example usage pattern including comprehensive error handling:\n * ```\n * try {\n * const result = await StandardProcessDrivers.writeEntitiesToMM(\n * 'WorkOrders',\n * workOrderRecords,\n * batchCacheManager\n * );\n *\n * // HTTP 200 - Complete success\n * console.log(`✅ Success: ${result.message}`);\n * console.log(`📊 Metrics: ${result.upsertedEntities} upserted, ${result.localDedupeCount} locally deduplicated, ${result.apiDedupeCount} API deduplicated`);\n *\n * } catch (error) {\n * if (error instanceof MMBatchValidationError) {\n * // HTTP 207 - Partial success with some batches failing due to validation errors\n * // HTTP 500 - A specific type representing complete failure due to validation issues;\n * // other 500 types represent failure due to other issues and are not converted to MMBatchValidationError\n * // Note: Each batch error contains ALL entities from the failing batch, not necessarily just the failed ones\n *\n * console.log(`⚠️ Batch processing completed with errors (HTTP ${error.httpStatus})`);\n * console.log(`📊 Metrics: ${error.upsertedEntities} upserted, ${error.localDedupeCount} locally deduplicated, ${error.apiDedupeCount} API deduplicated`);\n * console.log(`❌ Error count: ${error.errorCount}`);\n *\n * // Process specific batch errors for retry or logging\n * error.batchErrors.forEach((batchError, index) => {\n * console.log(`Batch ${index + 1} error: ${batchError.message}`);\n * console.log(`All entities in failing batch:`, batchError.affectedEntities);\n *\n * // Example: Queue entire failing batch for retry (contains both successful and failed entities)\n * await queueForRetry(batchError.affectedEntities);\n * });\n *\n * // Decide whether to continue or halt based on httpStatus\n * if (error.httpStatus === 207) {\n * // Partial success - some batches processed successfully, others failed\n * console.log('⚠️ Continuing with partial success');\n * } else if (error.httpStatus === 500) {\n * // Complete failure - all batches failed due to validation issues\n * console.log('🛑 Complete failure - no records processed');\n * throw error; // Re-throw if complete failure should halt the process\n * }\n *\n * } else {\n * // Other underlying errors (network issues, authentication, etc.)\n * console.error('🚨 Unexpected error during MM API call:', error);\n *\n * // Example: Check for specific error types\n * if (error.message?.includes('authentication')) {\n * console.error('🔐 Authentication issue - check MM API credentials');\n * } else if (error.message?.includes('network') || error.code === 'ECONNREFUSED') {\n * console.error('🌐 Network connectivity issue - check MM API endpoint');\n * }\n *\n * throw error; // Re-throw for upstream handling\n * }\n * }\n * ```\n *\n * @param entityType The type of entity being processed\n * @param mmRecords The records to process\n * @param batchCacheManager The batch cache manager instance; pass in null if caching is not desired\n *\n * @returns WriteEntitiesToMMResult on complete success (HTTP 200)\n * @throws MMBatchValidationError on partial success (HTTP 207) or complete failure (HTTP 500) with structured error details\n * @throws Error on other underlying issues (network, authentication, etc.)\n */\n static async writeEntitiesToMM(\n entityType: ERPObjType,\n mmRecords: IToRESTApiObject[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<WriteEntitiesToMMResult> {\n return MMEntityProcessor.writeEntities(\n entityType,\n mmRecords,\n batchCacheManager\n );\n }\n\n /**\n * Writes a batch of Work Order related entities to MM maintaining referential integrity by:\n * 1. Processing parts first (base entities)\n * 2. Processing part operations (depends on parts)\n * 3. Processing work orders (depends on parts)\n * 4. Processing work order operations (depends on work orders and part operations)\n *\n * This ensures all foreign key constraints are satisfied and prevents referential integrity errors.\n *\n * @param workOrderBatch The batch containing all related entities\n * @param batchCacheManager The batch cache manager instance; pass in null if caching is not desired\n *\n * @returns Combined results from all entity processing\n * @throws MMBatchValidationError on partial success or complete failure with structured error details\n * @throws Error on other underlying issues (network, authentication, etc.)\n */\n static async writeWorkOrderBatchToMM(\n workOrderBatch: MMSendWorkOrderBatch,\n batchCacheManager: BatchCacheManager | null\n ): Promise<{\n parts: WriteEntitiesToMMResult;\n partOperations: WriteEntitiesToMMResult;\n workOrders: WriteEntitiesToMMResult;\n workOrderOperations: WriteEntitiesToMMResult;\n }> {\n // Process entities in order to maintain referential integrity\n\n // 1. Parts first (no dependencies)\n const partsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PARTS,\n workOrderBatch.parts,\n batchCacheManager\n );\n\n // 2. Part Operations (depends on parts)\n const partOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PART_OPERATION,\n workOrderBatch.partOperations,\n batchCacheManager\n );\n\n // 3. Work Orders (depends on parts)\n const workOrdersResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDERS,\n workOrderBatch.workOrders,\n batchCacheManager\n );\n\n // 4. Work Order Operations (depends on work orders and part operations)\n const workOrderOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDER_OPERATIONS,\n workOrderBatch.workOrderOperations,\n batchCacheManager\n );\n\n return {\n parts: partsResult,\n partOperations: partOperationsResult,\n workOrders: workOrdersResult,\n workOrderOperations: workOrderOperationsResult,\n };\n }\n\n /**\n * Processes flattened work order data and syncs it to MM maintaining referential integrity.\n *\n * This method expects flattened data where each row contains both work order and operation information\n * with camelCase field names matching the GraphQL schema. It will automatically extract and deduplicate\n * parts, part operations, work orders, and work order operations, then process them in the correct order\n * to maintain referential integrity.\n *\n * @param flattenedData Array of flattened rows containing both work order and operation data (camelCase fields)\n * @param batchCacheManager The batch cache manager instance; pass in null if caching is not desired\n *\n * @returns Combined results from all entity processing with detailed logging information\n * @throws MMBatchValidationError on partial success or complete failure with structured error details\n * @throws Error on other underlying issues (network, authentication, etc.)\n */\n static async syncWorkOrderBatchFromFlattened(\n flattenedData: any[],\n batchCacheManager: BatchCacheManager | null\n ): Promise<{\n parts: WriteEntitiesToMMResult;\n partOperations: WriteEntitiesToMMResult;\n workOrders: WriteEntitiesToMMResult;\n workOrderOperations: WriteEntitiesToMMResult;\n }> {\n if (!flattenedData || flattenedData.length === 0) {\n throw new Error(\"No flattened work order data provided\");\n }\n\n // Process the flattened data - each row contains both work order and operation info\n const uniqueParts = new Map();\n const uniquePartOperations = new Map();\n const uniqueWorkOrders = new Map();\n const workOrderOperations = [];\n\n // Process each flattened row to extract entities\n for (const row of flattenedData) {\n // Extract unique parts\n const partKey = `${row.partNumber}|${row.partRevision}|${row.method}`;\n if (!uniqueParts.has(partKey)) {\n uniqueParts.set(partKey, {\n partNumber: row.partNumber, // → partNumber\n partRevision: row.partRevision, // → partRevision\n method: row.method, // → method\n });\n }\n\n // Extract unique part operations\n const partOpKey = `${row.partNumber}|${row.partRevision}|${row.method}|${row.sequenceNumber}`;\n if (!uniquePartOperations.has(partOpKey)) {\n uniquePartOperations.set(partOpKey, {\n partNumber: row.partNumber, // → partNumber\n partRevision: row.partRevision, // → partRevision\n method: row.method, // → method\n sequenceNumber: row.sequenceNumber, // → sequenceNumber\n resourceId: row.resourceId, // → resourceId\n cycleTimeMs: row.cycleTimeMs, // → cycleTimeMs\n setupTimeMs: row.setupTimeMs, // → setupTimeMs\n description: row.operationDescription || \"\", // → description\n quantityPerPart: row.quantityPerPart || 1, // → quantityPerPart\n });\n }\n\n // Extract unique work orders\n const workOrderKey = row.workOrderId;\n if (!uniqueWorkOrders.has(workOrderKey)) {\n uniqueWorkOrders.set(workOrderKey, {\n workOrderId: row.workOrderId, // → workOrderId\n lot: row.lot, // → lot\n split: row.split, // → split\n sub: row.sub, // → sub\n status: row.status, // → status\n dueDate: row.dueDate, // → dueDate\n description: row.description, // → description\n scheduledStartDate: row.scheduledStartDate, // → scheduledStartDate\n scheduledEndDate: row.scheduledEndDate, // → scheduledEndDate\n closedDate: row.closedDate, // → closedDate\n quantityRequired: row.quantityRequired, // → quantityRequired\n partNumber: row.partNumber, // → partNumber\n partRevision: row.partRevision, // → partRevision\n method: row.method, // → method\n });\n }\n\n // Add each work order operation (these are not deduplicated since each operation is unique)\n workOrderOperations.push({\n workOrderId: row.workOrderId, // → workOrderId\n lot: row.lot, // → lot\n split: row.split, // → split\n sub: row.sub, // → sub\n sequenceNumber: row.sequenceNumber, // → sequenceNumber\n resourceId: row.resourceId, // → resourceId\n startQuantity: row.startQuantity, // → startQuantity\n finishQuantity: row.finishQuantity, // → finishQuantity\n expectedRejectRate: row.expectedRejectRate, // → expectedRejectRate\n scheduledStartDate: row.opScheduledStartDate, // → scheduledStartDate\n scheduledFinishDate: row.opScheduledFinishDate, // → scheduledFinishDate\n closedDate: row.opClosedDate, // → closedDate\n cycleTimeMs: row.cycleTimeMs, // → cycleTimeMs\n setupTimeMs: row.setupTimeMs, // → setupTimeMs\n productionburdenRateHourly: row.productionburdenRateHourly, // → productionburdenRateHourly\n setupburdenRatehourly: row.setupburdenRatehourly, // → setupburdenRatehourly\n operationType: row.operationType, // → operationType\n quantityPerPart: row.quantityPerPart, // → quantityPerPart\n status: row.opStatus || row.status, // → status\n });\n }\n\n // Transform all the data directly to MM entities\n const parts = Array.from(uniqueParts.values()).map(\n (item) =>\n new MMSendPart(\n item.partNumber || \"\", // partNumber\n item.partRevision || \"\", // partRevision\n item.method || \"Standard\" // method\n )\n );\n\n const partOperations = Array.from(uniquePartOperations.values()).map(\n (item) =>\n new MMSendPartOperation(\n item.partNumber || \"\", // partNumber\n item.partRevision || \"\", // partRevision\n item.method || \"Standard\", // method\n item.sequenceNumber?.toString() || \"\", // sequenceNumber\n item.resourceId?.toString() || \"\", // resourceId\n item.cycleTimeMs || 0, // cycleTimeMs\n item.setupTimeMs || 0, // setupTimeMs\n item.description || \"\", // description\n item.quantityPerPart || 1 // quantityPerPart\n )\n );\n\n const transformedWorkOrders = Array.from(uniqueWorkOrders.values()).map(\n (item) =>\n new MMSendWorkOrder(\n item.workOrderId?.toString() || \"\", // workOrderId\n item.lot || \"\", // lot\n item.split || \"\", // split\n item.sub || \"\", // sub\n item.status || \"Open\", // status\n item.dueDate ? new Date(item.dueDate).toISOString() : null, // dueDate\n item.description || \"\", // description\n item.scheduledStartDate\n ? new Date(item.scheduledStartDate).toISOString()\n : null, // scheduledStartDate\n item.scheduledEndDate\n ? new Date(item.scheduledEndDate).toISOString()\n : null, // scheduledEndDate\n item.closedDate ? new Date(item.closedDate).toISOString() : null, // closedDate\n item.quantityRequired || 0, // quantityRequired\n item.partNumber || \"\", // partNumber\n item.partRevision || \"\", // partRevision\n item.method || \"Standard\" // method\n )\n );\n\n const transformedWorkOrderOperations = workOrderOperations.map(\n (item) =>\n new MMSendWorkOrderOperation(\n item.workOrderId?.toString() || \"\", // workOrderId\n item.lot || \"\", // lot\n item.split || \"\", // split\n item.sub || \"\", // sub\n item.sequenceNumber?.toString() || \"\", // sequenceNumber\n item.resourceId?.toString() || \"\", // resourceId\n item.startQuantity || 0, // startQuantity\n item.finishQuantity || 0, // finishQuantity\n item.expectedRejectRate || 0, // expectedRejectRate\n item.scheduledStartDate\n ? new Date(item.scheduledStartDate).toISOString()\n : null, // scheduledStartDate\n item.scheduledFinishDate\n ? new Date(item.scheduledFinishDate).toISOString()\n : null, // scheduledFinishDate\n item.closedDate ? new Date(item.closedDate).toISOString() : null, // closedDate\n item.cycleTimeMs || 0, // cycleTimeMs\n item.setupTimeMs || 0, // setupTimeMs\n parseFloat(item.productionburdenRateHourly || \"0\"), // productionburdenRateHourly\n parseFloat(item.setupburdenRatehourly || \"0\"), // setupburdenRatehourly\n item.operationType || \"Production\", // operationType\n item.quantityPerPart || 1, // quantityPerPart\n item.status || \"Open\" // status\n )\n );\n\n // Send each entity type individually in the correct order for referential integrity\n const partsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PARTS,\n parts,\n batchCacheManager\n );\n\n const partOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.PART_OPERATION,\n partOperations,\n batchCacheManager\n );\n\n const workOrdersResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDERS,\n transformedWorkOrders,\n batchCacheManager\n );\n\n const workOrderOperationsResult = await MMEntityProcessor.writeEntities(\n ERPObjType.WORK_ORDER_OPERATIONS,\n transformedWorkOrderOperations,\n batchCacheManager\n );\n\n return {\n parts: partsResult,\n partOperations: partOperationsResult,\n workOrders: workOrdersResult,\n workOrderOperations: workOrderOperationsResult,\n };\n }\n}\n","import { HTTPClient, HTTPClientFactory } from \"../../../utils/http-client\";\nimport {\n QueryParams,\n buildLogicalCondition,\n Pagination,\n} from \"./get-query-params\";\nimport { ErrorHandler } from \"../errors\";\nimport { APIResponse, ERPApiConfig } from \"../types\";\n\nconst DEFAULT_RETRY_ATTEMPTS = 0; // One try only, no retries\nconst DEFAULT_PAGINATION_LIMIT = 100; // Reasonable default for most APIs\n\n/**\n * RestAPIService is a utility class for making REST API requests to an ERP system.\n * Provides methods for GET, PATCH, and POST requests, with built-in pagination support.\n *\n * @param config - Configuration for the ERP API connection\n * @param retryAttempts - Optional number of retry attempts for failed requests. Defaults to 0 (no retries)\n * @param paginationLimit - Optional default limit for paginated requests. Defaults to 100 items per page\n *\n * NOTE: assumes bearertoken-based authentication.\n */\nexport class RestAPIService {\n private client: HTTPClient;\n private config: ERPApiConfig;\n private paginationLimit: number;\n\n constructor(\n config: ERPApiConfig,\n retryAttempts?: number,\n paginationLimit?: number\n ) {\n this.config = config;\n this.paginationLimit = paginationLimit ?? DEFAULT_PAGINATION_LIMIT;\n this.client = HTTPClientFactory.getInstance({\n baseUrl: config.apiUrl,\n retryAttempts: retryAttempts ?? DEFAULT_RETRY_ATTEMPTS,\n });\n }\n\n //#region Get Request Helpers\n private async getHeaders(): Promise<Record<string, string>> {\n const token = await this.config.getAuthToken();\n return {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n };\n }\n\n private buildQueryParams(query: QueryParams = {}): URLSearchParams {\n const { limit, nextPage, filter } = query;\n const queryParams: Record<string, string> = {};\n\n if (limit) {\n queryParams.take = limit.toString();\n }\n if (nextPage) {\n queryParams.skip = nextPage;\n }\n\n if (filter) {\n if (filter.in) {\n filter.in.forEach((filterIn) => {\n const query = filterIn.value.join(\"|\");\n queryParams[`${filterIn.field}[in]`] = query;\n });\n }\n\n if (filter.and) {\n queryParams[\"filter[and]\"] = buildLogicalCondition(filter.and, \"and\");\n }\n\n if (filter.or) {\n queryParams[\"filter[or]\"] = buildLogicalCondition(filter.or, \"or\");\n }\n }\n\n return new URLSearchParams(queryParams);\n }\n\n /**\n * Builds pagination metadata for API responses\n * @param limit - Optional limit for this specific request. If not provided, uses the service's default pagination limit\n * @param nextPage - Optional next page token\n * @param previousPage - Optional previous page token\n * @param dataLength - Number of items in the current response\n * @param totalCount - Total number of items available\n * @returns Pagination metadata including count, limit, and page tokens\n */\n private buildPagination(\n limit?: number,\n nextPage?: string,\n previousPage?: string,\n dataLength: number = 0,\n totalCount?: number\n ): Pagination {\n const paging: Pagination = {\n count: totalCount || 0,\n limit: limit || this.paginationLimit,\n nextPage: undefined,\n previousPage,\n };\n\n if (limit) {\n paging.limit = limit;\n const currentSkip = parseInt(nextPage || \"0\");\n\n // Only set nextPage if:\n // 1. We have data in this response\n // 2. We haven't reached the end (current skip + limit < total count)\n if (dataLength > 0 && (!totalCount || currentSkip + limit < totalCount)) {\n paging.nextPage = `${currentSkip + limit}`;\n }\n }\n\n return paging;\n }\n\n private buildUrl(endpoint: string, queryParams?: URLSearchParams): string {\n const pathParts = [this.config.apiUrl || \"\"];\n pathParts.push(endpoint);\n\n const path = pathParts.filter(Boolean).join(\"/\");\n return queryParams ? `${path}?${queryParams.toString()}` : path;\n }\n //#endregion get request helpers\n\n async get<T>(\n endpoint: string,\n query: QueryParams = {}\n ): Promise<APIResponse<T[]>> {\n try {\n const params = this.buildQueryParams(query);\n const url = this.buildUrl(endpoint, params);\n const headers = await this.getHeaders();\n\n const response = await this.client.request<{\n Data: T[];\n TotalCount: number;\n }>({\n method: \"GET\",\n url,\n headers,\n });\n\n const paging = this.buildPagination(\n query.limit,\n query.nextPage,\n query.previousPage,\n response.data.Data?.length,\n response.data.TotalCount\n );\n\n return {\n data: response.data.Data || [],\n metadata: {\n pagination: paging,\n },\n };\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n async patch<T>(\n endpoint: string,\n id: string,\n data: Partial<T>\n ): Promise<APIResponse<T>> {\n try {\n const url = this.buildUrl(`${endpoint}/${id}`);\n const headers = await this.getHeaders();\n\n const response = await this.client.request<T>({\n method: \"PATCH\",\n url,\n data,\n headers,\n });\n\n return {\n data: response.data,\n };\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n async post<TRequest, TResponse = TRequest>(\n endpoint: string,\n data: TRequest\n ): Promise<APIResponse<TResponse>> {\n try {\n const url = this.buildUrl(endpoint);\n const headers = await this.getHeaders();\n\n const response = await this.client.request<TResponse>({\n method: \"POST\",\n url,\n data,\n headers,\n });\n\n return {\n data: response.data,\n };\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Cleanup all HTTP connections and resources\n * Call this when the service is no longer needed\n */\n async destroy(): Promise<void> {\n await this.client.destroy();\n }\n}\n","import { ERPObjType } from \"../types/erp-types\";\n\nexport function getERPAPITypeFromEntity(\n entity: string,\n entityMap: Record<ERPObjType, { entity: string }>\n): ERPObjType | undefined {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const entry = Object.entries(entityMap).find(\n ([_, value]) => value.entity === entity\n );\n return entry ? (Number(entry[0]) as ERPObjType) : undefined;\n}\n","/** A utility to get the type of an error, particularly\n * useful for the purpose of error deduplication\n * \n * Note: prefers 'code' over 'name' in the expectation that this better\n * represents the error type\n */\nconst isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0;\n\nexport function getErrorType(error: unknown): string {\n if (error && typeof error === 'object') {\n const o = error as { code?: unknown; name?: unknown; constructor?: { name?: unknown } };\n if (isNonEmptyString(o.code)) return o.code;\n if (isNonEmptyString(o.name)) return o.name;\n const ctorName = o.constructor?.name;\n if (isNonEmptyString(ctorName) && ctorName !== 'Object') return ctorName;\n }\n return 'Error';\n}\n","/**\n * Error formatter utility for standardizing error messages across connectors\n * Extracts human-readable messages from various error types (axios, custom errors, etc.)\n */\n\nexport interface FormattedError {\n message: string;\n code: string;\n httpStatus?: number;\n metadata?: Record<string, any>;\n}\n\n/**\n * Extract meaningful error message from various error types\n */\nexport function formatError(error: any): FormattedError {\n if (!error) {\n return {\n message: 'Unknown error occurred',\n code: 'UNKNOWN_ERROR',\n };\n }\n\n // Handle axios errors FIRST (before checking for FormattedError)\n // because axios errors also have message and code properties\n if (error.isAxiosError || error.name === 'AxiosError') {\n return formatAxiosError(error);\n }\n\n // If it's already a FormattedError, return as-is\n // Check for httpStatus or metadata to distinguish from axios errors\n if (error.message && error.code && typeof error.message === 'string' && typeof error.code === 'string' && !error.config) {\n return error as FormattedError;\n }\n\n // Handle standard Error objects\n if (error instanceof Error) {\n return {\n message: error.message || 'An error occurred',\n code: 'ERROR',\n metadata: {\n name: error.name,\n },\n };\n }\n\n // Handle string errors\n if (typeof error === 'string') {\n return {\n message: error,\n code: 'ERROR',\n };\n }\n\n // Last resort: try to extract any useful info\n const message = error.message || error.toString?.() || 'Unknown error occurred';\n return {\n message: typeof message === 'string' ? message : JSON.stringify(message),\n code: error.code || 'UNKNOWN_ERROR',\n };\n}\n\n/**\n * Format axios-specific errors with detailed context\n */\nfunction formatAxiosError(error: any): FormattedError {\n const httpStatus = error.response?.status;\n const method = error.config?.method?.toUpperCase();\n const url = error.config?.url;\n \n // First check if connector already extracted the message\n let message = error._extractedMessage;\n \n // If not, try to extract from response data\n if (!message) {\n const extractedMessage = extractErrorMessage(error.response?.data);\n message = extractedMessage || error.response?.statusText || error.message || 'Request failed';\n }\n\n // Determine error code based on status\n const code = categorizeHttpError(httpStatus);\n\n const metadata: Record<string, any> = {\n method,\n url,\n };\n\n // Add additional context for specific error types\n if (httpStatus === 401 || httpStatus === 403) {\n metadata.hint = 'Check authentication credentials';\n } else if (httpStatus === 404) {\n metadata.hint = 'Resource not found - check endpoint URL';\n } else if (httpStatus && httpStatus >= 500) {\n metadata.hint = 'ERP system may be temporarily unavailable';\n }\n\n return {\n message,\n code,\n httpStatus,\n metadata,\n };\n}\n\n/**\n * Extract meaningful error message from response data\n * Handles various ERP-specific response formats\n */\nfunction extractErrorMessage(data: any): string | null {\n if (!data) return null;\n\n // Common error message fields across different ERPs\n const possibleFields = [\n 'ErrorMessage', // Epicor\n 'error.message', // Common REST format\n 'error', // Simple format\n 'Message', // .NET style\n 'message', // JavaScript style\n 'errorMessage', // Camel case\n 'error_message', // Snake case\n 'errors[0].message', // Array of errors\n 'title', // Problem details format\n 'detail', // Problem details format\n ];\n\n for (const field of possibleFields) {\n const value = getNestedValue(data, field);\n if (value && typeof value === 'string') {\n return value;\n }\n }\n\n // If data is a string, return it\n if (typeof data === 'string') {\n // Try to parse as JSON first\n try {\n const parsed = JSON.parse(data);\n return extractErrorMessage(parsed);\n } catch {\n return data;\n }\n }\n\n return null;\n}\n\n/**\n * Get nested value from object using dot notation\n */\nfunction getNestedValue(obj: any, path: string): any {\n const parts = path.split('.');\n let current = obj;\n\n for (const part of parts) {\n // Handle array notation like 'errors[0]'\n const arrayMatch = part.match(/(\\w+)\\[(\\d+)\\]/);\n if (arrayMatch) {\n const [, key, index] = arrayMatch;\n current = current?.[key]?.[parseInt(index, 10)];\n } else {\n current = current?.[part];\n }\n\n if (current === undefined || current === null) {\n return null;\n }\n }\n\n return current;\n}\n\n/**\n * Categorize HTTP errors into standard codes\n */\nfunction categorizeHttpError(status?: number): string {\n if (!status) return 'NETWORK_ERROR';\n\n if (status === 400) return 'VALIDATION_ERROR';\n if (status === 401) return 'AUTHENTICATION_ERROR';\n if (status === 403) return 'AUTHORIZATION_ERROR';\n if (status === 404) return 'NOT_FOUND';\n if (status === 409) return 'CONFLICT';\n if (status === 422) return 'VALIDATION_ERROR';\n if (status === 429) return 'RATE_LIMIT';\n if (status >= 500) return 'ERP_SERVER_ERROR';\n if (status >= 400) return 'CLIENT_ERROR';\n\n return 'HTTP_ERROR';\n}\n\n/**\n * Format error for logging (shorter, cleaner version)\n */\nexport function formatErrorForLogging(error: any): string {\n const formatted = formatError(error);\n \n let message = `[${formatted.code}] ${formatted.message}`;\n \n if (formatted.httpStatus) {\n message = `[${formatted.httpStatus}] ${message}`;\n }\n \n return message;\n}\n\n","import { MMApiClient } from '../../services/mm-api-service/mm-api-service';\nimport logger from '../../services/reporting-service/logger';\nimport type { LogDeduper } from './log-deduper';\n\nexport type LogLevelString = 'info' | 'warn' | 'error';\n\n/**\n * dedupeKey - key used to deduplicate log entries when a LogDeduper is injected into the MMConnectorLogger\n * \n * A recommended practice is to use a meaningful unique identifier for this log entry type.\n * Examples: SyncFromERPCompleted\n * SyncFromERPError:HTTPError\n */\nexport class LogEntry {\n level: LogLevelString;\n message: string;\n dedupeKey: string;\n eventTime: number;\n\n constructor(params: { level: LogLevelString; message: string; dedupeKey: string}) {\n this.level = params.level;\n this.message = params.message;\n this.dedupeKey = params.dedupeKey;\n this.eventTime = Date.now();\n }\n}\n\n/**\n * The response from the MM connector logging API\n */\nexport interface LogResponse {\n message: string;\n processed?: number;\n}\n\n/**\n * Helper function to check if a value is a LogResponse\n */\nfunction isLogResponse(value: unknown): value is LogResponse {\n if (value === null || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.message !== 'string') return false;\n if ('processed' in v && typeof v.processed !== 'number') return false;\n return true;\n}\n\n/**\n * MM Connector Logger for sending progress and error updates to the MM cloud\n * \n * This class provides helper methods to send individual logs to the\n * MM connector logging API, providing support for validation, retries, and error handling\n * according to the API specification. \n * \n * By injecting a LogDeduper, the log entries are deduplicated across jobs over a specified time window\n */\nexport class MMConnectorLogger {\n private readonly MAX_MSG_LEN = 2000;\n \n private mmApiClient: MMApiClient;\n private readonly deduper?: LogDeduper;\n private readonly source: string;\n\n constructor(source: string, deduper?: LogDeduper) {\n if (source.length < 1 || source.length > 64) {\n throw new Error('source must be 1-64 characters');\n }\n\n this.mmApiClient = new MMApiClient();\n this.deduper = deduper;\n this.source = source;\n }\n\n // Deduplication helpers are delegated to injected FileLogDeduper\n\n /**\n * Send a single log entry to the MM cloud with deduplication.\n * \n * The deduplication is handled by the injected LogDeduper.\n * If no deduper is injected, the log entry is sent without deduplication.\n * \n * The standard deduper, FileLogDeduper, stores the deduplication state in a file, \n * allowing deduplication across jobs, \n * \n * @param logEntry - The log entry to send\n * @returns Promise resolving to the API response or null if suppressed\n * @throws HTTPError if the request fails or Error if the log entry is invalid\n */\n async sendLog(logEntry: LogEntry): Promise<LogResponse | null> {\n this.validateLogEntry(logEntry);\n \n const now = Date.now();\n let messageToSend = logEntry.message;\n if (this.deduper) {\n const decision = await this.deduper.decide(logEntry, now);\n if (decision === null) return null; // suppressed\n messageToSend = decision;\n }\n\n try {\n const logEntryToSend = {\n source: this.source,\n level: logEntry.level,\n message: messageToSend\n };\n\n const response = await this.mmApiClient.sendConnectorLog(logEntryToSend);\n if (this.deduper) {\n await this.deduper.onSuccess(logEntry, now);\n }\n\n if (!isLogResponse(response)) {\n logger.warn('Unexpected success response format from MM API for connector log', { response });\n return { message: 'Unexpected success response format when sending log' };\n }\n return { message: response.message };\n\n } catch (error) {\n logger.error('Failed to send log to MM cloud', { \n level: logEntry.level,\n error: error instanceof Error ? error.message : 'Unknown error'\n });\n throw error;\n }\n }\n\n /**\n * @throws Error if validation fails\n */\n private validateLogEntry(logEntry: LogEntry): void {\n const allowedLevels: ReadonlyArray<LogLevelString> = ['info', 'warn', 'error'];\n if (!logEntry.level || !allowedLevels.includes(logEntry.level)) {\n throw new Error(`level must be one of: ${allowedLevels.join(', ')}`);\n }\n\n if (!logEntry.message || typeof logEntry.message !== 'string') {\n throw new Error('message is required and must be a string');\n }\n\n // Truncate message to MAX_MSG_LEN characters\n logEntry.message = logEntry.message.slice(0, this.MAX_MSG_LEN);\n\n if (!logEntry.dedupeKey || typeof logEntry.dedupeKey !== 'string') {\n throw new Error('dedupeKey is required and must be a string');\n }\n\n if (logEntry.dedupeKey.trim().length < 1) {\n throw new Error('dedupeKey must be a non-empty string');\n }\n }\n\n /**\n * Retry all failed transmissions silently\n * This method attempts to retry all messages that failed to transmit\n * and removes them from the failed list if successful, else leaves them for the client to retry\n * \n * Expected usage is by a client to call this as part of its own retry mechanism\n */\n async retryFailedTransmissions(): Promise<void> {\n if (!this.deduper || !this.deduper.retryFailedTransmissions) {\n return;\n }\n await this.deduper.retryFailedTransmissions(async (entry, message) => {\n await this.mmApiClient.sendConnectorLog({\n source: this.source,\n level: entry.level,\n message\n });\n });\n }\n\n /**\n * Clean up resources\n */\n async destroy(): Promise<void> {\n await this.mmApiClient.destroy();\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport logger from '../../services/reporting-service/logger';\nimport type { LogEntry, LogLevelString } from './mm-connector-logger';\n\n/**\n * Implement a custom LogDeduper or use the provided FileLogDeduper\n */\nexport interface LogDeduper {\n decide(entry: LogEntry, now: number): Promise<string | null>;\n onSuccess(entry: LogEntry, now: number): Promise<void>;\n retryFailedTransmissions?(send: (entry: LogEntry, message: string) => Promise<void>): Promise<void>;\n}\n\ninterface LogDedupeEntry {\n lastTransmitted: number;\n suppressedCount: number;\n firstUnsentEventTs: number; // Earliest unsent occurrence time for the current unsent batch (if any)\n lastEventTs: number; // Most recent occurrence time (allowed or suppressed)\n level: LogLevelString;\n message: string;\n}\n\n/**\n * LogDedupeStore is the store for of the most recent log entries for each dedupeKey\n */\ninterface LogDedupeStore {\n [key: string]: LogDedupeEntry;\n}\n\n/**\n * FileLogDeduper implements the LogDeduper interface to deduplicate\n * logs by storing them in a file; this allows deduping across jobs.\n */\nexport class FileLogDeduper implements LogDeduper {\n private readonly storeFilePath: string;\n private readonly windowMs: number;\n private readonly ttlMs: number;\n private readonly sweepIntervalMs: number;\n private lastSweepTsMs: number;\n\n private readonly DEFAULT_WINDOW_TEN_MINS = 600;\n private readonly DEFAULT_TTL_ONE_HOUR = 3600;\n private readonly DEFAULT_SWEEP_INTERVAL_FIVE_MINS = 300;\n private readonly DEFAULT_STORE_FILE_PATH = path.join('/tmp', 'log-deduplication.json');\n\n /**\n * Ctor.\n * @param storeFilePath: The path to the file where the deduplication store is stored; recommended is to use the default\n * @param windowSeconds: Suppression window. Duplicates within this period are suppressed.\n * @param ttlSeconds: Eviction TTL. Store entries for keys inactive beyond this are removed. Enforced to be ≥ windowSeconds.\n * @param sweepIntervalSeconds: Efficiency parameter. How often (min interval) to run opportunistic eviction; retry always sweeps\n * The sweep is lazy, used only when the store is accessed\n */\n constructor({\n storeFilePath = this.DEFAULT_STORE_FILE_PATH,\n windowSeconds = this.DEFAULT_WINDOW_TEN_MINS,\n ttlSeconds = this.DEFAULT_TTL_ONE_HOUR,\n sweepIntervalSeconds = this.DEFAULT_SWEEP_INTERVAL_FIVE_MINS\n } : {\n storeFilePath?: string;\n windowSeconds?: number;\n ttlSeconds?: number;\n sweepIntervalSeconds?: number;\n } = {}) {\n\n this.storeFilePath = storeFilePath;\n this.windowMs = Math.max(1, windowSeconds) * 1000;\n this.ttlMs = Math.max(this.windowMs, Math.max(1, ttlSeconds) * 1000);\n this.sweepIntervalMs = Math.max(1, sweepIntervalSeconds) * 1000;\n this.lastSweepTsMs = 0;\n\n this.ensureStoreFileExists();\n }\n\n /** \n * Deduplication gating function\n * Returns the formatted message to send, or null to suppress\n * Decision is based on the dedupeKey and the time of the entry\n */\n async decide(entry: LogEntry, now: number): Promise<string | null> {\n if (!entry.dedupeKey || typeof entry.dedupeKey !== 'string' || entry.dedupeKey.trim().length === 0) {\n throw new Error('dedupeKey is required and must be a non-empty string');\n }\n const key = entry.dedupeKey;\n\n return this.withLock(async () => {\n const store = this.readStore();\n\n // Opportunistic eviction gated by sweep interval\n if (now - this.lastSweepTsMs >= this.sweepIntervalMs) {\n this.evictExpiredInStore(store, now);\n this.lastSweepTsMs = now;\n this.writeStore(store);\n }\n const existing = store[key];\n\n if (existing) {\n const withinWindow = existing.lastTransmitted > 0 && (existing.lastTransmitted + this.windowMs) > now;\n if (withinWindow) {\n // Suppress and increment count\n store[key] = {\n ...existing,\n suppressedCount: existing.suppressedCount + 1,\n firstUnsentEventTs: existing.suppressedCount === 0 ? (entry.eventTime ?? now) : existing.firstUnsentEventTs,\n lastEventTs: entry.eventTime ?? now,\n level: entry.level,\n message: entry.message\n };\n this.writeStore(store);\n return null;\n }\n\n // Outside window: allow, include timestamp and prior suppression count\n const messageToSend = this.formatMessage(entry.message, entry.eventTime ?? now, existing.suppressedCount, existing.firstUnsentEventTs);\n store[key] = {\n ...existing,\n suppressedCount: 0,\n firstUnsentEventTs: 0,\n lastEventTs: entry.eventTime ?? now,\n level: entry.level,\n message: entry.message\n };\n this.writeStore(store);\n return messageToSend;\n }\n\n // New entry: allow, timestamp, no suppression count\n const messageToSend = this.formatMessage(entry.message, entry.eventTime ?? now, 0);\n store[key] = {\n lastTransmitted: 0,\n suppressedCount: 0,\n firstUnsentEventTs: entry.eventTime ?? now,\n lastEventTs: entry.eventTime ?? now,\n level: entry.level,\n message: entry.message\n };\n this.writeStore(store);\n return messageToSend;\n });\n }\n\n async onSuccess(entry: LogEntry, now: number): Promise<void> {\n if (!entry.dedupeKey || typeof entry.dedupeKey !== 'string' || entry.dedupeKey.trim().length === 0) {\n throw new Error('dedupeKey is required and must be a non-empty string');\n }\n const key = entry.dedupeKey;\n\n await this.withLock(async () => {\n const store = this.readStore();\n const existing = store[key];\n if (existing) {\n store[key] = {\n ...existing,\n lastTransmitted: now,\n firstUnsentEventTs: 0,\n suppressedCount: 0\n };\n this.writeStore(store);\n }\n });\n }\n\n async retryFailedTransmissions(send: (entry: LogEntry, message: string) => Promise<void>): Promise<void> {\n // Best-effort retry: first sweep expired entries, then iterate over entries with lastTransmitted === 0\n const now = Date.now();\n const entries = await this.withLock(async () => {\n const store = this.readStore();\n // Always sweep on retry\n this.evictExpiredInStore(store, now);\n this.lastSweepTsMs = now;\n this.writeStore(store); \n return Object.entries(store)\n .filter(([, rec]) => rec.lastTransmitted === 0)\n .map(([key, rec]) => ({ key, rec }));\n });\n\n for (const { key, rec } of entries) {\n try {\n const message = this.formatMessage(rec.message, rec.lastEventTs, rec.suppressedCount, rec.firstUnsentEventTs);\n await send({ level: rec.level, message: rec.message, dedupeKey: key, eventTime: rec.lastEventTs }, message);\n // Mark success\n await this.withLock(async () => {\n const store = this.readStore();\n const current = store[key];\n if (current) {\n store[key] = {\n ...current,\n lastTransmitted: Date.now(),\n suppressedCount: 0\n };\n this.writeStore(store);\n }\n });\n } catch (err) {\n // Leave entry for future retries; do not throw\n logger.error('Failed to retry failed transmission', { key, rec, error: err });\n return;\n }\n }\n }\n\n // --- Internals ---\n\n private ensureStoreFileExists(): void {\n try {\n if (!fs.existsSync(this.storeFilePath)) {\n fs.writeFileSync(this.storeFilePath, JSON.stringify({}), 'utf-8');\n }\n } catch {\n // ignore\n }\n }\n\n private readStore(): LogDedupeStore {\n try {\n if (!fs.existsSync(this.storeFilePath)) return {};\n const content = fs.readFileSync(this.storeFilePath, 'utf-8');\n return content ? (JSON.parse(content) as LogDedupeStore) : {};\n } catch {\n return {};\n }\n }\n\n private writeStore(store: LogDedupeStore): void {\n try {\n fs.writeFileSync(this.storeFilePath, JSON.stringify(store, null, 2), 'utf-8');\n } catch {\n // ignore\n }\n }\n\n private formatMessage(message: string, eventTs: number, suppressedCount: number, firstUnsentEventTs?: number): string {\n const timestamp = new Date(eventTs).toISOString();\n const base = `${timestamp} | ${message}`;\n if (suppressedCount > 0) {\n const since = firstUnsentEventTs && firstUnsentEventTs > 0 ? ` since ${new Date(firstUnsentEventTs).toISOString()}` : '';\n return `${base} (${suppressedCount} suppressed${since})`;\n }\n return base;\n }\n\n private async withLock<T>(fn: () => Promise<T>): Promise<T> {\n const lockPath = `${this.storeFilePath}.lock`;\n const start = Date.now();\n\n // Try to acquire the lock with retries (best effort)\n // Avoid infinite waits; cap at ~3 seconds\n while (true) {\n try {\n const fd = fs.openSync(lockPath, 'wx');\n try {\n const result = await fn();\n return result;\n } finally {\n try { fs.closeSync(fd); } catch {/* ignore */}\n try { fs.unlinkSync(lockPath); } catch {/* ignore */}\n }\n } catch {\n // Lock exists\n if (Date.now() - start > 3000) {\n // Timed out; proceed without lock to avoid deadlock\n return await fn();\n }\n await new Promise(resolve => setTimeout(resolve, 50));\n }\n }\n }\n\n /**\n * Evict expired entries from the store based on the TTL and the key's last transmitted time\n */\n private evictExpiredInStore(store: LogDedupeStore, now: number): void {\n const keys = Object.keys(store);\n if (keys.length === 0) return;\n for (const key of keys) {\n const rec = store[key];\n const referenceTs = rec.lastTransmitted > 0 ? rec.lastTransmitted : rec.lastEventTs;\n if (now - referenceTs > this.ttlMs) {\n delete store[key];\n }\n }\n }\n}\n","import logger from \"../services/reporting-service/logger\";\nimport { CoreConfiguration } from \"../services/data-sync-service/configuration-manager\";\nimport { SQLiteCoordinator } from \"../services/sqlite-service\";\nimport { getTimezoneOffsetAndPersist } from \"./time-utils\";\nimport knex from \"knex\";\nimport config from \"../knexfile\";\n\n/**\n * Utility class to handle application initialization tasks\n */\nexport class ApplicationInitializer {\n /**\n * Performs all necessary application initialization tasks\n * This should be called before starting any services or jobs\n */\n public static async initialize(): Promise<void> {\n try {\n logger.info(\n \"\\n================================INITIALIZING APPLICATION================================\\n\"\n );\n\n // Load and validate core configuration\n const coreConfig = CoreConfiguration.inst();\n logger.info(\"Core Configuration loaded:\", coreConfig.toSafeLogObject());\n\n // Perform database startup checks\n logger.info(\"Performing database startup checks...\");\n await SQLiteCoordinator.performStartupCheck();\n logger.info(\"Database startup checks completed successfully\");\n\n // Run database migrations to ensure all tables exist\n logger.info(\"Running database migrations...\");\n await ApplicationInitializer.runMigrations();\n logger.info(\"Database migrations completed successfully\");\n\n // Required for most connectors\n await getTimezoneOffsetAndPersist();\n\n logger.info(\n \"\\n================================APPLICATION INITIALIZATION COMPLETED================================\\n\"\n );\n } catch (error) {\n logger.error(\"Critical initialization failure. Exiting.\", error);\n process.exitCode = 1;\n }\n }\n\n /**\n * Runs database migrations to ensure all required tables exist\n */\n private static async runMigrations(): Promise<void> {\n const db = knex(config.local);\n try {\n await db.migrate.latest();\n } catch (error) {\n logger.error(\"Error running migrations:\", error);\n throw error;\n } finally {\n await db.destroy();\n }\n }\n}\n","import {\n HTTPClient,\n HTTPClientFactory,\n HTTPResponse,\n} from \"../../../utils/http-client\";\nimport { GraphQLResponse } from \"./types\";\nimport { ErrorHandler } from \"../errors\";\nimport { APIResponse } from \"../types\";\nimport { ERPApiConfig } from \"../types\";\n\nconst DEFAULT_RETRY_ATTEMPTS = 0; // One try only, no retries\n\nexport class GraphQLService {\n private client: HTTPClient;\n private config: ERPApiConfig;\n private endpoint: string;\n\n constructor(config: ERPApiConfig, endpoint: string, retryAttempts?: number) {\n this.config = config;\n this.endpoint = endpoint\n ? endpoint.startsWith(\"/\")\n ? endpoint\n : `/${endpoint}`\n : \"\";\n this.client = HTTPClientFactory.getInstance({\n baseUrl: config.apiUrl,\n retryAttempts: retryAttempts ?? DEFAULT_RETRY_ATTEMPTS,\n });\n }\n\n private async getHeaders(): Promise<Record<string, string>> {\n const token = await this.config.getAuthToken();\n return {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n };\n }\n\n /**\n * Handles a GraphQL response, properly handling both successful responses\n * and GraphQL-level errors (which come in a 200 HTTP response)\n * \n * From the GraphQL specification (https://spec.graphql.org/draft/#sec-Errors):\n> * \"When a GraphQL server encounters an error, it should return a response with a \n> * top-level \"errors\" field containing the error information. The response may still contain\n * a partial result in the \"data\" field if the error occurred after some data was already resolved.\"\n * \n * And specifically about HTTP status codes:\n * \"A server should return a 200 status code when a GraphQL operation successfully executes, including when \n * the operation returns errors. A server should return a 400 status code when a GraphQL operation fails to execute.\"\n */\n private handleGraphQLResponse<T>(\n response: HTTPResponse<GraphQLResponse<T>>\n ): APIResponse<T> {\n return {\n data: response.data.data as T,\n metadata: {\n errors: response.data.errors,\n extensions: response.data.extensions,\n },\n };\n }\n\n /**\n * Executes a GraphQL query\n * @param query The GraphQL query string\n * @param variables Optional variables for the query\n * @returns The query result\n * @throws {HTTPError} For HTTP/network errors only\n */\n async query<T>(\n query: string,\n variables?: Record<string, unknown>\n ): Promise<APIResponse<T>> {\n try {\n const headers = await this.getHeaders();\n const response = await this.client.request<GraphQLResponse<T>>({\n method: \"POST\",\n url: this.endpoint,\n data: { query, variables },\n headers,\n });\n\n return this.handleGraphQLResponse(response);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Executes a GraphQL mutation\n * @param mutation The GraphQL mutation string\n * @param variables Optional variables for the mutation\n * @returns The mutation result\n * @throws {HTTPError} For HTTP/network errors only\n */\n async mutate<T>(\n mutation: string,\n variables?: Record<string, unknown>\n ): Promise<APIResponse<T>> {\n try {\n const headers = await this.getHeaders();\n const response = await this.client.request<GraphQLResponse<T>>({\n method: \"POST\",\n url: this.endpoint,\n data: { query: mutation, variables },\n headers,\n });\n\n return this.handleGraphQLResponse(response);\n } catch (error) {\n ErrorHandler.handle(error);\n }\n }\n\n /**\n * Cleanup all HTTP connections and resources\n * Call this when the service is no longer needed\n */\n async destroy(): Promise<void> {\n await this.client.destroy();\n }\n}\n","/**\n * Configuration for OAuth client credentials flow\n */\nexport interface OAuthConfig {\n /** The URL to request the OAuth token from */\n authUrl: string;\n /** The client ID for OAuth authentication */\n clientId: string;\n /** The client secret for OAuth authentication */\n clientSecret: string;\n /** The scope for the OAuth token. Use empty string if no scope is needed. */\n scope: string;\n}\n\n/**\n * Standard OAuth 2.0 token response format as defined in RFC 6749\n */\nexport interface OAuthTokenResponse {\n /** The access token issued by the authorization server */\n access_token: string;\n /** The type of token issued (typically \"Bearer\") */\n token_type: string;\n /** The lifetime in seconds of the access token */\n expires_in: number;\n /** The refresh token, which can be used to obtain new access tokens (optional) */\n refresh_token?: string;\n /** The scope of the access token (optional) */\n scope?: string;\n}\n\n/**\n * A utility class for handling OAuth client credentials flow.\n * This is specifically for the client credentials grant type,\n * which is used for server-to-server authentication.\n */\nexport class OAuthClient {\n constructor(private config: OAuthConfig) {}\n\n /**\n * Requests an OAuth token using the client credentials flow.\n * The request is sent as application/x-www-form-urlencoded.\n * @returns A promise that resolves to the token response containing access_token and expires_in\n * @throws Error if the token request fails\n */\n async getToken(): Promise<OAuthTokenResponse> {\n const formData = new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n scope: this.config.scope,\n });\n\n const response = await fetch(this.config.authUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: formData,\n });\n\n if (!response.ok) {\n throw new Error(`OAuth token request failed: ${response.statusText}`);\n }\n\n const data = (await response.json()) as OAuthTokenResponse;\n if (data.token_type !== \"Bearer\") {\n throw new Error(`Unexpected token type: ${data.token_type}`);\n }\n\n return data;\n }\n}\n","/**\n * NATS Service - Central connection and subscription management\n * Allows connectors to register handlers for different NATS subjects\n */\n\nimport { connect, NatsConnection, StringCodec, Subscription } from \"nats\";\nimport { logger } from \"../reporting-service\";\n\nconst sc = StringCodec();\n\nexport interface NatsMessageHandler {\n /**\n * Handler function for NATS messages\n * - Return a value to reply (if msg has reply subject)\n * - Return void/undefined for pub-sub (no reply)\n */\n handle: (data: any, subject: string) => Promise<any | void>;\n}\n\nexport interface NatsHandlerRegistration {\n /**\n * Subject pattern to subscribe to (supports wildcards: *, >)\n * Examples:\n * - \"mm.14.epic01.labor-ticket.*\" (single level wildcard)\n * - \"mm.14.epic01.erp.>\" (multi-level wildcard)\n */\n subject: string;\n\n /**\n * Handler for this subject\n */\n handler: NatsMessageHandler;\n\n /**\n * Optional description for logging/debugging\n */\n description?: string;\n}\n\nexport interface NatsServiceConfig {\n /**\n * NATS server URLs\n */\n servers: string | string[];\n\n /**\n * Connection name (for monitoring)\n */\n name: string;\n\n /**\n * Location reference (for subject namespacing)\n */\n locationRef: string;\n\n /**\n * ERP type (epicor, infor, etc.)\n */\n erpType: string;\n\n /**\n * Enable/disable NATS\n */\n enabled: boolean;\n\n /**\n * Auto-reconnect settings\n */\n reconnect?: boolean;\n maxReconnectAttempts?: number;\n reconnectTimeWait?: number;\n}\n\nexport class NatsService {\n private connection: NatsConnection | null = null;\n private subscriptions: Map<string, Subscription> = new Map();\n private config: NatsServiceConfig;\n private handlers: NatsHandlerRegistration[] = [];\n private statusPublishTimer: NodeJS.Timeout | null = null;\n\n constructor(config: NatsServiceConfig) {\n this.config = config;\n }\n\n /**\n * Register a handler for a specific subject pattern\n */\n registerHandler(registration: NatsHandlerRegistration): void {\n logger.info(\"Registering NATS handler\", {\n subject: registration.subject,\n description: registration.description,\n });\n this.handlers.push(registration);\n }\n\n /**\n * Connect to NATS and start all registered handlers\n */\n async connect(): Promise<void> {\n if (!this.config.enabled) {\n logger.info(\"NATS is disabled, skipping connection\");\n return;\n }\n\n try {\n logger.info(\"Connecting to NATS...\", {\n servers: this.config.servers,\n name: this.config.name,\n });\n\n this.connection = await connect({\n servers: this.config.servers,\n name: this.config.name,\n reconnect: this.config.reconnect ?? true,\n maxReconnectAttempts: this.config.maxReconnectAttempts ?? -1,\n reconnectTimeWait: this.config.reconnectTimeWait ?? 2000,\n });\n\n logger.info(\"Connected to NATS\", {\n server: this.connection.getServer(),\n clientId: this.connection.info?.client_id,\n });\n\n // Start all registered handlers\n for (const registration of this.handlers) {\n await this.startHandler(registration);\n }\n\n // Start automatic status publishing\n this.startStatusPublishing();\n\n // Monitor connection status\n this.monitorConnection();\n\n // Graceful shutdown\n this.setupShutdown();\n } catch (error) {\n logger.error(\"Failed to connect to NATS\", { error });\n throw error;\n }\n }\n\n /**\n * Start a single handler (subscribe to its subject)\n */\n private async startHandler(registration: NatsHandlerRegistration): Promise<void> {\n if (!this.connection) {\n throw new Error(\"NATS connection not established\");\n }\n\n const sub = this.connection.subscribe(registration.subject);\n this.subscriptions.set(registration.subject, sub);\n\n logger.info(\"Started NATS handler\", {\n subject: registration.subject,\n description: registration.description,\n });\n\n // Process messages\n (async () => {\n for await (const msg of sub) {\n try {\n const data = sc.decode(msg.data);\n \n logger.info(\"Received NATS message\", {\n subject: msg.subject,\n hasReply: !!msg.reply,\n });\n\n // Parse JSON if possible\n let parsedData: any;\n try {\n parsedData = JSON.parse(data);\n } catch {\n parsedData = data;\n }\n\n // Call handler\n const response = await registration.handler.handle(parsedData, msg.subject);\n \n // If there's a reply subject and handler returned something, reply\n if (msg.reply && response !== undefined) {\n const responseStr = JSON.stringify(response);\n msg.respond(sc.encode(responseStr));\n logger.info(\"Sent reply\", { replySubject: msg.reply });\n }\n } catch (error) {\n logger.error(\"Error handling NATS message\", {\n subject: msg.subject,\n error,\n });\n\n // Send error response if reply expected\n if (msg.reply) {\n const errorResponse = {\n status: \"error\",\n error: {\n message: error instanceof Error ? error.message : \"Unknown error\",\n code: \"HANDLER_ERROR\",\n },\n };\n msg.respond(sc.encode(JSON.stringify(errorResponse)));\n }\n }\n }\n })();\n }\n\n /**\n * Publish a message to a subject (for pub/sub)\n */\n async publish(subject: string, data: any): Promise<void> {\n if (!this.connection) {\n throw new Error(\"NATS connection not established\");\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n this.connection.publish(subject, sc.encode(message));\n\n logger.info(\"Published NATS message\", { subject });\n }\n\n /**\n * Send a request and wait for reply (for request-reply)\n */\n async request(subject: string, data: any, timeoutMs: number = 30000): Promise<any> {\n if (!this.connection) {\n throw new Error(\"NATS connection not established\");\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n const response = await this.connection.request(\n subject,\n sc.encode(message),\n { timeout: timeoutMs }\n );\n\n const responseData = sc.decode(response.data);\n \n try {\n return JSON.parse(responseData);\n } catch {\n return responseData;\n }\n }\n\n /**\n * Check if connected to NATS\n */\n isConnected(): boolean {\n return this.connection !== null && !this.connection.isClosed();\n }\n\n /**\n * Start automatic status publishing (every 30 seconds)\n */\n private startStatusPublishing(): void {\n logger.info(\"Starting status publishing (every 30 seconds)\");\n\n // Publish immediately on start\n this.publishStatus();\n\n // Then publish every 30 seconds\n this.statusPublishTimer = setInterval(() => {\n this.publishStatus();\n }, 30000);\n }\n\n /**\n * Publish connector status\n */\n private async publishStatus(): Promise<void> {\n try {\n const status = {\n timestamp: new Date().toISOString(),\n locationRef: this.config.locationRef,\n erpType: this.config.erpType,\n natsConnected: this.isConnected(),\n };\n\n await this.publish(\n `mm.14.${this.config.locationRef}.erp.status`,\n status\n );\n\n logger.debug(\"Published connector status\");\n } catch (error) {\n logger.error(\"Failed to publish status\", { error });\n }\n }\n\n /**\n * Monitor connection status\n */\n private monitorConnection(): void {\n if (!this.connection) return;\n\n (async () => {\n for await (const status of this.connection!.status()) {\n // Only log important events, skip pingTimer\n if (status.type !== \"pingTimer\") {\n logger.info(\"NATS connection status\", {\n type: status.type,\n data: status.data,\n });\n }\n }\n })();\n }\n\n /**\n * Setup graceful shutdown\n */\n private setupShutdown(): void {\n const shutdown = async () => {\n logger.info(\"Shutting down NATS service...\");\n await this.disconnect();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n }\n\n /**\n * Disconnect from NATS\n */\n async disconnect(): Promise<void> {\n // Stop status publishing\n if (this.statusPublishTimer) {\n clearInterval(this.statusPublishTimer);\n this.statusPublishTimer = null;\n }\n\n // Close connection\n if (this.connection) {\n await this.connection.drain();\n this.connection = null;\n this.subscriptions.clear();\n logger.info(\"Disconnected from NATS\");\n }\n }\n\n /**\n * Get the location reference\n */\n getLocationRef(): string {\n return this.config.locationRef;\n }\n}\n\n","import logger from \"../reporting-service/logger\";\nimport { formatError } from \"../../utils/error-formatter\";\nimport { NatsService } from \"../nats-service/nats-service\";\nimport { MMReceiveLaborTicket, MMApiClient, getCompanyInfo } from \"../mm-api-service\";\nimport { SQLiteCoordinator } from \"../sqlite-service\";\n\n/**\n * Manages NATS-based real-time labor ticket processing\n */\nexport class NatsLaborTicketListener {\n private connector: any;\n private natsService?: NatsService;\n\n constructor(connector: any) {\n this.connector = connector;\n }\n\n /**\n * Start listening for labor ticket events via NATS\n */\n async start(): Promise<void> {\n try {\n // Fetch company info from MM API (includes locationRef)\n const companyInfo = await getCompanyInfo();\n const erpType = this.connector.type || \"unknown\";\n\n logger.info(\"Starting NATS listener for labor tickets\", {\n locationRef: companyInfo.locationRef,\n companyId: companyInfo.companyId,\n erpType,\n servers: process.env.NATS_SERVERS || \"nats://localhost:4222\",\n });\n\n this.natsService = new NatsService({\n servers: process.env.NATS_SERVERS || \"nats://localhost:4222\",\n name: `${erpType}-connector`,\n locationRef: companyInfo.locationRef,\n erpType,\n enabled: true,\n reconnect: true,\n maxReconnectAttempts: -1,\n reconnectTimeWait: 2000,\n });\n\n // Register health check handler\n this.registerHealthCheckHandler(companyInfo.locationRef, erpType);\n\n // Register labor ticket handler\n this.registerLaborTicketHandler(companyInfo.locationRef, erpType);\n\n // Connect to NATS\n await this.natsService.connect();\n\n logger.info(\"NATS listener started successfully\", {\n subject: `mm.14.${companyInfo.locationRef}.labor-ticket.*`,\n });\n } catch (error) {\n logger.error(\"Failed to start NATS listener\", { error });\n // Don't throw - allow REST polling to continue\n }\n }\n\n /**\n * Register health check handler - responds immediately to let Dashboard know connector is online\n */\n private registerHealthCheckHandler(locationRef: string, erpType: string): void {\n if (!this.natsService) return;\n\n this.natsService.registerHandler({\n subject: `mm.14.${locationRef}.erp.health`,\n description: \"Health check - responds immediately to indicate connector is online\",\n handler: {\n handle: async () => {\n logger.debug(\"Health check received, sending pong\");\n return {\n status: \"online\",\n timestamp: new Date().toISOString(),\n locationRef,\n erpType,\n };\n },\n },\n });\n }\n\n /**\n * Register labor ticket processing handler\n */\n private registerLaborTicketHandler(locationRef: string, erpType: string): void {\n if (!this.natsService) return;\n\n this.natsService.registerHandler({\n subject: `mm.14.${locationRef}.labor-ticket.*`,\n description: \"Process labor tickets in real-time from NATS\",\n handler: {\n handle: async ({ data }: any, subject: string) => {\n const action = subject.split(\".\").pop();\n const { actionPayload } = data;\n const startTime = Date.now();\n const { laborTicketRef } = actionPayload;\n\n logger.info(\"Received labor ticket via NATS\", {\n action,\n requestId: data.requestId,\n laborTicketRef,\n });\n\n // Use the same lock as to-erp job to prevent concurrent processing\n return await SQLiteCoordinator.executeWithLock(\"to-erp\", async () => {\n try {\n let laborTicketData;\n\n if (laborTicketRef) {\n // Fetch the full labor ticket data using the laborTicketRef\n const mmApiClient = new MMApiClient();\n const laborTicket = await mmApiClient.fetchLaborTicketByRef(laborTicketRef);\n\n logger.info(\"Fetched labor ticket data from MM API\", {\n laborTicketRef,\n laborTicketId: laborTicket.laborTicketId,\n });\n\n // Merge changed fields from actionPayload to ensure we process the actual changes\n laborTicketData = {\n ...laborTicket,\n ...actionPayload,\n };\n } else {\n // No laborTicketRef provided, use the actionPayload directly\n logger.info(\"No laborTicketRef provided, using actionPayload directly\", {\n requestId: data.requestId,\n });\n\n laborTicketData = actionPayload;\n }\n\n const mergedLaborTicket = new MMReceiveLaborTicket(laborTicketData);\n\n // Process labor ticket: ERP operation first, then create MM entities with ERP ID\n const result = await this.processLaborTicket(\n mergedLaborTicket,\n action || \"unknown\"\n );\n\n // Update checkpoint to prevent to-erp polling job from reprocessing this ticket\n await this.updateCheckpoint(erpType, result);\n\n // Return response with full labor ticket data for immediate frontend state update\n return {\n status: \"success\",\n requestId: data.requestId,\n action,\n timestamp: new Date().toISOString(),\n processingTimeMs: Date.now() - startTime,\n laborTicketRef: result.laborTicketRef,\n laborTicket: result.laborTicket,\n };\n } catch (error) {\n // Check if error was already formatted in inner catch block\n const formattedError = (error as any)?._formatted || formatError(error);\n\n // Debug logging\n logger.debug(\"Error details\", {\n hasFormatted: !!(error as any)?._formatted,\n isAxiosError: (error as any)?.isAxiosError,\n errorMessage: (error as any)?.message,\n responseStatus: (error as any)?.response?.status,\n responseData: (error as any)?.response?.data,\n });\n\n logger.error(\"Error handling labor ticket from NATS\", {\n error: formattedError.message,\n code: formattedError.code,\n requestId: data.requestId,\n laborTicketRef,\n });\n\n return {\n status: \"error\",\n requestId: data.requestId,\n action,\n timestamp: new Date().toISOString(),\n processingTimeMs: Date.now() - startTime,\n error: {\n message: formattedError.message,\n code: formattedError.code,\n httpStatus: formattedError.httpStatus,\n metadata: formattedError.metadata,\n laborTicketRef,\n },\n };\n }\n });\n },\n },\n });\n }\n\n /**\n * Process labor ticket in ERP and then create MM entities with ERP ID attached\n */\n private async processLaborTicket(\n laborTicket: MMReceiveLaborTicket,\n action: string\n ): Promise<{ laborTicketRef: string; laborTicket: any }> {\n const { MMEntityProcessor } = await import(\"../../utils/standard-process-drivers/mm-entity-processor\");\n const { ERPObjType } = await import(\"../../types/erp-types\");\n const { MMSendLaborTicket } = await import(\"../mm-api-service\");\n\n logger.info(\"Processing labor ticket: ERP first, then MM creation\", {\n laborTicketRef: laborTicket.laborTicketRef,\n action,\n hasLaborTicketId: !!laborTicket.laborTicketId,\n });\n\n try {\n let erpResult;\n\n if (action === \"create\" || !laborTicket.laborTicketId) {\n // Create new labor ticket in ERP\n erpResult = await this.connector.createLaborTicketInERP(laborTicket);\n logger.info(\"Successfully created labor ticket in ERP\", {\n laborTicketRef: laborTicket.laborTicketRef,\n erpUid: erpResult.erpUid,\n });\n } else {\n // Update existing labor ticket in ERP\n erpResult = { laborTicket: await this.connector.updateLaborTicketInERP(laborTicket) };\n logger.info(\"Successfully updated labor ticket in ERP\", {\n laborTicketRef: laborTicket.laborTicketRef,\n });\n }\n\n // Now update MM entities with ERP ID attached\n const laborTicketForMM = { ...laborTicket };\n if (erpResult.erpUid) {\n laborTicketForMM.laborTicketId = erpResult.erpUid;\n \n // If this was a create (no initial laborTicketId) with a laborTicketRef,\n // patch the existing orphaned MM record with the new ERP ID first\n if (!laborTicket.laborTicketId && laborTicketForMM.laborTicketRef) {\n const mmApiClient = new MMApiClient();\n await mmApiClient.updateLaborTicketIdByRef(\n laborTicketForMM.laborTicketRef,\n erpResult.erpUid\n );\n \n logger.info(\"Patched existing MM labor ticket with new ERP ID\", {\n laborTicketRef: laborTicketForMM.laborTicketRef,\n laborTicketId: erpResult.erpUid,\n });\n }\n }\n\n // Set state based on clockOut before writing to MM\n laborTicketForMM.state = laborTicketForMM.clockOut ? \"CLOSED\" : \"OPEN\";\n\n // Write all fields to MM (this updates the existing record now that it has laborTicketId)\n const mmLaborTicket = MMSendLaborTicket.fromPlainObject(laborTicketForMM as any);\n\n const mmResult = await MMEntityProcessor.writeEntities(\n ERPObjType.LABOR_TICKETS,\n [mmLaborTicket],\n null // No caching for real-time operations\n );\n\n logger.info(\"Successfully updated MM entities after ERP operation\", {\n laborTicketRef: laborTicketForMM.laborTicketRef,\n laborTicketId: laborTicketForMM.laborTicketId,\n entitiesCreated: mmResult.upsertedEntities,\n });\n\n return {\n laborTicketRef: laborTicketForMM.laborTicketRef!,\n laborTicket: laborTicketForMM,\n };\n } catch (error) {\n // Format error to preserve meaningful message\n const formattedError = formatError(error);\n\n logger.error(\"Failed to process labor ticket with MM creation\", {\n laborTicketRef: laborTicket.laborTicketRef,\n action,\n error: formattedError.message,\n code: formattedError.code,\n });\n\n // Create enhanced error to propagate\n const enhancedError: any = error;\n enhancedError._formatted = formattedError;\n throw enhancedError;\n }\n }\n\n /**\n * Update checkpoint to prevent to-erp polling job from reprocessing this ticket\n * Only updates if the new timestamp is later than the current checkpoint (prevents moving backwards)\n */\n private async updateCheckpoint(\n erpType: string,\n result: { laborTicketRef: string; laborTicket: any }\n ): Promise<void> {\n const mmApiClient = new MMApiClient();\n const currentCheckpoint = await mmApiClient.getCheckpoint({\n system: erpType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: \"\",\n },\n });\n\n const currentTimestamp = (currentCheckpoint as any)?.timestamp;\n const newTimestamp = result.laborTicket.updatedAt || new Date().toISOString();\n\n // Only update checkpoint if new timestamp is later than current (prevents moving backwards)\n if (!currentTimestamp || new Date(newTimestamp) > new Date(currentTimestamp)) {\n await mmApiClient.saveCheckpoint({\n system: erpType,\n table: \"labor_tickets\",\n checkpointType: \"export\",\n checkpointValue: {\n timestamp: newTimestamp,\n },\n });\n\n logger.debug(\"Updated export checkpoint after NATS processing\", {\n laborTicketRef: result.laborTicketRef,\n previousCheckpoint: currentTimestamp,\n newCheckpoint: newTimestamp,\n });\n } else {\n logger.debug(\"Skipped checkpoint update (timestamp not newer)\", {\n laborTicketRef: result.laborTicketRef,\n currentCheckpoint: currentTimestamp,\n ticketTimestamp: newTimestamp,\n });\n }\n }\n}\n\n","import Bree, { Job } from \"bree\";\nimport Graceful from \"@ladjs/graceful\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { CoreConfiguration } from \"./configuration-manager\";\nimport logger from \"../reporting-service/logger\";\nimport { createConnectorFromPath } from \"../../utils/connector-factory\";\nimport { NatsLaborTicketListener } from \"./nats-labor-ticket-listener\";\n\nexport const runDataSyncService = async (connectorPath: string) => {\n const config = CoreConfiguration.inst();\n\n try {\n // Create connector instance\n const connector = await createConnectorFromPath(connectorPath);\n\n // Start NATS listener if enabled\n if (process.env.NATS_ENABLED === \"true\") {\n const natsListener = new NatsLaborTicketListener(connector);\n await natsListener.start();\n }\n // Find the SDK's node_modules directory and jobs path using ES module import\n const currentFileUrl = import.meta.url;\n const currentFilePath = fileURLToPath(currentFileUrl);\n\n // Navigate from current file to the SDK root and then to jobs\n // Current: /path/to/sdk/dist/mm-erp-sdk.js.js\n // Target: /path/to/sdk/dist/services/data-sync-service/jobs\n const sdkDistPath = path.dirname(currentFilePath);\n const jobsPath = path.join(\n sdkDistPath,\n \"services\",\n \"data-sync-service\",\n \"jobs\"\n );\n\n const bree = new Bree({\n root: jobsPath,\n logger,\n worker: {\n env: {\n CONNECTOR_PATH: connectorPath,\n // Pass through all required environment variables\n MM_MAPPING_SERVICE_URL: process.env.MM_MAPPING_SERVICE_URL,\n MM_MAPPING_AUTH_SERVICE_URL: process.env.MM_MAPPING_AUTH_SERVICE_URL,\n MM_MAPPING_SERVICE_TOKEN: process.env.MM_MAPPING_SERVICE_TOKEN,\n ERP_SYSTEM: process.env.ERP_SYSTEM,\n LOG_LEVEL: process.env.LOG_LEVEL,\n NODE_ENV: process.env.NODE_ENV,\n RETRY_ATTEMPTS: process.env.RETRY_ATTEMPTS,\n CACHE_TTL: process.env.CACHE_TTL,\n // Pass through all other environment variables that might be needed\n ...process.env,\n },\n },\n jobs: [\n // {\n // name: 'run-migrations', // Running this once on startup will create the tables in the sqlite database\n // },\n {\n name: \"from-erp\",\n timeout: \"10s\",\n interval: config.fromErpInterval,\n },\n {\n name: \"to-erp\",\n //timeout: '3s', // Use timeout during development to see the job in action quickly\n interval: config.toErpInterval,\n },\n {\n name: \"retry-failed-labor-tickets\",\n interval: config.retryLaborTicketsInterval,\n },\n {\n name: \"clean-up-expired-cache\",\n interval: config.cacheExpirationCheckInterval,\n },\n ],\n });\n\n logger.info(\n \"\\n================================INITIATING DATA SYNC CYCLES (Bree)================================\\n\"\n );\n const jobsConfig = bree.config.jobs.map((job: Job) => ({\n name: job.name,\n interval: job.interval,\n timeout: job.timeout,\n }));\n logger.info(\"JOBS CONFIGURATION:\", { jobs: jobsConfig });\n\n const graceful = new Graceful({ brees: [bree] });\n graceful.listen();\n\n (async () => {\n await bree.start();\n })();\n\n bree.on(\"jobStarted\", (job) => {\n console.log(\"Job \" + job.name + \" started\");\n });\n\n bree.on(\"jobCompleted\", (job) => {\n if (job.error) {\n console.error(\"Job \" + job.name + \" failed:\", job.error);\n } else {\n console.log(\"Job \" + job.name + \" completed successfully!\");\n }\n });\n\n bree.on(\"error\", (error) => {\n console.error(\"Bree error:\", error);\n });\n\n // Exit and let the jobs take over\n logger.info(\n \"\\n================================DATA SYNC CYCLES INITIATION COMPLETED================================\\n\"\n );\n } catch (error) {\n logger.error(\"startUp: Error initiating data sync cycles:\", error);\n }\n};\n","import { z } from \"zod\";\nexport const SQLServerConfigSchema = z.object({\n user: z.string().nonempty(\"User is required.\"),\n password: z.string().nonempty(\"Password is required.\"),\n database: z.string().nonempty(\"Database name is required.\"),\n server: z.string().nonempty(\"Server is required.\"),\n port: z.coerce.number().int().positive(\"Port must be a positive integer.\").default(1433),\n connectionTimeout: z.coerce\n .number()\n .int()\n .positive(\"Connection timeout must be a positive integer.\")\n .default(30000),\n requestTimeout: z.coerce\n .number()\n .int()\n .positive(\"Request timeout must be a positive integer.\")\n .default(60000),\n pool: z.object({\n max: z.coerce\n .number()\n .int()\n .positive(\"Max pool size must be a positive integer.\")\n .default(10),\n min: z.coerce\n .number()\n .int()\n .nonnegative(\"Min pool size must be a non-negative integer.\")\n .default(0),\n idleTimeoutMillis: z.coerce\n .number()\n .int()\n .nonnegative(\"Idle timeout must be a non-negative integer.\")\n .default(30000),\n }),\n options: z.object({\n encrypt: z.coerce.boolean().default(false),\n trustServerCertificate: z.coerce.boolean().default(false),\n }),\n});\n","import sql, { ConnectionPool, IRecordSet } from \"mssql\";\nimport { SQLServerConfigSchema } from \"./internal/sql-server-config\";\nimport logger from \"../reporting-service/logger\";\nimport { ERPResponse } from \"../../types/erp-types\";\nimport { SQLServerConfiguration } from \"./configuration\";\n\ntype PagingParams = {\n limit?: number;\n offset?: number;\n};\n\nexport class SqlServerService {\n private connection: ConnectionPool | null = null;\n private config: SQLServerConfiguration;\n\n constructor(config: SQLServerConfiguration) {\n this.config = config;\n }\n\n async dispose() {\n await this.closeConnection();\n }\n\n /**\n * Execute a prepared statement and return the results\n *\n * @param statementToPrepare The SQL query to prepare\n * @param params The parameters to bind to the prepared statement\n * @param paging Optional paging parameters\n * @returns The entities fetched from the database, along with paging information\n */\n public async executePreparedStatement(\n statementToPrepare: string,\n params: Record<string, string>,\n paging?: PagingParams\n ): Promise<ERPResponse | undefined> {\n const connection = await this.openConnection();\n\n const preparedStatement = new sql.PreparedStatement(connection);\n\n // Add all parameters as inputs\n Object.keys(params).forEach((key) => {\n preparedStatement.input(key, sql.VarChar);\n });\n\n // If limit is specified, try SQL paging\n const finalQuery =\n paging?.limit !== undefined\n ? `${statementToPrepare} OFFSET ${paging.offset || 0} ROWS FETCH NEXT ${paging.limit} ROWS ONLY`\n : statementToPrepare;\n\n await preparedStatement.prepare(finalQuery);\n\n let records;\n try {\n records = await preparedStatement.execute(params);\n } catch (error) {\n const errorInfo = error as Error;\n const errorMessage =\n \"Error fetching data from ERP -> \" +\n (errorInfo.cause ? `Cause: ${errorInfo.cause}. ` : \"\") +\n `Name: ${errorInfo.name}. ` +\n `Message: ${errorInfo.message}. ` +\n `Stack: ${errorInfo.stack}`;\n\n throw new Error(errorMessage);\n } finally {\n try {\n await preparedStatement.unprepare();\n } catch (unprepareError) {\n logger.error(\"Error during unprepare:\", unprepareError);\n }\n }\n\n const allRecords = SqlServerService.recordsetToRecords(records?.recordset);\n const rowsFetched = records?.rowsAffected[0] || 0;\n\n // If SQL paging failed or we need to handle offset, apply in-memory paging\n const pagedData =\n paging?.offset !== undefined\n ? allRecords.slice(\n paging.offset,\n (paging.offset || 0) + (paging.limit || allRecords.length)\n )\n : allRecords;\n\n return {\n data: pagedData,\n paging: {\n count: rowsFetched,\n limit: paging?.limit || 0,\n offset: paging?.offset || 0,\n nextPage: (() => {\n const currentPageEnd = (paging?.offset || 0) + (paging?.limit || 0);\n return paging?.limit && currentPageEnd < rowsFetched\n ? String(currentPageEnd)\n : undefined;\n })(),\n previousPage: paging?.offset\n ? String(Math.max(0, (paging.offset || 0) - (paging.limit || 10)))\n : undefined,\n },\n };\n }\n\n /**\n * Opens a connection to SQL Server\n * Caches the connection so that it can be reused.\n * On failure to connect, throws\n */\n async openConnection(): Promise<ConnectionPool> {\n // If we have a connection and it's still connected, reuse it\n if (this.connection?.connected) {\n logger.debug(\"Reusing existing SQL Server connection\");\n return this.connection;\n }\n\n // If we have a connection but it's not connected, just null it out\n if (this.connection) {\n logger.debug(\"Clearing disconnected SQL Server connection\");\n this.connection = null;\n }\n\n try {\n logger.info(\"Opening new SQL Server connection\");\n const sqlConfig = SQLServerConfigSchema.parse({\n user: this.config.username,\n password: this.config.password,\n database: this.config.database,\n server: this.config.host,\n port: parseInt(this.config.port),\n connectionTimeout: parseInt(this.config.connectionTimeout),\n requestTimeout: parseInt(this.config.requestTimeout),\n pool: {\n max: parseInt(this.config.poolMax),\n min: parseInt(this.config.poolMin),\n idleTimeoutMillis: parseInt(this.config.idleTimeoutMillis),\n },\n options: {\n encrypt: this.config.encrypt,\n trustServerCertificate: this.config.trustServer,\n },\n });\n\n this.connection = await sql.connect(sqlConfig);\n return this.connection;\n } catch (error) {\n logger.error(\"SqlServerService>>openConnection>> Connection failed\", {\n error,\n });\n throw new Error(\"SQL Server connection failed\");\n }\n }\n\n /**\n * Transform a IRecordSet from a SQL query into an array of Record<string, string> instances.\n * @param recordset IRecordSet from a SQL query\n * @returns array of Record<string, string> instances\n */\n public static recordsetToRecords(\n recordset?: IRecordSet<Record<string, unknown>>\n ): Record<string, string>[] {\n const data: Record<string, string>[] =\n recordset?.map((row) => {\n const transformedRow: Record<string, string> = {};\n Object.keys(row).forEach((key) => {\n transformedRow[key] = row[key]?.toString() || \"\";\n });\n return transformedRow;\n }) || [];\n\n return data;\n }\n\n private async closeConnection(): Promise<void> {\n if (this.connection?.connected) {\n logger.info(\"Closing SQL Server connection\");\n try {\n await this.connection.close();\n } catch (error) {\n logger.error(\n \"SqlServerService::closeConnection: Error closing connection\",\n {\n error,\n }\n );\n }\n this.connection = null;\n }\n }\n}\n","import sql from \"mssql\";\nimport logger from \"../../reporting-service/logger\";\n\nexport interface SQLTransactionConfig {\n isolationLevel?: sql.IIsolationLevel;\n}\n\nexport interface SQLTransactionOperation<T, R> {\n execute(transaction: sql.Transaction, data: T): Promise<R>;\n}\n\nexport class SQLTransactionManager {\n constructor(private connection: sql.ConnectionPool) {}\n /**\n * Executes a transaction with the given operation and data, manages rollbacks\n * @param operation The SQLTransactionOperation to execute\n * @param data The sql input parameters to pass to the operation\n * @param config The configuration for the transaction\n * @returns The result of the operation; throws exceptions on failures\n */\n async executeTransaction<T, R>(\n operation: SQLTransactionOperation<T, R>,\n data: T,\n config: SQLTransactionConfig = {}\n ): Promise<R | undefined> {\n const transaction = new sql.Transaction(this.connection);\n const isolationLevel =\n config.isolationLevel || sql.ISOLATION_LEVEL.READ_COMMITTED;\n\n try {\n await transaction.begin(isolationLevel);\n const result = await operation.execute(transaction, data);\n await transaction.commit();\n return result;\n } catch (error) {\n logger.error(\"Transaction error:\", error);\n try {\n await transaction.rollback();\n } catch (rollbackError) {\n logger.error(\"Rollback error:\", rollbackError);\n }\n throw error;\n }\n }\n}\n","import sql from \"mssql\";\nimport { SQLInput } from \"../types/sql-input-param\";\nimport { SQLTransactionOperation } from \"./sql-transaction-manager\";\n\n/**\n * Insert a new labor ticket into the database.\n * Create a query named laborTicketsInsert in the sql_toERP folder, add its inputs to the SQLInput array\n * The laborTicketsInsert query is expected to return the NewRowUniqueId of the inserted labor ticket\n *\n * @param transaction The SQL transaction object\n * @param inputs The SQL input parameters\n * @returns The NewRowUniqueId of the inserted labor ticket\n */\nexport class SQLLaborTicketInsertOperation\n implements SQLTransactionOperation<SQLInput[], number>\n{\n constructor(private readonly sqlQuery: string) {}\n\n async execute(\n transaction: sql.Transaction,\n inputs: SQLInput[]\n ): Promise<number> {\n const request = transaction.request();\n inputs.forEach((input) => {\n request.input(input.name, input.type, input.value);\n });\n\n const result = await request.query(this.sqlQuery);\n\n if (!result.recordset[0].NewRowUniqueId) {\n throw new Error(\n \"No LaborTicketId generated by the labor ticket insert query: \" +\n JSON.stringify(request.parameters, null, 2)\n );\n }\n\n return result.recordset[0].NewRowUniqueId;\n }\n}\n\n/**\n * Update an existing labor ticket in the database.\n * Create a query named laborTicketsUpdate in the sql_toERP folder, add its inputs to the SQLInput array\n *\n * @param transaction The SQL transaction object\n * @param inputs The SQL input parameters\n * @returns The number of rows affected\n */\nexport class SQLLaborTicketUpdateOperation\n implements SQLTransactionOperation<SQLInput[], number>\n{\n constructor(private readonly sqlQuery: string) {}\n\n async execute(\n transaction: sql.Transaction,\n inputs: SQLInput[]\n ): Promise<number> {\n const request = transaction.request();\n inputs.forEach((input) => {\n request.input(input.name, input.type, input.value);\n });\n\n const result = await request.query(this.sqlQuery);\n return result.rowsAffected[0];\n }\n}\n","import { SQLTransactionManager } from \"./internal/sql-transaction-manager\";\nimport {\n SQLLaborTicketInsertOperation,\n SQLLaborTicketUpdateOperation,\n} from \"./internal/sql-labor-ticket-operations\";\nimport { MMReceiveLaborTicket } from \"../mm-api-service/types/receive-types\";\nimport { SqlServerService } from \"./sql-server-service\";\nimport logger from \"../reporting-service/logger\";\nimport { SQLInput } from \"./types/sql-input-param\";\n\nexport class SqlServerHelper {\n /**\n * Creates a new labor ticket in the SQL Server database\n * Opens but does not close the connection\n *\n * @param sqlServerService The SQL Server service to use for connections\n * @param laborTicket The labor ticket to create\n * @param sqlInputs The SQL query parameters\n * @param insertQuery The SQL query to execute for insertion\n *\n * @returns The processed labor ticket, with the ERP's laborTicketId set\n * Throws exceptions on failures, including if the query does not return a laborTicketId\n */\n static async createLaborTicket(\n sqlServerService: SqlServerService,\n laborTicket: MMReceiveLaborTicket,\n sqlInputs: SQLInput[],\n insertQuery: string\n ): Promise<MMReceiveLaborTicket> {\n const connection = await sqlServerService.openConnection();\n if (!connection) {\n throw new Error(\n \"Unable to establish database connection to create labor ticket.\"\n );\n }\n\n const transactionManager = new SQLTransactionManager(connection);\n const laborTicketId = await transactionManager.executeTransaction(\n new SQLLaborTicketInsertOperation(insertQuery),\n sqlInputs\n );\n\n if (laborTicketId) {\n laborTicket.laborTicketId = laborTicketId.toString();\n return laborTicket;\n } else {\n throw new Error(\n `The query did not return the labor ticket id for labor ticket ref: ${laborTicket.laborTicketRef}`\n );\n }\n }\n\n /**\n * Updates an existing labor ticket in the SQL Server database\n * Opens but does not close the connection\n * @param sqlServerService The SQL Server service to use for connections\n * @param laborTicket The labor ticket to update\n * @param sqlInputs The SQL query parameters\n * @param updateQuery The SQL query to execute for update\n *\n * @returns The processed labor ticket\n * Throws exceptions on failures\n */\n static async updateLaborTicket(\n sqlServerService: SqlServerService,\n laborTicket: MMReceiveLaborTicket,\n sqlInputs: SQLInput[],\n updateQuery: string\n ): Promise<MMReceiveLaborTicket> {\n const connection = await sqlServerService.openConnection();\n if (!connection) {\n throw new Error(\n \"Unable to establish database connection to update labor ticket.\"\n );\n }\n\n const transactionManager = new SQLTransactionManager(connection);\n await transactionManager.executeTransaction(\n new SQLLaborTicketUpdateOperation(updateQuery),\n sqlInputs\n );\n\n return laborTicket;\n }\n\n /**\n * Logs the SQL input parameters for debugging\n */\n static logQueryInputs(inputs: SQLInput[]): void {\n logger.info(\"SQL Input Variables:\");\n logger.info(\"----------------------------------------\");\n logger.info(\"Variable Name | Value\");\n logger.info(\"----------------------------------------\");\n inputs.forEach((input) => {\n logger.info(`${input.name.padEnd(20)} | ${input.value}`);\n });\n logger.info(\"----------------------------------------\");\n }\n}\n","import { PsqlConfiguration } from \"./configuration\";\nimport { ERPResponse } from \"../../types/erp-types\";\nimport { OdbcErrorResponse } from \"./internal/types/psql-types\";\nimport logger from \"../reporting-service/logger\";\n\ntype PagingParams = {\n limit?: number;\n offset?: number;\n};\n\n/**\n * ODBC connection interface for type safety\n */\ninterface OdbcConnection {\n query(sql: string): Promise<any[]>;\n close(): Promise<void>;\n}\n\n/**\n * ODBC module interface\n */\ninterface OdbcModule {\n connect(connectionString: string): Promise<OdbcConnection>;\n}\n\nexport class PsqlService {\n private config: PsqlConfiguration;\n private static odbcModule: OdbcModule | null = null;\n private static odbcLoadError: Error | null = null;\n\n constructor(config: PsqlConfiguration) {\n this.config = config;\n }\n\n /**\n * Dynamically load the ODBC module with lazy initialization and caching\n * @throws Error with helpful message if ODBC package is not installed\n */\n private static async getOdbc(): Promise<OdbcModule> {\n // If we've already tried and failed, throw the cached error\n if (this.odbcLoadError) {\n throw this.odbcLoadError;\n }\n\n // If already loaded, return cached module\n if (this.odbcModule) {\n return this.odbcModule;\n }\n\n try {\n // Dynamic import - only loads when actually needed\n // @ts-ignore - odbc is an optional dependency, may not be installed at build time\n const odbcImport = await import(\"odbc\");\n // Handle both default export and named export patterns\n const odbc = odbcImport.default || odbcImport;\n this.odbcModule = odbc as OdbcModule;\n return this.odbcModule;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.odbcLoadError = new Error(\n `ODBC package is required for PSQL service but is not installed or failed to load.\\n` +\n `Install it with: npm install odbc\\n` +\n `Also install OS-level dependencies, e.g. on Alpine Linux:\\n` +\n ` apk add --no-cache unixodbc unixodbc-dev python3 make g++\\n` +\n `For other Linux distributions, install unixodbc and unixodbc-dev packages.\\n` +\n `Original error: ${errorMessage}`\n );\n throw this.odbcLoadError;\n }\n }\n\n // REMOVED: dispose() method - not needed anymore\n // REMOVED: connection property - not needed anymore\n // REMOVED: openConnection() method - not needed anymore\n // REMOVED: closeConnection() method - not needed anymore\n\n /**\n * Build PSQL ODBC connection string\n * CRITICAL: ServerName must use IP.PORT format (e.g., 10.4.0.11.1583)\n */\n private buildConnectionString(): string {\n const serverName = `${this.config.host}.${this.config.port}`;\n\n return (\n [\n \"Driver={Pervasive ODBC Interface}\",\n `ServerName=${serverName}`,\n `DBQ=${this.config.database}`,\n `UID=${this.config.username}`,\n `PWD=${this.config.password}`,\n \"AutoDoubleQuote=0\",\n ].join(\";\") + \";\"\n );\n }\n\n /**\n * Execute a query and return the results\n * Creates a fresh connection for each query to avoid handle corruption\n *\n * SECURITY WARNING: This method executes the provided SQL string as-is.\n * - Parameter binding is NOT implemented; the `params` argument is currently ignored.\n * - Never concatenate untrusted/user-supplied input into `query`.\n * - Doing so can result in SQL injection vulnerabilities and data exposure.\n * If dynamic values are required, ensure they are strictly validated/escaped\n * or implement proper parameterized execution before accepting untrusted input.\n *\n * @param query The SQL query to execute\n * @param params Query parameters (currently unused for PSQL read operations)\n * @param paging Optional paging parameters\n * @returns The entities fetched from the database, along with paging information\n */\n public async executePreparedStatement(\n query: string,\n params: Record<string, string> = {},\n paging?: PagingParams\n ): Promise<ERPResponse | undefined> {\n // Dynamically load ODBC module (will throw helpful error if not installed)\n const odbc = await PsqlService.getOdbc();\n let connection: OdbcConnection | null = null;\n\n try {\n // Create fresh connection for THIS query only\n const connStr = this.buildConnectionString();\n logger.debug(\"Creating fresh PSQL connection for query\");\n connection = await odbc.connect(connStr);\n\n if (Object.keys(params).length > 0) {\n logger.warn(\n \"PsqlService: Query parameters provided but parameter binding not yet implemented. \" +\n \"Using direct query execution.\"\n );\n }\n\n const records = await connection.query(query);\n const allRecords = PsqlService.recordsetToRecords(records);\n const rowsFetched = allRecords.length;\n\n // Apply paging if requested\n const pagedData =\n paging?.offset !== undefined || paging?.limit !== undefined\n ? allRecords.slice(\n paging.offset || 0,\n (paging.offset || 0) + (paging.limit || allRecords.length)\n )\n : allRecords;\n\n return {\n data: pagedData,\n paging: {\n count: rowsFetched,\n limit: paging?.limit || 0,\n offset: paging?.offset || 0,\n nextPage:\n paging?.limit && (paging.offset || 0) + paging.limit < rowsFetched\n ? String((paging.offset || 0) + paging.limit)\n : undefined,\n previousPage: paging?.offset\n ? String(Math.max(0, (paging.offset || 0) - (paging.limit || 10)))\n : undefined,\n },\n };\n } catch (error) {\n // If this is an ODBC load error (from getOdbc), re-throw it as-is\n // since it already has a helpful error message\n if (error instanceof Error && error.message.includes(\"ODBC package is required\")) {\n throw error;\n }\n\n // Otherwise, handle as ODBC runtime error\n const errorInfo = error as OdbcErrorResponse;\n logger.error(\"Error fetching data from PSQL\", {\n error: errorInfo.message,\n odbcErrors: errorInfo.odbcErrors,\n query: query.substring(0, 200), // Log first 200 chars of query\n });\n\n throw this.handleOdbcError(errorInfo);\n } finally {\n // CRITICAL: Always close connection, even on error\n if (connection) {\n try {\n await connection.close();\n logger.debug(\"PSQL connection closed successfully\");\n } catch (err) {\n // Don't throw on close errors, just log\n logger.warn(\"Error closing PSQL connection (non-fatal)\", {\n error: err,\n });\n }\n }\n }\n }\n\n /**\n * Transform ODBC result set to array of Record<string, string> instances.\n * IMPORTANT: PSQL CHAR fields are often padded with spaces - we trim them\n */\n public static recordsetToRecords(recordset: any[]): Record<string, string>[] {\n if (!Array.isArray(recordset)) {\n return [];\n }\n\n const data: Record<string, string>[] = recordset.map((row) => {\n const transformedRow: Record<string, string> = {};\n Object.keys(row).forEach((key) => {\n const value = row[key];\n transformedRow[key] =\n value !== null && value !== undefined ? String(value).trim() : \"\";\n });\n return transformedRow;\n });\n\n return data;\n }\n\n /**\n * Handle ODBC errors and provide meaningful messages\n */\n private handleOdbcError(error: OdbcErrorResponse): Error {\n const odbcError = error.odbcErrors?.[0];\n const errorCode = odbcError?.state;\n const message = odbcError?.message || error.message;\n\n switch (errorCode) {\n case \"08S01\":\n return new Error(\n \"PSQL connection failed. Check: \" +\n \"1) PVSW environment variable set to /usr/local/psql/etc/pvsw.ini, \" +\n \"2) Network connectivity to ports 1583/3351, \" +\n \"3) ODBC configuration files in /usr/local/psql/etc/ and /etc/. \" +\n `Original error: ${message}`\n );\n case \"28000\":\n return new Error(\n `PSQL authentication failed. Check username/password. Original error: ${message}`\n );\n case \"42000\":\n return new Error(`PSQL SQL syntax error. Original error: ${message}`);\n case \"42S02\":\n return new Error(\n `PSQL table or view not found. Check table names in query. Original error: ${message}`\n );\n default:\n return new Error(`PSQL error (${errorCode || \"unknown\"}): ${message}`);\n }\n }\n}\n","/**\n * Helper functions for PSQL/Pervasive database operations\n */\n\n/**\n * Formats a date from PSQL YYMMDD format to ISO date string\n * @param psqlDate Date in YYMMDD format (e.g., \"250105\" for Jan 5, 2025)\n * @returns ISO date string (e.g., \"2025-01-05\") or null if invalid\n */\nexport function formatPsqlDate(psqlDate: string): string | null {\n if (!psqlDate || psqlDate.trim().length !== 6 || psqlDate === \"000000\") { \n return null; \n } \n\n try {\n const year = parseInt(psqlDate.substring(0, 2), 10);\n const month = parseInt(psqlDate.substring(2, 4), 10);\n const day = parseInt(psqlDate.substring(4, 6), 10);\n\n // Convert 2-digit year to 4-digit (assuming 2000s)\n const fullYear = year + 2000;\n\n // Basic validation\n if (month < 1 || month > 12 || day < 1 || day > 31) {\n return null;\n }\n\n const date = new Date(fullYear, month - 1, day);\n // Validate that the constructed date matches the input values\n if (\n date.getFullYear() !== fullYear ||\n date.getMonth() !== (month - 1) ||\n date.getDate() !== day\n ) {\n return null;\n }\n return date.toISOString().split(\"T\")[0];\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Formats a time from PSQL HHMM format to HH:MM:SS\n * @param psqlTime Time in HHMM format (e.g., \"1430\" for 2:30 PM)\n * @returns Time string in HH:MM:SS format or null if invalid\n */\nexport function formatPsqlTime(psqlTime: string): string | null {\n if (!psqlTime) {\n return null;\n }\n const trimmed = psqlTime.trim();\n if (trimmed.length !== 4) {\n return null;\n }\n try {\n const hours = parseInt(trimmed.substring(0, 2), 10);\n const minutes = parseInt(trimmed.substring(2, 4), 10);\n if (\n isNaN(hours) ||\n isNaN(minutes) ||\n hours < 0 ||\n hours > 23 ||\n minutes < 0 ||\n minutes > 59\n ) {\n return null;\n }\n // Pad hours and minutes to 2 digits\n const hoursStr = hours.toString().padStart(2, \"0\");\n const minutesStr = minutes.toString().padStart(2, \"0\");\n return `${hoursStr}:${minutesStr}:00`;\n } catch (error) {\n return null;\n }\n}\n\n/**\n * Combines PSQL date and time into ISO datetime string\n * @param psqlDate Date in YYMMDD format\n * @param psqlTime Time in HHMM format\n * @returns ISO datetime string or null if invalid\n */\nexport function combinePsqlDateTime(\n psqlDate: string,\n psqlTime: string\n): string | null {\n const date = formatPsqlDate(psqlDate);\n const time = formatPsqlTime(psqlTime);\n\n if (!date || !time) {\n return null;\n }\n\n return `${date}T${time}`;\n}\n\n/**\n * Helper to check if a PSQL date is \"empty\" (000000 or blank)\n */\nexport function isPsqlDateEmpty(psqlDate: string): boolean {\n return !psqlDate || psqlDate === \"000000\" || psqlDate.trim() === \"\";\n}\n\n/**\n * Clean and trim PSQL CHAR field (removes trailing spaces)\n * PSQL CHAR fields are fixed-width and padded with spaces\n */\nexport function cleanPsqlCharField(value: string | null | undefined): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n return String(value).trim();\n}\n"],"names":["ERPObjType","Operator","config","httpError","UrlBase","path","a","laborTicketResult","DEFAULT_RETRY_ATTEMPTS","query","index","messageToSend","MMEntityProcessor","MMSendLaborTicket"],"mappings":"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"}