Untitled

mail@pastecode.io avatar
unknown
plain_text
a month ago
6.8 kB
5
Indexable
Never
const dotenv = require('dotenv');
const Shopify = require('shopify-api-node');
const Bottleneck = require('bottleneck');
const cron = require('node-cron');

dotenv.config();

const INVENTORY_BUFFER = 2;

const sourceShopify = new Shopify({
    shopName: process.env.SOURCE_SHOP_NAME,
    accessToken: process.env.SOURCE_SHOPIFY_ACCESS_TOKEN,
    accessMode: 'private',
    apiVersion: '2021-07',
    scopes: ['read_inventory', 'read_products']
});

const destinationShopify = new Shopify({
    shopName: process.env.DESTINATION_SHOP_NAME,
    accessToken: process.env.DESTINATION_SHOPIFY_ACCESS_TOKEN,
    accessMode: 'private',
    apiVersion: '2024-04',
    scopes: ['read_inventory', 'write_inventory', 'read_products', 'write_products']
});

sourceShopify.shop.get()
    .then(shop => {
        console.log('Connected to Source Shopify successfully.');
    })
    .catch(err => {
        console.error('Failed to connect to Source Shopify:', err);
    });

destinationShopify.shop.get()
    .then(shop => {
        console.log('Connected to Destination Shopify successfully.');
    })
    .catch(err => {
        console.error('Failed to connect to Destination Shopify:', err);
    });

// LIST IS ONLY FETCHING FIRST 50 PRODUCTS, ADD RATE LIMIT FOR SHOPIFY API

// Function to fetch all source products inventory
async function getAllProducts() {
    try {
        let params = { limit: 250 };
        let allRecords = [];
        do {
            const products = await sourceShopify.product.list(params);
            allRecords = allRecords.concat(products)
            params = products.nextPageParameters;
        } while (params !== undefined);
        console.log(`${allRecords.length} products found in the source store.`);
        let allData = [];
        for(let product of allRecords){
            let allVariants = product.variants.map( i => ({
                id: product.id,
                title: product.title,
                SKU: i.sku,
                barcode: i.barcode,
                inventory: i.inventory_quantity > 0 ? i.inventory_quantity - INVENTORY_BUFFER : 0
            }))
            allData = [...allData, ...allVariants]
        }
        return allData;

    } catch (error) {
        console.error('Failed to fetch products:', error);
        return [];
    }
}

async function getAllProductsFromDestination() {
    try {
        let params = { limit: 250 };
        let allRecords = [];
        do {
            const products = await destinationShopify.product.list(params);
            allRecords = allRecords.concat(products)
            params = products.nextPageParameters;
        } while (params !== undefined);
        const allDestinationProducts = allRecords.filter(i => i.status === "active");
        console.log(`${allDestinationProducts.length} active products found in the destination store.`);
        return allDestinationProducts;
    } catch (error) {
        console.error('Failed to fetch products:', error);
        return [];
    }
}

async function updateForMultipleLocation(productFromDestination, prod, inventoryList) {
    try {
        inventoryList.forEach(async i => {
            await destinationShopify.inventoryLevel.set({ inventory_item_id: productFromDestination.inventory_item_id, location_id: i.location_id, available: prod.inventory })
            console.log(`Successfully updated for location ${i.location_id}`)
        })
    }
    catch (err) {
        console.log(err);
    }
}


// UPDATE INVENTORY NOT WORKING

// Function to update inventory in destination store
async function updateDestinationStore(products) {
    try {
        // Fetch product list from the destination store
        const destinationProducts = await getAllProductsFromDestination();
        
        for (const desProduct of destinationProducts) {
            let prod = {};
            let prodBarcode = '';
            const productFromDestination = desProduct.variants.find(i =>
                products.some(p => {
                    prodBarcode = p.barcode
                    if (p?.barcode === i?.barcode) {
                        prod = p;
                        return true
                    }
                })
            )
            // console.log(prod, "EXISTS", productFromDestination)
            if (productFromDestination) {
                const res = await destinationShopify.inventoryItem.get(productFromDestination.inventory_item_id)
                if (res.tracked) {
                    const inventoryList = await destinationShopify.inventoryLevel.list({ inventory_item_ids: productFromDestination.inventory_item_id })
                    if (inventoryList.length > 1) {
                        await updateForMultipleLocation(productFromDestination, prod, inventoryList);
                    } else {
                        const inventoryLocation = inventoryList[0].location_id;
                        await destinationShopify.inventoryLevel.set({ inventory_item_id: productFromDestination.inventory_item_id, location_id: inventoryLocation, available: prod.inventory })
                        console.log(`Inventory updated for product ${productFromDestination.title} with barcode ${productFromDestination.barcode}`);
                    }
                } else {
                    console.error(`Tracking not enabled for the inventory item ${productFromDestination.inventory_item_id} with variant barcode ${productFromDestination.barcode}`)
                }
            } else {
                console.error(`No matching products found in source table  ${prodBarcode ? "for barcode" + prodBarcode : "because barcode value is null"}`);
            }
        }
    } catch (error) {
        if (error.response && error.response.statusCode === 403) {
            console.error('Failed to update inventory in the destination store: You do not have permission to perform this action.');
        } else {
            console.error(`Failed to update inventory for product with barcode in the destination store:`, error);
        }
    }
}

// Rate limiting configuration
const limiter = new Bottleneck({
    maxConcurrent: 1, // Number of concurrent requests
    minTime: 2000 // Minimum time between each request (in milliseconds)
});

//Fetch products and update inventory with rate limiting
function cronJobEveryHour(){
limiter.schedule(getAllProducts)
    .then(products => {
        console.table(products);
        return updateDestinationStore(products);
    })
    .catch(error => {
        console.error('Error during product fetch or inventory update:', error);
    });
}

cron.schedule('0 0 */1 * * *', cronJobEveryHour);

cronJobEveryHour();
Leave a Comment