protected string FormatQuantities(APIUpdates quantityUpdates, KCBulkProductMaint graph)
        {
            List <string> preparedQties = new List <string>();

            foreach (KCAPIQuantity apiQuantity in quantityUpdates.Updates)
            {
                var dcName = graph.DCById.SelectSingle(apiQuantity.DistributionCenterID);
                if (dcName != null)
                {
                    preparedQties.Add($"{dcName.Code}={apiQuantity.Quantity}");
                }
            }

            return(string.Join(",", preparedQties));
        }
        public List <KCBulkProduct> HandleItems(List <KeyValuePair <string, InventoryItem> > productsForExport,
                                                Dictionary <string, KCAPIInventoryItem> MSMQPrices             = null,
                                                Dictionary <string, List <KCAPIQuantity> > MSMQQuantityUpdates = null)
        {
            if (productsForExport.Count == 0)
            {
                return(new List <KCBulkProduct>(0));
            }

            var dtos           = new List <Tuple <string, InventoryItem, KCBulkProduct> >(productsForExport.Count);
            var availableTasks = Environment.ProcessorCount - 1;
            var itemsToProcess = productsForExport.SplitList((productsForExport.Count / availableTasks) + 1).ToArray();

            Task[] tasks  = new Task[itemsToProcess.Length];
            object locker = new object();

            //force load extensions to slot (GetSlot<PXCacheExtensionCollection>)
            productsForExport[0].Value.GetExtension <InventoryItemPCExt>();

            //get slot data with extensions (slots are located in CallContext)
            var cacheExtensions = CallContext.GetData("PX.Data.PXCacheExtensionCollection");


            for (int x = 0; x < itemsToProcess.Length; x++)
            {
                int index = x;
                var items = itemsToProcess[index];
                tasks[index] = new Task(() =>
                {
                    //Set slot with extensions to other thread
                    CallContext.SetData("PX.Data.PXCacheExtensionCollection", cacheExtensions);
                    var graph = PXGraph.CreateInstance <KCBulkProductMaint>();
                    KCRelationshipSetupMaint relationshipGraph         = PXGraph.CreateInstance <KCRelationshipSetupMaint>();
                    KCClassificationsMappingMaint classificationsGraph = PXGraph.CreateInstance <KCClassificationsMappingMaint>();
                    KCIItemConversionDataMaint conversionGraph         = PXGraph.CreateInstance <KCIItemConversionDataMaint>();
                    KCMapInventoryItem itemMapper = new KCMapInventoryItem(relationshipGraph, classificationsGraph, graph, conversionGraph, _store, logger.LoggerProperties);

                    foreach (KeyValuePair <string, InventoryItem> product in items)
                    {
                        InventoryItem inventoryItem = product.Value;
                        var key = inventoryItem.InventoryCD;

                        KCAPIInventoryItem apiProduct = itemMapper.GetAPIInventoryItem(inventoryItem);
                        if (MSMQPrices != null)
                        {
                            apiProduct = SetPrices(apiProduct, MSMQPrices[key]);
                        }

                        List <string> labels = HandleLabels(inventoryItem, graph);
                        List <(string imagePlacement, string imageUrl)> images = HandleImages(inventoryItem, graph);

                        APIUpdates quantityUpdates;
                        if (MSMQQuantityUpdates?.ContainsKey(key) == true)
                        {
                            quantityUpdates = new APIUpdates()
                            {
                                UpdateType = "InStock",
                                Updates    = MSMQQuantityUpdates[key]
                            };
                        }
                        else
                        {
                            quantityUpdates = HandleQuantities(graph, inventoryItem);
                        }

                        string formattedLabels           = FormatLabels(labels);
                        string formattedImages           = FormatPictureUrls(images);
                        string formattedBundleComponents = FormatBundleComponents(apiProduct?.BundleComponents);
                        string formattedQuantities       = FormatQuantities(quantityUpdates, graph);

                        apiProduct.Labels              = formattedLabels;
                        apiProduct.PictureUrls         = formattedImages;
                        apiProduct.FtpBundleComponents = formattedBundleComponents;
                        apiProduct.DCQuantity          = formattedQuantities;
                        apiProduct.QuantityUpdateType  = "InStock";

                        lock (locker)
                        {
                            dtos.Add(new Tuple <string, InventoryItem, KCBulkProduct>(product.Key, inventoryItem, new KCBulkProduct(apiProduct, HandleAttributes(inventoryItem))));
                        }

                        SetSyncDateTime(inventoryItem, graph);
                        graph.Actions.PressSave();
                    }
                }, cancellationToken);
            }

            tasks.StartAndWaitAll(cancellationToken);
            Thread.Sleep((int)KCConstants.SYNC_DELAY_SECONDS * 1000);

            dtos.Sort(new KCProductTypeComparer());

            List <KCBulkProduct> list = dtos.Select(x => x.Item3).ToList();


            return(list);
        }