import { ERPObjType } from "../../types/erp-types.js"; import { MMReceiveDispatchPriority, MMReceiveLaborTicket, MMReceiveTimesheet, MMReceiveWorkOrder, MMReceiveWorkOrderOperation } from "./types/receive-types.js"; import { FetchDispatchPrioritiesFilter, RemoveDispatchPriorityScopeResult, ReplaceDispatchPriorityScopeResult } from "./dispatch-priorities.js"; import { MMTokenManager } from "./token-mgr.js"; import * as MM_SEND_TYPES from "./types/send-types.js"; import { HTTPClient } from "../../utils/http-client.js"; import { Checkpoint } from "./types/checkpoint.js"; import { MMAggregatedPartCountBucket, MMApiBaseResponse, MMMappedErpResourceRow, MMGraphQLReasonResponse, MMGraphQLWorkOrderResponse, MMMachineGroupsResponse } from "./types/mm-response-interfaces.js"; /** * Class to manage interactions with the MM Mapping Service * Integrates an MM Token Manager to handle the API token. */ export declare class MMApiClient { tokenMgr: MMTokenManager; api: HTTPClient; private baseUrls; resourceURLs: { 0: string; 1: string; 2: string; 3: string; 4: string; 5: string; 6: string; 7: string; MAPPING: string; CHECKPOINTS: string; TIMESHEETS: string; MACHINE_GROUPS: string; GRAPHQL: string; }; constructor(); private toMMApiPayload; private executeWithAuthRetry; /** * Fetch data from the MM API, with an auth retry mechanism * Auth Retry Level (executeWithAuthRetry) * - 401 (Unauthorized) and 403 (Forbidden) get special treatment * - These trigger a token refresh and one retry attempt * - If the retry still fails, the error is re-thrown * * HTTP Status Codes behavior: * - 2xx - Success, returns normally * - 4xx - Client errors, throws immediately * - 5xx - Server errors, retries then throws * * Flags: */ private getData; /** * Send data to the MM API, with an auth retry mechanism * Auth Retry Level (executeWithAuthRetry) * - 401 (Unauthorized) and 403 (Forbidden) get special treatment * - These trigger a token refresh and one retry attempt * - If the retry still fails, the error is re-thrown * * HTTP Status Codes behavior: * - 2xx - Success, returns normally * - 4xx - Client errors, throws immediately * - 5xx - Server errors, retries then throws * * Flags: * - 'raw' = true and 'dedupe' = true will be set for MM's ERP API url */ private postData; /** * Delete data from the MM API, with an auth retry mechanism * Auth Retry Level (executeWithAuthRetry) * - 401 (Unauthorized) and 403 (Forbidden) get special treatment * - These trigger a token refresh and one retry attempt * - If the retry still fails, the error is re-thrown * * HTTP Status Codes behavior: * - 2xx - Success, returns normally * - 4xx - Client errors, throws immediately * - 5xx - Server errors, retries then throws * */ private deleteData; /** * Patch data to the MM API, with an auth retry mechanism * Auth Retry Level (executeWithAuthRetry) * - 401 (Unauthorized) and 403 (Forbidden) get special treatment * - These trigger a token refresh and one retry attempt * - If the retry still fails, the error is re-thrown * * HTTP Status Codes behavior: * - 2xx - Success, returns normally * - 4xx - Client errors, throws immediately * - 5xx - Server errors, retries then throws * */ private patchData; private graphqlRequest; /** * Fetch a checkpoint for a specific system, table, and checkpoint type * @param checkpoint The checkpoint to fetch * @returns Promise with the checkpoint data */ private fetchCheckpoint; /** * Send resources to the MM API * @param resources Array of resources to send * @returns Promise with the API response */ sendResourcesToMM(resources: MM_SEND_TYPES.MMSendResource[]): Promise; /** * Send parts to the MM API * @param parts Array of parts to send * @returns Promise with the API response */ sendPartsToMM(parts: MM_SEND_TYPES.MMSendPart[]): Promise; /** * Send part operations to the MM API * @param operations Array of part operations to send * @returns Promise with the API response */ sendPartOperationsToMM(operations: MM_SEND_TYPES.MMSendPartOperation[]): Promise; /** * Send work orders to the MM API * @param workOrders Array of work orders to send * @param queryParams Optional query params forwarded to the endpoint (e.g. * `{ clearColumns: 'closedDate' }` to opt specific columns into null-clearing) * @returns Promise with the API response */ sendWorkOrdersToMM(workOrders: MM_SEND_TYPES.MMSendWorkOrderPayload[], queryParams?: { [key: string]: string; }): Promise; /** * Send work order operations to the MM API * @param workOrderOperations Array of work order operations to send * @param queryParams Optional query params forwarded to the endpoint (e.g. * `{ clearColumns: 'closedDate' }` to opt specific columns into null-clearing) * @returns Promise with the API response */ sendWorkOrderOperationsToMM(workOrderOperations: MM_SEND_TYPES.MMSendWorkOrderOperationPayload[], queryParams?: { [key: string]: string; }): Promise; /** * Send persons to the MM API * @param persons Array of persons to send * @returns Promise with the API response */ sendPersonsToMM(persons: MM_SEND_TYPES.MMSendPerson[]): Promise; /** * Send reasons to the MM API * @param reasons Array of reasons to send * @returns Promise with the API response */ sendReasonsToMM(reasons: MM_SEND_TYPES.MMSendReason[]): Promise; /** * Send labor tickets to the MM API * Note: Deduplication marking is handled by callers (NATS listener and to-erp job) * @param laborTickets Array of labor tickets to send * @returns Promise with the API response */ sendLaborTicketsToMM(laborTickets: MM_SEND_TYPES.MMSendLaborTicket[]): Promise; /** * Delete labor tickets from the MM API * @param laborTickets Array of labor ticket records to delete * @returns Promise with the API response */ deleteLaborTickets(laborTickets: Record[]): Promise; /** * Fetch transformed labor tickets from the MM API * @returns Promise with the transformed labor tickets */ fetchTransformedLaborTickets(): Promise; /** * Fetch labor ticket updates from the MM API * @param query Query parameters for filtering updates * @returns Promise with an array of labor ticket updates */ fetchLaborTicketUpdates(query: Record): Promise; /** * Fetch timesheet updates from the MM API (clock in/out rows pending export to ERP). * @param query Typically `{ system, checkpointType: 'export' }` aligned with labor export/updates. */ fetchTimesheetUpdates(query: Record): Promise; /** * Fetch a single labor ticket by reference from the MM API * @param laborTicketRef The labor ticket reference to fetch * @returns Promise with the labor ticket data */ fetchLaborTicketByRef(laborTicketRef: string): Promise; /** * Fetch a checkpoint for a specific system, table, and checkpoint type * @param checkpoint The checkpoint to fetch * @returns Promise with the checkpoint data */ getCheckpoint(checkpoint: Checkpoint): Promise; /** * Save a checkpoint to the MM API * @param checkpoint The checkpoint to save * @returns Promise with the API response */ saveCheckpoint(checkpoint: Checkpoint): Promise; /** * Initialize a checkpoint for a specific system, table, and checkpoint type * @param checkpoint The checkpoint to initialize * @returns Promise with the checkpoint data */ initializeCheckpoint(checkpoint: Checkpoint): Promise; /** * Send data to MM API based on the object type * @param type The type of ERP object to send * @param objects Array of objects to send * @returns Promise with the API response including affected rows count */ sendDataToMMByType(type: ERPObjType, objects: MM_SEND_TYPES.IToRESTApiObject[]): Promise; /** * Update the labor ticket ERP ID by laborTicketRef * @param laborTicketRef The MM API labor ticket unique ID * @param erpObjectId The ERP labor ticket ID to update * @returns The updated labor ticket */ updateLaborTicketIdByRef(laborTicketRef: string, laborTicketId: string): Promise; /** * Add failed labor ticket refs to the MM API * @param failedTicketRefs The failed labor ticket references * @returns The response from the MM API */ addFailedLaborTicketRefs(system: string, failedTicketRefs: Array): Promise; fetchFailedLaborTickets(system: string): Promise; /** * Send connector logs to the MM API * @param logEntry Single log entry to send * @returns Promise with the API response */ sendConnectorLog(logEntry: { source: string; level: string; message: string; }): Promise; /** * Send bulk connector logs to the MM API * @param logs Array of log entries to send * @returns Promise with the API response */ sendBulkConnectorLogs(logs: { source: string; level: string; message: string; }[]): Promise; deleteFailedLaborTicketIds(system: string, laborTicketRefs: Array): Promise; /** * Fetch machine groups from the MM API * @returns Promise with the machine groups data */ fetchMachineGroups(): Promise; /** * Fetch a single reason by reasonRef via GraphQL (e.g. when labor ticket has reasonRef but no reasons array). * @param reasonRef MM reason reference (bigint) * @returns The reason row with reasonId, code, etc., or null if not found */ fetchReasonByRef(reasonRef: string | number): Promise; /** * Fetch a specific work order using GraphQL * @param workOrderId Work order identifier to fetch * @returns Promise with the work order data (array with limit 1; may be empty if not found) */ fetchWorkOrderFromGraphQL(workOrderId: string): Promise; /** * Fetch ALL work orders for the company (token-scoped) using paged GraphQL queries. * * Pages by offset with a stable composite-key ordering until a short page is returned. * Any page failure throws (via the shared GraphQL error handling) — callers never * receive a silently truncated result. * * @param options.pageSize Rows per GraphQL page (default 500) * @returns All work order rows as {@link MMReceiveWorkOrder} (quantityRequired coerced to number) */ fetchAllErpWorkOrdersFromGraphQL(options?: { pageSize?: number; }): Promise; /** * Fetch ALL work order operations for the company (token-scoped) using paged GraphQL * queries. Same contract as {@link fetchAllErpWorkOrdersFromGraphQL}: stable * composite-key ordering, any page failure or malformed page throws — callers never * receive a silently truncated result. * * @param options.pageSize Rows per GraphQL page (default 500) * @returns All rows as {@link MMReceiveWorkOrderOperation} (nullable numeric fields kept * as `number | null`, `sequenceNumber` rendered as a string like the REST import) */ fetchAllErpWorkOrderOperationsFromGraphQL(options?: { pageSize?: number; }): Promise; /** * Fetch the work order operations of a **given set of work orders** (token-scoped) via * `erpWorkOrderOperations(where: { workOrderId: { _in: ... } })`. The filtered * counterpart of {@link fetchAllErpWorkOrderOperationsFromGraphQL} for callers that need * MM's operation identities (lot/split/sub, resourceId, status) for a bounded set of jobs * — e.g. an ordering authority resolving its rows before a dispatch-priority write — on * tenants where fetching every operation is not workable. * * Ids are trimmed, blanks dropped and duplicates removed (exact text match, like MM). * The list is sent in chunks of `chunkSize` ids, each chunk paged by `pageSize` with the * same stable composite-key ordering as the fetch-all query. Any page failure or * malformed page throws — callers never receive a silently truncated result. * * @param workOrderIds Work order ids to fetch operations for; an empty list returns `[]` without a request * @param options.chunkSize Ids per `_in` list (default 200) * @param options.pageSize Rows per GraphQL page (default 500) * @returns All matching rows as {@link MMReceiveWorkOrderOperation}, in chunk then key order */ fetchErpWorkOrderOperationsByWorkOrderIdsFromGraphQL(workOrderIds: readonly string[], options?: { chunkSize?: number; pageSize?: number; }): Promise; /** * Fetch dispatch-priority rows (token-scoped) from `erpWorkOrderOperationDispatchPriorities`, * filtered by scope and/or work order, paged and ordered by scope then priority. * See `dispatch-priorities.ts` for the table's semantics. * * A filter that names scopes or work orders but is empty after trimming returns `[]` * without a request (an empty `_in` must not degrade to "match everything"). * Any page failure or malformed page throws. * * @param filter.scopeResourceId / scopeResourceIds Restrict to these scopes * @param filter.workOrderId / workOrderIds Restrict to these work orders * @param filter.unresolvedOnly Only rows whose `workOrderOperation` relationship is null * @param filter.pageSize Rows per GraphQL page (default 500) */ fetchDispatchPrioritiesFromGraphQL(filter?: FetchDispatchPrioritiesFilter): Promise; /** * **Scope-level replace** of dispatch priorities: in one transaction, delete every row * with this `scopeResourceId` and insert `rows` as the scope's complete new list. Never a * per-row upsert — a list is valid only as a whole, and rows for operations that left the * list must disappear with it. An empty `rows` list clears the scope (same as * {@link removeDispatchPriorityScope}). * * Before any request, rows are validated (see `assertValidDispatchPriorityRows`: the SDK * requires `authorityOperationId`, which MM does not) and deduplicated on MM's unique key * (scope + workOrderId + sequenceNumber + lot + split + sub), keeping the better-ranked row * — the lower `priority`, or the earlier of the two on an exact tie — and reporting the * other in `droppedDuplicates`; without this, one collision would fail the whole scope. * * Writes go through the MM GraphQL proxy, not erp-api, and bypass the local dedupe cache * by construction — an unchanged scope is still re-asserted on every call. * * **Assumes a single writer per company**, which is MachineMetrics' deployment model: one * connector, single-threaded, writing in sequence. Concurrent replacements of one scope * are not serialized by the database and would silently leave the scope holding the union * of two complete lists; no lock is available at this layer to prevent it. See * `dispatch-priorities.ts` for the measurements and what would be needed to lift this. * * Failure semantics: a `GraphQLError` means MM evaluated and rejected the document on the * attempt that returned it, so that attempt wrote nothing (the document is atomic). A * transport failure (`HTTPError`: gateway error, timeout) leaves the outcome unknown — the * scope may or may not have been replaced. One caveat: with `MM_API_RETRY_ATTEMPTS` above * its default of 0 the HTTP client re-posts on timeout, and a retry can race its own * timed-out first attempt; the uniqueness violation it then reports means the first * attempt committed. Either way the next cycle's replace re-asserts the scope, so callers * should log and retry on the next sweep rather than reason about partial state. * * @param scopeResourceId The scope (ERP resource id of a machine or group); rows carrying a different `scopeResourceId` throw * @param rows The scope's complete ordered list * @throws {Error} on invalid rows, an empty scope id, or a malformed response * @throws {GraphQLError} when MM rejects the document (nothing is written) * @throws {HTTPError} on a transport failure (outcome unknown) */ replaceDispatchPriorityScope(scopeResourceId: string, rows: readonly MM_SEND_TYPES.MMSendDispatchPriority[]): Promise; /** * Remove every dispatch-priority row of a scope, so the App falls back to MM's own * ordering for it instead of a known-stale list. For a scope the authority stopped * issuing, or one the connector excluded this cycle. A scope whose read merely * **failed** should not be removed — its previous rows stay valid until replaced. * * The scope is addressed **exactly as given**, not trimmed: deletes, like reads, work in * MM's own text, so anything {@link fetchDispatchPriorityScopes} returns can be passed * straight back. A scope with stray whitespace therefore removes nothing and reports * `deletedRows: 0` rather than removing a different scope. * * @throws {Error} on an empty scope id or a malformed response * @throws {GraphQLError} when MM rejects the mutation */ removeDispatchPriorityScope(scopeResourceId: string): Promise; /** * The distinct scopes that currently hold dispatch-priority rows (token-scoped), ordered * by scope and returned **exactly as MM holds them**. Reads one column with `distinct_on`, * so it does not pull every row of every scope. Every entry can be passed straight to * {@link removeDispatchPriorityScope}, which addresses text verbatim too. * * For the roster comparison an authority connector runs each cycle: scopes MM holds but * the authority no longer issues are the ones to clear with * {@link removeDispatchPriorityScope}. * * @param options.pageSize Scopes per GraphQL page (default 500) * @throws {Error} on a malformed page — a short read here would look like "the authority * stopped issuing these scopes" and clear rows that should have stood */ fetchDispatchPriorityScopes(options?: { pageSize?: number; }): Promise; /** * Returns ERP labor resources mapped to MM machines via **MES** `/mes/settings/machine-settings`, * not GraphQL `erpResources`. Rows include machines with a finite `machineRef` and non-empty * `laborResourceId`. Optional `name`, `type`, `machineGroupId`, and `isResourceGroup` pass through when the API sends them. * * The bearer token (`mmApiAuthToken`) determines company scope; `companyId` must still be passed and * non-empty (trimmed) for backwards compatibility but is **not** sent as a query parameter. * * @param companyId - Expected MM company identifier; trimmed; empty throws. * @returns Mapped rows for machines with ERP labor mapping in MES settings. * @throws {Error} When `companyId` is missing after trim, `mmApiBaseUrl` / `mmApiAuthToken` are missing, * or the REST response is not a JSON array. */ fetchMappedErpResourcesForCompany(companyId: string): Promise; /** * Deduped ERP resource ids from **all** MES machine rows: each row’s `laborResourceId` (when set) * plus every id in `scheduledAs`, for connectors that need “anything that might run” scheduling / WO op * filtering. No `laborTicketEnabled` filter. * * The bearer token scopes company; `companyId` must be non-empty (trimmed) for API compatibility only. * * @throws {Error} When `companyId` is missing after trim or MES fetch fails. */ fetchMesSchedulableErpResourceIds(companyId: string): Promise; /** * GraphQL `aggregatedPartCounts` for one `machineRef`: **one HTTP request**; the API may return **multiple bucket rows** * (time buckets) for that machine. * * @param options.machineRef - Numeric MM machine reference (`machineRef` in GraphQL). * @param options.windowStartAt - Window start as an ISO-8601 string (e.g. `2025-01-01T00:00:00.000Z`). Must match MM GraphQL expectations. * @param options.windowEndAt - Window end, same format as `windowStartAt`. * @param options.metricKey - Metric to aggregate; defaults to `"part-count"` (`MM_DEFAULT_PART_COUNT_METRIC_KEY`). * @param options.bucketWidth - Optional. GraphQL duration string for bucket size (API-specific; e.g. hour/day windowing). * @param options.bucketResolution - Optional. Numeric resolution when the API supports sub-bucket refinement. * @param options.windowWidth - Optional. Alternative window sizing hint when supported by the schema. * @returns One row per bucket from `data.aggregatedPartCounts`. * @throws {Error} When the response lacks `data.aggregatedPartCounts`, a row fails parsing, or GraphQL returns errors. * @throws {RangeError} When `machineRef` or `bucketResolution` is not finite (via query builder). * * For multiple machines, call once per `machineRef` (for example `Promise.all`) so filtering and error policy stay explicit at the call site. */ fetchAggregatedPartCountsForMachine(options: { machineRef: number; windowStartAt: string; windowEndAt: string; metricKey?: string; bucketWidth?: string; bucketResolution?: number; windowWidth?: string; }): Promise; /** * Resolves the REST **`machine_id`** for **part adjustment** and other MM REST calls that take * `machine_id`, using GraphQL `machines` where `machineRef` matches. Docs often describe this as a UUID; * this method does not validate a particular string format—only that the GraphQL field is a non-empty string. * * @param machineRef - Numeric MM `machineRef`. Non-finite values throw. * @returns Trimmed `machine_id` string, or `null` if no row, unmapped machine, or when `machineId` is * missing, empty after trim, or not a string (avoids coercing arbitrary JSON values to strings). * @throws {RangeError} When `machineRef` is not finite. * @throws {Error} When GraphQL returns errors. */ fetchMachineIdForMachineRef(machineRef: number): Promise; /** * Cleanup all HTTP connections and resources * Call this when the service is no longer needed */ destroy(): Promise; } //# sourceMappingURL=mm-api-service.d.ts.map