using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using PartSource.Automation.Services; using PartSource.Data.Contexts; using PartSource.Data.Models; using Ratermania.Automation.Interfaces; using Ratermania.Shopify; using Ratermania.Shopify.Resources; namespace PartSource.Automation.Jobs { public class UpdateFitment : IAutomationJob { private readonly ILogger _logger; private readonly ShopifyClient _shopifyClient; private readonly PartSourceContext _partSourceContext; private readonly FitmentContext _fitmentContext; private readonly VehicleFitmentService _vehicleFitmentService; public UpdateFitment(ILogger logger, PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleFitmentService vehicleFitmentService) { _logger = logger; _partSourceContext = partSourceContext; _fitmentContext = fitmentContext; _shopifyClient = shopifyClient; _vehicleFitmentService = vehicleFitmentService; } public async Task Run(CancellationToken token, params string[] arguments) { IEnumerable products = null; IList partTypes = new List { "CA115-SC118-FL11803_Custom Lighting Accessories", "CA117-SC141-FL14106_Jeep Accessories", "CA117-SC141-FL14134_Truck Running Board and Steps", "CA117-SC141-FL14199_Bumpers, Bull Bars & Brush Guards", "CA117-SC157-FL15704_Headache Rack Frames", "CA117-SC158-FL15802_Salters and Plow Accessories", "CA117-SC699-FL69902_Crossover Boxes", "CA117-SC699-FL69903_Specialty Boxes", "CA117-SC699-FL69904_Transfer Tanks", "CA135-SC176-FL17601_Trailer Lighting, Stop, Turn, Tail", "CA135-SC186-FL18607_Roof Racks", "CA135-SC186-FL18608_Bike Carriers", "CA135-SC186-FL18609_Cargo Accessories", "CA135-SC186-FL18610_Cargo Carriers", "CA135-SC186-FL18611_Watersport Carriers", "CA135-SC192-FL19201_Class 1 Hitches", "CA135-SC192-FL19202_Class 2 Hitches", "CA135-SC192-FL19203_Class 3 Hitches", "CA135-SC192-FL19204_Towing, Heavy Duty", "CA135-SC192-FL19205_Towing Electrical, Vehicle Specific", "CA135-SC192-FL19206_Trailer Parts & Accessories", "CA135-SC192-FL19207_Trailer Winches, Jacks & Couplers", "CA135-SC192-FL19208_Class 5 Hitches", "CA135-SC192-FL19221_Towing Electrical, Connectors & Adapters", "CA135-SC192-FL19230_Towing Electrical, Controls & Converters", "CA135-SC192-FL19235_Towing Electrical, Harnesses", "CA135-SC192-FL19240_Towing Security, Non-Locking", "CA135-SC192-FL19245_Towing Class V", "CA135-SC192-FL19280_Towing Balls", "CA135-SC192-FL19281_Towing Ball Mounts", "CA135-SC192-FL19282_Towing Security, Locking", "CA135-SC192-FL19283_Towing Kits & Acc" }; foreach (string partType in partTypes) { try { products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 }, { "product_type", partType } }); //products = new List //{ // await _shopifyClient.Products.GetById(4388919574575) //}; } catch (Exception ex) { _logger.LogError("Failed to get products from Shopify", ex); throw; } int i = 1; while (products != null && products.Any()) { foreach (Product product in products) { ImportData importData = null; try { IEnumerable metafields = await _shopifyClient.Metafields.Get(new Dictionary { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } }); importData = new ImportData { LineCode = metafields.FirstOrDefault(m => m.Key == "custom_label_0")?.Value ?? string.Empty, PartNumber = metafields.FirstOrDefault(m => m.Key == "custom_label_1")?.Value ?? string.Empty, VariantSku = product.Variants[0].Sku // They know we can't do fitment for variants }; bool isFitment = false; string bodyHtml = product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("") + "".Length); IList vehicles = _vehicleFitmentService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); IList vehicleIdFitment = _vehicleFitmentService.GetVehicleIdFitment(vehicles); if (!vehicleIdFitment.Any()) { Console.WriteLine($"No fitment data for SKU {importData.VariantSku}"); continue; } if (vehicleIdFitment.Count > 0) { string vehicleIdString = string.Join(',', vehicleIdFitment.Select(j => $"v{j}")); bodyHtml += $"
{vehicleIdString}
"; isFitment = true; string json = JsonConvert.SerializeObject(vehicleIdFitment); if (json.Length < 100000) { Metafield vehicleMetafield = new Metafield { Namespace = "fitment", Key = "ids", Value = json, Type = "json_string", OwnerResource = "product", OwnerId = product.Id }; await _shopifyClient.Metafields.Add(vehicleMetafield); } else { _logger.LogWarning($"Vehicle ID fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); continue; } } IList ymmFitment = _vehicleFitmentService.GetYmmFitment(vehicles); if (ymmFitment.Count > 0) { isFitment = true; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(""); foreach (string fitment in ymmFitment) { try { string[] parts = fitment.Split(' ', 2); stringBuilder.AppendLine($""); } catch { // This is still a POC at this point. Oh well... } } stringBuilder.AppendLine("
This Part Fits
{parts[1]}{parts[0].Replace("-", ", ")}
"); bodyHtml += $"
{stringBuilder.ToString()}
"; string json = JsonConvert.SerializeObject(ymmFitment); if (json.Length < 100000) { Metafield ymmMetafield = new Metafield { Namespace = "fitment", Key = "seo", Value = json, Type = "json_string", OwnerResource = "product", OwnerId = product.Id }; await _shopifyClient.Metafields.Add(ymmMetafield); } else { _logger.LogWarning($"Year/make/model fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); continue; } } Metafield isFitmentMetafield = new Metafield { Namespace = "Flags", Key = "IsFitment", Value = isFitment.ToString(), Type = "string", OwnerResource = "product", OwnerId = product.Id }; await _shopifyClient.Metafields.Add(isFitmentMetafield); //Metafield lineCodeMetafield = new Metafield //{ // Namespace = "google", // Key = "custom_label_0", // Value = importData.LineCode, // Type = "string", // OwnerResource = "product", // OwnerId = product.Id //}; //await _shopifyClient.Metafields.Add(lineCodeMetafield); //Metafield partNumberMetafield = new Metafield //{ // Namespace = "google", // Key = "custom_label_1", // Value = importData.PartNumber, // Type = "string", // OwnerResource = "product", // OwnerId = product.Id //}; //await _shopifyClient.Metafields.Add(partNumberMetafield); List tags = new List(); for (int j = 0; j < vehicleIdFitment.Count; j += 25) { tags.Add(string.Join('-', vehicleIdFitment.Skip(j).Take(25).Select(j => $"v{j}"))); } tags.AddRange(ymmFitment); if (tags.Count > 249) { tags = tags.Take(249).ToList(); } string zzzIsFitment = isFitment ? "zzzIsFitment=true" : "zzzIsFitment=false"; tags.Add(zzzIsFitment); product.Tags = string.Join(',', tags); product.BodyHtml = bodyHtml; await _shopifyClient.Products.Update(product); importData.IsFitment = isFitment; importData.UpdatedAt = DateTime.Now; importData.UpdateType = "Fitment"; } catch (Exception ex) { _logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex); } } try { Console.WriteLine(i); _partSourceContext.SaveChanges(); products = await _shopifyClient.Products.GetNext(); i++; } catch (Exception ex) { _logger.LogWarning(ex, "Failed to get the next set of products. Retrying"); products = await _shopifyClient.Products.GetPrevious(); } } Console.WriteLine($"Finished {partType}"); } } } }