@machinemetrics/mm-erp-sdk 0.1.9-beta.1 → 0.1.9-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/README.md +5 -0
- package/dist/{config-CvA-mFWF.js → config-Bax6Ofp5.js} +2 -2
- package/dist/{config-CvA-mFWF.js.map → config-Bax6Ofp5.js.map} +1 -1
- package/dist/{connector-factory-BPm2GVVF.js → connector-factory-BaMIlES8.js} +2 -2
- package/dist/{connector-factory-BPm2GVVF.js.map → connector-factory-BaMIlES8.js.map} +1 -1
- package/dist/{hashed-cache-manager-B15NN8hK.js → hashed-cache-manager-C1u9jQgY.js} +4 -4
- package/dist/{hashed-cache-manager-B15NN8hK.js.map → hashed-cache-manager-C1u9jQgY.js.map} +1 -1
- package/dist/{index-D8qO1NyK.js → index-BkVlW0ZW.js} +2 -2
- package/dist/{index-D8qO1NyK.js.map → index-BkVlW0ZW.js.map} +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/{logger-BWw0_z9q.js → logger-DW5fyhVS.js} +114 -78
- package/dist/logger-DW5fyhVS.js.map +1 -0
- package/dist/mm-erp-sdk.js +7 -716
- package/dist/mm-erp-sdk.js.map +1 -1
- package/dist/services/data-sync-service/data-sync-service.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/clean-up-expired-cache.js +4 -4
- package/dist/services/data-sync-service/jobs/from-erp.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/from-erp.js +12 -5
- package/dist/services/data-sync-service/jobs/from-erp.js.map +1 -1
- package/dist/services/data-sync-service/jobs/retry-failed-labor-tickets.js +3 -3
- package/dist/services/data-sync-service/jobs/run-migrations.js +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 +4 -7
- package/dist/services/data-sync-service/jobs/to-erp.js.map +1 -1
- package/dist/services/mm-api-service/index.d.ts +0 -7
- package/dist/services/mm-api-service/index.d.ts.map +1 -1
- package/dist/services/mm-api-service/mm-api-service.d.ts +0 -6
- package/dist/services/mm-api-service/mm-api-service.d.ts.map +1 -1
- package/dist/services/reporting-service/logger.d.ts.map +1 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +2 -5
- package/src/index.ts +0 -3
- package/src/services/data-sync-service/data-sync-service.ts +0 -10
- package/src/services/data-sync-service/jobs/from-erp.ts +7 -2
- package/src/services/data-sync-service/jobs/to-erp.ts +1 -5
- package/src/services/mm-api-service/index.ts +0 -8
- package/src/services/mm-api-service/mm-api-service.ts +2 -19
- package/src/services/reporting-service/logger.ts +111 -81
- package/src/utils/index.ts +0 -6
- package/dist/logger-BWw0_z9q.js.map +0 -1
- package/dist/services/data-sync-service/nats-labor-ticket-listener.d.ts +0 -30
- package/dist/services/data-sync-service/nats-labor-ticket-listener.d.ts.map +0 -1
- package/dist/services/mm-api-service/company-info.d.ts +0 -13
- package/dist/services/mm-api-service/company-info.d.ts.map +0 -1
- package/dist/services/nats-service/nats-service.d.ts +0 -114
- package/dist/services/nats-service/nats-service.d.ts.map +0 -1
- package/dist/services/nats-service/test-nats-subscriber.d.ts +0 -6
- package/dist/services/nats-service/test-nats-subscriber.d.ts.map +0 -1
- package/dist/utils/error-formatter.d.ts +0 -19
- package/dist/utils/error-formatter.d.ts.map +0 -1
- package/src/services/data-sync-service/nats-labor-ticket-listener.ts +0 -341
- package/src/services/mm-api-service/company-info.ts +0 -87
- package/src/services/nats-service/nats-service.ts +0 -351
- package/src/services/nats-service/test-nats-subscriber.ts +0 -96
- package/src/utils/error-formatter.ts +0 -205
package/dist/mm-erp-sdk.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { C as CoreConfiguration, H as HashedCacheManager } from "./hashed-cache-manager-
|
|
2
|
-
import { E, g, a } from "./hashed-cache-manager-
|
|
3
|
-
import { l as logger } from "./logger-
|
|
4
|
-
import { g as getCachedMMToken, s as setCachedMMToken, a as setTimezoneOffsetInCache, b as setTimezoneNameInCache, c as getCachedTimezoneOffset, S as SQLiteCoordinator } from "./index-
|
|
5
|
-
import { f, d, e } from "./index-
|
|
1
|
+
import { C as CoreConfiguration, H as HashedCacheManager } from "./hashed-cache-manager-C1u9jQgY.js";
|
|
2
|
+
import { E, g, a } from "./hashed-cache-manager-C1u9jQgY.js";
|
|
3
|
+
import { l as logger } from "./logger-DW5fyhVS.js";
|
|
4
|
+
import { g as getCachedMMToken, s as setCachedMMToken, a as setTimezoneOffsetInCache, b as setTimezoneNameInCache, c as getCachedTimezoneOffset, S as SQLiteCoordinator } from "./index-BkVlW0ZW.js";
|
|
5
|
+
import { f, d, e } from "./index-BkVlW0ZW.js";
|
|
6
6
|
import axios, { AxiosError } from "axios";
|
|
7
7
|
import knex from "knex";
|
|
8
8
|
import { c as config } from "./knexfile-Bng2Ru9c.js";
|
|
9
9
|
import fs from "fs";
|
|
10
10
|
import path from "path";
|
|
11
|
-
import
|
|
11
|
+
import "./connector-factory-BaMIlES8.js";
|
|
12
12
|
import Bree from "bree";
|
|
13
13
|
import Graceful from "@ladjs/graceful";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
15
|
-
import { StringCodec, connect } from "nats";
|
|
16
15
|
import sql from "mssql";
|
|
17
16
|
import { z } from "zod";
|
|
18
17
|
var ERPObjType = /* @__PURE__ */ ((ERPObjType2) => {
|
|
@@ -26,10 +25,6 @@ var ERPObjType = /* @__PURE__ */ ((ERPObjType2) => {
|
|
|
26
25
|
ERPObjType2[ERPObjType2["LABOR_TICKETS"] = 7] = "LABOR_TICKETS";
|
|
27
26
|
return ERPObjType2;
|
|
28
27
|
})(ERPObjType || {});
|
|
29
|
-
const erpTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
30
|
-
__proto__: null,
|
|
31
|
-
ERPObjType
|
|
32
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
33
28
|
var Operator = /* @__PURE__ */ ((Operator2) => {
|
|
34
29
|
Operator2["eq"] = "eq";
|
|
35
30
|
Operator2["lt"] = "lt";
|
|
@@ -486,7 +481,7 @@ class MMApiClient {
|
|
|
486
481
|
const hasStatus = (err) => {
|
|
487
482
|
return typeof err === "object" && err !== null && "status" in err;
|
|
488
483
|
};
|
|
489
|
-
const isAuthError = hasStatus(error) && (error.status === 401 || error.status === 403)
|
|
484
|
+
const isAuthError = hasStatus(error) && (error.status === 401 || error.status === 403);
|
|
490
485
|
if (isAuthError && !options.token) {
|
|
491
486
|
logger.info("Retrying request with fresh token due to auth error");
|
|
492
487
|
this.tokenMgr.invalidateToken();
|
|
@@ -810,17 +805,6 @@ class MMApiClient {
|
|
|
810
805
|
);
|
|
811
806
|
return updates.data.map((ticket) => new MMReceiveLaborTicket(ticket));
|
|
812
807
|
}
|
|
813
|
-
/**
|
|
814
|
-
* Fetch a single labor ticket by reference from the MM API
|
|
815
|
-
* @param laborTicketRef The labor ticket reference to fetch
|
|
816
|
-
* @returns Promise with the labor ticket data
|
|
817
|
-
*/
|
|
818
|
-
async fetchLaborTicketByRef(laborTicketRef) {
|
|
819
|
-
const response = await this.getData(
|
|
820
|
-
`${this.resourceURLs[ERPObjType.LABOR_TICKETS]}/${laborTicketRef}`
|
|
821
|
-
);
|
|
822
|
-
return new MMReceiveLaborTicket(response.data);
|
|
823
|
-
}
|
|
824
808
|
/**
|
|
825
809
|
* Fetch a checkpoint for a specific system, table, and checkpoint type
|
|
826
810
|
* @param checkpoint The checkpoint to fetch
|
|
@@ -1334,65 +1318,6 @@ class MMSendLaborTicket {
|
|
|
1334
1318
|
);
|
|
1335
1319
|
}
|
|
1336
1320
|
}
|
|
1337
|
-
let companyInfoCache = null;
|
|
1338
|
-
const getCompanyInfo = async () => {
|
|
1339
|
-
if (companyInfoCache) {
|
|
1340
|
-
return companyInfoCache;
|
|
1341
|
-
}
|
|
1342
|
-
try {
|
|
1343
|
-
const config2 = CoreConfiguration.inst();
|
|
1344
|
-
const apiUrl = config2.mmApiBaseUrl;
|
|
1345
|
-
const authToken = config2.mmApiAuthToken;
|
|
1346
|
-
if (!apiUrl || !authToken) {
|
|
1347
|
-
throw new Error("Missing required configuration for company info fetch");
|
|
1348
|
-
}
|
|
1349
|
-
const client = HTTPClientFactory.getInstance({
|
|
1350
|
-
baseUrl: apiUrl,
|
|
1351
|
-
retryAttempts: config2.mmApiRetryAttempts
|
|
1352
|
-
});
|
|
1353
|
-
const response = await client.request({
|
|
1354
|
-
url: "/accounts/current?includeLocation=true",
|
|
1355
|
-
method: "GET",
|
|
1356
|
-
headers: {
|
|
1357
|
-
Authorization: `Bearer ${authToken}`
|
|
1358
|
-
}
|
|
1359
|
-
});
|
|
1360
|
-
const userInfo = response.data;
|
|
1361
|
-
if (!userInfo?.company) {
|
|
1362
|
-
throw new Error("Unable to retrieve company information from API");
|
|
1363
|
-
}
|
|
1364
|
-
logger.info("Fetched company info from /accounts/current", {
|
|
1365
|
-
locationRef: userInfo.locationRef,
|
|
1366
|
-
companyId: userInfo.company.id,
|
|
1367
|
-
timezone: userInfo.company.timezone
|
|
1368
|
-
});
|
|
1369
|
-
companyInfoCache = {
|
|
1370
|
-
timezone: userInfo.company.timezone,
|
|
1371
|
-
locationRef: String(userInfo.locationRef),
|
|
1372
|
-
// Convert number to string
|
|
1373
|
-
companyId: userInfo.company.id
|
|
1374
|
-
};
|
|
1375
|
-
return companyInfoCache;
|
|
1376
|
-
} catch (error) {
|
|
1377
|
-
throw new Error(
|
|
1378
|
-
`Failed to get company info: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1379
|
-
);
|
|
1380
|
-
}
|
|
1381
|
-
};
|
|
1382
|
-
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1383
|
-
__proto__: null,
|
|
1384
|
-
MMApiClient,
|
|
1385
|
-
MMReceiveLaborTicket,
|
|
1386
|
-
MMSendLaborTicket,
|
|
1387
|
-
MMSendPart,
|
|
1388
|
-
MMSendPartOperation,
|
|
1389
|
-
MMSendPerson,
|
|
1390
|
-
MMSendReason,
|
|
1391
|
-
MMSendResource,
|
|
1392
|
-
MMSendWorkOrder,
|
|
1393
|
-
MMSendWorkOrderOperation,
|
|
1394
|
-
getCompanyInfo
|
|
1395
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
1396
1321
|
function getUniqueRows(data, fields, sortFields) {
|
|
1397
1322
|
const createCompositeKey = (item) => {
|
|
1398
1323
|
return fields.map((field) => String(item[field])).join("|");
|
|
@@ -2573,10 +2498,6 @@ class MMEntityProcessor {
|
|
|
2573
2498
|
}
|
|
2574
2499
|
}
|
|
2575
2500
|
}
|
|
2576
|
-
const mmEntityProcessor = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2577
|
-
__proto__: null,
|
|
2578
|
-
MMEntityProcessor
|
|
2579
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
2580
2501
|
class MMBatchValidationError extends Error {
|
|
2581
2502
|
upsertedEntities;
|
|
2582
2503
|
localDedupeCount;
|
|
@@ -3174,146 +3095,6 @@ function getErrorType(error) {
|
|
|
3174
3095
|
}
|
|
3175
3096
|
return "Error";
|
|
3176
3097
|
}
|
|
3177
|
-
function formatError(error) {
|
|
3178
|
-
if (!error) {
|
|
3179
|
-
return {
|
|
3180
|
-
message: "Unknown error occurred",
|
|
3181
|
-
code: "UNKNOWN_ERROR"
|
|
3182
|
-
};
|
|
3183
|
-
}
|
|
3184
|
-
if (error.isAxiosError || error.name === "AxiosError") {
|
|
3185
|
-
return formatAxiosError(error);
|
|
3186
|
-
}
|
|
3187
|
-
if (error.message && error.code && typeof error.message === "string" && typeof error.code === "string" && !error.config) {
|
|
3188
|
-
return error;
|
|
3189
|
-
}
|
|
3190
|
-
if (error instanceof Error) {
|
|
3191
|
-
return {
|
|
3192
|
-
message: error.message || "An error occurred",
|
|
3193
|
-
code: "ERROR",
|
|
3194
|
-
metadata: {
|
|
3195
|
-
name: error.name
|
|
3196
|
-
}
|
|
3197
|
-
};
|
|
3198
|
-
}
|
|
3199
|
-
if (typeof error === "string") {
|
|
3200
|
-
return {
|
|
3201
|
-
message: error,
|
|
3202
|
-
code: "ERROR"
|
|
3203
|
-
};
|
|
3204
|
-
}
|
|
3205
|
-
const message = error.message || error.toString?.() || "Unknown error occurred";
|
|
3206
|
-
return {
|
|
3207
|
-
message: typeof message === "string" ? message : JSON.stringify(message),
|
|
3208
|
-
code: error.code || "UNKNOWN_ERROR"
|
|
3209
|
-
};
|
|
3210
|
-
}
|
|
3211
|
-
function formatAxiosError(error) {
|
|
3212
|
-
const httpStatus = error.response?.status;
|
|
3213
|
-
const method = error.config?.method?.toUpperCase();
|
|
3214
|
-
const url = error.config?.url;
|
|
3215
|
-
let message = error._extractedMessage;
|
|
3216
|
-
if (!message) {
|
|
3217
|
-
const extractedMessage = extractErrorMessage(error.response?.data);
|
|
3218
|
-
message = extractedMessage || error.response?.statusText || error.message || "Request failed";
|
|
3219
|
-
}
|
|
3220
|
-
const code = categorizeHttpError(httpStatus);
|
|
3221
|
-
const metadata = {
|
|
3222
|
-
method,
|
|
3223
|
-
url
|
|
3224
|
-
};
|
|
3225
|
-
if (httpStatus === 401 || httpStatus === 403) {
|
|
3226
|
-
metadata.hint = "Check authentication credentials";
|
|
3227
|
-
} else if (httpStatus === 404) {
|
|
3228
|
-
metadata.hint = "Resource not found - check endpoint URL";
|
|
3229
|
-
} else if (httpStatus && httpStatus >= 500) {
|
|
3230
|
-
metadata.hint = "ERP system may be temporarily unavailable";
|
|
3231
|
-
}
|
|
3232
|
-
return {
|
|
3233
|
-
message,
|
|
3234
|
-
code,
|
|
3235
|
-
httpStatus,
|
|
3236
|
-
metadata
|
|
3237
|
-
};
|
|
3238
|
-
}
|
|
3239
|
-
function extractErrorMessage(data) {
|
|
3240
|
-
if (!data) return null;
|
|
3241
|
-
const possibleFields = [
|
|
3242
|
-
"ErrorMessage",
|
|
3243
|
-
// Epicor
|
|
3244
|
-
"error.message",
|
|
3245
|
-
// Common REST format
|
|
3246
|
-
"error",
|
|
3247
|
-
// Simple format
|
|
3248
|
-
"Message",
|
|
3249
|
-
// .NET style
|
|
3250
|
-
"message",
|
|
3251
|
-
// JavaScript style
|
|
3252
|
-
"errorMessage",
|
|
3253
|
-
// Camel case
|
|
3254
|
-
"error_message",
|
|
3255
|
-
// Snake case
|
|
3256
|
-
"errors[0].message",
|
|
3257
|
-
// Array of errors
|
|
3258
|
-
"title",
|
|
3259
|
-
// Problem details format
|
|
3260
|
-
"detail"
|
|
3261
|
-
// Problem details format
|
|
3262
|
-
];
|
|
3263
|
-
for (const field of possibleFields) {
|
|
3264
|
-
const value = getNestedValue(data, field);
|
|
3265
|
-
if (value && typeof value === "string") {
|
|
3266
|
-
return value;
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
if (typeof data === "string") {
|
|
3270
|
-
try {
|
|
3271
|
-
const parsed = JSON.parse(data);
|
|
3272
|
-
return extractErrorMessage(parsed);
|
|
3273
|
-
} catch {
|
|
3274
|
-
return data;
|
|
3275
|
-
}
|
|
3276
|
-
}
|
|
3277
|
-
return null;
|
|
3278
|
-
}
|
|
3279
|
-
function getNestedValue(obj, path2) {
|
|
3280
|
-
const parts = path2.split(".");
|
|
3281
|
-
let current = obj;
|
|
3282
|
-
for (const part of parts) {
|
|
3283
|
-
const arrayMatch = part.match(/(\w+)\[(\d+)\]/);
|
|
3284
|
-
if (arrayMatch) {
|
|
3285
|
-
const [, key, index2] = arrayMatch;
|
|
3286
|
-
current = current?.[key]?.[parseInt(index2, 10)];
|
|
3287
|
-
} else {
|
|
3288
|
-
current = current?.[part];
|
|
3289
|
-
}
|
|
3290
|
-
if (current === void 0 || current === null) {
|
|
3291
|
-
return null;
|
|
3292
|
-
}
|
|
3293
|
-
}
|
|
3294
|
-
return current;
|
|
3295
|
-
}
|
|
3296
|
-
function categorizeHttpError(status) {
|
|
3297
|
-
if (!status) return "NETWORK_ERROR";
|
|
3298
|
-
if (status === 400) return "VALIDATION_ERROR";
|
|
3299
|
-
if (status === 401) return "AUTHENTICATION_ERROR";
|
|
3300
|
-
if (status === 403) return "AUTHORIZATION_ERROR";
|
|
3301
|
-
if (status === 404) return "NOT_FOUND";
|
|
3302
|
-
if (status === 409) return "CONFLICT";
|
|
3303
|
-
if (status === 422) return "VALIDATION_ERROR";
|
|
3304
|
-
if (status === 429) return "RATE_LIMIT";
|
|
3305
|
-
if (status >= 500) return "ERP_SERVER_ERROR";
|
|
3306
|
-
if (status >= 400) return "CLIENT_ERROR";
|
|
3307
|
-
return "HTTP_ERROR";
|
|
3308
|
-
}
|
|
3309
|
-
function formatErrorForLogging(error) {
|
|
3310
|
-
const formatted = formatError(error);
|
|
3311
|
-
let message = `[${formatted.code}] ${formatted.message}`;
|
|
3312
|
-
if (formatted.httpStatus) {
|
|
3313
|
-
message = `[${formatted.httpStatus}] ${message}`;
|
|
3314
|
-
}
|
|
3315
|
-
return message;
|
|
3316
|
-
}
|
|
3317
3098
|
class LogEntry {
|
|
3318
3099
|
level;
|
|
3319
3100
|
message;
|
|
@@ -3819,497 +3600,9 @@ class OAuthClient {
|
|
|
3819
3600
|
return data;
|
|
3820
3601
|
}
|
|
3821
3602
|
}
|
|
3822
|
-
const sc = StringCodec();
|
|
3823
|
-
class NatsService {
|
|
3824
|
-
connection = null;
|
|
3825
|
-
subscriptions = /* @__PURE__ */ new Map();
|
|
3826
|
-
config;
|
|
3827
|
-
handlers = [];
|
|
3828
|
-
statusPublishTimer = null;
|
|
3829
|
-
constructor(config2) {
|
|
3830
|
-
this.config = config2;
|
|
3831
|
-
}
|
|
3832
|
-
/**
|
|
3833
|
-
* Register a handler for a specific subject pattern
|
|
3834
|
-
*/
|
|
3835
|
-
registerHandler(registration) {
|
|
3836
|
-
logger.info("Registering NATS handler", {
|
|
3837
|
-
subject: registration.subject,
|
|
3838
|
-
description: registration.description
|
|
3839
|
-
});
|
|
3840
|
-
this.handlers.push(registration);
|
|
3841
|
-
}
|
|
3842
|
-
/**
|
|
3843
|
-
* Connect to NATS and start all registered handlers
|
|
3844
|
-
*/
|
|
3845
|
-
async connect() {
|
|
3846
|
-
if (!this.config.enabled) {
|
|
3847
|
-
logger.info("NATS is disabled, skipping connection");
|
|
3848
|
-
return;
|
|
3849
|
-
}
|
|
3850
|
-
try {
|
|
3851
|
-
logger.info("Connecting to NATS...", {
|
|
3852
|
-
servers: this.config.servers,
|
|
3853
|
-
name: this.config.name
|
|
3854
|
-
});
|
|
3855
|
-
this.connection = await connect({
|
|
3856
|
-
servers: this.config.servers,
|
|
3857
|
-
name: this.config.name,
|
|
3858
|
-
reconnect: this.config.reconnect ?? true,
|
|
3859
|
-
maxReconnectAttempts: this.config.maxReconnectAttempts ?? -1,
|
|
3860
|
-
reconnectTimeWait: this.config.reconnectTimeWait ?? 2e3
|
|
3861
|
-
});
|
|
3862
|
-
logger.info("Connected to NATS", {
|
|
3863
|
-
server: this.connection.getServer(),
|
|
3864
|
-
clientId: this.connection.info?.client_id
|
|
3865
|
-
});
|
|
3866
|
-
for (const registration of this.handlers) {
|
|
3867
|
-
await this.startHandler(registration);
|
|
3868
|
-
}
|
|
3869
|
-
this.startStatusPublishing();
|
|
3870
|
-
this.monitorConnection();
|
|
3871
|
-
this.setupShutdown();
|
|
3872
|
-
} catch (error) {
|
|
3873
|
-
logger.error("Failed to connect to NATS", { error });
|
|
3874
|
-
throw error;
|
|
3875
|
-
}
|
|
3876
|
-
}
|
|
3877
|
-
/**
|
|
3878
|
-
* Start a single handler (subscribe to its subject)
|
|
3879
|
-
*/
|
|
3880
|
-
async startHandler(registration) {
|
|
3881
|
-
if (!this.connection) {
|
|
3882
|
-
throw new Error("NATS connection not established");
|
|
3883
|
-
}
|
|
3884
|
-
const sub = this.connection.subscribe(registration.subject);
|
|
3885
|
-
this.subscriptions.set(registration.subject, sub);
|
|
3886
|
-
logger.info("Started NATS handler", {
|
|
3887
|
-
subject: registration.subject,
|
|
3888
|
-
description: registration.description
|
|
3889
|
-
});
|
|
3890
|
-
(async () => {
|
|
3891
|
-
for await (const msg of sub) {
|
|
3892
|
-
try {
|
|
3893
|
-
const data = sc.decode(msg.data);
|
|
3894
|
-
logger.info("Received NATS message", {
|
|
3895
|
-
subject: msg.subject,
|
|
3896
|
-
hasReply: !!msg.reply
|
|
3897
|
-
});
|
|
3898
|
-
let parsedData;
|
|
3899
|
-
try {
|
|
3900
|
-
parsedData = JSON.parse(data);
|
|
3901
|
-
} catch {
|
|
3902
|
-
parsedData = data;
|
|
3903
|
-
}
|
|
3904
|
-
const response = await registration.handler.handle(parsedData, msg.subject);
|
|
3905
|
-
if (msg.reply && response !== void 0) {
|
|
3906
|
-
const responseStr = JSON.stringify(response);
|
|
3907
|
-
msg.respond(sc.encode(responseStr));
|
|
3908
|
-
logger.info("Sent reply", { replySubject: msg.reply });
|
|
3909
|
-
}
|
|
3910
|
-
} catch (error) {
|
|
3911
|
-
logger.error("Error handling NATS message", {
|
|
3912
|
-
subject: msg.subject,
|
|
3913
|
-
error
|
|
3914
|
-
});
|
|
3915
|
-
if (msg.reply) {
|
|
3916
|
-
const errorResponse = {
|
|
3917
|
-
status: "error",
|
|
3918
|
-
error: {
|
|
3919
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
3920
|
-
code: "HANDLER_ERROR"
|
|
3921
|
-
}
|
|
3922
|
-
};
|
|
3923
|
-
msg.respond(sc.encode(JSON.stringify(errorResponse)));
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
|
-
})();
|
|
3928
|
-
}
|
|
3929
|
-
/**
|
|
3930
|
-
* Publish a message to a subject (for pub/sub)
|
|
3931
|
-
*/
|
|
3932
|
-
async publish(subject, data) {
|
|
3933
|
-
if (!this.connection) {
|
|
3934
|
-
throw new Error("NATS connection not established");
|
|
3935
|
-
}
|
|
3936
|
-
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
3937
|
-
this.connection.publish(subject, sc.encode(message));
|
|
3938
|
-
logger.info("Published NATS message", { subject });
|
|
3939
|
-
}
|
|
3940
|
-
/**
|
|
3941
|
-
* Send a request and wait for reply (for request-reply)
|
|
3942
|
-
*/
|
|
3943
|
-
async request(subject, data, timeoutMs = 3e4) {
|
|
3944
|
-
if (!this.connection) {
|
|
3945
|
-
throw new Error("NATS connection not established");
|
|
3946
|
-
}
|
|
3947
|
-
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
3948
|
-
const response = await this.connection.request(
|
|
3949
|
-
subject,
|
|
3950
|
-
sc.encode(message),
|
|
3951
|
-
{ timeout: timeoutMs }
|
|
3952
|
-
);
|
|
3953
|
-
const responseData = sc.decode(response.data);
|
|
3954
|
-
try {
|
|
3955
|
-
return JSON.parse(responseData);
|
|
3956
|
-
} catch {
|
|
3957
|
-
return responseData;
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
|
-
/**
|
|
3961
|
-
* Check if connected to NATS
|
|
3962
|
-
*/
|
|
3963
|
-
isConnected() {
|
|
3964
|
-
return this.connection !== null && !this.connection.isClosed();
|
|
3965
|
-
}
|
|
3966
|
-
/**
|
|
3967
|
-
* Start automatic status publishing (every 30 seconds)
|
|
3968
|
-
*/
|
|
3969
|
-
startStatusPublishing() {
|
|
3970
|
-
logger.info("Starting status publishing (every 30 seconds)");
|
|
3971
|
-
this.publishStatus();
|
|
3972
|
-
this.statusPublishTimer = setInterval(() => {
|
|
3973
|
-
this.publishStatus();
|
|
3974
|
-
}, 3e4);
|
|
3975
|
-
}
|
|
3976
|
-
/**
|
|
3977
|
-
* Publish connector status
|
|
3978
|
-
*/
|
|
3979
|
-
async publishStatus() {
|
|
3980
|
-
try {
|
|
3981
|
-
const status = {
|
|
3982
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3983
|
-
locationRef: this.config.locationRef,
|
|
3984
|
-
erpType: this.config.erpType,
|
|
3985
|
-
natsConnected: this.isConnected()
|
|
3986
|
-
};
|
|
3987
|
-
await this.publish(
|
|
3988
|
-
`mm.14.${this.config.locationRef}.erp.status`,
|
|
3989
|
-
status
|
|
3990
|
-
);
|
|
3991
|
-
logger.debug("Published connector status");
|
|
3992
|
-
} catch (error) {
|
|
3993
|
-
logger.error("Failed to publish status", { error });
|
|
3994
|
-
}
|
|
3995
|
-
}
|
|
3996
|
-
/**
|
|
3997
|
-
* Monitor connection status
|
|
3998
|
-
*/
|
|
3999
|
-
monitorConnection() {
|
|
4000
|
-
if (!this.connection) return;
|
|
4001
|
-
(async () => {
|
|
4002
|
-
for await (const status of this.connection.status()) {
|
|
4003
|
-
if (status.type !== "pingTimer") {
|
|
4004
|
-
logger.info("NATS connection status", {
|
|
4005
|
-
type: status.type,
|
|
4006
|
-
data: status.data
|
|
4007
|
-
});
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
4010
|
-
})();
|
|
4011
|
-
}
|
|
4012
|
-
/**
|
|
4013
|
-
* Setup graceful shutdown
|
|
4014
|
-
*/
|
|
4015
|
-
setupShutdown() {
|
|
4016
|
-
const shutdown = async () => {
|
|
4017
|
-
logger.info("Shutting down NATS service...");
|
|
4018
|
-
await this.disconnect();
|
|
4019
|
-
process.exit(0);
|
|
4020
|
-
};
|
|
4021
|
-
process.on("SIGINT", shutdown);
|
|
4022
|
-
process.on("SIGTERM", shutdown);
|
|
4023
|
-
}
|
|
4024
|
-
/**
|
|
4025
|
-
* Disconnect from NATS
|
|
4026
|
-
*/
|
|
4027
|
-
async disconnect() {
|
|
4028
|
-
if (this.statusPublishTimer) {
|
|
4029
|
-
clearInterval(this.statusPublishTimer);
|
|
4030
|
-
this.statusPublishTimer = null;
|
|
4031
|
-
}
|
|
4032
|
-
if (this.connection) {
|
|
4033
|
-
await this.connection.drain();
|
|
4034
|
-
this.connection = null;
|
|
4035
|
-
this.subscriptions.clear();
|
|
4036
|
-
logger.info("Disconnected from NATS");
|
|
4037
|
-
}
|
|
4038
|
-
}
|
|
4039
|
-
/**
|
|
4040
|
-
* Get the location reference
|
|
4041
|
-
*/
|
|
4042
|
-
getLocationRef() {
|
|
4043
|
-
return this.config.locationRef;
|
|
4044
|
-
}
|
|
4045
|
-
}
|
|
4046
|
-
class NatsLaborTicketListener {
|
|
4047
|
-
connector;
|
|
4048
|
-
natsService;
|
|
4049
|
-
constructor(connector) {
|
|
4050
|
-
this.connector = connector;
|
|
4051
|
-
}
|
|
4052
|
-
/**
|
|
4053
|
-
* Start listening for labor ticket events via NATS
|
|
4054
|
-
*/
|
|
4055
|
-
async start() {
|
|
4056
|
-
try {
|
|
4057
|
-
const companyInfo = await getCompanyInfo();
|
|
4058
|
-
const erpType = this.connector.type || "unknown";
|
|
4059
|
-
logger.info("Starting NATS listener for labor tickets", {
|
|
4060
|
-
locationRef: companyInfo.locationRef,
|
|
4061
|
-
companyId: companyInfo.companyId,
|
|
4062
|
-
erpType,
|
|
4063
|
-
servers: process.env.NATS_SERVERS || "nats://localhost:4222"
|
|
4064
|
-
});
|
|
4065
|
-
this.natsService = new NatsService({
|
|
4066
|
-
servers: process.env.NATS_SERVERS || "nats://localhost:4222",
|
|
4067
|
-
name: `${erpType}-connector`,
|
|
4068
|
-
locationRef: companyInfo.locationRef,
|
|
4069
|
-
erpType,
|
|
4070
|
-
enabled: true,
|
|
4071
|
-
reconnect: true,
|
|
4072
|
-
maxReconnectAttempts: -1,
|
|
4073
|
-
reconnectTimeWait: 2e3
|
|
4074
|
-
});
|
|
4075
|
-
this.registerHealthCheckHandler(companyInfo.locationRef, erpType);
|
|
4076
|
-
this.registerLaborTicketHandler(companyInfo.locationRef, erpType);
|
|
4077
|
-
await this.natsService.connect();
|
|
4078
|
-
logger.info("NATS listener started successfully", {
|
|
4079
|
-
subject: `mm.14.${companyInfo.locationRef}.labor-ticket.*`
|
|
4080
|
-
});
|
|
4081
|
-
} catch (error) {
|
|
4082
|
-
logger.error("Failed to start NATS listener", { error });
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
/**
|
|
4086
|
-
* Register health check handler - responds immediately to let Dashboard know connector is online
|
|
4087
|
-
*/
|
|
4088
|
-
registerHealthCheckHandler(locationRef, erpType) {
|
|
4089
|
-
if (!this.natsService) return;
|
|
4090
|
-
this.natsService.registerHandler({
|
|
4091
|
-
subject: `mm.14.${locationRef}.erp.health`,
|
|
4092
|
-
description: "Health check - responds immediately to indicate connector is online",
|
|
4093
|
-
handler: {
|
|
4094
|
-
handle: async () => {
|
|
4095
|
-
logger.debug("Health check received, sending pong");
|
|
4096
|
-
return {
|
|
4097
|
-
status: "online",
|
|
4098
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4099
|
-
locationRef,
|
|
4100
|
-
erpType
|
|
4101
|
-
};
|
|
4102
|
-
}
|
|
4103
|
-
}
|
|
4104
|
-
});
|
|
4105
|
-
}
|
|
4106
|
-
/**
|
|
4107
|
-
* Register labor ticket processing handler
|
|
4108
|
-
*/
|
|
4109
|
-
registerLaborTicketHandler(locationRef, erpType) {
|
|
4110
|
-
if (!this.natsService) return;
|
|
4111
|
-
this.natsService.registerHandler({
|
|
4112
|
-
subject: `mm.14.${locationRef}.labor-ticket.*`,
|
|
4113
|
-
description: "Process labor tickets in real-time from NATS",
|
|
4114
|
-
handler: {
|
|
4115
|
-
handle: async ({ data }, subject) => {
|
|
4116
|
-
const action = subject.split(".").pop();
|
|
4117
|
-
const { actionPayload } = data;
|
|
4118
|
-
const startTime = Date.now();
|
|
4119
|
-
const { laborTicketRef } = actionPayload;
|
|
4120
|
-
logger.info("Received labor ticket via NATS", {
|
|
4121
|
-
action,
|
|
4122
|
-
requestId: data.requestId,
|
|
4123
|
-
laborTicketRef
|
|
4124
|
-
});
|
|
4125
|
-
return await SQLiteCoordinator.executeWithLock("to-erp", async () => {
|
|
4126
|
-
try {
|
|
4127
|
-
let laborTicketData;
|
|
4128
|
-
if (laborTicketRef) {
|
|
4129
|
-
const mmApiClient = new MMApiClient();
|
|
4130
|
-
const laborTicket = await mmApiClient.fetchLaborTicketByRef(laborTicketRef);
|
|
4131
|
-
logger.info("Fetched labor ticket data from MM API", {
|
|
4132
|
-
laborTicketRef,
|
|
4133
|
-
laborTicketId: laborTicket.laborTicketId
|
|
4134
|
-
});
|
|
4135
|
-
laborTicketData = {
|
|
4136
|
-
...laborTicket,
|
|
4137
|
-
...actionPayload
|
|
4138
|
-
};
|
|
4139
|
-
} else {
|
|
4140
|
-
logger.info("No laborTicketRef provided, using actionPayload directly", {
|
|
4141
|
-
requestId: data.requestId
|
|
4142
|
-
});
|
|
4143
|
-
laborTicketData = actionPayload;
|
|
4144
|
-
}
|
|
4145
|
-
const mergedLaborTicket = new MMReceiveLaborTicket(laborTicketData);
|
|
4146
|
-
const result = await this.processLaborTicket(
|
|
4147
|
-
mergedLaborTicket,
|
|
4148
|
-
action || "unknown"
|
|
4149
|
-
);
|
|
4150
|
-
await this.updateCheckpoint(erpType, result);
|
|
4151
|
-
return {
|
|
4152
|
-
status: "success",
|
|
4153
|
-
requestId: data.requestId,
|
|
4154
|
-
action,
|
|
4155
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4156
|
-
processingTimeMs: Date.now() - startTime,
|
|
4157
|
-
laborTicketRef: result.laborTicketRef,
|
|
4158
|
-
laborTicket: result.laborTicket
|
|
4159
|
-
};
|
|
4160
|
-
} catch (error) {
|
|
4161
|
-
const formattedError = error?._formatted || formatError(error);
|
|
4162
|
-
logger.debug("Error details", {
|
|
4163
|
-
hasFormatted: !!error?._formatted,
|
|
4164
|
-
isAxiosError: error?.isAxiosError,
|
|
4165
|
-
errorMessage: error?.message,
|
|
4166
|
-
responseStatus: error?.response?.status,
|
|
4167
|
-
responseData: error?.response?.data
|
|
4168
|
-
});
|
|
4169
|
-
logger.error("Error handling labor ticket from NATS", {
|
|
4170
|
-
error: formattedError.message,
|
|
4171
|
-
code: formattedError.code,
|
|
4172
|
-
requestId: data.requestId,
|
|
4173
|
-
laborTicketRef
|
|
4174
|
-
});
|
|
4175
|
-
return {
|
|
4176
|
-
status: "error",
|
|
4177
|
-
requestId: data.requestId,
|
|
4178
|
-
action,
|
|
4179
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4180
|
-
processingTimeMs: Date.now() - startTime,
|
|
4181
|
-
error: {
|
|
4182
|
-
message: formattedError.message,
|
|
4183
|
-
code: formattedError.code,
|
|
4184
|
-
httpStatus: formattedError.httpStatus,
|
|
4185
|
-
metadata: formattedError.metadata,
|
|
4186
|
-
laborTicketRef
|
|
4187
|
-
}
|
|
4188
|
-
};
|
|
4189
|
-
}
|
|
4190
|
-
});
|
|
4191
|
-
}
|
|
4192
|
-
}
|
|
4193
|
-
});
|
|
4194
|
-
}
|
|
4195
|
-
/**
|
|
4196
|
-
* Process labor ticket in ERP and then create MM entities with ERP ID attached
|
|
4197
|
-
*/
|
|
4198
|
-
async processLaborTicket(laborTicket, action) {
|
|
4199
|
-
const { MMEntityProcessor: MMEntityProcessor2 } = await Promise.resolve().then(() => mmEntityProcessor);
|
|
4200
|
-
const { ERPObjType: ERPObjType2 } = await Promise.resolve().then(() => erpTypes);
|
|
4201
|
-
const { MMSendLaborTicket: MMSendLaborTicket2 } = await Promise.resolve().then(() => index);
|
|
4202
|
-
logger.info("Processing labor ticket: ERP first, then MM creation", {
|
|
4203
|
-
laborTicketRef: laborTicket.laborTicketRef,
|
|
4204
|
-
action,
|
|
4205
|
-
hasLaborTicketId: !!laborTicket.laborTicketId
|
|
4206
|
-
});
|
|
4207
|
-
try {
|
|
4208
|
-
let erpResult;
|
|
4209
|
-
if (action === "create" || !laborTicket.laborTicketId) {
|
|
4210
|
-
erpResult = await this.connector.createLaborTicketInERP(laborTicket);
|
|
4211
|
-
logger.info("Successfully created labor ticket in ERP", {
|
|
4212
|
-
laborTicketRef: laborTicket.laborTicketRef,
|
|
4213
|
-
erpUid: erpResult.erpUid
|
|
4214
|
-
});
|
|
4215
|
-
} else {
|
|
4216
|
-
erpResult = { laborTicket: await this.connector.updateLaborTicketInERP(laborTicket) };
|
|
4217
|
-
logger.info("Successfully updated labor ticket in ERP", {
|
|
4218
|
-
laborTicketRef: laborTicket.laborTicketRef
|
|
4219
|
-
});
|
|
4220
|
-
}
|
|
4221
|
-
const laborTicketForMM = { ...laborTicket };
|
|
4222
|
-
if (erpResult.erpUid) {
|
|
4223
|
-
laborTicketForMM.laborTicketId = erpResult.erpUid;
|
|
4224
|
-
if (!laborTicket.laborTicketId && laborTicketForMM.laborTicketRef) {
|
|
4225
|
-
const mmApiClient = new MMApiClient();
|
|
4226
|
-
await mmApiClient.updateLaborTicketIdByRef(
|
|
4227
|
-
laborTicketForMM.laborTicketRef,
|
|
4228
|
-
erpResult.erpUid
|
|
4229
|
-
);
|
|
4230
|
-
logger.info("Patched existing MM labor ticket with new ERP ID", {
|
|
4231
|
-
laborTicketRef: laborTicketForMM.laborTicketRef,
|
|
4232
|
-
laborTicketId: erpResult.erpUid
|
|
4233
|
-
});
|
|
4234
|
-
}
|
|
4235
|
-
}
|
|
4236
|
-
laborTicketForMM.state = laborTicketForMM.clockOut ? "CLOSED" : "OPEN";
|
|
4237
|
-
const mmLaborTicket = MMSendLaborTicket2.fromPlainObject(laborTicketForMM);
|
|
4238
|
-
const mmResult = await MMEntityProcessor2.writeEntities(
|
|
4239
|
-
ERPObjType2.LABOR_TICKETS,
|
|
4240
|
-
[mmLaborTicket],
|
|
4241
|
-
null
|
|
4242
|
-
// No caching for real-time operations
|
|
4243
|
-
);
|
|
4244
|
-
logger.info("Successfully updated MM entities after ERP operation", {
|
|
4245
|
-
laborTicketRef: laborTicketForMM.laborTicketRef,
|
|
4246
|
-
laborTicketId: laborTicketForMM.laborTicketId,
|
|
4247
|
-
entitiesCreated: mmResult.upsertedEntities
|
|
4248
|
-
});
|
|
4249
|
-
return {
|
|
4250
|
-
laborTicketRef: laborTicketForMM.laborTicketRef,
|
|
4251
|
-
laborTicket: laborTicketForMM
|
|
4252
|
-
};
|
|
4253
|
-
} catch (error) {
|
|
4254
|
-
const formattedError = formatError(error);
|
|
4255
|
-
logger.error("Failed to process labor ticket with MM creation", {
|
|
4256
|
-
laborTicketRef: laborTicket.laborTicketRef,
|
|
4257
|
-
action,
|
|
4258
|
-
error: formattedError.message,
|
|
4259
|
-
code: formattedError.code
|
|
4260
|
-
});
|
|
4261
|
-
const enhancedError = error;
|
|
4262
|
-
enhancedError._formatted = formattedError;
|
|
4263
|
-
throw enhancedError;
|
|
4264
|
-
}
|
|
4265
|
-
}
|
|
4266
|
-
/**
|
|
4267
|
-
* Update checkpoint to prevent to-erp polling job from reprocessing this ticket
|
|
4268
|
-
* Only updates if the new timestamp is later than the current checkpoint (prevents moving backwards)
|
|
4269
|
-
*/
|
|
4270
|
-
async updateCheckpoint(erpType, result) {
|
|
4271
|
-
const mmApiClient = new MMApiClient();
|
|
4272
|
-
const currentCheckpoint = await mmApiClient.getCheckpoint({
|
|
4273
|
-
system: erpType,
|
|
4274
|
-
table: "labor_tickets",
|
|
4275
|
-
checkpointType: "export",
|
|
4276
|
-
checkpointValue: {
|
|
4277
|
-
timestamp: ""
|
|
4278
|
-
}
|
|
4279
|
-
});
|
|
4280
|
-
const currentTimestamp = currentCheckpoint?.timestamp;
|
|
4281
|
-
const newTimestamp = result.laborTicket.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
|
|
4282
|
-
if (!currentTimestamp || new Date(newTimestamp) > new Date(currentTimestamp)) {
|
|
4283
|
-
await mmApiClient.saveCheckpoint({
|
|
4284
|
-
system: erpType,
|
|
4285
|
-
table: "labor_tickets",
|
|
4286
|
-
checkpointType: "export",
|
|
4287
|
-
checkpointValue: {
|
|
4288
|
-
timestamp: newTimestamp
|
|
4289
|
-
}
|
|
4290
|
-
});
|
|
4291
|
-
logger.debug("Updated export checkpoint after NATS processing", {
|
|
4292
|
-
laborTicketRef: result.laborTicketRef,
|
|
4293
|
-
previousCheckpoint: currentTimestamp,
|
|
4294
|
-
newCheckpoint: newTimestamp
|
|
4295
|
-
});
|
|
4296
|
-
} else {
|
|
4297
|
-
logger.debug("Skipped checkpoint update (timestamp not newer)", {
|
|
4298
|
-
laborTicketRef: result.laborTicketRef,
|
|
4299
|
-
currentCheckpoint: currentTimestamp,
|
|
4300
|
-
ticketTimestamp: newTimestamp
|
|
4301
|
-
});
|
|
4302
|
-
}
|
|
4303
|
-
}
|
|
4304
|
-
}
|
|
4305
3603
|
const runDataSyncService = async (connectorPath) => {
|
|
4306
3604
|
const config2 = CoreConfiguration.inst();
|
|
4307
3605
|
try {
|
|
4308
|
-
const connector = await createConnectorFromPath(connectorPath);
|
|
4309
|
-
if (process.env.NATS_ENABLED === "true") {
|
|
4310
|
-
const natsListener = new NatsLaborTicketListener(connector);
|
|
4311
|
-
await natsListener.start();
|
|
4312
|
-
}
|
|
4313
3606
|
const currentFileUrl = import.meta.url;
|
|
4314
3607
|
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
4315
3608
|
const sdkDistPath = path.dirname(currentFilePath);
|
|
@@ -4952,8 +4245,6 @@ export {
|
|
|
4952
4245
|
combinePsqlDateTime,
|
|
4953
4246
|
convertToLocalTime,
|
|
4954
4247
|
formatDateWithTZOffset,
|
|
4955
|
-
formatError,
|
|
4956
|
-
formatErrorForLogging,
|
|
4957
4248
|
formatPsqlDate,
|
|
4958
4249
|
formatPsqlTime,
|
|
4959
4250
|
f as getCachedTimezoneName,
|