@machinemetrics/mm-erp-sdk 0.9.3 → 0.9.4
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.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/data-sync-service/configuration-manager.d.ts.map +1 -1
- package/dist/services/data-sync-service/configuration-manager.js +14 -7
- package/dist/services/data-sync-service/configuration-manager.js.map +1 -1
- package/dist/services/data-sync-service/nats-labor-ticket-listener.d.ts.map +1 -1
- package/dist/services/data-sync-service/nats-labor-ticket-listener.js +20 -1
- package/dist/services/data-sync-service/nats-labor-ticket-listener.js.map +1 -1
- package/dist/services/erp-api-services/errors.d.ts.map +1 -1
- package/dist/services/erp-api-services/errors.js +3 -0
- package/dist/services/erp-api-services/errors.js.map +1 -1
- package/dist/services/mm-api-service/graphql-string-literal.d.ts +10 -0
- package/dist/services/mm-api-service/graphql-string-literal.d.ts.map +1 -0
- package/dist/services/mm-api-service/graphql-string-literal.js +12 -0
- package/dist/services/mm-api-service/graphql-string-literal.js.map +1 -0
- package/dist/services/mm-api-service/index.d.ts +2 -2
- package/dist/services/mm-api-service/index.d.ts.map +1 -1
- package/dist/services/mm-api-service/index.js.map +1 -1
- package/dist/services/mm-api-service/mm-aggregated-part-counts.d.ts +33 -0
- package/dist/services/mm-api-service/mm-aggregated-part-counts.d.ts.map +1 -0
- package/dist/services/mm-api-service/mm-aggregated-part-counts.js +95 -0
- package/dist/services/mm-api-service/mm-aggregated-part-counts.js.map +1 -0
- package/dist/services/mm-api-service/mm-api-service.d.ts +49 -1
- package/dist/services/mm-api-service/mm-api-service.d.ts.map +1 -1
- package/dist/services/mm-api-service/mm-api-service.js +127 -10
- package/dist/services/mm-api-service/mm-api-service.js.map +1 -1
- package/dist/services/mm-api-service/mm-graphql-proxy.d.ts +13 -0
- package/dist/services/mm-api-service/mm-graphql-proxy.d.ts.map +1 -0
- package/dist/services/mm-api-service/mm-graphql-proxy.js +25 -0
- package/dist/services/mm-api-service/mm-graphql-proxy.js.map +1 -0
- package/dist/services/mm-api-service/token-mgr.d.ts.map +1 -1
- package/dist/services/mm-api-service/token-mgr.js +0 -5
- package/dist/services/mm-api-service/token-mgr.js.map +1 -1
- package/dist/services/mm-api-service/types/mm-response-interfaces.d.ts +33 -0
- package/dist/services/mm-api-service/types/mm-response-interfaces.d.ts.map +1 -1
- package/dist/services/mm-api-service/types/mm-response-interfaces.js.map +1 -1
- package/dist/utils/resource-group.d.ts.map +1 -1
- package/dist/utils/resource-group.js +13 -1
- package/dist/utils/resource-group.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/services/data-sync-service/configuration-manager.ts +17 -7
- package/src/services/data-sync-service/nats-labor-ticket-listener.ts +23 -2
- package/src/services/erp-api-services/errors.ts +4 -0
- package/src/services/mm-api-service/graphql-string-literal.ts +11 -0
- package/src/services/mm-api-service/index.ts +4 -0
- package/src/services/mm-api-service/mm-aggregated-part-counts.ts +127 -0
- package/src/services/mm-api-service/mm-api-service.ts +172 -10
- package/src/services/mm-api-service/mm-graphql-proxy.ts +33 -0
- package/src/services/mm-api-service/token-mgr.ts +0 -5
- package/src/services/mm-api-service/types/mm-response-interfaces.ts +35 -0
- package/src/utils/resource-group.ts +15 -2
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL `aggregatedPartCounts` argument builder (inline `args: { ... }` fragment).
|
|
3
|
+
* `machineRef`, `metricKey`, `windowStartAt`, and `windowEndAt` are required in `args`.
|
|
4
|
+
* Optional tuning: `bucketWidth`, `bucketResolution`, and `windowWidth` are passed through when set (see MM GraphQL schema).
|
|
5
|
+
*
|
|
6
|
+
* @see https://developers.machinemetrics.com/docs/graphql/api
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { MMAggregatedPartCountBucket } from "./types/mm-response-interfaces.js";
|
|
10
|
+
import { escapeGraphqlStringLiteral } from "./graphql-string-literal.js";
|
|
11
|
+
|
|
12
|
+
export const MM_DEFAULT_PART_COUNT_METRIC_KEY = "part-count" as const;
|
|
13
|
+
|
|
14
|
+
export function buildAggregatedPartCountsArgsFragment(options: {
|
|
15
|
+
machineRef: number;
|
|
16
|
+
metricKey: string;
|
|
17
|
+
windowStartAt: string;
|
|
18
|
+
windowEndAt: string;
|
|
19
|
+
bucketWidth?: string;
|
|
20
|
+
bucketResolution?: number;
|
|
21
|
+
windowWidth?: string;
|
|
22
|
+
}): string {
|
|
23
|
+
const refInt = Math.trunc(Number(options.machineRef));
|
|
24
|
+
if (!Number.isFinite(refInt)) {
|
|
25
|
+
throw new RangeError("machineRef must be a finite number");
|
|
26
|
+
}
|
|
27
|
+
const parts: string[] = [
|
|
28
|
+
`machineRef: ${refInt}`,
|
|
29
|
+
`metricKey: "${escapeGraphqlStringLiteral(options.metricKey)}"`,
|
|
30
|
+
`windowStartAt: "${escapeGraphqlStringLiteral(options.windowStartAt)}"`,
|
|
31
|
+
`windowEndAt: "${escapeGraphqlStringLiteral(options.windowEndAt)}"`,
|
|
32
|
+
];
|
|
33
|
+
if (options.bucketWidth !== undefined) {
|
|
34
|
+
parts.push(`bucketWidth: "${escapeGraphqlStringLiteral(options.bucketWidth)}"`);
|
|
35
|
+
}
|
|
36
|
+
if (options.bucketResolution !== undefined) {
|
|
37
|
+
const bucketResolution = Number(options.bucketResolution);
|
|
38
|
+
if (!Number.isFinite(bucketResolution)) {
|
|
39
|
+
throw new RangeError("bucketResolution must be a finite number");
|
|
40
|
+
}
|
|
41
|
+
parts.push(`bucketResolution: ${bucketResolution}`);
|
|
42
|
+
}
|
|
43
|
+
if (options.windowWidth !== undefined) {
|
|
44
|
+
parts.push(`windowWidth: "${escapeGraphqlStringLiteral(options.windowWidth)}"`);
|
|
45
|
+
}
|
|
46
|
+
return parts.join(", ");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildAggregatedPartCountsQuery(options: {
|
|
50
|
+
machineRef: number;
|
|
51
|
+
metricKey: string;
|
|
52
|
+
windowStartAt: string;
|
|
53
|
+
windowEndAt: string;
|
|
54
|
+
bucketWidth?: string;
|
|
55
|
+
bucketResolution?: number;
|
|
56
|
+
windowWidth?: string;
|
|
57
|
+
}): string {
|
|
58
|
+
const args = buildAggregatedPartCountsArgsFragment(options);
|
|
59
|
+
return `query aggregatedPartCountsForMachine { aggregatedPartCounts(args: { ${args} }) { machineRef metricKey sumOfValues eventCount bucketStartAt bucketEndAt bucketWidth firstValue lastValue } }`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parses one `aggregatedPartCounts` bucket from JSON.
|
|
64
|
+
* Per MM GraphQL, each returned row includes non-null strings for `metricKey`, `bucketStartAt`, `bucketEndAt`, and `bucketWidth` (width may be empty); absence, null, or wrong types throw.
|
|
65
|
+
*/
|
|
66
|
+
export function parseAggregatedPartCountBucketRow(
|
|
67
|
+
raw: unknown
|
|
68
|
+
): MMAggregatedPartCountBucket {
|
|
69
|
+
if (typeof raw !== "object" || raw === null) {
|
|
70
|
+
throw new Error("aggregatedPartCounts: invalid row");
|
|
71
|
+
}
|
|
72
|
+
const o = raw as Record<string, unknown>;
|
|
73
|
+
const machineRef =
|
|
74
|
+
typeof o.machineRef === "number" ? o.machineRef : Number(o.machineRef);
|
|
75
|
+
const sumOfValues =
|
|
76
|
+
typeof o.sumOfValues === "number" ? o.sumOfValues : Number(o.sumOfValues);
|
|
77
|
+
const eventCount =
|
|
78
|
+
typeof o.eventCount === "number" ? o.eventCount : Number(o.eventCount);
|
|
79
|
+
if (typeof o.metricKey !== "string" || o.metricKey.length === 0) {
|
|
80
|
+
throw new Error("aggregatedPartCounts: invalid or missing metricKey");
|
|
81
|
+
}
|
|
82
|
+
if (typeof o.bucketStartAt !== "string" || o.bucketStartAt.length === 0) {
|
|
83
|
+
throw new Error("aggregatedPartCounts: invalid or missing bucketStartAt");
|
|
84
|
+
}
|
|
85
|
+
if (typeof o.bucketEndAt !== "string" || o.bucketEndAt.length === 0) {
|
|
86
|
+
throw new Error("aggregatedPartCounts: invalid or missing bucketEndAt");
|
|
87
|
+
}
|
|
88
|
+
if (typeof o.bucketWidth !== "string") {
|
|
89
|
+
throw new Error("aggregatedPartCounts: invalid or missing bucketWidth");
|
|
90
|
+
}
|
|
91
|
+
const metricKey = o.metricKey;
|
|
92
|
+
const bucketStartAt = o.bucketStartAt;
|
|
93
|
+
const bucketEndAt = o.bucketEndAt;
|
|
94
|
+
const bucketWidth = o.bucketWidth;
|
|
95
|
+
if (
|
|
96
|
+
!Number.isFinite(machineRef) ||
|
|
97
|
+
!Number.isFinite(sumOfValues) ||
|
|
98
|
+
!Number.isFinite(eventCount)
|
|
99
|
+
) {
|
|
100
|
+
throw new Error("aggregatedPartCounts: missing numeric fields");
|
|
101
|
+
}
|
|
102
|
+
const firstValue =
|
|
103
|
+
o.firstValue === null || o.firstValue === undefined
|
|
104
|
+
? null
|
|
105
|
+
: typeof o.firstValue === "number"
|
|
106
|
+
? o.firstValue
|
|
107
|
+
: Number(o.firstValue);
|
|
108
|
+
const lastValue =
|
|
109
|
+
o.lastValue === null || o.lastValue === undefined
|
|
110
|
+
? null
|
|
111
|
+
: typeof o.lastValue === "number"
|
|
112
|
+
? o.lastValue
|
|
113
|
+
: Number(o.lastValue);
|
|
114
|
+
return {
|
|
115
|
+
machineRef,
|
|
116
|
+
metricKey,
|
|
117
|
+
sumOfValues,
|
|
118
|
+
eventCount,
|
|
119
|
+
bucketStartAt,
|
|
120
|
+
bucketEndAt,
|
|
121
|
+
bucketWidth,
|
|
122
|
+
firstValue:
|
|
123
|
+
firstValue === null || Number.isFinite(firstValue) ? firstValue : null,
|
|
124
|
+
lastValue:
|
|
125
|
+
lastValue === null || Number.isFinite(lastValue) ? lastValue : null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -13,7 +13,19 @@ import { Checkpoint } from "./types/checkpoint.js";
|
|
|
13
13
|
import { CoreConfiguration } from "../data-sync-service/configuration-manager.js";
|
|
14
14
|
import { ErrorHandler } from "../erp-api-services/errors.js";
|
|
15
15
|
import {
|
|
16
|
+
assertMmGraphqlProxyResponseHasNoErrors,
|
|
17
|
+
buildMmGraphqlProxyRequestBody,
|
|
18
|
+
} from "./mm-graphql-proxy.js";
|
|
19
|
+
import {
|
|
20
|
+
buildAggregatedPartCountsQuery,
|
|
21
|
+
MM_DEFAULT_PART_COUNT_METRIC_KEY,
|
|
22
|
+
parseAggregatedPartCountBucketRow,
|
|
23
|
+
} from "./mm-aggregated-part-counts.js";
|
|
24
|
+
import { escapeGraphqlStringLiteral } from "./graphql-string-literal.js";
|
|
25
|
+
import {
|
|
26
|
+
MMAggregatedPartCountBucket,
|
|
16
27
|
MMApiBaseResponse,
|
|
28
|
+
MMMappedErpResourceRow,
|
|
17
29
|
MMGraphQLReasonResponse,
|
|
18
30
|
MMGraphQLResourceResponse,
|
|
19
31
|
MMGraphQLWorkOrderResponse,
|
|
@@ -344,11 +356,12 @@ export class MMApiClient {
|
|
|
344
356
|
variables?: Record<string, unknown>
|
|
345
357
|
): Promise<MMApiBaseResponse> {
|
|
346
358
|
try {
|
|
347
|
-
const body
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
359
|
+
const body = buildMmGraphqlProxyRequestBody(
|
|
360
|
+
operationName,
|
|
361
|
+
query,
|
|
362
|
+
variables
|
|
363
|
+
);
|
|
364
|
+
const result = (await this.postData(
|
|
352
365
|
this.resourceURLs[GRAPHQL],
|
|
353
366
|
body,
|
|
354
367
|
{},
|
|
@@ -357,7 +370,9 @@ export class MMApiClient {
|
|
|
357
370
|
token: CoreConfiguration.inst().mmApiAuthToken,
|
|
358
371
|
headers: { "Content-Type": "application/json" },
|
|
359
372
|
}
|
|
360
|
-
);
|
|
373
|
+
)) as Record<string, unknown> & MMApiBaseResponse;
|
|
374
|
+
assertMmGraphqlProxyResponseHasNoErrors(result);
|
|
375
|
+
return result;
|
|
361
376
|
} catch (error) {
|
|
362
377
|
ErrorHandler.handle(error);
|
|
363
378
|
}
|
|
@@ -838,10 +853,7 @@ export class MMApiClient {
|
|
|
838
853
|
async fetchWorkOrderFromGraphQL(
|
|
839
854
|
workOrderId: string
|
|
840
855
|
): Promise<MMGraphQLWorkOrderResponse> {
|
|
841
|
-
|
|
842
|
-
const escapedWorkOrderId = workOrderId
|
|
843
|
-
.replace(/\\/g, "\\\\")
|
|
844
|
-
.replace(/"/g, '\\"');
|
|
856
|
+
const escapedWorkOrderId = escapeGraphqlStringLiteral(workOrderId);
|
|
845
857
|
|
|
846
858
|
const query =
|
|
847
859
|
`query getErpWorkOrder { ` +
|
|
@@ -858,6 +870,127 @@ export class MMApiClient {
|
|
|
858
870
|
)) as unknown as MMGraphQLWorkOrderResponse;
|
|
859
871
|
}
|
|
860
872
|
|
|
873
|
+
/**
|
|
874
|
+
* Returns ERP resources for a company that are **mapped to MachineMetrics** (`machineRef` is set).
|
|
875
|
+
* GraphQL: `erpResources` filtered by `companyId` and non-null `machineRef`. Extends
|
|
876
|
+
* {@link fetchResourcesFromGraphQL} with company scope and richer fields (`isResourceGroup`, `machineGroupId`, etc.).
|
|
877
|
+
*
|
|
878
|
+
* @param companyId - MachineMetrics company identifier (string). Whitespace is trimmed; empty after trim throws.
|
|
879
|
+
* @returns Parsed rows; order matches the API response.
|
|
880
|
+
* @throws {Error} When `companyId` is missing, the response shape is invalid, or the GraphQL proxy returns top-level `errors`.
|
|
881
|
+
*/
|
|
882
|
+
async fetchMappedErpResourcesForCompany(
|
|
883
|
+
companyId: string
|
|
884
|
+
): Promise<MMMappedErpResourceRow[]> {
|
|
885
|
+
const id = companyId.trim();
|
|
886
|
+
if (!id) {
|
|
887
|
+
throw new Error("fetchMappedErpResourcesForCompany: companyId is required");
|
|
888
|
+
}
|
|
889
|
+
const safe = escapeGraphqlStringLiteral(id);
|
|
890
|
+
const query = `query mappedErpResourcesForCompany { erpResources(where: { companyId: { _eq: "${safe}" }, machineRef: { _is_null: false } }) { isResourceGroup machineGroupId machineRef resourceId name type } }`;
|
|
891
|
+
const response = (await this.graphqlRequest(
|
|
892
|
+
"mappedErpResourcesForCompany",
|
|
893
|
+
query
|
|
894
|
+
)) as { data?: { erpResources?: unknown } };
|
|
895
|
+
const list = response.data?.erpResources;
|
|
896
|
+
if (!Array.isArray(list)) {
|
|
897
|
+
throw new Error(
|
|
898
|
+
"fetchMappedErpResourcesForCompany: missing data.erpResources array"
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
return list.map(parseMappedErpResourceRow);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* GraphQL `aggregatedPartCounts` for one `machineRef`: **one HTTP request**; the API may return **multiple bucket rows**
|
|
906
|
+
* (time buckets) for that machine.
|
|
907
|
+
*
|
|
908
|
+
* @param options.machineRef - Numeric MM machine reference (`machineRef` in GraphQL).
|
|
909
|
+
* @param options.windowStartAt - Window start as an ISO-8601 string (e.g. `2025-01-01T00:00:00.000Z`). Must match MM GraphQL expectations.
|
|
910
|
+
* @param options.windowEndAt - Window end, same format as `windowStartAt`.
|
|
911
|
+
* @param options.metricKey - Metric to aggregate; defaults to `"part-count"` (`MM_DEFAULT_PART_COUNT_METRIC_KEY`).
|
|
912
|
+
* @param options.bucketWidth - Optional. GraphQL duration string for bucket size (API-specific; e.g. hour/day windowing).
|
|
913
|
+
* @param options.bucketResolution - Optional. Numeric resolution when the API supports sub-bucket refinement.
|
|
914
|
+
* @param options.windowWidth - Optional. Alternative window sizing hint when supported by the schema.
|
|
915
|
+
* @returns One row per bucket from `data.aggregatedPartCounts`.
|
|
916
|
+
* @throws {Error} When the response lacks `data.aggregatedPartCounts`, a row fails parsing, or GraphQL returns errors.
|
|
917
|
+
* @throws {RangeError} When `machineRef` or `bucketResolution` is not finite (via query builder).
|
|
918
|
+
*
|
|
919
|
+
* For multiple machines, call once per `machineRef` (for example `Promise.all`) so filtering and error policy stay explicit at the call site.
|
|
920
|
+
*/
|
|
921
|
+
async fetchAggregatedPartCountsForMachine(options: {
|
|
922
|
+
machineRef: number;
|
|
923
|
+
windowStartAt: string;
|
|
924
|
+
windowEndAt: string;
|
|
925
|
+
metricKey?: string;
|
|
926
|
+
bucketWidth?: string;
|
|
927
|
+
bucketResolution?: number;
|
|
928
|
+
windowWidth?: string;
|
|
929
|
+
}): Promise<MMAggregatedPartCountBucket[]> {
|
|
930
|
+
const metricKey =
|
|
931
|
+
options.metricKey?.trim() || MM_DEFAULT_PART_COUNT_METRIC_KEY;
|
|
932
|
+
const query = buildAggregatedPartCountsQuery({
|
|
933
|
+
machineRef: options.machineRef,
|
|
934
|
+
metricKey,
|
|
935
|
+
windowStartAt: options.windowStartAt,
|
|
936
|
+
windowEndAt: options.windowEndAt,
|
|
937
|
+
bucketWidth: options.bucketWidth,
|
|
938
|
+
bucketResolution: options.bucketResolution,
|
|
939
|
+
windowWidth: options.windowWidth,
|
|
940
|
+
});
|
|
941
|
+
const response = (await this.graphqlRequest(
|
|
942
|
+
"aggregatedPartCountsForMachine",
|
|
943
|
+
query
|
|
944
|
+
)) as { data?: { aggregatedPartCounts?: unknown } };
|
|
945
|
+
const list = response.data?.aggregatedPartCounts;
|
|
946
|
+
if (!Array.isArray(list)) {
|
|
947
|
+
throw new Error(
|
|
948
|
+
"fetchAggregatedPartCountsForMachine: missing data.aggregatedPartCounts array"
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
return list.map(parseAggregatedPartCountBucketRow);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Resolves the REST **`machine_id`** for **part adjustment** and other MM REST calls that take
|
|
956
|
+
* `machine_id`, using GraphQL `machines` where `machineRef` matches. Docs often describe this as a UUID;
|
|
957
|
+
* this method does not validate a particular string format—only that the GraphQL field is a non-empty string.
|
|
958
|
+
*
|
|
959
|
+
* @param machineRef - Numeric MM `machineRef`. Non-finite values throw.
|
|
960
|
+
* @returns Trimmed `machine_id` string, or `null` if no row, unmapped machine, or when `machineId` is
|
|
961
|
+
* missing, empty after trim, or not a string (avoids coercing arbitrary JSON values to strings).
|
|
962
|
+
* @throws {RangeError} When `machineRef` is not finite.
|
|
963
|
+
* @throws {Error} When GraphQL returns errors.
|
|
964
|
+
*/
|
|
965
|
+
async fetchMachineIdForMachineRef(
|
|
966
|
+
machineRef: number
|
|
967
|
+
): Promise<string | null> {
|
|
968
|
+
const ref = Math.trunc(Number(machineRef));
|
|
969
|
+
if (!Number.isFinite(ref)) {
|
|
970
|
+
throw new RangeError("fetchMachineIdForMachineRef: machineRef must be finite");
|
|
971
|
+
}
|
|
972
|
+
const query = `query mmMachineIdByRef { machines(where: { machineRef: { _eq: ${ref} } }, limit: 1) { machineId } }`;
|
|
973
|
+
const response = (await this.graphqlRequest(
|
|
974
|
+
"mmMachineIdByRef",
|
|
975
|
+
query
|
|
976
|
+
)) as { data?: { machines?: unknown } };
|
|
977
|
+
const list = response.data?.machines;
|
|
978
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
const row = list[0];
|
|
982
|
+
if (typeof row !== "object" || row === null) {
|
|
983
|
+
return null;
|
|
984
|
+
}
|
|
985
|
+
const o = row as Record<string, unknown>;
|
|
986
|
+
const mid = o.machineId;
|
|
987
|
+
if (typeof mid !== "string") {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
const s = mid.trim();
|
|
991
|
+
return s || null;
|
|
992
|
+
}
|
|
993
|
+
|
|
861
994
|
/**
|
|
862
995
|
* Cleanup all HTTP connections and resources
|
|
863
996
|
* Call this when the service is no longer needed
|
|
@@ -869,3 +1002,32 @@ export class MMApiClient {
|
|
|
869
1002
|
|
|
870
1003
|
//#endregion public methods
|
|
871
1004
|
}
|
|
1005
|
+
|
|
1006
|
+
function parseMappedErpResourceRow(raw: unknown): MMMappedErpResourceRow {
|
|
1007
|
+
if (typeof raw !== "object" || raw === null) {
|
|
1008
|
+
throw new Error("fetchMappedErpResourcesForCompany: invalid row");
|
|
1009
|
+
}
|
|
1010
|
+
const o = raw as Record<string, unknown>;
|
|
1011
|
+
const refRaw = o.machineRef;
|
|
1012
|
+
const ref = typeof refRaw === "number" ? refRaw : Number(refRaw);
|
|
1013
|
+
if (!Number.isFinite(ref)) {
|
|
1014
|
+
throw new Error("fetchMappedErpResourcesForCompany: invalid machineRef");
|
|
1015
|
+
}
|
|
1016
|
+
const rid = o.resourceId;
|
|
1017
|
+
return {
|
|
1018
|
+
isResourceGroup: Boolean(o.isResourceGroup),
|
|
1019
|
+
machineGroupId:
|
|
1020
|
+
o.machineGroupId === null || o.machineGroupId === undefined
|
|
1021
|
+
? null
|
|
1022
|
+
: String(o.machineGroupId),
|
|
1023
|
+
machineRef: Math.trunc(ref),
|
|
1024
|
+
resourceId:
|
|
1025
|
+
rid === null || rid === undefined
|
|
1026
|
+
? null
|
|
1027
|
+
: typeof rid === "string"
|
|
1028
|
+
? rid.trim() || null
|
|
1029
|
+
: String(rid).trim() || null,
|
|
1030
|
+
name: typeof o.name === "string" ? o.name : null,
|
|
1031
|
+
type: o.type === null || o.type === undefined ? null : String(o.type),
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { GraphQLError } from "../erp-api-services/errors.js";
|
|
2
|
+
import type { GraphQLServerError } from "../erp-api-services/graphql/types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* JSON body for POST to MM API `/proxy/graphql` (MachineMetrics GraphQL proxy).
|
|
6
|
+
* Used only by `MMApiClient` (not exported from the public SDK entrypoint).
|
|
7
|
+
*/
|
|
8
|
+
export function buildMmGraphqlProxyRequestBody(
|
|
9
|
+
operationName: string,
|
|
10
|
+
query: string,
|
|
11
|
+
variables?: Record<string, unknown>
|
|
12
|
+
): Record<string, unknown> {
|
|
13
|
+
const body: Record<string, unknown> = { operationName, query };
|
|
14
|
+
if (variables !== undefined && Object.keys(variables).length > 0) {
|
|
15
|
+
body.variables = variables;
|
|
16
|
+
}
|
|
17
|
+
return body;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GraphQL-over-HTTP often returns **HTTP 200** with a top-level `errors` array and partial or null `data`.
|
|
22
|
+
* Axios treats that as success, so callers must inspect the envelope.
|
|
23
|
+
*
|
|
24
|
+
* @throws {GraphQLError} when `errors` is a non-empty array
|
|
25
|
+
*/
|
|
26
|
+
export function assertMmGraphqlProxyResponseHasNoErrors(
|
|
27
|
+
response: Record<string, unknown>
|
|
28
|
+
): void {
|
|
29
|
+
const errors = response.errors;
|
|
30
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
31
|
+
throw new GraphQLError("GraphQL Error", errors as GraphQLServerError[]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -24,12 +24,7 @@ export class MMTokenManager {
|
|
|
24
24
|
? new Date(cached.expiration)
|
|
25
25
|
: null;
|
|
26
26
|
const config = CoreConfiguration.inst();
|
|
27
|
-
console.log("=== MMTOKEN MANAGER DEBUG ===");
|
|
28
|
-
console.log("config.mmApiBaseUrl:", config.mmApiBaseUrl);
|
|
29
|
-
console.log("config object keys:", Object.keys(config));
|
|
30
27
|
this.baseUrl = config.mmApiBaseUrl;
|
|
31
|
-
console.log("this.baseUrl set to:", this.baseUrl);
|
|
32
|
-
console.log("=== END MMTOKEN MANAGER DEBUG ===");
|
|
33
28
|
this.api = HTTPClientFactory.getInstance({
|
|
34
29
|
baseUrl: this.baseUrl,
|
|
35
30
|
retryAttempts: config.mmApiRetryAttempts,
|
|
@@ -331,6 +331,41 @@ export interface MMGraphQLWorkOrderResponse extends MMApiBaseResponse {
|
|
|
331
331
|
};
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
/**
|
|
335
|
+
* ERP resources for a company with a mapped MM machine (`machineRef` present).
|
|
336
|
+
*/
|
|
337
|
+
export interface MMMappedErpResourceRow {
|
|
338
|
+
isResourceGroup: boolean;
|
|
339
|
+
machineGroupId: string | null;
|
|
340
|
+
machineRef: number;
|
|
341
|
+
/** ERP resource id when exposed by GraphQL (connector-specific meaning). */
|
|
342
|
+
resourceId: string | null;
|
|
343
|
+
/** Display name from GraphQL; null when absent or not a string (callers may default for UI). */
|
|
344
|
+
name: string | null;
|
|
345
|
+
type: string | null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* One time bucket row from GraphQL `aggregatedPartCounts` for a single `machineRef` and `metricKey`.
|
|
350
|
+
* Window and bucket shape follow the MM GraphQL schema (see MachineMetrics GraphQL docs).
|
|
351
|
+
*/
|
|
352
|
+
export interface MMAggregatedPartCountBucket {
|
|
353
|
+
machineRef: number;
|
|
354
|
+
metricKey: string;
|
|
355
|
+
/** Sum of metric values across events in this bucket. */
|
|
356
|
+
sumOfValues: number;
|
|
357
|
+
/** Number of contributing events in this bucket. */
|
|
358
|
+
eventCount: number;
|
|
359
|
+
bucketStartAt: string;
|
|
360
|
+
bucketEndAt: string;
|
|
361
|
+
/** Duration string for this bucket when the API provides it (may be empty). */
|
|
362
|
+
bucketWidth: string;
|
|
363
|
+
/** Optional gauge at bucket start; null when not applicable. */
|
|
364
|
+
firstValue: number | null;
|
|
365
|
+
/** Optional gauge at bucket end; null when not applicable. */
|
|
366
|
+
lastValue: number | null;
|
|
367
|
+
}
|
|
368
|
+
|
|
334
369
|
/**
|
|
335
370
|
* Response for machine groups fetch
|
|
336
371
|
* Used by fetchMachineGroups() method
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sdkLogger as logger } from "../services/reporting-service/logger.js";
|
|
2
|
+
import { GraphQLError } from "../services/erp-api-services/errors.js";
|
|
2
3
|
import {
|
|
3
4
|
MMApiClient,
|
|
4
5
|
MMGraphQLResourceResponse,
|
|
@@ -22,8 +23,20 @@ export async function getResourceERPGroupId(
|
|
|
22
23
|
): Promise<string | undefined> {
|
|
23
24
|
try {
|
|
24
25
|
// Get the ERP resources to map resource IDs to machine refs
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
let erpResources: MMGraphQLResourceResponse;
|
|
27
|
+
try {
|
|
28
|
+
erpResources =
|
|
29
|
+
(await client.fetchResourcesFromGraphQL()) as MMGraphQLResourceResponse;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error instanceof GraphQLError) {
|
|
32
|
+
logger.warn(
|
|
33
|
+
"ERP resources GraphQL request failed; cannot resolve resource group",
|
|
34
|
+
{ resourceId, graphqlErrors: error.errors }
|
|
35
|
+
);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
27
40
|
if (!erpResources?.data?.machineResource) {
|
|
28
41
|
logger.error("Failed to fetch ERP resources");
|
|
29
42
|
return undefined;
|