| Current Path : /proc/thread-self/root/home/deltalab/PMS/partner-manager-backend/services/ |
| Current File : //proc/thread-self/root/home/deltalab/PMS/partner-manager-backend/services/ims.js |
/* eslint-disable max-len */
/* eslint-disable no-continue */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-await-in-loop */
/* eslint-disable no-use-before-define */
/* eslint-disable new-cap */
/**
* IMS Contacting service
* Speaks with the IMS connector to create, sync and update
*/
require('dotenv').config();
const axios = require('axios');
const { gql, GraphQLClient } = require('graphql-request');
// CONFIGURATION ===================================
const imsEndpoint = process.env.IMS_CONN_URL;
const marketplaceId = process.env.MARKETPLACE_ID;
// ======================================
// MODELS ==========================================
const { listingModel } = require('../models/mongoose/listing');
const { listingBaseModel } = require('../models/mongoose/listing');
const { productModel } = require('../models/mongoose/product');
const { productBaseModel } = require('../models/mongoose/product');
const { warehouseJournalModel } = require('../models/mongoose/warehouse-journal');
const { channelModel } = require('../models/mongoose/channel');
const { partnerModel } = require('../models/mongoose/partner');
const { warehouseModel } = require('../models/mongoose/warehouse');
// WRAPPERS ========================================
/**
* Wrapper for the product creation
* @param {function} resolver The resolver to be wrapped
* @returns The wrapped resolver
*/
function productCreateWrapper(resolver) {
return resolver.wrapResolve((next) => async (rp) => {
// Create the mongo document
// Create the product model based on the given record
const response = await next(rp);
// Sanity check
const product = response.record;
// Call IMS to create a product and get a reference
const imsgid = await createIMSProduct(product);
// Sanity check
if (!imsgid) {
return {
error: { message: `could not create product ${product.sku}` },
};
}
const savedProduct = new productModel(product);
// Assign the reference to the product
savedProduct.imsgid = imsgid;
savedProduct.isNew = false;
await savedProduct.save();
// Save the product
// Create a suitable response
return {
recordId: savedProduct._id,
record: savedProduct,
};
});
}
/**
* Wrapper for the product update
* @param {function} resolver The resolver to be wrapped
* @returns The wrapped resolver
*/
function referenceUpdateWrapper(resolver) {
return resolver.wrapResolve((next) => async (rp) => {
// Update the mongo document
const payload = await next(rp);
const productId = payload.record.imsgid;
const listingsId = payload.record.listingIds;
// If the product is already in the imsgid
if (productId) {
if (listingsId && listingsId.length > 0) {
await addProductToIMSListing(productId, listingsId);
}
const product = new productModel(payload.record);
const response = await updateIMSProduct(payload.record);
if (!response) {
console.log(`Could not update product ${productId} in IMS`);
}
// check the media for required uploads
await checkIMSProductMedia(product);
product.isNew = false; // ensure update only
await product.save();
} else {
// If the product is not in the ims, try and create it there
const imsgid = await createIMSProduct(payload.record);
// Sanity check
if (imsgid) {
const product = new productModel(payload.record);
product.imsgid = imsgid;
// check all the media
await checkIMSProductMedia(product);
product.isNew = false; // ensure update only
await product.save();
} else {
console.log(`Cannot retrieve imsgid for ${payload.record._id}`);
}
}
return payload;
});
}
/**
* Wrapper for the product deletion
* @param {function} resolver The resolver to be wrapped
* @returns The wrapped resolver
*/
function productRemoveByIdWrapper(resolver) {
return resolver.wrapResolve((next) => async (rp) => {
const { _id } = rp.args;
// Look for the product to be removed
const product = await productModel.findById(_id);
// Sanity check
if (!product) {
return { error: { message: `No such product to remove ${_id}` } };
}
// Check the product is mapped in the IMS
if (product.imsgid) {
// Contact the IMS connector and register the callbacks
const imsgid = await deleteIMSProduct(product.imsgid);
if (!imsgid) {
console.log(`cannot remove ims product ${product.imsgid}`);
// return { error: { message: `Cannot remove IMS product ${product.imsgid}` } };
}
}
console.log(`removing product ${product.imsgid}`);
// If everything is fine, just remove it using the mongoose resolver
const payload = await next(rp);
return payload;
});
}
/**
* Filter resolvers for authenticated users
*/
function listingCreateOneWrapper(resolver) {
return resolver.wrapResolve(() => async ({ args }) => {
// Create the mongo document using the standard wrapper
const { record } = args;
// Create the local model based on the created record
const listing = new listingModel();
listing.name = record.name;
listing.handle = record.handle;
listing.partnerId = record.partnerId;
listing.isNew = true; // if isNew is false, the record will only be updated
// Contact the IMS connector and retrieve the imsgid
// const imsgid = await createIMSListing(listing); not needed in magento environment
// Sanity check
// if (!imsgid) {
// return {
// error: { message: `could not create listing ${listing.name}` },
// };
// }
// Prepare the listing for update
// listing.imsgid = imsgid;
// Save the record (the id is the same so it will update the existing entry)
const savedListing = await listing.save();
// Compose the mongoose response object
return {
recordId: savedListing._id,
record: savedListing,
};
});
}
function listingUpdateByIdWrapper(resolver) {
return resolver.wrapResolve((next) => async (rp) => {
const payload = await next(rp);
// const response = await updateIMSListing(payload);
return payload;
});
}
/**
* Remove the entry both locally
*/
function listingRemoveByIdWrapper(resolver) {
return resolver.wrapResolve((next) => async (rp) => {
// Retrieve the listingModel
const { _id } = rp.args;
const listing = await listingModel.findById(_id);
// Sanity check
if (!listing) {
return {
error: { message: `Could not find listing to remove for ${_id}` },
};
}
// Remove the ims listing if one was paired
if (listing.imsgid) {
// Contact the IMS connector and remove the listing
const removeResult = await removeIMSListing(listing.imsgid);
// Verify the listing was removed for consistency
if (!removeResult) {
// Return a readable error to explain the situation
return {
error: {
message: `Could not remove listing ${listing.imsgid} from IMS`,
},
};
}
}
// Remove the mongo document using the standard resolver
const payload = await next(rp);
return payload;
});
}
// SERVICE FUNCTIONS ===============================
/**
* Take the provided product (as a MongoDB document) and send it to the IMSConnector to create it on the IMS
* @param {productModel} product The product to be created in ims
* @returns return the IMS Global Id (imsgid) for the product or null if it was not created
* @deprecated
*/
async function createIMSProduct(product) {
// Prepare a base product to be sent over
const productBase = new productBaseModel(product);
// GraphQL
const graphClient = new GraphQLClient(imsEndpoint);
const createProdQuery = gql`
mutation ($input: ProductBaseInput!){
productCreateOne(input: $input){
imsgid
}
}`;
const args = {
input: productBase,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(createProdQuery, args);
// Sanity check
if (!response.productCreateOne) return null;
// Return the imsgid of the IMS created product
return response.productCreateOne.imsgid;
}
/**
* Take the provided product (as a MongoDB document) and send it to the IMSConnector to update it on the IMS
* @param {Product} product The product to update to the ims
* @returns the imsgid of the updated product
* @deprecated
*/
async function updateIMSProduct(product) {
const productBase = new productBaseModel(product);
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const updateProdQuery = gql`
mutation ($product: ProductBaseInput!){
productUpdateOne(input: $product){
imsgid
}
}`;
const args = {
product: productBase,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(updateProdQuery, args);
if (!response.productUpdateOne) return null;
return response.productUpdateOne.imsgid;
}
/**
* Check all the product media are uploaded.
* If media is correcly uploaded, the imsgid is set and it is not uploaded again
* @param {*} product the reference to the product, save afterwards to preserve the imsgid!
*/
async function checkIMSProductMedia(product) {
// check media content
const mediaLength = product.media.length;
for (let i = 0; i < mediaLength; ++i) {
const productMedia = product.media[i];
// only upload if imsgid is not set
if (!productMedia.imsgid) {
console.log(`going to upload ${productMedia.name} to ${product.title}`);
const imsgid = await uploadIMSProductMedia(product.imsgid, productMedia);
// set the imsgid and save
if (!imsgid) {
console.log(`could not upload ${productMedia.name} to ${product.title}`);
} else {
product.media[i].imsgid = imsgid;
}
}
}
// Look for media in ims that are not in product
const imsMedia = await getIMSProductMedia(product.imsgid);
const toDeleteIds = [];
for (const media of imsMedia) {
let found = false;
for (const productMedia of product.media) {
// Compare the media by imsgid
if (productMedia.imsgid === media.imsgid) {
found = true;
break;
}
}
// If a ims media is not in the product,
// add it to the list to be removed
if (!found) {
console.log(`media ${media.imsgid} is not used and will be removed`);
toDeleteIds.push(media.imsgid);
}
}
// if there are media to be removed
if (toDeleteIds.length > 0) {
console.log(`removing ${toDeleteIds.length} media from ${product.title}`);
await deleteIMSProductMedia(product.imsgid, toDeleteIds);
}
}
/**
* Send the query to upload a media to the IMS
* and associate it to the product
* @param {*} productId
* @param {*} productMedia
* @returns the media ims gid or null if it failed
* @deprecated
*/
async function uploadIMSProductMedia(productId, productMedia) {
// productCreateMedia(
// productId: "123123",
// media:{
// name: "123",
// type: "image/jpeg",
// data: "123123123",
// })
// {
// imsgid
// }
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const uploadMediaQuery = gql`
mutation ($productId: ID!, $media: MediaInput!){
productCreateMedia (productId: $productId, media: $media){
imsgid
}
}`;
const args = {
productId,
media: productMedia,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(uploadMediaQuery, args);
if (!response.productCreateMedia) return null;
return response.productCreateMedia.imsgid;
}
/**
* Remove the given image from the product
* @param {string} productId
* @param {[string]} imsgids
* @deprecated
*/
async function deleteIMSProductMedia(productId, imsgids) {
// mutation deletemedia {
// productDeleteMedia(productId:"gid://shopify/Product/6734676787376",
// mediaIds: []
// ) {
// imsgid
// }
// }
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const deleteMediaQuery = gql`
mutation ($productId: ID!, $mediaIds: [ID!]!) {
productDeleteMedia (
productId: $productId,
mediaIds: $mediaIds )
{
imsgid
}
}`;
const args = {
productId,
mediaIds: imsgids,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(deleteMediaQuery, args);
if (!response.productDeleteMedia) return null;
return response.productDeleteMedia;
}
/**
* Retrieve the ims product media ids
* @param {string} imsgid the product imsgid
* @deprecated
*/
async function getIMSProductMedia(imsgid) {
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const productQuery = gql`
query ($productId: ID!) {
productMedia (productId: $productId) {
imsgid
}
}`;
const args = {
productId: imsgid,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(productQuery, args);
return response.productMedia;
}
/**
* Take the provided product (as a MongoDB document) and send it to the IMSConnector to delete it off the IMS
* @param {Product} record The product to update
* @returns the id of the removed product or null if error
* @deprecated
*/
async function deleteIMSProduct(imsgid) {
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const deleteProdQuery = gql`
mutation ($id: ID!){
productDeleteById(id: $id){
imsgid
}
}`;
const args = {
id: imsgid,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(deleteProdQuery, args);
if (!response.productDeleteById) return null;
return response.productDeleteById.imsgid;
}
/**
* Create
* @param {listingModel} listing
* @returns imsgid
*/
async function createIMSListing(listing) {
const listingBase = new listingBaseModel(listing);
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const createListingQuery = gql`
mutation listingCreateOne($input: ListingBaseInput!) {
listingCreateOne(input: $input) {
imsgid
}
}`;
const args = {
input: listingBase,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(createListingQuery, args);
// Sanity check
if (!response.listingCreateOne) return null;
return response.listingCreateOne.imsgid;
}
/**
* Take the provided listing (as a MongoDB document) and send it to the IMSConnector to update it on the IMS
* @param {Listing} record The listing to update to the ims
* @returns the imsgid of the updated listing
*/
async function updateIMSListing(listingInput) {
const listingData = new listingModel(listingInput.record);
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const updateListQuery = gql`
mutation ($listing: ListingInput!){
listingUpdateOne(input: $listing){
imsgid
}
}`;
const args = {
listing: listingData,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(updateListQuery, args);
if (!response.listingUpdateOne) return null;
return response.listingUpdateOne.imsgid;
}
/**
* Request the IMS to remove he listing with the given imsgid
* @param {*} imsgid
* @returns the imsgid of he removed listing or null
*/
async function removeIMSListing(imsgid) {
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const createListingQuery = gql`
mutation ($imsgid: String!) {
listingDeleteById(imsgid: $imsgid) {
imsgid
}
}`;
const args = {
imsgid,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(createListingQuery, args);
// Sanity check
if (!response.listingDeleteById) return null;
return response.listingDeleteById.imsgid;
}
/**
* @param {*} imsgid
* @param {*} quantity
* @deprecated
*/
async function adjustProductQuantity(imsgid, quantity) {
// GraphQL related stuff
const graphClient = new GraphQLClient(imsEndpoint);
const adjustQuantityQuery = gql`
mutation ($imsgid: ID!, $quantity: Float!) {
productAdjustQuantity(imsgid: $imsgid, quantity: $quantity) {
imsgid,
availability
}
}`;
const args = {
imsgid,
quantity,
};
// Send request to IMS connector using the ad-hoc client
const response = await graphClient.request(adjustQuantityQuery, args);
// Sanity check
if (!response.productAdjustQuantity) return null;
return response.productAdjustQuantity;
}
// INTERNAL FUNCTIONS ==============================================
async function addProductToIMSListing(productImsgid, listingIds) {
let success = true;
// Add the product to all the listings
for (const listingId of listingIds) {
const listing = await listingModel.findById(listingId);
if (!listing) {
console.log(`cannot add ${productImsgid} to ${listingId}: no such listing`);
continue;
}
const response = await addProductToListing(listing.imsgid, productImsgid);
success = success && (response != null);
}
return success;
}
async function removeProductFromIMSListing(productImsgid, listingIds) {
let success = true;
// Add the product to all the listings
for (const listingId of listingIds) {
const listing = await listingModel.findById(listingId);
const response = await removeProductFromListing(listing.imsgid, productImsgid);
success = success && (response != null);
}
return success;
}
async function addProductToListing(listingId, productId) {
const graphClient = new GraphQLClient(imsEndpoint);
const addProductToCollection = gql`
mutation ($imsgid: String, $products:[String!]!) {
listingAddProducts(imsgid:$imsgid, products:$products) {
imsgid
}
}`;
const args = {
imsgid: listingId,
products: [productId],
};
const response = await graphClient.request(addProductToCollection, args);
if (!response.listingAddProducts) return null;
return response.listingAddProducts.imsgid;
}
async function removeProductFromListing(listingId, productId) {
const graphClient = new GraphQLClient(imsEndpoint);
const removeProductFromCollection = gql`
mutation ($imsgid: String, $products:[String!]!) {
listingRemoveProducts(imsgid:$imsgid, products:$products) {
imsgid
}
}`;
const args = {
imsgid: listingId,
products: [productId],
};
const response = await graphClient.request(removeProductFromCollection, args);
if (!response.listingRemoveProducts) return null;
return response.listingRemoveProducts.imsgid;
}
async function addEntryToWarehouseJournal(warehouseId, productId, partnerId, variation, totalQuantity, reason, refrigerated, confirmed = false, transfer = false, omsgid = '', origin = undefined) {
const journalEntry = new warehouseJournalModel();
journalEntry.date = new Date();
journalEntry.warehouseId = warehouseId;
if (transfer) {
journalEntry.warehouseOriginId = origin;
}
journalEntry.productId = productId;
journalEntry.partnerId = partnerId;
journalEntry.variation = variation;
journalEntry.totalQuantity = totalQuantity;
journalEntry.reason = reason;
journalEntry.refrigerated = refrigerated;
journalEntry.confirmed = confirmed || !(reason === 'INCREASE' || reason === 'INITIAL'); //if I add new stock to a warehouse, it has to be confirmed before being put on sale
journalEntry.transfer = transfer;
journalEntry.omsgid = omsgid;
await journalEntry.save();
}
async function createReferenceBooking(productId, warehouseId, variation, omsgid) {
const product = await productModel.findById(productId);
if (product) {
let found = false;
for (let j = 0; j < product.inventoryLevels.length; j++) {
if (product.inventoryLevels[j].warehouseId.equals(warehouseId)) {
found = true;
if (omsgid) {
if (!product.inventoryLevels[j].bookings) {
product[j].bookings = [];
}
product.inventoryLevels[j].bookings.push({ amount: variation, omsgid, fulfilled: false });
} else {
product.inventoryLevels[j].amount += variation;
}
await product.save();
console.log('AGGIORNATO PRODOTTO');
break;
}
}
if (!found) {
console.log(`Order from channel without transfer needed, adding it with ${variation} starting quantity`);
product.inventoryLevels.push({ warehouseId, amount: 0, bookings: [{ amount: variation, omsgid, fulfilled: false }] });
await product.save();
}
}
}
// REST ===========================================
async function createIMSProductRest(reference, channelId) {
// retrieve channel from product
const channel = await channelModel.findOne({ _id: channelId });
const websiteIds = await getWebsiteIdList(reference, channel);
// get partner to send
const partner = await partnerModel.findOne({ _id: reference.partnerId });
// Prepare a base product to be sent over
const productBase = new productBaseModel(reference);
const payload = {
product: productBase,
channel,
partner,
websiteIds,
};
// const response = await axios.post(`${imsEndpoint}/rest/products`, payload);
const response = await axios.post(`${channel.imsAddress}/rest/products`, payload);
// Sanity check
if (!response.data.success) return null;
// Return the imsgid of the IMS created product
return response.data.data.id;
}
async function productCreate(product, channelId) {
const imsgid = await createIMSProductRest(product, channelId);
// Sanity check
if (!imsgid) {
throw new Error(`could not create product ${product.sku}`);
}
return imsgid;
}
async function getStoreFromName(storeName) {
const response = await axios.get(`${imsEndpoint}/rest/stores/${storeName}`);
if (!response.data.success) {
throw new Error(`could not delete product ${storeName}`);
}
return response.data;
}
async function uploadImageToProduct(sku, filename, extension, base64Data) {
// Prepare a base product to be sent over
const address = await retrieveImsEndpointFromSKU(sku);
const url = `${address}/rest/products/uploadImage`;
const dataObj = {
sku,
filename,
extension,
data: base64Data,
};
const response = await axios.post(url, dataObj);
// Sanity check
if (!response.data) return null;
return response.data;
}
async function deleteMediafromProduct(sku, imsgid) {
const address = await retrieveImsEndpointFromSKU(sku);
// Prepare a base product to be sent over
const url = `${address}/rest/products/deleteImage/${sku}/${imsgid}`;
const response = await axios.delete(url);
// Sanity check
if (!response.data.success) return null;
return response.data.success;
}
async function updateIMSReferenceRest(reference, sku) {
// Prepare a base product to be sent over
const productBase = new productBaseModel(reference);
const partner = await partnerModel.findOne({ _id: reference.partnerId });
for (let i = 0; i < reference.offers.length; i++) {
const offer = reference.offers[i];
const channel = await channelModel.findById(offer.channelId);
const websiteIds = await getWebsiteIdList(reference, channel);
const linkedProducts = await getLinkedProducts(reference, channel);
productBase.price = offer.price;
productBase.imsCategories = offer.imsCategories;
productBase.deleted = offer.deleted;
productBase.title = offer.title;
productBase.customDescription = offer.description;
productBase.attributes = offer.attributes;
// validity
productBase.imsEnabled = productBase.imsEnabled ? offer.imsEnabled : false;
// VALID FROM
if (productBase.isValidFrom && offer.isValidFrom) {
productBase.isValidFrom = true;
productBase.validFrom = productBase.validFrom > offer.validFrom ? offer.validFrom : productBase.validFrom;
}
if (!productBase.isValidFrom && offer.isValidFrom) {
productBase.isValidFrom = true;
productBase.validFrom = offer.validFrom;
}
// VALID UNTIL
if (productBase.isValidUntil && offer.isValidUntil) {
productBase.isValidUntil = true;
productBase.validUntil = productBase.validUntil < offer.validUntil ? productBase.validUntil : offer.validUntil;
}
if (!productBase.isValidUntil && offer.isValidUntil) {
productBase.isValidUntil = true;
productBase.validUntil = offer.validUntil;
}
const quantity = await getQuantityByChannel(reference, channel);
const dataObj = {
referenceUpdate: productBase,
partner,
linkedProducts,
channel,
quantity,
websiteIds,
};
// const url = product.deleted ? `${imsEndpoint}/rest/products/delete/${sku}` : `${imsEndpoint}/rest/products/${sku}`;
const url = reference.deleted || offer.deleted ? `${channel.imsAddress}/rest/products/delete/${offer.sku}` : `${channel.imsAddress}/rest/products/${offer.sku}`;
const response = await axios.put(url, dataObj);
// Sanity check
if (!response.data.success) return null;
// Return the imsgid of the IMS created product
// return response.data.data.id;
}
return true;
}
async function getQuantityByChannel(reference, channel, omsgid = undefined) {
let totalQuantity = 0;
for (let i = 0; i < reference.inventoryLevels.length; i++) {
const inventoryLevel = reference.inventoryLevels[i];
const warehouse = await warehouseModel.findById({ _id: inventoryLevel.warehouseId });
if (warehouse) {
for (let j = 0; j < warehouse.channelAssignments.length; j++) {
if (warehouse.channelAssignments[j].channelId.equals(channel._id)) {
console.log(`[QUANTITA] - Aggiungo ${inventoryLevel.amount} come giacenza iniziale`);
totalQuantity += inventoryLevel.amount;
if (inventoryLevel.bookings) {
for (let k = 0; k < inventoryLevel.bookings.length; k++) {
if (omsgid && inventoryLevel.bookings[k].omsgid !== omsgid) {
continue;
}
if (!inventoryLevel.bookings[k].fulfilled) {
console.log(`[QUANTITA] - Trovata prenotazione aperta, aggiungo se è del canale giusto`);
if (inventoryLevel.bookings[k].channelId) {
if (inventoryLevel.bookings[k].channelId === channel._id.toString()) {
console.log(`[QUANTITA] - Aggiungo ${inventoryLevel.bookings[k].amount} come prenotazione`);
totalQuantity += inventoryLevel.bookings[k].amount;
console.log(`[QUANTITA] - Totale parziale: ${totalQuantity}`);
}
}
}
}
}
//add quantity from pending transactions
totalQuantity += await readPendingTransactionsQuantity(reference._id, warehouse._id, omsgid);
}
}
}
}
console.log(`[QUANTITA] - TOTAL QUANTITY FOR ${reference.title} IS ${totalQuantity}`);
return totalQuantity;
}
async function readPendingTransactionsQuantity(referenceId, warehouseId, omsgid = undefined) {
const requestBody = {
confirmed: false,
reason: 'INCREASE',
productId: referenceId,
warehouseId,
};
if (omsgid) {
requestBody.omsgid = omsgid;
}
const warehouseJournalList = await warehouseJournalModel.find(requestBody);
let quantity = 0;
for (let i = 0; i < warehouseJournalList.length; i++) {
quantity += warehouseJournalList[i].variation;
}
return quantity;
}
async function getLinkedProducts(product, channel) {
if (channel._id.toString() !== process.env.MARKETPLACE_ID) {
console.log('[LINKED PRODUCTS] Il canale non è quello giusto!');
return [];
}
//TODO: ricarico referenza per avere dati puliti
const reference = await productModel.findById(product._id);
const recomms = [];
if (reference && reference.linkedProducts) {
console.log(`[LINKED PRODUCTS] Ce ne sono! ${reference.linkedProducts.length}`);
for (let i = 0; i < reference.linkedProducts.length; i++) {
const recommendedProduct = await productModel.findById(reference.linkedProducts[i].productId);
if (recommendedProduct && recommendedProduct.offers) {
for (let j = 0; j < recommendedProduct.offers.length; j++) {
if (recommendedProduct.offers[j].channelId.toString() === process.env.MARKETPLACE_ID) {
console.log('[LINKED PRODUCTS] Canale corretto!');
recomms.push(recommendedProduct.offers[j].sku);
}
}
}
}
}
console.log(`[LINKED PRODUCTS] ${recomms.length}`);
return recomms;
}
async function referenceUpdate(reference, sku) {
const updatedProduct = await updateIMSReferenceRest(reference, sku);
// Sanity check
if (!updatedProduct) {
throw new Error(`could not update product ${reference.sku}`);
}
return updatedProduct;
}
async function isOfferInIMS(offer) {
const channel = await channelModel.findOne({ _id: offer.channelId });
const response = await axios.get(`${channel.imsAddress}/rest/products/imsProduct/${offer.sku}/${channel.storeName}`);
if (!response.data.success) {
throw new Error('could not find product in store by sku');
}
return response.data.data;
}
async function productDelete(referenceSku) {
const address = await retrieveImsEndpointFromSKU(referenceSku);
const response = await axios.delete(`${address}/rest/products/${referenceSku}`);
if (!response.data.success) {
throw new Error(`could not delete product ${referenceSku}`);
}
}
async function getAttributeSets(storeName, channelId) {
const channel = await channelModel.findOne({ _id: channelId });
const response = await axios.get(`${channel.imsAddress}/rest/attribute-sets/${storeName}`);
if (!response.data.success) {
throw new Error('could not find attribute sets');
}
return response.data.data;
}
async function readAttributeSetByName(storeName, name, channelId) {
const channel = await channelModel.findOne({ _id: channelId });
const response = await axios.get(`${channel.imsAddress}/rest/attribute-sets/${storeName}/${name}`);
if (!response.data.success) {
throw new Error(`Cannot find attribute set ${name}`);
}
return response.data.data;
}
async function getCategories(storeName, channelId) {
const channel = await channelModel.findOne({ _id: channelId });
const response = await axios.get(`${channel.imsAddress}/rest/categories/${storeName}`);
if (!response.data.success) {
throw new Error('could not find categories');
}
return response.data.data;
}
async function getCategoryById(storeName, id, channelId) {
const channel = await channelModel.findOne({ _id: channelId });
const response = await axios.get(`${channel.imsAddress}/rest/categories/${storeName}/${id}`);
if (!response.data.success) {
throw new Error(`Cannot find category ${id}`);
}
return response.data.data;
}
async function getCategoryByName(storeName, id, channelId) {
const channel = await channelModel.findOne({ _id: channelId });
const response = await axios.get(`${channel.imsAddress}/rest/categories/${storeName}/filter/${id}`);
if (!response.data.success) {
throw new Error(`Cannot find category ${id}`);
}
return response.data.data;
}
async function getTaxCodes(storeName, channelId) {
const channel = await channelModel.findOne({ _id: channelId });
const response = await axios.get(`${channel.imsAddress}/rest/tax/${storeName}`);
if (!response.data.success) {
throw new Error('could not find tax codes');
}
return response.data.data;
}
async function getBuyButton(productSKUs, channel) {
const address = await retrieveImsEndpointFromSKU(productSKUs[0]);
// Prepare a base product to be sent over
const url = `${address}/rest/buybutton`;
const dataObj = {
productSKUs,
channel,
};
const response = await axios.post(url, dataObj);
// Sanity check
if (!response.data) return null;
return response.data;
}
// UTILITIES
async function retrieveImsEndpointFromSKU(sku) {
const product = await productModel.find({ sku });
if (product) {
const channel = await channelModel.findById(product.channelId);
if (channel) {
return channel.imsAddress;
}
}
return '';
}
async function getWebsiteIdList(product, channel) {
const websiteIds = [];
for (let i = 0; i < product.offers.length; i++) {
const offer = product.offers[i];
const offerChannel = await channelModel.findById(offer.channelId);
if (offerChannel) {
if (offerChannel.prefix === channel.prefix) { // load all the websites of the channel where this product is sold
websiteIds.push(offerChannel.websiteId);
}
}
}
const ids = [...new Set(websiteIds)];
return ids;
}
// EXPORTS =========================================
module.exports = {
// GraphQL wrappers
productCreateWrapper,
referenceUpdateWrapper,
productRemoveByIdWrapper,
removeProductFromIMSListing,
listingCreateOneWrapper,
listingUpdateByIdWrapper,
listingRemoveByIdWrapper,
// APIs
addProductToIMSListing,
addProductToListing,
createIMSListing,
createIMSProduct,
deleteIMSProduct,
removeIMSListing,
removeProductFromListing,
updateIMSProduct,
updateIMSListing,
getIMSProductMedia,
deleteIMSProductMedia,
adjustProductQuantity,
addEntryToWarehouseJournal,
createReferenceBooking,
// REST
productCreate,
referenceUpdate,
productDelete,
uploadImageToProduct,
deleteMediafromProduct,
isOfferInIMS,
getQuantityByChannel,
// REST ATTRIBUTE SETS
getAttributeSets,
readAttributeSetByName,
// REST CATEGORIES
getCategories,
getCategoryById,
getCategoryByName,
// REST TAX CODES
getTaxCodes,
// REST STORES
getStoreFromName,
// BUYBUTTON
getBuyButton,
};