dcos-core-monalisav2-latam 1.0.0

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 (129) hide show
  1. package/README.md +130 -0
  2. package/index.js +350 -0
  3. package/package.json +52 -0
  4. package/src/auth/handler.js +3 -0
  5. package/src/common/MondelezCastOrder.js +449 -0
  6. package/src/common/utils/AuthSecurity.js +46 -0
  7. package/src/common/utils/account-error-handler.js +279 -0
  8. package/src/common/utils/account-error-helper.js +231 -0
  9. package/src/common/utils/account-properties-handler.js +355 -0
  10. package/src/common/utils/api-response.js +62 -0
  11. package/src/common/utils/aws-services.js +186 -0
  12. package/src/common/utils/constants/account-error-codes.json +801 -0
  13. package/src/common/utils/constants.js +37 -0
  14. package/src/common/utils/convert/MondelezClientsItemsCast.js +52 -0
  15. package/src/common/utils/convert/MondelezInventoryItemsCast.js +15 -0
  16. package/src/common/utils/convert/MondelezOrderStatusCast.js +34 -0
  17. package/src/common/utils/convert/MondelezPricesItemsCast.js +37 -0
  18. package/src/common/utils/cron-ftp-get.js +143 -0
  19. package/src/common/utils/data-tables-helper.js +213 -0
  20. package/src/common/utils/date-range-calculator.js +113 -0
  21. package/src/common/utils/delay.js +17 -0
  22. package/src/common/utils/ftp-sftp.js +320 -0
  23. package/src/common/utils/logger.js +126 -0
  24. package/src/common/utils/nodemailerLib.js +61 -0
  25. package/src/common/utils/product-unit-converter.js +168 -0
  26. package/src/common/utils/schemas-utils.js +101 -0
  27. package/src/common/utils/seller-email-sharing-service.js +441 -0
  28. package/src/common/utils/sftp-utils.js +202 -0
  29. package/src/common/utils/status.js +15 -0
  30. package/src/common/utils/util.js +236 -0
  31. package/src/common/utils/validate-state-order.js +35 -0
  32. package/src/common/utils/validateProviders.js +67 -0
  33. package/src/common/utils/validation-data.js +45 -0
  34. package/src/common/utils/vtex/save-hooks.js +65 -0
  35. package/src/common/utils/vtex/save-schemas.js +65 -0
  36. package/src/common/utils/vtex-hook-handler.js +71 -0
  37. package/src/common/validation/AccountCoordinatesValidation.js +350 -0
  38. package/src/common/validation/GeneralErrorValidation.js +11 -0
  39. package/src/common/validation/MainErrorValidation.js +8 -0
  40. package/src/entities/account.js +639 -0
  41. package/src/entities/clients.js +104 -0
  42. package/src/entities/controlprice.js +196 -0
  43. package/src/entities/controlstock.js +206 -0
  44. package/src/entities/cron.js +77 -0
  45. package/src/entities/cronjob.js +71 -0
  46. package/src/entities/orders.js +195 -0
  47. package/src/entities/sftp-inbound.js +88 -0
  48. package/src/entities/sku.js +220 -0
  49. package/src/entities/taxpromotion.js +249 -0
  50. package/src/functions/account/account-get.js +262 -0
  51. package/src/functions/account/account-handler.js +299 -0
  52. package/src/functions/account/clients.js +10 -0
  53. package/src/functions/account/index.js +208 -0
  54. package/src/functions/actions/save-promotions-order-history.js +324 -0
  55. package/src/functions/affiliates/affiliates-hook-consumer.js +87 -0
  56. package/src/functions/affiliates/affiliates-hook-producer.js +45 -0
  57. package/src/functions/clients/clients-audience.js +62 -0
  58. package/src/functions/clients/clients-consumer.js +648 -0
  59. package/src/functions/clients/clients-producer.js +362 -0
  60. package/src/functions/clients/clients-suggested-product-consumer.js +166 -0
  61. package/src/functions/clients/helpers/suggested-product-mdlz.js +233 -0
  62. package/src/functions/clients_peru/email.html +129 -0
  63. package/src/functions/clients_peru/splitfile.js +357 -0
  64. package/src/functions/clients_peru/updateClients.js +1334 -0
  65. package/src/functions/clients_peru/utils.js +243 -0
  66. package/src/functions/cronjobs/cron-jobs-manager.js +40 -0
  67. package/src/functions/cronjobs/cron-jobs.js +171 -0
  68. package/src/functions/crons/cron.js +39 -0
  69. package/src/functions/distributors/distributor-handler.js +81 -0
  70. package/src/functions/distributors/distributor.js +535 -0
  71. package/src/functions/distributors/index.js +60 -0
  72. package/src/functions/financialpolicy/assign-financialpolicy.js +111 -0
  73. package/src/functions/financialpolicy/get-financialpolicy.js +91 -0
  74. package/src/functions/financialpolicy/index.js +28 -0
  75. package/src/functions/inventory/catalog-sync-consumer.js +17 -0
  76. package/src/functions/inventory/catalog-sync-handler.js +311 -0
  77. package/src/functions/inventory/inventory-consumer.js +119 -0
  78. package/src/functions/inventory/inventory-producer.js +197 -0
  79. package/src/functions/multiPresentation/multipre-queue.js +155 -0
  80. package/src/functions/multiPresentation/multipres.js +459 -0
  81. package/src/functions/nodeflow/index.js +83 -0
  82. package/src/functions/nodeflow/nodeflow-cron.js +200 -0
  83. package/src/functions/nodeflow/nodeflow-pub.js +203 -0
  84. package/src/functions/nodeflow/nodeflow-pvt.js +266 -0
  85. package/src/functions/notifications/download-leads-handler.js +67 -0
  86. package/src/functions/notifications/new-leads-notification-consumer.js +17 -0
  87. package/src/functions/notifications/new-leads-notification-handler.js +359 -0
  88. package/src/functions/notifications/order-status-notification-handler.js +482 -0
  89. package/src/functions/notifications/promotion-notification-handler.js +193 -0
  90. package/src/functions/orders/index.js +32 -0
  91. package/src/functions/orders/orders-cancel-handler.js +74 -0
  92. package/src/functions/orders/orders-handler.js +280 -0
  93. package/src/functions/orders/orders-hook-consumer.js +137 -0
  94. package/src/functions/orders/orders-hook-producer.js +170 -0
  95. package/src/functions/orders/orders-notifications-handler.js +137 -0
  96. package/src/functions/orders/orders-status-consumer.js +461 -0
  97. package/src/functions/orders/orders-status-producer.js +443 -0
  98. package/src/functions/prices/index.js +75 -0
  99. package/src/functions/prices/prices-consumer.js +236 -0
  100. package/src/functions/prices/prices-producer.js +323 -0
  101. package/src/functions/prices/promotion-and-tax.js +1284 -0
  102. package/src/functions/routesflow/assign-routeflow-queue.js +77 -0
  103. package/src/functions/schemas/vtex/handle-schemas.js +102 -0
  104. package/src/functions/security/process_gas.js +221 -0
  105. package/src/functions/security/security-handler.js +950 -0
  106. package/src/functions/sftp/sftp-consumer.js +453 -0
  107. package/src/functions/sftpIntegrations/processes/redirectServices.js +184 -0
  108. package/src/functions/sftpIntegrations/processes/validateFileSchema.js +226 -0
  109. package/src/functions/sftpIntegrations/schemas/credential-schema.js +123 -0
  110. package/src/functions/sftpIntegrations/schemas/record-schema.js +131 -0
  111. package/src/functions/sftpIntegrations/schemas/sftp_required_fields.json +3 -0
  112. package/src/functions/sftpIntegrations/sftp-config-producer.js +112 -0
  113. package/src/functions/sftpIntegrations/sftp-consumer.js +700 -0
  114. package/src/functions/sftpIntegrations/test/validateFile.test.js +122 -0
  115. package/src/functions/sftpIntegrations/utils/connect-dynamo.js +29 -0
  116. package/src/functions/sftpIntegrations/utils/split-data.js +25 -0
  117. package/src/functions/utils/index.js +130 -0
  118. package/src/functions/vtex/vtex-helpers.js +694 -0
  119. package/src/integrations/accountErrors/AccountErrorManager.js +437 -0
  120. package/src/integrations/audience/Audience.js +70 -0
  121. package/src/integrations/financialPolicy/FinancialPolicyApi.js +377 -0
  122. package/src/integrations/index.js +0 -0
  123. package/src/integrations/mobilvendor/MobilvendorApi.js +405 -0
  124. package/src/integrations/productmultipresentation/ProductMultiPresentation.js +200 -0
  125. package/src/mdlz/auth/SecretManagerApi.js +77 -0
  126. package/src/mdlz/client/MdlzApi.js +70 -0
  127. package/src/vtex/clients/ProvidersApi.js +51 -0
  128. package/src/vtex/clients/VtexApi.js +511 -0
  129. package/src/vtex/models/VtexOrder.js +87 -0
@@ -0,0 +1,482 @@
1
+ const VtexApi = require('../../vtex/clients/VtexApi');
2
+ const ProviderApi = require('../../vtex/clients/ProvidersApi');
3
+ const {
4
+ isObjectNotEmpty,
5
+ validateURL,
6
+ processStringOrObject,
7
+ evaluateCondition
8
+ } = require('../../common/utils/validateProviders');
9
+ const Logger = require("../../common/utils/logger");
10
+
11
+ // Constants
12
+ const HTTP_STATUS = {
13
+ OK: 200,
14
+ BAD_REQUEST: 400,
15
+ NOT_FOUND: 404,
16
+ NO_CONTENT: 204,
17
+ INTERNAL_SERVER_ERROR: 500,
18
+ BAD_GATEWAY: 502
19
+ };
20
+
21
+ const ERROR_MESSAGES = {
22
+ NO_RECORDS: 'No records found in event',
23
+ INVALID_JSON: 'Invalid JSON in record body',
24
+ MISSING_ACCOUNT_CONFIG: 'Missing AccountConfig or credentials',
25
+ MISSING_ORDER_ID: 'Missing orderInfo.OrderId',
26
+ VTEX_FETCH_ERROR: 'Error fetching order from VTEX',
27
+ ORDER_NOT_FOUND: 'Order info not found in VTEX response',
28
+ NOTIFICATIONS_INACTIVE: 'Notifications not active or no providers configured',
29
+ NO_NOTIFICATIONS: 'No notifications to send',
30
+ PROVIDERS_FAILED: 'Failed to process provider notifications',
31
+ PROCESSING_ERROR: 'Error processing record'
32
+ };
33
+
34
+ /**
35
+ * Main handler for order status notifications
36
+ * Processes SQS records containing order information and sends notifications to configured providers
37
+ */
38
+ module.exports.handler = async (event) => {
39
+
40
+ try {
41
+ const validationResult = validateEvent(event);
42
+ if (!validationResult.isValid) {
43
+ Logger.error(validationResult.error);
44
+ return createErrorResponse(HTTP_STATUS.BAD_REQUEST, validationResult.error);
45
+ }
46
+
47
+ const results = await processRecords(event.Records);
48
+ return createSuccessResponse(results);
49
+
50
+ } catch (error) {
51
+ Logger.error(ERROR_MESSAGES.PROCESSING_ERROR, error);
52
+ return createErrorResponse(HTTP_STATUS.INTERNAL_SERVER_ERROR, error.message);
53
+ }
54
+ };
55
+
56
+ /**
57
+ * Validates the incoming event structure
58
+ */
59
+ function validateEvent(event) {
60
+ if (!event || !Array.isArray(event.Records) || event.Records.length === 0) {
61
+ return { isValid: false, error: ERROR_MESSAGES.NO_RECORDS };
62
+ }
63
+ return { isValid: true };
64
+ }
65
+
66
+ /**
67
+ * Processes all records in the event
68
+ */
69
+ async function processRecords(records) {
70
+
71
+ const results = {
72
+ processed: 0,
73
+ errors: 0,
74
+ notifications: 0
75
+ };
76
+
77
+ for (const record of records) {
78
+ try {
79
+ const result = await processSingleRecord(record);
80
+ results.processed++;
81
+
82
+ if (result.notificationsSent > 0) {
83
+ results.notifications += result.notificationsSent;
84
+ }
85
+ } catch (error) {
86
+ Logger.error('Error processing record', error);
87
+ results.errors++;
88
+ }
89
+ }
90
+
91
+ return results;
92
+ }
93
+
94
+ /**
95
+ * Processes a single SQS record
96
+ */
97
+ async function processSingleRecord(record) {
98
+ Logger.debug(`Processing record: ${record.body}`);
99
+
100
+ const order = parseOrderData(record.body);
101
+ Logger.setAccount(order?.accountConfig);
102
+ if (!order) {
103
+ throw new Error(ERROR_MESSAGES.INVALID_JSON);
104
+ }
105
+
106
+ const accountConfig = validateAccountConfig(order.accountConfig);
107
+ const orderInfo = await fetchOrderFromVtex(order, accountConfig);
108
+ const responseClientData = await getEmailClientVTEX(accountConfig, orderInfo?.clientProfileData?.email);
109
+ const orderState = order?.orderInfo?.State;
110
+ const client = responseClientData?.data;
111
+
112
+ const notificationResult = await processNotifications(orderInfo, accountConfig, client, orderState);
113
+
114
+ return {
115
+ orderId: orderInfo.OrderId,
116
+ notificationsSent: notificationResult.successCount
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Safely parses JSON from record body
122
+ */
123
+ function parseOrderData(body) {
124
+ try {
125
+ return JSON.parse(body);
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Validates account configuration
133
+ */
134
+ function validateAccountConfig(accountConfig) {
135
+ if (!accountConfig || !accountConfig.AccountName || !accountConfig.Credentials) {
136
+ throw new Error(ERROR_MESSAGES.MISSING_ACCOUNT_CONFIG);
137
+ }
138
+ return accountConfig;
139
+ }
140
+
141
+ /**
142
+ * Fetches order information from VTEX API
143
+ */
144
+ async function fetchOrderFromVtex(order, accountConfig) {
145
+ const orderId = order?.orderInfo?.OrderId;
146
+ if (!orderId) {
147
+ throw new Error(ERROR_MESSAGES.MISSING_ORDER_ID);
148
+ }
149
+
150
+ const vtexApi = new VtexApi(
151
+ accountConfig.AccountName,
152
+ accountConfig.Credentials.key,
153
+ accountConfig.Credentials.token
154
+ );
155
+
156
+ try {
157
+ const response = await vtexApi.fetch(`/oms/pvt/orders/${orderId}`, { method: "GET" });
158
+ Logger.debug("VTEX Order Response:", response?.data);
159
+
160
+ if (!response?.data) {
161
+ throw new Error(ERROR_MESSAGES.ORDER_NOT_FOUND, { orderId });
162
+ }
163
+
164
+ return response.data;
165
+ } catch (error) {
166
+ Logger.error(ERROR_MESSAGES.VTEX_FETCH_ERROR, error);
167
+ throw new Error(`${ERROR_MESSAGES.VTEX_FETCH_ERROR}: ${error.message}`);
168
+ }
169
+ }
170
+
171
+ async function getEmailClientVTEX(accountConfig, emailAlias) {
172
+
173
+ const vtexApi = new VtexApi(
174
+ accountConfig.AccountName,
175
+ accountConfig.Credentials.key,
176
+ accountConfig.Credentials.token
177
+ );
178
+
179
+ if (emailAlias) {
180
+ try {
181
+ const responseParentDataEmail = await vtexApi.fetchOrderMail(emailAlias);
182
+ const email = responseParentDataEmail?.data?.email;
183
+ const idCT = (email)?.split("+")[1]?.split("@")[0].toString();
184
+
185
+ if (idCT) {
186
+ return await vtexApi.fetch(`/dataentities/${accountConfig?.AcronymClientVtex}/documents/${idCT}?_fields=_all`, { method: "GET" });
187
+ }else{
188
+ return null;
189
+ }
190
+ } catch (error) {
191
+ Logger.error(error);
192
+ return null;
193
+ }
194
+ }else{
195
+ Logger.error("Email alias is not provided");
196
+ return null;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Processes notifications for the order
202
+ */
203
+ async function processNotifications(orderInfo, accountConfig, client, orderState) {
204
+ const notificationConfig = accountConfig?.NotificationsVTEX;
205
+
206
+ if (!isNotificationConfigValid(notificationConfig)) {
207
+ Logger.info(ERROR_MESSAGES.NOTIFICATIONS_INACTIVE);
208
+ return { successCount: 0 };
209
+ }
210
+
211
+ Logger.debug('Preparing notifications', {
212
+ isActive: notificationConfig.isActive,
213
+ providerCount: notificationConfig.NotificationProviders.length
214
+ });
215
+
216
+ const requests = await buildNotificationRequests(
217
+ notificationConfig.NotificationProviders,
218
+ orderInfo,
219
+ orderState,
220
+ client,
221
+ Logger
222
+ );
223
+
224
+ if (requests.length === 0) {
225
+ Logger.info(ERROR_MESSAGES.NO_NOTIFICATIONS);
226
+ return { successCount: 0 };
227
+ }
228
+
229
+ const successCount = await sendNotificationRequests(requests, Logger);
230
+ return { successCount };
231
+ }
232
+
233
+ /**
234
+ * Validates notification configuration
235
+ */
236
+ function isNotificationConfigValid(config) {
237
+ return config?.isActive &&
238
+ Array.isArray(config.NotificationProviders) &&
239
+ config.NotificationProviders.length > 0;
240
+ }
241
+
242
+ /**
243
+ * Builds notification requests for all configured providers
244
+ */
245
+ async function buildNotificationRequests(providers, orderData, orderStatus, client, logger) {
246
+ if (!Array.isArray(providers) || providers.length === 0) {
247
+ return [];
248
+ }
249
+
250
+ const requests = [];
251
+
252
+ for (const provider of providers) {
253
+ try {
254
+ const request = await buildSingleProviderRequest(provider, orderData, orderStatus, client, logger);
255
+ if (request) {
256
+ requests.push(request);
257
+ }
258
+ } catch (error) {
259
+ logger.error(`Error building request for provider ${provider.Name}`, error);
260
+ }
261
+ }
262
+
263
+ return requests;
264
+ }
265
+
266
+ /**
267
+ * Builds a single provider notification request
268
+ */
269
+ async function buildSingleProviderRequest(provider, orderData, orderStatus, client, logger) {
270
+ const { Name, Method, Path: URL, Headers, Status } = provider;
271
+
272
+ logger.info(`Building request for provider: ${provider}`);
273
+
274
+ const url = validateURL(URL);
275
+ if (!url) {
276
+ return null;
277
+ }
278
+
279
+ const notificationPayload = await getNotificationPayload(Status, orderStatus, orderData, client, logger);
280
+ if (!isObjectNotEmpty(notificationPayload)) {
281
+ return null;
282
+ }
283
+
284
+ const headers = parseHeaders(Headers, Name);
285
+
286
+ return {
287
+ data: {
288
+ name: Name,
289
+ url: url,
290
+ method: Method || 'GET',
291
+ headers: headers,
292
+ },
293
+ status: notificationPayload
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Parses and validates headers configuration
299
+ */
300
+ function parseHeaders(headers, providerName) {
301
+ try {
302
+ if (typeof headers === 'string') {
303
+ return eval(headers);
304
+ }
305
+ return headers || {};
306
+ } catch (error) {
307
+ return {};
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Generates notification payload based on order status
313
+ */
314
+ async function getNotificationPayload(statusMap, orderStatus, orderData, client, logger) {
315
+
316
+ logger.debug(`Getting notification payload for status: ${orderStatus}`, { statusMap });
317
+
318
+ if (!isObjectNotEmpty(statusMap)) {
319
+ return {};
320
+ }
321
+
322
+ try {
323
+ if (!statusMap.hasOwnProperty(orderStatus)) {
324
+ return {};
325
+ }
326
+
327
+ let payload = statusMap[orderStatus];
328
+
329
+ if (typeof payload !== 'object' || !isObjectNotEmpty(payload)) {
330
+ logger.info(`Payload for status ${orderStatus} is empty or invalid`);
331
+ return {};
332
+ }
333
+
334
+ // Evaluate conditions if present
335
+ if ('conditions' in payload) {
336
+ const conditionResult = evaluateCondition(payload.conditions, orderData, client);
337
+ if (!conditionResult) {
338
+ return {};
339
+ }
340
+ }
341
+
342
+ // Process dynamic query and body
343
+ payload = processDynamicFields(payload, orderData, client, logger);
344
+ logger.debug('Processed payload:', payload);
345
+
346
+ return payload;
347
+ } catch (error) {
348
+ return {};
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Processes dynamic fields in the payload
354
+ */
355
+ function processDynamicFields(payload, orderData, client, logger) {
356
+
357
+ logger.debug('Processing dynamic fields in payload', { payload });
358
+
359
+ const processedPayload = { ...payload };
360
+
361
+ if ('query' in processedPayload) {
362
+ processedPayload.query = processStringOrObject(processedPayload, 'query', orderData, client);
363
+ }
364
+
365
+ if ('body' in processedPayload) {
366
+ processedPayload.body = processStringOrObject(processedPayload, 'body', orderData, client);
367
+ }
368
+
369
+ return processedPayload;
370
+ }
371
+
372
+ /**
373
+ * Sends notification requests to all providers
374
+ */
375
+ async function sendNotificationRequests(requests, logger) {
376
+ if (!Array.isArray(requests) || requests.length === 0) {
377
+ logger.error('No requests to process');
378
+ return 0;
379
+ }
380
+
381
+ let successCount = 0;
382
+
383
+ for (const request of requests) {
384
+ try {
385
+ const success = await sendSingleNotification(request, logger);
386
+ if (success) {
387
+ successCount++;
388
+ }
389
+ } catch (error) {
390
+ logger.error(`Error sending notification for ${request.data.name}`, error);
391
+ }
392
+ }
393
+
394
+ return successCount;
395
+ }
396
+
397
+ /**
398
+ * Sends a single notification request
399
+ */
400
+ async function sendSingleNotification(request, logger) {
401
+ const { data: requestData, status: requestStatus } = request;
402
+
403
+ if (!isObjectNotEmpty(requestData) || !isObjectNotEmpty(requestStatus)) {
404
+ logger.error('Request data or status is empty, skipping');
405
+ return false;
406
+ }
407
+
408
+ const url = buildNotificationUrl(requestData.url, requestStatus);
409
+ if (!url) {
410
+ logger.error(`Invalid URL for provider ${requestData.name}`);
411
+ return false;
412
+ }
413
+
414
+ try {
415
+ const apiClient = new ProviderApi();
416
+ await apiClient.fetch(url, {
417
+ name: requestData.name || 'PROVIDER',
418
+ method: requestData.method || 'GET',
419
+ headers: requestData.headers || {},
420
+ body: JSON.stringify(requestStatus.body) || {}
421
+ });
422
+
423
+ logger.debug(`Successfully sent notification to ${requestData.name}`);
424
+ return true;
425
+ } catch (error) {
426
+ logger.error(`Failed to send notification to ${requestData.name} at ${requestData.url}`, error);
427
+ return false;
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Builds the complete notification URL with query parameters
433
+ */
434
+ function buildNotificationUrl(baseUrl, requestStatus) {
435
+ if (typeof baseUrl !== 'string') {
436
+ return null;
437
+ }
438
+
439
+ let url = baseUrl;
440
+
441
+ // Add path if present
442
+ if (requestStatus?.path) {
443
+ url += requestStatus.path;
444
+ }
445
+
446
+ // Add query parameters if present
447
+ if (requestStatus?.query) {
448
+ const queryString = new URLSearchParams(requestStatus.query).toString();
449
+ if (queryString) {
450
+ url += `?${queryString}`;
451
+ }
452
+ }
453
+
454
+ return url;
455
+ }
456
+
457
+ /**
458
+ * Creates a standardized error response
459
+ */
460
+ function createErrorResponse(statusCode, message) {
461
+ return {
462
+ statusCode,
463
+ body: JSON.stringify({ error: message })
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Creates a standardized success response
469
+ */
470
+ function createSuccessResponse(results) {
471
+ return {
472
+ statusCode: HTTP_STATUS.OK,
473
+ body: JSON.stringify({
474
+ message: 'Notifications processed successfully',
475
+ results: {
476
+ processed: results.processed,
477
+ errors: results.errors,
478
+ notificationsSent: results.notifications
479
+ }
480
+ })
481
+ };
482
+ }
@@ -0,0 +1,193 @@
1
+ const { validate } = require('validate.js');
2
+ const AccountData = require('../../entities/account');
3
+ const ApiResponse = require('../../common/utils/api-response');
4
+ const VtexApi = require('../../vtex/clients/VtexApi');
5
+ const Logger = require("../../common/utils/logger");
6
+
7
+ const { STAGE = 'dev' } = process?.env ?? {};
8
+
9
+ module.exports.handler = async (event, context) => {
10
+ const { awsRequestId } = context;
11
+
12
+ try {
13
+ let { body } = event;
14
+ body = JSON.parse(body);
15
+
16
+ // Condicional (middleware) para despliegue del hook en VTEX
17
+ if (body?.hookConfig && body?.hookConfig == 'ping') {
18
+ return ApiResponse.response(200, { hook: 'OK' });
19
+ }
20
+
21
+ // Se valida el cuerpo de la petición
22
+ let errors = validateHookBody(body);
23
+ if (errors) {
24
+ Logger.error(errors);
25
+ return ApiResponse.response(400, { errors });
26
+ }
27
+
28
+ // Se extrae los datos enviados por el Hook de VTEX
29
+ const { OrderId, State, Origin } = body;
30
+ const { Account, Key } = Origin;
31
+
32
+ // Consulta de datos configurados
33
+ const accountConfig = await AccountData.getAccountDataByAccountName(Account);
34
+ if (!accountConfig) {
35
+ return ApiResponse.response(400, 'Account not found');
36
+ }
37
+ Logger.setAccount(accountConfig);
38
+ // Se desestructura las variables de configuración asociadas al account 'Account'
39
+ const { Credentials, PromotionNotificationStock } = accountConfig;
40
+ if (!Credentials || !PromotionNotificationStock) {
41
+ return ApiResponse.response(400, 'Credentials or PromotionNotificationStock parameters not found');
42
+ }
43
+
44
+ // Credenciales VTEX asociadas al account
45
+ const { key, token } = Credentials;
46
+ // Parámetros de la integración 'PromotionNotificationStock'
47
+ const { isActive = false, vtexAppKey, customDataId, entityName, schema = 'default' } = PromotionNotificationStock;
48
+
49
+ // Se valida si la integración se encuentra activa
50
+ if (!isActive) {
51
+ let message = `The promotion notification process is not active for the account ${Account}`;
52
+ return ApiResponse.response(400, { errors: message });
53
+ }
54
+ // Se valida si el AppKey del Hook concuerda con el valor parametrizado
55
+ if (Key !== vtexAppKey) {
56
+ let message = `The VTEX App Key ${Key} not apply in this hook`;
57
+ return ApiResponse.response(400, { errors: message });
58
+ }
59
+
60
+ // Consulta de los datos de la orden en VTEX
61
+ const apiClient = new VtexApi(Account, key, token);
62
+ let vtexOrder = await apiClient.fetch(`/oms/pvt/orders/${OrderId}`, {}, false).then(response => response.data);
63
+
64
+ // Se desestructura los datos de la orden
65
+ let { merchantName, ratesAndBenefitsData, customData } = vtexOrder;
66
+ let promotionsData = {}, promotions = [];
67
+
68
+ // Se obtiene la información de las promociones aplicadas a la orden
69
+ let orderPromotions = {};
70
+ if (ratesAndBenefitsData?.rateAndBenefitsIdentifiers) {
71
+ for (let promotion of ratesAndBenefitsData.rateAndBenefitsIdentifiers) {
72
+ const { id, name, description } = promotion;
73
+ if (!orderPromotions[id]) {
74
+ orderPromotions[id] = { name, description };
75
+ }
76
+ }
77
+ }
78
+ Logger.debug('Promotions in order ' + OrderId, orderPromotions);
79
+
80
+ // Se valida la información de stock no disponible
81
+ if (customData?.customApps) {
82
+ Logger.debug('customData for order ' + OrderId, customData.customApps);
83
+ for (let itemData of customData.customApps) {
84
+ const { id, fields } = itemData;
85
+ if (id == customDataId) {
86
+ let { skusWithoutStock = '', promotionsWithoutStock = '' } = fields;
87
+
88
+ // Se separa la lista de promociones y SKU's asociados
89
+ promotionsWithoutStock = promotionsWithoutStock.split(',');
90
+ skusWithoutStock = skusWithoutStock.split(';');
91
+
92
+ if (promotionsWithoutStock.length !== skusWithoutStock.length) {
93
+ Logger.warn(`Promotions count (${promotionsWithoutStock.length}) doesn't match with the sku items count (${skusWithoutStock.length})`);
94
+ } else {
95
+ // Se recorre la lista de promociones
96
+ promotionsWithoutStock.forEach((promotionId, index) => {
97
+ Logger.debug(`Validating data for promotion ${promotionId}`);
98
+ if (promotionId && orderPromotions[promotionId]) {
99
+ const { name, description } = orderPromotions[promotionId];
100
+
101
+ // Se valida si existe lista de SKU asociados a la promoción
102
+ if (skusWithoutStock[index]) {
103
+ let skus = skusWithoutStock[index].split(',');
104
+ Logger.debug(`SKU items: `, skus);
105
+ if (skus.length) {
106
+ // Se agrupa los datos de las promociones por Id
107
+ if (!promotionsData.hasOwnProperty(promotionId)) {
108
+ promotionsData[promotionId] = { name, description, stock: {} };
109
+ }
110
+
111
+ // Se agrupan los SKU's asociados a la promoción
112
+ for (let sku of skus) {
113
+ if (!promotionsData[promotionId].stock.hasOwnProperty(sku)) {
114
+ promotionsData[promotionId].stock[sku] = true;
115
+ }
116
+ }
117
+ }
118
+ } else {
119
+ Logger.error(`SKU items is not defined for the promotion ${promotionId}`);
120
+ }
121
+ } else {
122
+ Logger.error(`Promotion id '${promotionId}' not found in the order ${OrderId}`);
123
+ }
124
+ });
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ // Se valida si existen promociones sin SKU asociados
131
+ if (Object.keys(promotionsData).length) {
132
+ // Insertar el registro en VTEX
133
+ for (let promotionId in promotionsData) {
134
+ const { name, description, stock } = promotionsData[promotionId];
135
+ promotions.push({
136
+ id: promotionId,
137
+ name, description,
138
+ skus: Object.keys(stock)
139
+ });
140
+ }
141
+
142
+ // Insertar el registro en la entidad de Master Data V2;
143
+ // para que de allí se dispare el trigger que hace el envío del correo electrónico.
144
+ await apiClient.fetch(`/dataentities/${entityName}/documents?_schema=${schema}`, {
145
+ method: 'PATCH',
146
+ data: { id: OrderId, merchantName, promotions }
147
+ }, false).then(response => {
148
+ const { data, status } = response;
149
+ Logger.debug({ status, data });
150
+ }).catch(Logger.error);
151
+ }
152
+
153
+ return ApiResponse.response(200, {
154
+ orderId: OrderId,
155
+ merchantName,
156
+ promotions
157
+ });
158
+ } catch (ex) {
159
+ Logger.error(ex);
160
+ return ApiResponse.response(500, { 'error': ex.message, awsRequestId });
161
+ }
162
+ };
163
+
164
+ function validateHookBody(body) {
165
+ return validate(body, {
166
+ 'Domain': {
167
+ 'presence': { allowEmpty: false },
168
+ 'inclusion': ['Marketplace']
169
+ },
170
+ 'OrderId': {
171
+ 'presence': { allowEmpty: false },
172
+ 'format': {
173
+ 'pattern': '^\\d{13}-01$',
174
+ 'message': function (value, attribute, validatorOptions, attributes, globalOptions) {
175
+ const { pattern } = validatorOptions;
176
+ return `'${value}' doesn't match with the pattern '${pattern}'`;
177
+ }
178
+ },
179
+ },
180
+ 'State': {
181
+ 'presence': { allowEmpty: false }
182
+ },
183
+ 'Origin': {
184
+ 'presence': { allowEmpty: false }
185
+ },
186
+ 'Origin.Account': {
187
+ 'presence': { allowEmpty: false }
188
+ },
189
+ 'Origin.Key': {
190
+ 'presence': { allowEmpty: false }
191
+ }
192
+ })
193
+ }
@@ -0,0 +1,32 @@
1
+
2
+ const ApiResponse = require("../../common/utils/api-response");
3
+ const get_order = require("./orders-handler");
4
+ const cancel_order = require("./orders-cancel-handler");
5
+
6
+ module.exports.handler= async (req,res)=>{
7
+ const resource= req?.resource ?? null;
8
+ const method= req?.httpMethod ? String(req?.httpMethod).toUpperCase() : null;
9
+
10
+ if(resource && method){
11
+ switch(resource){
12
+ case "/orders/id/{orderId}":
13
+ switch(method){
14
+ case "GET":
15
+ return await get_order?.getOrders(req,res);
16
+ default:
17
+ return ApiResponse.response(400, {error: "error method!."});
18
+ }
19
+ case "/orders/cancel/{orderId}":
20
+ switch(method){
21
+ case "POST":
22
+ return await cancel_order?.cancel(req,res);
23
+ default:
24
+ return ApiResponse.response(400, {error: "error method!."});
25
+ }
26
+ default:
27
+ return ApiResponse.response(400, {error: "error resource!."});
28
+ }
29
+ }else{
30
+ return ApiResponse.response(400, {error: "error resource!."});
31
+ }
32
+ }