diff --git a/PartSource.Api/Controllers/PartsController.cs b/PartSource.Api/Controllers/PartsController.cs index fb9ccb0..cc9f5ad 100644 --- a/PartSource.Api/Controllers/PartsController.cs +++ b/PartSource.Api/Controllers/PartsController.cs @@ -4,6 +4,7 @@ using PartSource.Data.Dtos; using PartSource.Data.Models; using PartSource.Data.Nexpart; using PartSource.Services; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -41,6 +42,77 @@ namespace PartSource.Api.Controllers return NotFound(); } + try + { + string[] segments = vehicleFitment.NoteText.Split(']'); + vehicleFitment.PartDescription = segments[0].TrimStart('['); + vehicleFitment.DriveTypes = GetDriveTypesFromNote(vehicleFitment.NoteText); + vehicleFitment.Notes = segments[1].Split(';') + .Select(n => n.Trim()) + .ToList(); + } + + catch + { + throw new InvalidOperationException($"The note_text field provided by WHI for {vehicleFitment.LineCode} {vehicleFitment.PartNumber} was in an invalid format."); + } + + SmartPageDataSearch smartPageDataSearch = new SmartPageDataSearch + { + Items = new[] + { + new Item { PartNumber = vehicleFitment.PartNumber, MfrCode = vehicleFitment.LineCode } + } + }; + + SmartPageDataSearchResponse smartPageResponse = await _nexpartService.SendRequest(smartPageDataSearch); + if (smartPageResponse.ResponseBody?.Item != null) + { + PartType[] partTypes = smartPageResponse.ResponseBody.Item.Select(i => new PartType + { + Id = i.Part.PartType.Id + }) + .ToArray(); + + ApplicationSearch applicationSearch = new ApplicationSearch + { + VehicleIdentifier = new VehicleIdentifier + { + BaseVehicleId = vehicleFitment.BaseVehicleId, + EngineConfigId = vehicleFitment.EngineConfigId + }, + MfrCode = new[] { vehicleFitment.LineCode }, + PartType = partTypes, + GroupBy = "MFR", + QuestionOption = "QUESTION_OTHERWISE_APP" + }; + + ApplicationSearchResponse response = await _nexpartService.SendRequest(applicationSearch); + if (response.ResponseBody != null && response.ResponseBody is Questions) + { + Question driveTypeQuestion = ((Questions)response.ResponseBody).Question + .Where(q => q.Attribute == "DRIVE_TYPE") + .FirstOrDefault(); + + if (driveTypeQuestion != null) + { + foreach (Answer answer in driveTypeQuestion.Answer) + { + applicationSearch.Criterion = new[] + { + new Criterion { Attribute = "DRIVE_TYPE", Id = answer.Id} + }; + + ApplicationSearchResponse driveTypeResponse = await _nexpartService.SendRequest(applicationSearch); + if (driveTypeResponse.ResponseBody != null && ((Apps)driveTypeResponse.ResponseBody).App.Where(a => a.Part == vehicleFitment.PartNumber).Any()) + { + vehicleFitment.DriveTypes.Add(answer.Value); + } + } + } + } + } + return Ok(vehicleFitment); } @@ -51,15 +123,24 @@ namespace PartSource.Api.Controllers Part part = await _partService.GetPartBySku(sku); Vehicle vehicle = await _vehicleService.GetVehicleById(vehicleId); - if (part == null || vehicle == null) + if (part == null) { return BadRequest(new { - Message = $"No data is available for SKU {sku}. Confirm it is available in the database maintained by Sound Press.", + 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" }); } + if (vehicle == null) + { + return BadRequest(new + { + Message = $"No vehicle data is available for vehicle ID {vehicleId}. 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 { @@ -87,7 +168,7 @@ namespace PartSource.Api.Controllers { Id = i.Part.PartType.Id }) - .ToArray(); + .ToArray(); ApplicationSearch applicationSearch = new ApplicationSearch { @@ -98,13 +179,13 @@ namespace PartSource.Api.Controllers MfrCode = mappings.Select(m => m.WhiCode).ToArray(), PartType = new[] { new PartType { Id = smartPageResponse.ResponseBody.Item[0].Part.PartType.Id } }, Criterion = new[] + { + new Criterion { - new Criterion - { - Attribute = "REGION", - Id = 2 - } - }, + Attribute = "REGION", + Id = 2 + } + }, GroupBy = "PARTTYPE" }; @@ -120,7 +201,7 @@ namespace PartSource.Api.Controllers } IList positions = new List(); - foreach (App app in response.ResponseBody?.App) + foreach (App app in ((Apps)response.ResponseBody)?.App) { if (!string.IsNullOrEmpty(app.Position) && app.Part == part.PartNumber) { @@ -136,5 +217,33 @@ namespace PartSource.Api.Controllers }); } + private IList GetDriveTypesFromNote(string fitmentNote) + { + fitmentNote = fitmentNote.ToUpperInvariant(); + IList driveTypes = new List(); + + if (fitmentNote.Contains("FWD")) + { + driveTypes.Add("FWD"); + } + + if (fitmentNote.Contains("RWD")) + { + driveTypes.Add("RWD"); + } + + if (fitmentNote.Contains("AWD")) + { + driveTypes.Add("AWD"); + } + + if (fitmentNote.Contains("4WD")) + { + driveTypes.Add("4WD"); + } + + return driveTypes; + } + } } diff --git a/PartSource.Api/Controllers/WipersController.cs b/PartSource.Api/Controllers/WipersController.cs index 5245389..4ae77aa 100644 --- a/PartSource.Api/Controllers/WipersController.cs +++ b/PartSource.Api/Controllers/WipersController.cs @@ -51,7 +51,7 @@ namespace PartSource.Api.Controllers if (response.ResponseBody != null) { - return NexpartResponse(response); + return NexpartResponse(response); } else diff --git a/PartSource.Api/PartSource.Api.csproj b/PartSource.Api/PartSource.Api.csproj index 9557f13..09e7782 100644 --- a/PartSource.Api/PartSource.Api.csproj +++ b/PartSource.Api/PartSource.Api.csproj @@ -4,6 +4,7 @@ net6.0 f9e2fd37-0f2d-4e3a-955a-8e49a16fce1c Debug;Release;Also Debug + en-us;en @@ -34,6 +35,7 @@ + diff --git a/PartSource.Api/Properties/launchSettings.json b/PartSource.Api/Properties/launchSettings.json index 319f4c1..468c578 100644 --- a/PartSource.Api/Properties/launchSettings.json +++ b/PartSource.Api/Properties/launchSettings.json @@ -1,13 +1,4 @@ { - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:31337", - "sslPort": 0 - } - }, - "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "IIS Express": { "commandName": "IISExpress", @@ -18,12 +9,20 @@ }, "PartSource.Api": { "commandName": "Project", - "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "https://localhost:5001;http://localhost:5000" } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:31337", + "sslPort": 0 + } } } \ No newline at end of file diff --git a/PartSource.Api/appsettings.json b/PartSource.Api/appsettings.json index b7c90dd..7059cbb 100644 --- a/PartSource.Api/appsettings.json +++ b/PartSource.Api/appsettings.json @@ -3,6 +3,7 @@ "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" + "FitmentDatabase": "Server=tcp:ps-automation.eastus2.cloudapp.azure.com,1433;Initial Catalog=WhiFitment;User ID=sa;Password=GZ0`-ekd~[2u;Encrypt=True;TrustServerCertificate=True;Connection Timeout=300" }, "Logging": { "LogLevel": { diff --git a/PartSource.Automation/Jobs/ExecuteSsisPackages.cs b/PartSource.Automation/Jobs/ExecuteSsisPackages.cs index 305a12e..c6f30cc 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 Availability" }; + private readonly string[] _ssisPackages = {"Parts Availability" }; public ExecuteSsisPackages(EmailService emailService, IConfiguration configuration, SsisService ssisService, ILogger logger) { @@ -36,7 +36,7 @@ namespace PartSource.Automation.Jobs { try { - _ftpService.Download($"{package}.txt"); + // _ftpService.Download($"{package}.txt"); _ssisService.Execute($"{package}.dtsx"); _logger.LogInformation($"Execution of SSIS package {package} completed successfully."); diff --git a/PartSource.Automation/Jobs/POC/ImageList.cs b/PartSource.Automation/Jobs/POC/ImageList.cs new file mode 100644 index 0000000..556dc3a --- /dev/null +++ b/PartSource.Automation/Jobs/POC/ImageList.cs @@ -0,0 +1,95 @@ +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; + private readonly FitmentContext _fitmentContext; + + public GetImageUrls(NexpartService nexpartService, PartSourceContext partSourceContext, FitmentContext fitmentContext) + { + _nexpartService = nexpartService; + _partSourceContext = partSourceContext; + _fitmentContext = fitmentContext; + } + + public async Task Run(CancellationToken token, params string[] arguments) + { + IList rows = new List { + "\"Line Code\", \"Part Number\", \"Image URL(s)\"" + }; + + IList parts = await _fitmentContext.Parts.ToListAsync(); + string oldLineCode = string.Empty; + IList mappings = new List(); + + foreach (Data.Models.Part part in parts) + { + if (part.LineCode != oldLineCode) + { + mappings = await _fitmentContext.DcfMappings.Where(d => d.LineCode == part.LineCode).ToListAsync(); + } + ; + + foreach (DcfMapping mapping in mappings) + { + SmartPageDataSearch dataSearch = new SmartPageDataSearch + { + Items = new Item[] + { + new Item + { + MfrCode = mapping.WhiCode, + PartNumber = part.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($"\"{part.LineCode}\", \"{part.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/UpdateBulbFitment.cs b/PartSource.Automation/Jobs/POC/UpdateBulbFitment.cs index 6c0fddf..b2275b4 100644 --- a/PartSource.Automation/Jobs/POC/UpdateBulbFitment.cs +++ b/PartSource.Automation/Jobs/POC/UpdateBulbFitment.cs @@ -68,7 +68,7 @@ namespace PartSource.Automation.Jobs.POC ApplicationSearchResponse response = await _nexpartService.SendRequest(applicationSearch); if (response.ResponseBody != null) { - foreach (App app in response.ResponseBody.App) + foreach (App app in ((Apps)response.ResponseBody).App) { try { diff --git a/PartSource.Automation/Jobs/POC/UpdateWiperFitment.cs b/PartSource.Automation/Jobs/POC/UpdateWiperFitment.cs index 68d58d4..e1c582b 100644 --- a/PartSource.Automation/Jobs/POC/UpdateWiperFitment.cs +++ b/PartSource.Automation/Jobs/POC/UpdateWiperFitment.cs @@ -68,7 +68,7 @@ namespace PartSource.Automation.Jobs.POC ApplicationSearchResponse response = await _nexpartService.SendRequest(applicationSearch); if (response.ResponseBody != null) { - foreach (App app in response.ResponseBody.App) + foreach (App app in ((Apps)response.ResponseBody).App) { try { diff --git a/PartSource.Automation/Jobs/PartsSync.cs b/PartSource.Automation/Jobs/PartsSync.cs new file mode 100644 index 0000000..34e1cee --- /dev/null +++ b/PartSource.Automation/Jobs/PartsSync.cs @@ -0,0 +1,89 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PartSource.Automation.Models.Jobs; +using PartSource.Automation.Services; +using PartSource.Data.Contexts; +using PartSource.Data.Models; +using Ratermania.Automation.Interfaces; +using Ratermania.Shopify; +using Ratermania.Shopify.Resources; +using Ratermania.Shopify.Resources.Enums; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mail; +using System.Threading; +using System.Threading.Tasks; + +namespace PartSource.Automation.Jobs +{ + public class PartsSync : IAutomationJob + { + private readonly ILogger _logger; + private readonly FitmentContext _fitmentContext; + private readonly ShopifyClient _shopifyClient; + + public PartsSync(ILogger logger, FitmentContext fitmentContext, ShopifyClient shopifyClient) + { + _logger = logger; + _fitmentContext = fitmentContext; + _shopifyClient = shopifyClient; + } + + public async Task Run(CancellationToken token, params string[] arguments) + { + IEnumerable products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 } }); + + while (products != null && products.Any()) + { + + foreach (Product product in products) + { + try + { + IEnumerable metafields = await _shopifyClient.Metafields.Get(new Dictionary { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } }); + Part part = new Part + { + LineCode = metafields.FirstOrDefault(m => m.Key == "custom_label_0")?.Value ?? string.Empty, + PartNumber = metafields.FirstOrDefault(m => m.Key == "custom_label_1")?.Value ?? string.Empty, + Sku = product.Variants[0].Sku // They know we can't do fitment for variants + }; + + // part.PartNumber = part.PartNumber.Replace("-", string.Empty); + + if ( + string.IsNullOrEmpty(part.LineCode) + || string.IsNullOrEmpty(part.PartNumber) + || int.TryParse(part.LineCode, out _)) //If the line code is numeric, it cannot have fitment data associated with it. + { + continue; + } + + Part? existing = await _fitmentContext.Parts.FirstOrDefaultAsync(p => p.Sku == part.Sku); + if (existing == null) + { + await _fitmentContext.Parts.AddAsync(part); + await _fitmentContext.SaveChangesAsync(); + } + } + + catch (Exception ex) + { + _logger.LogInformation(ex.Message); + } + } + + try + { + products = await _shopifyClient.Products.GetNext(); + } + + catch (Exception ex) + { + _logger.LogInformation(ex.Message); + } + } + } + } +} \ No newline at end of file diff --git a/PartSource.Automation/Jobs/ProcessWhiFitment.cs b/PartSource.Automation/Jobs/ProcessWhiFitment.cs index 5a6f482..44e601c 100644 --- a/PartSource.Automation/Jobs/ProcessWhiFitment.cs +++ b/PartSource.Automation/Jobs/ProcessWhiFitment.cs @@ -56,24 +56,25 @@ namespace PartSource.Automation.Jobs fileGroups.Enqueue(fileGroup); } - Task[] taskArray = new Task[8]; - + Task[] taskArray = new Task[18]; for (int i = 0; i < taskArray.Length; i++) { taskArray[i] = Task.Factory.StartNew(() => { while (fileGroups.TryDequeue(out IGrouping fileGroup)) { + string tableName = string.Empty; + foreach (FileInfo fileInfo in fileGroup) { try { string filename = Decompress(fileInfo); - string tableName = fileInfo.Name.Substring(0, fileInfo.Name.IndexOf('.')); + DataTable dataTable = GetDataTable(filename, out tableName); - DataTable dataTable = GetDataTable(filename); + string tempTable = $"Fitment_{Guid.NewGuid():N}_{tableName}"; - _whiSeoService.BulkCopyFitment(dataTable, tableName); + _whiSeoService.BulkCopyFitment(dataTable, tempTable); _logger.LogInformation($"Copied {fileInfo.Name} to the database."); File.Delete(filename); @@ -85,19 +86,18 @@ namespace PartSource.Automation.Jobs } } - string fitmentTable = fileGroup.Key.Substring(0, fileGroup.Key.IndexOf('.')); - _whiSeoService.CreateFitmentTable(fitmentTable); + _whiSeoService.CreateFitmentTable(tableName); - _logger.LogInformation($"Created fitment table for part group {fitmentTable}."); + _logger.LogInformation($"Created fitment table for part group {tableName}."); } }); } Task.WaitAll(taskArray); - _whiSeoService.SaveNotes(_noteDictionary); - //_whiSeoService.CreateFitmentView(); + + _whiSeoService.CreateFitmentView(); } public string Decompress(FileInfo fileInfo) @@ -112,8 +112,10 @@ namespace PartSource.Automation.Jobs return decompressedFile; } - private DataTable GetDataTable(string filename) + private DataTable GetDataTable(string filename, out string lineCode) { + lineCode = string.Empty; + using DataTable dataTable = new DataTable(); dataTable.Columns.Add("LineCode", typeof(string)); dataTable.Columns.Add("PartNumber", typeof(string)); @@ -121,8 +123,9 @@ namespace PartSource.Automation.Jobs dataTable.Columns.Add("EngineConfigId", typeof(int)); dataTable.Columns.Add("Position", typeof(string)); dataTable.Columns.Add("FitmentNoteHash", typeof(string)); + dataTable.Columns.Add("PartTerminologyId", typeof(int)); - using StreamReader reader = new StreamReader(filename); + using StreamReader reader = new StreamReader(filename); string line = reader.ReadLine(); // Burn the header row while (reader.Peek() > 0) @@ -135,7 +138,7 @@ namespace PartSource.Automation.Jobs columns[i] = columns[i].Replace("\"", string.Empty); } - string lineCode = Regex.Replace(columns[0], "[^a-zA-Z0-9]", string.Empty).Trim(); + 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(); @@ -149,10 +152,11 @@ namespace PartSource.Automation.Jobs if (!string.IsNullOrEmpty(lineCode) && !string.IsNullOrEmpty(partNumber) + && int.TryParse(columns[2], out int partTerminologyId) && 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 }); + dataTable.Rows.Add(new object[] { lineCode, partNumber, baseVehicleId, engineConfigId, position, noteTextHash, partTerminologyId }); } } diff --git a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs index 2c0352d..07cf3ca 100644 --- a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs +++ b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs @@ -1,26 +1,21 @@ -using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +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; using Ratermania.Automation.Interfaces; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Data; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; namespace PartSource.Automation.Jobs { - public class ProcessWhiVehicles : IAutomationJob + public class ProcessWhiVehicles : IAutomationJob { private readonly ILogger _logger; private readonly WhiSeoService _whiSeoService; diff --git a/PartSource.Automation/Jobs/UpdateFitment.cs b/PartSource.Automation/Jobs/UpdateFitment.cs index 2de202a..b08ae3b 100644 --- a/PartSource.Automation/Jobs/UpdateFitment.cs +++ b/PartSource.Automation/Jobs/UpdateFitment.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using PartSource.Automation.Services; @@ -13,6 +14,7 @@ using PartSource.Data.Models; using Ratermania.Automation.Interfaces; using Ratermania.Shopify; using Ratermania.Shopify.Resources; +using System.Web; namespace PartSource.Automation.Jobs { @@ -36,216 +38,148 @@ namespace PartSource.Automation.Jobs public async Task Run(CancellationToken token, params string[] arguments) { - IEnumerable products = null; + IList productTypes = await _fitmentContext.ProductTypes + .Where(p => p.Active) + .Select(p => HttpUtility.UrlEncode(p.Name)) + .ToListAsync(); - try + foreach (string productType in productTypes) { - //products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 } }); - products = new List - { - await _shopifyClient.Products.GetById(7285013446703) - }; - } + _logger.LogInformation("Processing {productType}", HttpUtility.UrlDecode(productType)); - catch (Exception ex) - { - _logger.LogError("Failed to get products from Shopify", ex); - throw; - } - - int i = 1; - - while (products != null && products.Any()) - { - foreach (Product product in products) - { - ImportData importData = null; - - try - { - IEnumerable metafields = await _shopifyClient.Metafields.Get(new Dictionary { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } }); - importData = new ImportData - { - LineCode = metafields.FirstOrDefault(m => m.Key == "custom_label_0")?.Value ?? string.Empty, - PartNumber = metafields.FirstOrDefault(m => m.Key == "custom_label_1")?.Value ?? string.Empty, - VariantSku = product.Variants[0].Sku // They know we can't do fitment for variants - }; - - bool isFitment = false; - string bodyHtml = string.IsNullOrEmpty(product.BodyHtml) - ? "
    " - : product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("") + "".Length); - - IList vehicles = await _vehicleFitmentService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); - IList vehicleIdFitment = _vehicleFitmentService.GetVehicleIdFitment(vehicles); - - if (vehicleIdFitment.Count == 0) - { - continue; - } - string vehicleIdString = string.Join(',', vehicleIdFitment.Select(j => $"v{j}")); - - bodyHtml += $"
    {vehicleIdString}
    "; - - isFitment = true; - - string json = JsonConvert.SerializeObject(vehicleIdFitment); - if (json.Length < 100000) - { - Metafield vehicleMetafield = new Metafield - { - Namespace = "fitment", - Key = "ids", - Value = json, - Type = "json_string", - OwnerResource = "product", - OwnerId = product.Id - }; - - await _shopifyClient.Metafields.Add(vehicleMetafield); - } - - else - { - _logger.LogWarning($"Vehicle ID fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); - continue; - } - - IList ymmFitment = _vehicleFitmentService.GetYmmFitment(vehicles); - if (ymmFitment.Count > 0) - { - isFitment = true; - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine(""); - - foreach (string fitment in ymmFitment) - { - try - { - string[] parts = fitment.Split(' ', 2); - - stringBuilder.AppendLine($""); - } - - catch - { - // This is still a POC at this point. Oh well... - } - } - - stringBuilder.AppendLine("
    This Part Fits
    {parts[1]}{parts[0].Replace("-", ", ")}
    "); - - bodyHtml += $"
    {stringBuilder.ToString()}
    "; - - json = JsonConvert.SerializeObject(ymmFitment); - if (json.Length < 100000) - { - Metafield ymmMetafield = new Metafield - { - Namespace = "fitment", - Key = "seo", - Value = json, - Type = "json_string", - OwnerResource = "product", - OwnerId = product.Id - }; - - await _shopifyClient.Metafields.Add(ymmMetafield); - } - - else - { - _logger.LogWarning($"Year/make/model fitment data for SKU {importData.VariantSku} is too large for Shopify and cannot be added."); - continue; - } - } - - Metafield isFitmentMetafield = new Metafield - { - Namespace = "Flags", - Key = "IsFitment", - Value = isFitment.ToString(), - Type = "string", - OwnerResource = "product", - OwnerId = product.Id - }; - - await _shopifyClient.Metafields.Add(isFitmentMetafield); - - //Metafield lineCodeMetafield = new Metafield - //{ - // Namespace = "google", - // Key = "custom_label_0", - // Value = importData.LineCode, - // Type = "string", - // OwnerResource = "product", - // OwnerId = product.Id - //}; - - //await _shopifyClient.Metafields.Add(lineCodeMetafield); - - //Metafield partNumberMetafield = new Metafield - //{ - // Namespace = "google", - // Key = "custom_label_1", - // Value = importData.PartNumber, - // Type = "string", - // OwnerResource = "product", - // OwnerId = product.Id - //}; - - //await _shopifyClient.Metafields.Add(partNumberMetafield); - - List tags = new List(); - - for (int j = 0; j < vehicleIdFitment.Count; j += 25) - { - tags.Add(string.Join('-', vehicleIdFitment.Skip(j).Take(25).Select(j => $"v{j}"))); - } - - tags.AddRange(ymmFitment); - - if (tags.Count > 249) - { - tags = tags.Take(249).ToList(); - } - - string zzzIsFitment = isFitment - ? "zzzIsFitment=true" - : "zzzIsFitment=false"; - - tags.Add(zzzIsFitment); - - product.Tags = string.Join(',', tags); - product.BodyHtml = bodyHtml; - await _shopifyClient.Products.Update(product); - - importData.IsFitment = isFitment; - importData.UpdatedAt = DateTime.Now; - importData.UpdateType = "Fitment"; - } - - catch (Exception ex) - { - _logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex); - } - - } + IEnumerable products = null; try { - Console.WriteLine(i); - - _partSourceContext.SaveChanges(); - products = await _shopifyClient.Products.GetNext(); - - i++; + products = await _shopifyClient.Products.Get(new Dictionary { { "limit", 250 }, { "product_type", productType } }); + //products = new List + //{ + // await _shopifyClient.Products.GetById(7458071052335) + //}; } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to get the next set of products. Retrying"); - products = await _shopifyClient.Products.GetPrevious(); + _logger.LogError("Failed to get products from Shopify", ex); + throw; + } + + while (products != null && products.Any()) + { + foreach (Product product in products) + { + ImportData importData = null; + bool isFitment = false; + + try + { + IEnumerable metafields = await _shopifyClient.Metafields.Get(new Dictionary { { "metafield[owner_id]", product.Id }, { "metafield[owner_resource]", "product" } }); + importData = new ImportData + { + LineCode = metafields.FirstOrDefault(m => m.Key == "custom_label_0")?.Value ?? string.Empty, + PartNumber = metafields.FirstOrDefault(m => m.Key == "custom_label_1")?.Value ?? string.Empty, + VariantSku = product.Variants[0].Sku // They know we can't do fitment for variants + }; + + importData.PartNumber = importData.PartNumber.Replace("-", string.Empty); + + //If the line code is numeric, it cannot have fitment data associated with it. + if (int.TryParse(importData.LineCode, out _)) + { + continue; + } + + // Extract Partsource bullet points if present. + string bodyHtml = string.IsNullOrEmpty(product.BodyHtml) + ? string.Empty + : product.BodyHtml.Substring(0, product.BodyHtml.IndexOf("") + "".Length); + + IList vehicles = _vehicleFitmentService.GetVehiclesForPart(importData.PartNumber, importData.LineCode); + IList vehicleIdFitment = _vehicleFitmentService.GetVehicleIdFitment(vehicles); + + if (!vehicleIdFitment.Any()) + { + Console.WriteLine($"No fitment data for {importData.LineCode} {importData.PartNumber}"); + continue; + } + + string vehicleIdString = string.Join(',', vehicleIdFitment.Select(j => $"v{j}")); + + bodyHtml += $"
    {vehicleIdString}
    "; + isFitment = true; + + IList ymmFitment = _vehicleFitmentService.GetYmmFitment(vehicles); + if (ymmFitment.Count > 0) + { + isFitment = true; + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(""); + + foreach (string fitment in ymmFitment) + { + try + { + string[] parts = fitment.Split(' ', 2); + stringBuilder.AppendLine($""); + } + + catch (Exception ex) + { + _logger.LogWarning(ex, "YMM fitment for {fitment} was in an invalid format", fitment); + } + } + + stringBuilder.AppendLine("
    This Part Fits
    {parts[1]}{parts[0].Replace("-", ", ")}
    "); + + bodyHtml += $"
    {stringBuilder}
    "; + } + + List tags = new List(); + + for (int j = 0; j < vehicleIdFitment.Count; j += 25) + { + tags.Add(string.Join('-', vehicleIdFitment.Skip(j).Take(25).Select(j => $"v{j}"))); + } + + tags.AddRange(ymmFitment); + + if (tags.Count > 249) + { + tags = tags.Take(249).ToList(); + } + + string zzzIsFitment = isFitment + ? "zzzIsFitment=true" + : "zzzIsFitment=false"; + + tags.Add(zzzIsFitment); + + product.Tags = string.Join(',', tags); + product.BodyHtml = bodyHtml; + await _shopifyClient.Products.Update(product); + + importData.IsFitment = isFitment; + importData.UpdatedAt = DateTime.Now; + importData.UpdateType = "Fitment"; + } + + catch (Exception ex) + { + _logger.LogError($"Failed to updated fitment data for SKU {importData?.VariantSku} - {ex.Message}", ex); + } + + } + try + { + products = await _shopifyClient.Products.GetNext(); + + } + + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to get the next set of products. Retrying"); + products = await _shopifyClient.Products.GetPrevious(); + } } } } diff --git a/PartSource.Automation/Models/Configuration/FtpConfiguration.cs b/PartSource.Automation/Models/Configuration/FtpConfiguration.cs index 7feee23..cceef98 100644 --- a/PartSource.Automation/Models/Configuration/FtpConfiguration.cs +++ b/PartSource.Automation/Models/Configuration/FtpConfiguration.cs @@ -16,5 +16,7 @@ namespace PartSource.Automation.Models.Configuration public string Username { get; set; } public string Password { get; set; } + + public int Port { get; set; } } } diff --git a/PartSource.Automation/Services/VehicleFitmentService.cs b/PartSource.Automation/Services/VehicleFitmentService.cs index a4355ba..34a6fc5 100644 --- a/PartSource.Automation/Services/VehicleFitmentService.cs +++ b/PartSource.Automation/Services/VehicleFitmentService.cs @@ -91,7 +91,9 @@ namespace PartSource.Automation.Services public IList GetVehicleIdFitment(IList vehicles) { - return vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToArray(); + return vehicles != null + ? vehicles.Select(v => v.VehicleToEngineConfigId).Distinct().ToArray() + : new List(); } public async Task> GetVehiclesForPart(string partNumber, string lineCode, int maxVehicles = 0) diff --git a/PartSource.Automation/Services/WhiSeoService.cs b/PartSource.Automation/Services/WhiSeoService.cs index 78cf932..0f35c91 100644 --- a/PartSource.Automation/Services/WhiSeoService.cs +++ b/PartSource.Automation/Services/WhiSeoService.cs @@ -5,167 +5,154 @@ using Microsoft.Extensions.Logging; using PartSource.Automation.Models.Configuration; using PartSource.Automation.Models.Enums; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Text; +using System.Threading.Tasks; namespace PartSource.Automation.Services { - public class WhiSeoService - { - private readonly FtpService _ftpService; - private readonly string _connectionString; - private readonly ILogger _logger; + public class WhiSeoService + { + private readonly FtpService _ftpService; + private readonly string _connectionString; + private readonly ILogger _logger; - public WhiSeoService(IConfiguration configuration, ILogger logger) - { - FtpConfiguration ftpConfiguration = configuration.GetSection("FtpServers:WhiConfiguration").Get(); - _ftpService = new FtpService(ftpConfiguration); + public WhiSeoService(IConfiguration configuration, ILogger logger) + { + FtpConfiguration ftpConfiguration = configuration.GetSection("FtpServers:WhiConfiguration").Get(); + _ftpService = new FtpService(ftpConfiguration); - _connectionString = configuration.GetConnectionString("FitmentDatabase"); + _connectionString = configuration.GetConnectionString("FitmentDatabase"); - _logger = logger; - } + _logger = logger; + } - public void GetFiles(SeoDataType seoDataType) - { - string seoDataTypeString = seoDataType.ToString().ToLowerInvariant(); - string[] files = _ftpService.ListFiles(seoDataTypeString); + public void GetFiles(SeoDataType seoDataType) + { + string seoDataTypeString = seoDataType.ToString().ToLowerInvariant(); + + // WHI changed the transfer protocol to SFTP and then messed with the directory structure. + // Since fitment isn't really all that automated anyway, just download the files manually with an SFTP client. + Console.WriteLine($"Remember to manually download the {seoDataTypeString} files with an SFTP client. Press any key to continue."); + Console.ReadLine(); + } - foreach (string file in files) - { - if (file.Contains(".csv")) - { - try - { - _ftpService.Download($"{seoDataTypeString}/{file}"); - _logger.LogInformation($"Finished downloading {file}."); - } + public void TruncateVehicleTable() + { + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); + using SqlCommand command = new SqlCommand($"truncate table dbo.Vehicle", connection); + command.ExecuteNonQuery(); + } - catch (Exception ex) - { - _logger.LogWarning($"Failed to download {file}, quitting", ex); - throw; - } - } - } - } + public void TruncateFitmentTables() + { + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); - public void TruncateVehicleTable() - { - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + using SqlCommand command = new SqlCommand($"exec DropFitmentTables", connection); + command.ExecuteNonQuery(); + } - using SqlCommand command = new SqlCommand($"truncate table dbo.Vehicle", connection); - command.ExecuteNonQuery(); - } + public void SaveNotes(IDictionary notes) + { + using DataTable dataTable = new DataTable(); + dataTable.Columns.Add("NoteText", typeof(string)); + dataTable.Columns.Add("Hash", typeof(string)); - public void TruncateFitmentTables() - { - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + foreach (KeyValuePair note in notes) + { - using SqlCommand command = new SqlCommand($"exec DropFitmentTables", connection); - command.ExecuteNonQuery(); - } + dataTable.Rows.Add(new string[] { note.Value, note.Key }); + } - public void SaveNotes(IDictionary notes) - { - using DataTable dataTable = new DataTable(); - dataTable.Columns.Add("NoteText", typeof(string)); - dataTable.Columns.Add("Hash", typeof(string)); + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); - foreach (KeyValuePair note in notes) - { + using SqlBulkCopy bulk = new SqlBulkCopy(connection) + { + DestinationTableName = $"FitmentNote", + BulkCopyTimeout = 14400 + }; - dataTable.Rows.Add(new string[] { note.Value, note.Key }); - } + bulk.WriteToServer(dataTable); + } - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + public void BulkCopyFitment(DataTable dataTable, string tableName) + { + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); - using SqlBulkCopy bulk = new SqlBulkCopy(connection) - { - DestinationTableName = $"FitmentNote", - BulkCopyTimeout = 14400 - }; + string sql = string.Empty; - bulk.WriteToServer(dataTable); - } + using SqlCommand command = new SqlCommand($"EXEC CreateFitmentTempTable @tableName = '{tableName}'", connection); + command.ExecuteNonQuery(); - public void BulkCopyFitment(DataTable dataTable, string tableName) - { - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + using SqlBulkCopy bulk = new SqlBulkCopy(connection) + { + DestinationTableName = $"FitmentTemp.{tableName}", + BulkCopyTimeout = 14400 + }; - string sql = string.Empty; + bulk.WriteToServer(dataTable); + } - using SqlCommand command = new SqlCommand($"EXEC CreateFitmentTempTable @tableName = '{tableName}'", connection); - command.ExecuteNonQuery(); + public void BulkCopyVehicle(DataTable dataTable, string tableName) + { + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); - using SqlBulkCopy bulk = new SqlBulkCopy(connection) - { - DestinationTableName = $"FitmentTemp.{tableName}", - BulkCopyTimeout = 14400 - }; + string sql = string.Empty; - bulk.WriteToServer(dataTable); - } + using SqlCommand command = new SqlCommand($"EXEC CreateVehicleTempTable @tableName = '{tableName}'", connection); + command.ExecuteNonQuery(); - public void BulkCopyVehicle(DataTable dataTable, string tableName) - { - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + using SqlBulkCopy bulk = new SqlBulkCopy(connection) + { + DestinationTableName = $"VehicleTemp.{tableName}", + BulkCopyTimeout = 14400 + }; - string sql = string.Empty; + bulk.WriteToServer(dataTable); + } - using SqlCommand command = new SqlCommand($"EXEC CreateVehicleTempTable @tableName = '{tableName}'", connection); - command.ExecuteNonQuery(); + public void CreateFitmentTable(string tableName) + { + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); - using SqlBulkCopy bulk = new SqlBulkCopy(connection) - { - DestinationTableName = $"VehicleTemp.{tableName}", - BulkCopyTimeout = 14400 - }; + using SqlCommand command = new SqlCommand($"exec CreateFitmentTable @tableName = '{tableName}'", connection); + command.CommandTimeout = 1800; + command.ExecuteNonQuery(); + } - bulk.WriteToServer(dataTable); - } + public void CreateFitmentView() + { + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); - public void CreateFitmentTable(string tableName) - { - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + using SqlCommand command = new SqlCommand($"exec CreateFitmentView", connection); + command.CommandTimeout = 1800; + command.ExecuteNonQuery(); - using SqlCommand command = new SqlCommand($"exec CreateFitmentTable @tableName = '{tableName}'", connection); - command.CommandTimeout = 1800; - command.ExecuteNonQuery(); - } + using SqlCommand command2 = new SqlCommand($"exec CreateFitmentIndexes", connection); + command.CommandTimeout = 3600; + command2.ExecuteNonQuery(); + } - public void CreateFitmentView() - { - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + public void CreateVehicleTable() + { + using SqlConnection connection = new SqlConnection(_connectionString); + connection.Open(); - 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() - { - using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); - - using SqlCommand command = new SqlCommand($"exec CreateVehicleTable", connection); - command.CommandTimeout = 1800; - command.ExecuteNonQuery(); - } - } + using SqlCommand command = new SqlCommand($"exec CreateVehicleTable", connection); + command.CommandTimeout = 1800; + command.ExecuteNonQuery(); + } + } } #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities \ No newline at end of file diff --git a/PartSource.Automation/appsettings.json b/PartSource.Automation/appsettings.json index 2fc2d0a..005fb59 100644 --- a/PartSource.Automation/appsettings.json +++ b/PartSource.Automation/appsettings.json @@ -1,8 +1,7 @@ { "ConnectionStrings": { - //"FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;Integrated Security=true;TrustServerCertificate=True", - //"FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;User ID=stageuser;Password=FXepK^cFYS|[H<;Encrypt=True;TrustServerCertificate=True;Connection Timeout=300", - "FitmentDatabase": "Data Source=localhost;User ID=stageuser;Password=FXepK^cFYS|[H<;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Intent=ReadWrite;Multi Subnet Failover=False", + "FitmentDatabase": "Data Source=localhost;Initial Catalog=WhiFitment;Integrated Security=true;TrustServerCertificate=true", + //"FitmentDatabase": "Server=tcp:ps-automation.eastus2.cloudapp.azure.com,1433;Initial Catalog=WhiFitment;User ID=sa;Password=GZ0`-ekd~[2u;Encrypt=True;TrustServerCertificate=True;Connection Timeout=300", "PartSourceDatabase": "Server=tcp:ps-whi.database.windows.net,1433;Initial Catalog=ps-whi-stage;Persist Security Info=False;User ID=ps-whi;Password=9-^*N5dw!6:|.5Q;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" }, "emailConfiguration": { @@ -16,19 +15,15 @@ "Username": "ps-ftp\\$ps-ftp", "Password": "ycvXptffBxqkBXW4vuRYqn4Zi1soCvnvMMolTe5HNSeAlcl3bAyJYtNhG579", "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" + "Destination": "C:\\Partsource.Automation\\Downloads", + "Port": 21 }, "WhiConfiguration": { "Username": "ctc_seo", - "Password": "be34hz64e4", + "Password": "YD3gtaQ5kPdtNKs", "Url": "ftp://ftp.whisolutions.com", - "Destination": "C:\\Partsource.Automation\\Downloads\\WHI" + "Destination": "C:\\Partsource.Automation\\Downloads\\WHI", + "Port": 3001 } }, "ssisConfiguration": { @@ -43,7 +38,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 ffd356b..a46169c 100644 --- a/PartSource.Data/Contexts/FitmentContext.cs +++ b/PartSource.Data/Contexts/FitmentContext.cs @@ -13,15 +13,19 @@ namespace PartSource.Data.Contexts public DbSet DcfMappings { get; set; } + public DbSet Parts { get; set; } + public DbSet Fitments { get; set; } public DbSet FitmentNotes { get; set; } + public DbSet ProductTypes { get; set; } + public DbSet Vehicles { get; set; } - public DbSet VehicleFitments { get; set; } + public DbSet VehicleFitments { get; set; } - public DbSet Wipers { get; set; } + public DbSet Wipers { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -30,9 +34,9 @@ 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(); + modelBuilder.Entity().HasNoKey(); - foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) + foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { entityType.SetTableName(entityType.ClrType.Name); } diff --git a/PartSource.Data/Contexts/PartSourceContext.cs b/PartSource.Data/Contexts/PartSourceContext.cs index 6fc0e76..1ec414e 100644 --- a/PartSource.Data/Contexts/PartSourceContext.cs +++ b/PartSource.Data/Contexts/PartSourceContext.cs @@ -26,7 +26,7 @@ namespace PartSource.Data.Contexts public DbSet PartPrices { get; set; } - public DbSet Parts { get; set; } + // public DbSet Parts { get; set; } public DbSet ShopifyChangelogs { get; set; } diff --git a/PartSource.Data/Dtos/VehicleFitmentDto.cs b/PartSource.Data/Dtos/VehicleFitmentDto.cs index 682137d..de558db 100644 --- a/PartSource.Data/Dtos/VehicleFitmentDto.cs +++ b/PartSource.Data/Dtos/VehicleFitmentDto.cs @@ -5,8 +5,15 @@ using System.Text; namespace PartSource.Data.Dtos { - public class VehicleFitmentDto : VehicleFitment - { + public class VehicleFitmentDto : VehicleFitment + { + public string PartDescription { get; set; } + + // May not be needed, but don't remove just yet public IList SubmodelNames { get; set; } + + public IList DriveTypes { get; set; } + + public IList Notes { get; set; } } -} +} \ No newline at end of file diff --git a/PartSource.Data/Models/ProductType.cs b/PartSource.Data/Models/ProductType.cs new file mode 100644 index 0000000..844b618 --- /dev/null +++ b/PartSource.Data/Models/ProductType.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 ProductType + { + [Key] + public string Name { get; set; } + + public bool Active { get; set; } + } +} diff --git a/PartSource.Data/Models/VehicleFitment.cs b/PartSource.Data/Models/VehicleFitment.cs index ed55446..7e39a58 100644 --- a/PartSource.Data/Models/VehicleFitment.cs +++ b/PartSource.Data/Models/VehicleFitment.cs @@ -1,4 +1,6 @@ -namespace PartSource.Data.Models +using Newtonsoft.Json; + +namespace PartSource.Data.Models { public class VehicleFitment { @@ -8,6 +10,7 @@ public string PartNumber { get; set; } + [JsonIgnore] public string NoteText { get; set; } public int Year { get; set; } diff --git a/PartSource.Data/Nexpart/Answer.cs b/PartSource.Data/Nexpart/Answer.cs new file mode 100644 index 0000000..f0b2166 --- /dev/null +++ b/PartSource.Data/Nexpart/Answer.cs @@ -0,0 +1,22 @@ +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(Namespace = "http://whisolutions.com/pss/common/helper/parts")] + public class Answer + { + [XmlAttribute] + public int Id { get; set; } + + [XmlAttribute] + public int Count { get; set; } + + [XmlText] + public string Value { get; set; } + } +} diff --git a/PartSource.Data/Nexpart/ApplicationSearch.cs b/PartSource.Data/Nexpart/ApplicationSearch.cs index b4a3a29..174031c 100644 --- a/PartSource.Data/Nexpart/ApplicationSearch.cs +++ b/PartSource.Data/Nexpart/ApplicationSearch.cs @@ -31,9 +31,15 @@ namespace PartSource.Data.Nexpart public bool SecondaryDCF => true; [XmlElement(Order = 6)] - public Criterion[] Criterion { get; set; } + public string[] AppOption { get; set; } [XmlElement(Order = 7)] + public Criterion[] Criterion { get; set; } + + [XmlElement(Order = 8)] public string GroupBy { get; set; } + + [XmlElement(Order = 9)] + public string QuestionOption { get; set; } } } diff --git a/PartSource.Data/Nexpart/ApplicationSearchResponse.cs b/PartSource.Data/Nexpart/ApplicationSearchResponse.cs index 1b20c86..f5a4a6f 100644 --- a/PartSource.Data/Nexpart/ApplicationSearchResponse.cs +++ b/PartSource.Data/Nexpart/ApplicationSearchResponse.cs @@ -9,13 +9,13 @@ using PartSource.Data.Nexpart.Interfaces; namespace PartSource.Data.Nexpart { [XmlType(AnonymousType = true, Namespace = "http://whisolutions.com/pss/common/model/parts")] - public class ApplicationSearchResponse : IResponseElement + public class ApplicationSearchResponse : IResponseElement { - [XmlElement] public PSResponseHeader PSResponseHeader { get; set; } - [XmlElement(ElementName = nameof(Apps))] - public Apps ResponseBody { get; set; } + [XmlElement(ElementName = nameof(Apps), Namespace = "http://whisolutions.com/pss/common/model/parts", Type = typeof(Apps))] + [XmlElement(ElementName = nameof(Questions), Namespace = "http://whisolutions.com/pss/common/model/parts", Type = typeof(Questions))] + public object ResponseBody { get; set; } } } diff --git a/PartSource.Data/Nexpart/PSRequestHeader.cs b/PartSource.Data/Nexpart/PSRequestHeader.cs index c42f82c..7f3a7ae 100644 --- a/PartSource.Data/Nexpart/PSRequestHeader.cs +++ b/PartSource.Data/Nexpart/PSRequestHeader.cs @@ -13,7 +13,7 @@ namespace PartSource.Data.Nexpart { public PSRequestHeader() { - this.SvcVersion = "1.0"; + this.SvcVersion = "2.0"; this.ReturnWarnings = "true"; } diff --git a/PartSource.Data/Nexpart/Question.cs b/PartSource.Data/Nexpart/Question.cs new file mode 100644 index 0000000..45ca2fc --- /dev/null +++ b/PartSource.Data/Nexpart/Question.cs @@ -0,0 +1,25 @@ +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(Namespace = "http://whisolutions.com/pss/common/helper/parts")] + public class Question + { + [XmlAttribute(AttributeName = "Attrib")] + public string Attribute { get; set; } + + [XmlAttribute] + public int Count { get; set; } + + [XmlAttribute] + public string Text { get; set; } + + [XmlElement] + public Answer[] Answer { get; set; } + } +} diff --git a/PartSource.Data/Nexpart/Questions.cs b/PartSource.Data/Nexpart/Questions.cs new file mode 100644 index 0000000..a6d1914 --- /dev/null +++ b/PartSource.Data/Nexpart/Questions.cs @@ -0,0 +1,19 @@ +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(Namespace = "http://whisolutions.com/pss/common/model/parts")] + public class Questions + { + [XmlElement(Namespace = "http://whisolutions.com/pss/common/helper/parts")] + public Question[] Question { get; set; } + + [XmlAttribute] + public int NumApps { get; set; } + } +} diff --git a/PartSource.Services/FitmentService.cs b/PartSource.Services/FitmentService.cs index 6353f3d..264d6d0 100644 --- a/PartSource.Services/FitmentService.cs +++ b/PartSource.Services/FitmentService.cs @@ -1,13 +1,13 @@ 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 { @@ -35,21 +35,11 @@ namespace PartSource.Services ModelName = vf.ModelName, BaseVehicleId = vf.BaseVehicleId, EngineConfigId = vf.EngineConfigId, - VehicleToEngineConfigId = vf.VehicleToEngineConfigId + VehicleToEngineConfigId = vf.VehicleToEngineConfigId, + SubmodelName = vf.SubmodelName }) .FirstOrDefaultAsync(); - if (vehicleFitment == null) - { - return null; - } - - vehicleFitment.SubmodelNames = await _fitmentContext.VehicleFitments - .Where(vf => vf.BaseVehicleId == vehicleFitment.BaseVehicleId && vf.Sku == sku) - .Select(vf => vf.SubmodelName) - .Distinct() - .ToListAsync(); - return vehicleFitment; } diff --git a/PartSource.Services/NexpartService.cs b/PartSource.Services/NexpartService.cs index c09dc82..0bf411b 100644 --- a/PartSource.Services/NexpartService.cs +++ b/PartSource.Services/NexpartService.cs @@ -25,17 +25,20 @@ namespace PartSource.Services U content; string x = textWriter.ToString(); - - using (HttpClient client = new HttpClient()) + System.Diagnostics.Debug.WriteLine(x); + + using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "QjM4ODAyMzM3QjQxNEM2QTk4M0RFMjM0Mjk4Rjk4M0UtOUIzNUUxNzNBQUYxNEE2QjhCQjI2RjZDOUY2ODk1NDU6MkMzOUVCOTYtRDBBRS00QkVBLTlCMzItMUYyNTA5MDJGQTE0"); try { //HttpResponseMessage response = await client.PostAsync(ConfigurationManager.AppSettings["NexpartUrl"], (HttpContent)new StringContent(sb.ToString(), Encoding.UTF8, "text/xml")); - HttpResponseMessage response = await client.PostAsync("http://acespssprod.nexpart.com:8085/partselect/2.0/services/PartSelectService.PartSelectHttpSoap11Endpoint", new StringContent(textWriter.ToString(), Encoding.UTF8)); + HttpResponseMessage response = await client.PostAsync("http://acespssprod.nexpart.com:8085/partselect/2.0/services/PartSelectService.PartSelectHttpSoap11Endpoint", new StringContent(textWriter.ToString(), Encoding.UTF8, "text/xml")); Stream result = await response.Content.ReadAsStreamAsync(); + string str = await response.Content.ReadAsStringAsync(); - + System.Diagnostics.Debug.WriteLine(str); + content = (U)((Envelope)serializer.Deserialize(result)).Body.Content; ; } @@ -44,6 +47,7 @@ namespace PartSource.Services throw; } } + return content; } } diff --git a/PartSource.Services/PartService.cs b/PartSource.Services/PartService.cs index 4162a3d..fda4ffb 100644 --- a/PartSource.Services/PartService.cs +++ b/PartSource.Services/PartService.cs @@ -12,26 +12,28 @@ namespace PartSource.Services { public class PartService { - private readonly PartSourceContext _context; + private readonly PartSourceContext _partSourceContext; + private readonly FitmentContext _fitmentContext; - public PartService(PartSourceContext context) + public PartService(PartSourceContext partSourceContext, FitmentContext fitmentContext) { - _context = context; + _partSourceContext = partSourceContext; + _fitmentContext = fitmentContext; } public async Task GetInventory(int sku, int storeNumber) { - return await _context.PartAvailabilities.FirstOrDefaultAsync(s => s.Store == storeNumber && s.SKU == sku); + return await _partSourceContext.PartAvailabilities.FirstOrDefaultAsync(s => s.Store == storeNumber && s.SKU == sku); } public async Task GetPartBySku(string sku) { - return await _context.Parts.SingleOrDefaultAsync(p => p.Sku == sku); + return await _fitmentContext.Parts.SingleOrDefaultAsync(p => p.Sku == sku); } public async Task> GetDcfMapping(string partsourceLineCode) { - return await _context.DcfMappings + return await _fitmentContext.DcfMappings .Where(dcf => dcf.LineCode == partsourceLineCode) .ToListAsync(); }