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,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
+ };