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'; 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.update( { clientId: clientId, clientSecret: clientSecret, currentToken: accessToken, tokenExpiration: tokenExpirationDate, }, { where: { id: 1, }, } ); 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); }); } /** * Returns the uuidv4 string with the prefix 'Vn-' * * @param {string} uuidv4 - uuidv4 string * @returns */ async function vnUuid(uuidv4) { return 'Vn-' + uuidv4; } /** * Returns the uuidv4 string without the prefix 'Vn-' * * @param {string} uuidv4 - uuidv4 string with prefix 'Vn-' * @returns */ async function removeVnPrefix(uuidv4) { return uuidv4.replace('Vn-', ''); } /** * 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 /** * * Inserts every tradeitem from the organization into the DB * * @param {*} organizationGln */ async function getTradeitems(organizationGln) { const headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + await getJWT(), 'X-Api-Key': process.env.API_KEY, }; // Get the organization id from the organizationGln const organizationsUrl = `${BASE_CUSTOMER_URL}organizations/gln/${organizationGln}`; const organizationsRequest = await fetch(organizationsUrl, { method: 'GET', headers: headers, }); const organizationsResponse = await organizationsRequest.json(); const organizationId = organizationsResponse.organizationId; console.log('Organization ID: ', organizationId); // Get the tradeitems from the organization const tradeitemsUrl = `${BASE_CUSTOMER_URL}trade-items?supplierOrganizationId=${organizationId}`; const tradeitemsRequest = await fetch(tradeitemsUrl, { method: 'GET', headers: headers, }); const tradeitemsResponse = await tradeitemsRequest.json(); // If there is no tradeitems if (tradeitemsResponse.length == 0) { return; } // if there is at least one tradeitem, save it in the database //console.log(tradeitemsResponse[0]); try { if (tradeitemsResponse.length > 0 && tradeitemsResponse != null) { let bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_grey); bar.start(tradeitemsResponse.length, 0); await tradeitemsResponse.forEach(async item => { bar.increment(); try { item.tradeItemId = await vnUuid(item.tradeItemId); await models.suppliers.upsert({ supplierId: await vnUuid(organizationId), supplierGln: organizationGln, }); await models.tradeItem.upsert({ tradeItemId: item.tradeItemId, supplierOrganizationId: organizationGln, supplierOrganizationUuid: await vnUuid(organizationId), code: item.code, gtin: item.gtin, vbnProductCode: item.vbnProductCode, name: item.name, isDeleted: item.isDeleted, sequenceNumber: item.sequenceNumber, tradeItemVersion: item.tradeItemVersion, isCustomerSpecific: item.isCustomerSpecific, isHiddenInCatalog: item.isHiddenInCatalog, }); await item.characteristics.forEach((characteristic) => { models.characteristics.upsert({ tradeItemFk: item.tradeItemId, vbnCode: characteristic.vbnCode, vbnValueCode: characteristic.vbnValueCode, }); }); await item.seasonalPeriods.forEach((seasonalPeriod) => { models.seasonalPeriod.upsert({ tradeItemFk: item.tradeItemId, startWeek: seasonalPeriod.startWeek, endWeek: seasonalPeriod.endWeek, }); }); await item.photos.forEach(async (photo) => { photo.id = await vnUuid(photo.id); models.photos.upsert({ tradeItemFk: item.tradeItemId, photoId: photo.id, url: photo.url, seasonalPeriodFk: JSON.stringify(photo.seasonalPeriod), type: photo.type, primary: photo.primary, }); }); await item.packingConfigurations.forEach(async (packagingConfiguration) => { let uuid = uuidv4(); uuid = await vnUuid(uuid); await models.packingConfigurations.upsert({ tradeItemFk: item.tradeItemId, packingConfigurationId: uuid, piecesPerPackage: packagingConfiguration.piecesPerPackage, bunchesPerPackage: packagingConfiguration.bunchesPerPackage, photoUrl: packagingConfiguration.photoUrl, packagesPerLayer: packagingConfiguration.packagesPerLayer, layersPerLoadCarrier: packagingConfiguration.layersPerLoadCarrier, transportHeightInCm: packagingConfiguration.transportHeightInCm, loadCarrierType: packagingConfiguration.loadCarrierType, isPrimary: packagingConfiguration.isPrimary, additionalPricePerPiece: JSON.stringify(packagingConfiguration.additionalPricePerPiece), }); await models.package.upsert({ packingConfigurationsId: uuid, vbnPackageCode: packagingConfiguration.package.vbnPackageCode, vbnPackageValueCode: packagingConfiguration.package.vbnPackageValueCode, }); }); await item.botanicalNames.forEach((botanicalName) => { models.botanicalNames.upsert({ tradeItemFk: item.tradeItemId, name: botanicalName, }); }); await item.countryOfOriginIsoCodes.forEach((countryOfOriginIsoCode) => { models.countryOfOriginIsoCodes.upsert({ tradeItemFk: item.tradeItemId, isoCode: countryOfOriginIsoCode, }); }); } catch (error) { console.log('Missing data for the tradeitem: ' + item.tradeItemId); bar.stop(); return; } }); bar.stop(); } } catch (error) { console.log('There was an error while saving the data to the database, trying again in 5 seconds...'); await sleep(5000); await getTradeitems(organizationGln); } } /** * Gets a single tradeitem from the API and inserts it into the database * * @param {*} tradeItemId */ async function getTradeItem(tradeItemId) { try { const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${await getJWT()}`, 'X-Api-Key': process.env.API_KEY, }; const tradeitemUrl = `${BASE_CUSTOMER_URL}trade-items/${tradeItemId}`; const tradeitemRequest = await fetch(tradeitemUrl, { method: 'GET', headers: headers, }); const tradeitemResponse = await tradeitemRequest.json(); tradeitemResponse.tradeItemId = await vnUuid(tradeitemResponse.tradeItemId); await models.tradeItem.upsert({ tradeItemId: tradeitemResponse.tradeItemId, supplierOrganizationId: tradeitemResponse.supplierOrganizationGln, supplierOrganizationUuid: await vnUuid(tradeitemResponse.supplierOrganizationId), code: tradeitemResponse.code, gtin: tradeitemResponse.gtin, vbnProductCode: tradeitemResponse.vbnProductCode, name: tradeitemResponse.name, isDeleted: tradeitemResponse.isDeleted, sequenceNumber: tradeitemResponse.sequenceNumber, tradeItemVersion: tradeitemResponse.tradeItemVersion, isCustomerSpecific: tradeitemResponse.isCustomerSpecific, isHiddenInCatalog: tradeitemResponse.isHiddenInCatalog, }); await tradeitemResponse.characteristics.forEach((characteristic) => { models.characteristics.upsert({ tradeItemFk: tradeitemResponse.tradeItemId, vbnCode: characteristic.vbnCode, vbnValueCode: characteristic.vbnValueCode, }); }); await tradeitemResponse.seasonalPeriods.forEach((seasonalPeriod) => { models.seasonalPeriod.upsert({ tradeItemFk: tradeitemResponse.tradeItemId, startWeek: seasonalPeriod.startWeek, endWeek: seasonalPeriod.endWeek, }); }); await tradeitemResponse.photos.forEach(async (photo) => { photo.id = await vnUuid(photo.id); models.photos.upsert({ tradeItemFk: tradeitemResponse.tradeItemId, photoId: photo.id, url: photo.url, seasonalPeriodFk: JSON.stringify(photo.seasonalPeriod), type: photo.type, primary: photo.primary, }); }); await tradeitemResponse.packingConfigurations.forEach(async (packagingConfiguration) => { let uuid = uuidv4(); uuid = await vnUuid(uuid); await models.packingConfigurations.upsert({ tradeItemFk: tradeitemResponse.tradeItemId, packingConfigurationId: uuid, piecesPerPackage: packagingConfiguration.piecesPerPackage, bunchesPerPackage: packagingConfiguration.bunchesPerPackage, photoUrl: packagingConfiguration.photoUrl, packagesPerLayer: packagingConfiguration.packagesPerLayer, layersPerLoadCarrier: packagingConfiguration.layersPerLoadCarrier, transportHeightInCm: packagingConfiguration.transportHeightInCm, loadCarrierType: packagingConfiguration.loadCarrierType, isPrimary: packagingConfiguration.isPrimary, additionalPricePerPiece: JSON.stringify(packagingConfiguration.additionalPricePerPiece), }); await models.package.upsert({ packingConfigurationsId: uuid, vbnPackageCode: packagingConfiguration.package.vbnPackageCode, vbnPackageValueCode: packagingConfiguration.package.vbnPackageValueCode, }); }); await tradeitemResponse.botanicalNames.forEach((botanicalName) => { models.botanicalNames.upsert({ tradeItemFk: tradeitemResponse.tradeItemId, name: botanicalName, }); }); await tradeitemResponse.countryOfOriginIsoCodes.forEach((countryOfOriginIsoCode) => { models.countryOfOriginIsoCodes.upsert({ tradeItemFk: tradeitemResponse.tradeItemId, isoCode: countryOfOriginIsoCode, }); }); return true; } catch (error) { console.log('There was an error while saving the data to the database, trying again in 5 seconds...'); console.log(error.message); await sleep(5000); await getTradeItem(tradeItemId); } } /** * Gets the stock for all suppliers * * Does this by getting all suppliers and then calling the getStockBySupplier function for each supplier */ async function getStock() { const suppliers = await models.suppliers.findAll(); const supplierQueue = []; suppliers.forEach(async (supplier) => { supplierQueue.push(await getStockBySupplier(supplier.supplierId)); }); await asyncQueue(supplierQueue); } /** * Returns the stock for a given supplier * * @param {*} supplierId * @returns void - inserts the stock into the database */ async function getStockBySupplier(supplierId) { supplierId = await removeVnPrefix(supplierId); const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${await getJWT()}`, 'X-Api-Key': process.env.API_KEY, }; const stockUrl = `${BASE_CUSTOMER_URL}supply-lines?supplierOrganizationId=${supplierId}`; let stockRequest; try { stockRequest = await fetch(stockUrl, { method: 'GET', headers: headers, }); } catch (error) { console.log(error.message); return; } const stockResponse = await stockRequest.json(); if (stockResponse.length == 0) { return; } try { if (stockResponse.length > 0 && stockResponse != null) { let bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_grey); bar.start(stockResponse.length, 0); await stockResponse.forEach(async supplyLine => { bar.increment(); let alreadyExists = await models.supplyLines.findOne({ where: { supplyLineId: supplyLine.supplyLineId, } }); if (alreadyExists) { return; } try { await models.supplyLines.upsert({ supplyLineId: supplyLine.supplyLineId, status: supplyLine.status, supplierOrganizationId: await vnUuid(supplyLine.supplierOrganizationId), pricePerPiece: supplyLine.pricePerPiece, numberOfPieces: supplyLine.numberOfPieces, deliveryPeriod: supplyLine.deliveryPeriod, orderPeriod: supplyLine.orderPeriod, warehouseId: await vnUuid(supplyLine.warehouseId), sequenceNumber: supplyLine.sequenceNumber, type: supplyLine.type, isDeleted: supplyLine.isDeleted, salesUnit: supplyLine.salesUnit, agreementReferenceCode: supplyLine.agreementReference.code, agreementReferenceDescription: supplyLine.agreementReference.description, isLimited: supplyLine.isLimited, isCustomerSpecific: supplyLine.isCustomerSpecific, tradeItemFk: await vnUuid(supplyLine.tradeItemId), }); supplyLine.volumePrices.forEach(async (volumePrice) => { await models.volumePrices.upsert({ supplyLineFk: supplyLine.supplyLineId, unit: volumePrice.unit, pricePerPiece: volumePrice.pricePerPiece, }); }); } catch (error) { if (error.name == 'SequelizeForeignKeyConstraintError') { bar.stop(); console.log('Missing data for the tradeitem: ' + supplyLine.tradeItemId); console.log('Trying to get the tradeitem data...'); await getTradeItem(supplyLine.tradeItemId); return; } } }); bar.stop(); } } catch (error) { console.log('There was an error while saving the data to the database, trying again in 5 seconds...'); await sleep(5000); await getStockBySupplier(supplierId); } } export { getClientToken, updateClientConfig, getJWT, getTradeitems, sleep, asyncQueue, getStock };