From 68c9e01ef1f6e73e43e2eceb5ee19c62cea29b6c Mon Sep 17 00:00:00 2001 From: Tom Raterman Date: Wed, 23 Aug 2023 15:04:54 -0400 Subject: [PATCH] . --- PartSource.Api/Controllers/PartsController.cs | 48 ++-- PartSource.Api/PartSource.Api.csproj | 1 - PartSource.Api/Startup.cs | 9 +- PartSource.Api/appsettings.json | 1 + .../Extensions/FileInfoExtensions.cs | 23 ++ .../Jobs/ExecuteSsisPackages.cs | 2 +- .../Jobs/POC/BulkUpdateInventory.cs | 85 +++++++ .../Jobs/POC/GetImageUrls.cs | 119 +++++++++ .../Jobs/POC/GetImageUrlsTemp.cs | 106 +++++++++ PartSource.Automation/Jobs/POC/ImageList.cs | 84 ------- .../Jobs/POC/PartialInventoryUpdate.cs | 86 ++++--- .../Jobs/ProcessWhiFitment.cs | 5 +- .../Jobs/ProcessWhiVehicles.cs | 5 +- PartSource.Automation/Jobs/UpdateFitment.cs | 63 ++--- .../Jobs/UpdatePositioning.cs | 2 +- PartSource.Automation/Program.cs | 73 +++--- PartSource.Automation/Services/FtpService.cs | 11 +- PartSource.Automation/Services/SsisService.cs | 2 +- .../Services/VehicleFitmentService.cs | 45 +--- PartSource.Automation/appsettings.json | 8 +- PartSource.Data/Contexts/FitmentContext.cs | 3 + PartSource.Data/Dtos/VehicleFitmentDto.cs | 10 +- PartSource.Data/Models/VehicleFitment.cs | 27 +++ PartSource.Services/FitmentService.cs | 225 +++++++++--------- 24 files changed, 655 insertions(+), 388 deletions(-) create mode 100644 PartSource.Automation/Extensions/FileInfoExtensions.cs create mode 100644 PartSource.Automation/Jobs/POC/BulkUpdateInventory.cs create mode 100644 PartSource.Automation/Jobs/POC/GetImageUrls.cs create mode 100644 PartSource.Automation/Jobs/POC/GetImageUrlsTemp.cs delete mode 100644 PartSource.Automation/Jobs/POC/ImageList.cs create mode 100644 PartSource.Data/Models/VehicleFitment.cs diff --git a/PartSource.Api/Controllers/PartsController.cs b/PartSource.Api/Controllers/PartsController.cs index a007504..fb9ccb0 100644 --- a/PartSource.Api/Controllers/PartsController.cs +++ b/PartSource.Api/Controllers/PartsController.cs @@ -19,12 +19,29 @@ namespace PartSource.Api.Controllers private readonly NexpartService _nexpartService; private readonly PartService _partService; private readonly VehicleService _vehicleService; + private readonly FitmentService _fitmentService; - public PartsController(NexpartService nexpartService, PartService partService, VehicleService vehicleService) + public PartsController(NexpartService nexpartService, PartService partService, VehicleService vehicleService, FitmentService fitmentService) { _nexpartService = nexpartService; _partService = partService; _vehicleService = vehicleService; + _fitmentService = fitmentService; + } + + [HttpGet] + [Route("fitment")] + [Route("fitmentnote")] + public async Task GetFitment([FromQuery] string sku, [FromQuery] int vehicleId) + { + VehicleFitmentDto vehicleFitment = await _fitmentService.GetFitmentNotes(sku, vehicleId); + + if (vehicleFitment == null) + { + return NotFound(); + } + + return Ok(vehicleFitment); } [HttpGet] @@ -34,31 +51,22 @@ namespace PartSource.Api.Controllers Part part = await _partService.GetPartBySku(sku); Vehicle vehicle = await _vehicleService.GetVehicleById(vehicleId); - if (part == null) + if (part == null || vehicle == null) { return BadRequest(new { - Message = $"No part data is available for SKU {sku}. Confirm it is available in the database maintained by Sound Press.", + Message = $"No data is available for SKU {sku}. Confirm it is available in the database maintained by Sound Press.", Reason = $"{nameof(_partService.GetPartBySku)} returned null" }); } - 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(); + { + PartNumber = part.PartNumber, + MfrCode = m.WhiCode + }) + .ToArray(); SmartPageDataSearch smartPageDataSearch = new SmartPageDataSearch { @@ -76,9 +84,9 @@ namespace PartSource.Api.Controllers } PartType[] partTypes = smartPageResponse.ResponseBody.Item.Select(i => new PartType - { - Id = i.Part.PartType.Id - }) + { + Id = i.Part.PartType.Id + }) .ToArray(); ApplicationSearch applicationSearch = new ApplicationSearch diff --git a/PartSource.Api/PartSource.Api.csproj b/PartSource.Api/PartSource.Api.csproj index b04d8fa..9557f13 100644 --- a/PartSource.Api/PartSource.Api.csproj +++ b/PartSource.Api/PartSource.Api.csproj @@ -23,7 +23,6 @@ - Always diff --git a/PartSource.Api/Startup.cs b/PartSource.Api/Startup.cs index ec84a24..b555ea1 100644 --- a/PartSource.Api/Startup.cs +++ b/PartSource.Api/Startup.cs @@ -52,13 +52,12 @@ namespace PartSource.Api services.AddAutoMapper(typeof(PartSourceProfile)); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddCors(o => o.AddPolicy("Default", builder => { builder.AllowAnyOrigin() @@ -69,9 +68,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. diff --git a/PartSource.Api/appsettings.json b/PartSource.Api/appsettings.json index b3e4a45..b7c90dd 100644 --- a/PartSource.Api/appsettings.json +++ b/PartSource.Api/appsettings.json @@ -1,6 +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": "Server=tcp:ps-automation.eastus2.cloudapp.azure.com,1433;Initial Catalog=WhiFitment;User ID=automation;Password=)6L)XP%m(x-UU#M;Encrypt=True;TrustServerCertificate=True;Connection Timeout=300" //"FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;Integrated Security=true" }, "Logging": { diff --git a/PartSource.Automation/Extensions/FileInfoExtensions.cs b/PartSource.Automation/Extensions/FileInfoExtensions.cs new file mode 100644 index 0000000..538602c --- /dev/null +++ b/PartSource.Automation/Extensions/FileInfoExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace PartSource.Automation.Extensions +{ + public static class FileInfoExtensions + { + public static DateTime GetWhiTimestamp(this FileInfo fileInfo) + { + Match match = Regex.Match(fileInfo.Name, "[0-9]{8}"); + + return match.Success && DateTime.TryParseExact(match.Value, "MMddyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime timestamp) + ? timestamp + : DateTime.MinValue; + } + } +} diff --git a/PartSource.Automation/Jobs/ExecuteSsisPackages.cs b/PartSource.Automation/Jobs/ExecuteSsisPackages.cs index b078563..305a12e 100644 --- a/PartSource.Automation/Jobs/ExecuteSsisPackages.cs +++ b/PartSource.Automation/Jobs/ExecuteSsisPackages.cs @@ -17,7 +17,7 @@ namespace PartSource.Automation.Jobs private readonly ILogger _logger; // TODO: set from config - private readonly string[] _ssisPackages = {"Parts Price", "Parts Availability" }; + private readonly string[] _ssisPackages = { "Parts Availability" }; public ExecuteSsisPackages(EmailService emailService, IConfiguration configuration, SsisService ssisService, ILogger logger) { diff --git a/PartSource.Automation/Jobs/POC/BulkUpdateInventory.cs b/PartSource.Automation/Jobs/POC/BulkUpdateInventory.cs new file mode 100644 index 0000000..067d963 --- /dev/null +++ b/PartSource.Automation/Jobs/POC/BulkUpdateInventory.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using PartSource.Automation.Models.Configuration; +using PartSource.Automation.Models.Ftp; +using PartSource.Automation.Services; +using Ratermania.Automation.Interfaces; + +namespace PartSource.Automation.Jobs.POC +{ + public class BulkUpdateInventory : IAutomationJob + { + private readonly FtpService _ftpService; + + public BulkUpdateInventory(IConfiguration configuration) + { + FtpConfiguration ftpConfiguration = configuration.GetSection("FtpServers:AzureConfiguration").Get(); + _ftpService = new FtpService(ftpConfiguration); + } + + public async Task Run(CancellationToken token, params string[] arguments) + { + FtpFileInfo lastUploadedFile = _ftpService.ListFilesExtended() + .Where(f => f.FileType == FtpFileType.File && f.Filename.IndexOf("Availability") > -1) + .OrderByDescending(f => f.Modified) + .First(); + + string file = _ftpService.Download(lastUploadedFile.Filename, Path.GetTempPath()); + + DataTable dataTable = GetDataTable(file); + + using SqlConnection connection = new SqlConnection("Server=tcp:ps-automation-stage.eastus2.cloudapp.azure.com,1433;Initial Catalog=ps-whi-stage;Persist Security Info=False;User ID=stageuser;Password=]FXepK^cFYS|[H<;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;"); + connection.Open(); + + using SqlBulkCopy bulk = new SqlBulkCopy(connection) + { + DestinationTableName = $"PartAvailability", + BulkCopyTimeout = 14400 + }; + + bulk.WriteToServer(dataTable); + + return; + } + + private DataTable GetDataTable(string filename) + { + using DataTable dataTable = new DataTable(); + dataTable.Columns.Add("Store", typeof(int)); + dataTable.Columns.Add("SKU", typeof(string)); + dataTable.Columns.Add("QTY", typeof(int)); + + using StreamReader reader = new StreamReader(filename); + string line = reader.ReadLine(); // Burn the header row + + 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 sku = columns[1].Trim(); + if (int.TryParse(columns[0], out int store) + && !string.IsNullOrEmpty(sku) + && int.TryParse(columns[2], out int quantity)) + { + dataTable.Rows.Add(new object[] { store, sku, quantity }); + } + } + + return dataTable; + } + } +} diff --git a/PartSource.Automation/Jobs/POC/GetImageUrls.cs b/PartSource.Automation/Jobs/POC/GetImageUrls.cs new file mode 100644 index 0000000..05a5beb --- /dev/null +++ b/PartSource.Automation/Jobs/POC/GetImageUrls.cs @@ -0,0 +1,119 @@ +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 FitmentContext _fitmentContext; + private readonly PartService _partService; + private readonly ShopifyClient _shopifyClient; + + public GetImageUrls(NexpartService nexpartService, PartService partService, FitmentContext fitmentContext, ShopifyClient shopifyClient) + { + _nexpartService = nexpartService; + _fitmentContext = fitmentContext; + _partService = partService; + _shopifyClient = shopifyClient; + } + + public async Task Run(CancellationToken token, params string[] arguments) + { + IList rows = new List { + "\"Line Code\", \"Part Number\", \"Image URL(s)\"" + }; + + using StreamReader reader = new StreamReader("C:\\Users\\Tom\\Desktop\\image parts.csv"); + string line = reader.ReadLine(); // Burn the header row + + 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 partsourceCode = columns[0].Trim(); + string partNumber = columns[1].Trim(); + + + IList dcfMappings = await _partService.GetDcfMapping(partsourceCode); + if (dcfMappings.Count == 0) + { + Console.WriteLine($"No images for {partsourceCode} {partNumber}"); + } + + bool hasImage = false; + foreach (DcfMapping mapping in dcfMappings) + { + if (hasImage) + { + continue; + } + + SmartPageDataSearch dataSearch = new SmartPageDataSearch + { + Items = new Item[] + { + new Item + { + MfrCode = mapping.WhiCode, + PartNumber = partNumber + } + }, + DataOption = new[] { "ALL" } + }; + + SmartPageDataSearchResponse response = await _nexpartService.SendRequest(dataSearch); + + if (response.ResponseBody.Item?.Length > 0) + { + List urls = new List(); + + 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($"\"{partsourceCode}\", \"{partNumber}\", \"{string.Join(";", urls)}\""); + hasImage = true; + } + } + } + + if (!hasImage) + { + Console.WriteLine($"No images for {partsourceCode} {partNumber}"); + } + } + + await File.WriteAllLinesAsync($"C:\\users\\Tom\\desktop\\WHI Images {DateTime.Now:yyyyMMdd}.csv", rows); + } + } +} diff --git a/PartSource.Automation/Jobs/POC/GetImageUrlsTemp.cs b/PartSource.Automation/Jobs/POC/GetImageUrlsTemp.cs new file mode 100644 index 0000000..2c951ea --- /dev/null +++ b/PartSource.Automation/Jobs/POC/GetImageUrlsTemp.cs @@ -0,0 +1,106 @@ +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 GetImageUrlsTemp : IAutomationJob + { + private readonly NexpartService _nexpartService; + private readonly FitmentContext _fitmentContext; + private readonly PartService _partService; + private readonly ShopifyClient _shopifyClient; + + private readonly string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public GetImageUrlsTemp(NexpartService nexpartService, PartService partService, FitmentContext fitmentContext, ShopifyClient shopifyClient) + { + _nexpartService = nexpartService; + _fitmentContext = fitmentContext; + _partService = partService; + _shopifyClient = shopifyClient; + } + + public async Task Run(CancellationToken token, params string[] arguments) + { + IList> parts = new List>(); + parts.Add(new KeyValuePair("DAY", "89310")); + parts.Add(new KeyValuePair("CNI", "141.40113")); + parts.Add(new KeyValuePair("PRF", "MU19631")); + parts.Add(new KeyValuePair("TRK", "SB8100")); + parts.Add(new KeyValuePair("MON", "906970")); + parts.Add(new KeyValuePair("FEL", "70804")); + parts.Add(new KeyValuePair("FEL", "SS71198")); + parts.Add(new KeyValuePair("CFP", "STS314")); + parts.Add(new KeyValuePair("NGK", "21517")); + parts.Add(new KeyValuePair("NGK", "RC-XX89")); + parts.Add(new KeyValuePair("FRA", "CA176")); + + for (int i = 0; i < chars.Length; i++) + { + for (int j = 0; j < chars.Length; j++) + { + for (int k = 0; k < chars.Length; k++) + { + string actualLineCode = $"{chars[i]}{chars[j]}{chars[k]}"; + System.Diagnostics.Debug.WriteLine(actualLineCode); + + foreach (KeyValuePair part in parts) + { + + + SmartPageDataSearch dataSearch = new SmartPageDataSearch + { + Items = new Item[] + { + new Item + { + MfrCode = actualLineCode, + PartNumber = part.Value + } + }, + DataOption = new[] { "DIST_LINE", "ALL" } + }; + + SmartPageDataSearchResponse response = await _nexpartService.SendRequest(dataSearch); + + if (response.ResponseBody.Item?.Length > 0) + { + List urls = new List(); + + 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) + { + Console.WriteLine($"Image {urls[0]} found for {part.Value}. Expected: {part.Key}, Actual: {actualLineCode}"); + } + } + } + } + } + } + } + } +} diff --git a/PartSource.Automation/Jobs/POC/ImageList.cs b/PartSource.Automation/Jobs/POC/ImageList.cs deleted file mode 100644 index 77ec44e..0000000 --- a/PartSource.Automation/Jobs/POC/ImageList.cs +++ /dev/null @@ -1,84 +0,0 @@ -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 rows = new List { - "\"Line Code\", \"Part Number\", \"Image URL(s)\"" - }; - - IList 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(dataSearch); - - if (response.ResponseBody.Item?.Length > 0) - { - List urls = new List(); - - 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); - } - } -} \ No newline at end of file diff --git a/PartSource.Automation/Jobs/POC/PartialInventoryUpdate.cs b/PartSource.Automation/Jobs/POC/PartialInventoryUpdate.cs index 754cc30..fddc010 100644 --- a/PartSource.Automation/Jobs/POC/PartialInventoryUpdate.cs +++ b/PartSource.Automation/Jobs/POC/PartialInventoryUpdate.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using PartSource.Automation.Models.Configuration; using PartSource.Automation.Models.Ftp; using PartSource.Automation.Services; @@ -18,67 +19,62 @@ namespace PartSource.Automation.Jobs.POC public class PartialInventoryUpdate : IAutomationJob { private readonly FtpService _ftpService; + private readonly ILogger _logger; - public PartialInventoryUpdate(IConfiguration configuration) + public PartialInventoryUpdate(IConfiguration configuration, ILogger logger) { - FtpConfiguration ftpConfiguration = configuration.GetSection("FtpServers:AzureConfiguration").Get(); + FtpConfiguration ftpConfiguration = configuration.GetSection("FtpServers:AutomationConfiguration").Get(); _ftpService = new FtpService(ftpConfiguration); + + _logger = logger; } public async Task Run(CancellationToken token, params string[] arguments) { - FtpFileInfo lastUploadedFile = _ftpService.ListFilesExtended("") - .Where(f => f.FileType == FtpFileType.File && f.Filename.IndexOf("Availability") > -1) + FtpFileInfo lastUploadedFile = _ftpService.ListFilesExtended() + .Where(f => f.FileType == FtpFileType.File && f.Modified >= DateTime.Now.AddHours(-24) && f.Filename.IndexOf("Availability Partial") > -1) .OrderByDescending(f => f.Modified) - .First(); + .FirstOrDefault(); - string file = _ftpService.Download(lastUploadedFile.Filename, Path.GetTempPath()); + if (lastUploadedFile == null) + { + _logger.LogInformation($"No partial inventory file available for the time period {DateTime.Now.AddHours(-24)} - {DateTime.Now}"); + return; + } - DataTable dataTable = GetDataTable(file); + string file = _ftpService.Download($"{lastUploadedFile.Filename}", "C:\\Users\\Tom\\Desktop"); - using SqlConnection connection = new SqlConnection("Server=tcp:ps-automation-stage.eastus2.cloudapp.azure.com,1433;Initial Catalog=ps-whi-stage;Persist Security Info=False;User ID=stageuser;Password=]FXepK^cFYS|[H<;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;"); + + using SqlConnection connection = new SqlConnection("Server=tcp:ps-automation-stage.eastus2.cloudapp.azure.com,1433;Initial Catalog=ps-whi-stage;Persist Security Info=False;User ID=stageuser;Password=]FXepK^cFYS|[H<;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;"); connection.Open(); - using SqlBulkCopy bulk = new SqlBulkCopy(connection) - { - DestinationTableName = $"PartAvailability", - BulkCopyTimeout = 14400 - }; + using StreamReader reader = new StreamReader(file); + string line = reader.ReadLine(); // Burn the header row - bulk.WriteToServer(dataTable); + 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); + } + + if (int.TryParse(columns[0], out int store) + && int.TryParse(columns[1], out int quantity) + && int.TryParse(columns[2], out int sku)) + { + using SqlCommand sqlCommand = new SqlCommand("UPDATE Inventory SET QTY = @qty WHERE SKU = @sku AND Store = @store", connection); + sqlCommand.Parameters.Add(new SqlParameter("qty", quantity)); + sqlCommand.Parameters.Add(new SqlParameter("sku", sku)); + sqlCommand.Parameters.Add(new SqlParameter("store", store)); + + await sqlCommand.ExecuteNonQueryAsync(); + } + } return; } - - private DataTable GetDataTable(string filename) - { - using DataTable dataTable = new DataTable(); - dataTable.Columns.Add("Store", typeof(int)); - dataTable.Columns.Add("SKU", typeof(int)); - dataTable.Columns.Add("QTY", typeof(int)); - - using StreamReader reader = new StreamReader(filename); - string line = reader.ReadLine(); // Burn the header row - - 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); - } - - if (int.TryParse(columns[0], out int store) - && int.TryParse(columns[1], out int sku) - && int.TryParse(columns[2], out int quantity)) - { - dataTable.Rows.Add(new object[] { store, sku, quantity }); - } - } - - return dataTable; - } } } diff --git a/PartSource.Automation/Jobs/ProcessWhiFitment.cs b/PartSource.Automation/Jobs/ProcessWhiFitment.cs index b625e65..5a6f482 100644 --- a/PartSource.Automation/Jobs/ProcessWhiFitment.cs +++ b/PartSource.Automation/Jobs/ProcessWhiFitment.cs @@ -96,9 +96,8 @@ namespace PartSource.Automation.Jobs Task.WaitAll(taskArray); - // _whiSeoService.CreateFitmentView(); - - //_whiSeoService.SaveNotes(_noteDictionary); + _whiSeoService.SaveNotes(_noteDictionary); + //_whiSeoService.CreateFitmentView(); } public string Decompress(FileInfo fileInfo) diff --git a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs index 6ff9f06..2c0352d 100644 --- a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs +++ b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using PartSource.Automation.Extensions; using PartSource.Automation.Models.Configuration; using PartSource.Automation.Models.Enums; using PartSource.Automation.Services; @@ -45,7 +46,9 @@ namespace PartSource.Automation.Jobs 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")) + .OrderByDescending(f => f.GetWhiTimestamp()); foreach (FileInfo fileInfo in files) { diff --git a/PartSource.Automation/Jobs/UpdateFitment.cs b/PartSource.Automation/Jobs/UpdateFitment.cs index 5eb9c25..2de202a 100644 --- a/PartSource.Automation/Jobs/UpdateFitment.cs +++ b/PartSource.Automation/Jobs/UpdateFitment.cs @@ -40,11 +40,11 @@ namespace PartSource.Automation.Jobs try { - products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 } }); - //products = new List - //{ - // await _shopifyClient.Products.GetById(4388919574575) - //}; + //products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 } }); + products = new List + { + await _shopifyClient.Products.GetById(7285013446703) + }; } catch (Exception ex) @@ -72,40 +72,43 @@ namespace PartSource.Automation.Jobs }; bool isFitment = false; - string bodyHtml = product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("") + "".Length); + string bodyHtml = string.IsNullOrEmpty(product.BodyHtml) + ? "
    " + : product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("") + "".Length); - IList vehicles = _vehicleFitmentService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); + IList vehicles = await _vehicleFitmentService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); IList vehicleIdFitment = _vehicleFitmentService.GetVehicleIdFitment(vehicles); - if (vehicleIdFitment.Count > 0) + if (vehicleIdFitment.Count == 0) { - string vehicleIdString = string.Join(',', vehicleIdFitment.Select(j => $"v{j}")); + continue; + } + string vehicleIdString = string.Join(',', vehicleIdFitment.Select(j => $"v{j}")); - bodyHtml += $"
    {vehicleIdString}
    "; + bodyHtml += $"
    {vehicleIdString}
    "; - isFitment = true; + isFitment = true; - string json = JsonConvert.SerializeObject(vehicleIdFitment); - if (json.Length < 100000) + string json = JsonConvert.SerializeObject(vehicleIdFitment); + if (json.Length < 100000) + { + Metafield vehicleMetafield = new Metafield { - Metafield vehicleMetafield = new Metafield - { - Namespace = "fitment", - Key = "ids", - Value = json, - Type = "json_string", - OwnerResource = "product", - OwnerId = product.Id - }; + Namespace = "fitment", + Key = "ids", + Value = json, + Type = "json_string", + OwnerResource = "product", + OwnerId = product.Id + }; - await _shopifyClient.Metafields.Add(vehicleMetafield); - } + await _shopifyClient.Metafields.Add(vehicleMetafield); + } - else - { - _logger.LogWarning($"Vehicle ID fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); - continue; - } + else + { + _logger.LogWarning($"Vehicle ID fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); + continue; } IList ymmFitment = _vehicleFitmentService.GetYmmFitment(vehicles); @@ -135,7 +138,7 @@ namespace PartSource.Automation.Jobs bodyHtml += $"
    {stringBuilder.ToString()}
    "; - string json = JsonConvert.SerializeObject(ymmFitment); + json = JsonConvert.SerializeObject(ymmFitment); if (json.Length < 100000) { Metafield ymmMetafield = new Metafield diff --git a/PartSource.Automation/Jobs/UpdatePositioning.cs b/PartSource.Automation/Jobs/UpdatePositioning.cs index e4cbcf6..9359cba 100644 --- a/PartSource.Automation/Jobs/UpdatePositioning.cs +++ b/PartSource.Automation/Jobs/UpdatePositioning.cs @@ -64,7 +64,7 @@ namespace PartSource.Automation.Jobs } IList fitments = GetPositionOrderedFitments(importData?.PartNumber, importData?.LineCode); - IList vehicles = _vehicleFitmentService.GetVehiclesForPart(importData?.PartNumber, importData?.LineCode); + IList vehicles = await _vehicleFitmentService.GetVehiclesForPart(importData?.PartNumber, importData?.LineCode); if (fitments.Count == 0 || vehicles.Count == 0) { diff --git a/PartSource.Automation/Program.cs b/PartSource.Automation/Program.cs index 8790f7e..259ba2f 100644 --- a/PartSource.Automation/Program.cs +++ b/PartSource.Automation/Program.cs @@ -64,49 +64,49 @@ namespace PartSource.Automation .AddShopify(options => { - options.ApiKey = builder.Configuration["Shopify:ApiKey"]; - options.ApiSecret = builder.Configuration["Shopify:ApiSecret"]; - options.ApiVersion = "2022-10"; - options.ShopDomain = builder.Configuration["Shopify:ShopDomain"]; + options.ApiKey = builder.Configuration["Shopify:ApiKey"]; + options.ApiSecret = builder.Configuration["Shopify:ApiSecret"]; + options.ApiVersion = "2022-10"; + options.ShopDomain = builder.Configuration["Shopify:ShopDomain"]; - //options.ApiKey = "9a533dad460321c6ce8f30bf5b8691ed"; - //options.ApiSecret = "dc9e28365d9858e544d57ac7af43fee7"; - //options.ApiVersion = "2022-10"; - //options.ShopDomain = "dev-partsource.myshopify.com"; - }) + //options.ApiKey = "9a533dad460321c6ce8f30bf5b8691ed"; + //options.ApiSecret = "dc9e28365d9858e544d57ac7af43fee7"; + //options.ApiVersion = "2022-10"; + //options.ShopDomain = "dev-partsource.myshopify.com"; + }) .AddAutomation(options => { options.HasBaseInterval(new TimeSpan(0, 15, 0)) .HasMaxFailures(1) - //.HasJob(options => options.HasInterval(new TimeSpan(7, 0, 0, 0))); - // - //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0))) - // .HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0))); - //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) - //.HasDependency() - //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0))); - //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) - // .HasDependency() - // .HasDependency() - // .HasDependency() - // .StartsAt(DateTime.Today.AddHours(8)) - //) ; - //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) - // .StartsAt(DateTime.Parse("2021-04-01 08:00:00")) - //) - //.HasJob(options => - // options.HasInterval(new TimeSpan(24, 0, 0)) - // //.StartsAt(DateTime.Today.AddHours(25)) - // ) + //.HasJob(options => options.HasInterval(new TimeSpan(7, 0, 0, 0))); + // + //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0))) + // .HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0))); + //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) + //.HasDependency() + //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0))); + //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) + // .HasDependency() + // .HasDependency() + // .HasDependency() + // .StartsAt(DateTime.Today.AddHours(8)) + //) ; + //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) + // .StartsAt(DateTime.Parse("2021-04-01 08:00:00")) + //) + //.HasJob(options => + // options.HasInterval(new TimeSpan(24, 0, 0)) + // //.StartsAt(DateTime.Today.AddHours(25)) + // ) - .HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0)) - //.HasDependency() - // .StartsAt(DateTime.Today.AddHours(28)) - ); - //); - //.AddApiServer(); - }) + .HasJob(options => options.HasInterval(new TimeSpan(1, 0, 0)) + //.HasDependency() + .StartsAt(new DateTime(2023, 01, 01, 01, 0, 0)) + ); + //); + //.AddApiServer(); + }) .AddSingleton() .AddSingleton() @@ -114,6 +114,7 @@ namespace PartSource.Automation .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddAutoMapper(typeof(PartSourceProfile)); }) diff --git a/PartSource.Automation/Services/FtpService.cs b/PartSource.Automation/Services/FtpService.cs index 84d5a80..c2598a3 100644 --- a/PartSource.Automation/Services/FtpService.cs +++ b/PartSource.Automation/Services/FtpService.cs @@ -19,7 +19,7 @@ namespace PartSource.Automation.Services _ftpConfiguration = ftpConfiguration; } - public IList ListFilesExtended(string directory) + public IList ListFilesExtended(string directory = "") { FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{directory}")); request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); @@ -67,7 +67,7 @@ namespace PartSource.Automation.Services return files; } - public string[] ListFiles(string directory) + public string[] ListFiles(string directory = "") { FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{directory}")); request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); @@ -102,7 +102,12 @@ namespace PartSource.Automation.Services } destination = Path.Combine(destination, filename); - + string destinationDirectory = Path.GetDirectoryName(destination); + if (!Directory.Exists(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + using FtpWebResponse response = (FtpWebResponse)request.GetResponse(); using Stream responseStream = response.GetResponseStream(); using FileStream fileStream = new FileStream(destination, FileMode.Create); diff --git a/PartSource.Automation/Services/SsisService.cs b/PartSource.Automation/Services/SsisService.cs index 6fccf2a..4c15b17 100644 --- a/PartSource.Automation/Services/SsisService.cs +++ b/PartSource.Automation/Services/SsisService.cs @@ -28,7 +28,7 @@ namespace PartSource.Automation.Services { StartInfo = new ProcessStartInfo { - FileName = "dtexec", + FileName = "C:\\Program Files\\Microsoft SQL Server\\150\\DTS\\Binn\\dtexec.exe", Arguments = $"/file \"{_ssisConfiguration.Directory}\\{packageName}\"", UseShellExecute = false, CreateNoWindow = false, diff --git a/PartSource.Automation/Services/VehicleFitmentService.cs b/PartSource.Automation/Services/VehicleFitmentService.cs index d8d5a3d..a4355ba 100644 --- a/PartSource.Automation/Services/VehicleFitmentService.cs +++ b/PartSource.Automation/Services/VehicleFitmentService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using PartSource.Data.Contexts; using PartSource.Data.Dtos; using PartSource.Data.Models; @@ -93,18 +94,19 @@ namespace PartSource.Automation.Services return vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToArray(); } - public IList GetVehiclesForPart(string partNumber, string lineCode, int maxVehicles = 0) + public async Task> 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); + partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9]", string.Empty); - IQueryable whiCodes = _fitmentContext.DcfMappings - .Where(d => d.LineCode == lineCode) - .Select(d => d.WhiCode); + IList whiCodes = await _fitmentContext.DcfMappings + .Where(dcf => dcf.LineCode == lineCode) + .Select(dcf => dcf.WhiCode) + .ToListAsync(); IQueryable vehicles = _fitmentContext.Fitments .Where(f => f.PartNumber == partNumber && whiCodes.Contains(f.LineCode)) @@ -122,38 +124,5 @@ namespace PartSource.Automation.Services 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.Automation/appsettings.json b/PartSource.Automation/appsettings.json index cb63be3..4377f40 100644 --- a/PartSource.Automation/appsettings.json +++ b/PartSource.Automation/appsettings.json @@ -16,6 +16,12 @@ "Url": "ftp://waws-prod-yq1-007.ftp.azurewebsites.windows.net/site/wwwroot", "Destination": "C:\\Partsource.Automation\\Downloads" }, + "AutomationConfiguration": { + "Username": "stageuser", + "Password": "FXepK^cFYS|[H<", + "Url": "ftp://ps-automation-stage.eastus2.cloudapp.azure.com", + "Destination": "C:\\Partsource.Automation\\Downloads\\Stage" + }, "WhiConfiguration": { "Username": "ctc_seo", "Password": "be34hz64e4", @@ -35,7 +41,7 @@ "LogLevel": { "Default": "Information", "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.Hosting.Lifetime": "Information" // "Microsoft.EntityFrameworkCore.Database.Command": "Information" }, "EventLog": { diff --git a/PartSource.Data/Contexts/FitmentContext.cs b/PartSource.Data/Contexts/FitmentContext.cs index ef1c9a4..ffd356b 100644 --- a/PartSource.Data/Contexts/FitmentContext.cs +++ b/PartSource.Data/Contexts/FitmentContext.cs @@ -19,6 +19,8 @@ namespace PartSource.Data.Contexts public DbSet Vehicles { get; set; } + public DbSet VehicleFitments { get; set; } + public DbSet Wipers { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -28,6 +30,7 @@ namespace PartSource.Data.Contexts 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}); + modelBuilder.Entity().HasNoKey(); foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { diff --git a/PartSource.Data/Dtos/VehicleFitmentDto.cs b/PartSource.Data/Dtos/VehicleFitmentDto.cs index 56bf211..682137d 100644 --- a/PartSource.Data/Dtos/VehicleFitmentDto.cs +++ b/PartSource.Data/Dtos/VehicleFitmentDto.cs @@ -5,10 +5,8 @@ using System.Text; namespace PartSource.Data.Dtos { - public class VehicleFitmentDto - { - public Fitment Fitment { get; set; } - - public Vehicle Vehicle { get; set; } - } + public class VehicleFitmentDto : VehicleFitment + { + public IList SubmodelNames { get; set; } + } } diff --git a/PartSource.Data/Models/VehicleFitment.cs b/PartSource.Data/Models/VehicleFitment.cs new file mode 100644 index 0000000..ed55446 --- /dev/null +++ b/PartSource.Data/Models/VehicleFitment.cs @@ -0,0 +1,27 @@ +namespace PartSource.Data.Models +{ + public class VehicleFitment + { + public string Sku { get; set; } + + public string LineCode { get; set; } + + public string PartNumber { get; set; } + + public string NoteText { get; set; } + + public int Year { get; set; } + + public string MakeName { get; set; } + + public string ModelName { get; set; } + + public string SubmodelName { get; set; } + + public int BaseVehicleId { get; set; } + + public int EngineConfigId { get; set; } + + public int VehicleToEngineConfigId { get; set; } + } +} diff --git a/PartSource.Services/FitmentService.cs b/PartSource.Services/FitmentService.cs index 38a2354..6353f3d 100644 --- a/PartSource.Services/FitmentService.cs +++ b/PartSource.Services/FitmentService.cs @@ -1,159 +1,160 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using PartSource.Data.Contexts; using PartSource.Data.Dtos; using PartSource.Data.Models; +using PartSource.Data.Nexpart; namespace PartSource.Services { public class FitmentService { - private readonly FitmentContext _fitmentContext; + private readonly FitmentContext _fitmentContext; - public FitmentService(FitmentContext fitmentContext) + public FitmentService(FitmentContext fitmentContext) { - _fitmentContext = fitmentContext; + _fitmentContext = fitmentContext; } - public IList GetYmmFitment(IList vehicles) - { - if (vehicles.Count == 0) - { - return new string[0]; - } + public async Task GetFitmentNotes(string sku, int vehicleId) + { + VehicleFitmentDto vehicleFitment = await _fitmentContext.VehicleFitments + .Where(vf => vf.VehicleToEngineConfigId == vehicleId && vf.Sku == sku) + .Select(vf => new VehicleFitmentDto + { + Sku = vf.Sku, + LineCode = vf.LineCode, + PartNumber = vf.PartNumber, + NoteText = vf.NoteText, + Year = vf.Year, + MakeName = vf.MakeName, + ModelName = vf.ModelName, + BaseVehicleId = vf.BaseVehicleId, + EngineConfigId = vf.EngineConfigId, + VehicleToEngineConfigId = vf.VehicleToEngineConfigId + }) + .FirstOrDefaultAsync(); - IList fitmentTags = new List(); + if (vehicleFitment == null) + { + return null; + } - IList makeModels = vehicles.OrderBy(v => v.MakeName).ThenBy(v => v.ModelName).Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList(); + vehicleFitment.SubmodelNames = await _fitmentContext.VehicleFitments + .Where(vf => vf.BaseVehicleId == vehicleFitment.BaseVehicleId && vf.Sku == sku) + .Select(vf => vf.SubmodelName) + .Distinct() + .ToListAsync(); - foreach (string makeModel in makeModels) - { - string make = makeModel.Split(',')[0]; - string model = makeModel.Split(',')[1]; + return vehicleFitment; + } - List years = vehicles - .Where(v => v.MakeName == make && v.ModelName == model) - .OrderBy(v => v.Year) - .Select(v => v.Year.ToString().Trim()) - .Distinct() - .ToList(); + public IList GetYmmFitment(IList vehicles) + { + if (vehicles.Count == 0) + { + return new string[0]; + } - string tag = $"{string.Join('-', years)} {make.Trim()} {model.Trim()}"; + IList fitmentTags = new List(); - System.Diagnostics.Debug.WriteLine(tag); + IList makeModels = vehicles.OrderBy(v => v.MakeName).ThenBy(v => v.ModelName).Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList(); - fitmentTags.Add(tag); - } + foreach (string makeModel in makeModels) + { + string make = makeModel.Split(',')[0]; + string model = makeModel.Split(',')[1]; - return fitmentTags; - } + List years = vehicles + .Where(v => v.MakeName == make && v.ModelName == model) + .OrderBy(v => v.Year) + .Select(v => v.Year.ToString().Trim()) + .Distinct() + .ToList(); - public IList GetYmmFitmentRange(IList vehicles) - { - if (vehicles.Count == 0) - { - return new string[0]; - } + string tag = $"{string.Join('-', years)} {make.Trim()} {model.Trim()}"; - IList fitmentTags = new List(); + System.Diagnostics.Debug.WriteLine(tag); - IList makeModels = vehicles.Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList(); + fitmentTags.Add(tag); + } - foreach (string makeModel in makeModels) - { - string make = makeModel.Split(',')[0]; - string model = makeModel.Split(',')[1]; + return fitmentTags; + } - int minYear = vehicles - .Where(v => v.MakeName == make && v.ModelName == model) - .Min(v => v.Year); + public IList GetYmmFitmentRange(IList vehicles) + { + if (vehicles.Count == 0) + { + return new string[0]; + } - int maxYear = vehicles - .Where(v => v.MakeName == make && v.ModelName == model) - .Max(v => v.Year); + IList fitmentTags = new List(); - string tag = minYear == maxYear - ? $"{minYear} {make.Trim()} {model.Trim()}" - : $"{minYear}-{maxYear} {make.Trim()} {model.Trim()}"; + IList makeModels = vehicles.Select(v => $"{v.MakeName},{v.ModelName}").Distinct().ToList(); - System.Diagnostics.Debug.WriteLine(tag); + foreach (string makeModel in makeModels) + { + string make = makeModel.Split(',')[0]; + string model = makeModel.Split(',')[1]; - fitmentTags.Add(tag); - } + int minYear = vehicles + .Where(v => v.MakeName == make && v.ModelName == model) + .Min(v => v.Year); - return fitmentTags; - } + int maxYear = vehicles + .Where(v => v.MakeName == make && v.ModelName == model) + .Max(v => v.Year); - public IList GetVehicleIdFitment(IList vehicles) - { - return vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToList(); - } + string tag = minYear == maxYear + ? $"{minYear} {make.Trim()} {model.Trim()}" + : $"{minYear}-{maxYear} {make.Trim()} {model.Trim()}"; - public IList GetVehiclesForPart(string partNumber, string lineCode, int maxVehicles = 0) - { - if (string.IsNullOrEmpty(partNumber) || string.IsNullOrEmpty(lineCode)) - { - return null; - } + System.Diagnostics.Debug.WriteLine(tag); - partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9]", string.Empty); + fitmentTags.Add(tag); + } - IQueryable whiCodes = _fitmentContext.DcfMappings - .Where(d => d.LineCode == lineCode) - .Select(d => d.WhiCode); + return fitmentTags; + } - 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); + public IList GetVehicleIdFitment(IList vehicles) + { + return vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToList(); + } - if (maxVehicles > 0) - { - vehicles = vehicles.Take(maxVehicles); - } + public IList GetVehiclesForPart(string partNumber, string lineCode, int maxVehicles = 0) + { + if (string.IsNullOrEmpty(partNumber) || string.IsNullOrEmpty(lineCode)) + { + return null; + } - return vehicles.ToList(); - } + partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9]", string.Empty); - public IList GetVehicleFitmentForPart(string partNumber, string lineCode, int maxVehicles = 0) - { - if (string.IsNullOrEmpty(partNumber) || string.IsNullOrEmpty(lineCode)) - { - return null; - } + IQueryable whiCodes = _fitmentContext.DcfMappings + .Where(d => d.LineCode == lineCode) + .Select(d => d.WhiCode); - partNumber = Regex.Replace(partNumber, "[^a-zA-Z0-9\\-]", string.Empty); + 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); - IQueryable whiCodes = _fitmentContext.DcfMappings - .Where(d => d.LineCode == lineCode) - .Select(d => d.WhiCode); + if (maxVehicles > 0) + { + vehicles = vehicles.Take(maxVehicles); + } - 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(); - } - } + return vehicles.ToList(); + } + } }