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,111 @@
|
|
|
1
|
+
const ApiResponse = require("../../common/utils/api-response");
|
|
2
|
+
const AccountData = require("../../entities/account");
|
|
3
|
+
const ClientData = require("../../entities/clients");
|
|
4
|
+
const MobilvendorApi = require("../../integrations/mobilvendor/MobilvendorApi");
|
|
5
|
+
const FinancialPolicyApi = require("../../integrations/financialPolicy/FinancialPolicyApi");
|
|
6
|
+
const Logger = require("../../common/utils/logger");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import("aws-lambda").APIGatewayProxyEvent} event
|
|
10
|
+
* Service name: assign-financialpolicy.js
|
|
11
|
+
* Lambda name: assing_financialpolicy
|
|
12
|
+
* Consumer service: src/functions/financialpolicy/assign-financialpolicy.handler
|
|
13
|
+
* end Point: POST /financialpolicy/{orderFormId}
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const handler = async (event) => {
|
|
18
|
+
const { orderFormId } = event.pathParameters;
|
|
19
|
+
const { client_id: clientId } = event.requestContext.authorizer.claims;
|
|
20
|
+
const { assign = true, creditDays = 0 } = JSON.parse(event.body) || {};
|
|
21
|
+
try {
|
|
22
|
+
const listAccounts = await ClientData.getClient(clientId);
|
|
23
|
+
const accountName = listAccounts.AccountNames.values[0] || null;
|
|
24
|
+
if (!accountName) {
|
|
25
|
+
return ApiResponse.response(400, { approved: false, message: "AccountName is Required" });
|
|
26
|
+
}
|
|
27
|
+
let configAccount = await AccountData.getAccountDataByAccountName(accountName);
|
|
28
|
+
if (!configAccount) {
|
|
29
|
+
return ApiResponse.response(400, { approved: false, message: "Account not found" });
|
|
30
|
+
}
|
|
31
|
+
if (configAccount.ParentAccountName !== configAccount.AccountName) {
|
|
32
|
+
configAccount = await AccountData.getAccountDataByAccountName(configAccount.ParentAccountName);
|
|
33
|
+
}
|
|
34
|
+
if (!orderFormId) {
|
|
35
|
+
return ApiResponse.response(400, { approved: false, message: "orderForm is Required" });
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
if (!configAccount.FinancialPolicyEcu?.isActive) {
|
|
39
|
+
return ApiResponse.response(401, { approved: false, message: "Financial Policy is not active" });
|
|
40
|
+
}
|
|
41
|
+
const providerApiName = configAccount?.FinancialPolicyEcu?.providerApi || 'MobilvendorApi';
|
|
42
|
+
let ApiClass;
|
|
43
|
+
try {
|
|
44
|
+
ApiClass = eval(providerApiName);
|
|
45
|
+
} catch (evalError) {
|
|
46
|
+
Logger.error(`Provider ${providerApiName} not found, falling back to MobilvendorApi`);
|
|
47
|
+
ApiClass = MobilvendorApi;
|
|
48
|
+
}
|
|
49
|
+
if (!ApiClass) {
|
|
50
|
+
Logger.error('API class not found, falling back to MobilvendorApi');
|
|
51
|
+
ApiClass = MobilvendorApi;
|
|
52
|
+
}
|
|
53
|
+
const responseApi = new ApiClass(
|
|
54
|
+
configAccount.AccountName,
|
|
55
|
+
configAccount.Credentials.key,
|
|
56
|
+
configAccount.Credentials.token,
|
|
57
|
+
configAccount.AcronymClientVtex,
|
|
58
|
+
configAccount.FinancialPolicyEcu
|
|
59
|
+
);
|
|
60
|
+
const orderForm = await responseApi.getOrderFormById(orderFormId);
|
|
61
|
+
const email = orderForm?.clientProfileData?.email ?? null;
|
|
62
|
+
const responseMasterDataCT = email ? await responseApi.getClientById(email) : null;
|
|
63
|
+
const accountNameWl = responseMasterDataCT?.an ?? null;
|
|
64
|
+
if (accountNameWl) {
|
|
65
|
+
const configAccountWl = await AccountData.getAccountDataByAccountName(accountNameWl);
|
|
66
|
+
if (configAccountWl?.FinancialPolicyEcu && responseApi.setConfig) {
|
|
67
|
+
responseApi.setConfig(configAccountWl.FinancialPolicyEcu);
|
|
68
|
+
} else if(!responseApi.setConfig) {
|
|
69
|
+
return ApiResponse.response(400, { approved: false, message: "Function setConfig not found" });
|
|
70
|
+
} else {
|
|
71
|
+
return ApiResponse.response(400, { approved: false, message: "AccountWl not found" });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!assign) {
|
|
75
|
+
await responseApi.deleteCustomData(orderFormId, orderForm?.customData);
|
|
76
|
+
return ApiResponse.response(401, { approved: false, message: "unapproved credit" });
|
|
77
|
+
}
|
|
78
|
+
const responseApiBalance = await responseApi.getCustomerBalance(orderForm, creditDays, responseMasterDataCT);
|
|
79
|
+
const bodyOrderFormCustomData = {
|
|
80
|
+
approved: responseApiBalance.approved || false,
|
|
81
|
+
withCredit: responseApiBalance.withCredit || false,
|
|
82
|
+
paymentTerm: responseApiBalance.paymentTerm || 0,
|
|
83
|
+
maximumCreditAmount: responseApiBalance.maximumCreditAmount || 0,
|
|
84
|
+
paymentMethodCode: responseApiBalance.paymentMethodCode || "-",
|
|
85
|
+
creditDays: responseApiBalance.creditDays || 0,
|
|
86
|
+
paymentMethodName: responseApiBalance.paymentMethodName || "-",
|
|
87
|
+
};
|
|
88
|
+
if (!responseApiBalance.approved) {
|
|
89
|
+
await responseApi.deleteCustomData(orderFormId, orderForm?.customData);
|
|
90
|
+
return ApiResponse.response(401, { approved: false, message: "unapproved credit" });
|
|
91
|
+
}
|
|
92
|
+
const updateOrderFormCustomData = await responseApi.updateOrderFormCustomData(
|
|
93
|
+
orderFormId,
|
|
94
|
+
configAccount.FinancialPolicyEcu.configVtex.id,
|
|
95
|
+
bodyOrderFormCustomData
|
|
96
|
+
);
|
|
97
|
+
if (!updateOrderFormCustomData) {
|
|
98
|
+
return ApiResponse.response(401, { approved: false, message: "Error updating customData orderForm" });
|
|
99
|
+
}
|
|
100
|
+
return ApiResponse.response(200, bodyOrderFormCustomData);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
Logger.error('Error creating API instance:', error);
|
|
103
|
+
return ApiResponse.response(404, { approved: false, message: "Error creating API instance" });
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
Logger.error("Error clients producer:", error);
|
|
107
|
+
return ApiResponse.response(401, { approved: false, message: "Error login mobilvendor" });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
module.exports = { handler };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const ApiResponse = require("../../common/utils/api-response");
|
|
2
|
+
const AccountData = require("../../entities/account");
|
|
3
|
+
const ClientData = require("../../entities/clients");
|
|
4
|
+
const MobilvendorApi = require("../../integrations/mobilvendor/MobilvendorApi");
|
|
5
|
+
const FinancialPolicyApi = require("../../integrations/financialPolicy/FinancialPolicyApi");
|
|
6
|
+
const Logger = require("../../common/utils/logger");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {import("aws-lambda").APIGatewayProxyEvent} event
|
|
10
|
+
* Service name: get-financialpolicy.js
|
|
11
|
+
* Lambda name: get_financialpolicy
|
|
12
|
+
* Consumer service: src/functions/financialpolicy/get-financialpolicy.handler
|
|
13
|
+
* end Point: GET /financialpolicy/{orderFormId}
|
|
14
|
+
* @returns {Promise<ApiResponse>}
|
|
15
|
+
*/
|
|
16
|
+
const handler = async (event) => {
|
|
17
|
+
const { orderFormId } = event.pathParameters || {};
|
|
18
|
+
const { client_id: clientId } = event.requestContext.authorizer.claims || {};
|
|
19
|
+
try {
|
|
20
|
+
const listAccounts = await ClientData.getClient(clientId);
|
|
21
|
+
const accountName = listAccounts.AccountNames.values[0] || null;
|
|
22
|
+
if (!accountName) {
|
|
23
|
+
return ApiResponse.response(400, { approved: false, message: "AccountName is Required" });
|
|
24
|
+
}
|
|
25
|
+
let configAccount = await AccountData.getAccountDataByAccountName(accountName);
|
|
26
|
+
if (!configAccount) {
|
|
27
|
+
return ApiResponse.response(400, { approved: false, message: "Account not found" });
|
|
28
|
+
}
|
|
29
|
+
if (configAccount.ParentAccountName !== configAccount.AccountName) {
|
|
30
|
+
configAccount = await AccountData.getAccountDataByAccountName(configAccount.ParentAccountName);
|
|
31
|
+
}
|
|
32
|
+
Logger.setAccount(configAccount);
|
|
33
|
+
if (!orderFormId) {
|
|
34
|
+
return ApiResponse.response(404, { approved: false, message: "orderForm is not found" });
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const providerApiName = configAccount?.FinancialPolicyEcu?.providerApi || 'MobilvendorApi';
|
|
38
|
+
let ApiClass;
|
|
39
|
+
try {
|
|
40
|
+
ApiClass = eval(providerApiName);
|
|
41
|
+
Logger.debug(`Provider ${providerApiName} found`);
|
|
42
|
+
Logger.debug(ApiClass);
|
|
43
|
+
} catch (evalError) {
|
|
44
|
+
Logger.warn(`Provider ${providerApiName} not found, falling back to MobilvendorApi`);
|
|
45
|
+
ApiClass = MobilvendorApi;
|
|
46
|
+
}
|
|
47
|
+
if (!ApiClass) {
|
|
48
|
+
Logger.warn('API class not found, falling back to MobilvendorApi');
|
|
49
|
+
ApiClass = MobilvendorApi;
|
|
50
|
+
}
|
|
51
|
+
const responseApi = new ApiClass(
|
|
52
|
+
configAccount.AccountName,
|
|
53
|
+
configAccount.Credentials.key,
|
|
54
|
+
configAccount.Credentials.token,
|
|
55
|
+
configAccount.AcronymClientVtex,
|
|
56
|
+
configAccount.FinancialPolicyEcu
|
|
57
|
+
);
|
|
58
|
+
const orderForm = await responseApi.getOrderFormById(orderFormId);
|
|
59
|
+
const email = orderForm?.clientProfileData?.email ?? null;
|
|
60
|
+
const responseMasterDataCT = email ? await responseApi.getClientById(email) : null;
|
|
61
|
+
const accountNameWl = responseMasterDataCT?.an ?? null;
|
|
62
|
+
if (accountNameWl) {
|
|
63
|
+
const configAccountWl = await AccountData.getAccountDataByAccountName(accountNameWl);
|
|
64
|
+
if (configAccountWl?.FinancialPolicyEcu && responseApi.setConfig) {
|
|
65
|
+
responseApi.setConfig(configAccountWl.FinancialPolicyEcu);
|
|
66
|
+
} else if(!responseApi.setConfig){
|
|
67
|
+
return ApiResponse.response(400, { approved: false, message: "Function setConfig not found" });
|
|
68
|
+
}else{
|
|
69
|
+
return ApiResponse.response(400, { approved: false, message: "AccountWl not found" });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const responseBalance = await responseApi.getCustomerBalance(orderForm, null, responseMasterDataCT);
|
|
73
|
+
if (!responseBalance) {
|
|
74
|
+
return ApiResponse.response(404, { approved: false, message: "Client not found" });
|
|
75
|
+
}
|
|
76
|
+
return ApiResponse.response(200, {
|
|
77
|
+
withCredit: responseBalance.customerWithCredit || false,
|
|
78
|
+
maximumCreditAmount: responseBalance.maximumCreditAmount || 0,
|
|
79
|
+
paymentTerm: responseBalance.paymentTerm || 0
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
Logger.error('Error creating API instance:', error);
|
|
83
|
+
return ApiResponse.response(404, { approved: false, message: "Error creating API instance" });
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
Logger.error("Error clients producer:", error);
|
|
87
|
+
return ApiResponse.response(404, { approved: false, message: "Error clients producer" });
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
module.exports = { handler };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
const ApiResponse = require("../../common/utils/api-response");
|
|
3
|
+
const financialpolicyget = require("./get-financialpolicy");
|
|
4
|
+
const financialpolicyassing = require("./assign-financialpolicy");
|
|
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 "/financialpolicy/{orderFormId}":
|
|
13
|
+
switch(method){
|
|
14
|
+
case "GET":
|
|
15
|
+
return await financialpolicyget?.handler(req,res);
|
|
16
|
+
case "POST":
|
|
17
|
+
return await financialpolicyassing?.handler(req,res);
|
|
18
|
+
default:
|
|
19
|
+
return ApiResponse.response(400, {error: "error method!."});
|
|
20
|
+
}
|
|
21
|
+
break;
|
|
22
|
+
default:
|
|
23
|
+
return ApiResponse.response(400, {error: "error resource!."});
|
|
24
|
+
}
|
|
25
|
+
}else{
|
|
26
|
+
return ApiResponse.response(400, {error: "error resource!."});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const AccountData = require("../../entities/account");
|
|
2
|
+
const { handler: catalogSyncHandler } = require("./catalog-sync-handler");
|
|
3
|
+
|
|
4
|
+
exports.processCatalogSync = 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 === 'catalog-sync') {
|
|
11
|
+
await catalogSyncHandler({
|
|
12
|
+
source: 'cron',
|
|
13
|
+
pathParameters: { accountName },
|
|
14
|
+
...values
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
const ApiResponse = require("../../common/utils/api-response");
|
|
2
|
+
const AWSServices = require("../../common/utils/aws-services");
|
|
3
|
+
const AccountData = require("../../entities/account");
|
|
4
|
+
const Logger = require("../../common/utils/logger");
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
const GeneralRequiredError = require("../../common/validation/GeneralErrorValidation.js");
|
|
7
|
+
|
|
8
|
+
const INVENTORY_SKU_DATA_TABLE = process.env.INVENTORY_SKU_DATA_TABLE;
|
|
9
|
+
const SQS_AFFILIATE_HOOK_QUEUE_URL = process.env.SQS_AFFILIATE_HOOK_QUEUE_URL;
|
|
10
|
+
|
|
11
|
+
async function handler(event) {
|
|
12
|
+
let accountName;
|
|
13
|
+
if (event.source === 'cron') {
|
|
14
|
+
accountName = event.accountName;
|
|
15
|
+
}
|
|
16
|
+
// Manejo para llamada HTTP
|
|
17
|
+
else {
|
|
18
|
+
accountName = event.pathParameters?.accountName;
|
|
19
|
+
if (!accountName) {
|
|
20
|
+
return ApiResponse.response(400, { error: "accountName is required in URL path" });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (!accountName) {
|
|
26
|
+
return ApiResponse.response(400, { error: "accountName is required in URL path" });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const account = await AccountData.getAccountDataByAccountName(accountName);
|
|
30
|
+
if (!account) {
|
|
31
|
+
return ApiResponse.response(404, { error: `Account ${accountName} not found` });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Logger.setAccount(account);
|
|
35
|
+
const result = await syncCatalogForAccount(account);
|
|
36
|
+
return ApiResponse.response(200, {
|
|
37
|
+
message: "Catalog synchronization completed successfully",
|
|
38
|
+
stats: result.stats
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
Logger.error("Catalog sync failed", {
|
|
42
|
+
error: error.message,
|
|
43
|
+
stack: error.stack,
|
|
44
|
+
accountName: event.pathParameters?.accountName
|
|
45
|
+
});
|
|
46
|
+
return ApiResponse.response(500, { error: error.message });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function syncCatalogForAccount(account) {
|
|
51
|
+
Logger.info(`Starting catalog synchronization for account: ${account.AccountName}`);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const authToken = await authenticateWithVtex(account);
|
|
55
|
+
Logger.info("VTEX authentication successful");
|
|
56
|
+
|
|
57
|
+
const [vtexCatalog, dynamoCatalog] = await Promise.all([
|
|
58
|
+
getVtexCatalog(account, authToken),
|
|
59
|
+
getDynamoCatalog(account)
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
Logger.info(`Data retrieved: ${vtexCatalog.length} items from VTEX, ${dynamoCatalog.length} items from DynamoDB`);
|
|
63
|
+
|
|
64
|
+
return await syncCatalogs(account, vtexCatalog, dynamoCatalog);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
Logger.error(`Catalog synchronization failed: ${error.message}`);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function authenticateWithVtex(account) {
|
|
72
|
+
const { key, token } = account.Credentials || {};
|
|
73
|
+
if (!key || !token) {
|
|
74
|
+
throw new GeneralRequiredError("Missing VTEX credentials");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const response = await axios({
|
|
79
|
+
method: 'post',
|
|
80
|
+
url: `https://api.vtexcommercestable.com.br/api/vtexid/apptoken/login?an=${account.AccountName}`,
|
|
81
|
+
headers: {
|
|
82
|
+
'X-VTEX-API-AppKey': key,
|
|
83
|
+
'X-VTEX-API-AppToken': token,
|
|
84
|
+
'Content-Type': 'application/json'
|
|
85
|
+
},
|
|
86
|
+
data: {
|
|
87
|
+
appkey: key,
|
|
88
|
+
apptoken: token
|
|
89
|
+
},
|
|
90
|
+
timeout: 10000
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (response.data?.authStatus !== "Success") {
|
|
94
|
+
throw new Error(`VTEX authentication failed: ${response.data?.authStatus || 'Invalid response'}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return response.data.token;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
Logger.error(`VTEX authentication error: ${error.message}`);
|
|
100
|
+
throw new Error(`VTEX authentication failed: ${error.message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function getVtexCatalog(account, authToken) {
|
|
105
|
+
const BASE_URL = `https://${account.AccountName}.vtexcommercestable.com.br`;
|
|
106
|
+
|
|
107
|
+
Logger.info(`Fetching VTEX catalog for account ${account.AccountName}`);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const data = `Filters[SkuId]=&Filters[IsVisible]=&Filters[ProductName]=&Filters[BrandId]=&Filters[CategoryIds]=&Filters[StoreId]=0&Filters[IsActive]=true&Filters[SellerId]=&Fields[]=StockKeepingUnitId&Fields[]=StockKeepingUnitName&Fields[]=ActivateSkuIfPossible&Fields[]=StockKeepingUnitIsActive&Fields[]=StockKeepingUnitEan&Fields[]=ReferenceStockKeepingUnitId&Fields[]=RewardValue&Fields[]=ProductId&Fields[]=ProductName&Fields[]=ProductDescriptionShort&Fields[]=ProductIsActive&Fields[]=DepartmentId&Fields[]=CategoryId&Fields[]=CategoryName&Fields[]=BrandId&Fields[]=BrandName&Fields[]=DepartmentName&Fields[]=Height&Fields[]=Width&Fields[]=Length&Fields[]=WeightKg&Fields[]=RealHeight&Fields[]=RealWidth&Fields[]=RealLength&Fields[]=RealWeight&Fields[]=CubicWeight&Fields[]=MeasurementUnit&Fields[]=UnitMultiplier&Fields[]=StockKeepingUnitIsKit&Fields[]=EstimatedDateArrival&Fields[]=ManufacturerCode&Fields[]=ProductRefId&Fields[]=CommercialCondition&Fields[]=ProductIsVisible&Fields[]=ProductDescription&Fields[]=ProductLinkId&Fields[]=ProductReleaseDate&Fields[]=ProductKeyWords&Fields[]=ProductTitle&Fields[]=ProductMetaTagDescription&Fields[]=ProductSupplierId&Fields[]=ProductShowWithoutStock&Fields[]=Stores&Fields[]=Accessories&Fields[]=Similar&Fields[]=Suggestions&Fields[]=Attachments&Fields[]=ShowTogether&Rows=2000`;
|
|
111
|
+
|
|
112
|
+
const headers = {
|
|
113
|
+
'accept': 'application/json',
|
|
114
|
+
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
115
|
+
'cookie': `VtexIdclientAutCookie=${authToken}`
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const axiosConfig = {
|
|
119
|
+
method: 'post',
|
|
120
|
+
url: `${BASE_URL}/admin/API/StockKeepingUnitReport/LoadContent/`,
|
|
121
|
+
headers: headers,
|
|
122
|
+
data: data,
|
|
123
|
+
timeout: 30000
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const response = await axios(axiosConfig);
|
|
127
|
+
|
|
128
|
+
if (!response.data) {
|
|
129
|
+
throw new Error('Empty response received from VTEX API');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!response.data.Data && response.data.data) {
|
|
133
|
+
Logger.warn("Data field found with lowercase 'd' instead of uppercase 'D'");
|
|
134
|
+
response.data.Data = response.data.data;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!Array.isArray(response.data.Data)) {
|
|
138
|
+
throw new Error(`Expected Data to be an array, got ${typeof response.data.Data}: ${JSON.stringify(response.data).substring(0, 500)}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const items = response.data.Data;
|
|
142
|
+
Logger.info(`Retrieved ${items.length} items from VTEX catalog`);
|
|
143
|
+
|
|
144
|
+
return items;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const errorInfo = {
|
|
147
|
+
message: error.message,
|
|
148
|
+
stack: error.stack,
|
|
149
|
+
code: error.code,
|
|
150
|
+
name: error.name
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (error.response) {
|
|
154
|
+
errorInfo.responseStatus = error.response.status;
|
|
155
|
+
errorInfo.responseStatusText = error.response.statusText;
|
|
156
|
+
errorInfo.responseHeaders = error.response.headers;
|
|
157
|
+
errorInfo.responseData = error.response.data
|
|
158
|
+
? (typeof error.response.data === 'string'
|
|
159
|
+
? error.response.data.substring(0, 1000)
|
|
160
|
+
: JSON.stringify(error.response.data).substring(0, 1000))
|
|
161
|
+
: null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
Logger.error(`Failed to fetch VTEX catalog:`, errorInfo);
|
|
165
|
+
throw new Error(`Failed to fetch VTEX catalog: ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function getDynamoCatalog(account) {
|
|
170
|
+
Logger.info("Fetching catalog data from DynamoDB");
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
let allItems = [];
|
|
174
|
+
let lastEvaluatedKey = null;
|
|
175
|
+
|
|
176
|
+
do {
|
|
177
|
+
const params = {
|
|
178
|
+
TableName: INVENTORY_SKU_DATA_TABLE,
|
|
179
|
+
FilterExpression: "ParentAccountName = :accountName",
|
|
180
|
+
ExpressionAttributeValues: {
|
|
181
|
+
":accountName": { S: account.AccountName }
|
|
182
|
+
},
|
|
183
|
+
ExclusiveStartKey: lastEvaluatedKey
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const result = await AWSServices.dynamoScan(params);
|
|
187
|
+
|
|
188
|
+
const items = (result.Items || []).map(item => AWSServices.unmarshall(item));
|
|
189
|
+
allItems = allItems.concat(items);
|
|
190
|
+
|
|
191
|
+
lastEvaluatedKey = result.LastEvaluatedKey;
|
|
192
|
+
|
|
193
|
+
Logger.debug(`Retrieved ${items.length} items from DynamoDB, total so far: ${allItems.length}`);
|
|
194
|
+
} while (lastEvaluatedKey);
|
|
195
|
+
|
|
196
|
+
Logger.info(`Retrieved ${allItems.length} total items from DynamoDB`);
|
|
197
|
+
return allItems;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
Logger.error(`Error fetching DynamoDB catalog: ${error.message}`);
|
|
200
|
+
throw new Error(`Failed to fetch DynamoDB catalog: ${error.message}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function sendToAffiliateHookQueue(accountName, skuId) {
|
|
205
|
+
try {
|
|
206
|
+
const messageBody = {
|
|
207
|
+
IdSku: skuId,
|
|
208
|
+
An: accountName
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
await AWSServices.sendSQSMessage(SQS_AFFILIATE_HOOK_QUEUE_URL, messageBody);
|
|
212
|
+
return true;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
Logger.error(`Error sending SKU ${skuId} to affiliate hook queue:`, error);
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function syncCatalogs(account, vtexCatalog, dynamoCatalog) {
|
|
220
|
+
Logger.info("Starting catalog comparison and synchronization");
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const vtexMap = new Map();
|
|
224
|
+
vtexCatalog.forEach(item => {
|
|
225
|
+
if (item.StockKeepingUnitId) {
|
|
226
|
+
vtexMap.set(item.StockKeepingUnitId.toString(), item);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const dynamoMap = new Map();
|
|
231
|
+
dynamoCatalog.forEach(item => {
|
|
232
|
+
const skuId = item.SkuId || item.Sku ||
|
|
233
|
+
(item.AccountNameAndSku && item.AccountNameAndSku.replace(account.AccountName, ''));
|
|
234
|
+
if (skuId) {
|
|
235
|
+
dynamoMap.set(skuId.toString(), item);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const itemsToDelete = [];
|
|
240
|
+
dynamoMap.forEach((dynamoItem, skuId) => {
|
|
241
|
+
if (!vtexMap.has(skuId)) {
|
|
242
|
+
itemsToDelete.push({
|
|
243
|
+
AccountNameAndSku: dynamoItem.AccountNameAndSku,
|
|
244
|
+
ParentAccountName: account.AccountName
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
let itemsSentToQueue = 0;
|
|
250
|
+
for (const [skuId, vtexItem] of vtexMap.entries()) {
|
|
251
|
+
await sendToAffiliateHookQueue(account.AccountName, skuId);
|
|
252
|
+
itemsSentToQueue++;
|
|
253
|
+
|
|
254
|
+
if (itemsSentToQueue % 50 === 0) {
|
|
255
|
+
Logger.info(`Progress: ${itemsSentToQueue}/${vtexMap.size} items sent to queue`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
Logger.info(`Synchronization analysis complete: ${itemsToDelete.length} items to delete, ${itemsSentToQueue} items sent to queue`);
|
|
260
|
+
|
|
261
|
+
if (itemsToDelete.length > 0) {
|
|
262
|
+
Logger.info(`Deleting ${itemsToDelete.length} items from DynamoDB`);
|
|
263
|
+
await batchDeleteItems(itemsToDelete);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const stats = {
|
|
267
|
+
vtexItems: vtexCatalog.length,
|
|
268
|
+
dynamoItemsBefore: dynamoCatalog.length,
|
|
269
|
+
itemsDeleted: itemsToDelete.length,
|
|
270
|
+
itemsSentToQueue: itemsSentToQueue,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
Logger.info("Catalog synchronization completed successfully", stats);
|
|
274
|
+
return { stats };
|
|
275
|
+
} catch (error) {
|
|
276
|
+
Logger.error(`Error synchronizing catalogs: ${error.message}`);
|
|
277
|
+
throw new Error(`Failed to synchronize catalogs: ${error.message}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function batchDeleteItems(items) {
|
|
282
|
+
const BATCH_SIZE = 25;
|
|
283
|
+
const batches = Math.ceil(items.length / BATCH_SIZE);
|
|
284
|
+
|
|
285
|
+
Logger.info(`Starting batch delete operation: ${items.length} items in ${batches} batches`);
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < batches; i++) {
|
|
288
|
+
const batchItems = items.slice(i * BATCH_SIZE, (i + 1) * BATCH_SIZE);
|
|
289
|
+
|
|
290
|
+
const requestItems = {};
|
|
291
|
+
requestItems[INVENTORY_SKU_DATA_TABLE] = batchItems.map(item => ({
|
|
292
|
+
DeleteRequest: {
|
|
293
|
+
Key: AWSServices.marshall({
|
|
294
|
+
AccountNameAndSku: item.AccountNameAndSku,
|
|
295
|
+
ParentAccountName: item.ParentAccountName
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
}));
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
await AWSServices.dynamoBatchWrite({ RequestItems: requestItems });
|
|
302
|
+
Logger.debug(`Completed delete batch ${i + 1}/${batches}`);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
Logger.error(`Error in delete batch ${i + 1}/${batches}: ${error.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
Logger.info('Batch delete operation completed');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = { handler };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const VtexApi = require("../../vtex/clients/VtexApi");
|
|
2
|
+
const AWSServices = require("../../common/utils/aws-services");
|
|
3
|
+
const ControlStock = require("../../entities/controlstock");
|
|
4
|
+
const SkuData = require("../../entities/sku");
|
|
5
|
+
const convertQuantity = require("../../common/utils/product-unit-converter");
|
|
6
|
+
const Logger = require("../../common/utils/logger");
|
|
7
|
+
|
|
8
|
+
const SQS_INVENTORY_DQL_QUEUE_URL = process.env.SQS_INVENTORY_DQL_QUEUE_URL;
|
|
9
|
+
const SQS_INVENTORY_QUEUE_URL = process.env.SQS_INVENTORY_QUEUE_URL;
|
|
10
|
+
const MAX_ATTEMPS = process.env.MAX_ATTEMPS || 3;
|
|
11
|
+
|
|
12
|
+
const consumer = async (event) => {
|
|
13
|
+
for (const record of event.Records) {
|
|
14
|
+
let { an, key, token, warehouse, sku, skuRefId, quantity, hoursValidStockPrice, ValidateSkuPriceInventoryInProcess, ParentAccountName, SkuRefUsed, accountConfig, individualUnits } = JSON.parse(record.body);
|
|
15
|
+
try {
|
|
16
|
+
|
|
17
|
+
// obtener sku vtex
|
|
18
|
+
if(ValidateSkuPriceInventoryInProcess===true){
|
|
19
|
+
try{
|
|
20
|
+
sku= await SkuData.getSkuVtex({account: ParentAccountName, sku: sku, SkuRefUsed: SkuRefUsed});
|
|
21
|
+
} catch(esku){
|
|
22
|
+
Logger.error('Error get sku vtex.', esku);
|
|
23
|
+
try{
|
|
24
|
+
await SkuData.saveLogProductsNotFount({an:an, key:key, token: token, entity:"logstocknotfound", sku:sku, value: JSON.stringify({warehouse:warehouse, sku:sku, quantity:quantity })});
|
|
25
|
+
}catch(e){
|
|
26
|
+
Logger.error('error save log price', e);
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
quantity = await getQuantity(sku, skuRefId, quantity, accountConfig, individualUnits);
|
|
33
|
+
|
|
34
|
+
// Validacion valores ya informados
|
|
35
|
+
const validStockInternal= await ControlStock.validNotificationStockVtex({
|
|
36
|
+
skuId: sku,
|
|
37
|
+
account: an,
|
|
38
|
+
warehouse: warehouse,
|
|
39
|
+
hoursUpdate: hoursValidStockPrice ?? 24,
|
|
40
|
+
quantity: quantity
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if(validStockInternal === false){
|
|
44
|
+
Logger.debug('inventario ya informado. No se notifica a VTEX. ', {sku, warehouse, quantity});
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// fin validacion valores informados
|
|
48
|
+
|
|
49
|
+
const vtexApi = new VtexApi(an, key, token);
|
|
50
|
+
const response = await vtexApi.fetch(`/logistics/pvt/inventory/skus/${sku}/warehouses/${warehouse}`, {
|
|
51
|
+
method: "PUT",
|
|
52
|
+
data: {
|
|
53
|
+
quantity: quantity,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
if (response.status >= 200 && response.status < 300) {
|
|
57
|
+
Logger.debug("Inventory updated successfully.");
|
|
58
|
+
} else {
|
|
59
|
+
const attemps = JSON.parse(record.body)?.attemps || 0;
|
|
60
|
+
Logger.error(
|
|
61
|
+
"Error updating inventory. Status: ",
|
|
62
|
+
response.status,
|
|
63
|
+
". Message: ",
|
|
64
|
+
response?.data?.message,
|
|
65
|
+
" - Try: ",
|
|
66
|
+
attemps
|
|
67
|
+
);
|
|
68
|
+
if (attemps < MAX_ATTEMPS) {
|
|
69
|
+
await AWSServices.sendSQSMessage(
|
|
70
|
+
SQS_INVENTORY_QUEUE_URL,
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
an,
|
|
73
|
+
key,
|
|
74
|
+
token,
|
|
75
|
+
warehouse,
|
|
76
|
+
sku,
|
|
77
|
+
quantity,
|
|
78
|
+
attemps: attemps + 1,
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error("Max attemps reached.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
Logger.error(error);
|
|
87
|
+
try {
|
|
88
|
+
await AWSServices.sendSQSMessage(
|
|
89
|
+
SQS_INVENTORY_DQL_QUEUE_URL,
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
body: record.body,
|
|
92
|
+
error: error?.message,
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
Logger.error("Sending message to DQL Queue: ", error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const getQuantity = async (skuVtex, skuRefId, quantity, accountConfig, individualUnits) => {
|
|
103
|
+
try {
|
|
104
|
+
if(accountConfig?.ProductUnitConversion?.isActive) {
|
|
105
|
+
quantity = await convertQuantity(skuVtex, skuRefId, quantity, accountConfig, individualUnits);
|
|
106
|
+
}
|
|
107
|
+
} catch (conversionError) {
|
|
108
|
+
Logger.error('ProductUnitConversion: Error en conversión, usando cantidad original', {
|
|
109
|
+
skuRefId,
|
|
110
|
+
originalQuantity: quantity,
|
|
111
|
+
error: conversionError.message
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return quantity;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
consumer,
|
|
119
|
+
};
|