floriday/utils.js

603 lines
20 KiB
JavaScript

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