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,67 @@
1
+ const VtexApi = require("../../vtex/clients/VtexApi");
2
+ const AccountData = require("../../entities/account");
3
+ const ApiResponse = require("../../common/utils/api-response");
4
+ const jwt = require('jsonwebtoken');
5
+ const Logger = require("../../common/utils/logger");
6
+
7
+ const SECRET_KEY = process.env.JWT_SECRET || 'your-secret-key';
8
+ const handler = async (event) => {
9
+ try {
10
+ const { token } = event.queryStringParameters;
11
+ const { accountName, fileId, entity } = decryptUrlParams(token);
12
+
13
+ if (!accountName || !fileId) {
14
+ return ApiResponse.response(400, {
15
+ success: false,
16
+ message: "Missing required parameters"
17
+ });
18
+ }
19
+ //Obtener configuración de cuenta
20
+ const configAccount = await AccountData.getAccountDataByAccountName(accountName);
21
+ if (!configAccount?.NewLeadsNotification) {
22
+ return ApiResponse.response(404, {
23
+ success: false,
24
+ message: "Account configuration not found"
25
+ });
26
+ }
27
+ const vtexApi = new VtexApi(
28
+ configAccount.AccountName,
29
+ configAccount.Credentials.key,
30
+ configAccount.Credentials.token
31
+ );
32
+
33
+ const fileData = await vtexApi.fetch(
34
+ `/dataentities/${configAccount.NewLeadsNotification.fileUploadEntity}/search?_fields=_all&id=${fileId}`,
35
+ {
36
+ method: 'GET',
37
+ headers: {
38
+ 'Cache-Control': 'no-cache',
39
+ 'Content-Type': 'application/json',
40
+ 'REST-Range': 'resources=0-1'
41
+ }
42
+ }
43
+ );
44
+ return {
45
+ statusCode: 200,
46
+ headers: {
47
+ 'Content-Type': 'text/csv',
48
+ 'Content-Disposition': `attachment; filename="${fileData.data[0].fileName}"`,
49
+ },
50
+ body: fileData.data[0].fileContent
51
+ };
52
+ } catch (error) {
53
+ Logger.error('Download error:', error);
54
+ return ApiResponse.response(500, {
55
+ success: false,
56
+ message: "Error downloading file",
57
+ error: error.message,
58
+ id: fileId
59
+ });
60
+ }
61
+ };
62
+
63
+ function decryptUrlParams(token) {
64
+ return jwt.verify(token, SECRET_KEY);
65
+ }
66
+
67
+ module.exports = { handler };
@@ -0,0 +1,17 @@
1
+ const AccountData = require("../../entities/account");
2
+ const { handler: newLeadsHandler } = require("./new-leads-notification-handler");
3
+
4
+ exports.processNewLeadsNotification = async (accountName, actionProcess, values) => {
5
+ const accountConfig = await AccountData.getAccountDataByAccountName(accountName);
6
+ if (!accountConfig) {
7
+ throw new Error("Account configuration not found");
8
+ }
9
+
10
+ if (actionProcess === 'new-leads') {
11
+ await newLeadsHandler({
12
+ source: 'cron',
13
+ accountName,
14
+ ...values
15
+ });
16
+ }
17
+ };
@@ -0,0 +1,359 @@
1
+ //new-leads-notification-handler.js
2
+ const VtexHelper = require("../vtex/vtex-helpers");
3
+ const VtexApi = require("../../vtex/clients/VtexApi");
4
+ const ApiResponse = require("../../common/utils/api-response");
5
+ const AccountData = require("../../entities/account");
6
+ const ClientData = require("../../entities/clients");
7
+ const { calculateDateRange } = require("../../common/utils/date-range-calculator");
8
+ const jwt = require('jsonwebtoken');
9
+ const Logger = require("../../common/utils/logger");
10
+
11
+ const SECRET_KEY = process.env.JWT_SECRET || 'your-secret-key';
12
+ const BASE_URL_API = `https://${process.env.BASE_URL_API}${(process.env.ENV_URL_API == 'prod' ? '' : `/${process.env.ENV_URL_API}`)}` || 'https://localhost:3000';
13
+
14
+ const handler = async (event) => {
15
+ try {
16
+ let accountName, configAccount;
17
+ const accountHttp = event?.pathParameters?.an ?? null;
18
+
19
+ if (event.source === 'cron') {
20
+ accountName = event.accountName;
21
+ } else if(accountHttp){
22
+ accountName = accountHttp;
23
+ }
24
+
25
+ if (!accountName) {
26
+ return ApiResponse.response(400, {
27
+ success: false,
28
+ message: "AccountName is Required"
29
+ });
30
+ }
31
+
32
+ configAccount = await AccountData.getAccountDataByAccountName(accountName);
33
+ if (!configAccount) {
34
+ return ApiResponse.response(400, { success: false, message: "Account not found" });
35
+ }
36
+
37
+ const newLeadsConfig = configAccount.NewLeadsNotification;
38
+ const configValidation = VtexHelper.validateLeadsConfig(newLeadsConfig);
39
+
40
+ if (!configValidation.isValid) {
41
+ return ApiResponse.response(400, {
42
+ success: false,
43
+ message: "Invalid leads configuration",
44
+ errors: configValidation.errors
45
+ });
46
+ }
47
+
48
+ const vtexApi = new VtexApi(
49
+ configAccount.AccountName,
50
+ configAccount.Credentials.key,
51
+ configAccount.Credentials.token
52
+ );
53
+
54
+ const dateRange = calculateDateRange(
55
+ newLeadsConfig.rangeQuery,
56
+ newLeadsConfig.rangeValue
57
+ );
58
+
59
+ const currentDate = new Date().toISOString();
60
+ const fileName = `new-leads-${newLeadsConfig.fileUploadEntity}-${currentDate}.csv`;
61
+
62
+ const leads = await getLeadsFromMasterData(
63
+ vtexApi,
64
+ newLeadsConfig.newLeadsEntity,
65
+ dateRange,
66
+ configAccount
67
+ );
68
+
69
+ if (!leads || leads.length === 0) {
70
+ return ApiResponse.response(200, {
71
+ success: true,
72
+ message: "No new leads found in the specified date range"
73
+ });
74
+ }
75
+
76
+ // Crear documento con CSV
77
+ const document = await createLeadsDocument(vtexApi, newLeadsConfig, leads, dateRange, fileName);
78
+
79
+ // Generar URL
80
+ const downloadUrl = generatePublicDownloadUrl(
81
+ configAccount.AccountName,
82
+ document.documentId,
83
+ newLeadsConfig.fileUploadEntity
84
+ );
85
+
86
+ // Actualizar documento con URL y enviar notificación
87
+ await sendNotification(vtexApi, configAccount, downloadUrl, dateRange, fileName, document.fileContent, document.documentId);
88
+
89
+ return ApiResponse.response(200, {
90
+ success: true,
91
+ message: "New leads notification processed successfully",
92
+ data: {
93
+ leadsCount: leads.length,
94
+ downloadUrl,
95
+ dateRange,
96
+ documentId: document.documentId,
97
+ metadata: {
98
+ accountName: configAccount.AccountName,
99
+ entity: newLeadsConfig.newLeadsEntity,
100
+ expirationTime: newLeadsConfig.ExpirationTimeFile
101
+ }
102
+ }
103
+ });
104
+
105
+ } catch (error) {
106
+ return ApiResponse.response(500, {
107
+ success: false,
108
+ message: "Error processing new leads notification",
109
+ error: error.message
110
+ });
111
+ }
112
+ };
113
+
114
+ async function getLeadsFromMasterData(vtexApi, entity, dateRange, configAccount) {
115
+ const config = configAccount.NewLeadsNotification;
116
+ const leads = [];
117
+ let stopApi = false;
118
+ let scrollToken = null;
119
+ let page = 1;
120
+
121
+ while (!stopApi) {
122
+ try {
123
+ const listLeads = await getLeadsSearchAllVTEX({
124
+ vtexApi: vtexApi,
125
+ entity: entity,
126
+ _fields: config.fields,
127
+ size: config.ScrollSize,
128
+ page: page,
129
+ scroll: true,
130
+ scrollToken: scrollToken,
131
+ dateField: config.dateField,
132
+ startDate: dateRange.startDate,
133
+ endDate: dateRange.endDate,
134
+ config: config
135
+ });
136
+
137
+ if (listLeads && listLeads?.data && listLeads?.data?.length > 0) {
138
+ leads.push(...listLeads.data);
139
+
140
+ if (!listLeads.scrollToken) {
141
+ scrollToken = null;
142
+ stopApi = true;
143
+ } else {
144
+ scrollToken = listLeads.scrollToken;
145
+ page++;
146
+ }
147
+
148
+ // Opcional: Detener si se alcanza el máximo de solicitudes de scroll
149
+ if (page > config.MaxScrollRequests) {
150
+ stopApi = true;
151
+ }
152
+ } else {
153
+ stopApi = true;
154
+ }
155
+ } catch (error) {
156
+ Logger.error(`Error al recuperar lote de leads: ${error.message}`);
157
+ stopApi = true;
158
+ }
159
+ }
160
+
161
+ return leads;
162
+ }
163
+
164
+ async function getLeadsSearchAllVTEX({
165
+ vtexApi,
166
+ entity = 'CT',
167
+ _fields = "_all",
168
+ size = 1000,
169
+ page = 1,
170
+ scroll = false,
171
+ scrollToken = null,
172
+ dateField = null,
173
+ startDate = null,
174
+ endDate = null,
175
+ config = {}
176
+ }) {
177
+ if (!vtexApi) {
178
+ return null;
179
+ }
180
+
181
+ const endpoint = scroll ? '/scroll' : '/search';
182
+ const url = `/dataentities/${entity}${endpoint}?_fields=${_fields}`;
183
+
184
+ const requestOptions = {
185
+ method: 'GET',
186
+ headers: {
187
+ 'Cache-Control': 'no-cache'
188
+ }
189
+ };
190
+
191
+ // Agregar filtro de rango de fechas
192
+ if (dateField && startDate && endDate) {
193
+ let whereClause = `${dateField} between ${startDate} AND ${endDate}`;
194
+
195
+ // Agregar validación de campo duplicado si existe
196
+ if (config?.duplicateField) {
197
+ whereClause += ` AND ${config.duplicateField} is null`;
198
+ }
199
+
200
+ requestOptions.params = {
201
+ ...(requestOptions.params || {}),
202
+ _where: whereClause
203
+ };
204
+ }
205
+
206
+ if (scroll === false) {
207
+ requestOptions.headers['REST-Range'] = `resources=${(page > 1 ? ((parseFloat(page)*parseFloat(size))+1) : 0)}-${(page > 1 ? (parseFloat((page+1))*parseFloat(size)) : size)}`;
208
+ } else if (scroll === true && typeof scrollToken === 'string') {
209
+ requestOptions.params = {
210
+ ...(requestOptions.params || {}),
211
+ _token: scrollToken
212
+ };
213
+ } else {
214
+ requestOptions.params = {
215
+ ...(requestOptions.params || {}),
216
+ _size: size
217
+ };
218
+ }
219
+
220
+ try {
221
+ const response = await vtexApi.fetch(url, requestOptions);
222
+
223
+ return {
224
+ data: response.data,
225
+ page: response.data?.length === 0 ? null : (parseInt(page) + 1),
226
+ scrollToken: response.headers['x-vtex-md-token'] || null
227
+ };
228
+ } catch (error) {
229
+ Logger.error('Error en getLeadsSearchAllVTEX:', error);
230
+ throw error;
231
+ }
232
+ }
233
+
234
+ function transformLeadFields(lead, leadMap) {
235
+ if (!lead || typeof lead !== 'object') return {};
236
+ if (!leadMap || typeof leadMap !== 'object') return {};
237
+
238
+ const transformedLead = {};
239
+
240
+ for (const [csvHeader, mapConfig] of Object.entries(leadMap)) {
241
+ let value = '';
242
+
243
+ try {
244
+ if (typeof mapConfig === 'string') {
245
+ value = lead[mapConfig] ?? '';
246
+ } else if (typeof mapConfig === 'object' && mapConfig !== null) {
247
+ if (!mapConfig.field) continue;
248
+
249
+ if (Array.isArray(mapConfig.field)) {
250
+ const fieldValues = mapConfig.field
251
+ .map(field => {
252
+ const fieldValue = lead[field];
253
+ return fieldValue != null ? String(fieldValue).trim() : '';
254
+ })
255
+ .filter(Boolean);
256
+ value = fieldValues.length > 0 ? fieldValues.join(' ') : '';
257
+ } else {
258
+ value = lead[mapConfig.field] ?? '';
259
+ }
260
+
261
+ if (mapConfig.transform) {
262
+ try {
263
+ const transformResult = new Function('value', `return ${mapConfig.transform}`)(value);
264
+ value = transformResult ?? value;
265
+ } catch (transformError) {
266
+ Logger.error(`Transform error for ${csvHeader}: ${transformError.message}`);
267
+ }
268
+ }
269
+ }
270
+
271
+ value = value !== null && value !== undefined ? String(value).trim() : '';
272
+
273
+ if (typeof value === 'string' && value.includes(',')) {
274
+ value = `"${value.replace(/"/g, '""')}"`;
275
+ }
276
+
277
+ transformedLead[csvHeader] = value;
278
+
279
+ } catch (error) {
280
+ Logger.error(`Error processing ${csvHeader}: ${error.message}`);
281
+ transformedLead[csvHeader] = '';
282
+ }
283
+ }
284
+
285
+ return transformedLead;
286
+ }
287
+
288
+ async function createLeadsDocument(vtexApi, config, leads, dateRange, fileName) {
289
+ const formattedLeads = leads.map(lead => transformLeadFields(lead, config.leadMap));
290
+ const uniqueDocumentId = `${config.fileUploadEntity}-${Math.random().toString(36).slice(2, 11)}`;;
291
+ const currentDate = new Date().toISOString();
292
+
293
+ const headers = Object.keys(config.leadMap);
294
+ const csvRows = [headers.join(',')]; // Header row
295
+
296
+ formattedLeads.forEach(lead => {
297
+ const row = headers.map(header => {
298
+ const value = lead[header] || '';
299
+ // Escapar comas y comillas en valores
300
+ return `"${String(value).replace(/"/g, '""')}"`;
301
+ });
302
+ csvRows.push(row.join(','));
303
+ });
304
+
305
+ const csvContent = csvRows.join('\n');
306
+
307
+ const documentSave = {
308
+ documentId: uniqueDocumentId,
309
+ fileName,
310
+ fileContent: csvContent,
311
+ downloadUrl: null,
312
+ generationDate: currentDate
313
+ };
314
+
315
+ return documentSave;
316
+ }
317
+
318
+ function encryptUrlParams(params) {
319
+ return jwt.sign(params, SECRET_KEY);
320
+ }
321
+
322
+ function generatePublicDownloadUrl(accountName, fileId, entity) {
323
+ const params = { accountName, fileId, entity };
324
+ const token = encryptUrlParams(params);
325
+ return `${BASE_URL_API}/new-leads/download?token=${token}`;
326
+ }
327
+
328
+ async function sendNotification(vtexApi, config, downloadUrl, dateRange, fileName, fileContent, documentId) {
329
+ const currentDate = new Date().toISOString();
330
+ const { fileUploadEntity, schema = '' } = config.NewLeadsNotification;
331
+
332
+ const document = {
333
+ id: documentId,
334
+ downloadUrl,
335
+ generationDate: currentDate,
336
+ fileName,
337
+ fileContent
338
+ };
339
+
340
+ // Construir URL asegurando que termine con /
341
+ const baseUrl = `/dataentities/${fileUploadEntity}/documents/`;
342
+ const url = schema ? `${baseUrl}?_schema=${schema}` : baseUrl;
343
+ const response = await vtexApi.fetch(url,
344
+ {
345
+ method: 'PATCH',
346
+ data: document,
347
+ headers: {
348
+ 'Cache-Control': 'no-cache',
349
+ 'Content-Type': 'application/json'
350
+ }
351
+ }
352
+ );
353
+
354
+ if (response.status >= 400) {
355
+ throw new Error(`Failed to update notification document. Status: ${response.status}, Message: ${response.statusText || 'Unknown error'}`);
356
+ }
357
+ }
358
+
359
+ module.exports = { handler };