diff --git a/PartSource.Api/Startup.cs b/PartSource.Api/Startup.cs index af62368..b555ea1 100644 --- a/PartSource.Api/Startup.cs +++ b/PartSource.Api/Startup.cs @@ -50,16 +50,14 @@ namespace PartSource.Api }); services.AddAutoMapper(typeof(PartSourceProfile)); - + + services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddCors(o => o.AddPolicy("Default", builder => { builder.AllowAnyOrigin() diff --git a/PartSource.Api/appsettings.json b/PartSource.Api/appsettings.json index fe5b261..7059cbb 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" "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" }, 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/POC/BulkUpdateInventory.cs b/PartSource.Automation/Jobs/POC/BulkUpdateInventory.cs new file mode 100644 index 0000000..5b60373 --- /dev/null +++ b/PartSource.Automation/Jobs/POC/BulkUpdateInventory.cs @@ -0,0 +1,100 @@ +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 Microsoft.Extensions.Logging; +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; + private readonly ILogger _logger; + + public BulkUpdateInventory(IConfiguration configuration, ILogger logger) + { + 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 Full") > -1) + .OrderByDescending(f => f.Modified) + .FirstOrDefault(); + + if (lastUploadedFile == null) + { + _logger.LogInformation($"No full inventory file available."); + return; + } + + string file = _ftpService.Download(lastUploadedFile.Filename); + + DataTable dataTable = GetDataTable(file); + + using SqlConnection connection = new SqlConnection("Server=tcp:ps-whi.database.windows.net,1433;Initial Catalog=ps-whi-test;Persist Security Info=False;User ID=ps-whi;Password=9-^*N5dw!6:|.5Q;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"); + connection.Open(); + + using SqlCommand command = new SqlCommand("TRUNCATE TABLE PartAvailability", connection); + await command.ExecuteNonQueryAsync(); + + using SqlBulkCopy bulk = new SqlBulkCopy(connection) + { + DestinationTableName = "PartAvailability", + BulkCopyTimeout = 14400 + }; + + bulk.WriteToServer(dataTable); + + _ftpService.Delete(lastUploadedFile.Filename); + + 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/PartialInventoryUpdate.cs b/PartSource.Automation/Jobs/POC/PartialInventoryUpdate.cs new file mode 100644 index 0000000..ff6286e --- /dev/null +++ b/PartSource.Automation/Jobs/POC/PartialInventoryUpdate.cs @@ -0,0 +1,83 @@ +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 Microsoft.Extensions.Logging; +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 PartialInventoryUpdate : IAutomationJob + { + private readonly FtpService _ftpService; + private readonly ILogger _logger; + + public PartialInventoryUpdate(IConfiguration configuration, ILogger logger) + { + 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 Partial") > -1) + .OrderByDescending(f => f.Modified) + .FirstOrDefault(); + + if (lastUploadedFile == null) + { + _logger.LogInformation($"No partial inventory file available."); + return; + } + + _logger.LogInformation("Processing {filename}", lastUploadedFile.Filename); + + string file = _ftpService.Download($"{lastUploadedFile.Filename}"); + + using SqlConnection connection = new SqlConnection("Server=tcp:ps-whi.database.windows.net,1433;Initial Catalog=ps-whi-test;Persist Security Info=False;User ID=ps-whi;Password=9-^*N5dw!6:|.5Q;MultipleActiveResultSets=True;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"); + connection.Open(); + + using StreamReader reader = new StreamReader(file); + 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)) + { + using SqlCommand sqlCommand = new SqlCommand("UPDATE PartAvailability 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(); + } + } + + _ftpService.Delete(lastUploadedFile.Filename); + + return; + } + } +} diff --git a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs index e6558f7..07cf3ca 100644 --- a/PartSource.Automation/Jobs/ProcessWhiVehicles.cs +++ b/PartSource.Automation/Jobs/ProcessWhiVehicles.cs @@ -7,6 +7,7 @@ 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; @@ -40,7 +41,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/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/Models/Ftp/FtpFileInfo.cs b/PartSource.Automation/Models/Ftp/FtpFileInfo.cs new file mode 100644 index 0000000..7c76489 --- /dev/null +++ b/PartSource.Automation/Models/Ftp/FtpFileInfo.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PartSource.Automation.Models.Ftp +{ + public class FtpFileInfo + { + public string Filename { get; set; } + + public DateTime Modified { get; set; } + + public long Size { get; set; } + + public FtpFileType FileType { get; set; } + } +} diff --git a/PartSource.Automation/Models/Ftp/FtpFileType.cs b/PartSource.Automation/Models/Ftp/FtpFileType.cs new file mode 100644 index 0000000..2c9dcf7 --- /dev/null +++ b/PartSource.Automation/Models/Ftp/FtpFileType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PartSource.Automation.Models.Ftp +{ + public enum FtpFileType + { + File, + Directory + } +} diff --git a/PartSource.Automation/Program.cs b/PartSource.Automation/Program.cs index ac3d679..9b3b2a3 100644 --- a/PartSource.Automation/Program.cs +++ b/PartSource.Automation/Program.cs @@ -1,5 +1,4 @@ -using AutoMapper; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -7,12 +6,10 @@ using Microsoft.Extensions.Logging; using PartSource.Automation.Jobs; using PartSource.Automation.Jobs.POC; using PartSource.Automation.Services; -using PartSource.Data; using PartSource.Data.AutoMapper; using PartSource.Data.Contexts; using PartSource.Services; using Ratermania.Automation.DependencyInjection; -using Ratermania.Automation.Logging; using Ratermania.Shopify.DependencyInjection; using System; using System.IO; @@ -20,50 +17,51 @@ using System.Threading.Tasks; namespace PartSource.Automation { - class Program - { - static async Task Main(string[] args){ - try - { - using IHost host = CreateHostBuilder().Build(); + class Program + { + static async Task Main(string[] args) + { + try + { + using IHost host = CreateHostBuilder().Build(); - await host.StartAsync(); - } + await host.StartAsync(); + } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - throw; - } - } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + throw; + } + } - private static IHostBuilder CreateHostBuilder() - { - return Host.CreateDefaultBuilder() - .ConfigureAppConfiguration(builder => - { - string environment = Environment.GetEnvironmentVariable("AUTOMATION_ENVIRONMENT"); + private static IHostBuilder CreateHostBuilder() + { + return Host.CreateDefaultBuilder() + .ConfigureAppConfiguration(builder => + { + string environment = Environment.GetEnvironmentVariable("AUTOMATION_ENVIRONMENT"); - builder.SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true); - }) - .ConfigureServices((builder, services) => - { - services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("PartSourceDatabase"), opts => opts.EnableRetryOnFailure()) - ) + builder.SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true); + }) + .ConfigureServices((builder, services) => + { + services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("PartSourceDatabase"), opts => opts.EnableRetryOnFailure()) + ) - .AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("FitmentDatabase"), opts => - { - opts.EnableRetryOnFailure(); - opts.CommandTimeout(600); - }) - ) + .AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("FitmentDatabase"), opts => + { + opts.EnableRetryOnFailure(); + opts.CommandTimeout(600); + }) + ) - .AddShopify(options => - { + .AddShopify(options => + { options.ApiKey = builder.Configuration["Shopify:ApiKey"]; options.ApiSecret = builder.Configuration["Shopify:ApiSecret"]; options.ApiVersion = "2022-10"; @@ -75,56 +73,60 @@ namespace PartSource.Automation //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")) - //) + .AddAutomation(options => + { + options.HasBaseInterval(new TimeSpan(0, 5, 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(1, 0, 0)) + .StartsAt(DateTime.Today.AddHours(-27)) + ) + .HasJob(options => + options.HasInterval(new TimeSpan(1, 0, 0)) + .StartsAt(DateTime.Today.AddHours(-27).AddMinutes(30)) + ); - .HasJob(options => - options.HasInterval(new TimeSpan(24, 0, 0)) - // .StartsAt(DateTime.Today.AddMinutes(15)) - // ) - - //.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(DateTime.Today) + //); + //); + //.AddApiServer(); }) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() - .AddAutoMapper(typeof(PartSourceProfile)); - }) - .ConfigureLogging((builder, logging) => - { - logging.AddEventLog(); - logging.AddConsole(); + .AddAutoMapper(typeof(PartSourceProfile)); + }) + .ConfigureLogging((builder, logging) => + { + logging.AddEventLog(); + logging.AddConsole(); - // logging.AddProvider(new AutomationLoggerProvider()); - }); - } - } + // logging.AddProvider(new AutomationLoggerProvider()); + }); + } + } } diff --git a/PartSource.Automation/Services/FtpService.cs b/PartSource.Automation/Services/FtpService.cs index 25ed55a..3609296 100644 --- a/PartSource.Automation/Services/FtpService.cs +++ b/PartSource.Automation/Services/FtpService.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using PartSource.Automation.Models.Configuration; +using PartSource.Automation.Models.Ftp; using System; using System.Collections.Generic; using System.Configuration; @@ -9,44 +10,119 @@ using System.Threading.Tasks; namespace PartSource.Automation.Services { - public class FtpService - { - private readonly FtpConfiguration _ftpConfiguration; + public class FtpService + { + private readonly FtpConfiguration _ftpConfiguration; - public FtpService(FtpConfiguration ftpConfiguration) - { - _ftpConfiguration = ftpConfiguration; - } + public FtpService(FtpConfiguration ftpConfiguration) + { + _ftpConfiguration = ftpConfiguration; + } - public string[] ListFiles(string directory) - { - FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{directory}")); - request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); - request.Method = WebRequestMethods.Ftp.ListDirectory; + public IList ListFilesExtended(string directory = "") + { + FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{directory}")); + request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); + request.Method = WebRequestMethods.Ftp.ListDirectoryDetails; - using FtpWebResponse response = (FtpWebResponse)request.GetResponse(); - using StreamReader reader = new StreamReader(response.GetResponseStream()); + using FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + using StreamReader reader = new StreamReader(response.GetResponseStream()); - string files = reader.ReadToEnd(); + IList files = new List(); + string[] fileStrings = reader.ReadToEnd().Split("\r\n"); - return files.Length > 0 - ? files.Split("\r\n") - : Array.Empty(); - } + foreach (string fileString in fileStrings) + { + if (string.IsNullOrEmpty(fileString)) + { + continue; + } - public void Download(string filename) - { - string file = $"{_ftpConfiguration.Destination}\\{filename.Replace("/", "\\")}"; + string dateString = fileString[..17]; + string[] sizeAndName = fileString[18..].TrimStart().Split(" ", 2); - FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{filename}")); - request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); - request.Method = WebRequestMethods.Ftp.DownloadFile; - - using FtpWebResponse response = (FtpWebResponse)request.GetResponse(); - using Stream responseStream = response.GetResponseStream(); - using FileStream fileStream = new FileStream($"{_ftpConfiguration.Destination}\\{filename.Replace("/", "\\")}", FileMode.Create); - - responseStream.CopyTo(fileStream); - } - } + if (sizeAndName[0].ToUpperInvariant().IndexOf("DIR") > -1) + { + files.Add(new FtpFileInfo + { + Modified = DateTime.Parse(fileString[..17]), + Size = 0, + Filename = sizeAndName[1].Trim(), + FileType = FtpFileType.Directory + }); + } + + else + { + files.Add(new FtpFileInfo + { + Modified = DateTime.Parse(fileString[..17]), + Size = long.Parse(sizeAndName[0]), + Filename = sizeAndName[1].Trim(), + FileType = FtpFileType.File + }); + } + } + + return files; + } + + public string[] ListFiles(string directory = "") + { + FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{directory}")); + request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); + request.Method = WebRequestMethods.Ftp.ListDirectory; + + using FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + using StreamReader reader = new StreamReader(response.GetResponseStream()); + + string files = reader.ReadToEnd(); + + return files.Length > 0 + ? files.Split("\r\n") + : Array.Empty(); + } + + public string Download(string filename, string destination = null) + { + FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{filename}")); + request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); + request.Method = WebRequestMethods.Ftp.DownloadFile; + + if (string.IsNullOrEmpty(destination)) + { + destination = _ftpConfiguration.Destination; + } + + if (Path.DirectorySeparatorChar == '\\') + { + filename = filename.Replace("/", "\\"); + } + + 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); + + responseStream.CopyTo(fileStream); + + return destination; + } + + public void Delete(string filename) + { + FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri($"{_ftpConfiguration.Url}/{filename}")); + request.Credentials = new NetworkCredential(_ftpConfiguration.Username, _ftpConfiguration.Password); + request.Method = WebRequestMethods.Ftp.DeleteFile; + + + using FtpWebResponse response = (FtpWebResponse)request.GetResponse(); + } + } } diff --git a/PartSource.Automation/Services/SsisService.cs b/PartSource.Automation/Services/SsisService.cs index 246227b..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 = "C:\\Program Files (x86)\\Microsoft SQL Server\\130\\DTS\\Binn\\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 7e7c59e..34a6fc5 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; @@ -95,7 +96,7 @@ namespace PartSource.Automation.Services : new List(); } - 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)) { @@ -104,9 +105,10 @@ namespace PartSource.Automation.Services 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))