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.
- package/README.md +130 -0
- package/index.js +350 -0
- package/package.json +52 -0
- package/src/auth/handler.js +3 -0
- package/src/common/MondelezCastOrder.js +449 -0
- package/src/common/utils/AuthSecurity.js +46 -0
- package/src/common/utils/account-error-handler.js +279 -0
- package/src/common/utils/account-error-helper.js +231 -0
- package/src/common/utils/account-properties-handler.js +355 -0
- package/src/common/utils/api-response.js +62 -0
- package/src/common/utils/aws-services.js +186 -0
- package/src/common/utils/constants/account-error-codes.json +801 -0
- package/src/common/utils/constants.js +37 -0
- package/src/common/utils/convert/MondelezClientsItemsCast.js +52 -0
- package/src/common/utils/convert/MondelezInventoryItemsCast.js +15 -0
- package/src/common/utils/convert/MondelezOrderStatusCast.js +34 -0
- package/src/common/utils/convert/MondelezPricesItemsCast.js +37 -0
- package/src/common/utils/cron-ftp-get.js +143 -0
- package/src/common/utils/data-tables-helper.js +213 -0
- package/src/common/utils/date-range-calculator.js +113 -0
- package/src/common/utils/delay.js +17 -0
- package/src/common/utils/ftp-sftp.js +320 -0
- package/src/common/utils/logger.js +126 -0
- package/src/common/utils/nodemailerLib.js +61 -0
- package/src/common/utils/product-unit-converter.js +168 -0
- package/src/common/utils/schemas-utils.js +101 -0
- package/src/common/utils/seller-email-sharing-service.js +441 -0
- package/src/common/utils/sftp-utils.js +202 -0
- package/src/common/utils/status.js +15 -0
- package/src/common/utils/util.js +236 -0
- package/src/common/utils/validate-state-order.js +35 -0
- package/src/common/utils/validateProviders.js +67 -0
- package/src/common/utils/validation-data.js +45 -0
- package/src/common/utils/vtex/save-hooks.js +65 -0
- package/src/common/utils/vtex/save-schemas.js +65 -0
- package/src/common/utils/vtex-hook-handler.js +71 -0
- package/src/common/validation/AccountCoordinatesValidation.js +350 -0
- package/src/common/validation/GeneralErrorValidation.js +11 -0
- package/src/common/validation/MainErrorValidation.js +8 -0
- package/src/entities/account.js +639 -0
- package/src/entities/clients.js +104 -0
- package/src/entities/controlprice.js +196 -0
- package/src/entities/controlstock.js +206 -0
- package/src/entities/cron.js +77 -0
- package/src/entities/cronjob.js +71 -0
- package/src/entities/orders.js +195 -0
- package/src/entities/sftp-inbound.js +88 -0
- package/src/entities/sku.js +220 -0
- package/src/entities/taxpromotion.js +249 -0
- package/src/functions/account/account-get.js +262 -0
- package/src/functions/account/account-handler.js +299 -0
- package/src/functions/account/clients.js +10 -0
- package/src/functions/account/index.js +208 -0
- package/src/functions/actions/save-promotions-order-history.js +324 -0
- package/src/functions/affiliates/affiliates-hook-consumer.js +87 -0
- package/src/functions/affiliates/affiliates-hook-producer.js +45 -0
- package/src/functions/clients/clients-audience.js +62 -0
- package/src/functions/clients/clients-consumer.js +648 -0
- package/src/functions/clients/clients-producer.js +362 -0
- package/src/functions/clients/clients-suggested-product-consumer.js +166 -0
- package/src/functions/clients/helpers/suggested-product-mdlz.js +233 -0
- package/src/functions/clients_peru/email.html +129 -0
- package/src/functions/clients_peru/splitfile.js +357 -0
- package/src/functions/clients_peru/updateClients.js +1334 -0
- package/src/functions/clients_peru/utils.js +243 -0
- package/src/functions/cronjobs/cron-jobs-manager.js +40 -0
- package/src/functions/cronjobs/cron-jobs.js +171 -0
- package/src/functions/crons/cron.js +39 -0
- package/src/functions/distributors/distributor-handler.js +81 -0
- package/src/functions/distributors/distributor.js +535 -0
- package/src/functions/distributors/index.js +60 -0
- package/src/functions/financialpolicy/assign-financialpolicy.js +111 -0
- package/src/functions/financialpolicy/get-financialpolicy.js +91 -0
- package/src/functions/financialpolicy/index.js +28 -0
- package/src/functions/inventory/catalog-sync-consumer.js +17 -0
- package/src/functions/inventory/catalog-sync-handler.js +311 -0
- package/src/functions/inventory/inventory-consumer.js +119 -0
- package/src/functions/inventory/inventory-producer.js +197 -0
- package/src/functions/multiPresentation/multipre-queue.js +155 -0
- package/src/functions/multiPresentation/multipres.js +459 -0
- package/src/functions/nodeflow/index.js +83 -0
- package/src/functions/nodeflow/nodeflow-cron.js +200 -0
- package/src/functions/nodeflow/nodeflow-pub.js +203 -0
- package/src/functions/nodeflow/nodeflow-pvt.js +266 -0
- package/src/functions/notifications/download-leads-handler.js +67 -0
- package/src/functions/notifications/new-leads-notification-consumer.js +17 -0
- package/src/functions/notifications/new-leads-notification-handler.js +359 -0
- package/src/functions/notifications/order-status-notification-handler.js +482 -0
- package/src/functions/notifications/promotion-notification-handler.js +193 -0
- package/src/functions/orders/index.js +32 -0
- package/src/functions/orders/orders-cancel-handler.js +74 -0
- package/src/functions/orders/orders-handler.js +280 -0
- package/src/functions/orders/orders-hook-consumer.js +137 -0
- package/src/functions/orders/orders-hook-producer.js +170 -0
- package/src/functions/orders/orders-notifications-handler.js +137 -0
- package/src/functions/orders/orders-status-consumer.js +461 -0
- package/src/functions/orders/orders-status-producer.js +443 -0
- package/src/functions/prices/index.js +75 -0
- package/src/functions/prices/prices-consumer.js +236 -0
- package/src/functions/prices/prices-producer.js +323 -0
- package/src/functions/prices/promotion-and-tax.js +1284 -0
- package/src/functions/routesflow/assign-routeflow-queue.js +77 -0
- package/src/functions/schemas/vtex/handle-schemas.js +102 -0
- package/src/functions/security/process_gas.js +221 -0
- package/src/functions/security/security-handler.js +950 -0
- package/src/functions/sftp/sftp-consumer.js +453 -0
- package/src/functions/sftpIntegrations/processes/redirectServices.js +184 -0
- package/src/functions/sftpIntegrations/processes/validateFileSchema.js +226 -0
- package/src/functions/sftpIntegrations/schemas/credential-schema.js +123 -0
- package/src/functions/sftpIntegrations/schemas/record-schema.js +131 -0
- package/src/functions/sftpIntegrations/schemas/sftp_required_fields.json +3 -0
- package/src/functions/sftpIntegrations/sftp-config-producer.js +112 -0
- package/src/functions/sftpIntegrations/sftp-consumer.js +700 -0
- package/src/functions/sftpIntegrations/test/validateFile.test.js +122 -0
- package/src/functions/sftpIntegrations/utils/connect-dynamo.js +29 -0
- package/src/functions/sftpIntegrations/utils/split-data.js +25 -0
- package/src/functions/utils/index.js +130 -0
- package/src/functions/vtex/vtex-helpers.js +694 -0
- package/src/integrations/accountErrors/AccountErrorManager.js +437 -0
- package/src/integrations/audience/Audience.js +70 -0
- package/src/integrations/financialPolicy/FinancialPolicyApi.js +377 -0
- package/src/integrations/index.js +0 -0
- package/src/integrations/mobilvendor/MobilvendorApi.js +405 -0
- package/src/integrations/productmultipresentation/ProductMultiPresentation.js +200 -0
- package/src/mdlz/auth/SecretManagerApi.js +77 -0
- package/src/mdlz/client/MdlzApi.js +70 -0
- package/src/vtex/clients/ProvidersApi.js +51 -0
- package/src/vtex/clients/VtexApi.js +511 -0
- 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
|
+
}
|