Stock is now being obtained

This commit is contained in:
Pau 2023-01-16 14:52:08 +01:00
parent 2de5e10e3e
commit 6c8230d1a0
9 changed files with 334 additions and 23 deletions

View File

@ -1,6 +1,6 @@
# Floriday
Requires [Node.js](https://nodejs.org/en/) v14.x.x or higher.
Requires [Node.js](https://nodejs.org/en/) v15.14.0 or higher.
The Floriday service project should perform the following tasks:

View File

@ -15,36 +15,51 @@ try {
if (process.env.QUERYSUPPLIERS) {
process.env.HowManySuppliers ??= suppliers.suppliers.length;
console.log('Querying suppliers...');
console.log(suppliers.suppliers.length + ' suppliers found');
console.log(process.env.HowManySuppliers + ' suppliers will be queried.');
const bar1 = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);
bar1.start(suppliers.suppliers.length, 0);
bar1.start(process.env.HowManySuppliers, 0);
let functionQueue = [];
// vnUtils.getTradeitems(suppliers.suppliers[i].SupplierGLN)
for (let i = 0; i < suppliers.suppliers.length; i++) {
await vnUtils.getTradeitems(suppliers.suppliers[i].SupplierGLN).then(() => {
for (let i = 0; i < process.env.HowManySuppliers; i++) {
functionQueue.push(async () => {
await vnUtils.getTradeitems(suppliers.suppliers[i].SupplierGLN);
bar1.increment();
});
}
await vnUtils.asyncQueue(functionQueue, 10);
bar1.stop();
console.log('Done querying trade items.');
}
setInterval(async () => {
// eslint-disable-next-line no-constant-condition
while (true) {
console.log('Querying the API to check for new data...');
console.log('Current token expiration date: ', tokenExpirationDate);
await vnUtils.getStock();
if (moment().isAfter(tokenExpirationDate)) {
console.log('Token expired, getting a new one...');
tokenExpirationDate = await vnUtils.getClientToken(models);
}
if (process.env.STATUS == 'development') {
await vnUtils.sleep(15000);
} else {
await vnUtils.sleep(30000);
}
}
}, process.env.STATUS == 'development' ? 15000 : 30000);
} catch (error) {
console.error('Unable to connect to the database:', error);
}

View File

@ -13,7 +13,7 @@ const clientConfig = {
type: Sequelize.STRING,
},
currentToken: {
type: Sequelize.STRING(5000),
type: Sequelize.STRING(2000),
},
tokenExpiration: {
type: Sequelize.STRING,

View File

@ -1,6 +1,5 @@
import { Sequelize } from 'sequelize';
import dotenv from 'dotenv';
import {sleep} from '../utils.js';
dotenv.config();
let sequelize = createConnection();
@ -14,6 +13,9 @@ import seasonalPeriod from './seasonalPeriod.js';
import tradeItem from './tradeItem.js';
import clientConfig from './clientConfig.js';
import characteristics from './characteristics.js';
import supplyLine from './supplyLine.js';
import volumePrices from './volumePrices.js';
import suppliers from './suppliers.js';
/**
* Contains all the models that are related to the application.
@ -45,6 +47,9 @@ let models = {
seasonalPeriod: seasonalPeriod(sequelize),
clientConfig: clientConfig(sequelize),
botanicalNames: botanicalNames(sequelize),
supplyLines: supplyLine(sequelize),
volumePrices: volumePrices(sequelize),
suppliers: suppliers(sequelize),
};
models.characteristics.belongsTo(models.tradeItem, {
@ -108,6 +113,18 @@ models.countryOfOriginIsoCodes.belongsTo(models.tradeItem, {
targetKey: 'tradeItemId',
});
models.volumePrices.belongsTo(models.supplyLines, {
foreignKey: 'supplyLineFk',
as: 'supplyLine_Fk',
targetKey: 'supplyLineId',
});
models.supplyLines.belongsTo(models.tradeItem, {
foreignKey: 'tradeItemFk',
as: 'tradeItem_Fk',
targetKey: 'tradeItemId',
});
if (process.env.FORCE_SYNC) {
console.log('Syncing the models...');
await sequelize.sync({ force: true });
@ -135,7 +152,7 @@ function createConnection() {
dialect: process.env.DB_DIALECT,
logging: false,
pool: {
max: 20,
max: 40,
min: 0,
acquire: 60000,
idle: 10000,

24
models/suppliers.js Normal file
View File

@ -0,0 +1,24 @@
import { Sequelize } from 'sequelize';
const suppliers = {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
supplierId: {
type: Sequelize.STRING,
unique: true,
},
supplierGln: {
type: Sequelize.STRING,
},
};
export default (sequelize) => {
const Suppliers = sequelize.define('FDsuppliers', suppliers, {
timestamps: false,
freezeTableName: true,
});
return Suppliers;
};

68
models/supplyLine.js Normal file
View File

@ -0,0 +1,68 @@
import { Sequelize } from 'sequelize';
const supplyLine = {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
supplyLineId: {
type: Sequelize.STRING,
unique: true,
},
status: {
type: Sequelize.STRING,
},
supplierOrganizationId: {
type: Sequelize.STRING,
},
pricePerPiece: {
type: Sequelize.JSON,
},
numberOfPieces : {
type: Sequelize.INTEGER,
},
deliveryPeriod: {
type: Sequelize.JSON,
},
orderPeriod: {
type: Sequelize.JSON,
},
wharehouseId: {
type: Sequelize.STRING,
},
sequenceNumber: {
type: Sequelize.INTEGER,
},
type: {
type: Sequelize.STRING,
},
isDeleted: {
type: Sequelize.BOOLEAN,
},
salesUnit: {
type: Sequelize.STRING,
},
agreementReferenceCode: {
type: Sequelize.STRING,
},
agreementReferenceDescription: {
type: Sequelize.STRING,
},
isLimited: {
type: Sequelize.BOOLEAN,
},
isCustomerSpecific: {
type: Sequelize.BOOLEAN,
}
};
export default (sequelize) => {
const SupplyLine = sequelize.define('FDsupplyLine', supplyLine, {
timestamps: false,
freezeTableName: true,
});
return SupplyLine;
};

View File

@ -13,6 +13,9 @@ const tradeItem = {
supplierOrganizationId: {
type: Sequelize.STRING
},
supplierOrganizationUuid: {
type: Sequelize.STRING
},
code: {
type: Sequelize.STRING
},

23
models/volumePrices.js Normal file
View File

@ -0,0 +1,23 @@
import { Sequelize } from 'sequelize';
const volumePrices = {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
unit: {
type: Sequelize.STRING,
},
pricePerPiece: {
type: Sequelize.INTEGER,
},
};
export default (sequelize) => {
const VolumePrices = sequelize.define('FDvolumePrices', volumePrices, {
timestamps: false,
freezeTableName: true,
});
return VolumePrices;
};

181
utils.js
View File

@ -11,6 +11,8 @@ dotenv.config();
*/
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
*
@ -121,6 +123,63 @@ async function sleep(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
@ -135,8 +194,6 @@ async function getTradeitems(organizationGln) {
'X-Api-Key': process.env.API_KEY,
};
const BASE_CUSTOMER_URL = 'https://api.staging.floriday.io/customers-api/2022v2/';
// Get the organization id from the organizationGln
const organizationsUrl = `${BASE_CUSTOMER_URL}organizations/gln/${organizationGln}`;
@ -181,11 +238,17 @@ async function getTradeitems(organizationGln) {
try {
item.tradeItemId = 'Vn-' + item.tradeItemId;
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,
@ -214,9 +277,9 @@ async function getTradeitems(organizationGln) {
});
await item.photos.forEach((photo) => {
await item.photos.forEach(async (photo) => {
photo.id = 'Vn-' + photo.id;
photo.id = await vnUuid(photo.id);
models.photos.upsert({
tradeItemFk: item.tradeItemId,
@ -231,7 +294,7 @@ async function getTradeitems(organizationGln) {
await item.packingConfigurations.forEach(async (packagingConfiguration) => {
let uuid = uuidv4();
uuid = 'Vn-' + uuid;
uuid = await vnUuid(uuid);
await models.packingConfigurations.upsert({
tradeItemFk: item.tradeItemId,
@ -270,7 +333,9 @@ async function getTradeitems(organizationGln) {
});
} catch (error) {
console.log('There was an error while saving the data to the database');
console.log('Missing data for the tradeitem: ' + item.tradeItemId);
bar.stop();
return;
}
});
@ -278,9 +343,105 @@ async function getTradeitems(organizationGln) {
}
} catch (error) {
console.log('There was an error while saving the data to the database');
throw new Error(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);
}
}
export { getClientToken, updateClientConfig, getJWT, getTradeitems, sleep };
async function getStock() {
const suppliers = await models.suppliers.findAll();
const supplierQueue = [];
suppliers.forEach(async (supplier) => {
supplierQueue.push(await getStockBySupplier(supplier.supplierId));
});
await asyncQueue(supplierQueue);
}
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}`;
const stockRequest = await fetch(stockUrl, {
method: 'GET',
headers: headers,
});
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();
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),
});
await models.volumePrices.upsert({
unit: supplyLine.volumePrices.unit,
price: supplyLine.volumePrices.price,
supplyLineFk: await vnUuid(supplyLine.supplyLineId),
});
} catch (error) {
if (error.name == 'SequelizeForeignKeyConstraintError') {
console.log('Missing data for the tradeitem: ' + supplyLine.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 getStockBySupplier(supplierId);
}
}
export { getClientToken, updateClientConfig, getJWT, getTradeitems, sleep, asyncQueue, getStock };