@devx-commerce/plugin-gati 0.0.2-beta.103 → 0.0.2-beta.104

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.
Files changed (25) hide show
  1. package/.medusa/server/src/api/erp/webhook/config.js +1 -1
  2. package/.medusa/server/src/api/erp/webhook/route.js +90 -6
  3. package/.medusa/server/src/commands/bulk-jobs/erp-webhook-batch.js +513 -0
  4. package/.medusa/server/src/commands/utils/command-args.js +85 -0
  5. package/.medusa/server/src/modules/erp-event/migrations/Migration20250908000353.js +14 -0
  6. package/.medusa/server/src/modules/erp-event/migrations/Migration20250908001104.js +16 -0
  7. package/.medusa/server/src/modules/erp-event/models/erp-event.js +3 -1
  8. package/.medusa/server/src/workflows/bulk-jobs-task-trigger/index.js +31 -0
  9. package/.medusa/server/src/workflows/bulk-jobs-task-trigger/steps/create-job-tracking.js +15 -0
  10. package/.medusa/server/src/workflows/bulk-jobs-task-trigger/steps/trigger-ecs-task.js +97 -0
  11. package/.medusa/server/src/workflows/erp-event/index.js +17 -8
  12. package/.medusa/server/src/workflows/erp-event/steps/create-erp-event.js +3 -1
  13. package/.medusa/server/src/workflows/erp-event/steps/index.js +19 -0
  14. package/.medusa/server/src/workflows/erp-event/steps/update-erp-event.js +22 -0
  15. package/.medusa/server/src/workflows/erp-event/workflows/create-erp-event.js +10 -0
  16. package/.medusa/server/src/workflows/erp-event/workflows/index.js +18 -0
  17. package/.medusa/server/src/workflows/erp-event/workflows/update-erp-event.js +10 -0
  18. package/.medusa/server/src/workflows/hooks/product-created.js +7 -9
  19. package/.medusa/server/src/workflows/hooks/product-updated.js +8 -9
  20. package/.medusa/server/src/workflows/hooks/variant-created.js +7 -9
  21. package/.medusa/server/src/workflows/hooks/variant-updated.js +7 -9
  22. package/.medusa/server/src/workflows/notification/index.js +10 -0
  23. package/.medusa/server/src/workflows/notification/steps/create-notification.js +15 -0
  24. package/.medusa/server/src/workflows/types/bulk-export.js +11 -0
  25. package/package.json +2 -1
@@ -0,0 +1,513 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.default = erpWebhookBatchProcessingScript;
5
+ const client_s3_1 = require("@aws-sdk/client-s3");
6
+ const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
7
+ const utils_1 = require("@medusajs/framework/utils");
8
+ const node_http_handler_1 = require("@smithy/node-http-handler");
9
+ const config_1 = require("../../api/erp/webhook/config");
10
+ const update_erp_event_1 = require("../../workflows/erp-event/workflows/update-erp-event");
11
+ const notification_1 = require("../../workflows/notification");
12
+ const command_args_1 = require("../utils/command-args");
13
+ class S3MultipartUploader {
14
+ constructor(bucket, fileKey, s3Client, logger) {
15
+ this.uploadId = null;
16
+ this.parts = [];
17
+ this.currentPart = 1;
18
+ this.currentBuffer = Buffer.alloc(0);
19
+ this.minPartSize = 5 * 1024 * 1024; // 5MB minimum for multipart
20
+ this.totalSize = 0;
21
+ this.s3Client = s3Client;
22
+ this.bucket = bucket;
23
+ this.fileKey = fileKey;
24
+ this.logger = logger;
25
+ }
26
+ async initializeUpload() {
27
+ try {
28
+ const command = new client_s3_1.CreateMultipartUploadCommand({
29
+ Bucket: this.bucket,
30
+ Key: this.fileKey,
31
+ ContentType: "text/csv",
32
+ ContentDisposition: `attachment; filename="${this.fileKey
33
+ .split("/")
34
+ .pop()}"`,
35
+ Metadata: {
36
+ "generated-by": "medusa-erp-batch-processor",
37
+ "created-at": new Date().toISOString(),
38
+ "export-type": "error-report",
39
+ },
40
+ });
41
+ const response = await this.s3Client.send(command);
42
+ this.uploadId = response.UploadId;
43
+ this.logger.info(`S3 multipart upload initialized. UploadId: ${this.uploadId}`);
44
+ }
45
+ catch (error) {
46
+ this.logger.error("Failed to initialize S3 multipart upload", error);
47
+ throw error;
48
+ }
49
+ }
50
+ async writeChunk(csvChunk) {
51
+ if (!this.uploadId) {
52
+ await this.initializeUpload();
53
+ }
54
+ const chunkBuffer = Buffer.from(csvChunk, "utf-8");
55
+ this.currentBuffer = Buffer.concat([this.currentBuffer, chunkBuffer]);
56
+ this.totalSize += chunkBuffer.length;
57
+ // Upload when buffer reaches minimum part size
58
+ if (this.currentBuffer.length >= this.minPartSize) {
59
+ await this.uploadPart();
60
+ }
61
+ }
62
+ async uploadPart() {
63
+ if (this.currentBuffer.length === 0 || !this.uploadId)
64
+ return;
65
+ try {
66
+ this.logger.info(`Uploading part ${this.currentPart}, size: ${(this.currentBuffer.length /
67
+ 1024 /
68
+ 1024).toFixed(2)}MB`);
69
+ const command = new client_s3_1.UploadPartCommand({
70
+ Bucket: this.bucket,
71
+ Key: this.fileKey,
72
+ PartNumber: this.currentPart,
73
+ UploadId: this.uploadId,
74
+ Body: this.currentBuffer,
75
+ });
76
+ const response = await this.s3Client.send(command);
77
+ if (!response.ETag) {
78
+ throw new Error(`Part ${this.currentPart} upload failed: Missing ETag in response`);
79
+ }
80
+ this.parts.push({
81
+ ETag: response.ETag,
82
+ PartNumber: this.currentPart,
83
+ });
84
+ this.logger.info(`Part ${this.currentPart} uploaded successfully, ETag: ${response.ETag}`);
85
+ // Clear buffer and increment part number
86
+ this.currentBuffer = Buffer.alloc(0);
87
+ this.currentPart++;
88
+ }
89
+ catch (error) {
90
+ this.logger.error(`Failed to upload part ${this.currentPart}`, error);
91
+ throw error;
92
+ }
93
+ }
94
+ async finalize() {
95
+ if (!this.uploadId) {
96
+ throw new Error("Upload not initialized");
97
+ }
98
+ try {
99
+ // Upload any remaining data in buffer as final part
100
+ if (this.currentBuffer.length > 0) {
101
+ await this.uploadPart();
102
+ }
103
+ if (this.parts.length === 0) {
104
+ throw new Error("No parts uploaded");
105
+ }
106
+ // Complete multipart upload
107
+ const command = new client_s3_1.CompleteMultipartUploadCommand({
108
+ Bucket: this.bucket,
109
+ Key: this.fileKey,
110
+ UploadId: this.uploadId,
111
+ MultipartUpload: {
112
+ Parts: this.parts.sort((a, b) => a.PartNumber - b.PartNumber),
113
+ },
114
+ });
115
+ const response = await this.s3Client.send(command);
116
+ this.logger.info(`S3 multipart upload completed successfully`);
117
+ this.logger.info(`Location: ${response.Location}`);
118
+ this.logger.info(`Total parts: ${this.parts.length}`);
119
+ this.logger.info(`Total size: ${(this.totalSize / 1024 / 1024).toFixed(2)}MB`);
120
+ return {
121
+ fileId: this.fileKey,
122
+ fileKey: this.fileKey,
123
+ fileSize: this.totalSize,
124
+ };
125
+ }
126
+ catch (error) {
127
+ this.logger.error("Failed to complete S3 multipart upload", error);
128
+ await this.abort();
129
+ throw error;
130
+ }
131
+ }
132
+ async abort() {
133
+ if (!this.uploadId)
134
+ return;
135
+ try {
136
+ const command = new client_s3_1.AbortMultipartUploadCommand({
137
+ Bucket: this.bucket,
138
+ Key: this.fileKey,
139
+ UploadId: this.uploadId,
140
+ });
141
+ await this.s3Client.send(command);
142
+ this.logger.info(`S3 multipart upload aborted: ${this.uploadId}`);
143
+ }
144
+ catch (error) {
145
+ this.logger.error("Failed to abort S3 multipart upload", error);
146
+ }
147
+ }
148
+ }
149
+ async function generateErrorCSV(errors) {
150
+ const csvHeaders = [
151
+ "Batch Index",
152
+ "Batch Size",
153
+ "Failed Items",
154
+ "Error Message",
155
+ "Timestamp",
156
+ ];
157
+ const csvRows = errors.map((error) => [
158
+ error.batchIndex.toString(),
159
+ error.batchSize.toString(),
160
+ `"${error.items.join(", ")}"`, // Wrap in quotes and join items
161
+ `"${error.error.replace(/"/g, '""')}"`, // Escape quotes in error message
162
+ error.timestamp,
163
+ ]);
164
+ const csvContent = [
165
+ csvHeaders.join(","),
166
+ ...csvRows.map((row) => row.join(",")),
167
+ ].join("\n");
168
+ return csvContent;
169
+ }
170
+ async function uploadErrorCSVToS3(container, csvContent, jobId, datafor, operation) {
171
+ const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
172
+ let uploader = null;
173
+ try {
174
+ // Get S3 configuration from environment
175
+ const bucket = process.env.AWS_MEDIA_BUCKET;
176
+ if (!bucket) {
177
+ throw new Error("AWS_MEDIA_BUCKET not configured");
178
+ }
179
+ // Create optimized S3 client
180
+ const s3Client = new client_s3_1.S3Client({
181
+ region: process.env.AWS_REGION || "ap-south-1",
182
+ credentials: {
183
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
184
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
185
+ },
186
+ ...(process.env.S3_ENDPOINT && { endpoint: process.env.S3_ENDPOINT }),
187
+ requestHandler: new node_http_handler_1.NodeHttpHandler({
188
+ connectionTimeout: 30000, // 30s connection timeout
189
+ socketTimeout: 120000, // 2min socket timeout for large uploads
190
+ httpsAgent: {
191
+ keepAlive: true,
192
+ maxSockets: 50, // Connection pooling
193
+ maxFreeSockets: 10,
194
+ keepAliveMsecs: 30000, // Keep connections alive for 30s
195
+ },
196
+ }),
197
+ maxAttempts: 3, // Retry failed requests
198
+ retryMode: "adaptive", // Intelligent retry strategy
199
+ });
200
+ const fileName = `erp-batch-errors-${datafor}-${operation}-${jobId}-${Date.now()}.csv`;
201
+ const fileKey = `bulk-jobs/errors/${fileName}`;
202
+ // Initialize S3 multipart uploader
203
+ uploader = new S3MultipartUploader(bucket, fileKey, s3Client, logger);
204
+ // Upload CSV content in chunks
205
+ logger.info(`Uploading error CSV to S3: ${fileKey}`);
206
+ await uploader.writeChunk(csvContent);
207
+ // Finalize the upload
208
+ const uploadResult = await uploader.finalize();
209
+ logger.info(`Error CSV uploaded successfully: ${uploadResult.fileKey}`);
210
+ logger.info(`File size: ${(uploadResult.fileSize / 1024).toFixed(2)} KB`);
211
+ // Generate presigned download URL
212
+ let downloadUrl;
213
+ try {
214
+ // Generate presigned URL using AWS SDK
215
+ const getObjectCommand = new client_s3_1.GetObjectCommand({
216
+ Bucket: bucket,
217
+ Key: uploadResult.fileKey,
218
+ });
219
+ downloadUrl = await (0, s3_request_presigner_1.getSignedUrl)(s3Client, getObjectCommand, {
220
+ expiresIn: 24 * 60 * 60, // 24 hours expiration
221
+ });
222
+ logger.info(`Generated presigned download URL for: ${uploadResult.fileKey}`);
223
+ logger.info(`URL expires in 24 hours`);
224
+ }
225
+ catch (presignError) {
226
+ // Fallback to direct S3 URL (may not work if bucket is private)
227
+ logger.warn(`Presigned URL generation failed, using direct S3 URL: ${presignError.message}`);
228
+ downloadUrl = `https://${bucket}.s3.${process.env.AWS_REGION || "ap-south-1"}.amazonaws.com/${uploadResult.fileKey}`;
229
+ logger.warn("Using direct S3 URL - this may not work if the bucket is private");
230
+ }
231
+ return { fileKey: uploadResult.fileKey, downloadUrl };
232
+ }
233
+ catch (error) {
234
+ // Abort multipart upload if it was started
235
+ if (uploader) {
236
+ await uploader.abort();
237
+ }
238
+ logger.error("Failed to upload error CSV to S3:", error);
239
+ throw error;
240
+ }
241
+ }
242
+ async function sendErrorNotificationEmail(container, jobId, datafor, operation, totalItems, failedBatches, totalBatches, csvDownloadUrl, csvFileName) {
243
+ const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
244
+ try {
245
+ await (0, notification_1.createNotificationWorkflow)(container).run({
246
+ input: {
247
+ to: "vasu.chapadia@devxconsultancy.com", // Will be filled by notification service
248
+ channel: "email",
249
+ template: "erp-batch-report",
250
+ data: {
251
+ jobId,
252
+ datafor: JSON.stringify(datafor),
253
+ operation,
254
+ totalItems,
255
+ failedBatches,
256
+ totalBatches,
257
+ errorRate: ((failedBatches / totalBatches) * 100).toFixed(2),
258
+ csvFileName,
259
+ csvDownloadUrl,
260
+ },
261
+ },
262
+ });
263
+ logger.info("Error notification email sent successfully");
264
+ }
265
+ catch (error) {
266
+ logger.error("Failed to send error notification email:", error);
267
+ // Don't throw here - we don't want email failure to fail the entire job
268
+ }
269
+ }
270
+ async function logJobStatus(container, jobId, status, errorMessage, result) {
271
+ const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
272
+ logger.info(`Job ${jobId} status: ${status}`);
273
+ if (errorMessage) {
274
+ logger.error(`Job ${jobId} error: ${errorMessage}`);
275
+ }
276
+ if (result) {
277
+ logger.info(`Job ${jobId} result: ${JSON.stringify(result)}`);
278
+ }
279
+ }
280
+ async function processBatch(container, datafor, operation, batch, batchIndex, totalBatches, logger) {
281
+ const errors = [];
282
+ let processed = 0;
283
+ try {
284
+ logger.info(`Processing batch ${batchIndex + 1}/${totalBatches} - ${batch.length} items for ${datafor} ${operation}`);
285
+ const workflowConfig = config_1.MASTER_WORKFLOW_CONFIG[datafor];
286
+ if (!workflowConfig) {
287
+ throw new Error(`Unsupported datafor type: ${datafor}`);
288
+ }
289
+ const workflowHandler = workflowConfig[operation];
290
+ if (!workflowHandler) {
291
+ throw new Error(`Unsupported operation: ${operation} for ${datafor}`);
292
+ }
293
+ const input = (0, config_1.getWorkflowInput)(datafor, operation, batch);
294
+ // Execute the workflow for this batch
295
+ await workflowHandler(container, input);
296
+ processed = batch.length;
297
+ logger.info(`✅ Successfully processed batch ${batchIndex + 1}/${totalBatches} - ${processed} items`);
298
+ return { success: true, processed, errors };
299
+ }
300
+ catch (error) {
301
+ const errorMessage = error instanceof Error ? error.message : String(error);
302
+ errors.push(`Batch ${batchIndex + 1}: ${errorMessage}`);
303
+ logger.error(`❌ Failed to process batch ${batchIndex + 1}/${totalBatches}: ${errorMessage}`);
304
+ const batchError = {
305
+ batchIndex: batchIndex + 1,
306
+ batchSize: batch.length,
307
+ items: batch,
308
+ error: errorMessage,
309
+ timestamp: new Date().toISOString(),
310
+ };
311
+ return { success: false, processed, errors, batchError };
312
+ }
313
+ }
314
+ async function erpWebhookBatchProcessingScript({ container, }) {
315
+ const jobId = (0, command_args_1.getArgString)("job-id", `erp_webhook_batch_job_${Date.now()}`);
316
+ const batchSize = (0, command_args_1.getArgNumber)("batch-size", 200);
317
+ const erpEventId = (0, command_args_1.getArgString)("event-id");
318
+ const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
319
+ if (!erpEventId) {
320
+ logger.error("event-id parameter is required");
321
+ return;
322
+ }
323
+ try {
324
+ const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
325
+ logger.info(`ERP webhook batch processing job started: ${jobId}`);
326
+ logger.info(`Event ID: ${erpEventId}`);
327
+ logger.info(`Batch size: ${batchSize} items per batch`);
328
+ logger.info(`Initial memory usage: ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB`);
329
+ await logJobStatus(container, jobId, "processing");
330
+ // Get the ERP event data
331
+ logger.info("Fetching ERP event data...");
332
+ const startTime = Date.now();
333
+ const { data: [erpEvent], } = await query.graph({
334
+ entity: "erp_event",
335
+ fields: ["id", "datafor", "operation", "data"],
336
+ filters: {
337
+ id: erpEventId,
338
+ },
339
+ pagination: {
340
+ take: 1,
341
+ },
342
+ });
343
+ if (!erpEvent) {
344
+ logger.warn(`No ERP event found with ID: ${erpEventId}`);
345
+ await logJobStatus(container, jobId, "completed", undefined, {
346
+ message: "No ERP event found",
347
+ });
348
+ return;
349
+ }
350
+ const { datafor, operation, data } = erpEvent;
351
+ const totalItems = data.length;
352
+ if (totalItems === 0) {
353
+ logger.warn("No data items to process in the ERP event");
354
+ await logJobStatus(container, jobId, "completed", undefined, {
355
+ message: "No data items to process",
356
+ });
357
+ return;
358
+ }
359
+ logger.info(`Processing ${totalItems} items for ${datafor} ${operation}`);
360
+ // Split data into batches
361
+ const batches = [];
362
+ for (let i = 0; i < totalItems; i += batchSize) {
363
+ batches.push(data.slice(i, i + batchSize));
364
+ }
365
+ const totalBatches = batches.length;
366
+ logger.info(`Split into ${totalBatches} batches of up to ${batchSize} items each`);
367
+ // Process batches
368
+ let totalProcessed = 0;
369
+ let totalErrors = 0;
370
+ const allErrors = [];
371
+ const batchErrors = [];
372
+ for (let i = 0; i < batches.length; i++) {
373
+ const batch = batches[i];
374
+ try {
375
+ const result = await processBatch(container, datafor, operation, batch, i, totalBatches, logger);
376
+ totalProcessed += result.processed;
377
+ if (!result.success) {
378
+ totalErrors++;
379
+ allErrors.push(...result.errors);
380
+ if (result.batchError) {
381
+ batchErrors.push(result.batchError);
382
+ }
383
+ }
384
+ // Log progress every 5 batches
385
+ if ((i + 1) % 5 === 0 || i === totalBatches - 1) {
386
+ const memUsage = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
387
+ logger.info(`Progress: ${i + 1}/${totalBatches} batches, ${totalProcessed}/${totalItems} items processed, Memory: ${memUsage}MB`);
388
+ // Force garbage collection for large jobs (if enabled)
389
+ if (memUsage > 1500 && global.gc) {
390
+ logger.info("High memory usage detected, triggering garbage collection");
391
+ global.gc();
392
+ }
393
+ }
394
+ }
395
+ catch (error) {
396
+ totalErrors++;
397
+ const errorMessage = error instanceof Error ? error.message : String(error);
398
+ allErrors.push(`Batch ${i + 1}: ${errorMessage}`);
399
+ logger.error(`Failed to process batch ${i + 1}: ${errorMessage}`);
400
+ // Add to batch errors for CSV generation
401
+ const batchError = {
402
+ batchIndex: i + 1,
403
+ batchSize: batch.length,
404
+ items: batch,
405
+ error: errorMessage,
406
+ timestamp: new Date().toISOString(),
407
+ };
408
+ batchErrors.push(batchError);
409
+ // Continue processing other batches even if one fails
410
+ continue;
411
+ }
412
+ }
413
+ const processingTime = Date.now() - startTime;
414
+ const throughput = Math.round(totalProcessed / (processingTime / 1000));
415
+ const successRate = totalItems > 0 ? (totalProcessed / totalItems) * 100 : 0;
416
+ logger.info(`Batch processing completed!`);
417
+ logger.info(`Total items: ${totalItems}`);
418
+ logger.info(`Successfully processed: ${totalProcessed}`);
419
+ logger.info(`Failed batches: ${totalErrors}/${totalBatches}`);
420
+ logger.info(`Success rate: ${successRate.toFixed(2)}%`);
421
+ logger.info(`Processing time: ${processingTime}ms`);
422
+ logger.info(`Throughput: ${throughput} items/second`);
423
+ // Generate CSV and send email if there were errors
424
+ let csvDownloadUrl = "";
425
+ let csvFileName = "";
426
+ if (totalErrors > 0 && batchErrors.length > 0) {
427
+ try {
428
+ logger.info(`Generating error CSV for ${batchErrors.length} failed batches...`);
429
+ const csvContent = await generateErrorCSV(batchErrors);
430
+ const uploadResult = await uploadErrorCSVToS3(container, csvContent, jobId, datafor, operation);
431
+ csvDownloadUrl = uploadResult.downloadUrl;
432
+ csvFileName =
433
+ uploadResult.fileKey.split("/").pop() || "error-report.csv";
434
+ logger.info(`Error CSV generated and uploaded: ${csvFileName}`);
435
+ // Send error notification email
436
+ await sendErrorNotificationEmail(container, jobId, datafor, operation, totalItems, totalErrors, totalBatches, csvDownloadUrl, csvFileName);
437
+ logger.info("Error notification email sent with CSV attachment");
438
+ }
439
+ catch (csvError) {
440
+ logger.error("Failed to generate/send error CSV:", csvError);
441
+ // Don't throw here - the main job processing was completed
442
+ }
443
+ }
444
+ // Send completion notification
445
+ const notificationTitle = totalErrors === 0
446
+ ? `✅ ERP Batch Processing Completed Successfully`
447
+ : `⚠️ ERP Batch Processing Completed with Errors`;
448
+ const notificationDescription = totalErrors === 0
449
+ ? `Successfully processed all ${totalProcessed} items for ${datafor} ${operation}`
450
+ : `Processed ${totalProcessed}/${totalItems} items for ${datafor} ${operation}. ${totalErrors} batches failed.${csvFileName ? ` Error report: ${csvFileName}` : ""}`;
451
+ await (0, notification_1.createNotificationWorkflow)(container).run({
452
+ input: {
453
+ to: "",
454
+ channel: "feed",
455
+ template: "admin-ui",
456
+ data: {
457
+ title: notificationTitle,
458
+ description: notificationDescription,
459
+ },
460
+ },
461
+ });
462
+ await logJobStatus(container, jobId, totalErrors === 0 ? "completed" : "completed_with_errors", totalErrors > 0 ? `${totalErrors} batches failed` : undefined, {
463
+ datafor,
464
+ operation,
465
+ totalItems,
466
+ processedItems: totalProcessed,
467
+ failedBatches: totalErrors,
468
+ successRate: successRate.toFixed(2),
469
+ processingTimeMs: processingTime,
470
+ throughputPerSecond: throughput,
471
+ erpEventId,
472
+ errors: allErrors.slice(0, 10), // Only include first 10 errors to avoid log overflow
473
+ });
474
+ await (0, update_erp_event_1.updateErpEventWorkflow)(container).run({
475
+ input: {
476
+ id: erpEventId,
477
+ status: totalErrors > 0 ? "failed" : "completed",
478
+ sync_completed_at: new Date(),
479
+ },
480
+ });
481
+ logger.info(`ERP webhook batch processing job completed: ${jobId}`);
482
+ logger.info(`Final memory usage: ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB`);
483
+ // If there were errors, throw to indicate partial failure
484
+ if (totalErrors > 0) {
485
+ throw new Error(`${totalErrors} out of ${totalBatches} batches failed`);
486
+ }
487
+ }
488
+ catch (error) {
489
+ logger.error("Error in ERP webhook batch processing:", error);
490
+ await logJobStatus(container, jobId, "failed", error.message);
491
+ await (0, update_erp_event_1.updateErpEventWorkflow)(container).run({
492
+ input: {
493
+ id: erpEventId,
494
+ status: "failed",
495
+ sync_completed_at: new Date(),
496
+ },
497
+ });
498
+ // Send failure notification
499
+ await (0, notification_1.createNotificationWorkflow)(container).run({
500
+ input: {
501
+ to: "",
502
+ channel: "feed",
503
+ template: "admin-ui",
504
+ data: {
505
+ title: "❌ ERP Batch Processing Failed",
506
+ description: `ERP batch processing job ${jobId} and event ${erpEventId} failed. Please check logs for details.`,
507
+ },
508
+ },
509
+ });
510
+ throw error;
511
+ }
512
+ }
513
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJwLXdlYmhvb2stYmF0Y2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvY29tbWFuZHMvYnVsay1qb2JzL2VycC13ZWJob29rLWJhdGNoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQXdkQSxrREFzU0M7QUE1dkJELGtEQU80QjtBQUM1Qix3RUFBNkQ7QUFHN0QscURBQXNFO0FBQ3RFLGlFQUE0RDtBQUU1RCx5REFLc0M7QUFDdEMsMkZBQThGO0FBQzlGLCtEQUEwRTtBQUMxRSx3REFBbUU7QUFnQm5FLE1BQU0sbUJBQW1CO0lBWXZCLFlBQ0UsTUFBYyxFQUNkLE9BQWUsRUFDZixRQUFrQixFQUNsQixNQUFXO1FBWkwsYUFBUSxHQUFrQixJQUFJLENBQUM7UUFDL0IsVUFBSyxHQUFnRCxFQUFFLENBQUM7UUFDeEQsZ0JBQVcsR0FBRyxDQUFDLENBQUM7UUFDaEIsa0JBQWEsR0FBVyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hDLGdCQUFXLEdBQVcsQ0FBQyxHQUFHLElBQUksR0FBRyxJQUFJLENBQUMsQ0FBQyw0QkFBNEI7UUFDbkUsY0FBUyxHQUFHLENBQUMsQ0FBQztRQVNwQixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQztRQUN6QixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztJQUN2QixDQUFDO0lBRUQsS0FBSyxDQUFDLGdCQUFnQjtRQUNwQixJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sR0FBRyxJQUFJLHdDQUE0QixDQUFDO2dCQUMvQyxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU07Z0JBQ25CLEdBQUcsRUFBRSxJQUFJLENBQUMsT0FBTztnQkFDakIsV0FBVyxFQUFFLFVBQVU7Z0JBQ3ZCLGtCQUFrQixFQUFFLHlCQUF5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
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
3
+ * Command Line Arguments Utility Functions
4
+ *
5
+ * KISS Principle: Simple, reusable helpers for parsing command line arguments
6
+ * Used across all bulk export commands
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getArgString = getArgString;
10
+ exports.getArgNumber = getArgNumber;
11
+ exports.getArgJSON = getArgJSON;
12
+ exports.getArgBoolean = getArgBoolean;
13
+ /**
14
+ * Get string argument from command line or environment variable
15
+ * @param name - Argument name (e.g., 'user-email')
16
+ * @param fallback - Default value if not found
17
+ * @returns The argument value or fallback
18
+ */
19
+ function getArgString(name, fallback) {
20
+ // Check command line arguments first
21
+ const arg = process.argv.find((arg) => arg.startsWith(`--${name}=`));
22
+ if (arg)
23
+ return arg.split("=")[1];
24
+ // Check environment variable as fallback
25
+ const envVal = process.env[name.toUpperCase().replace(/-/g, "_")];
26
+ if (envVal)
27
+ return envVal;
28
+ return fallback;
29
+ }
30
+ /**
31
+ * Get number argument from command line or environment variable
32
+ * @param name - Argument name (e.g., 'batch-size')
33
+ * @param fallback - Default value if not found or invalid
34
+ * @returns The argument value as number or fallback
35
+ */
36
+ function getArgNumber(name, fallback) {
37
+ // Check command line arguments first
38
+ const arg = process.argv.find((arg) => arg.startsWith(`--${name}=`));
39
+ if (arg) {
40
+ const num = parseInt(arg.split("=")[1], 10);
41
+ if (!Number.isNaN(num))
42
+ return num;
43
+ }
44
+ // Check environment variable as fallback
45
+ const envVal = process.env[name.toUpperCase().replace(/-/g, "_")];
46
+ if (envVal) {
47
+ const num = parseInt(envVal, 10);
48
+ if (!Number.isNaN(num))
49
+ return num;
50
+ }
51
+ return fallback || 200;
52
+ }
53
+ /**
54
+ * Get JSON object argument from command line
55
+ * @param name - Argument name (e.g., 'filters')
56
+ * @param fallback - Default value if not found or invalid JSON
57
+ * @returns Parsed JSON object or fallback
58
+ */
59
+ function getArgJSON(name, fallback) {
60
+ const jsonStr = getArgString(name);
61
+ if (!jsonStr)
62
+ return fallback;
63
+ try {
64
+ return JSON.parse(jsonStr);
65
+ }
66
+ catch (error) {
67
+ console.warn(`Failed to parse JSON argument --${name}: ${error.message}`);
68
+ return fallback;
69
+ }
70
+ }
71
+ /**
72
+ * Get boolean argument from command line
73
+ * @param name - Argument name (e.g., 'dry-run')
74
+ * @param fallback - Default value if not found
75
+ * @returns Boolean value
76
+ */
77
+ function getArgBoolean(name, fallback = false) {
78
+ const value = getArgString(name);
79
+ if (!value)
80
+ return fallback;
81
+ // Handle various boolean representations
82
+ const normalized = value.toLowerCase();
83
+ return normalized === "true" || normalized === "1" || normalized === "yes";
84
+ }
85
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWFuZC1hcmdzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbW1hbmRzL3V0aWxzL2NvbW1hbmQtYXJncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7O0dBS0c7O0FBUUgsb0NBYUM7QUFRRCxvQ0FnQkM7QUFRRCxnQ0FVQztBQVFELHNDQU9DO0FBNUVEOzs7OztHQUtHO0FBQ0gsU0FBZ0IsWUFBWSxDQUMxQixJQUFZLEVBQ1osUUFBaUI7SUFFakIscUNBQXFDO0lBQ3JDLE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLEtBQUssSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3JFLElBQUksR0FBRztRQUFFLE9BQU8sR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUVsQyx5Q0FBeUM7SUFDekMsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ2xFLElBQUksTUFBTTtRQUFFLE9BQU8sTUFBTSxDQUFDO0lBRTFCLE9BQU8sUUFBUSxDQUFDO0FBQ2xCLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQWdCLFlBQVksQ0FBQyxJQUFZLEVBQUUsUUFBaUI7SUFDMUQscUNBQXFDO0lBQ3JDLE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLEtBQUssSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3JFLElBQUksR0FBRyxFQUFFLENBQUM7UUFDUixNQUFNLEdBQUcsR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUM1QyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUM7WUFBRSxPQUFPLEdBQUcsQ0FBQztJQUNyQyxDQUFDO0lBRUQseUNBQXlDO0lBQ3pDLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNsRSxJQUFJLE1BQU0sRUFBRSxDQUFDO1FBQ1gsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNqQyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUM7WUFBRSxPQUFPLEdBQUcsQ0FBQztJQUNyQyxDQUFDO0lBRUQsT0FBTyxRQUFRLElBQUksR0FBRyxDQUFDO0FBQ3pCLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQWdCLFVBQVUsQ0FBVSxJQUFZLEVBQUUsUUFBWTtJQUM1RCxNQUFNLE9BQU8sR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbkMsSUFBSSxDQUFDLE9BQU87UUFBRSxPQUFPLFFBQVEsQ0FBQztJQUU5QixJQUFJLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFNLENBQUM7SUFDbEMsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixPQUFPLENBQUMsSUFBSSxDQUFDLG1DQUFtQyxJQUFJLEtBQUssS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7UUFDMUUsT0FBTyxRQUFRLENBQUM7SUFDbEIsQ0FBQztBQUNILENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQWdCLGFBQWEsQ0FBQyxJQUFZLEVBQUUsUUFBUSxHQUFHLEtBQUs7SUFDMUQsTUFBTSxLQUFLLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2pDLElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxRQUFRLENBQUM7SUFFNUIseUNBQXlDO0lBQ3pDLE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUN2QyxPQUFPLFVBQVUsS0FBSyxNQUFNLElBQUksVUFBVSxLQUFLLEdBQUcsSUFBSSxVQUFVLEtBQUssS0FBSyxDQUFDO0FBQzdFLENBQUMifQ==
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Migration20250908000353 = void 0;
4
+ const migrations_1 = require("@mikro-orm/migrations");
5
+ class Migration20250908000353 extends migrations_1.Migration {
6
+ async up() {
7
+ this.addSql(`alter table if exists "erp_event" add column if not exists "sync_completed_at" timestamptz not null, add column if not exists "status" text check ("status" in ('pending', 'completed', 'failed')) not null default 'pending';`);
8
+ }
9
+ async down() {
10
+ this.addSql(`alter table if exists "erp_event" drop column if exists "sync_completed_at", drop column if exists "status";`);
11
+ }
12
+ }
13
+ exports.Migration20250908000353 = Migration20250908000353;
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTA5MDgwMDAzNTMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9lcnAtZXZlbnQvbWlncmF0aW9ucy9NaWdyYXRpb24yMDI1MDkwODAwMDM1My50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxzREFBa0Q7QUFFbEQsTUFBYSx1QkFBd0IsU0FBUSxzQkFBUztJQUUzQyxLQUFLLENBQUMsRUFBRTtRQUNmLElBQUksQ0FBQyxNQUFNLENBQUMsZ09BQWdPLENBQUMsQ0FBQztJQUNoUCxDQUFDO0lBRVEsS0FBSyxDQUFDLElBQUk7UUFDakIsSUFBSSxDQUFDLE1BQU0sQ0FBQyw4R0FBOEcsQ0FBQyxDQUFDO0lBQzlILENBQUM7Q0FFRjtBQVZELDBEQVVDIn0=