diff --git a/PartSource.Api/Controllers/PartsController.cs b/PartSource.Api/Controllers/PartsController.cs index 032dcb7..a007504 100644 --- a/PartSource.Api/Controllers/PartsController.cs +++ b/PartSource.Api/Controllers/PartsController.cs @@ -5,91 +5,128 @@ using PartSource.Data.Models; using PartSource.Data.Nexpart; using PartSource.Services; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Part = PartSource.Data.Models.Part; namespace PartSource.Api.Controllers { - [Route("[controller]")] + [Route("v2/[controller]")] [ApiController] [ApiExplorerSettings(GroupName = "v1")] public class PartsController : BaseNexpartController { private readonly NexpartService _nexpartService; private readonly PartService _partService; + private readonly VehicleService _vehicleService; - public PartsController(NexpartService nexpartService, PartService partService) + public PartsController(NexpartService nexpartService, PartService partService, VehicleService vehicleService) { - this._nexpartService = nexpartService; + _nexpartService = nexpartService; _partService = partService; + _vehicleService = vehicleService; } [HttpGet] - [Route("PartNumber/{partNumber}/LineCode/{lineCode}")] - public ActionResult GetPart(string partNumber, string lineCode) + [Route("positions")] + public async Task GetPositions([FromQuery] string sku, [FromQuery] int vehicleId) { - new SmartPageDataSearch().Items = new Item[1] - { - new Item() - { - PartNumber = partNumber.ToUpperInvariant(), - MfrCode = lineCode.ToUpperInvariant() - } - }; - return (ActionResult)this.Ok(); - } + Part part = await _partService.GetPartBySku(sku); + Vehicle vehicle = await _vehicleService.GetVehicleById(vehicleId); - [HttpGet] - [Route("search/basevehicleid/{baseVehicleId}")] - public async Task Search(int baseVehicleId, [FromQuery] string query) - { - PartsController partsController = this; - PartTypeSearch requestContent = new PartTypeSearch() + if (part == null) { - SearchString = query, - SearchType = "ALL", - SearchOptions = "PARTIAL_MATCH", - VehicleIdentifier = new VehicleIdentifier() + return BadRequest(new { - BaseVehicleId = baseVehicleId - } - }; - - PartTypeSearchResponse response = await _nexpartService.SendRequest(requestContent); - - return partsController.NexpartResponse(response); - } - - [HttpGet] - [Route("validate/partTypeId/{partTypeId}/baseVehicleId/{baseVehicleId}")] - public async Task ValidatePartFitment(int partTypeId, int baseVehicleId) - { - PartsController partsController = this; - PartTypesValidateLookup typesValidateLookup = new PartTypesValidateLookup(); - typesValidateLookup.PartTypes = new PartType[1] - { - new PartType() { Id = partTypeId } - }; - typesValidateLookup.VehicleIdentifier = new VehicleIdentifier() - { - BaseVehicleId = baseVehicleId - }; - PartTypesValidateLookup requestContent = typesValidateLookup; - PartTypesValidateLookupResponse response = await partsController._nexpartService.SendRequest(requestContent); - return partsController.NexpartResponse(response); - } - - [HttpGet] - [Route("search/fitment")] - public async Task FitmentSearch([FromQuery] FitmentSearchDto fitmentSearchDto) - { - IList fitments = _partService.GetFitments(fitmentSearchDto); - - if (fitments == null) - { - return NotFound(); + Message = $"No part data is available for SKU {sku}. Confirm it is available in the database maintained by Sound Press.", + Reason = $"{nameof(_partService.GetPartBySku)} returned null" + }); } - return Ok(new { Data = fitments }); + if (vehicle == null) + { + return BadRequest(new + { + Message = $"No vehicle data is available for SKU {sku}. Confirm it is available in the database maintained by Sound Press.", + Reason = $"{nameof(_vehicleService.GetVehicleById)} returned null" + }); + } + + IList mappings = await _partService.GetDcfMapping(part.LineCode); + Item[] items = mappings.Select(m => new Item + { + PartNumber = part.PartNumber, + MfrCode = m.WhiCode + }) + .ToArray(); + + SmartPageDataSearch smartPageDataSearch = new SmartPageDataSearch + { + Items = items + }; + + SmartPageDataSearchResponse smartPageResponse = await _nexpartService.SendRequest(smartPageDataSearch); + if (smartPageResponse.ResponseBody?.Item == null) + { + return NotFound(new + { + Message = $"No WHI data is available for SKU {sku}", + Reason = $"{nameof(SmartPageDataSearch)} returned null" + }); + } + + PartType[] partTypes = smartPageResponse.ResponseBody.Item.Select(i => new PartType + { + Id = i.Part.PartType.Id + }) + .ToArray(); + + ApplicationSearch applicationSearch = new ApplicationSearch + { + VehicleIdentifier = new VehicleIdentifier + { + BaseVehicleId = vehicle.BaseVehicleId + }, + MfrCode = mappings.Select(m => m.WhiCode).ToArray(), + PartType = new[] { new PartType { Id = smartPageResponse.ResponseBody.Item[0].Part.PartType.Id } }, + Criterion = new[] + { + new Criterion + { + Attribute = "REGION", + Id = 2 + } + }, + GroupBy = "PARTTYPE" + }; + + ApplicationSearchResponse response = await _nexpartService.SendRequest(applicationSearch); + + if (response.ResponseBody == null) + { + return NotFound(new + { + Message = $"No WHI data is available for SKU {sku}", + Reason = $"{nameof(ApplicationSearch)} returned null" + }); + } + + IList positions = new List(); + foreach (App app in response.ResponseBody?.App) + { + if (!string.IsNullOrEmpty(app.Position) && app.Part == part.PartNumber) + { + positions.Add(app.Position); + } + } + + return Ok(new + { + VehicleId = vehicleId, + Sku = sku, + Positions = positions.Distinct() + }); } + } } diff --git a/PartSource.Api/Controllers/WipersController.cs b/PartSource.Api/Controllers/WipersController.cs new file mode 100644 index 0000000..5245389 --- /dev/null +++ b/PartSource.Api/Controllers/WipersController.cs @@ -0,0 +1,63 @@ + +using Microsoft.AspNetCore.Mvc; +using PartSource.Data.Dtos; +using PartSource.Data.Models; +using PartSource.Data.Nexpart; +using PartSource.Services; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace PartSource.Api.Controllers +{ + [Route("[controller]")] + [ApiController] + [ApiExplorerSettings(GroupName = "v1")] + public class WipersController : BaseNexpartController + { + private readonly NexpartService _nexpartService; + + public WipersController(NexpartService nexpartService) + { + _nexpartService = nexpartService; + } + + [HttpGet] + [Route("{baseVehicleId}")] + public async Task GetWipersForVehicle(int baseVehicleId) + { + ApplicationSearch applicationSearch = new ApplicationSearch + { + VehicleIdentifier = new VehicleIdentifier + { + BaseVehicleId = baseVehicleId + }, + MfrCode = new[] { "BOS", "TRI" }, + PartType = new[] + { + new PartType { Id = 8852 } + }, + Criterion = new[] + { + new Criterion + { + Attribute = "REGION", + Id = 2 + } + }, + GroupBy = "PARTTYPE" + }; + + ApplicationSearchResponse response = await _nexpartService.SendRequest(applicationSearch); + + if (response.ResponseBody != null) + { + return NexpartResponse(response); + } + + else + { + return NotFound(); + } + } + } +} diff --git a/PartSource.Api/PartSource.Api.csproj b/PartSource.Api/PartSource.Api.csproj index 39087ef..48caee0 100644 --- a/PartSource.Api/PartSource.Api.csproj +++ b/PartSource.Api/PartSource.Api.csproj @@ -30,14 +30,14 @@ - - + + - - + + - - + + diff --git a/PartSource.Api/Startup.cs b/PartSource.Api/Startup.cs index 1ba5b6a..aca4403 100644 --- a/PartSource.Api/Startup.cs +++ b/PartSource.Api/Startup.cs @@ -67,9 +67,9 @@ namespace PartSource.Api services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("PartSourceDatabase")) ); - services.AddDbContext(options => - options.UseSqlServer(Configuration.GetConnectionString("FitmentDatabase")) - ); + //services.AddDbContext(options => + // options.UseSqlServer(Configuration.GetConnectionString("FitmentDatabase")) + //); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -86,12 +86,12 @@ namespace PartSource.Api app.UseCors("Default"); - app.UseSwagger(); - app.UseReDoc(c => - { - c.SpecUrl = "/swagger/v2/swagger.json"; - c.ExpandResponses(string.Empty); - }); + //app.UseSwagger(); + //app.UseReDoc(c => + //{ + // c.SpecUrl = "/swagger/v2/swagger.json"; + // c.ExpandResponses(string.Empty); + //}); // app.UseExceptionHandler("/Error"); // app.UseHttpsRedirection(); diff --git a/PartSource.Api/appsettings.json b/PartSource.Api/appsettings.json index 5d9e49f..b3e4a45 100644 --- a/PartSource.Api/appsettings.json +++ b/PartSource.Api/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { "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=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;", - "FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;Integrated Security=true" + //"FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;Integrated Security=true" }, "Logging": { "LogLevel": { diff --git a/PartSource.Automation/Jobs/GetNexpartMenuItems.cs b/PartSource.Automation/Jobs/GetNexpartMenuItems.cs index 12b14d7..41b2397 100644 --- a/PartSource.Automation/Jobs/GetNexpartMenuItems.cs +++ b/PartSource.Automation/Jobs/GetNexpartMenuItems.cs @@ -27,7 +27,7 @@ namespace PartSource.Automation.Jobs MenuNodesLookup menuNodesLookup = new MenuNodesLookup { - MenuId = 1, + MenuId = 2, NumberOfLevels = 1 }; @@ -39,7 +39,7 @@ namespace PartSource.Automation.Jobs MenuNodesLookup subgroupLookup = new MenuNodesLookup { - MenuId = 1, + MenuId = 2, NumberOfLevels = 1, ParentMenuNodeId = categoryNode.Id }; @@ -52,7 +52,7 @@ namespace PartSource.Automation.Jobs MenuNodesLookup thirdLookup = new MenuNodesLookup { - MenuId = 1, + MenuId = 2, NumberOfLevels = 1, ParentMenuNodeId = subgroupNode.Id }; @@ -67,7 +67,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); ; } diff --git a/PartSource.Automation/Jobs/POC/UpdateBulbFitment.cs b/PartSource.Automation/Jobs/POC/UpdateBulbFitment.cs new file mode 100644 index 0000000..eacd838 --- /dev/null +++ b/PartSource.Automation/Jobs/POC/UpdateBulbFitment.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +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 UpdateBulbFitment : IAutomationJob + { + private readonly FitmentContext _fitmentContext; + private readonly NexpartService _nexpartService; + private readonly ShopifyClient _shopifyClient; + + public UpdateBulbFitment(FitmentContext fitmentContext, NexpartService nexpartService, ShopifyClient shopifyClient) + { + _fitmentContext = fitmentContext; + _nexpartService = nexpartService; + _shopifyClient = shopifyClient; + } + + public async Task Run() + { + await BuildDatabase(); + await UpdateShopify(); + } + + public async Task BuildDatabase() + { + IList baseVehicles = await _fitmentContext.Vehicles + .Select(v => v.BaseVehicleId) + .Distinct() + .OrderBy(i => i) + .ToListAsync(); + + foreach (int baseVehicleId in baseVehicles) + { + ApplicationSearch applicationSearch = new ApplicationSearch + { + VehicleIdentifier = new VehicleIdentifier + { + BaseVehicleId = baseVehicleId + }, + MfrCode = new[] { "C23", "CBX", "CCH", "CCJ", "CF1", "CHU", "GOO", "GPL", "OEB", "UTY", "TYC", "ILB", "SYL", "SYR", "PLP", "FOU", }, + PartType = new[] { new PartType { Id = 11696 }, new PartType { Id = 11701 }, new PartType { Id = 13343 }, new PartType { Id = 13661 }, new PartType { Id = 13662 }, new PartType { Id = 13663 }, new PartType { Id = 13675 }, new PartType { Id = 13676 }, new PartType { Id = 13677 }, new PartType { Id = 13678 }, new PartType { Id = 13716 } }, + Criterion = new[] + { + new Criterion + { + Attribute = "REGION", + Id = 2 + } + }, + GroupBy = "PARTTYPE" + }; + + ApplicationSearchResponse response = await _nexpartService.SendRequest(applicationSearch); + if (response.ResponseBody != null) + { + foreach (App app in response.ResponseBody.App) + { + try + { + if (string.IsNullOrEmpty(app.Position)) + { + app.Position = "not provided by WHI"; + } + + await _fitmentContext.Database.ExecuteSqlRawAsync("INSERT INTO Wiper (BaseVehicleId, LineCode, PartNumber, Position, PartName) VALUES ({0}, {1}, {2}, {3}, {4});", + baseVehicleId, app.MfrCode, Regex.Replace(app.Part, "[^a-zA-Z0-9]", string.Empty), app.Position, app.MfrLabel); + } + + catch (Exception ex) + { + Console.WriteLine($"Could not save {app.MfrCode}, {app.Part}, {app.Position}, {app.MfrLabel} for {baseVehicleId}: {ex.Message}"); + } + } + } + + Console.WriteLine(baseVehicleId); + } + } + + private async Task UpdateShopify() + { + //foreach (string productType in new[] { "CA171-SC223-FL22302_Halogen Lighting - Certified", "CA171-SC223-FL22303_Halogen Lighting - Xtra Vision", "CA171-SC223-FL22304_Halogen Lighting - Silverstar", "CA171-SC223-FL22305_Halogen Lighting - Silverstar Ultra", "CA171-SC223-FL22306_Halogen Lighting - Silverstar Zxe", "CA171-SC223-FL22307_Sealed Beams - OPP", "CA171-SC223-FL22308_Headlight Assemblies", "CA171-SC223-FL22311_Halogen Lighting - Fog Vision", "CA171-SC223-FL22314_Halogen Lighting - Sylvania Standard", "CA171-SC223-FL22315_Forward Lighting - HID", "CA171-SC223-FL22317_Sealed Beams - Silverstar", "CA171-SC223-FL22318_Sealed Beams - Xtra Vision", "CA171-SC223-FL22319_Forward Lighting - LED", "CA171-SC239-FL23910_Minibulbs - Long Life", "CA171-SC239-FL23920_Minibulbs - Silver Star", "CA171-SC239-FL23940_Minibulbs - LED" }) + //{ + IEnumerable products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 } }); + + while (products != null && products.Any()) + { + foreach (Product product in products) + { + try + { + string partNumber = Regex.Replace(product.Title.Split(' ')[0], "[^a-zA-Z0-9]", string.Empty); + + IList wipers = await _fitmentContext.Wipers + .Where(w => w.PartNumber == partNumber) + .OrderBy(w => w.Position) + .ToListAsync(); + + string currentPosition = wipers.FirstOrDefault()?.Position; + if (currentPosition == null) + { + continue; + } + + List vehicleIds = new List(); + + foreach (Wiper wiper in wipers) + { + if (wiper.Position != currentPosition) + { + await SavePositionMetafield(product, vehicleIds, currentPosition); + + currentPosition = wiper.Position; + vehicleIds = new List(); + } + + IList fitmentVehicleIds = _fitmentContext.Vehicles + .Where(v => v.BaseVehicleId == wiper.BaseVehicleId) + .Select(v => v.VehicleToEngineConfigId) + .Distinct() + .ToList(); + + vehicleIds.AddRange(fitmentVehicleIds); + } + + await SavePositionMetafield(product, vehicleIds, currentPosition); + } + + catch (Exception ex) + { + Console.WriteLine($"Could not update {product.Id}: {ex.Message}"); + } + } + + products = await _shopifyClient.Products.GetNext(); + } + // } + } + + //[SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "It's a Shopify metafield key")] + private async Task SavePositionMetafield(Product product, IList vehicleIds, string position) + { + if (vehicleIds.Count == 0) + { + return; + } + + string json = JsonConvert.SerializeObject(vehicleIds); + if (json.Length >= 100000) + { + // TODO: Logging + return; + } + + string key = position.ToLowerInvariant().Replace(" ", "_"); + if (key.Length > 20) + { + key = key.Substring(0, 20); + } + + Metafield vehicleMetafield = new Metafield + { + Namespace = "position", + Key = key, + Value = json, + ValueType = "json_string", + OwnerResource = "product", + OwnerId = product.Id + }; + + ; + System.Diagnostics.Debug.WriteLine(json); + + await _shopifyClient.Metafields.Add(vehicleMetafield); + } + } +} \ No newline at end of file diff --git a/PartSource.Automation/Jobs/POC/UpdateWiperFitment.cs b/PartSource.Automation/Jobs/POC/UpdateWiperFitment.cs new file mode 100644 index 0000000..a6f36fc --- /dev/null +++ b/PartSource.Automation/Jobs/POC/UpdateWiperFitment.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +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 UpdateWiperFitment : IAutomationJob + { + private readonly FitmentContext _fitmentContext; + private readonly NexpartService _nexpartService; + private readonly ShopifyClient _shopifyClient; + + public UpdateWiperFitment(FitmentContext fitmentContext, NexpartService nexpartService, ShopifyClient shopifyClient) + { + _fitmentContext = fitmentContext; + _nexpartService = nexpartService; + _shopifyClient = shopifyClient; + } + + public async Task Run() + { + // await BuildDatabase(); + await UpdateShopify(); + } + + public async Task BuildDatabase() + { + IList baseVehicles = await _fitmentContext.Vehicles + .Select(v => v.BaseVehicleId) + .Distinct() + .OrderBy(i => i) + .ToListAsync(); + + foreach (int baseVehicleId in baseVehicles) + { + ApplicationSearch applicationSearch = new ApplicationSearch + { + VehicleIdentifier = new VehicleIdentifier + { + BaseVehicleId = baseVehicleId + }, + MfrCode = new[] { "BOS", "TRI" }, + PartType = new[] { new PartType { Id = 8852 } }, + Criterion = new[] + { + new Criterion + { + Attribute = "REGION", + Id = 2 + } + }, + GroupBy = "PARTTYPE" + }; + + ApplicationSearchResponse response = await _nexpartService.SendRequest(applicationSearch); + if (response.ResponseBody != null) + { + foreach (App app in response.ResponseBody.App) + { + try + { + await _fitmentContext.Database.ExecuteSqlRawAsync("INSERT INTO Wiper (BaseVehicleId, LineCode, PartNumber, Position, PartName) VALUES ({0}, {1}, {2}, {3}, {4});", + baseVehicleId, app.MfrCode, Regex.Replace(app.Part, "[^a-zA-Z0-9]", string.Empty), app.Position, app.MfrLabel); + } + + catch (Exception ex) + { + Console.WriteLine($"Could not save {app.MfrCode}, {app.Part}, {app.Position}, {app.MfrLabel} for {baseVehicleId}: {ex.Message}"); + } + } + } + + Console.WriteLine(baseVehicleId); + } + } + + private async Task UpdateShopify() + { + foreach (string productType in new[] { "CA172-SC231-FL23107_(PS) Wipers - TRICO Neoform", "CA172-SC231-FL23109_(PS) Wipers - TRICO Tech/Exact Fit", "CA172-SC231-FL23110_Wiper Accessories", "CA172-SC231-FL23116_(PS) Wipers - Bosch Insight (Hybrid)", "CA172-SC231-FL23117_(PS) Wipers - Bosch Clear Advantage (Beam)" }) + { + IEnumerable products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 }, { "product_type", productType } }); + + System.Diagnostics.Debug.WriteLine($"{productType}: Count: {products.Count()}"); + + while (products != null && products.Any()) + { + foreach (Product product in products) + { + try + { + string partNumber = Regex.Replace(product.Title.Split(' ')[0], "[^a-zA-Z0-9]", string.Empty); + + IList wipers = await _fitmentContext.Wipers + .Where(w => w.PartNumber == partNumber) + .OrderBy(w => w.Position) + .ToListAsync(); + + string currentPosition = wipers[0].Position; + List vehicleIds = new List(); + + foreach (Wiper wiper in wipers) + { + if (wiper.Position != currentPosition) + { + await SavePositionMetafield(product, vehicleIds, currentPosition); + + currentPosition = wiper.Position; + vehicleIds = new List(); + } + + IList fitmentVehicleIds = _fitmentContext.Vehicles + .Where(v => v.BaseVehicleId == wiper.BaseVehicleId) + .Select(v => v.VehicleToEngineConfigId) + .Distinct() + .ToList(); + + vehicleIds.AddRange(fitmentVehicleIds); + } + + await SavePositionMetafield(product, vehicleIds, currentPosition); + } + + catch (Exception ex) + { + Console.WriteLine($"Could not update {product.Id}: {ex.Message}"); + } + } + + products = await _shopifyClient.Products.GetNext(); + } + } + } + + //[SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "It's a Shopify metafield key")] + private async Task SavePositionMetafield(Product product, IList vehicleIds, string position) + { + if (vehicleIds.Count == 0) + { + return; + } + + string json = JsonConvert.SerializeObject(vehicleIds); + if (json.Length >= 100000) + { + // TODO: Logging + return; + } + + string key = position.ToLowerInvariant().Replace(" ", "_"); + if (key.Length > 20) + { + key = key.Substring(0, 20); + } + + Metafield vehicleMetafield = new Metafield + { + Namespace = "position", + Key = key, + Value = json, + ValueType = "json_string", + OwnerResource = "product", + OwnerId = product.Id + }; + + System.Diagnostics.Debug.WriteLine(json); + + await _shopifyClient.Metafields.Add(vehicleMetafield); + } + } +} \ No newline at end of file diff --git a/PartSource.Automation/Jobs/ProcessWhiFitment.cs b/PartSource.Automation/Jobs/ProcessWhiFitment.cs index c64b226..1203c35 100644 --- a/PartSource.Automation/Jobs/ProcessWhiFitment.cs +++ b/PartSource.Automation/Jobs/ProcessWhiFitment.cs @@ -18,163 +18,163 @@ using System.Threading.Tasks; namespace PartSource.Automation.Jobs { - public class ProcessWhiFitment : IAutomationJob - { - private readonly ILogger _logger; - private readonly WhiSeoService _whiSeoService; - private readonly FtpConfiguration _ftpConfiguration; - private readonly SeoDataType _seoDataType; + public class ProcessWhiFitment : IAutomationJob + { + private readonly ILogger _logger; + private readonly WhiSeoService _whiSeoService; + private readonly FtpConfiguration _ftpConfiguration; + private readonly SeoDataType _seoDataType; - private readonly IDictionary _noteDictionary; + private readonly IDictionary _noteDictionary; - public ProcessWhiFitment(IConfiguration configuration, ILogger logger, WhiSeoService whiSeoService) - { - _logger = logger; - _whiSeoService = whiSeoService; + public ProcessWhiFitment(IConfiguration configuration, ILogger logger, WhiSeoService whiSeoService) + { + _logger = logger; + _whiSeoService = whiSeoService; - _seoDataType = SeoDataType.Fitment; + _seoDataType = SeoDataType.Fitment; - _ftpConfiguration = configuration.GetSection("ftpServers:WhiConfiguration").Get(); + _ftpConfiguration = configuration.GetSection("ftpServers:WhiConfiguration").Get(); - _noteDictionary = new ConcurrentDictionary(); - } + _noteDictionary = new ConcurrentDictionary(); + } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2008:Do not create tasks without passing a TaskScheduler", Justification = "")] - public async Task Run() - { - _whiSeoService.TruncateFitmentTables(); - // _whiSeoService.GetFiles(_seoDataType); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2008:Do not create tasks without passing a TaskScheduler", Justification = "")] + public async Task Run() + { + _whiSeoService.TruncateFitmentTables(); + // _whiSeoService.GetFiles(_seoDataType); - string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant()); - DirectoryInfo directoryInfo = new DirectoryInfo(directory); + string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant()); + DirectoryInfo directoryInfo = new DirectoryInfo(directory); - ConcurrentQueue> fileGroups = new ConcurrentQueue>(); + ConcurrentQueue> fileGroups = new ConcurrentQueue>(); - foreach (IGrouping fileGroup in directoryInfo.GetFiles().Where(f => f.Name.EndsWith("csv.gz")).GroupBy(x => x.Name.Split('_').Last())) - { - fileGroups.Enqueue(fileGroup); - } + foreach (IGrouping fileGroup in directoryInfo.GetFiles().Where(f => f.Name.EndsWith("csv.gz")).GroupBy(x => x.Name.Split('_').Last())) + { + fileGroups.Enqueue(fileGroup); + } - Task[] taskArray = new Task[8]; + Task[] taskArray = new Task[8]; - for (int i = 0; i < taskArray.Length; i++) - { - taskArray[i] = Task.Factory.StartNew(() => - { - while (fileGroups.TryDequeue(out IGrouping fileGroup)) - { - foreach (FileInfo fileInfo in fileGroup) - { - try - { - string filename = Decompress(fileInfo); - string tableName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf('.')); + for (int i = 0; i < taskArray.Length; i++) + { + taskArray[i] = Task.Factory.StartNew(() => + { + while (fileGroups.TryDequeue(out IGrouping fileGroup)) + { + foreach (FileInfo fileInfo in fileGroup) + { + try + { + string filename = Decompress(fileInfo); + string tableName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf('.')); - DataTable dataTable = GetDataTable(filename); + DataTable dataTable = GetDataTable(filename); - _whiSeoService.BulkCopyFitment(dataTable, tableName); - _logger.LogInformation($"Copied {fileInfo.Name} to the database."); + _whiSeoService.BulkCopyFitment(dataTable, tableName); + _logger.LogInformation($"Copied {fileInfo.Name} to the database."); - File.Delete(filename); - } + File.Delete(filename); + } - catch (Exception ex) - { - _logger.LogError($"Failed to write {fileInfo.Name} to the database - {ex.Message}", ex); - } - } + catch (Exception ex) + { + _logger.LogError($"Failed to write {fileInfo.Name} to the database - {ex.Message}", ex); + } + } - string fitmentTable = fileGroup.Key.Substring(0, fileGroup.Key.IndexOf('.')); - _whiSeoService.CreateFitmentTable(fitmentTable); + string fitmentTable = fileGroup.Key.Substring(0, fileGroup.Key.IndexOf('.')); + _whiSeoService.CreateFitmentTable(fitmentTable); - _logger.LogInformation($"Created fitment table for part group {fitmentTable}."); + _logger.LogInformation($"Created fitment table for part group {fitmentTable}."); - } - }); - } + } + }); + } - Task.WaitAll(taskArray); + Task.WaitAll(taskArray); - _whiSeoService.CreateFitmentView(); + _whiSeoService.CreateFitmentView(); - _whiSeoService.SaveNotes(_noteDictionary); - } + _whiSeoService.SaveNotes(_noteDictionary); + } - public string Decompress(FileInfo fileInfo) - { - string decompressedFile = fileInfo.FullName.Remove(fileInfo.FullName.Length - fileInfo.Extension.Length); + public string Decompress(FileInfo fileInfo) + { + string decompressedFile = fileInfo.FullName.Remove(fileInfo.FullName.Length - fileInfo.Extension.Length); - using FileStream filestream = File.Create(decompressedFile); - using GZipStream decompressionStream = new GZipStream(fileInfo.OpenRead(), CompressionMode.Decompress); + using FileStream filestream = File.Create(decompressedFile); + using GZipStream decompressionStream = new GZipStream(fileInfo.OpenRead(), CompressionMode.Decompress); - decompressionStream.CopyTo(filestream); + decompressionStream.CopyTo(filestream); - return decompressedFile; - } + return decompressedFile; + } - private DataTable GetDataTable(string filename) - { - using DataTable dataTable = new DataTable(); - dataTable.Columns.Add("LineCode", typeof(string)); - dataTable.Columns.Add("PartNumber", typeof(string)); - dataTable.Columns.Add("BaseVehicleId", typeof(int)); - dataTable.Columns.Add("EngineConfigId", typeof(int)); - dataTable.Columns.Add("Position", typeof(string)); - dataTable.Columns.Add("FitmentNoteHash", typeof(string)); + private DataTable GetDataTable(string filename) + { + using DataTable dataTable = new DataTable(); + dataTable.Columns.Add("LineCode", typeof(string)); + dataTable.Columns.Add("PartNumber", typeof(string)); + dataTable.Columns.Add("BaseVehicleId", typeof(int)); + dataTable.Columns.Add("EngineConfigId", typeof(int)); + dataTable.Columns.Add("Position", typeof(string)); + dataTable.Columns.Add("FitmentNoteHash", typeof(string)); - using StreamReader reader = new StreamReader(filename); - string line = reader.ReadLine(); // Burn the header row + using StreamReader reader = new StreamReader(filename); + string line = reader.ReadLine(); // Burn the header row - while (reader.Peek() > 0) - { - line = reader.ReadLine(); + while (reader.Peek() > 0) + { + line = reader.ReadLine(); - string[] columns = line.Split("\",\""); - for (int i = 0; i < columns.Length; i++) - { - columns[i] = columns[i].Replace("\"", string.Empty); - } + string[] columns = line.Split("\",\""); + for (int i = 0; i < columns.Length; i++) + { + columns[i] = columns[i].Replace("\"", string.Empty); + } - string lineCode = Regex.Replace(columns[0], "[^a-zA-Z0-9]", string.Empty).Trim(); - string partNumber = Regex.Replace(columns[1], "[^a-zA-Z0-9]", string.Empty).Trim(); - string position = columns[7].Trim(); + string lineCode = Regex.Replace(columns[0], "[^a-zA-Z0-9]", string.Empty).Trim(); + string partNumber = Regex.Replace(columns[1], "[^a-zA-Z0-9]", string.Empty).Trim(); + string position = columns[7].Trim(); - string noteText = columns[4].Trim(); - string noteTextHash = GetMD5Hash(noteText); + string noteText = columns[4].Trim(); + string noteTextHash = GetMD5Hash(noteText); - if (!_noteDictionary.ContainsKey(noteTextHash)) - { - _noteDictionary.Add(noteTextHash, noteText); - } + if (!_noteDictionary.ContainsKey(noteTextHash)) + { + _noteDictionary.Add(noteTextHash, noteText); + } - if (!string.IsNullOrEmpty(lineCode) - && !string.IsNullOrEmpty(partNumber) - && int.TryParse(columns[5], out int baseVehicleId) - && int.TryParse(columns[6], out int engineConfigId)) - { - dataTable.Rows.Add(new object[] { lineCode, partNumber, baseVehicleId, engineConfigId, position, noteTextHash }); - } - } + if (!string.IsNullOrEmpty(lineCode) + && !string.IsNullOrEmpty(partNumber) + && int.TryParse(columns[5], out int baseVehicleId) + && int.TryParse(columns[6], out int engineConfigId)) + { + dataTable.Rows.Add(new object[] { lineCode, partNumber, baseVehicleId, engineConfigId, position, noteTextHash }); + } + } - return dataTable; - } + return dataTable; + } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms", Justification = "Not used for security")] - private string GetMD5Hash(string input) - { - using MD5 md5 = MD5.Create(); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms", Justification = "Not used for security")] + private string GetMD5Hash(string input) + { + using MD5 md5 = MD5.Create(); - byte[] inputBytes = Encoding.UTF8.GetBytes(input); - byte[] hashBytes = md5.ComputeHash(inputBytes); + byte[] inputBytes = Encoding.UTF8.GetBytes(input); + byte[] hashBytes = md5.ComputeHash(inputBytes); - StringBuilder stringBuilder = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < hashBytes.Length; i++) - { - stringBuilder.Append(hashBytes[i].ToString("X2")); - } + for (int i = 0; i < hashBytes.Length; i++) + { + stringBuilder.Append(hashBytes[i].ToString("X2")); + } - return stringBuilder.ToString(); - } - } + return stringBuilder.ToString(); + } + } } \ No newline at end of file diff --git a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs index 83ff005..dc64557 100644 --- a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs +++ b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs @@ -18,109 +18,117 @@ using System.Threading.Tasks; namespace PartSource.Automation.Jobs { - public class ProcessWhiVehicles : IAutomationJob - { - private readonly ILogger _logger; - private readonly WhiSeoService _whiSeoService; - private readonly FtpConfiguration _ftpConfiguration; - private readonly SeoDataType _seoDataType; + public class ProcessWhiVehicles : IAutomationJob + { + private readonly ILogger _logger; + private readonly WhiSeoService _whiSeoService; + private readonly FtpConfiguration _ftpConfiguration; + private readonly SeoDataType _seoDataType; - public ProcessWhiVehicles(IConfiguration configuration, ILogger logger, WhiSeoService whiSeoService) - { - _logger = logger; - _whiSeoService = whiSeoService; + public ProcessWhiVehicles(IConfiguration configuration, ILogger logger, WhiSeoService whiSeoService) + { + _logger = logger; + _whiSeoService = whiSeoService; - _seoDataType = SeoDataType.Vehicle; + _seoDataType = SeoDataType.Vehicle; - _ftpConfiguration = configuration.GetSection("ftpServers:WhiConfiguration").Get(); + _ftpConfiguration = configuration.GetSection("ftpServers:WhiConfiguration").Get(); - } + } - public async Task Run() - { - _whiSeoService.TruncateVehicleTable(); - _whiSeoService.GetFiles(_seoDataType); + public async Task Run() + { + _whiSeoService.TruncateVehicleTables(); + _whiSeoService.GetFiles(_seoDataType); - string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant()); - DirectoryInfo directoryInfo = new DirectoryInfo(directory); + string directory = Path.Combine(_ftpConfiguration.Destination, _seoDataType.ToString().ToLowerInvariant()); + DirectoryInfo directoryInfo = new DirectoryInfo(directory); - IEnumerable files = directoryInfo.GetFiles().Where(f => f.Name.StartsWith("seo_aces_vehicle_feed")); + IEnumerable files = directoryInfo.GetFiles().Where(f => f.Name.StartsWith("seo_aces_vehicle_feed")); - foreach (FileInfo fileInfo in files) - { - try - { - string tableName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf('.')); + foreach (FileInfo fileInfo in files) + { + try + { + string tableName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf('.')); - DataTable dataTable = GetDataTable(fileInfo.FullName); + DataTable dataTable = GetDataTable(fileInfo.FullName); - _whiSeoService.BulkCopyVehicle(dataTable, tableName); - _logger.LogInformation($"Copied {fileInfo.Name} to the database."); + _whiSeoService.BulkCopyVehicle(dataTable, tableName); + _logger.LogInformation($"Copied {fileInfo.Name} to the database."); - File.Delete(fileInfo.FullName); - } + File.Delete(fileInfo.FullName); + } - catch (Exception ex) - { - _logger.LogError($"Failed to write {fileInfo.Name} to the database - {ex.Message}", ex); - } - } + catch (Exception ex) + { + _logger.LogError($"Failed to write {fileInfo.Name} to the database - {ex.Message}", ex); + } + } - _whiSeoService.CreateVehicleTable(); + _whiSeoService.CreateVehicleTable(); - _logger.LogInformation($"Created vehicle table."); - } + _logger.LogInformation($"Created vehicle table."); + } - private DataTable GetDataTable(string filename) - { - using DataTable dataTable = new DataTable(); - dataTable.Columns.Add("Year", typeof(int)); - dataTable.Columns.Add("MakeId", typeof(int)); - dataTable.Columns.Add("MakeName", typeof(string)); - dataTable.Columns.Add("ModelId", typeof(int)); - dataTable.Columns.Add("ModelName", typeof(string)); - dataTable.Columns.Add("EngineConfigId", typeof(int)); - dataTable.Columns.Add("EngineDescription", typeof(string)); - dataTable.Columns.Add("BaseVehicleId", typeof(int)); - dataTable.Columns.Add("VehicleToEngineConfigId", typeof(int)); - dataTable.Columns.Add("SubmodelId", typeof(int)); - dataTable.Columns.Add("SubmodelName", typeof(string)); + private DataTable GetDataTable(string filename) + { + using DataTable dataTable = new DataTable(); + dataTable.Columns.Add("Year", typeof(int)); + dataTable.Columns.Add("MakeId", typeof(int)); + dataTable.Columns.Add("MakeName", typeof(string)); + dataTable.Columns.Add("ModelId", typeof(int)); + dataTable.Columns.Add("ModelName", typeof(string)); + dataTable.Columns.Add("RegionId", typeof(int)); + dataTable.Columns.Add("RegionName", typeof(string)); + dataTable.Columns.Add("VehicleTypeId", typeof(int)); + dataTable.Columns.Add("EngineConfigId", typeof(int)); + dataTable.Columns.Add("EngineDescription", typeof(string)); + dataTable.Columns.Add("BaseVehicleId", typeof(int)); + dataTable.Columns.Add("VehicleToEngineConfigId", typeof(int)); + dataTable.Columns.Add("SubmodelId", typeof(int)); + dataTable.Columns.Add("SubmodelName", typeof(string)); - using StreamReader reader = new StreamReader(filename); - string line = reader.ReadLine(); // Burn the header row + using StreamReader reader = new StreamReader(filename); + string line = reader.ReadLine(); // Burn the header row - while (reader.Peek() > 0) - { - line = reader.ReadLine(); + while (reader.Peek() > 0) + { + line = reader.ReadLine(); - string[] columns = line.Split("\",\""); - for (int i = 0; i < columns.Length; i++) - { - columns[i] = columns[i].Replace("\"", string.Empty); - } + string[] columns = line.Split("\",\""); + for (int i = 0; i < columns.Length; i++) + { + columns[i] = columns[i].Replace("\"", string.Empty); + } - string makeName = columns[4].Trim(); - string modelName = columns[6].Trim(); - string submodelName = columns[34].Trim(); - string engineDescription = columns[51].Trim(); + string makeName = columns[4].Trim(); + string modelName = columns[6].Trim(); + string regionName = columns[8].Trim(); + string submodelName = columns[34].Trim(); + string engineDescription = columns[51].Trim(); - if (!string.IsNullOrEmpty(makeName) - && !string.IsNullOrEmpty(modelName) - && !string.IsNullOrEmpty(submodelName) - && !string.IsNullOrEmpty(engineDescription) - && int.TryParse(columns[0], out int baseVehicleId) - && int.TryParse(columns[2], out int year) - && int.TryParse(columns[3], out int makeId) - && int.TryParse(columns[5], out int modelId) - && int.TryParse(columns[33], out int submodelId) - && int.TryParse(columns[35], out int engineConfigId) - && int.TryParse(columns[36], out int vehicleToEngineConfigId)) - { - dataTable.Rows.Add(new object[] { year, makeId, makeName, modelId, modelName, engineConfigId, engineDescription, baseVehicleId, vehicleToEngineConfigId, submodelId, submodelName }); - } - } + if (!string.IsNullOrEmpty(makeName) + && !string.IsNullOrEmpty(modelName) + && !string.IsNullOrEmpty(regionName) + && !string.IsNullOrEmpty(submodelName) + && !string.IsNullOrEmpty(engineDescription) + && int.TryParse(columns[0], out int baseVehicleId) + && int.TryParse(columns[2], out int year) + && int.TryParse(columns[3], out int makeId) + && int.TryParse(columns[5], out int modelId) + && int.TryParse(columns[7], out int regionId) + && int.TryParse(columns[9], out int vehicleTypeId) + && int.TryParse(columns[33], out int submodelId) + && int.TryParse(columns[35], out int engineConfigId) + && int.TryParse(columns[36], out int vehicleToEngineConfigId) + && 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 }); + } + } - return dataTable; - } - } + return dataTable; + } + } } \ No newline at end of file diff --git a/PartSource.Automation/Jobs/SyncronizeProducts.cs b/PartSource.Automation/Jobs/SyncronizeProducts.cs deleted file mode 100644 index 10263d5..0000000 --- a/PartSource.Automation/Jobs/SyncronizeProducts.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using PartSource.Data.Contexts; -using PartSource.Data.Models; -using Ratermania.Automation.Interfaces; -using Ratermania.Shopify; -using Ratermania.Shopify.Resources; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace PartSource.Automation.Jobs -{ - /// - /// Ensures syncronization between Shopify IDs and Partsource SKUs - /// - public class SyncronizeProducts : IAutomationJob - { - private readonly PartSourceContext _partSourceContext; - private readonly ShopifyClient _shopifyClient; - private readonly ILogger _logger; - - public SyncronizeProducts(ILogger logger, PartSourceContext partSourceContext, ShopifyClient shopifyClient) - { - _partSourceContext = partSourceContext; - _shopifyClient = shopifyClient; - _logger = logger; - } - - - public async Task Run() - { - IList importData = _partSourceContext.ImportData.FromSql($"SELECT * FROM ImportDataFilters").ToList(); - - IEnumerable products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 } }); - - while (products?.Any() == true) - { - - foreach (Product product in products) - { - foreach (Variant variant in product.Variants) - { - ImportData item = importData.FirstOrDefault(i => i.VariantSku == variant.Sku); - - if (item != null) - { - _partSourceContext.Database.ExecuteSqlCommand($"UPDATE ImportDataFilters SET ShopifyId = {product.Id} WHERE VariantSku = {variant.Sku}"); - } - } - } - - try - { - _logger.LogInformation("Did 250"); - //await _partSourceContext.SaveChangesAsync(); - } - - catch - { - Console.WriteLine("Failed to save a batch of products"); - } - - finally - { - products = await _shopifyClient.Products.GetNext(); - } - } - } - } -} \ No newline at end of file diff --git a/PartSource.Automation/Jobs/UpdateFitment.cs b/PartSource.Automation/Jobs/UpdateFitment.cs index d6c39ee..11ba060 100644 --- a/PartSource.Automation/Jobs/UpdateFitment.cs +++ b/PartSource.Automation/Jobs/UpdateFitment.cs @@ -20,257 +20,227 @@ using System.Threading.Tasks; 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 VehicleService _vehicleService; + public class UpdateFitment : IAutomationJob + { + private readonly ILogger _logger; + private readonly ShopifyClient _shopifyClient; + private readonly PartSourceContext _partSourceContext; + private readonly FitmentContext _fitmentContext; + private readonly VehicleService _vehicleService; - public UpdateFitment(ILogger logger, PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleService vehicleService) - { - _logger = logger; - _partSourceContext = partSourceContext; - _fitmentContext = fitmentContext; - _shopifyClient = shopifyClient; - _vehicleService = vehicleService; - } + public UpdateFitment(ILogger logger, PartSourceContext partSourceContext, FitmentContext fitmentContext, ShopifyClient shopifyClient, VehicleService vehicleService) + { + _logger = logger; + _partSourceContext = partSourceContext; + _fitmentContext = fitmentContext; + _shopifyClient = shopifyClient; + _vehicleService = vehicleService; + } - public async Task Run() - { - IList productTypes = new List - { - "CA108-SC349-FL34907_CV Shafts, New" - }; + public async Task Run() + { + IEnumerable products = null; - foreach (string type in productTypes) - { - IEnumerable products = null; + try + { + products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 } }); + } - try - { - products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 }, { "product_type", "CA108-SC349-FL34907_CV Shafts, New" } }); - } + catch (Exception ex) + { + _logger.LogError("Failed to get products from Shopify", ex); + throw; + } - catch (Exception ex) - { - _logger.LogError("Failed to get products from Shopify", ex); - throw; - } + int i = 1; - int i = 1; + while (products != null && products.Any()) + { + foreach (Product product in products) + { + // Wiper blades are a separate fitment process. + if (product.ProductType.Contains("CA172-SC231")) + { + continue; + } - while (products != null && products.Any()) - { - foreach (Product product in products) - { - ImportData importData = null; + ImportData importData = null; - try - { - IEnumerable metafields = await _shopifyClient.Metafields.Get(new Dictionary { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } }); + try + { + IEnumerable metafields = await _shopifyClient.Metafields.Get(new Dictionary { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } }); - //importData = await _partSourceContext.ImportData.FirstOrDefaultAsync(parts => parts.ShopifyId == product.Id); + //importData = await _partSourceContext.ImportData.FirstOrDefaultAsync(parts => parts.ShopifyId == product.Id); - //if (importData == null) - //{ - // continue; - 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 - }; - // } + //if (importData == null) + //{ + // continue; + 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 + }; + // } - //importData.PartNumber = product.Title.Split(' ')[0]; + bool isFitment = false; + string bodyHtml = product.BodyHtml[..(product.BodyHtml.IndexOf("") + "".Length)]; - bool isFitment = false; - string bodyHtml = product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("") + "".Length); + IList vehicles = _vehicleService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); - IList vehicles = _vehicleService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); + IList vehicleIdFitment = _vehicleService.GetVehicleIdFitment(vehicles); - //if (vehicles.Count > 250) - //{ - // vehicles = vehicles.Take(250) - // .ToList(); + if (vehicleIdFitment.Any()) + { + string vehicleIdString = string.Join('-', vehicleIdFitment.Select(j => $"v{j}")); - // _logger.LogInformation($"SKU {importData.VariantSku} fits more than 250 vehicles. Only the first 250 will be used."); - //} + bodyHtml += $"
{vehicleIdString}
"; - IList vehicleIdFitment = _vehicleService.GetVehicleIdFitment(vehicles); + isFitment = true; - if (vehicleIdFitment.Count > 0) - { - string vehicleIdString = string.Join('-', vehicleIdFitment.Select(j => $"v{j}")); + string json = JsonConvert.SerializeObject(vehicleIdFitment); + Metafield vehicleMetafield = new Metafield + { + Namespace = "fitment", + Key = "ids", + Value = json, + ValueType = "json_string", + OwnerResource = "product", + OwnerId = product.Id + }; - bodyHtml += $"
{vehicleIdString}
"; + await _shopifyClient.Metafields.Add(vehicleMetafield); + } - isFitment = true; + IList ymmFitment = _vehicleService.GetYmmFitment(vehicles); + if (ymmFitment.Count > 0) + { + isFitment = true; - string json = JsonConvert.SerializeObject(vehicleIdFitment); - if (json.Length < 100000) - { - Metafield vehicleMetafield = new Metafield - { - Namespace = "fitment", - Key = "ids", - Value = json, - ValueType = "json_string", - OwnerResource = "product", - OwnerId = product.Id - }; + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(""); - await _shopifyClient.Metafields.Add(vehicleMetafield); - } + foreach (string fitment in ymmFitment) + { + try + { + string[] parts = fitment.Split(' ', 2); - else - { - _logger.LogWarning($"Vehicle ID fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); - continue; - } - } + stringBuilder.AppendLine($""); + } - IList ymmFitment = _vehicleService.GetYmmFitment(vehicles); - if (ymmFitment.Count > 0) - { - isFitment = true; + catch + { + // This is still a POC at this point. Oh well... + } + } - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("
This Part Fits
{parts[1]}{parts[0].Replace("-", ", ")}
"); + stringBuilder.AppendLine("
This Part Fits
"); - foreach (string fitment in ymmFitment) - { - try - { - string[] parts = fitment.Split(' ', 2); + bodyHtml += $"
{stringBuilder.ToString()}
"; - stringBuilder.AppendLine($"{parts[1]}{parts[0].Replace("-", ", ")}"); - } + string json = JsonConvert.SerializeObject(ymmFitment); + Metafield ymmMetafield = new Metafield + { + Namespace = "fitment", + Key = "seo", + Value = json, + ValueType = "json_string", + OwnerResource = "product", + OwnerId = product.Id + }; - catch - { - // This is still a POC at this point. Oh well... - } - } + await _shopifyClient.Metafields.Add(ymmMetafield); + } - stringBuilder.AppendLine(""); + Metafield isFitmentMetafield = new Metafield + { + Namespace = "Flags", + Key = "IsFitment", + Value = isFitment.ToString(), + ValueType = "string", + OwnerResource = "product", + OwnerId = product.Id + }; - bodyHtml += $"
{stringBuilder.ToString()}
"; + await _shopifyClient.Metafields.Add(isFitmentMetafield); - string json = JsonConvert.SerializeObject(ymmFitment); - if (json.Length < 100000) - { - Metafield ymmMetafield = new Metafield - { - Namespace = "fitment", - Key = "seo", - Value = json, - ValueType = "json_string", - OwnerResource = "product", - OwnerId = product.Id - }; + Metafield lineCodeMetafield = new Metafield + { + Namespace = "google", + Key = "custom_label_0", + Value = importData.LineCode, + ValueType = "string", + OwnerResource = "product", + OwnerId = product.Id + }; - await _shopifyClient.Metafields.Add(ymmMetafield); - } + // await _shopifyClient.Metafields.Add(lineCodeMetafield); - else - { - _logger.LogWarning($"Year/make/model fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); - continue; - } - } + Metafield partNumberMetafield = new Metafield + { + Namespace = "google", + Key = "custom_label_1", + Value = importData.PartNumber, + ValueType = "string", + OwnerResource = "product", + OwnerId = product.Id + }; - Metafield isFitmentMetafield = new Metafield - { - Namespace = "Flags", - Key = "IsFitment", - Value = isFitment.ToString(), - ValueType = "string", - OwnerResource = "product", - OwnerId = product.Id - }; + //await _shopifyClient.Metafields.Add(partNumberMetafield); - await _shopifyClient.Metafields.Add(isFitmentMetafield); + List tags = new List(); - Metafield lineCodeMetafield = new Metafield - { - Namespace = "google", - Key = "custom_label_0", - Value = importData.LineCode, - ValueType = "string", - OwnerResource = "product", - OwnerId = product.Id - }; + for (int j = 0; j < vehicleIdFitment.Count; j += 25) + { + tags.Add(string.Join('-', vehicleIdFitment.Skip(j).Take(25).Select(j => $"v{j}"))); + } - //await _shopifyClient.Metafields.Add(lineCodeMetafield); + tags.AddRange(ymmFitment); - Metafield partNumberMetafield = new Metafield - { - Namespace = "google", - Key = "custom_label_1", - Value = importData.PartNumber, - ValueType = "string", - OwnerResource = "product", - OwnerId = product.Id - }; + if (tags.Count > 249) + { + tags = tags.Take(249).ToList(); + } - // await _shopifyClient.Metafields.Add(partNumberMetafield); + string zzzIsFitment = isFitment + ? "zzzIsFitment=true" + : "zzzIsFitment=false"; - List tags = new List(); + tags.Add(zzzIsFitment); - for (int j = 0; j < vehicleIdFitment.Count; j += 25) - { - tags.Add(string.Join('-', vehicleIdFitment.Skip(j).Take(25).Select(j => $"v{j}"))); - } + product.Tags = string.Join(',', tags); + product.BodyHtml = bodyHtml; - tags.AddRange(ymmFitment); + await _shopifyClient.Products.Update(product); - if (tags.Count > 249) - { - tags = tags.Take(249).ToList(); - } + importData.IsFitment = isFitment; + importData.UpdatedAt = DateTime.Now; + importData.UpdateType = "Fitment"; + } - string zzzIsFitment = isFitment - ? "zzzIsFitment=true" - : "zzzIsFitment=false"; + catch (Exception ex) + { + _logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex); + } + } - tags.Add(zzzIsFitment); + try + { + Console.WriteLine(i); - //product.Tags = string.Join(',', tags); - product.BodyHtml = bodyHtml; - await _shopifyClient.Products.Update(product); + _partSourceContext.SaveChanges(); + products = await _shopifyClient.Products.GetNext(); - importData.IsFitment = isFitment; - importData.UpdatedAt = DateTime.Now; - importData.UpdateType = "Fitment"; - } + i++; + } - 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(); - } - } - } - ; - } -} + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to get the next set of products. Retrying"); + products = await _shopifyClient.Products.GetPrevious(); + } + } + } + } } diff --git a/PartSource.Automation/Jobs/UpdatePositioning.cs b/PartSource.Automation/Jobs/UpdatePositioning.cs index 505eddf..59b271b 100644 --- a/PartSource.Automation/Jobs/UpdatePositioning.cs +++ b/PartSource.Automation/Jobs/UpdatePositioning.cs @@ -41,6 +41,8 @@ namespace PartSource.Automation.Jobs IEnumerable products = await _shopifyClient.Products.Get(parameters); + int i = 1; + while (products != null && products.Any()) { foreach (Product product in products) @@ -94,45 +96,51 @@ namespace PartSource.Automation.Jobs await SavePositionMetafield(product, vehicleIds, currentPosition); - //IList notes = fitments.Select(f => f.NoteText) + IList notes = fitments.Select(f => f.FitmentNoteHash) + .Distinct() + .ToList(); - // .Distinct() - // .ToList(); + IList vehicleNotes = new List(); - //IList vehicleNotes = new List(); + foreach (string noteHash in notes) + { + FitmentNote fitmentNote = await _fitmentContext.FitmentNotes.FirstOrDefaultAsync(f => f.Hash == noteHash); - //foreach (string noteText in notes) - //{ - // 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(); + if (fitmentNote == null) + { + continue; + } - // vehicleNotes.Add(new { noteText, vehicleIds }); - //} + vehicleIds = fitments.Where(f => f.FitmentNoteHash == noteHash) + .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(); - //string json = JsonConvert.SerializeObject(vehicleNotes); - //if (json.Length >= 100000) - //{ - // continue; - //} + vehicleNotes.Add(new { fitmentNote.NoteText, vehicleIds }); + } - //Metafield vehicleMetafield = new Metafield - //{ - // Namespace = "fitment", - // Key = "note_text", - // Value = json, - // ValueType = "json_string", - // OwnerResource = "product", - // OwnerId = product.Id - //}; + string json = JsonConvert.SerializeObject(vehicleNotes); + if (json.Length >= 100000) + { + continue; + } - //await _shopifyClient.Metafields.Add(vehicleMetafield); + Metafield vehicleMetafield = new Metafield + { + Namespace = "fitment", + Key = "note_text", + Value = json, + ValueType = "json_string", + OwnerResource = "product", + OwnerId = product.Id + }; - //importData.UpdatedAt = DateTime.Now; - //importData.UpdateType = "Positioning"; - } + await _shopifyClient.Metafields.Add(vehicleMetafield); + + //importData.UpdatedAt = DateTime.Now; + //importData.UpdateType = "Positioning"; + } catch (Exception ex) { @@ -141,7 +149,8 @@ namespace PartSource.Automation.Jobs } try - { + { + Console.WriteLine(i); products = await _shopifyClient.Products.GetNext(); } @@ -200,7 +209,7 @@ namespace PartSource.Automation.Jobs OwnerId = product.Id }; - System.Diagnostics.Debug.WriteLine(json); + //System.Diagnostics.Debug.WriteLine(json); await _shopifyClient.Metafields.Add(vehicleMetafield); } diff --git a/PartSource.Automation/Jobs/UpdatePricing.cs b/PartSource.Automation/Jobs/UpdatePricing.cs index 442d78a..adbd547 100644 --- a/PartSource.Automation/Jobs/UpdatePricing.cs +++ b/PartSource.Automation/Jobs/UpdatePricing.cs @@ -85,7 +85,7 @@ namespace PartSource.Automation.Jobs product.Variants[i].Price = partPrice.Your_Price.Value; product.Variants[i].CompareAtPrice = partPrice.Compare_Price.Value; - product.PublishedAt = partPrice.Active.Trim().ToUpperInvariant() == "Y" ? (DateTime?)DateTime.Now : null; + product.PublishedAt = partPrice.Active.Trim().ToUpperInvariant() == "Y" ? DateTime.Now : null; product.PublishedScope = PublishedScope.Global; //Metafield metafield = new Metafield diff --git a/PartSource.Automation/PartSource.Automation.csproj b/PartSource.Automation/PartSource.Automation.csproj index bac6b3f..2fd53a8 100644 --- a/PartSource.Automation/PartSource.Automation.csproj +++ b/PartSource.Automation/PartSource.Automation.csproj @@ -7,17 +7,17 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + diff --git a/PartSource.Automation/Program.cs b/PartSource.Automation/Program.cs index 10afc63..6925c45 100644 --- a/PartSource.Automation/Program.cs +++ b/PartSource.Automation/Program.cs @@ -73,24 +73,30 @@ namespace PartSource.Automation //options.ApiKey = "9a533dad460321c6ce8f30bf5b8691ed"; //options.ApiSecret = "dc9e28365d9858e544d57ac7af43fee7"; - //options.ApiVersion = "2020-01"; + //options.ApiVersion = "2021-01"; //options.ShopDomain = "dev-partsource.myshopify.com"; }) .AddAutomation(options => { + //options.HasBaseInterval(new TimeSpan(0, 15, 0)) + // .HasMaxFailures(3) + // .HasJob(options => + // options.HasInterval(new TimeSpan(24, 0, 0)) + // .StartsAt(DateTime.Today.AddHours(26)) + // ) + // .HasJob(options => + // options.HasInterval(new TimeSpan(24, 0, 0)) + // .StartsAt(DateTime.Today.AddHours(27)) + // .HasDependency() + // ); + options.HasBaseInterval(new TimeSpan(0, 15, 0)) .HasMaxFailures(3) - .HasJob(options => + .HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) - .StartsAt(DateTime.Today.AddHours(26)) - ) - .HasJob(options => - options.HasInterval(new TimeSpan(24, 0, 0)) - .StartsAt(DateTime.Today.AddHours(27)) - .HasDependency() ); - //.AddApiServer(); + //.AddApiServer(); }) .AddSingleton(builder.Configuration.GetSection("FtpServers:AzureConfiguration").Get()) @@ -101,7 +107,6 @@ namespace PartSource.Automation .AddSingleton() .AddSingleton() - .AddAutoMapper(typeof(PartSourceProfile)); }) .ConfigureLogging((builder, logging) => diff --git a/PartSource.Automation/Services/WhiSeoService.cs b/PartSource.Automation/Services/WhiSeoService.cs index e49152b..42b5c1a 100644 --- a/PartSource.Automation/Services/WhiSeoService.cs +++ b/PartSource.Automation/Services/WhiSeoService.cs @@ -1,18 +1,17 @@ #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.Logging; using PartSource.Automation.Models.Configuration; 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 { - public class WhiSeoService + public class WhiSeoService { private readonly FtpService _ftpService; private readonly string _connectionString; @@ -53,12 +52,12 @@ namespace PartSource.Automation.Services } } - public void TruncateVehicleTable() + public void TruncateVehicleTables() { using SqlConnection connection = new SqlConnection(_connectionString); connection.Open(); - using SqlCommand command = new SqlCommand($"truncate table dbo.Vehicle", connection); + using SqlCommand command = new SqlCommand($"exec DropVehicleTables", connection); command.ExecuteNonQuery(); } @@ -151,16 +150,10 @@ namespace PartSource.Automation.Services using SqlCommand command = new SqlCommand($"exec CreateFitmentView", connection); command.CommandTimeout = 1800; command.ExecuteNonQuery(); - - using SqlCommand command2 = new SqlCommand($"exec CreateFitmentIndexes", connection); - command.CommandTimeout = 1800; - command2.ExecuteNonQuery(); } public void CreateVehicleTable() { - return; - using SqlConnection connection = new SqlConnection(_connectionString); connection.Open(); diff --git a/PartSource.Data/Contexts/FitmentContext.cs b/PartSource.Data/Contexts/FitmentContext.cs index b9b2927..ef1c9a4 100644 --- a/PartSource.Data/Contexts/FitmentContext.cs +++ b/PartSource.Data/Contexts/FitmentContext.cs @@ -15,18 +15,23 @@ namespace PartSource.Data.Contexts public DbSet Fitments { get; set; } + public DbSet FitmentNotes { get; set; } + public DbSet Vehicles { get; set; } + public DbSet Wipers { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity().HasKey(d => new { d.LineCode, d.WhiCode }); modelBuilder.Entity().HasKey(f => new { f.BaseVehicleId, f.EngineConfigId, f.LineCode, f.PartNumber }); + modelBuilder.Entity().HasKey(f => new { f.BaseVehicleId, f.PartNumber, f.LineCode, f.Position}); foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { - entityType.Relational().TableName = entityType.ClrType.Name; + entityType.SetTableName(entityType.ClrType.Name); } } } diff --git a/PartSource.Data/Contexts/PartSourceContext.cs b/PartSource.Data/Contexts/PartSourceContext.cs index 1f84836..6fc0e76 100644 --- a/PartSource.Data/Contexts/PartSourceContext.cs +++ b/PartSource.Data/Contexts/PartSourceContext.cs @@ -14,9 +14,7 @@ namespace PartSource.Data.Contexts public DbSet ApiClients { get; set; } - public DbSet ProductBackups { get; set; } - - public DbSet Manufacturers { get; set; } + public DbSet DcfMappings { get; set; } public DbSet ImportData { get; set; } @@ -36,27 +34,22 @@ namespace PartSource.Data.Contexts public DbSet PartAvailabilities { get; set; } - public DbQuery BaseVehicles { get; set; } + public DbSet BaseVehicles { get; set; } - public DbQuery Engines { get; set; } + public DbSet Engines { get; set; } - public DbQuery Submodels { get; set; } + public DbSet Submodels { get; set; } - public DbQuery VehicleMakes { get; set; } + public DbSet VehicleMakes { get; set; } - public DbQuery VehicleModels { get; set; } + public DbSet VehicleModels { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Query().ToView(nameof(BaseVehicle)); - modelBuilder.Query().ToView(nameof(Engine)); - modelBuilder.Query().ToView(nameof(Submodel)); - modelBuilder.Query().ToView(nameof(VehicleMake)); - modelBuilder.Query().ToView(nameof(VehicleModel)); - modelBuilder.Entity().HasKey(p => new { p.Store, p.SKU }); + modelBuilder.Entity().HasKey(d => new { d.LineCode, d.WhiCode }); modelBuilder.Entity() .Property(s => s.ResourceType) @@ -68,7 +61,7 @@ namespace PartSource.Data.Contexts foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { - entityType.Relational().TableName = entityType.ClrType.Name; + entityType.SetTableName(entityType.ClrType.Name); } } diff --git a/PartSource.Data/Models/BaseVehicle.cs b/PartSource.Data/Models/BaseVehicle.cs index dd64b74..c094dfb 100644 --- a/PartSource.Data/Models/BaseVehicle.cs +++ b/PartSource.Data/Models/BaseVehicle.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text; @@ -7,6 +8,7 @@ namespace PartSource.Data.Models { public class BaseVehicle { + [Key] public int BaseVehicleId { get; set; } public string Make { get; set; } diff --git a/PartSource.Data/Models/Engine.cs b/PartSource.Data/Models/Engine.cs index 16fdb40..07e68cf 100644 --- a/PartSource.Data/Models/Engine.cs +++ b/PartSource.Data/Models/Engine.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Security.Cryptography; using System.Text; namespace PartSource.Data.Models { + public class Engine { + [Key] public int EngineConfigId { get; set; } public string Description { get; set; } diff --git a/PartSource.Data/Models/FitmentNote.cs b/PartSource.Data/Models/FitmentNote.cs new file mode 100644 index 0000000..ca584d9 --- /dev/null +++ b/PartSource.Data/Models/FitmentNote.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PartSource.Data.Models +{ + public class FitmentNote + { + public string NoteText { get; set; } + + [Key] + public string Hash { get; set; } + } +} diff --git a/PartSource.Data/Models/Part.cs b/PartSource.Data/Models/Part.cs index b674b60..478d5a3 100644 --- a/PartSource.Data/Models/Part.cs +++ b/PartSource.Data/Models/Part.cs @@ -1,27 +1,14 @@ -// Decompiled with JetBrains decompiler -// Type: PartSource.Data.Models.Part -// 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.ComponentModel.DataAnnotations; namespace PartSource.Data.Models { - public class Part - { - public int Id { get; set; } + public class Part + { + [Key] + public string Sku { get; set; } + + public string LineCode { get; set; } - public int ManufacturerId { get; set; } - - public long? ShopifyId { get; set; } - - public int Sku { get; set; } - - public string PartNumber { get; set; } - - public string Name { get; set; } - - public string Description { get; set; } - - public Manufacturer Manufacturer { get; set; } - } + public string PartNumber { get; set; } + } } diff --git a/PartSource.Data/Models/Submodel.cs b/PartSource.Data/Models/Submodel.cs index b606f77..1b03a98 100644 --- a/PartSource.Data/Models/Submodel.cs +++ b/PartSource.Data/Models/Submodel.cs @@ -9,6 +9,7 @@ namespace PartSource.Data.Models { public class Submodel { + [Key] public int SubmodelId { get; set; } public string Name { get; set; } diff --git a/PartSource.Data/Models/VehicleMake.cs b/PartSource.Data/Models/VehicleMake.cs index f03fac4..6514627 100644 --- a/PartSource.Data/Models/VehicleMake.cs +++ b/PartSource.Data/Models/VehicleMake.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text; @@ -8,6 +9,7 @@ namespace PartSource.Data.Models public class VehicleMake { [DatabaseGenerated(DatabaseGeneratedOption.None)] + [Key] public int MakeId { get; set; } public string Name { get; set; } diff --git a/PartSource.Data/Models/VehicleModel.cs b/PartSource.Data/Models/VehicleModel.cs index d5f7120..f9ce464 100644 --- a/PartSource.Data/Models/VehicleModel.cs +++ b/PartSource.Data/Models/VehicleModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text; @@ -9,6 +10,7 @@ namespace PartSource.Data.Models public class VehicleModel { [DatabaseGenerated(DatabaseGeneratedOption.None)] + [Key] public int ModelId { get; set; } public int Year { get; set; } diff --git a/PartSource.Data/Models/Wiper.cs b/PartSource.Data/Models/Wiper.cs new file mode 100644 index 0000000..90ea7ee --- /dev/null +++ b/PartSource.Data/Models/Wiper.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PartSource.Data.Models +{ + public class Wiper + { + public int BaseVehicleId { get; set; } + + public string LineCode { get; set; } + + public string PartNumber { get; set; } + + public string Position { get; set; } + + public string PartName { get; set; } + } +} diff --git a/PartSource.Data/Nexpart/App.cs b/PartSource.Data/Nexpart/App.cs new file mode 100644 index 0000000..81308cf --- /dev/null +++ b/PartSource.Data/Nexpart/App.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Newtonsoft.Json; + +namespace PartSource.Data.Nexpart +{ + [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] + public class App + { + [XmlElement] + [JsonProperty("lineCode")] + public string MfrCode { get; set; } + + [XmlElement] + [JsonProperty("description")] + public string MfrLabel { get; set; } + + [XmlElement] + [JsonProperty("position")] + public string Position { get; set; } + + [XmlElement] + [JsonProperty("partNumber")] + public string Part { get; set; } + } +} diff --git a/PartSource.Data/Nexpart/ApplicationSearch.cs b/PartSource.Data/Nexpart/ApplicationSearch.cs new file mode 100644 index 0000000..d343f32 --- /dev/null +++ b/PartSource.Data/Nexpart/ApplicationSearch.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace PartSource.Data.Nexpart +{ + [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectService-v1")] + public class ApplicationSearch + { + public ApplicationSearch() + { + PSRequestHeader = new PSRequestHeader(); + } + + [XmlElement(Order = 1)] + public PSRequestHeader PSRequestHeader { get; set; } + + [XmlElement(Order = 2)] + public VehicleIdentifier VehicleIdentifier { get; set; } + + [XmlElement(Order = 3)] + public string[] MfrCode { get; set; } + + [XmlElement(Order = 4)] + public PartType[] PartType { get; set; } + + [XmlElement(Order = 5)] + public bool SecondaryDCF => true; + + [XmlElement(Order = 6)] + public Criterion[] Criterion { get; set; } + + [XmlElement(Order = 7)] + public string GroupBy { get; set; } + } +} diff --git a/PartSource.Data/Nexpart/BuyersGuideSearchResponse.cs b/PartSource.Data/Nexpart/ApplicationSearchResponse.cs similarity index 53% rename from PartSource.Data/Nexpart/BuyersGuideSearchResponse.cs rename to PartSource.Data/Nexpart/ApplicationSearchResponse.cs index 678077f..b9b4bbf 100644 --- a/PartSource.Data/Nexpart/BuyersGuideSearchResponse.cs +++ b/PartSource.Data/Nexpart/ApplicationSearchResponse.cs @@ -1,18 +1,21 @@ -using PartSource.Data.Nexpart.Interfaces; -using System; +using System; using System.Collections.Generic; +using System.Linq; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; +using PartSource.Data.Nexpart.Interfaces; namespace PartSource.Data.Nexpart { [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectService-v1")] - public class BuyersGuideSearchResponse : IResponseElement + public class ApplicationSearchResponse : IResponseElement { + [XmlElement] public PSResponseHeader PSResponseHeader { get; set; } - [XmlElement(ElementName = "BuyersGuideData")] - public BuyersGuideData ResponseBody { get; set; } + [XmlElement(ElementName = nameof(Apps))] + public Apps ResponseBody { get; set; } } } diff --git a/PartSource.Data/Nexpart/Apps.cs b/PartSource.Data/Nexpart/Apps.cs index 3b2cd67..c10214d 100644 --- a/PartSource.Data/Nexpart/Apps.cs +++ b/PartSource.Data/Nexpart/Apps.cs @@ -1,14 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; +using System.Xml.Serialization; +using Newtonsoft.Json; namespace PartSource.Data.Nexpart { [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] public class Apps { - [XmlElement] - public BuyersGuideMake[] Make { get; set; } + [XmlElement(Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] + [JsonProperty("wipers")] + public App[] App { get; set; } } } diff --git a/PartSource.Data/Nexpart/Body.cs b/PartSource.Data/Nexpart/Body.cs index 270d011..c38881f 100644 --- a/PartSource.Data/Nexpart/Body.cs +++ b/PartSource.Data/Nexpart/Body.cs @@ -11,12 +11,12 @@ namespace PartSource.Data.Nexpart [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectService-v1")] public class Body { + [XmlElement(ElementName = "ApplicationSearch", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(ApplicationSearch))] + [XmlElement(ElementName = "ApplicationSearchResponse", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(ApplicationSearchResponse))] [XmlElement(ElementName = "BaseVehicleDetailLookup", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(BaseVehicleDetailLookup))] [XmlElement(ElementName = "BaseVehicleDetailLookupResponse", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(BaseVehicleDetailLookupResponse))] [XmlElement(ElementName = "BaseVehicleSearch", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(BaseVehicleSearch))] [XmlElement(ElementName = "BaseVehicleSearchResponse", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(BaseVehicleSearchResponse))] - [XmlElement(ElementName = "BuyersGuideSearch", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(BuyersGuideSearch))] - [XmlElement(ElementName = "BuyersGuideSearchResponse", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(BuyersGuideSearchResponse))] [XmlElement(ElementName = "EngineSearch", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(EngineSearch))] [XmlElement(ElementName = "EngineSearchResponse", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(EngineSearchResponse))] [XmlElement(ElementName = "MakeSearch", Namespace = "http://whisolutions.com/PartSelectService-v1", Type = typeof(MakeSearch))] diff --git a/PartSource.Data/Nexpart/BuyersGuideData.cs b/PartSource.Data/Nexpart/BuyersGuideData.cs deleted file mode 100644 index 6dd5127..0000000 --- a/PartSource.Data/Nexpart/BuyersGuideData.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; - -namespace PartSource.Data.Nexpart -{ - [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectService-v1")] - public class BuyersGuideData - { - [XmlElement(Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] - public Apps Apps { get; set; } - } -} diff --git a/PartSource.Data/Nexpart/BuyersGuideEngine.cs b/PartSource.Data/Nexpart/BuyersGuideEngine.cs deleted file mode 100644 index b7330dc..0000000 --- a/PartSource.Data/Nexpart/BuyersGuideEngine.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; - -namespace PartSource.Data.Nexpart -{ - [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] - public class BuyersGuideEngine - { - [XmlAttribute] - public string Desc { get; set; } - - [XmlAttribute] - public int PerVehicle { get; set; } - - [XmlAttribute] - public int Year { get; set; } - } -} diff --git a/PartSource.Data/Nexpart/BuyersGuideMake.cs b/PartSource.Data/Nexpart/BuyersGuideMake.cs deleted file mode 100644 index 75c790f..0000000 --- a/PartSource.Data/Nexpart/BuyersGuideMake.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; - -namespace PartSource.Data.Nexpart -{ - [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] - public class BuyersGuideMake - { - [XmlAttribute] - public string Name { get; set; } - - [XmlAttribute] - public int FromYear { get; set; } - - [XmlAttribute] - public int ToYear { get; set; } - - [XmlAttribute] - public int MakeCount { get; set; } - - [XmlElement] - public BuyersGuideModel[] Model { get; set; } - } -} diff --git a/PartSource.Data/Nexpart/BuyersGuideModel.cs b/PartSource.Data/Nexpart/BuyersGuideModel.cs deleted file mode 100644 index 7c4e55b..0000000 --- a/PartSource.Data/Nexpart/BuyersGuideModel.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; - -namespace PartSource.Data.Nexpart -{ - [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] - public class BuyersGuideModel - { - [XmlAttribute] - public string Name { get; set; } - - [XmlAttribute] - public int FromYear { get; set; } - - [XmlAttribute] - public int ToYear { get; set; } - - [XmlAttribute] - public int ModelCount { get; set; } - - [XmlElement] - public BuyersGuideEngine[] Engine { get; set; } - } -} diff --git a/PartSource.Data/Nexpart/BuyersGuidePart.cs b/PartSource.Data/Nexpart/BuyersGuidePart.cs deleted file mode 100644 index 52065ce..0000000 --- a/PartSource.Data/Nexpart/BuyersGuidePart.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; - -namespace PartSource.Data.Nexpart -{ - [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectServ/2011-07-21")] - public class BuyersGuidePart - { - [XmlAttribute] - public string PartNumber { get; set; } - - [XmlAttribute] - public string MfrCode { get; set; } - } -} diff --git a/PartSource.Data/Nexpart/BuyersGuideSearch.cs b/PartSource.Data/Nexpart/BuyersGuideSearch.cs deleted file mode 100644 index 7f95c77..0000000 --- a/PartSource.Data/Nexpart/BuyersGuideSearch.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; - -namespace PartSource.Data.Nexpart -{ - [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/PartSelectService-v1")] - public class BuyersGuideSearch - { - public BuyersGuideSearch() - { - PSRequestHeader = new PSRequestHeader(); - } - - [XmlElement(Order = 1)] - public PSRequestHeader PSRequestHeader { get; set; } - - [XmlElement(Order = 2)] - public BuyersGuidePart Part { get; set; } - } -} diff --git a/PartSource.Data/Nexpart/PartType.cs b/PartSource.Data/Nexpart/PartType.cs index 03d1ecd..ad179e8 100644 --- a/PartSource.Data/Nexpart/PartType.cs +++ b/PartSource.Data/Nexpart/PartType.cs @@ -13,11 +13,5 @@ namespace PartSource.Data.Nexpart { [XmlAttribute] public int Id { get; set; } - - [XmlAttribute] - public int PositionGroupId { get; set; } - - [XmlAttribute] - public string Description { get; set; } } } diff --git a/PartSource.Data/PartSource.Data.csproj b/PartSource.Data/PartSource.Data.csproj index 668f275..c5f77b4 100644 --- a/PartSource.Data/PartSource.Data.csproj +++ b/PartSource.Data/PartSource.Data.csproj @@ -17,10 +17,13 @@ - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/PartSource.Services/FitmentService.cs b/PartSource.Services/FitmentService.cs new file mode 100644 index 0000000..38a2354 --- /dev/null +++ b/PartSource.Services/FitmentService.cs @@ -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.Services +{ + public class FitmentService + { + private readonly FitmentContext _fitmentContext; + + public FitmentService(FitmentContext fitmentContext) + { + _fitmentContext = fitmentContext; + } + + public IList GetYmmFitment(IList vehicles) + { + if (vehicles.Count == 0) + { + return new string[0]; + } + + IList fitmentTags = new List(); + + IList 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 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 GetYmmFitmentRange(IList vehicles) + { + if (vehicles.Count == 0) + { + return new string[0]; + } + + IList fitmentTags = new List(); + + IList 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 GetVehicleIdFitment(IList vehicles) + { + return vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToList(); + } + + public IList 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 whiCodes = _fitmentContext.DcfMappings + .Where(d => d.LineCode == lineCode) + .Select(d => d.WhiCode); + + IQueryable 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 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 whiCodes = _fitmentContext.DcfMappings + .Where(d => d.LineCode == lineCode) + .Select(d => d.WhiCode); + + IQueryable 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(); + } + } +} diff --git a/PartSource.Services/NexpartService.cs b/PartSource.Services/NexpartService.cs index 1c96908..c0c24d3 100644 --- a/PartSource.Services/NexpartService.cs +++ b/PartSource.Services/NexpartService.cs @@ -19,8 +19,6 @@ namespace PartSource.Services XmlSerializer serializer = new XmlSerializer(typeof(Envelope)); StringBuilder sb = new StringBuilder(); - - using (TextWriter textWriter = new StringWriter(sb)) { serializer.Serialize(textWriter, (object)envelope); @@ -37,7 +35,9 @@ namespace PartSource.Services HttpResponseMessage response = await client.PostAsync("http://acespssprod.nexpart.com:8081/partselect/1.0/services/PartSelectService.PartSelectHttpSoap11Endpoint/", new StringContent(textWriter.ToString(), Encoding.UTF8)); Stream result = await response.Content.ReadAsStreamAsync(); string str = await response.Content.ReadAsStringAsync(); + content = (U)((Envelope)serializer.Deserialize(result)).Body.Content; + ; } catch (Exception ex) { diff --git a/PartSource.Services/PartService.cs b/PartSource.Services/PartService.cs index 5b7c2c9..4162a3d 100644 --- a/PartSource.Services/PartService.cs +++ b/PartSource.Services/PartService.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; namespace PartSource.Services { - [Obsolete] public class PartService { private readonly PartSourceContext _context; @@ -20,31 +19,21 @@ namespace PartSource.Services _context = context; } - public Part GetPart(string partNumber, string lineCode) - { - return _context.Parts.FirstOrDefault(p => p.PartNumber == partNumber && p.Manufacturer.LineCode == lineCode); - } - - public Part GetPart(int sku) - { - return _context.Parts.FirstOrDefault(p => p.Sku == sku); - } - public async Task GetInventory(int sku, int storeNumber) { return await _context.PartAvailabilities.FirstOrDefaultAsync(s => s.Store == storeNumber && s.SKU == sku); } - public IList GetFitments(FitmentSearchDto fitmentSearchDto) + public async Task GetPartBySku(string sku) { - return null; + return await _context.Parts.SingleOrDefaultAsync(p => p.Sku == sku); + } - //return _context.Fitments.Where(f => - // f.ManufacturerCode == fitmentSearchDto.ManufacturerCode && - // f.PartNumber == fitmentSearchDto.PartNumber && - // f.BaseVehicleId == fitmentSearchDto.BaseVehicleId && - // f.EngineConfigId == fitmentSearchDto.EngineConfigId - //).ToList(); + public async Task> GetDcfMapping(string partsourceLineCode) + { + return await _context.DcfMappings + .Where(dcf => dcf.LineCode == partsourceLineCode) + .ToListAsync(); } } } diff --git a/PartSource.Services/PartSource.Services.csproj b/PartSource.Services/PartSource.Services.csproj index d95d776..483842b 100644 --- a/PartSource.Services/PartSource.Services.csproj +++ b/PartSource.Services/PartSource.Services.csproj @@ -12,8 +12,8 @@ - - + + @@ -21,10 +21,4 @@ - - - PreserveNewest - - - diff --git a/PartSource.Services/appsettings.json b/PartSource.Services/appsettings.json deleted file mode 100644 index c48779c..0000000 --- a/PartSource.Services/appsettings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "ConnectionStrings": { - //"PartSourceDatabase": "Server=(localdb)\\mssqllocaldb;Database=PartSource;Trusted_Connection=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=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" - }, - "emailConfiguration": { - - "From": "alerts@ps-shopify.canadaeast.cloudapp.azure.com", - // "To": "tom@soundpress.com,Anas.Bajwa@Partsource.ca", - "To": "tommy@localhost", - "SmtpHost": "localhost" - }, - "ftpConfiguration": { - "Username": "ps-ftp\\$ps-ftp", - "Password": "ycvXptffBxqkBXW4vuRYqn4Zi1soCvnvMMolTe5HNSeAlcl3bAyJYtNhG579", - "Url": "ftp://waws-prod-yq1-007.ftp.azurewebsites.windows.net/site/wwwroot", - "Destination": "C:\\Users\\soundpress\\Desktop" - }, - "ssisConfiguration": { - "Directory": "c:\\users\\soundpress\\desktop" - } -} \ No newline at end of file