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,700 @@
|
|
|
1
|
+
const AdmZip = require('adm-zip');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const moment = require('moment');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { connectToFtp, getFilesInFolder, getFileContent, moveProcessedFile, createFolder, downloadFile, uploadFile } = require("../../common/utils/sftp-utils");
|
|
7
|
+
const { getErrorData, getDateValues, setQueryParams } = require("../../common/utils/util");
|
|
8
|
+
const AccountData = require("../../entities/account");
|
|
9
|
+
const ValidFile = require('./processes/validateFileSchema');
|
|
10
|
+
const { sendMessageToSqs } = require('../../common/utils/aws-services');
|
|
11
|
+
const DynamoDBConnector = require('./utils/connect-dynamo');
|
|
12
|
+
const ServicesRedirect = require('./processes/redirectServices');
|
|
13
|
+
const VtexApi = require('../../vtex/clients/VtexApi');
|
|
14
|
+
const Logger = require('../../common/utils/logger');
|
|
15
|
+
const { delayStatusProcess } = require('../../common/utils/delay');
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
SFTP_DATA_SCHEMA_TABLE,
|
|
19
|
+
SFTP_CREDENTIALS_TABLE,
|
|
20
|
+
SFTP_JOBS_QUEUE_URL,
|
|
21
|
+
SFTP_JOBS_DLQ_QUEUE_URL,
|
|
22
|
+
SQS_CRON_JOBS_QUEUE_URL,
|
|
23
|
+
SFTP_MAX_ATTEMPS = 3
|
|
24
|
+
} = process?.env ?? {};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Función que ejecuta el proceso de consultar los archivos en el servidor FTP.
|
|
28
|
+
* @param {Object} objectData Objeto proveniente de la cola SQS donde se ejecuta el Cron Job.
|
|
29
|
+
*/
|
|
30
|
+
module.exports.callback = async (objectData) => {
|
|
31
|
+
const { AccountName, ActionProcess, attemps = 0 } = objectData;
|
|
32
|
+
|
|
33
|
+
const dateValues = getDateValues();
|
|
34
|
+
const { timestamp } = dateValues;
|
|
35
|
+
|
|
36
|
+
const logRecord = {
|
|
37
|
+
id: `cron_${AccountName}_${ActionProcess}`,
|
|
38
|
+
actionProcess: ActionProcess,
|
|
39
|
+
process: 'find_file',
|
|
40
|
+
timestamp,
|
|
41
|
+
success: true,
|
|
42
|
+
error: null,
|
|
43
|
+
notify: true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let clientFTP;
|
|
47
|
+
let vtexApiClient, entityName, schemaName;
|
|
48
|
+
try {
|
|
49
|
+
// Consulta los parámetros de la integración requeridos
|
|
50
|
+
const configAccount = await AccountData.getAccountDataByAccountName(AccountName);
|
|
51
|
+
const { ftpId, directoryProcess, ...integrationParameters } = await getIntegrationParameters(AccountName, ActionProcess, configAccount);
|
|
52
|
+
|
|
53
|
+
// Se obtiene la información
|
|
54
|
+
vtexApiClient = integrationParameters.apiClient;
|
|
55
|
+
entityName = integrationParameters.entityName;
|
|
56
|
+
schemaName = integrationParameters.schemaName;
|
|
57
|
+
|
|
58
|
+
// Conexión al servidor FTP
|
|
59
|
+
Logger.info(`Connecting to FTP ${ftpId}`);
|
|
60
|
+
const ftpCredentials = await getFtpProperties(ftpId);
|
|
61
|
+
clientFTP = await connectToFtp(ftpCredentials);
|
|
62
|
+
|
|
63
|
+
// Se obtiene la carpeta de pending
|
|
64
|
+
const { pendingPath, schemaId } = directoryProcess[ActionProcess];
|
|
65
|
+
|
|
66
|
+
// Se obtiene los datos del schema, para validar el nombre del archivo
|
|
67
|
+
const { PatternName } = await getSchemaProperties(schemaId);
|
|
68
|
+
// Se aplica el replace, debido a que el valor puede ser una expresión regular
|
|
69
|
+
// que contiene "\\"
|
|
70
|
+
logRecord.filename = PatternName.replaceAll('\\', '\\\\');
|
|
71
|
+
logRecord.folderPath = pendingPath;
|
|
72
|
+
|
|
73
|
+
// Se consulta los archivos existentes en la carpeta
|
|
74
|
+
const files = await getFilesInFolder(clientFTP, pendingPath), promises = [];
|
|
75
|
+
if (files?.length) {
|
|
76
|
+
for (let file of files) {
|
|
77
|
+
const { original } = file;
|
|
78
|
+
const { name, type } = original;
|
|
79
|
+
// Se valida que el elemento corresponda a un archivo, y el nombre concuerde con la RegExp definida
|
|
80
|
+
if (type == '-' && name.match(new RegExp(PatternName)) !== null) {
|
|
81
|
+
// Se agrega a la promise el path del archivo encontrado
|
|
82
|
+
Logger.info(`File found ${name}`);
|
|
83
|
+
promises.push(`${pendingPath}/${name}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Se ejecutan las promises (si existen)
|
|
89
|
+
logRecord.countFiles = promises.length;
|
|
90
|
+
if (logRecord.countFiles) {
|
|
91
|
+
await Promise.allSettled(promises.map(filePath => {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
// Se inserta en la cola SQS el accountName, nombre del proceso y ruta del archivo a procesar
|
|
94
|
+
sendMessageToSqs(SFTP_JOBS_QUEUE_URL, {
|
|
95
|
+
accountName: AccountName,
|
|
96
|
+
actionProcess: ActionProcess,
|
|
97
|
+
filePath,
|
|
98
|
+
timestamp
|
|
99
|
+
}).then(() => resolve(filePath)).catch(reject);
|
|
100
|
+
});
|
|
101
|
+
}));
|
|
102
|
+
} else {
|
|
103
|
+
// Si no se encontró archivos, se procede a registrar el log indicando que no hubo archivos (logRecord.countFiles = 0).
|
|
104
|
+
// Esto hara que se envíe la notificación indicando que se ejecutó el job, pero no encontró archivo
|
|
105
|
+
await saveInLogEntity(vtexApiClient, entityName, schemaName, logRecord);
|
|
106
|
+
}
|
|
107
|
+
} catch (ex) {
|
|
108
|
+
// Se valida que el número de intentos no supere el valor máximo configurado
|
|
109
|
+
if (attemps < SFTP_MAX_ATTEMPS) {
|
|
110
|
+
await delayStatusProcess(5);
|
|
111
|
+
Logger.warn('Error in sftp callback', { AccountName, ActionProcess, attemps });
|
|
112
|
+
|
|
113
|
+
// Se encola el message en la cola de jobs para procesar nuevamente, aumentando el n° de reintentos
|
|
114
|
+
objectData.attemps = attemps + 1;
|
|
115
|
+
await sendMessageToSqs(SQS_CRON_JOBS_QUEUE_URL, objectData).catch(Logger.error);
|
|
116
|
+
} else {
|
|
117
|
+
// Se registra en la entidad de logs la información de error en el proceso.
|
|
118
|
+
// Este registro hará que se envíe el mensaje de error por correo electrónico a través del trigger configurado en la entidad.
|
|
119
|
+
logRecord.success = false;
|
|
120
|
+
logRecord.error = ex.message
|
|
121
|
+
await saveInLogEntity(vtexApiClient, entityName, schemaName, logRecord);
|
|
122
|
+
throw ex;
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
// Se cierra la conexión al servidor FTP (si esta existe)
|
|
126
|
+
if (clientFTP?.client) {
|
|
127
|
+
clientFTP.client.end();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Función que ejecuta el proceso de procesar nuevamente los archivos que presentaron errores de ejecución.
|
|
134
|
+
* @param {Object} objectData Objeto proveniente de la cola SQS donde se ejecuta el Cron Job.
|
|
135
|
+
*/
|
|
136
|
+
module.exports.reprocessFailed = async (objectData) => {
|
|
137
|
+
const { AccountName, ActionProcess } = objectData;
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// Consulta los parámetros de la integración requeridos
|
|
141
|
+
const configAccount = await AccountData.getAccountDataByAccountName(AccountName);
|
|
142
|
+
const integrationParameters = await getIntegrationParameters(AccountName, ActionProcess, configAccount, false);
|
|
143
|
+
|
|
144
|
+
// Se obtiene la información para el consumo de la API de VTEX
|
|
145
|
+
const { apiClient, entityName, schemaName } = integrationParameters;
|
|
146
|
+
|
|
147
|
+
// Se consulta los registros cuyo archivo no fue procesado exitosamente
|
|
148
|
+
const urlSearch = setQueryParams(`/dataentities/${entityName}/search`, {
|
|
149
|
+
_schema: schemaName,
|
|
150
|
+
_fields: 'id,accountName,actionProcess,filename,folderPath,contentType,timestamp',
|
|
151
|
+
_where: '(success=false)'
|
|
152
|
+
});
|
|
153
|
+
const records = await apiClient.fetch(urlSearch, {
|
|
154
|
+
method: 'GET',
|
|
155
|
+
validateStatus: status => status >= 200 && status < 400
|
|
156
|
+
});
|
|
157
|
+
if (records?.status == 200) {
|
|
158
|
+
let { data } = records;
|
|
159
|
+
Logger.info(`Record for reprocess: ${data?.length}`);
|
|
160
|
+
if (data?.length) {
|
|
161
|
+
// Se encola nuevamente la información de los archivos cuyo proceso falló,
|
|
162
|
+
// para procesarlos nuevamente tal como se hizo originalmente.
|
|
163
|
+
do {
|
|
164
|
+
// Se extrae las primeras 'maxRows' filas de la lista
|
|
165
|
+
let rowsData = data.splice(0, 10);
|
|
166
|
+
await Promise.allSettled(rowsData.map(item => {
|
|
167
|
+
const { id, accountName, actionProcess, filename, folderPath, contentType, timestamp } = item;
|
|
168
|
+
|
|
169
|
+
// Se define la ruta del archivo concatenando la carpeta donde se procesó,
|
|
170
|
+
// el Id que corresponde al nombre final y la extensión del archivo original.
|
|
171
|
+
const filePath = `${folderPath}/${filename}`;
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
// Se envia a la cola el Id del registro
|
|
174
|
+
sendMessageToSqs(SFTP_JOBS_QUEUE_URL, {
|
|
175
|
+
id, accountName, actionProcess, filePath, contentType, timestamp
|
|
176
|
+
}).then(resolve).catch(reject);
|
|
177
|
+
});
|
|
178
|
+
})).catch(Logger.error);
|
|
179
|
+
} while (data.length);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (ex) {
|
|
183
|
+
const { info } = getErrorData(ex);
|
|
184
|
+
Logger.error('Error in reprocessFailed', info);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Función que se invoca desde el trigger de la cola SQS definida en la propiedad 'sftpjobs-queue' del archivo 'constructs.yml'.
|
|
190
|
+
* @param {*} event Objeto que contiene información de una petición como: el body, path y método de una petición HTTP, o la lista de registros en una cola SQS.
|
|
191
|
+
* @param {*} context Objeto que contiene información sobre la invocación, la función y el entorno de ejecución.
|
|
192
|
+
*/
|
|
193
|
+
module.exports.processFile = async (event, context) => {
|
|
194
|
+
// ID de la petición en AWS
|
|
195
|
+
const { awsRequestId } = context;
|
|
196
|
+
|
|
197
|
+
for (const record of event.Records) {
|
|
198
|
+
// Se obtiene el contenido del cuerpo del mensaje de la cola SQS
|
|
199
|
+
let { body } = record;
|
|
200
|
+
body = JSON.parse(body);
|
|
201
|
+
|
|
202
|
+
// Se ejecuta el proceso de validar el archivo, con los valores enviados a la cola SQS
|
|
203
|
+
await handleProcessFile(awsRequestId, body);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Extrae la información del archivo,
|
|
209
|
+
* @param {string} awsRequestId Id de la petición de AWS.
|
|
210
|
+
* @param {object} sqsMessage Mensaje SQS con la información del archivo a procesar.
|
|
211
|
+
* @param {string} sqsMessage.accountName Nombre del accountName.
|
|
212
|
+
* @param {string} sqsMessage.actionProcess Nombre del proceso.
|
|
213
|
+
* @param {string} sqsMessage.filePath Ruta del archivo a procesar.
|
|
214
|
+
* @param {string} sqsMessage.contentType Tipo de dato del archivo a procesar (si aplica).
|
|
215
|
+
* @param {string} sqsMessage.timestamp Marca de tiempo de cuando se ejecuta el proceso.
|
|
216
|
+
*/
|
|
217
|
+
async function handleProcessFile(awsRequestId, sqsMessage) {
|
|
218
|
+
let { id, accountName, actionProcess, filePath, contentType = null, timestamp = null, attemps = 0 } = sqsMessage;
|
|
219
|
+
|
|
220
|
+
let clientFTP;
|
|
221
|
+
|
|
222
|
+
// Se establece el valor
|
|
223
|
+
if (!timestamp) {
|
|
224
|
+
const dateValues = getDateValues();
|
|
225
|
+
timestamp = dateValues.timestamp;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Se extrae el nombre y extensión del archivo
|
|
229
|
+
const fileData = path.parse(filePath);
|
|
230
|
+
const { base, name, dir } = fileData;
|
|
231
|
+
|
|
232
|
+
// Si se definió el Id, este valor será el nombre del archivo a procesar
|
|
233
|
+
const fileName = id ?? (contentType ? name : `${name}_${timestamp}`);
|
|
234
|
+
|
|
235
|
+
// Variables asociadas al nombre de la entidad, conexión a la API de VTEX y objeto JSON a insertar
|
|
236
|
+
let vtexApiClient, entityName, schemaName;
|
|
237
|
+
const logRecord = {
|
|
238
|
+
id: fileName,
|
|
239
|
+
filename: base,
|
|
240
|
+
folderPath: dir,
|
|
241
|
+
contentType,
|
|
242
|
+
actionProcess,
|
|
243
|
+
timestamp,
|
|
244
|
+
error: null
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Si se está procesando un archivo dividido, se define notify=false para que no envíe notificación;
|
|
248
|
+
// salvo que ocurra un error en el proceso.
|
|
249
|
+
logRecord.notify = contentType ? false : true;
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
// Se consulta los datos de integración asociados a la cuenta
|
|
253
|
+
const configAccount = await AccountData.getAccountDataByAccountName(accountName);
|
|
254
|
+
const { maxRows, ftpId, directoryProcess, ...integrationParameters } = await getIntegrationParameters(accountName, actionProcess, configAccount);
|
|
255
|
+
vtexApiClient = integrationParameters.apiClient;
|
|
256
|
+
entityName = integrationParameters.entityName;
|
|
257
|
+
schemaName = integrationParameters.schemaName;
|
|
258
|
+
|
|
259
|
+
// Se obtiene la carpeta de pending
|
|
260
|
+
const { process: processName } = directoryProcess[actionProcess];
|
|
261
|
+
logRecord.process = processName;
|
|
262
|
+
Logger.info(`Executing process ${processName} in account ${accountName}`);
|
|
263
|
+
|
|
264
|
+
// Conexión al servidor FTP
|
|
265
|
+
Logger.info(`Connecting to FTP ${ftpId}`);
|
|
266
|
+
const ftpCredentials = await getFtpProperties(ftpId);
|
|
267
|
+
clientFTP = await connectToFtp(ftpCredentials);
|
|
268
|
+
|
|
269
|
+
// Si el proceso es de descomprimir archivo .zip
|
|
270
|
+
if (processName == 'unzip_file') {
|
|
271
|
+
await descompressZipFile(clientFTP, logRecord, directoryProcess, ftpCredentials, {
|
|
272
|
+
accountName, actionProcess, filePath, timestamp, fileName, fileData
|
|
273
|
+
})
|
|
274
|
+
} else {
|
|
275
|
+
await callbackProcessFile(clientFTP, logRecord, maxRows, directoryProcess, configAccount,
|
|
276
|
+
{ accountName, actionProcess, filePath, contentType, timestamp, fileName, fileData }
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await saveInLogEntity(vtexApiClient, entityName, schemaName, logRecord);
|
|
281
|
+
} catch (ex) {
|
|
282
|
+
const { message } = getErrorData(ex);
|
|
283
|
+
|
|
284
|
+
// Se valida que el número de intentos no supere el valor máximo configurado
|
|
285
|
+
if (attemps < SFTP_MAX_ATTEMPS) {
|
|
286
|
+
await delayStatusProcess(5);
|
|
287
|
+
Logger.warn('Error in handleProcessFile', { accountName, actionProcess, filePath, attemps, message });
|
|
288
|
+
|
|
289
|
+
// Se encola el message para procesar nuevamente, aumentando el n° de reintentos
|
|
290
|
+
sqsMessage.attemps = attemps + 1;
|
|
291
|
+
await sendMessageToSqs(SFTP_JOBS_QUEUE_URL, sqsMessage).catch(Logger.error);
|
|
292
|
+
} else {
|
|
293
|
+
Logger.error('Error in handleProcessFile', message);
|
|
294
|
+
|
|
295
|
+
logRecord.success = false;
|
|
296
|
+
logRecord.error = message;
|
|
297
|
+
|
|
298
|
+
await saveInLogEntity(vtexApiClient, entityName, schemaName, logRecord);
|
|
299
|
+
await sendMessageToSqs(SFTP_JOBS_DLQ_QUEUE_URL, {
|
|
300
|
+
accountName, actionProcess, filePath,
|
|
301
|
+
reason: message,
|
|
302
|
+
awsRequestId
|
|
303
|
+
}).catch(ex => {
|
|
304
|
+
Logger.error('Error in handleProcessFile', ex);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
} finally {
|
|
308
|
+
// Se cierra la conexión al servidor FTP (si esta existe)
|
|
309
|
+
if (clientFTP?.client) {
|
|
310
|
+
clientFTP.client.end();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function descompressZipFile(clientFTP, logRecord, directoryProcess, ftpCredentials, handleParams) {
|
|
316
|
+
// Se obtiene los parámetros de la función de donde se invoca esta
|
|
317
|
+
const { accountName, actionProcess, filePath, timestamp, fileName, fileData } = handleParams;
|
|
318
|
+
|
|
319
|
+
// Se extrae el nombre y extensión del archivo
|
|
320
|
+
const { base, ext } = fileData;
|
|
321
|
+
|
|
322
|
+
if (ext !== '.zip') {
|
|
323
|
+
throw new Error(`File extension '${ext}' is not valid`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Se obtiene la información del proceso a ejecutar con el archivo, así como las carpetas requeridas
|
|
327
|
+
const { schemaId, pendingPath, processedPath } = directoryProcess[actionProcess];
|
|
328
|
+
Logger.info(`Getting schema file ${schemaId}`);
|
|
329
|
+
const schema = await getSchemaProperties(schemaId);
|
|
330
|
+
const { ContentType, ExtractOptions, PatternName } = schema;
|
|
331
|
+
if (ContentType !== 'zip') {
|
|
332
|
+
throw new Error(`Content type '${ContentType}' is not valid`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Se define la propiedad que contiene información de los archivos del .zip
|
|
336
|
+
logRecord.zipData = {
|
|
337
|
+
count: 0, // Archivos existentes
|
|
338
|
+
extracted: 0, // Archivos extraidos
|
|
339
|
+
errors: {} // Errores en procesamiento de los archivos
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Se descarga el archivo .zip al directorio temporal de la Lambda
|
|
343
|
+
const targetFolder = '/tmp';
|
|
344
|
+
const localZipFile = `${targetFolder}/${fileName}.zip`;
|
|
345
|
+
Logger.info(`Downloading file ${filePath} to ${targetFolder}`);
|
|
346
|
+
const downloadedFile = await downloadFile(clientFTP, filePath, localZipFile, ftpCredentials?.type);
|
|
347
|
+
if (downloadedFile) {
|
|
348
|
+
// Si existen valores por extraer
|
|
349
|
+
Logger.info(`Options for extract files: ${ExtractOptions?.length}`);
|
|
350
|
+
if (ExtractOptions?.length) {
|
|
351
|
+
const zipFile = new AdmZip(localZipFile);
|
|
352
|
+
const zipEntries = zipFile.getEntries();
|
|
353
|
+
|
|
354
|
+
// Se define el N° de archivos existentes en el .zip
|
|
355
|
+
logRecord.zipData.count = zipEntries.length;
|
|
356
|
+
|
|
357
|
+
for (let zipEntry of zipEntries) {
|
|
358
|
+
const { entryName } = zipEntry;
|
|
359
|
+
const filteredFiles = ExtractOptions.filter(option => {
|
|
360
|
+
const { PatternName } = option;
|
|
361
|
+
return entryName.match(new RegExp(PatternName));
|
|
362
|
+
});
|
|
363
|
+
for (let filteredFile of filteredFiles) {
|
|
364
|
+
const { ActionProcess, FolderPath = pendingPath } = filteredFile;
|
|
365
|
+
logRecord.zipData.extracted++;
|
|
366
|
+
|
|
367
|
+
// Se extrar el archivo del .zip
|
|
368
|
+
Logger.info(`Extracting file ${entryName}`);
|
|
369
|
+
const extractFile = zipFile.extractEntryTo(entryName, targetFolder, true, true);
|
|
370
|
+
if (extractFile) {
|
|
371
|
+
// Se referencia la ruta del archivo local extraido, y la ruta donde se subirá el archivo
|
|
372
|
+
const localPath = `${targetFolder}/${entryName}`, remotePath = `${FolderPath}/${entryName}`;
|
|
373
|
+
Logger.info(`Uploading file ${localPath} to ${remotePath}`);
|
|
374
|
+
const uploadedFile = await uploadFile(clientFTP, localPath, remotePath, ftpCredentials?.type).catch(ex => {
|
|
375
|
+
const { message, info } = getErrorData(ex);
|
|
376
|
+
Logger.error(message, info);
|
|
377
|
+
logRecord.zipData.errors[entryName] = message;
|
|
378
|
+
});
|
|
379
|
+
// Se valida si el archivo se subió al servidor FTP, y si se tiene definido una acción a ejecutar
|
|
380
|
+
Logger.info(`Uploaded file: ${uploadedFile}`);
|
|
381
|
+
if (uploadedFile && ActionProcess) {
|
|
382
|
+
Logger.info(`Set action process ${ActionProcess}`);
|
|
383
|
+
// Se envía el message a la cola SQS para procesar el archivo en cuestión
|
|
384
|
+
sendMessageToSqs(
|
|
385
|
+
SFTP_JOBS_QUEUE_URL,
|
|
386
|
+
{ accountName, actionProcess: ActionProcess, filePath: remotePath, timestamp }
|
|
387
|
+
).catch(ex => {
|
|
388
|
+
const { info } = getErrorData(ex);
|
|
389
|
+
Logger.error(filePath, info);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
logRecord.zipData.errors[entryName] = 'File not extracted';
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Se obtiene el folder final a donde se moverán los archivos
|
|
400
|
+
const targetFolderRender = getRenderValue(PatternName, base, processedPath);
|
|
401
|
+
|
|
402
|
+
// Se mueve el archivo .zip original
|
|
403
|
+
Logger.info(`Moving file ${filePath} to ${targetFolderRender}`);
|
|
404
|
+
await moveProcessedFile(clientFTP, filePath, targetFolderRender, `${fileName}${ext}`).then(() => {
|
|
405
|
+
logRecord.success = true;
|
|
406
|
+
}).catch(ex => {
|
|
407
|
+
Logger.error('Error with moveProcessedFile in descompressZipFile', ex);
|
|
408
|
+
logRecord.success = false;
|
|
409
|
+
logRecord.error = ex.message
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
logRecord.success = false;
|
|
413
|
+
logRecord.error = `File ${filePath} cannot be downloaded`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function callbackProcessFile(clientFTP, logRecord, maxRows, directoryProcess, configAccount, handleParams) {
|
|
418
|
+
// Se obtiene los parámetros de la función de donde se invoca esta
|
|
419
|
+
const { accountName, actionProcess, filePath, contentType, timestamp, fileName, fileData } = handleParams;
|
|
420
|
+
|
|
421
|
+
// Se extrae el nombre y extensión del archivo
|
|
422
|
+
const { base, ext } = fileData;
|
|
423
|
+
|
|
424
|
+
// Se obtiene la información del proceso a ejecutar con el archivo, así como las carpetas requeridas
|
|
425
|
+
const { schemaId, process, processingPath, processedPath, logsPath } = directoryProcess[actionProcess];
|
|
426
|
+
const schema = await getSchemaProperties(schemaId);
|
|
427
|
+
const { PatternName } = schema;
|
|
428
|
+
|
|
429
|
+
const fileValidator = new ValidFile(schema);
|
|
430
|
+
|
|
431
|
+
// Se obtiene el contenido del archivo
|
|
432
|
+
Logger.info(`Getting content of file ${filePath}`);
|
|
433
|
+
const fileContent = await getFileContent(clientFTP, filePath);
|
|
434
|
+
|
|
435
|
+
// Se procede a extraer los datos del archivo a formato JSON
|
|
436
|
+
let { valid: validFile, errors: errorsFile, data: dataFile } = await fileValidator.extractContentFile({ content: fileContent }, contentType);
|
|
437
|
+
if (validFile && dataFile?.length) {
|
|
438
|
+
// Se define el número de filas
|
|
439
|
+
Logger.info(`Records count: ${dataFile.length}`);
|
|
440
|
+
logRecord.rows = dataFile.length;
|
|
441
|
+
|
|
442
|
+
// Se valida si el número de registros es menor al valor máximo configurado, para en caso contrario proceder a dividir el archivo grande
|
|
443
|
+
// en archivos mas pequeños en formato JSON.
|
|
444
|
+
if (dataFile.length <= maxRows) {
|
|
445
|
+
// Uso de clase para configAccount
|
|
446
|
+
const servicesRedirect = new ServicesRedirect(configAccount);
|
|
447
|
+
|
|
448
|
+
// Array donde se consolida los errores de validación presentados
|
|
449
|
+
const errors = [];
|
|
450
|
+
|
|
451
|
+
// Se ejecuta el proceso dentro de una promise
|
|
452
|
+
await Promise.allSettled(dataFile.map(data => {
|
|
453
|
+
return new Promise(resolve => {
|
|
454
|
+
// Se ejecuta la validación del registro con base a un Schema
|
|
455
|
+
const { valid: validRecord, errors: errorsRecord } = fileValidator.validateRecordSchema(data);
|
|
456
|
+
if (validRecord) {
|
|
457
|
+
// Se obtiene la data normalizada, para enviar a la respectiva cola SQS, segun el proceso a ejecutar
|
|
458
|
+
let newData = fileValidator.standardizeData(data);
|
|
459
|
+
servicesRedirect.initSendData({ typeProcess: process, data: newData }).then(serviceResponse => {
|
|
460
|
+
resolve(newData);
|
|
461
|
+
}).catch(ex => {
|
|
462
|
+
const { message } = getErrorData(ex);
|
|
463
|
+
Logger.error(message);
|
|
464
|
+
resolve(null);
|
|
465
|
+
});
|
|
466
|
+
} else {
|
|
467
|
+
errors.push(JSON.stringify({ data, errorsRecord }))
|
|
468
|
+
Logger.warn('Error in validation', { data, errorsRecord });
|
|
469
|
+
resolve(null);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}));
|
|
473
|
+
|
|
474
|
+
Logger.info(`Errors count: ${errors.length}`);
|
|
475
|
+
logRecord.errorRows = errors.length;
|
|
476
|
+
|
|
477
|
+
// Se genera el archivo de log con los datos de errores (si existieron)
|
|
478
|
+
if (errors.length) {
|
|
479
|
+
// Se crea la carpeta donde se moveran los archivos de log
|
|
480
|
+
await createFolder(clientFTP, logsPath);
|
|
481
|
+
|
|
482
|
+
// Se escribe el archivo de log
|
|
483
|
+
await createFile(clientFTP, `${fileName}.log`, errors.join('\n'), logsPath).catch(ex => { });
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
// Se obtiene el N° de archivos estimado, a partir de la división entre el N° de filas
|
|
487
|
+
// del archivo y el número de filas máximo configurado.
|
|
488
|
+
const estimatedFiles = Math.ceil(dataFile.length / maxRows);
|
|
489
|
+
const digits = String(estimatedFiles).length;
|
|
490
|
+
Logger.info(`Splitting file ${filePath} into ${estimatedFiles} files`);
|
|
491
|
+
|
|
492
|
+
// N° de archivos en que se divide el archivo original
|
|
493
|
+
logRecord.countFiles = estimatedFiles;
|
|
494
|
+
|
|
495
|
+
// Se concatena en la lista de promises los lotes de filas para generar
|
|
496
|
+
let promises = [], count = 1;
|
|
497
|
+
while (dataFile.length > 0) {
|
|
498
|
+
promises.push({ count, data: dataFile.splice(0, maxRows) });
|
|
499
|
+
count++;
|
|
500
|
+
}
|
|
501
|
+
if (promises.length) {
|
|
502
|
+
// Se crea la carpeta donde se moveran los archivos divididos (si aplica)
|
|
503
|
+
await createFolder(clientFTP, processingPath).catch(ex => {
|
|
504
|
+
Logger.error('Error with createFolder in callbackProcessFile', ex);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Se generan los archivos JSON
|
|
508
|
+
Logger.info(`Generating ${promises.length} splitted files`);
|
|
509
|
+
await Promise.allSettled(promises.map(item => {
|
|
510
|
+
return new Promise((resolve, reject) => {
|
|
511
|
+
const { count, data } = item;
|
|
512
|
+
const jsonFileName = `${fileName}_${String(count).padStart(digits, '0')}.json`;
|
|
513
|
+
|
|
514
|
+
// Se escribe el archivo en un directorio temporal
|
|
515
|
+
createFile(clientFTP, jsonFileName, JSON.stringify(data), processingPath).then(filePath => {
|
|
516
|
+
// Se envía el message a la cola SQS para procesar el archivo en cuestión
|
|
517
|
+
Logger.info(`Sending message to SQS`);
|
|
518
|
+
sendMessageToSqs(
|
|
519
|
+
SFTP_JOBS_QUEUE_URL,
|
|
520
|
+
{ accountName, actionProcess, filePath, contentType: 'json', timestamp }
|
|
521
|
+
).then(() => {
|
|
522
|
+
Logger.info(`File processed ${filePath}`);
|
|
523
|
+
resolve(filePath)
|
|
524
|
+
}).catch(ex => {
|
|
525
|
+
const { info } = getErrorData(ex);
|
|
526
|
+
Logger.error(filePath, info);
|
|
527
|
+
reject(new Error('Error while send Data'));
|
|
528
|
+
});
|
|
529
|
+
}).catch(ex => {
|
|
530
|
+
Logger.info(`Error sending message`, ex);
|
|
531
|
+
reject(new Error('Error while upload FTP file'));
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
}));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Se obtiene el folder final a donde se moverán los archivos
|
|
539
|
+
const targetFolderRender = getRenderValue(PatternName, base, processedPath);
|
|
540
|
+
|
|
541
|
+
// Se mueve el archivo a la carpeta de procesados
|
|
542
|
+
// Al nombre del archivo se le concatena una marca de tiempo "timestamp"
|
|
543
|
+
Logger.info(`Moving file ${filePath} to ${targetFolderRender}`);
|
|
544
|
+
await moveProcessedFile(clientFTP, filePath, targetFolderRender, `${fileName}${ext}`).then(() => {
|
|
545
|
+
logRecord.success = true;
|
|
546
|
+
}).catch(ex => {
|
|
547
|
+
Logger.error('Error with moveProcessedFile in callbackProcessFile', ex);
|
|
548
|
+
logRecord.success = false;
|
|
549
|
+
logRecord.error = ex.message
|
|
550
|
+
});
|
|
551
|
+
} else {
|
|
552
|
+
Logger.warn('Error validating file', errorsFile);
|
|
553
|
+
logRecord.success = false;
|
|
554
|
+
logRecord.error = errorsFile.join('|');
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async function getIntegrationParameters(accountName, actionProcess, configAccount, includeDirectory = true) {
|
|
559
|
+
// Obtener datos de la integración SFTP
|
|
560
|
+
const { SftpIntegration, Credentials } = configAccount;
|
|
561
|
+
if (!SftpIntegration) {
|
|
562
|
+
throw new Error(`SftpIntegration not found for account ${accountName}`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Se define si se imprimen todos los mensajes de Logger.
|
|
566
|
+
Logger.setAccount(configAccount);
|
|
567
|
+
|
|
568
|
+
// Se establece una conexión a la API de VTEX
|
|
569
|
+
const { key, token } = Credentials;
|
|
570
|
+
const apiClient = new VtexApi(accountName, key, token);
|
|
571
|
+
|
|
572
|
+
// Se consulta las propiedades de la integración
|
|
573
|
+
let { isActive, maxRows, ftpId, directoryProcess, entityName, schemaName } = SftpIntegration;
|
|
574
|
+
if (!isActive) {
|
|
575
|
+
throw new Error(`SftpIntegration is not actived for account ${accountName}`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const itemsData = { apiClient, maxRows, ftpId, entityName, schemaName };
|
|
579
|
+
if (includeDirectory) {
|
|
580
|
+
if (!directoryProcess?.[actionProcess]) {
|
|
581
|
+
throw new Error(`Directory options not found for actionProcess '${actionProcess}' in account ${accountName}`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
itemsData.directoryProcess = directoryProcess;
|
|
585
|
+
|
|
586
|
+
// Se establece el valor asociado al proceso (si este existe) dentro de las configuraciones
|
|
587
|
+
for (let prop of ['maxRows', 'ftpId', 'entityName', 'schemaName']) {
|
|
588
|
+
if (directoryProcess[actionProcess]?.[prop]) {
|
|
589
|
+
itemsData[prop] = directoryProcess[actionProcess][prop];
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return itemsData;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Consulta los datos del schema
|
|
599
|
+
* @param {string} schemaId Id del schema
|
|
600
|
+
* @returns Datos del schema.
|
|
601
|
+
*/
|
|
602
|
+
async function getSchemaProperties(schemaId) {
|
|
603
|
+
// Se obtiene los datos del schema, para validar el nombre del archivo
|
|
604
|
+
const dynamoConnector = new DynamoDBConnector(SFTP_DATA_SCHEMA_TABLE);
|
|
605
|
+
const schema = await dynamoConnector.getItem({ SchemaId: schemaId });
|
|
606
|
+
return schema;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Consulta en DynamoDB las credenciales de servidor SFTP asociadas a un Id.
|
|
611
|
+
* @param {string} ftpId Id de la credencial FTP.
|
|
612
|
+
* @returns Objeto con los datos de conexión al servidor FTP.
|
|
613
|
+
*/
|
|
614
|
+
async function getFtpProperties(ftpId) {
|
|
615
|
+
// Se obtiene los datos del schema, para validar el nombre del archivo
|
|
616
|
+
const dynamoConnector = new DynamoDBConnector(SFTP_CREDENTIALS_TABLE);
|
|
617
|
+
const schema = await dynamoConnector.getItem({ FtpId: ftpId });
|
|
618
|
+
const { Type: type, Host: host, Port: port, User: user, Password: password } = schema;
|
|
619
|
+
return { type, host, port, user, password };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Crea un archivo en la carpeta "temp" y subirlo a un servidor FTP.
|
|
624
|
+
* @param {*} clientFTP Instancia de conexión al servidor FTP.
|
|
625
|
+
* @param {string} fileName Nombre del archivo.
|
|
626
|
+
* @param {string} data Contenido del archivo a crear.
|
|
627
|
+
* @param {string} targetFolder Carpeta del FTP donde se subirá el archivo.
|
|
628
|
+
* @return Path del archivo generado.
|
|
629
|
+
*/
|
|
630
|
+
async function createFile(clientFTP, fileName, data, targetFolder) {
|
|
631
|
+
return new Promise((resolve, reject) => {
|
|
632
|
+
const tempPath = `/tmp/${fileName}`;
|
|
633
|
+
Logger.info(`Creating file ${tempPath}`);
|
|
634
|
+
fs.writeFile(tempPath, data, 'utf-8', err => {
|
|
635
|
+
if (err) {
|
|
636
|
+
Logger.error('Error in createFile', err);
|
|
637
|
+
reject(err);
|
|
638
|
+
} else {
|
|
639
|
+
// Path del archivo JSON generado
|
|
640
|
+
const filePath = `${targetFolder}/${fileName}`;
|
|
641
|
+
Logger.info(`Uploading file ${filePath}`);
|
|
642
|
+
clientFTP.put(fs.createReadStream(tempPath), filePath).then(() => {
|
|
643
|
+
Logger.info(`File uploaded ${filePath}`);
|
|
644
|
+
resolve(filePath)
|
|
645
|
+
}).catch(ex => {
|
|
646
|
+
const { info } = getErrorData(ex);
|
|
647
|
+
Logger.error('Error in createFile.put', filePath, info);
|
|
648
|
+
reject(new Error('Error while upload FTP file'));
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Renderiza el valor de un texto a partir de una expresión regular, y/o valores de fecha del sistema.
|
|
657
|
+
* @param {*} patternName Expresión regular usada para la búsqueda del archivo.
|
|
658
|
+
* @param {*} fileName Nombre del archivo encontrado con la expresión regular.
|
|
659
|
+
* @param {*} processedPath Ruta del directorio a renderizar.
|
|
660
|
+
* @return Ruta del directorio con los comodines de RegExp sustituidos por los valores extraidos de la expresión regular,
|
|
661
|
+
* y/o valores de fecha sustituidos por los respectivos valores.
|
|
662
|
+
* @see {@link https://momentjs.com/|Moment.js Docs}
|
|
663
|
+
*/
|
|
664
|
+
function getRenderValue(patternName, fileName, processedPath) {
|
|
665
|
+
// Expresión regular del filename
|
|
666
|
+
const regex = new RegExp(patternName);
|
|
667
|
+
|
|
668
|
+
// Se valida valores agrupados de la expresión regular, para sustituir en el nombre del folder
|
|
669
|
+
const match = fileName.match(regex);
|
|
670
|
+
if (match?.length > 1) {
|
|
671
|
+
processedPath = fileName.replace(regex, processedPath);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Se formatea el texto con las variables de moment.js a partir de la fecha y hora actual del sistema
|
|
675
|
+
const currentDate = new Date();
|
|
676
|
+
processedPath = moment(currentDate).format(processedPath);
|
|
677
|
+
return processedPath;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Registra los datos de log en la entidad de Master Data.
|
|
682
|
+
* @param {*} vtexApiClient Instancia de conexión a la API de VTEX.
|
|
683
|
+
* @param {string} entityName Nombre de la entidad de Master Data.
|
|
684
|
+
* @param {string} schemaName NOmbre del schema de la entidad de Master Data.
|
|
685
|
+
* @param {object} logRecord Objeto de datos del log.
|
|
686
|
+
*/
|
|
687
|
+
async function saveInLogEntity(vtexApiClient, entityName, schemaName, logRecord) {
|
|
688
|
+
if (vtexApiClient && entityName && schemaName) {
|
|
689
|
+
// Se inserta en la entidad de Master Data los datos de log
|
|
690
|
+
Logger.info(`Saving log data in entity ${entityName}:${schemaName}`);
|
|
691
|
+
await vtexApiClient.fetch(`/dataentities/${entityName}/documents?_schema=${schemaName}`, {
|
|
692
|
+
method: 'PATCH',
|
|
693
|
+
data: logRecord,
|
|
694
|
+
validateStatus: status => status >= 200 && status < 400
|
|
695
|
+
}).catch(ex => {
|
|
696
|
+
const { message } = getErrorData(ex);
|
|
697
|
+
Logger.error('Error with vtexApiClient.fetch', message);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|