private int SyncProducts(ConnectsterUser user) { int returnCount = 0; //Ask the db for credentials ShopifyStoreAuth shopifyAuth = _database.SelectShopifyUserDetails(user); ShopsterUser shopsterUser = _database.SelectShopsterUserDetails(user); var apiContext = new ConnectsterApiContext { AccessToken = shopsterUser.AuthToken, AccessTokenSecret = shopsterUser.AuthSecret }; List<InventoryItemType> shopsterList; ConnectsterProductMap productMap; List<ShopifyProduct> shopifyItems; ShopifyMetafieldMap shopifyMapping; //Todo: Decide the lowest DetailGroup needed try { shopsterList = _shopsterComm.GetAllInventoryItemsForUser(apiContext, "All").Where( item => item.IsStoreVisible == true).Select(item => item).ToList(); productMap = new ConnectsterProductMap(user); shopifyItems = _shopifyComm.GetAllProducts(shopifyAuth); shopifyMapping = new ShopifyMetafieldMap(_shopifyComm, shopifyAuth); } catch (Exception e) { logger.FatalFormat( "ConnectsterController::SyncProducts(): Exception while creating lists. Exception is ({0}).", e.Message); return 0; //todo : throw our own exception class to the program to indicate the user should be disabled or ignored for a while. } if (shopifyItems == null) { //todo : again we need to throw our own exception class to indicate the user should be disabled or ignored for a while. return 0; } //create these lists here to reduce redundancy in proceeding calls. List<int> shopsterIds = shopsterList.Select(item => Convert.ToInt32(item.ItemId)).ToList(); List<int> shopifyIds = shopifyItems.Select(item => (int) item.Id).ToList(); _database.UpdateShopsterProductTimeStamps(shopsterList, DateTime.UtcNow); #if DEBUG //Some debugging info foreach (ConnectsterProduct mappedItem in productMap.GetMappedItems()) { logger.DebugFormat( "ConnectsterController::CreateListsAndBegin(): ShopsterifyDB has ShopsterId({0}, Date({1:yyyy-MM-dd HH:mm:ss}) \tMapped to \t ShopifyId({2}, Date({3:yyyy-MM-dd HH:mm:ss})", mappedItem.SourceId, mappedItem.SourceDate, mappedItem.DestinationId, mappedItem.DestinationDate); } foreach (ShopifyProduct item in shopifyItems) { logger.DebugFormat( "ConnectsterController::CreateListsAndBegin(): Shopify has shopifyItem({0}, Date:{1: yyyy-MM-dd HH:mm:ss})", item.Id, ((DateTime) item.Variants[0].UpdatedAt)); } foreach (ShopifyMetafieldMapping mapping in shopifyMapping.GetMappedItems()) { logger.DebugFormat("Shopify has Item({0}) --> ShopsterId({1})", mapping.SourceId, mapping.DestinationId); } #endif //Now do the actual syncronizing actions //This will ensure that all shopster products are in the db and on shopify returnCount += PushProductToShopify(shopsterList, productMap, shopifyMapping, shopifyIds, shopifyItems, shopifyAuth, apiContext); //This will clean up all the broken Maps returnCount += FixOrphanedMappings(shopsterIds, productMap, shopifyMapping, shopifyIds, shopifyAuth); //This will delete all orphaned shopify items (not mapped at all, but tagged as shopster items). return (returnCount + DeleteOrphanedShopifyProducts(shopsterList, productMap, shopifyMapping, shopifyAuth)); }
//End of PushProductToShopify /// <summary> /// Deals with any orphaned cases with a Mapped remaining. Attempts to clean up any shopify Items in the process. /// </summary> /// <param name="shopsterIds"></param> /// <param name="productMap"></param> /// <param name="shopifyMapping"></param> /// <param name="shopifyIds"></param> /// <param name="shopifyAuth"></param> /// <returns>Number of "items" affected</returns> private int FixOrphanedMappings(IList<int> shopsterIds, ConnectsterProductMap productMap, ShopifyMetafieldMap shopifyMapping, IList<int> shopifyIds, ShopifyStoreAuth shopifyAuth) { int returnCount = 0; //Foreach productMap for which there is no corresponding shopsterItem. // Delete the map, and if applicable delete the shopifyItem. foreach (ConnectsterProduct shopsterifyProduct in productMap.GetMappedItems() .Where(p => !shopsterIds.Contains(p.SourceId)).Select(p => p).ToList()) { //case 2 if (!shopifyIds.Contains(shopsterifyProduct.DestinationId)) { productMap.DeleteMapping(shopsterifyProduct); returnCount++; } else //case 6 { if (DeleteProductAndDeleteMap(shopifyAuth, productMap, shopsterifyProduct)) { shopifyIds.Remove(shopsterifyProduct.DestinationId); ConnectsterProduct product = shopsterifyProduct; ShopifyMetafieldMapping toRemove = shopifyMapping.GetMappedItems().Where( map => map.SourceId == product.DestinationId).Select(map => map). FirstOrDefault(); shopifyMapping.GetMappedItems().Remove(toRemove); returnCount++; } } } return returnCount; }
/// <summary> /// Pushes all shopster products to shopify. Deals with all error states with an existing shopster Item. Deals with cases 1357, all ending in case7's state (see wiki). /// </summary> /// <param name="shopsterItems"></param> /// <param name="productMap"></param> /// <param name="shopifyMap"></param> /// <param name="shopifyIds"></param> /// <param name="shopifyItems"></param> /// <param name="storeAuth"></param> /// <param name="apiContext"></param> /// <returns>A count of how many products were fixed/changed.</returns> private int PushProductToShopify(IEnumerable<InventoryItemType> shopsterItems, ConnectsterProductMap productMap, ShopifyMetafieldMap shopifyMap, IList<int> shopifyIds, List<ShopifyProduct> shopifyItems, ShopifyStoreAuth storeAuth, ApiContext apiContext) { if (shopifyItems == null) throw new ArgumentNullException("shopifyItems"); int returnCount = 0; //All of these are ending up in a Case7 (All good) state. foreach (InventoryItemType shopsterItem in shopsterItems) { InventoryItemType inventoryItem = shopsterItem; IList<ConnectsterProduct> mappings = productMap.GetMappedItems() .Where(p => p.SourceId == Convert.ToInt32(inventoryItem.ItemId)).Select(p => p).ToList(); if (mappings.Count > 0) //Cases 3,7 { foreach (ConnectsterProduct mapping in mappings) { if (!shopifyIds.Contains(mapping.DestinationId)) //If we have a broken mapping. (Case3) { //if the metafields have a mapping, fix the mapping if (shopifyMap.GetDestinationIds().Contains(Convert.ToInt32(shopsterItem.ItemId))) { //There is a shopifyItem that wants to be linked to this shopsterItem if (shopifyItems.Count > 0) { ShopifyProduct shopifyItem = shopifyItems .Where(item => item.Id == shopifyMap.GetSourcesForDestiation( Convert.ToInt32(shopsterItem.ItemId)).Take(1).First()) .Select(item => item).First(); if (shopifyItem.Id != null) mapping.DestinationId = (int) shopifyItem.Id; productMap.AddMapping(mapping); //Add mapping will update if it is already there. return returnCount; } } else { //upload the product List<InventoryCategoryType> categories = (shopsterItem.Categories == null || shopsterItem.Categories.Count < 1) ? new List<InventoryCategoryType>(1) : new List<InventoryCategoryType>(shopsterItem.Categories.Count); if (shopsterItem.Categories != null) categories.AddRange(shopsterItem.Categories.Select(categoryId => _shopsterComm.GetCategory(apiContext, categoryId))); ShopifyProduct sP = ShopsterifyConverter.convertItem(shopsterItem, categories); ShopifyResponse<ShopifyProduct> response = _shopifyComm.CreateProduct(storeAuth, sP); if (response.State == ResponseState.OK) { _metaFieldShopsterProductId.Value = Convert.ToInt32(shopsterItem.ItemId).ToString(); ShopifyResponse<ShopifyMetafield> responseMetafield = _shopifyComm.CreateMetafield(storeAuth, _metaFieldShopsterProductId, response.ResponseObject.Id); if (responseMetafield == null) throw new NotImplementedException(); mapping.DestinationId = response.ResponseObject.Id != null ? (int) response.ResponseObject.Id : 0; productMap.AddMapping(mapping); //Add mapping will update if it is already there. returnCount++; } } } else //Case7 update and ensure metafield map is correct. { //Todo enable the below comparison of Sourcedate and DestinationDate //if (mapping.SourceDate > mapping.DestinationDate) if (mapping.DestinationDate < DateTime.UtcNow.AddMinutes(-5)) { List<InventoryCategoryType> categories = (shopsterItem.Categories == null || shopsterItem.Categories.Count < 1) ? new List<InventoryCategoryType>(1) : new List<InventoryCategoryType>(shopsterItem.Categories.Count); if (shopsterItem.Categories != null) categories.AddRange(shopsterItem.Categories.Select(categoryId => _shopsterComm.GetCategory(apiContext, categoryId))); ShopifyProduct shopifyProduct = ShopsterifyConverter.convertItem(shopsterItem, categories); shopifyProduct.Id = productMap.GetProductTable()[Convert.ToInt32(shopsterItem.ItemId)]; ShopifyVariant[] shopifyVars = shopifyItems.Where(item => item.Id == shopifyProduct.Id).Select( item => item.Variants).FirstOrDefault(); foreach (ShopifyVariant sV in shopifyProduct.Variants) { sV.ProductId = shopifyProduct.Id; if (shopifyVars != null) sV.Id = shopifyVars[0].Id; } ShopifyResponse<ShopifyProduct> response = _shopifyComm.UpdateProduct(storeAuth, shopifyProduct); if (response.State == ResponseState.OK) { //Todo add debug logging that this item was updated correctly returnCount++; logger.InfoFormat( "ConnectsterController:: Updated shopify item({0}) successfully.", shopifyProduct.Id); _database.UpdateShopifyProductTimeStamp(response.ResponseObject, DateTime.UtcNow); } } else { logger.InfoFormat("ConnectsterController:: Skipped update due to timestamps."); } } } } else //Cases 1, 5 -- Product exists, no mappings found, maybe on shopify { InventoryItemType inventoryItemType = shopsterItem; IList<ShopifyMetafieldMapping> metaFieldMappings = shopifyMap.GetMappedItems() .Where(map => map.DestinationId == Convert.ToInt32(inventoryItemType.ItemId)) .Select(map => map).ToList(); if (metaFieldMappings.Count > 0) //There are products on shopify that think they belong to this shopsteritem { //Case 5 foreach (ShopifyMetafieldMapping metaFieldMapping in metaFieldMappings) { //rebuild the mapping, this call pushes through to the db. ShopifyMetafieldMapping mapping = metaFieldMapping; InventoryItemType item1 = shopsterItem; productMap.AddMapping(new ConnectsterProduct(shopsterItem, shopifyItems .Where( item => (item.Id == mapping. SourceId) && (mapping. DestinationId == Convert.ToInt32( item1.ItemId))) .Select(item => item).Take(1) .ToList()[0])); returnCount++; } } else //There aren't any products on shopify that belong to this shopsterItem { //Case 1 //upload the product List<InventoryCategoryType> categories = (shopsterItem.Categories == null || shopsterItem.Categories.Count < 1) ? new List<InventoryCategoryType>(1) : new List<InventoryCategoryType>(shopsterItem.Categories.Count); if (shopsterItem.Categories != null) categories.AddRange(shopsterItem.Categories.Select(categoryId => _shopsterComm.GetCategory(apiContext, categoryId))); ShopifyProduct sP = ShopsterifyConverter.convertItem(shopsterItem, categories); ShopifyResponse<ShopifyProduct> response = _shopifyComm.CreateProduct(storeAuth, sP); if (response.State == ResponseState.OK) { _metaFieldShopsterProductId.Value = Convert.ToInt32(shopsterItem.ItemId).ToString(); ShopifyResponse<ShopifyMetafield> responseMetafield = _shopifyComm.CreateMetafield( storeAuth, _metaFieldShopsterProductId, response.ResponseObject.Id); if (responseMetafield.State == ResponseState.OK) { //create the mapping var mapping = new ConnectsterProduct(shopsterItem, response.ResponseObject); productMap.AddMapping(mapping); } returnCount++; } } } } return returnCount; }
/// <summary> /// Deletes completely orphaned ShopifyProducts (one's which are tagged) but not related to an existing shopsteritem or a mapping. /// </summary> /// <param name="shopsterItems"></param> /// <param name="productMap"></param> /// <param name="shopifyMapping"></param> /// <param name="shopifyAuth"></param> /// <returns></returns> private int DeleteOrphanedShopifyProducts(IEnumerable<InventoryItemType> shopsterItems, ConnectsterProductMap productMap, ShopifyMetafieldMap shopifyMapping, ShopifyStoreAuth shopifyAuth) { //DeleteOrphanedShopifyProducts(productMap, shopifyMapping, user, shopifyAuth) List<ShopifyMetafieldMapping> notMappedProductsList = shopifyMapping.GetMappedItems() .Where(item => (!productMap.GetDestinationIds().Contains(item.SourceId))).Select(item => item).ToList(); return notMappedProductsList.Where(notMappedProduct => shopsterItems.Where(item => Convert.ToInt32(item.ItemId) == notMappedProduct.DestinationId).Count() == 0).Count(notMappedProduct => _shopifyComm.DeleteProduct(shopifyAuth, notMappedProduct.SourceId)); }