import moment from 'moment'; import fetch from 'node-fetch'; import dotenv from 'dotenv'; import { models } from './models/index.js'; import { v4 as uuidv4 } from 'uuid'; //import cliProgress from 'cli-progress'; import suppliersGln from './suppliersGln.js'; dotenv.config(); /** * The Endpoint where the Access Token is requested */ const _accessTokenEndpoint = 'https://idm.staging.floriday.io/oauth2/ausmw6b47z1BnlHkw0h7/v1/token'; const BASE_CUSTOMER_URL = 'https://api.staging.floriday.io/customers-api/2022v2/'; /** * Gets the Access Token from the client config table * * @param {sequelize.models} models * @returns {Date} tokenExpirationDate formated as YYYY-MM-DD HH:mm:ss */ async function getClientToken() { const clientConfigData = await models.clientConfig.findAll(); const now = moment().format('YYYY-MM-DD HH:mm:ss'); const tokenExpirationDate = clientConfigData[0].tokenExpiration; if (clientConfigData[0].tokenExpiration == null || moment(now).isAfter(tokenExpirationDate)) { let clientId = clientConfigData[0].clientId; let clientSecret = clientConfigData[0].clientSecret; const tokenRequest = await fetch(_accessTokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}&scope=role:app catalog:read supply:read organization:read network:write network:read`, }); const tokenResponse = await tokenRequest.json(); if (tokenRequest.status === 200) { console.log('Token request successful'); } else { throw new Error( `Token request failed with status ${tokenRequest.status}` ); } const accessToken = tokenResponse.access_token; let now = moment().format('YYYY-MM-DD HH:mm:ss'); let tokenExpirationDate = moment(now) .add(tokenResponse.expires_in, 's') .format('YYYY-MM-DD HH:mm:ss'); await updateClientConfig( clientId, clientSecret, accessToken, tokenExpirationDate ); return tokenExpirationDate; } else { console.log('Using the current token...'); return tokenExpirationDate; } } /** * Updates the Access Token in the client config table * * @param {sequelize.models} models * @param {String} clientId * @param {String} clientSecret * @param {String} accessToken * @param {String} tokenExpirationDate */ async function updateClientConfig(clientId, clientSecret, accessToken, tokenExpirationDate) { try { console.log('Updating the client config with the new token...'); await models.clientConfig.upsert({ id: 1, clientId: clientId, clientSecret: clientSecret, currentToken: accessToken, tokenExpiration: tokenExpirationDate, requestLimit: 500, }); console.log('Client config updated, new Token set'); console.log('New token expiration date: ', tokenExpirationDate); } catch (error) { console.log('There was a error while updating the client config'); console.log(error); } } /** * returns the current Access Token * * @returns */ async function getJWT() { const clientConfigData = await models.clientConfig.findAll(); return clientConfigData[0].currentToken; } /** * pauses the execution of the script for the given amount of milliseconds * * @param {integer} ms * @returns A promise that resolves after ms milliseconds. */ async function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } /** * Recieves an array of functions and executes them in a queue with the given concurrency * * @param {Array} fnArray * @param {Number} concurrency * @returns */ async function asyncQueue(fnArray, concurrency = 1) { const results = []; // 1 const queue = fnArray.map((fn, index) => () => fn().then((result) => results[index] = result)); const run = async () => { // 2 const fn = queue.shift(); if (fn) { await fn(); await run(); } }; const promises = []; // 3 while (concurrency--) { // 4 promises.push(run()); } await Promise.all(promises); // 5 return results; } // 1. Create an array of functions that will be executed in a queue // 2. Create a function that will execute the functions in the queue // 3. Create an array of promises that will execute the run function // 4. Execute the run function while the concurrency is greater than 0 // 5. Return the results /** * Syncs the sequence number for the given model * if no params are given it will reset all the sequence number to 0 * * @param {Number} current - current sequence number * @param {String} model - model name * @param {Number} maximumSequenceNumber - maximum sequence number * @returns */ async function syncSequence(current = 0, model = null ,maximumSequenceNumber = 0){ if (model == null && current == 0){ let mockModels = ['suppliers','tradeItems','supplyLines',]; for (let i = 0; i < mockModels.length; i++) { const element = mockModels[i]; console.log('Syncing sequence for: ', element); await syncSequence(0, element); } } else { let tx = await models.sequelize.transaction(); try { let sequence = await models.sequenceNumber.findOrCreate({ where: { model: model }, defaults: { model: model, sequenceNumber: current, maximumSequenceNumber: maximumSequenceNumber }, transaction: tx }); if (sequence[1] == false){ await models.sequenceNumber.update({ sequenceNumber: current, maximumSequenceNumber: maximumSequenceNumber }, { where: { model: model }, transaction: tx }); } await tx.commit(); } catch (error) { await tx.rollback(); console.log('Error while syncing sequence number for: ', model); } } } async function syncSuppliers(){ let headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${await getJWT()}`, 'X-Api-Key': process.env.API_KEY }; let maximumSequenceNumber = await fetch(`${BASE_CUSTOMER_URL}organizations/current-max-sequence`, { method: 'GET', headers: headers }); maximumSequenceNumber = await maximumSequenceNumber.json(); console.log('Maximum sequence number: ', maximumSequenceNumber); for (let i = 0; i < maximumSequenceNumber; i++) { let query = `${BASE_CUSTOMER_URL}organizations/sync/${i}?organizationType=SUPPLIER&limit=1000`; let response = await fetch(query, { method: 'GET', headers: headers }); let data = await response.json(); let suppliers = data.results; for (let supplier of suppliers) { i = supplier.sequenceNumber; await models.supplier.upsert({ isConnected: false, commercialName: supplier.commercialName, email: supplier.email, phone: supplier.phone, website: supplier.website, mailingAddress: supplier.mailingAddress, physicalAddress: supplier.physicalAddress, pythosanitaryNumber: supplier.pythosanitaryNumber, sequenceNumber: supplier.sequenceNumber, organizationId: supplier.organizationId, companyGln: supplier.companyGln, name: supplier.name, endDate: supplier.endDate, rfhRelationId: supplier.rfhRelationId, organizationType: supplier.organizationType, paymentProviders: `${supplier.paymentProviders}`, }); console.log('INSERTED:\t', supplier.commercialName, '\nsequenceNumber:\t', supplier.sequenceNumber); } await syncSequence(i, 'suppliers', maximumSequenceNumber); } } async function syncConnections(){ let connections = await models.connection.findAll(); let headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${await getJWT()}`, 'X-Api-Key': process.env.API_KEY }; let remoteConnections = await fetch(`${BASE_CUSTOMER_URL}connections`, { method: 'GET', headers: headers }); remoteConnections = await remoteConnections.json(); for (let connection of connections){ if (connection.isConnected == false){ continue; } let remoteConnection = remoteConnections.find(remoteConnection => remoteConnection == connection.organizationId); if (remoteConnection == undefined){ console.log('Connection: ', connection, 'does not exist in the remote server'); console.log('Creating remote connection'); await fetch(`${BASE_CUSTOMER_URL}connections/${connection.organizationId}`, { method: 'PUT', headers: headers }); await models.connection.update({ isConnected: true }, { where: { organizationId: connection.organizationId } }); await models.supplier.update({ isConnected: true }, { where: { organizationId: connection.organizationId } }); } else { console.log('Connection: ', connection, 'exists in the remote server'); await models.connection.update({ isConnected: true }, { where: { organizationId: connection.organizationId } }); await models.supplier.update({ isConnected: true }, { where: { organizationId: connection.organizationId } }); } } } async function syncTradeItems(){ const suppliers = await models.supplier.findAll(); let headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${await getJWT()}`, 'X-Api-Key': process.env.API_KEY }; let i = 0; console.log('Syncing trade items'); for (let supplier of suppliers) { i++; if (!supplier.isConnected){ console.log('Supplier: ', supplier.commercialName, 'is not connected'); console.log('Skipping supplier', supplier.commercialName, '(', i, '/', suppliers.length, ')'); continue; } let query = `${BASE_CUSTOMER_URL}trade-items?supplierOrganizationId=${supplier.organizationId}`; try { let request = await fetch(query, { method: 'GET', headers: headers }); let tradeItems = await request.json(); if (tradeItems.length == 0) { console.log('No trade items for supplier: ', supplier.commercialName); continue; } for (let tradeItem of tradeItems) { let tx = await models.sequelize.transaction(); console.log('Syncing trade item: ', tradeItem.name); try { await models.tradeItem.upsert({ tradeItemId: tradeItem.tradeItemId, supplierOrganizationId: tradeItem.supplierOrganizationId, code: tradeItem.code, gtin: tradeItem.gtin, vbnProductCode: tradeItem.vbnProductCode, name: tradeItem.name, isDeleted: tradeItem.isDeleted, sequenceNumber: tradeItem.sequenceNumber, tradeItemVersion: tradeItem.tradeItemVersion, isCustomerSpecific: tradeItem.isCustomerSpecific, isHiddenInCatalog: tradeItem.isHiddenInCatalog, }, {transaction: tx}); let characteristics = tradeItem.characteristics; for (let characteristic of characteristics) { await models.characteristic.upsert({ vbnCode: characteristic.vbnCode, vbnValueCode: characteristic.vbnValueCode, tradeItemFk: tradeItem.tradeItemId, }, {transaction: tx}); } let seasonalPeriods = tradeItem.seasonalPeriods; for (let seasonalPeriod of seasonalPeriods) { await models.seasonalPeriod.upsert({ startWeek: seasonalPeriod.startWeek, endWeek: seasonalPeriod.endWeek, tradeItemFk: tradeItem.tradeItemId, }, {transaction: tx}); } let photos = tradeItem.photos; for (let photo of photos) { await models.photo.upsert({ id: photo.id, url: photo.url, type: photo.type, primary: photo.primary, tradeItemFk: tradeItem.tradeItemId, }, {transaction: tx}); } let packingConfigurations = tradeItem.packingConfigurations; for (let packingConfiguration of packingConfigurations) { let uuid = uuidv4(); await models.packingConfiguration.upsert({ packingConfigurationId: uuid, piecesPerPackage: packingConfiguration.piecesPerPackage, bunchesPerPackage: packingConfiguration.bunchesPerPackage, photoUrl: packingConfiguration.photoUrl, packagesPerLayer: packingConfiguration.packagesPerLayer, layersPerLoadCarrier: packingConfiguration.layersPerLoadCarrier, additionalPricePerPiece : JSON.stringify(packingConfiguration.additionalPricePerPiece), transportHeightInCm: packingConfiguration.transportHeightInCm, loadCarrierType: packingConfiguration.loadCarrierType, isPrimary: packingConfiguration.isPrimary, tradeItemFk: tradeItem.tradeItemId, }, {transaction: tx}); await models.package.upsert({ vbnPackageCode: packingConfiguration.package.vbnPackageCode, customPackageId: packingConfiguration.package.customPackageId, packingConfigurationFk: uuid, }, {transaction: tx}); } let countryOfOriginIsoCodes = tradeItem.countryOfOriginIsoCodes; countryOfOriginIsoCodes ??= 0; for (let countryOfOriginIsoCode of countryOfOriginIsoCodes) { await models.countryOfOriginIsoCode.upsert({ isoCode: countryOfOriginIsoCode, tradeItemFk: tradeItem.tradeItemId, }, {transaction: tx}); } let botanicalNames = tradeItem.botanicalNames; for (let botanicalName of botanicalNames) { await models.botanicalName.upsert({ name: botanicalName.name, tradeItemFk: tradeItem.tradeItemId, }, {transaction: tx}); } await tx.commit(); } catch (error) { await tx.rollback(); console.log('Error while syncing trade items for: ', supplier.commercialName); console.log(error); } } console.log('Synced trade items for: ', supplier.commercialName); console.log('Remaining suppliers: ', suppliers.length - i, 'of', suppliers.length); console.log('Total trade items: ', tradeItems.length); } catch (error) { console.log('Error while syncing trade items for: ', supplier.commercialName); console.log(error); } } } async function syncSupplyLines(){ console.log('Syncing supply lines'); let suppliers = await models.supplier.findAll(/*{ where: { isConnected: true } }*/); console.log('Found', suppliers.length, 'connected suppliers'); let promises = []; // Launch a promise for each supplier for (let supplier of suppliers) { let headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${await getJWT()}`, 'X-Api-Key': process.env.API_KEY }; console.log('Requesting supply lines for CATALOG'); // eslint-disable-next-line no-async-promise-executor let promise = new Promise(async (resolve, reject) => { let queryCATALOG = `${BASE_CUSTOMER_URL}supply-lines?supplierOrganizationId=${supplier.organizationId}&supplyType=CATALOG`; let queryBATCH = `${BASE_CUSTOMER_URL}supply-lines?supplierOrganizationId=${supplier.organizationId}&supplyType=BATCH`; try { let requestCATALOG = await fetch(queryCATALOG, { method: 'GET', headers: headers }); let requestBATCH = await fetch(queryBATCH, { method: 'GET', headers: headers }); let supplyLinesCATALOG = await requestCATALOG.json(); let supplyLinesBATCH = await requestBATCH.json(); if (supplyLinesBATCH.length == 0) { console.log('No supply lines for BATCH on: ', supplier.commercialName); } if (supplyLinesCATALOG.length == 0) { console.log('No supply lines for CATALOG on:', supplier.commercialName); } let supplyLines = supplyLinesCATALOG.concat(supplyLinesBATCH); if (supplyLines.length > 0) { console.log('Syncing supply lines for: ', supplier.commercialName); console.log('Found', supplyLines.length, 'supply lines'); } resolve(supplyLines); } catch (error) { console.log('Error while syncing supply lines for: ', supplier.commercialName); reject(error); } }); promises.push(promise); } let supplyLines = await Promise.all(promises); for (let supplyLine of supplyLines) { for (let line of supplyLine) { let tradeItem = await models.tradeItem.findOne({ where: { tradeItemId: line.tradeItemId } }); if (!tradeItem) { console.log('Trade item not found for supply line: ', line.supplyLineId); console.log('Trade item id: ', line.tradeItemId); continue; } await models.supplyLine.upsert({ supplyLineId: line.supplyLineId, status: line.status, supplierOrganizationId: line.supplierOrganizationId, pricePerPiece: line.pricePerPiece, numberOfPieces: line.numberOfPieces, deliveryPeriod: line.deliveryPeriod, orderPeriod: line.orderPeriod, wharehouseId: line.wharehouseId, sequenceNumber: line.sequenceNumber, type: line.type, isDeleted: line.isDeleted, salesUnit: line.salesUnit, agreementReferenceCode: line.agreementReference.code, agreementReferenceDescription: line.agreementReference.description, isLimited: line.isLimited, isCustomerSpecific: line.isCustomerSpecific, tradeItemFk: line.tradeItemId, }); } } console.log('Synced supply lines'); } export { getClientToken, updateClientConfig, getJWT, sleep, asyncQueue, syncSequence, syncSuppliers, syncTradeItems, syncConnections, syncSupplyLines };