@machinemetrics/mm-erp-sdk 0.1.9-beta.0 → 0.1.9-beta.1
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/{config-C2Dse2g2.js → config-CvA-mFWF.js} +2 -2
- package/dist/{config-C2Dse2g2.js.map → config-CvA-mFWF.js.map} +1 -1
- package/dist/{connector-factory-BGykiICT.js → connector-factory-BPm2GVVF.js} +2 -2
- package/dist/{connector-factory-BGykiICT.js.map → connector-factory-BPm2GVVF.js.map} +1 -1
- package/dist/{hashed-cache-manager-DbG72eRm.js → hashed-cache-manager-B15NN8hK.js} +4 -4
- package/dist/{hashed-cache-manager-DbG72eRm.js.map → hashed-cache-manager-B15NN8hK.js.map} +1 -1
- package/dist/{index-Cq9tNcJT.js → index-D8qO1NyK.js} +2 -2
- package/dist/{index-Cq9tNcJT.js.map → index-D8qO1NyK.js.map} +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/{logger-DeKxCUPp.js → logger-BWw0_z9q.js} +39 -14
- package/dist/logger-BWw0_z9q.js.map +1 -0
- package/dist/mm-erp-sdk.js +718 -13
- 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 +5 -12
- 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 +7 -4
- 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/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/index.d.ts +7 -0
- package/dist/services/mm-api-service/index.d.ts.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/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/test-nats-subscriber.d.ts +6 -0
- package/dist/services/nats-service/test-nats-subscriber.d.ts.map +1 -0
- package/dist/services/reporting-service/logger.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/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/index.ts +3 -0
- package/src/services/data-sync-service/data-sync-service.ts +10 -0
- package/src/services/data-sync-service/jobs/from-erp.ts +2 -7
- package/src/services/data-sync-service/jobs/to-erp.ts +5 -1
- package/src/services/data-sync-service/nats-labor-ticket-listener.ts +341 -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/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 +38 -1
- package/src/utils/error-formatter.ts +205 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/standard-process-drivers/labor-ticket-erp-synchronizer.ts +1 -5
- package/dist/logger-DeKxCUPp.js.map +0 -1
package/dist/mm-erp-sdk.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
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-B15NN8hK.js";
|
|
2
|
+
import { E, g, a } from "./hashed-cache-manager-B15NN8hK.js";
|
|
3
|
+
import { l as logger } from "./logger-BWw0_z9q.js";
|
|
4
|
+
import { g as getCachedMMToken, s as setCachedMMToken, a as setTimezoneOffsetInCache, b as setTimezoneNameInCache, c as getCachedTimezoneOffset, S as SQLiteCoordinator } from "./index-D8qO1NyK.js";
|
|
5
|
+
import { f, d, e } from "./index-D8qO1NyK.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 "./connector-factory-
|
|
11
|
+
import { c as createConnectorFromPath } from "./connector-factory-BPm2GVVF.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";
|
|
15
16
|
import sql from "mssql";
|
|
16
17
|
import { z } from "zod";
|
|
17
18
|
var ERPObjType = /* @__PURE__ */ ((ERPObjType2) => {
|
|
@@ -25,6 +26,10 @@ var ERPObjType = /* @__PURE__ */ ((ERPObjType2) => {
|
|
|
25
26
|
ERPObjType2[ERPObjType2["LABOR_TICKETS"] = 7] = "LABOR_TICKETS";
|
|
26
27
|
return ERPObjType2;
|
|
27
28
|
})(ERPObjType || {});
|
|
29
|
+
const erpTypes = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
30
|
+
__proto__: null,
|
|
31
|
+
ERPObjType
|
|
32
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
28
33
|
var Operator = /* @__PURE__ */ ((Operator2) => {
|
|
29
34
|
Operator2["eq"] = "eq";
|
|
30
35
|
Operator2["lt"] = "lt";
|
|
@@ -481,7 +486,7 @@ class MMApiClient {
|
|
|
481
486
|
const hasStatus = (err) => {
|
|
482
487
|
return typeof err === "object" && err !== null && "status" in err;
|
|
483
488
|
};
|
|
484
|
-
const isAuthError = hasStatus(error) && (error.status === 401 || error.status === 403);
|
|
489
|
+
const isAuthError = hasStatus(error) && (error.status === 401 || error.status === 403) || hasStatus(error) && error.status === 500 && typeof error.data?.error === "string" && error.data.error.includes("JWT");
|
|
485
490
|
if (isAuthError && !options.token) {
|
|
486
491
|
logger.info("Retrying request with fresh token due to auth error");
|
|
487
492
|
this.tokenMgr.invalidateToken();
|
|
@@ -805,6 +810,17 @@ class MMApiClient {
|
|
|
805
810
|
);
|
|
806
811
|
return updates.data.map((ticket) => new MMReceiveLaborTicket(ticket));
|
|
807
812
|
}
|
|
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
|
+
}
|
|
808
824
|
/**
|
|
809
825
|
* Fetch a checkpoint for a specific system, table, and checkpoint type
|
|
810
826
|
* @param checkpoint The checkpoint to fetch
|
|
@@ -840,7 +856,7 @@ class MMApiClient {
|
|
|
840
856
|
logger.info("saveCheckpoint:", { saveCheckpoint });
|
|
841
857
|
} else {
|
|
842
858
|
logger.info("Existing checkpoint found:", {
|
|
843
|
-
|
|
859
|
+
timestamp: checkpointResponse.timestamp
|
|
844
860
|
});
|
|
845
861
|
}
|
|
846
862
|
}
|
|
@@ -1318,6 +1334,65 @@ class MMSendLaborTicket {
|
|
|
1318
1334
|
);
|
|
1319
1335
|
}
|
|
1320
1336
|
}
|
|
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" }));
|
|
1321
1396
|
function getUniqueRows(data, fields, sortFields) {
|
|
1322
1397
|
const createCompositeKey = (item) => {
|
|
1323
1398
|
return fields.map((field) => String(item[field])).join("|");
|
|
@@ -1695,8 +1770,7 @@ class LaborTicketERPSynchronizer {
|
|
|
1695
1770
|
);
|
|
1696
1771
|
const mostRecentUpdate = laborTicketsUpdates.reduce(
|
|
1697
1772
|
(latest, ticket) => {
|
|
1698
|
-
if (!ticket.updatedAt) return latest;
|
|
1699
|
-
if (!latest) return ticket.updatedAt;
|
|
1773
|
+
if (!latest || !ticket.updatedAt) return latest;
|
|
1700
1774
|
return new Date(ticket.updatedAt) > new Date(latest) ? ticket.updatedAt : latest;
|
|
1701
1775
|
},
|
|
1702
1776
|
null
|
|
@@ -1752,9 +1826,6 @@ class LaborTicketERPSynchronizer {
|
|
|
1752
1826
|
timestamp: mostRecentUpdate || fallbackTimestamp
|
|
1753
1827
|
}
|
|
1754
1828
|
});
|
|
1755
|
-
logger.info("syncLaborTicketsToERP: Checkpoint saved:", {
|
|
1756
|
-
checkpointTimestamp: mostRecentUpdate || fallbackTimestamp
|
|
1757
|
-
});
|
|
1758
1829
|
} catch (error) {
|
|
1759
1830
|
logger.error("syncLaborTicketsToERP: Error:", error);
|
|
1760
1831
|
}
|
|
@@ -2502,6 +2573,10 @@ class MMEntityProcessor {
|
|
|
2502
2573
|
}
|
|
2503
2574
|
}
|
|
2504
2575
|
}
|
|
2576
|
+
const mmEntityProcessor = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
2577
|
+
__proto__: null,
|
|
2578
|
+
MMEntityProcessor
|
|
2579
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
2505
2580
|
class MMBatchValidationError extends Error {
|
|
2506
2581
|
upsertedEntities;
|
|
2507
2582
|
localDedupeCount;
|
|
@@ -3099,6 +3174,146 @@ function getErrorType(error) {
|
|
|
3099
3174
|
}
|
|
3100
3175
|
return "Error";
|
|
3101
3176
|
}
|
|
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
|
+
}
|
|
3102
3317
|
class LogEntry {
|
|
3103
3318
|
level;
|
|
3104
3319
|
message;
|
|
@@ -3604,9 +3819,497 @@ class OAuthClient {
|
|
|
3604
3819
|
return data;
|
|
3605
3820
|
}
|
|
3606
3821
|
}
|
|
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
|
+
}
|
|
3607
4305
|
const runDataSyncService = async (connectorPath) => {
|
|
3608
4306
|
const config2 = CoreConfiguration.inst();
|
|
3609
4307
|
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
|
+
}
|
|
3610
4313
|
const currentFileUrl = import.meta.url;
|
|
3611
4314
|
const currentFilePath = fileURLToPath(currentFileUrl);
|
|
3612
4315
|
const sdkDistPath = path.dirname(currentFilePath);
|
|
@@ -4249,6 +4952,8 @@ export {
|
|
|
4249
4952
|
combinePsqlDateTime,
|
|
4250
4953
|
convertToLocalTime,
|
|
4251
4954
|
formatDateWithTZOffset,
|
|
4955
|
+
formatError,
|
|
4956
|
+
formatErrorForLogging,
|
|
4252
4957
|
formatPsqlDate,
|
|
4253
4958
|
formatPsqlTime,
|
|
4254
4959
|
f as getCachedTimezoneName,
|