@machinemetrics/mm-erp-sdk 0.3.0-beta.0 → 0.3.0-beta.2
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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +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 +30 -30
- package/dist/services/data-sync-service/configuration-manager.js.map +1 -1
- package/dist/services/data-sync-service/data-sync-service.d.ts.map +1 -1
- package/dist/services/data-sync-service/data-sync-service.js +9 -0
- package/dist/services/data-sync-service/data-sync-service.js.map +1 -1
- package/dist/services/data-sync-service/jobs/clean-up-expired-cache.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/clean-up-expired-cache.js +1 -2
- package/dist/services/data-sync-service/jobs/clean-up-expired-cache.js.map +1 -1
- package/dist/services/data-sync-service/jobs/from-erp.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/from-erp.js +7 -13
- package/dist/services/data-sync-service/jobs/from-erp.js.map +1 -1
- package/dist/services/data-sync-service/jobs/retry-failed-labor-tickets.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/retry-failed-labor-tickets.js +1 -2
- package/dist/services/data-sync-service/jobs/retry-failed-labor-tickets.js.map +1 -1
- package/dist/services/data-sync-service/jobs/run-migrations.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/run-migrations.js +1 -2
- package/dist/services/data-sync-service/jobs/run-migrations.js.map +1 -1
- package/dist/services/data-sync-service/jobs/to-erp.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/to-erp.js +12 -3
- package/dist/services/data-sync-service/jobs/to-erp.js.map +1 -1
- package/dist/services/data-sync-service/nats-labor-ticket-listener.d.ts +30 -0
- package/dist/services/data-sync-service/nats-labor-ticket-listener.d.ts.map +1 -0
- package/dist/services/data-sync-service/nats-labor-ticket-listener.js +290 -0
- package/dist/services/data-sync-service/nats-labor-ticket-listener.js.map +1 -0
- package/dist/services/mm-api-service/company-info.d.ts +13 -0
- package/dist/services/mm-api-service/company-info.d.ts.map +1 -0
- package/dist/services/mm-api-service/company-info.js +60 -0
- package/dist/services/mm-api-service/company-info.js.map +1 -0
- package/dist/services/mm-api-service/index.d.ts +7 -0
- package/dist/services/mm-api-service/index.d.ts.map +1 -1
- package/dist/services/mm-api-service/index.js +5 -0
- package/dist/services/mm-api-service/index.js.map +1 -1
- package/dist/services/mm-api-service/mm-api-service.d.ts +6 -0
- package/dist/services/mm-api-service/mm-api-service.d.ts.map +1 -1
- package/dist/services/mm-api-service/mm-api-service.js +15 -3
- package/dist/services/mm-api-service/mm-api-service.js.map +1 -1
- package/dist/services/mm-api-service/types/receive-types.d.ts +3 -0
- package/dist/services/mm-api-service/types/receive-types.d.ts.map +1 -1
- package/dist/services/mm-api-service/types/receive-types.js +1 -0
- package/dist/services/mm-api-service/types/receive-types.js.map +1 -1
- package/dist/services/mm-api-service/types/send-types.d.ts +17 -8
- package/dist/services/mm-api-service/types/send-types.d.ts.map +1 -1
- package/dist/services/mm-api-service/types/send-types.js +41 -17
- package/dist/services/mm-api-service/types/send-types.js.map +1 -1
- package/dist/services/nats-service/nats-service.d.ts +114 -0
- package/dist/services/nats-service/nats-service.d.ts.map +1 -0
- package/dist/services/nats-service/nats-service.js +244 -0
- package/dist/services/nats-service/nats-service.js.map +1 -0
- package/dist/services/nats-service/test-nats-subscriber.d.ts +6 -0
- package/dist/services/nats-service/test-nats-subscriber.d.ts.map +1 -0
- package/dist/services/nats-service/test-nats-subscriber.js +79 -0
- package/dist/services/nats-service/test-nats-subscriber.js.map +1 -0
- package/dist/services/reporting-service/logger.d.ts.map +1 -1
- package/dist/services/reporting-service/logger.js +31 -6
- package/dist/services/reporting-service/logger.js.map +1 -1
- package/dist/types/erp-connector.d.ts +1 -8
- package/dist/types/erp-connector.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/error-formatter.d.ts +19 -0
- package/dist/utils/error-formatter.d.ts.map +1 -0
- package/dist/utils/error-formatter.js +184 -0
- package/dist/utils/error-formatter.js.map +1 -0
- package/dist/utils/http-client.js +2 -4
- package/dist/utils/http-client.js.map +1 -1
- package/dist/utils/index.d.ts +5 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/local-data-store/jobs-shared-data.d.ts +0 -2
- package/dist/utils/local-data-store/jobs-shared-data.d.ts.map +1 -1
- package/dist/utils/local-data-store/jobs-shared-data.js +0 -2
- package/dist/utils/local-data-store/jobs-shared-data.js.map +1 -1
- package/dist/utils/mm-labor-ticket-helpers.d.ts +4 -3
- package/dist/utils/mm-labor-ticket-helpers.d.ts.map +1 -1
- package/dist/utils/mm-labor-ticket-helpers.js +7 -12
- package/dist/utils/mm-labor-ticket-helpers.js.map +1 -1
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.d.ts +0 -15
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.d.ts.map +1 -1
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.js +46 -180
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.js.map +1 -1
- package/dist/utils/standard-process-drivers/mm-entity-processor.d.ts +1 -7
- package/dist/utils/standard-process-drivers/mm-entity-processor.d.ts.map +1 -1
- package/dist/utils/standard-process-drivers/mm-entity-processor.js +1 -7
- package/dist/utils/standard-process-drivers/mm-entity-processor.js.map +1 -1
- package/dist/utils/standard-process-drivers/standard-process-drivers.d.ts +2 -8
- package/dist/utils/standard-process-drivers/standard-process-drivers.d.ts.map +1 -1
- package/dist/utils/standard-process-drivers/standard-process-drivers.js +18 -27
- package/dist/utils/standard-process-drivers/standard-process-drivers.js.map +1 -1
- package/dist/utils/time-utils.d.ts.map +1 -1
- package/dist/utils/time-utils.js +0 -7
- package/dist/utils/time-utils.js.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +3 -0
- package/src/services/data-sync-service/configuration-manager.ts +37 -50
- package/src/services/data-sync-service/data-sync-service.ts +10 -0
- package/src/services/data-sync-service/jobs/clean-up-expired-cache.ts +1 -2
- package/src/services/data-sync-service/jobs/from-erp.ts +7 -13
- package/src/services/data-sync-service/jobs/retry-failed-labor-tickets.ts +1 -2
- package/src/services/data-sync-service/jobs/run-migrations.ts +1 -2
- package/src/services/data-sync-service/jobs/to-erp.ts +12 -3
- package/src/services/data-sync-service/nats-labor-ticket-listener.ts +342 -0
- package/src/services/mm-api-service/company-info.ts +87 -0
- package/src/services/mm-api-service/index.ts +8 -0
- package/src/services/mm-api-service/mm-api-service.ts +20 -3
- package/src/services/mm-api-service/types/receive-types.ts +1 -0
- package/src/services/mm-api-service/types/send-types.ts +40 -16
- package/src/services/nats-service/nats-service.ts +351 -0
- package/src/services/nats-service/test-nats-subscriber.ts +96 -0
- package/src/services/reporting-service/logger.ts +39 -7
- package/src/types/erp-connector.ts +1 -8
- package/src/types/index.ts +0 -8
- package/src/utils/error-formatter.ts +205 -0
- package/src/utils/http-client.ts +3 -4
- package/src/utils/index.ts +6 -5
- package/src/utils/local-data-store/jobs-shared-data.ts +0 -2
- package/src/utils/mm-labor-ticket-helpers.ts +8 -11
- package/src/utils/standard-process-drivers/labor-ticket-erp-synchronizer.ts +64 -220
- package/src/utils/standard-process-drivers/mm-entity-processor.ts +1 -7
- package/src/utils/standard-process-drivers/standard-process-drivers.ts +19 -33
- package/src/utils/time-utils.ts +0 -11
- package/dist/types/flattened-work-order.d.ts +0 -99
- package/dist/types/flattened-work-order.d.ts.map +0 -1
- package/dist/types/flattened-work-order.js +0 -2
- package/dist/types/flattened-work-order.js.map +0 -1
- package/dist/utils/env.d.ts +0 -8
- package/dist/utils/env.d.ts.map +0 -1
- package/dist/utils/env.js +0 -58
- package/dist/utils/env.js.map +0 -1
- package/dist/utils/erp-timezone-utils.d.ts +0 -20
- package/dist/utils/erp-timezone-utils.d.ts.map +0 -1
- package/dist/utils/erp-timezone-utils.js +0 -75
- package/dist/utils/erp-timezone-utils.js.map +0 -1
- package/src/types/flattened-work-order.ts +0 -108
- package/src/utils/env.ts +0 -75
- package/src/utils/erp-timezone-utils.ts +0 -99
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createLogger, format, transports } from "winston";
|
|
2
2
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { getEnvString } from "../../utils/env.js"; // Utility function to get environment variables with default values and validation
|
|
5
4
|
|
|
6
5
|
const logDirectory = "logs";
|
|
7
|
-
const initialLogLevel =
|
|
8
|
-
const initialNodeEnv = getEnvString("NODE_ENV", "development");
|
|
6
|
+
const initialLogLevel = process.env.LOG_LEVEL || "info";
|
|
9
7
|
|
|
10
8
|
const LOGGER_ERROR_PREFIX = "[mm-erp-sdk logger]";
|
|
11
9
|
|
|
@@ -52,8 +50,45 @@ const truncateString = (str: string): string => {
|
|
|
52
50
|
return result;
|
|
53
51
|
};
|
|
54
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Safe JSON serializer that handles circular references and extracts meaningful error data
|
|
55
|
+
*/
|
|
56
|
+
const safeStringify = (obj: any, indent: number = 2): string => {
|
|
57
|
+
const seen = new WeakSet();
|
|
58
|
+
|
|
59
|
+
return JSON.stringify(
|
|
60
|
+
obj,
|
|
61
|
+
(key, value) => {
|
|
62
|
+
// Handle Error objects specially
|
|
63
|
+
if (value instanceof Error) {
|
|
64
|
+
return {
|
|
65
|
+
message: value.message,
|
|
66
|
+
name: value.name,
|
|
67
|
+
stack: value.stack,
|
|
68
|
+
...(value.constructor.name === 'HTTPError' || value.constructor.name === 'AxiosError' ? {
|
|
69
|
+
status: (value as any).status,
|
|
70
|
+
code: (value as any).code,
|
|
71
|
+
data: (value as any).data,
|
|
72
|
+
} : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle circular references
|
|
77
|
+
if (typeof value === "object" && value !== null) {
|
|
78
|
+
if (seen.has(value)) {
|
|
79
|
+
return "[Circular]";
|
|
80
|
+
}
|
|
81
|
+
seen.add(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return value;
|
|
85
|
+
},
|
|
86
|
+
indent
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
55
90
|
const baseFormat = format.printf(({ timestamp, level, message, ...rest }) => {
|
|
56
|
-
let restString =
|
|
91
|
+
let restString = safeStringify(rest, 2);
|
|
57
92
|
restString = restString === "{}" ? "" : restString;
|
|
58
93
|
|
|
59
94
|
let formattedMessage: string;
|
|
@@ -242,9 +277,6 @@ export const configureLogger = (logLevel: string, nodeEnv: string) => {
|
|
|
242
277
|
logger.level = logLevel;
|
|
243
278
|
};
|
|
244
279
|
|
|
245
|
-
// Ensure file logging is configured as soon as the module loads
|
|
246
|
-
configureLogger(initialLogLevel, initialNodeEnv);
|
|
247
|
-
|
|
248
280
|
// Add fatal method for Graceful compatibility
|
|
249
281
|
(logger as any).fatal = (err: any, meta?: any) => logger.error(err, meta);
|
|
250
282
|
|
|
@@ -14,10 +14,6 @@ export interface IERPConnector {
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Sync data from the ERP system to MM.
|
|
17
|
-
* Timezone expectations:
|
|
18
|
-
* - When sending ERP data to MachineMetrics (e.g., via StandardProcessDrivers or MMEntityProcessor),
|
|
19
|
-
* connectors MUST convert all datetime fields to ISO-8601 UTC strings (trailing `Z` or explicit offset)
|
|
20
|
-
* before invoking SDK helpers. The SDK validates/forwards these values but does not apply timezone shifts.
|
|
21
17
|
*/
|
|
22
18
|
syncFromERP(): Promise<void>;
|
|
23
19
|
syncFromERPCompleted(): Promise<void>;
|
|
@@ -35,16 +31,13 @@ export interface IERPConnector {
|
|
|
35
31
|
retryFailedLaborTicketsCompleted(): Promise<void>;
|
|
36
32
|
|
|
37
33
|
/**
|
|
38
|
-
* Start the connector. Typically a connector should delegate control to the data-sync-service
|
|
34
|
+
* Start the connector. Typically a connector should delegate control to the data-sync-service.
|
|
39
35
|
*/
|
|
40
36
|
startUp(): Promise<void>;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
39
|
/**
|
|
44
40
|
* Interface for standard process methods that call ERP connectors back to create or update labor tickets.
|
|
45
|
-
*
|
|
46
|
-
* Timestamp fields on `MMReceiveLaborTicket` objects have already been converted by the SDKfrom MM's
|
|
47
|
-
* UTC API responses into the ERP's local timezone, based on the company's timezone setting.
|
|
48
41
|
*/
|
|
49
42
|
export interface IERPLaborTicketHandler {
|
|
50
43
|
createLaborTicketInERP(
|
package/src/types/index.ts
CHANGED
|
@@ -10,11 +10,3 @@ export type {
|
|
|
10
10
|
ERPResponse,
|
|
11
11
|
ERPObject,
|
|
12
12
|
} from "./erp-types.js";
|
|
13
|
-
export type {
|
|
14
|
-
FlattenedWorkOrderRow,
|
|
15
|
-
FlattenedWorkOrderRowBase,
|
|
16
|
-
FlattenedWorkOrderPartFields,
|
|
17
|
-
FlattenedWorkOrderPartOperationFields,
|
|
18
|
-
FlattenedWorkOrderFields,
|
|
19
|
-
FlattenedWorkOrderOperationFields,
|
|
20
|
-
} from "./flattened-work-order.js";
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error formatter utility for standardizing error messages across connectors
|
|
3
|
+
* Extracts human-readable messages from various error types (axios, custom errors, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface FormattedError {
|
|
7
|
+
message: string;
|
|
8
|
+
code: string;
|
|
9
|
+
httpStatus?: number;
|
|
10
|
+
metadata?: Record<string, any>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract meaningful error message from various error types
|
|
15
|
+
*/
|
|
16
|
+
export function formatError(error: any): FormattedError {
|
|
17
|
+
if (!error) {
|
|
18
|
+
return {
|
|
19
|
+
message: 'Unknown error occurred',
|
|
20
|
+
code: 'UNKNOWN_ERROR',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle axios errors FIRST (before checking for FormattedError)
|
|
25
|
+
// because axios errors also have message and code properties
|
|
26
|
+
if (error.isAxiosError || error.name === 'AxiosError') {
|
|
27
|
+
return formatAxiosError(error);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// If it's already a FormattedError, return as-is
|
|
31
|
+
// Check for httpStatus or metadata to distinguish from axios errors
|
|
32
|
+
if (error.message && error.code && typeof error.message === 'string' && typeof error.code === 'string' && !error.config) {
|
|
33
|
+
return error as FormattedError;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Handle standard Error objects
|
|
37
|
+
if (error instanceof Error) {
|
|
38
|
+
return {
|
|
39
|
+
message: error.message || 'An error occurred',
|
|
40
|
+
code: 'ERROR',
|
|
41
|
+
metadata: {
|
|
42
|
+
name: error.name,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Handle string errors
|
|
48
|
+
if (typeof error === 'string') {
|
|
49
|
+
return {
|
|
50
|
+
message: error,
|
|
51
|
+
code: 'ERROR',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Last resort: try to extract any useful info
|
|
56
|
+
const message = error.message || error.toString?.() || 'Unknown error occurred';
|
|
57
|
+
return {
|
|
58
|
+
message: typeof message === 'string' ? message : JSON.stringify(message),
|
|
59
|
+
code: error.code || 'UNKNOWN_ERROR',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Format axios-specific errors with detailed context
|
|
65
|
+
*/
|
|
66
|
+
function formatAxiosError(error: any): FormattedError {
|
|
67
|
+
const httpStatus = error.response?.status;
|
|
68
|
+
const method = error.config?.method?.toUpperCase();
|
|
69
|
+
const url = error.config?.url;
|
|
70
|
+
|
|
71
|
+
// First check if connector already extracted the message
|
|
72
|
+
let message = error._extractedMessage;
|
|
73
|
+
|
|
74
|
+
// If not, try to extract from response data
|
|
75
|
+
if (!message) {
|
|
76
|
+
const extractedMessage = extractErrorMessage(error.response?.data);
|
|
77
|
+
message = extractedMessage || error.response?.statusText || error.message || 'Request failed';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Determine error code based on status
|
|
81
|
+
const code = categorizeHttpError(httpStatus);
|
|
82
|
+
|
|
83
|
+
const metadata: Record<string, any> = {
|
|
84
|
+
method,
|
|
85
|
+
url,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Add additional context for specific error types
|
|
89
|
+
if (httpStatus === 401 || httpStatus === 403) {
|
|
90
|
+
metadata.hint = 'Check authentication credentials';
|
|
91
|
+
} else if (httpStatus === 404) {
|
|
92
|
+
metadata.hint = 'Resource not found - check endpoint URL';
|
|
93
|
+
} else if (httpStatus && httpStatus >= 500) {
|
|
94
|
+
metadata.hint = 'ERP system may be temporarily unavailable';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
message,
|
|
99
|
+
code,
|
|
100
|
+
httpStatus,
|
|
101
|
+
metadata,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Extract meaningful error message from response data
|
|
107
|
+
* Handles various ERP-specific response formats
|
|
108
|
+
*/
|
|
109
|
+
function extractErrorMessage(data: any): string | null {
|
|
110
|
+
if (!data) return null;
|
|
111
|
+
|
|
112
|
+
// Common error message fields across different ERPs
|
|
113
|
+
const possibleFields = [
|
|
114
|
+
'ErrorMessage', // Epicor
|
|
115
|
+
'error.message', // Common REST format
|
|
116
|
+
'error', // Simple format
|
|
117
|
+
'Message', // .NET style
|
|
118
|
+
'message', // JavaScript style
|
|
119
|
+
'errorMessage', // Camel case
|
|
120
|
+
'error_message', // Snake case
|
|
121
|
+
'errors[0].message', // Array of errors
|
|
122
|
+
'title', // Problem details format
|
|
123
|
+
'detail', // Problem details format
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const field of possibleFields) {
|
|
127
|
+
const value = getNestedValue(data, field);
|
|
128
|
+
if (value && typeof value === 'string') {
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// If data is a string, return it
|
|
134
|
+
if (typeof data === 'string') {
|
|
135
|
+
// Try to parse as JSON first
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(data);
|
|
138
|
+
return extractErrorMessage(parsed);
|
|
139
|
+
} catch {
|
|
140
|
+
return data;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get nested value from object using dot notation
|
|
149
|
+
*/
|
|
150
|
+
function getNestedValue(obj: any, path: string): any {
|
|
151
|
+
const parts = path.split('.');
|
|
152
|
+
let current = obj;
|
|
153
|
+
|
|
154
|
+
for (const part of parts) {
|
|
155
|
+
// Handle array notation like 'errors[0]'
|
|
156
|
+
const arrayMatch = part.match(/(\w+)\[(\d+)\]/);
|
|
157
|
+
if (arrayMatch) {
|
|
158
|
+
const [, key, index] = arrayMatch;
|
|
159
|
+
current = current?.[key]?.[parseInt(index, 10)];
|
|
160
|
+
} else {
|
|
161
|
+
current = current?.[part];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (current === undefined || current === null) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Categorize HTTP errors into standard codes
|
|
174
|
+
*/
|
|
175
|
+
function categorizeHttpError(status?: number): string {
|
|
176
|
+
if (!status) return 'NETWORK_ERROR';
|
|
177
|
+
|
|
178
|
+
if (status === 400) return 'VALIDATION_ERROR';
|
|
179
|
+
if (status === 401) return 'AUTHENTICATION_ERROR';
|
|
180
|
+
if (status === 403) return 'AUTHORIZATION_ERROR';
|
|
181
|
+
if (status === 404) return 'NOT_FOUND';
|
|
182
|
+
if (status === 409) return 'CONFLICT';
|
|
183
|
+
if (status === 422) return 'VALIDATION_ERROR';
|
|
184
|
+
if (status === 429) return 'RATE_LIMIT';
|
|
185
|
+
if (status >= 500) return 'ERP_SERVER_ERROR';
|
|
186
|
+
if (status >= 400) return 'CLIENT_ERROR';
|
|
187
|
+
|
|
188
|
+
return 'HTTP_ERROR';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Format error for logging (shorter, cleaner version)
|
|
193
|
+
*/
|
|
194
|
+
export function formatErrorForLogging(error: any): string {
|
|
195
|
+
const formatted = formatError(error);
|
|
196
|
+
|
|
197
|
+
let message = `[${formatted.code}] ${formatted.message}`;
|
|
198
|
+
|
|
199
|
+
if (formatted.httpStatus) {
|
|
200
|
+
message = `[${formatted.httpStatus}] ${message}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return message;
|
|
204
|
+
}
|
|
205
|
+
|
package/src/utils/http-client.ts
CHANGED
|
@@ -64,9 +64,10 @@ class AxiosClient implements HTTPClient {
|
|
|
64
64
|
* It can be convenient to set `baseURL` for an instance of axios to pass relative URLs to methods of that instance.
|
|
65
65
|
*/
|
|
66
66
|
constructor(baseUrl: string, retryAttempts: number) {
|
|
67
|
+
const timeout = parseInt(process.env.MM_API_TIMEOUT || "30000");
|
|
67
68
|
this.client = axios.create({
|
|
68
69
|
baseURL: baseUrl,
|
|
69
|
-
timeout:
|
|
70
|
+
timeout: timeout,
|
|
70
71
|
headers: {
|
|
71
72
|
"Content-Type": "application/json",
|
|
72
73
|
},
|
|
@@ -108,7 +109,7 @@ class AxiosClient implements HTTPClient {
|
|
|
108
109
|
params: config.params,
|
|
109
110
|
signal: controller.signal,
|
|
110
111
|
};
|
|
111
|
-
|
|
112
|
+
|
|
112
113
|
logger.info("HTTP request starting", {
|
|
113
114
|
url: config.url,
|
|
114
115
|
method: config.method,
|
|
@@ -127,10 +128,8 @@ class AxiosClient implements HTTPClient {
|
|
|
127
128
|
try {
|
|
128
129
|
for (let attempt = 0; attempt <= this.retryAttempts; attempt++) {
|
|
129
130
|
try {
|
|
130
|
-
// MLW TODO
|
|
131
131
|
logger.info(`HTTP request attempt ${attempt + 1}/${this.retryAttempts + 1}`);
|
|
132
132
|
const response = await this.client.request<T>(axiosConfig);
|
|
133
|
-
// MLW TODO
|
|
134
133
|
logger.info("HTTP request succeeded", { status: response.status });
|
|
135
134
|
return {
|
|
136
135
|
data: response.data,
|
package/src/utils/index.ts
CHANGED
|
@@ -17,11 +17,6 @@ export {
|
|
|
17
17
|
export { getTimezoneOffsetAndPersist } from "./time-utils.js";
|
|
18
18
|
export { formatDateWithTZOffset, convertToLocalTime, toISOWithOffset } from "./timezone.js";
|
|
19
19
|
export { applyTimezoneOffsetsToFields } from "./time-utils.js";
|
|
20
|
-
export {
|
|
21
|
-
convertUtcDateTimeToErpLocal,
|
|
22
|
-
convertErpLocalDateTimeToUtc,
|
|
23
|
-
getERPTimezone,
|
|
24
|
-
} from "./erp-timezone-utils.js";
|
|
25
20
|
export * from "./time-utils.js";
|
|
26
21
|
|
|
27
22
|
/**
|
|
@@ -69,6 +64,12 @@ export type { HTTPClient, HTTPRequestConfig, HTTPResponse } from "./http-client.
|
|
|
69
64
|
export * from "./mm-labor-ticket-helpers.js";
|
|
70
65
|
export { getErrorType } from './error-utils.js';
|
|
71
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Error formatting utilities
|
|
69
|
+
*/
|
|
70
|
+
export { formatError, formatErrorForLogging } from './error-formatter.js';
|
|
71
|
+
export type { FormattedError } from './error-formatter.js';
|
|
72
|
+
|
|
72
73
|
/**
|
|
73
74
|
* MM Connector Logger utilities
|
|
74
75
|
*/
|
|
@@ -72,8 +72,6 @@ export const setInitialLoadComplete = (complete: boolean): void => {
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* @deprecated The cached numeric offset cannot account for DST changes.
|
|
76
|
-
* Prefer using the cached timezone name with Luxon helpers for conversions.
|
|
77
75
|
* Gets the company's cached current timezone offset (e.g., -5)
|
|
78
76
|
* @returns The cached timezone offset or 0 if not found
|
|
79
77
|
*/
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { convertToLocalTime, toISOWithOffset } from "./timezone.js";
|
|
1
2
|
import { MMReceiveLaborTicket } from "../services/mm-api-service/types/receive-types.js";
|
|
2
|
-
import { convertUtcDateTimeToErpLocal, getERPTimezone } from "./erp-timezone-utils.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Apply timezone offsets to datetime fields specifically for the MMApiReceiveLaborTicket object from the Zulu-based MM API
|
|
6
|
+
* (presumably) before sending to the ERP
|
|
7
7
|
* @param laborTicket The MMApiReceiveLaborTicket object to convert
|
|
8
|
+
* @param timezoneOffset The timezone offset to apply
|
|
8
9
|
*/
|
|
9
10
|
export function convertLaborTicketToLocalTimezone(
|
|
10
|
-
laborTicket: MMReceiveLaborTicket
|
|
11
|
+
laborTicket: MMReceiveLaborTicket,
|
|
12
|
+
timezoneOffset: number
|
|
11
13
|
): MMReceiveLaborTicket {
|
|
12
14
|
const timeFields = [
|
|
13
15
|
"clockIn",
|
|
@@ -18,14 +20,9 @@ export function convertLaborTicketToLocalTimezone(
|
|
|
18
20
|
"workOrderOperationClosedDate",
|
|
19
21
|
] as const;
|
|
20
22
|
|
|
21
|
-
const timezone = getERPTimezone();
|
|
22
23
|
timeFields.forEach((field) => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
laborTicket[field] = convertUtcDateTimeToErpLocal(value, timezone);
|
|
26
|
-
} else {
|
|
27
|
-
laborTicket[field] = null;
|
|
28
|
-
}
|
|
24
|
+
const localTime = convertToLocalTime(laborTicket[field], timezoneOffset);
|
|
25
|
+
laborTicket[field] = localTime ? toISOWithOffset(localTime, timezoneOffset) : null;
|
|
29
26
|
});
|
|
30
27
|
return laborTicket;
|
|
31
28
|
}
|