@devx-commerce/plugin-gati 0.0.11 → 0.0.12

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