Fitment update

This commit is contained in:
2023-01-29 11:48:13 -05:00
parent b259b77967
commit ff20615481
22 changed files with 777 additions and 1004 deletions

View File

@@ -35,7 +35,6 @@
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.5" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.5" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Ratermania.Shopify" Version="6.16.8" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" />
</ItemGroup> </ItemGroup>

View File

@@ -57,6 +57,8 @@ namespace PartSource.Api
services.AddTransient<VehicleService>(); services.AddTransient<VehicleService>();
services.AddTransient<ShopifyChangelogService>(); services.AddTransient<ShopifyChangelogService>();
services.AddTransient<FitmentContext>();
services.AddCors(o => o.AddPolicy("Default", builder => services.AddCors(o => o.AddPolicy("Default", builder =>
{ {
builder.AllowAnyOrigin() builder.AllowAnyOrigin()

View File

@@ -17,7 +17,7 @@ namespace PartSource.Automation.Jobs
private readonly ILogger<ExecuteSsisPackages> _logger; private readonly ILogger<ExecuteSsisPackages> _logger;
// TODO: set from config // TODO: set from config
private readonly string[] _ssisPackages = { "Parts Price", "Parts Availability" }; private readonly string[] _ssisPackages = {"Parts Price", "Parts Availability" };
public ExecuteSsisPackages(EmailService emailService, IConfiguration configuration, SsisService ssisService, ILogger<ExecuteSsisPackages> logger) public ExecuteSsisPackages(EmailService emailService, IConfiguration configuration, SsisService ssisService, ILogger<ExecuteSsisPackages> logger)
{ {
@@ -28,7 +28,6 @@ namespace PartSource.Automation.Jobs
_ssisService = ssisService; _ssisService = ssisService;
_logger = logger; _logger = logger;
} }
public async Task Run(CancellationToken token, params string[] arguments) public async Task Run(CancellationToken token, params string[] arguments)
{ {
await Task.Run(() => await Task.Run(() =>
@@ -51,7 +50,7 @@ namespace PartSource.Automation.Jobs
} }
} }
_emailService.Send("Database Updates Complete", "Database updates to support pricing and availability have completed successfully."); // _emailService.Send("Database Updates Complete", "Database updates to support pricing and availability have completed successfully.");
}); });
} }
} }

View File

@@ -28,7 +28,7 @@ namespace PartSource.Automation.Jobs
MenuNodesLookup menuNodesLookup = new MenuNodesLookup MenuNodesLookup menuNodesLookup = new MenuNodesLookup
{ {
MenuId = 2, MenuId = 1,
NumberOfLevels = 1 NumberOfLevels = 1
}; };
@@ -40,7 +40,7 @@ namespace PartSource.Automation.Jobs
MenuNodesLookup subgroupLookup = new MenuNodesLookup MenuNodesLookup subgroupLookup = new MenuNodesLookup
{ {
MenuId = 2, MenuId = 1,
NumberOfLevels = 1, NumberOfLevels = 1,
ParentMenuNodeId = categoryNode.Id ParentMenuNodeId = categoryNode.Id
}; };
@@ -53,7 +53,7 @@ namespace PartSource.Automation.Jobs
MenuNodesLookup thirdLookup = new MenuNodesLookup MenuNodesLookup thirdLookup = new MenuNodesLookup
{ {
MenuId = 2, MenuId = 1,
NumberOfLevels = 1, NumberOfLevels = 1,
ParentMenuNodeId = subgroupNode.Id ParentMenuNodeId = subgroupNode.Id
}; };
@@ -68,7 +68,7 @@ namespace PartSource.Automation.Jobs
} }
} }
//await File.WriteAllLinesAsync("C:\\users\\Tommy\\desktop\\Partsource Menu Items.csv", rows); await File.WriteAllLinesAsync("C:\\users\\Tommy\\desktop\\Partsource Menu Items.csv", rows);
; ;
} }

View File

@@ -1,66 +0,0 @@
using Ratermania.Automation.Interfaces;
using Ratermania.Shopify;
using Ratermania.Shopify.Resources;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace PartSource.Automation.Jobs.POC
{
public class FixMultipleSeoTables : IAutomationJob
{
private readonly ShopifyClient _shopifyClient;
public FixMultipleSeoTables(ShopifyClient shopifyClient)
{
_shopifyClient = shopifyClient;
}
public async Task Run(CancellationToken token, params string[] arguments)
{
IEnumerable<Product> products = await _shopifyClient.Products.Get(new Dictionary<string, object> { { "limit", 250 }, { "product_type", "CA112-SC137-FL13750_Intake Manifolds" } });
while (products != null && products.Any())
{
foreach (Product product in products)
{
try
{
string[] parts = product.BodyHtml.Split("<div id=\"seoData\">");
if (parts.Length > 2)
{
string ul = product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("</ul>") + "</ul>".Length);
string seoData = "<div id=\"seoData\">" + parts[1].Substring(0, parts[1].IndexOf("</table>") + "</table>".Length) + "</div>";
string vehicleIds = new Regex("<div id=\"vehicleIDs\".*</div>").Match(product.BodyHtml).Value;
product.BodyHtml = ul + seoData + vehicleIds;
await _shopifyClient.Products.Update(product);
}
}
catch
{
Console.WriteLine($"Failed to update {product.Id}");
}
}
try
{
Console.WriteLine("Did 250");
products = await _shopifyClient.Products.GetNext();
}
catch (Exception ex)
{
products = await _shopifyClient.Products.GetPrevious();
}
}
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using PartSource.Data.Contexts;
using PartSource.Data.Models;
using PartSource.Data.Nexpart;
using PartSource.Services;
using Ratermania.Automation.Interfaces;
using Ratermania.Shopify;
using Ratermania.Shopify.Resources;
namespace PartSource.Automation.Jobs.POC
{
public class GetImageUrls : IAutomationJob
{
private readonly NexpartService _nexpartService;
private readonly PartSourceContext _partSourceContext;
public GetImageUrls(NexpartService nexpartService, PartSourceContext partSourceContext)
{
_nexpartService = nexpartService;
_partSourceContext = partSourceContext;
}
public async Task Run(CancellationToken token, params string[] arguments)
{
IList<string> rows = new List<string> {
"\"Line Code\", \"Part Number\", \"Image URL(s)\""
};
IList<ImportData> importData = await _partSourceContext.ImportData
//.Take(5000)
.ToListAsync();
foreach (ImportData item in importData)
{
SmartPageDataSearch dataSearch = new SmartPageDataSearch
{
Items = new Item[]
{
new Item
{
MfrCode = item.LineCode,
PartNumber = item.PartNumber
}
},
DataOption = new[] { "DIST_LINE", "ALL" }
};
SmartPageDataSearchResponse response = await _nexpartService.SendRequest<SmartPageDataSearch, SmartPageDataSearchResponse>(dataSearch);
if (response.ResponseBody.Item?.Length > 0)
{
List<string> urls = new List<string>();
if (!string.IsNullOrEmpty(response.ResponseBody.Item[0].PrimaryImg?.ImgUrl))
{
urls.Add(response.ResponseBody.Item[0].PrimaryImg?.ImgUrl);
};
if (response.ResponseBody.Item[0].AddImgs?.AddImg?.Length > 0)
{
urls.AddRange(response.ResponseBody.Item[0].AddImgs.AddImg.Select(i => i.AddImgUrl));
}
if (urls.Count > 0)
{
rows.Add($"\"{item.LineCode}\", \"{item.PartNumber}\", \"{string.Join(";", urls)}\"");
}
}
}
await File.WriteAllLinesAsync("C:\\users\\Tommy\\desktop\\WHI Images.csv", rows);
}
}
}

View File

@@ -1,146 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PartSource.Automation.Models;
using PartSource.Data;
using PartSource.Data.Contexts;
using PartSource.Data.Models;
using PartSource.Services;
using Ratermania.Automation.Interfaces;
using Ratermania.Shopify;
using Ratermania.Shopify.Exceptions;
using Ratermania.Shopify.Resources;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace PartSource.Automation.Jobs
{
public class UpdateFitmentHtml : IAutomationJob
{
private readonly ILogger<UpdateFitmentHtml> _logger;
private readonly ShopifyClient _shopifyClient;
private readonly PartSourceContext _partSourceContext;
private readonly FitmentContext _fitmentContext;
private readonly VehicleService _vehicleService;
public UpdateFitmentHtml(ILogger<UpdateFitmentHtml> logger, PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleService vehicleService)
{
_logger = logger;
_partSourceContext = partSourceContext;
_fitmentContext = fitmentContext;
_shopifyClient = shopifyClient;
_vehicleService = vehicleService;
}
public async Task Run(CancellationToken token, params string[] arguments)
{
IEnumerable<Product> products = null;
try
{
products = await _shopifyClient.Products.Get(new Dictionary<string, object> { { "limit", 250 }, { "vendor", "FRAM" } });
}
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
{
importData = await _partSourceContext.ImportData.FirstOrDefaultAsync(parts => parts.VariantSku == product.Variants[0].Sku);
if (importData == null)
{
continue;
}
product.BodyHtml = importData.BodyHtml;
IList<Vehicle> vehicles = _vehicleService.GetVehiclesForPart(importData.PartNumber, importData.LineCode);
IList<string> ymmFitment = _vehicleService.GetYmmFitment(vehicles);
if (ymmFitment.Count > 0)
{
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
{
// This is still a POC at this point. Oh well...
}
}
stringBuilder.AppendLine("</table>");
product.BodyHtml += $"<div id=\"seoData\">{stringBuilder.ToString()}</div>";
}
IList<int> vehicleIdFitment = _vehicleService.GetVehicleIdFitment(vehicles);
if (vehicleIdFitment.Count > 0)
{
string vehicleIdString = string.Join('-', vehicleIdFitment.Select(j => $"v{j}"));
product.BodyHtml += $"<div id=\"vehicleIDs\" style=\"display:none;\">{vehicleIdString}</div>";
}
List<string> tags = new List<string>
{
importData.LineCode,
importData.PartNumber,
"zzzIsFitment=true"
};
product.Tags = string.Join(',', tags);
await _shopifyClient.Products.Update(product);
}
catch (Exception ex)
{
_logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex);
}
}
try
{
Console.WriteLine(i);
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();
}
}
}
}
}

View File

@@ -1,145 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PartSource.Automation.Models;
using PartSource.Data;
using PartSource.Data.Contexts;
using PartSource.Data.Dtos;
using PartSource.Data.Models;
using PartSource.Services;
using Ratermania.Automation.Interfaces;
using Ratermania.Shopify;
using Ratermania.Shopify.Exceptions;
using Ratermania.Shopify.Resources;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace PartSource.Automation.Jobs.POC
{
public class UpdateFitmentScratchpad : IAutomationJob
{
private readonly ILogger<UpdateFitmentScratchpad> _logger;
private readonly ShopifyClient _shopifyClient;
private readonly PartSourceContext _partSourceContext;
private readonly FitmentContext _fitmentContext;
private readonly VehicleService _vehicleService;
public UpdateFitmentScratchpad(ILogger<UpdateFitmentScratchpad> logger, PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleService vehicleService)
{
_logger = logger;
_partSourceContext = partSourceContext;
_fitmentContext = fitmentContext;
_shopifyClient = shopifyClient;
_vehicleService = vehicleService;
}
public async Task Run(CancellationToken token, params string[] arguments)
{
IList<string> productTypes = new List<string>
{
"C172-S231-F23107_(PS) Wipers - TRICO Neoform",
"CA172-SC231-FL23106_Wiper Blades - Bosch Icon",
"CA172-SC231-FL23107_(PS) Wipers - TRICO Neoform",
"CA172-SC231-FL23109_(PS) Wipers - TRICO Tech/Exact Fit",
"CA172-SC231-FL23110_Wiper Accessories",
"CA172-SC231-FL23114_Wiper Blades - Rear",
"CA172-SC231-FL23116_(PS) Wipers - Bosch Insight (Hybrid)",
"CA172-SC231-FL23117_(PS) Wipers - Bosch Clear Advantage (Beam)",
"CA172-SC231-FL23118_(PS) Wipers - Winter",
"CA172-SC231-FL23125_Wiper Blades - Bosch Areotwin"
};
IList<string> csvData = new List<string>
{
"\"Line Code\", \"Part Number\", \"Year\", \"Make\", \"Model\", \"Position\""
};
foreach (string type in productTypes)
{
IEnumerable<Product> products = null;
try
{
products = await _shopifyClient.Products.Get(new Dictionary<string, object> { { "limit", 250 }, { "product_type", type } });
}
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;
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 = product.Title.Split(' ')[0],
VariantSku = product.Variants[0].Sku // They know we can't do fitment for variants
};
string csvRow = product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("</ul>") + "</ul>".Length);
IList<VehicleFitmentDto> vehicles = _vehicleService.GetVehicleFitmentForPart(importData.PartNumber, importData.LineCode);
if (vehicles.Count > 0)
{
foreach (VehicleFitmentDto vehicle in vehicles)
{
try
{
csvData.Add($"\"{importData.LineCode}\", \"{importData.PartNumber}\", \"{vehicle.Vehicle.Year}\", \"{vehicle.Vehicle.MakeName}\", \"{vehicle.Vehicle.ModelName}\", \"{vehicle.Fitment.Position}\"");
}
catch
{
// This is still a POC at this point. Oh well...
}
}
}
}
catch (Exception ex)
{
_logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex);
}
}
try
{
Console.WriteLine($"Did an iteration of {type}");
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();
}
}
}
await File.WriteAllLinesAsync("C:\\users\\Tommy\\desktop\\Wiper Fitment.csv", csvData);
;
}
}
}

View File

@@ -40,11 +40,11 @@ namespace PartSource.Automation.Jobs
_noteDictionary = new ConcurrentDictionary<string, string>(); _noteDictionary = new ConcurrentDictionary<string, string>();
} }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2008:Do not create tasks without passing a TaskScheduler", Justification = "Not library code")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2008:Do not create tasks without passing a TaskScheduler", Justification = "<Pending>")]
public async Task Run(CancellationToken token, params string[] arguments) public async Task Run(CancellationToken token, params string[] arguments)
{ {
_whiSeoService.TruncateFitmentTables(); _whiSeoService.TruncateFitmentTables();
_whiSeoService.GetFiles(_seoDataType); // _whiSeoService.GetFiles(_seoDataType);
string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant()); string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant());
DirectoryInfo directoryInfo = new DirectoryInfo(directory); DirectoryInfo directoryInfo = new DirectoryInfo(directory);
@@ -56,7 +56,7 @@ namespace PartSource.Automation.Jobs
fileGroups.Enqueue(fileGroup); fileGroups.Enqueue(fileGroup);
} }
Task[] taskArray = new Task[Environment.ProcessorCount / 2]; Task[] taskArray = new Task[8];
for (int i = 0; i < taskArray.Length; i++) for (int i = 0; i < taskArray.Length; i++)
{ {
@@ -91,7 +91,7 @@ namespace PartSource.Automation.Jobs
_logger.LogInformation($"Created fitment table for part group {fitmentTable}."); _logger.LogInformation($"Created fitment table for part group {fitmentTable}.");
} }
}, token); });
} }
Task.WaitAll(taskArray); Task.WaitAll(taskArray);

View File

@@ -39,8 +39,8 @@ namespace PartSource.Automation.Jobs
public async Task Run(CancellationToken token, params string[] arguments) public async Task Run(CancellationToken token, params string[] arguments)
{ {
_whiSeoService.TruncateVehicleTables(); _whiSeoService.TruncateVehicleTable();
_whiSeoService.GetFiles(_seoDataType); // _whiSeoService.GetFiles(_seoDataType);
string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant()); string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant());
DirectoryInfo directoryInfo = new DirectoryInfo(directory); DirectoryInfo directoryInfo = new DirectoryInfo(directory);
@@ -111,7 +111,6 @@ namespace PartSource.Automation.Jobs
if (!string.IsNullOrEmpty(makeName) if (!string.IsNullOrEmpty(makeName)
&& !string.IsNullOrEmpty(modelName) && !string.IsNullOrEmpty(modelName)
&& !string.IsNullOrEmpty(regionName)
&& !string.IsNullOrEmpty(submodelName) && !string.IsNullOrEmpty(submodelName)
&& !string.IsNullOrEmpty(engineDescription) && !string.IsNullOrEmpty(engineDescription)
&& int.TryParse(columns[0], out int baseVehicleId) && int.TryParse(columns[0], out int baseVehicleId)
@@ -122,12 +121,14 @@ namespace PartSource.Automation.Jobs
&& int.TryParse(columns[9], out int vehicleTypeId) && int.TryParse(columns[9], out int vehicleTypeId)
&& int.TryParse(columns[33], out int submodelId) && int.TryParse(columns[33], out int submodelId)
&& int.TryParse(columns[35], out int engineConfigId) && int.TryParse(columns[35], out int engineConfigId)
&& int.TryParse(columns[36], out int vehicleToEngineConfigId) && int.TryParse(columns[36], out int vehicleToEngineConfigId))
&& new[] { 5, 6, 7 }.Contains(vehicleTypeId)) {
if (regionId == 2 && new[] { 5, 6, 7 }.Contains(vehicleTypeId))
{ {
dataTable.Rows.Add(new object[] { year, makeId, makeName, modelId, modelName, regionId, regionName, vehicleTypeId, engineConfigId, engineDescription, baseVehicleId, vehicleToEngineConfigId, submodelId, submodelName }); dataTable.Rows.Add(new object[] { year, makeId, makeName, modelId, modelName, regionId, regionName, vehicleTypeId, engineConfigId, engineDescription, baseVehicleId, vehicleToEngineConfigId, submodelId, submodelName });
} }
} }
}
return dataTable; return dataTable;
} }

View File

@@ -1,23 +1,18 @@
using Microsoft.EntityFrameworkCore; using System;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PartSource.Automation.Models;
using PartSource.Data;
using PartSource.Data.Contexts;
using PartSource.Data.Models;
using PartSource.Services;
using Ratermania.Automation.Interfaces;
using Ratermania.Shopify;
using Ratermania.Shopify.Exceptions;
using Ratermania.Shopify.Resources;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; 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 namespace PartSource.Automation.Jobs
{ {
@@ -27,19 +22,20 @@ namespace PartSource.Automation.Jobs
private readonly ShopifyClient _shopifyClient; private readonly ShopifyClient _shopifyClient;
private readonly PartSourceContext _partSourceContext; private readonly PartSourceContext _partSourceContext;
private readonly FitmentContext _fitmentContext; private readonly FitmentContext _fitmentContext;
private readonly VehicleService _vehicleService; private readonly VehicleFitmentService _vehicleFitmentService;
public UpdateFitment(ILogger<UpdateFitment> logger, PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleService vehicleService) public UpdateFitment(ILogger<UpdateFitment> logger, PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleFitmentService vehicleFitmentService)
{ {
_logger = logger; _logger = logger;
_partSourceContext = partSourceContext; _partSourceContext = partSourceContext;
_fitmentContext = fitmentContext; _fitmentContext = fitmentContext;
_shopifyClient = shopifyClient; _shopifyClient = shopifyClient;
_vehicleService = vehicleService; _vehicleFitmentService = vehicleFitmentService;
} }
public async Task Run(CancellationToken token, params string[] arguments) public async Task Run(CancellationToken token, params string[] arguments)
{ {
IEnumerable<Product> products = null; IEnumerable<Product> products = null;
try try
@@ -59,53 +55,41 @@ namespace PartSource.Automation.Jobs
{ {
foreach (Product product in products) foreach (Product product in products)
{ {
// Wiper blades are a separate fitment process.
if (product.ProductType.Contains("CA172-SC231"))
{
continue;
}
ImportData importData = null; ImportData importData = null;
try try
{ {
IEnumerable<Metafield> metafields = await _shopifyClient.Metafields.Get(new Dictionary<string, object> { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } }); IEnumerable<Metafield> metafields = await _shopifyClient.Metafields.Get(new Dictionary<string, object> { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } });
//importData = await _partSourceContext.ImportData.FirstOrDefaultAsync(parts => parts.ShopifyId == product.Id);
//if (importData == null)
//{
// continue;
importData = new ImportData importData = new ImportData
{ {
LineCode = metafields.FirstOrDefault(m => m.Key == "custom_label_0").Value ?? string.Empty, LineCode = metafields.FirstOrDefault(m => m.Key == "custom_label_0").Value ?? string.Empty,
PartNumber = product.Title.Split(' ')[0], 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 VariantSku = product.Variants[0].Sku // They know we can't do fitment for variants
}; };
// }
bool isFitment = false; bool isFitment = false;
string bodyHtml = product.BodyHtml[..(product.BodyHtml.IndexOf("</ul>") + "</ul>".Length)]; string bodyHtml = product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("</ul>") + "</ul>".Length);
IList<Vehicle> vehicles = _vehicleService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); IList<Vehicle> vehicles = _vehicleFitmentService.GetVehiclesForPart(importData.PartNumber, importData.LineCode);
IList<int> vehicleIdFitment = _vehicleFitmentService.GetVehicleIdFitment(vehicles);
IList<int> vehicleIdFitment = _vehicleService.GetVehicleIdFitment(vehicles); if (vehicleIdFitment.Count > 0)
if (vehicleIdFitment.Any())
{ {
string vehicleIdString = string.Join('-', vehicleIdFitment.Select(j => $"v{j}")); string vehicleIdString = string.Join(',', vehicleIdFitment.Select(j => $"v{j}"));
bodyHtml += $"<div id=\"vehicleIDs\" style=\"display:none;\">{vehicleIdString}</div>"; bodyHtml += $"<div id=\"vehicleIDs\" style=\"display:none;\">{vehicleIdString}</div>";
isFitment = true; isFitment = true;
string json = JsonConvert.SerializeObject(vehicleIdFitment); string json = JsonConvert.SerializeObject(vehicleIdFitment);
if (json.Length < 100000)
{
Metafield vehicleMetafield = new Metafield Metafield vehicleMetafield = new Metafield
{ {
Namespace = "fitment", Namespace = "fitment",
Key = "ids", Key = "ids",
Value = json, Value = json,
Type = "json", Type = "json_string",
OwnerResource = "product", OwnerResource = "product",
OwnerId = product.Id OwnerId = product.Id
}; };
@@ -113,7 +97,14 @@ namespace PartSource.Automation.Jobs
await _shopifyClient.Metafields.Add(vehicleMetafield); await _shopifyClient.Metafields.Add(vehicleMetafield);
} }
IList<string> ymmFitment = _vehicleService.GetYmmFitment(vehicles); else
{
_logger.LogWarning($"Vehicle ID fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added.");
continue;
}
}
IList<string> ymmFitment = _vehicleFitmentService.GetYmmFitment(vehicles);
if (ymmFitment.Count > 0) if (ymmFitment.Count > 0)
{ {
isFitment = true; isFitment = true;
@@ -141,12 +132,14 @@ namespace PartSource.Automation.Jobs
bodyHtml += $"<div id=\"seoData\">{stringBuilder.ToString()}</div>"; bodyHtml += $"<div id=\"seoData\">{stringBuilder.ToString()}</div>";
string json = JsonConvert.SerializeObject(ymmFitment); string json = JsonConvert.SerializeObject(ymmFitment);
if (json.Length < 100000)
{
Metafield ymmMetafield = new Metafield Metafield ymmMetafield = new Metafield
{ {
Namespace = "fitment", Namespace = "fitment",
Key = "seo", Key = "seo",
Value = json, Value = json,
Type = "single_line_text_field", Type = "json_string",
OwnerResource = "product", OwnerResource = "product",
OwnerId = product.Id OwnerId = product.Id
}; };
@@ -154,39 +147,46 @@ namespace PartSource.Automation.Jobs
await _shopifyClient.Metafields.Add(ymmMetafield); 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 Metafield isFitmentMetafield = new Metafield
{ {
Namespace = "Flags", Namespace = "Flags",
Key = "IsFitment", Key = "IsFitment",
Value = isFitment.ToString(), Value = isFitment.ToString(),
Type = "single_line_text_field", Type = "string",
OwnerResource = "product", OwnerResource = "product",
OwnerId = product.Id OwnerId = product.Id
}; };
await _shopifyClient.Metafields.Add(isFitmentMetafield); await _shopifyClient.Metafields.Add(isFitmentMetafield);
Metafield lineCodeMetafield = new Metafield //Metafield lineCodeMetafield = new Metafield
{ //{
Namespace = "google", // Namespace = "google",
Key = "custom_label_0", // Key = "custom_label_0",
Value = importData.LineCode, // Value = importData.LineCode,
Type = "single_line_text_field", // Type = "string",
OwnerResource = "product", // OwnerResource = "product",
OwnerId = product.Id // OwnerId = product.Id
}; //};
// await _shopifyClient.Metafields.Add(lineCodeMetafield); //await _shopifyClient.Metafields.Add(lineCodeMetafield);
Metafield partNumberMetafield = new Metafield //Metafield partNumberMetafield = new Metafield
{ //{
Namespace = "google", // Namespace = "google",
Key = "custom_label_1", // Key = "custom_label_1",
Value = importData.PartNumber, // Value = importData.PartNumber,
Type = "single_line_text_field", // Type = "string",
OwnerResource = "product", // OwnerResource = "product",
OwnerId = product.Id // OwnerId = product.Id
}; //};
//await _shopifyClient.Metafields.Add(partNumberMetafield); //await _shopifyClient.Metafields.Add(partNumberMetafield);
@@ -212,7 +212,6 @@ namespace PartSource.Automation.Jobs
product.Tags = string.Join(',', tags); product.Tags = string.Join(',', tags);
product.BodyHtml = bodyHtml; product.BodyHtml = bodyHtml;
await _shopifyClient.Products.Update(product); await _shopifyClient.Products.Update(product);
importData.IsFitment = isFitment; importData.IsFitment = isFitment;
@@ -224,8 +223,8 @@ namespace PartSource.Automation.Jobs
{ {
_logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex); _logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex);
} }
}
}
try try
{ {
Console.WriteLine(i); Console.WriteLine(i);

View File

@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json; using Newtonsoft.Json;
using PartSource.Automation.Models; using PartSource.Automation.Models;
using PartSource.Automation.Services;
using PartSource.Data; using PartSource.Data;
using PartSource.Data.Contexts; using PartSource.Data.Contexts;
using PartSource.Data.Models; using PartSource.Data.Models;
@@ -23,14 +24,14 @@ namespace PartSource.Automation.Jobs
private readonly ShopifyClient _shopifyClient; private readonly ShopifyClient _shopifyClient;
private readonly PartSourceContext _partSourceContext; private readonly PartSourceContext _partSourceContext;
private readonly FitmentContext _fitmentContext; private readonly FitmentContext _fitmentContext;
private readonly VehicleService _vehicleService; private readonly VehicleFitmentService _vehicleFitmentService;
public UpdatePositioning(PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleService vehicleService) public UpdatePositioning(PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleFitmentService vehicleFitmentService)
{ {
_partSourceContext = partSourceContext; _partSourceContext = partSourceContext;
_fitmentContext = fitmentContext; _fitmentContext = fitmentContext;
_shopifyClient = shopifyClient; _shopifyClient = shopifyClient;
_vehicleService = vehicleService; _vehicleFitmentService = vehicleFitmentService;
} }
public async Task Run(CancellationToken token, params string[] arguments) public async Task Run(CancellationToken token, params string[] arguments)
@@ -42,8 +43,6 @@ namespace PartSource.Automation.Jobs
IEnumerable<Product> products = await _shopifyClient.Products.Get(parameters); IEnumerable<Product> products = await _shopifyClient.Products.Get(parameters);
int i = 1;
while (products != null && products.Any()) while (products != null && products.Any())
{ {
foreach (Product product in products) foreach (Product product in products)
@@ -65,7 +64,7 @@ namespace PartSource.Automation.Jobs
} }
IList<Fitment> fitments = GetPositionOrderedFitments(importData?.PartNumber, importData?.LineCode); IList<Fitment> fitments = GetPositionOrderedFitments(importData?.PartNumber, importData?.LineCode);
IList<Vehicle> vehicles = _vehicleService.GetVehiclesForPart(importData?.PartNumber, importData?.LineCode); IList<Vehicle> vehicles = _vehicleFitmentService.GetVehiclesForPart(importData?.PartNumber, importData?.LineCode);
if (fitments.Count == 0 || vehicles.Count == 0) if (fitments.Count == 0 || vehicles.Count == 0)
{ {
@@ -97,47 +96,41 @@ namespace PartSource.Automation.Jobs
await SavePositionMetafield(product, vehicleIds, currentPosition); await SavePositionMetafield(product, vehicleIds, currentPosition);
IList<string> notes = fitments.Select(f => f.FitmentNoteHash) //IList<string> notes = fitments.Select(f => f.NoteText)
.Distinct()
.ToList();
IList<object> vehicleNotes = new List<object>(); // .Distinct()
// .ToList();
foreach (string noteHash in notes) //IList<object> vehicleNotes = new List<object>();
{
FitmentNote fitmentNote = await _fitmentContext.FitmentNotes.FirstOrDefaultAsync(f => f.Hash == noteHash);
if (fitmentNote == null) //foreach (string noteText in notes)
{ //{
continue; // vehicleIds = fitments.Where(f => f.NoteText == noteText)
} // .Select(f => new { f.EngineConfigId, f.BaseVehicleId })
// .SelectMany(f => vehicles.Where(v => v.BaseVehicleId == f.BaseVehicleId && v.EngineConfigId == f.EngineConfigId))
// .Select(v => v.VehicleToEngineConfigId)
// .ToList();
vehicleIds = fitments.Where(f => f.FitmentNoteHash == noteHash) // vehicleNotes.Add(new { noteText, vehicleIds });
.Select(f => new { f.EngineConfigId, f.BaseVehicleId }) //}
.SelectMany(f => vehicles.Where(v => v.BaseVehicleId == f.BaseVehicleId && v.EngineConfigId == f.EngineConfigId))
.Select(v => v.VehicleToEngineConfigId)
.ToList();
vehicleNotes.Add(new { fitmentNote.NoteText, vehicleIds }); //string json = JsonConvert.SerializeObject(vehicleNotes);
} //if (json.Length >= 100000)
//{
// continue;
//}
string json = JsonConvert.SerializeObject(vehicleNotes); //Metafield vehicleMetafield = new Metafield
if (json.Length >= 100000) //{
{ // Namespace = "fitment",
continue; // Key = "note_text",
} // Value = json,
// ValueType = "json_string",
// OwnerResource = "product",
// OwnerId = product.Id
//};
Metafield vehicleMetafield = new Metafield //await _shopifyClient.Metafields.Add(vehicleMetafield);
{
Namespace = "fitment",
Key = "note_text",
Value = json,
Type = "json",
OwnerResource = "product",
OwnerId = product.Id
};
await _shopifyClient.Metafields.Add(vehicleMetafield);
//importData.UpdatedAt = DateTime.Now; //importData.UpdatedAt = DateTime.Now;
//importData.UpdateType = "Positioning"; //importData.UpdateType = "Positioning";
@@ -151,7 +144,6 @@ namespace PartSource.Automation.Jobs
try try
{ {
Console.WriteLine(i);
products = await _shopifyClient.Products.GetNext(); products = await _shopifyClient.Products.GetNext();
} }
@@ -205,12 +197,12 @@ namespace PartSource.Automation.Jobs
Namespace = "position", Namespace = "position",
Key = key, Key = key,
Value = json, Value = json,
Type = "json", Type = "json_string",
OwnerResource = "product", OwnerResource = "product",
OwnerId = product.Id OwnerId = product.Id
}; };
//System.Diagnostics.Debug.WriteLine(json); System.Diagnostics.Debug.WriteLine(json);
await _shopifyClient.Metafields.Add(vehicleMetafield); await _shopifyClient.Metafields.Add(vehicleMetafield);
} }

View File

@@ -35,14 +35,13 @@ namespace PartSource.Automation.Jobs
public async Task Run(CancellationToken token, params string[] arguments) public async Task Run(CancellationToken token, params string[] arguments)
{ {
List<UpdatePricingResult> pricingReport = new List<UpdatePricingResult>();
IEnumerable<Product> products = null; IEnumerable<Product> products = null;
IEnumerable<PartPrice> prices = null; IEnumerable<PartPrice> prices = null;
try try
{ {
products = await _shopifyClient.Products.Get(new Dictionary<string, object> { { "limit", 250 } }); products = await _shopifyClient.Products.Get(new Dictionary<string, object> { { "limit", 250 } });
prices = await _partSourceContext.PartPrices.AsNoTracking().ToListAsync(token); prices = await _partSourceContext.PartPrices.AsNoTracking().ToListAsync();
} }
catch (Exception ex) catch (Exception ex)
@@ -52,6 +51,7 @@ namespace PartSource.Automation.Jobs
throw; throw;
} }
int count = 0;
while (products != null && products.Any()) while (products != null && products.Any())
{ {
foreach (Product product in products) foreach (Product product in products)
@@ -76,12 +76,12 @@ namespace PartSource.Automation.Jobs
try try
{ {
await _shopifyClient.Metafields.Add(new Metafield _shopifyClient.BulkActions.Add(new Metafield
{ {
Namespace = "Pricing", Namespace = "Pricing",
Key = "CorePrice", Key = "CorePrice",
Value = partPrice.Core_Price.HasValue ? partPrice.Core_Price.Value.ToString() : "0.00", Value = partPrice.Core_Price.HasValue ? partPrice.Core_Price.Value.ToString() : "0.00",
Type = "single_line_text_field", Type = "string",
OwnerResource = "product", OwnerResource = "product",
OwnerId = product.Id OwnerId = product.Id
}); });
@@ -89,13 +89,15 @@ namespace PartSource.Automation.Jobs
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning(ex, $"Failed to update core price metafield for product ID {product.Id}"); _logger.LogWarning(ex, $"Failed to update pricing for product ID {product.Id}");
} }
} }
try try
{ {
await _shopifyClient.Products.Update(product); _shopifyClient.BulkActions.Update(product);
} }
catch (Exception ex) catch (Exception ex)
@@ -105,12 +107,11 @@ namespace PartSource.Automation.Jobs
} }
} }
try try
{ {
count += products.Count();
products = await _shopifyClient.Products.GetNext(); products = await _shopifyClient.Products.GetNext();
_logger.LogInformation($"Total updated: {count}");
_logger.LogInformation($"Total updated: {pricingReport.Count}");
} }
catch (Exception ex) catch (Exception ex)
@@ -120,7 +121,12 @@ namespace PartSource.Automation.Jobs
} }
} }
_emailService.Send("Pricing Update Completed", $"The pricing update has completed."); while (_shopifyClient.BulkActions.PendingCount() > 0)
{
await Task.Delay(15 * 1000, token);
_logger.LogInformation(_shopifyClient.BulkActions.PendingCount().ToString());
}
// _emailService.Send("Pricing Update Completed", $"The pricing update has completed.");
} }
} }
} }

View File

@@ -21,10 +21,11 @@
<PackageReference Include="Ratermania.Automation" Version="6.16.9" /> <PackageReference Include="Ratermania.Automation" Version="6.16.9" />
<PackageReference Include="Ratermania.Automation.Common" Version="6.16.9" /> <PackageReference Include="Ratermania.Automation.Common" Version="6.16.9" />
<PackageReference Include="Ratermania.JwtSpot" Version="6.16.9" /> <PackageReference Include="Ratermania.JwtSpot" Version="6.16.9" />
<PackageReference Include="Ratermania.Shopify" Version="6.16.8" /> <PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\ratermania\Packages\Shopify\Shopify.csproj" />
<ProjectReference Include="..\PartSource.Data\PartSource.Data.csproj" /> <ProjectReference Include="..\PartSource.Data\PartSource.Data.csproj" />
<ProjectReference Include="..\PartSource.Services\PartSource.Services.csproj" /> <ProjectReference Include="..\PartSource.Services\PartSource.Services.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -6,7 +6,6 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using PartSource.Automation.Jobs; using PartSource.Automation.Jobs;
using PartSource.Automation.Jobs.POC; using PartSource.Automation.Jobs.POC;
using PartSource.Automation.Models.Configuration;
using PartSource.Automation.Services; using PartSource.Automation.Services;
using PartSource.Data; using PartSource.Data;
using PartSource.Data.AutoMapper; using PartSource.Data.AutoMapper;
@@ -15,7 +14,6 @@ using PartSource.Services;
using Ratermania.Automation.DependencyInjection; using Ratermania.Automation.DependencyInjection;
using Ratermania.Automation.Logging; using Ratermania.Automation.Logging;
using Ratermania.Shopify.DependencyInjection; using Ratermania.Shopify.DependencyInjection;
using Ratermania.JwtSpot.Configuration;
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -24,8 +22,7 @@ namespace PartSource.Automation
{ {
class Program class Program
{ {
static async Task Main(string[] args) static async Task Main(string[] args){
{
try try
{ {
using IHost host = CreateHostBuilder().Build(); using IHost host = CreateHostBuilder().Build();
@@ -79,30 +76,43 @@ namespace PartSource.Automation
}) })
.AddAutomation(options => .AddAutomation(options =>
{
options.HasBaseInterval(new TimeSpan(0, 15, 0)) options.HasBaseInterval(new TimeSpan(0, 15, 0))
.HasMaxFailures(3) .HasMaxFailures(1)
.HasJob<ExecuteSsisPackages>(options => //.HasJob<TestJob>(options => options.HasInterval(new TimeSpan(7, 0, 0, 0)));
options.HasInterval(new TimeSpan(24, 0, 0)) //
.StartsAt(DateTime.Today.AddHours(26))) //.HasJob<SyncronizeProducts>(options => options.HasInterval(new TimeSpan(24, 0, 0)))
.HasJob<UpdatePricing>(options => // .HasJob<ProcessWhiFitment>(options => options.HasInterval(new TimeSpan(24, 0, 0)));
options.HasInterval(new TimeSpan(24, 0, 0)) //.HasJob<ProcessWhiVehicles>(options => options.HasInterval(new TimeSpan(24, 0, 0))
.StartsAt(DateTime.Today.AddHours(27)) //.HasDependency<SyncronizeProducts>()
.HasDependency<ExecuteSsisPackages>()) //.HasJob<UpdateFitment>(options => options.HasInterval(new TimeSpan(24, 0, 0)));
.UseApiServer(opts => //.HasJob<UpdatePositioning>(options => options.HasInterval(new TimeSpan(24, 0, 0))
opts.HasApiKey("PartsourceAPIKey") // .HasDependency<UpdateFitment>()
.UseJwtSpot(jwt => // .HasDependency<ProcessWhiFitment>()
jwt.HasAudience(builder.Configuration["JwtSpot:Audience"]) // .HasDependency<SyncronizeProducts>()
.HasIssuer(builder.Configuration["JwtSpot:Issuer"]) // .StartsAt(DateTime.Today.AddHours(8))
.UseX509Certificate(builder.Configuration["JwtSpot:CertPath"]) //) ;
.UseJwksUrl(builder.Configuration["JwtSpot:JwksUrl"]))) //.HasJob<StatusCheck>(options => options.HasInterval(new TimeSpan(24, 0, 0))
.UseSqlServer(builder.Configuration.GetConnectionString("AutomationDatabase"))) // .StartsAt(DateTime.Parse("2021-04-01 08:00:00"))
//)
//.HasJob<ExecuteSsisPackages>(options =>
// options.HasInterval(new TimeSpan(24, 0, 0))
// //.StartsAt(DateTime.Today.AddHours(25))
// )
.HasJob<UpdatePricing>(options => options.HasInterval(new TimeSpan(24, 0, 0))
//.HasDependency<ExecuteSsisPackages>()
// .StartsAt(DateTime.Today.AddHours(28))
);
//);
//.AddApiServer();
})
.AddSingleton(builder.Configuration.GetSection("FtpServers:AzureConfiguration").Get<FtpConfiguration>())
.AddSingleton<FtpService>()
.AddSingleton<EmailService>() .AddSingleton<EmailService>()
.AddSingleton<SsisService>() .AddSingleton<SsisService>()
.AddSingleton<WhiSeoService>() .AddSingleton<WhiSeoService>()
.AddSingleton<VehicleService>() .AddSingleton<VehicleService>()
.AddSingleton<VehicleFitmentService>()
.AddSingleton<NexpartService>() .AddSingleton<NexpartService>()
.AddAutoMapper(typeof(PartSourceProfile)); .AddAutoMapper(typeof(PartSourceProfile));
@@ -112,7 +122,7 @@ namespace PartSource.Automation
logging.AddEventLog(); logging.AddEventLog();
logging.AddConsole(); logging.AddConsole();
//logging.AddProvider(new AutomationLoggerProvider()); // logging.AddProvider(new AutomationLoggerProvider());
}); });
} }
} }

View File

@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using PartSource.Data.Contexts;
using PartSource.Data.Dtos;
using PartSource.Data.Models;
namespace PartSource.Automation.Services
{
public class VehicleFitmentService
{
private readonly FitmentContext _fitmentContext;
public VehicleFitmentService(FitmentContext fitmentContext)
{
_fitmentContext = fitmentContext;
}
public IList<string> GetYmmFitment(IList<Vehicle> vehicles)
{
if (vehicles.Count == 0)
{
return new string[0];
}
IList<string> fitmentTags = new List<string>();
IList<string> makeModels = vehicles.OrderBy(v => v.MakeName).ThenBy(v => v.ModelName).Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList();
foreach (string makeModel in makeModels)
{
string make = makeModel.Split(',')[0];
string model = makeModel.Split(',')[1];
List<string> years = vehicles
.Where(v => v.MakeName == make && v.ModelName == model)
.OrderBy(v => v.Year)
.Select(v => v.Year.ToString().Trim())
.Distinct()
.ToList();
string tag = $"{string.Join('-', years)} {make.Trim()} {model.Trim()}";
System.Diagnostics.Debug.WriteLine(tag);
fitmentTags.Add(tag);
}
return fitmentTags;
}
public IList<string> GetYmmFitmentRange(IList<Vehicle> vehicles)
{
if (vehicles.Count == 0)
{
return new string[0];
}
IList<string> fitmentTags = new List<string>();
IList<string> makeModels = vehicles.Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList();
foreach (string makeModel in makeModels)
{
string make = makeModel.Split(',')[0];
string model = makeModel.Split(',')[1];
int minYear = vehicles
.Where(v => v.MakeName == make && v.ModelName == model)
.Min(v => v.Year);
int maxYear = vehicles
.Where(v => v.MakeName == make && v.ModelName == model)
.Max(v => v.Year);
string tag = minYear == maxYear
? $"{minYear} {make.Trim()} {model.Trim()}"
: $"{minYear}-{maxYear} {make.Trim()} {model.Trim()}";
System.Diagnostics.Debug.WriteLine(tag);
fitmentTags.Add(tag);
}
return fitmentTags;
}
public IList<int> GetVehicleIdFitment(IList<Vehicle> vehicles)
{
return vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToArray();
}
public IList<Vehicle> GetVehiclesForPart(string partNumber, string lineCode, int maxVehicles = 0)
{
if (string.IsNullOrEmpty(partNumber) || string.IsNullOrEmpty(lineCode))
{
return null;
}
partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9\\-]", string.Empty);
IQueryable<string> whiCodes = _fitmentContext.DcfMappings
.Where(d => d.LineCode == lineCode)
.Select(d => d.WhiCode);
IQueryable<Vehicle> vehicles = _fitmentContext.Fitments
.Where(f => f.PartNumber == partNumber && whiCodes.Contains(f.LineCode))
.Join(_fitmentContext.Vehicles,
f => new { f.BaseVehicleId, f.EngineConfigId },
v => new { v.BaseVehicleId, v.EngineConfigId },
(f, v) => v)
.Distinct()
.OrderByDescending(x => x.Year);
if (maxVehicles > 0)
{
vehicles = vehicles.Take(maxVehicles);
}
return vehicles.ToList();
}
public IList<VehicleFitmentDto> GetVehicleFitmentForPart(string partNumber, string lineCode, int maxVehicles = 0)
{
if (string.IsNullOrEmpty(partNumber) || string.IsNullOrEmpty(lineCode))
{
return null;
}
partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9\\-]", string.Empty);
IQueryable<string> whiCodes = _fitmentContext.DcfMappings
.Where(d => d.LineCode == lineCode)
.Select(d => d.WhiCode);
IQueryable<VehicleFitmentDto> vehicles = _fitmentContext.Fitments
.Where(f => f.PartNumber == partNumber && whiCodes.Contains(f.LineCode))
.Join(_fitmentContext.Vehicles,
f => new { f.BaseVehicleId, f.EngineConfigId },
v => new { v.BaseVehicleId, v.EngineConfigId },
(f, v) => new VehicleFitmentDto
{
Fitment = f,
Vehicle = v
})
.Distinct();
if (maxVehicles > 0)
{
vehicles = vehicles.Take(maxVehicles);
}
return vehicles.ToList();
}
}
}

View File

@@ -1,13 +1,14 @@
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
using System;
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using PartSource.Automation.Models.Configuration; using PartSource.Automation.Models.Configuration;
using PartSource.Automation.Models.Enums; using PartSource.Automation.Models.Enums;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Text;
namespace PartSource.Automation.Services namespace PartSource.Automation.Services
{ {
@@ -52,12 +53,12 @@ namespace PartSource.Automation.Services
} }
} }
public void TruncateVehicleTables() public void TruncateVehicleTable()
{ {
using SqlConnection connection = new SqlConnection(_connectionString); using SqlConnection connection = new SqlConnection(_connectionString);
connection.Open(); connection.Open();
using SqlCommand command = new SqlCommand($"exec DropVehicleTables", connection); using SqlCommand command = new SqlCommand($"truncate table dbo.Vehicle", connection);
command.ExecuteNonQuery(); command.ExecuteNonQuery();
} }
@@ -150,6 +151,10 @@ namespace PartSource.Automation.Services
using SqlCommand command = new SqlCommand($"exec CreateFitmentView", connection); using SqlCommand command = new SqlCommand($"exec CreateFitmentView", connection);
command.CommandTimeout = 1800; command.CommandTimeout = 1800;
command.ExecuteNonQuery(); command.ExecuteNonQuery();
using SqlCommand command2 = new SqlCommand($"exec CreateFitmentIndexes", connection);
command.CommandTimeout = 1800;
command2.ExecuteNonQuery();
} }
public void CreateVehicleTable() public void CreateVehicleTable()

View File

@@ -1,7 +1,6 @@
{ {
"ConnectionStrings": { "ConnectionStrings": {
"AutomationDatabase": "Data Source=localhost;Initial Catalog=Automation;Integrated Security=true;Trust Server Certificate=true;", "FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;Integrated Security=true;TrustServerCertificate=True",
"FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;Integrated Security=true",
"PartSourceDatabase": "Server=tcp:ps-whi.database.windows.net,1433;Initial Catalog=ps-whi-stage;Persist Security Info=False;User ID=ps-whi;Password=9-^*N5dw!6:|.5Q;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" "PartSourceDatabase": "Server=tcp:ps-whi.database.windows.net,1433;Initial Catalog=ps-whi-stage;Persist Security Info=False;User ID=ps-whi;Password=9-^*N5dw!6:|.5Q;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}, },
"emailConfiguration": { "emailConfiguration": {
@@ -32,17 +31,11 @@
"ApiSecret": "527a3b4213c2c7ecb214728a899052df", "ApiSecret": "527a3b4213c2c7ecb214728a899052df",
"ShopDomain": "partsource.myshopify.com" "ShopDomain": "partsource.myshopify.com"
}, },
"JwtSpot": {
"Audience": "Ratermania.Automation",
"Issuer": "https://tomraterman.com",
"JwksUrl": "http://localhost:5103/jwks",
"CertPath": "C:\\Users\\tom\\Desktop\\PartsourceAutomation.pfx"
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Microsoft": "Warning", "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information",
// "Microsoft.EntityFrameworkCore.Database.Command": "Information" // "Microsoft.EntityFrameworkCore.Database.Command": "Information"
}, },
"EventLog": { "EventLog": {

View File

@@ -1,10 +1,4 @@
// Decompiled with JetBrains decompiler using System.Xml.Serialization;
// Type: PartSource.Data.Nexpart.SmartPageDataSearch
// Assembly: PartSource.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 3EDAB3F5-83E7-4F65-906E-B40192014C57
// Assembly location: C:\Users\Tommy\Desktop\PS temp\PartSource.Data.dll
using System.Xml.Serialization;
namespace PartSource.Data.Nexpart namespace PartSource.Data.Nexpart
{ {
@@ -13,13 +7,18 @@ namespace PartSource.Data.Nexpart
{ {
public SmartPageDataSearch() public SmartPageDataSearch()
{ {
this.PSRequestHeader = new PSRequestHeader(); PSRequestHeader = new PSRequestHeader();
} }
[XmlElement(ElementName = "PSRequestHeader", Namespace = "http://whisolutions.com/PartSelectService-v1", Order = 1)] [XmlElement(ElementName = "PSRequestHeader", Namespace = "http://whisolutions.com/PartSelectService-v1", Order = 1)]
public PSRequestHeader PSRequestHeader { get; set; } public PSRequestHeader PSRequestHeader { get; set; }
[XmlElement(ElementName = "Item", Namespace = "http://whisolutions.com/PartSelectService-v1", Order = 2)] [XmlElement(ElementName = "Item", Namespace = "http://whisolutions.com/PartSelectService-v1", Order = 2)]
public Item[] Items { get; set; } public Item[] Items { get; set; }
[XmlElement(ElementName = "DataOption", Namespace = "http://whisolutions.com/PartSelectService-v1", Order = 3)]
public string[] DataOption { get; set; }
} }
} }

View File

@@ -14,10 +14,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="11.0.1" /> <PackageReference Include="AutoMapper" Version="11.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Ratermania.Shopify" Version="6.16.8" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\ratermania\Packages\Shopify\Shopify.csproj" />
<ProjectReference Include="..\PartSource.Data\PartSource.Data.csproj" /> <ProjectReference Include="..\PartSource.Data\PartSource.Data.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -18,13 +18,11 @@ namespace PartSource.Services
{ {
private readonly IMapper _mapper; private readonly IMapper _mapper;
private readonly PartSourceContext _partSourceContext; private readonly PartSourceContext _partSourceContext;
private readonly FitmentContext _fitmentContext;
public VehicleService(IMapper mapper, PartSourceContext partSourceContext, FitmentContext fitmentContext) public VehicleService(IMapper mapper, PartSourceContext partSourceContext)
{ {
_mapper = mapper; _mapper = mapper;
_partSourceContext = partSourceContext; _partSourceContext = partSourceContext;
_fitmentContext = fitmentContext;
} }
public async Task<IList<Vehicle>> GetVehicles(VehicleDto vehicleQuery) public async Task<IList<Vehicle>> GetVehicles(VehicleDto vehicleQuery)
@@ -234,143 +232,6 @@ namespace PartSource.Services
.ToListAsync(); .ToListAsync();
} }
#endregion #endregion
public IList<string> GetYmmFitment(IList<Vehicle> vehicles)
{
if (vehicles.Count == 0)
{
return new string[0];
}
IList<string> fitmentTags = new List<string>();
IList<string> makeModels = vehicles.OrderBy(v => v.MakeName).ThenBy(v => v.ModelName).Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList();
foreach (string makeModel in makeModels)
{
string make = makeModel.Split(',')[0];
string model = makeModel.Split(',')[1];
List<string> years = vehicles
.Where(v => v.MakeName == make && v.ModelName == model)
.OrderBy(v => v.Year)
.Select(v => v.Year.ToString().Trim())
.Distinct()
.ToList();
string tag = $"{string.Join('-', years)} {make.Trim()} {model.Trim()}";
System.Diagnostics.Debug.WriteLine(tag);
fitmentTags.Add(tag);
}
return fitmentTags;
}
public IList<string> GetYmmFitmentRange(IList<Vehicle> vehicles)
{
if (vehicles.Count == 0)
{
return new string[0];
}
IList<string> fitmentTags = new List<string>();
IList<string> makeModels = vehicles.Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList();
foreach (string makeModel in makeModels)
{
string make = makeModel.Split(',')[0];
string model = makeModel.Split(',')[1];
int minYear = vehicles
.Where(v => v.MakeName == make && v.ModelName == model)
.Min(v => v.Year);
int maxYear = vehicles
.Where(v => v.MakeName == make && v.ModelName == model)
.Max(v => v.Year);
string tag = minYear == maxYear
? $"{minYear} {make.Trim()} {model.Trim()}"
: $"{minYear}-{maxYear} {make.Trim()} {model.Trim()}";
System.Diagnostics.Debug.WriteLine(tag);
fitmentTags.Add(tag);
}
return fitmentTags;
}
public IList<int> GetVehicleIdFitment(IList<Vehicle> vehicles)
{
return vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToArray();
}
public IList<Vehicle> GetVehiclesForPart(string partNumber, string lineCode, int maxVehicles = 0)
{
if (string.IsNullOrEmpty(partNumber) || string.IsNullOrEmpty(lineCode))
{
return null;
}
partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9\\-]", string.Empty);
IQueryable<string> whiCodes = _fitmentContext.DcfMappings
.Where(d => d.LineCode == lineCode)
.Select(d => d.WhiCode);
IQueryable<Vehicle> vehicles = _fitmentContext.Fitments
.Where(f => f.PartNumber == partNumber && whiCodes.Contains(f.LineCode))
.Join(_fitmentContext.Vehicles,
f => new { f.BaseVehicleId, f.EngineConfigId },
v => new { v.BaseVehicleId, v.EngineConfigId },
(f, v) => v)
.Distinct()
.OrderByDescending(x => x.Year);
if (maxVehicles > 0)
{
vehicles = vehicles.Take(maxVehicles);
}
return vehicles.ToList();
}
public IList<VehicleFitmentDto> GetVehicleFitmentForPart(string partNumber, string lineCode, int maxVehicles = 0)
{
if (string.IsNullOrEmpty(partNumber) || string.IsNullOrEmpty(lineCode))
{
return null;
}
partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9\\-]", string.Empty);
IQueryable<string> whiCodes = _fitmentContext.DcfMappings
.Where(d => d.LineCode == lineCode)
.Select(d => d.WhiCode);
IQueryable<VehicleFitmentDto> vehicles = _fitmentContext.Fitments
.Where(f => f.PartNumber == partNumber && whiCodes.Contains(f.LineCode))
.Join(_fitmentContext.Vehicles,
f => new { f.BaseVehicleId, f.EngineConfigId },
v => new { v.BaseVehicleId, v.EngineConfigId },
(f, v) => new VehicleFitmentDto
{
Fitment = f,
Vehicle = v
})
.Distinct();
if (maxVehicles > 0)
{
vehicles = vehicles.Take(maxVehicles);
}
return vehicles.ToList();
}
} }
} }

View File

@@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PartSource.Services", "Part
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PartSource.Automation", "PartSource.Automation\PartSource.Automation.csproj", "{C85D675B-A76C-4F9C-9C57-1E063211C946}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PartSource.Automation", "PartSource.Automation\PartSource.Automation.csproj", "{C85D675B-A76C-4F9C-9C57-1E063211C946}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shopify", "..\ratermania\Packages\Shopify\Shopify.csproj", "{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Also Debug|Any CPU = Also Debug|Any CPU Also Debug|Any CPU = Also Debug|Any CPU
@@ -96,6 +98,24 @@ Global
{C85D675B-A76C-4F9C-9C57-1E063211C946}.Release|x64.Build.0 = Release|Any CPU {C85D675B-A76C-4F9C-9C57-1E063211C946}.Release|x64.Build.0 = Release|Any CPU
{C85D675B-A76C-4F9C-9C57-1E063211C946}.Release|x86.ActiveCfg = Release|Any CPU {C85D675B-A76C-4F9C-9C57-1E063211C946}.Release|x86.ActiveCfg = Release|Any CPU
{C85D675B-A76C-4F9C-9C57-1E063211C946}.Release|x86.Build.0 = Release|Any CPU {C85D675B-A76C-4F9C-9C57-1E063211C946}.Release|x86.Build.0 = Release|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Also Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Also Debug|Any CPU.Build.0 = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Also Debug|x64.ActiveCfg = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Also Debug|x64.Build.0 = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Also Debug|x86.ActiveCfg = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Also Debug|x86.Build.0 = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Debug|x64.ActiveCfg = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Debug|x64.Build.0 = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Debug|x86.Build.0 = Debug|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Release|Any CPU.Build.0 = Release|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Release|x64.ActiveCfg = Release|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Release|x64.Build.0 = Release|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Release|x86.ActiveCfg = Release|Any CPU
{1A9096CE-AF40-4DBA-A754-93F8CFC1EBDA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE