Files
Partsource/PartSource.Automation/Jobs/UpdateFitment.cs
2026-02-17 09:47:41 -05:00

203 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
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;
using System.Web;
using System.IO;
namespace PartSource.Automation.Jobs
{
public class UpdateFitment : IAutomationJob
{
private readonly ILogger<UpdateFitment> _logger;
private readonly ShopifyClient _shopifyClient;
private readonly PartSourceContext _partSourceContext;
private readonly FitmentContext _fitmentContext;
private readonly VehicleFitmentService _vehicleFitmentService;
public UpdateFitment(ILogger<UpdateFitment> 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)
{
string logfile = Path.Combine(Path.GetTempPath(), $"missing_fitment_{DateTime.Now:yyyy-MM-dd}.csv");
using StreamWriter noFitmentWriter = File.AppendText(logfile);
noFitmentWriter.WriteLine("SKU,LineCode,PartNumber");
_logger.LogInformation($"Logging products without fitment to {logfile}");
IList<ProductType> productTypes = await _fitmentContext.ProductTypes
.Where(p => p.Active)
.ToListAsync();
foreach (ProductType productType in productTypes)
{
_logger.LogInformation("Processing {productType}", productType.Name);
IEnumerable<Product> products = null;
try
{
products = await _shopifyClient.Products.Get(new Dictionary<string, object> { { "limit", 250 }, { "product_type", HttpUtility.UrlEncode(productType.Name) } });
//products = new List<Product>
//{
// await _shopifyClient.Products.GetById(7458071052335)
//};
}
catch (Exception ex)
{
_logger.LogError("Failed to get products from Shopify", ex);
throw;
}
while (products != null && products.Any())
{
foreach (Product product in products)
{
ImportData importData = null;
bool isFitment = false;
try
{
IEnumerable<Metafield> metafields = await _shopifyClient.Metafields.Get(new Dictionary<string, object> { { "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
};
importData.PartNumber = importData.PartNumber.Replace("-", string.Empty);
//If the line code is numeric, it cannot have fitment data associated with it.
if (int.TryParse(importData.LineCode, out _))
{
continue;
}
// Temporarily skip castrol oils
if (importData.LineCode == "CST")
{
continue;
}
// Extract Partsource bullet points if present.
string bodyHtml = string.IsNullOrEmpty(product.BodyHtml)
? string.Empty
: product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("</ul>") + "</ul>".Length);
IList<Vehicle> vehicles = await _vehicleFitmentService.GetVehiclesForPart(importData.PartNumber, importData.LineCode);
IList<int> vehicleIdFitment = _vehicleFitmentService.GetVehicleIdFitment(vehicles);
if (!vehicleIdFitment.Any())
{
Console.WriteLine($"No fitment data for {importData.LineCode} {importData.PartNumber}");
noFitmentWriter.WriteLine($"\"{importData.VariantSku}\",\"{importData.LineCode}\",\"{importData.PartNumber}\"");
continue;
}
string vehicleIdString = string.Join(',', vehicleIdFitment.Select(j => $"v{j}"));
bodyHtml += $"<div id=\"vehicleIDs\" style=\"display:none;\">{vehicleIdString}</div>";
isFitment = true;
IList<string> ymmFitment = _vehicleFitmentService.GetYmmFitment(vehicles);
if (ymmFitment.Count > 0)
{
isFitment = true;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine("<table><tr><th colspan=\"2\">This Part Fits</th></tr>");
foreach (string fitment in ymmFitment)
{
try
{
string[] parts = fitment.Split(' ', 2);
stringBuilder.AppendLine($"<tr><td>{parts[1]}</td><td>{parts[0].Replace("-", ", ")}</td></tr>");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "YMM fitment for {fitment} was in an invalid format", fitment);
}
}
stringBuilder.AppendLine("</table>");
bodyHtml += $"<div id=\"seoData\">{stringBuilder}</div>";
}
List<string> tags = new List<string>();
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
{
products = await _shopifyClient.Products.GetNext();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get the next set of products. Retrying");
products = await _shopifyClient.Products.GetPrevious();
}
}
productType.Active = false;
await _fitmentContext.SaveChangesAsync();
}
}
}
}