From 41a7f57988581167e635c648a8aa65bb4220ecfd Mon Sep 17 00:00:00 2001 From: Tom Raterman Date: Thu, 29 May 2025 09:55:07 -0400 Subject: [PATCH] Add support for inventory timestamps --- .../Controllers/InventoryController.cs | 5 +- .../Jobs/BulkUpdateInventory.cs | 10 ++-- PartSource.Automation/Jobs/POC/ImageList.cs | 2 +- .../Jobs/PartialInventoryUpdate.cs | 51 +++++++++++++++---- PartSource.Automation/Program.cs | 2 +- PartSource.Data/Contexts/PartSourceContext.cs | 4 +- ...rtsAvailability.cs => PartAvailability.cs} | 15 ++---- PartSource.Services/PartService.cs | 2 +- 8 files changed, 60 insertions(+), 31 deletions(-) rename PartSource.Data/Models/{PartsAvailability.cs => PartAvailability.cs} (57%) diff --git a/PartSource.Api/Controllers/InventoryController.cs b/PartSource.Api/Controllers/InventoryController.cs index 3071a82..ab01a7e 100644 --- a/PartSource.Api/Controllers/InventoryController.cs +++ b/PartSource.Api/Controllers/InventoryController.cs @@ -23,7 +23,7 @@ namespace PartSource.Api.Controllers [Route("sku/{sku}/storeNumber/{storeNumber}")] public async Task GetInventory(int sku, int storeNumber) { - PartsAvailability inventory = await _inventoryService.GetInventory(sku, storeNumber); + PartAvailability inventory = await _inventoryService.GetInventory(sku, storeNumber); if (inventory == null) { @@ -36,7 +36,8 @@ namespace PartSource.Api.Controllers { StoreNumber = inventory.Store, Sku = sku, - Quantity = inventory.QTY + Quantity = inventory.QTY, + Updated = inventory.Updated ?? System.DateTime.MinValue } }); } diff --git a/PartSource.Automation/Jobs/BulkUpdateInventory.cs b/PartSource.Automation/Jobs/BulkUpdateInventory.cs index 8dfcfdc..29fd40d 100644 --- a/PartSource.Automation/Jobs/BulkUpdateInventory.cs +++ b/PartSource.Automation/Jobs/BulkUpdateInventory.cs @@ -53,7 +53,7 @@ namespace PartSource.Automation.Jobs connection.Open(); using SqlCommand command = new SqlCommand("TRUNCATE TABLE PartAvailability", connection); - await command.ExecuteNonQueryAsync(); + await command.ExecuteNonQueryAsync(token); using SqlBulkCopy bulk = new SqlBulkCopy(connection) { @@ -61,7 +61,7 @@ namespace PartSource.Automation.Jobs BulkCopyTimeout = 14400 }; - bulk.WriteToServer(dataTable); + await bulk.WriteToServerAsync(dataTable, token); _ftpService.Delete(lastUploadedFile.Filename); @@ -74,6 +74,7 @@ namespace PartSource.Automation.Jobs dataTable.Columns.Add("Store", typeof(int)); dataTable.Columns.Add("SKU", typeof(string)); dataTable.Columns.Add("QTY", typeof(int)); + dataTable.Columns.Add("Updated", typeof(DateTime)); using StreamReader reader = new StreamReader(filename); string line = reader.ReadLine(); // Burn the header row @@ -91,9 +92,10 @@ namespace PartSource.Automation.Jobs string sku = columns[1].Trim(); if (int.TryParse(columns[0], out int store) && !string.IsNullOrEmpty(sku) - && int.TryParse(columns[2], out int quantity)) + && int.TryParse(columns[2], out int quantity) + && DateTime.TryParse(columns[3], out DateTime updated)) { - dataTable.Rows.Add(new object[] { store, sku, quantity }); + dataTable.Rows.Add(new object[] { store, sku, quantity, updated }); } } diff --git a/PartSource.Automation/Jobs/POC/ImageList.cs b/PartSource.Automation/Jobs/POC/ImageList.cs index 48a54eb..3ba4d12 100644 --- a/PartSource.Automation/Jobs/POC/ImageList.cs +++ b/PartSource.Automation/Jobs/POC/ImageList.cs @@ -24,7 +24,7 @@ namespace PartSource.Automation.Jobs.POC private readonly PartSourceContext _partSourceContext; private readonly FitmentContext _fitmentContext; - public GetImageUrls(NexpartService nexpartService, PartSourceContext partSourceContext, FitmentContext fitmentContext) + public ImageList(NexpartService nexpartService, PartSourceContext partSourceContext, FitmentContext fitmentContext) { _nexpartService = nexpartService; _partSourceContext = partSourceContext; diff --git a/PartSource.Automation/Jobs/PartialInventoryUpdate.cs b/PartSource.Automation/Jobs/PartialInventoryUpdate.cs index 89ade32..92e74f0 100644 --- a/PartSource.Automation/Jobs/PartialInventoryUpdate.cs +++ b/PartSource.Automation/Jobs/PartialInventoryUpdate.cs @@ -32,6 +32,7 @@ namespace PartSource.Automation.Jobs _logger = logger; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities")] public async Task Run(CancellationToken token, params string[] arguments) { FtpFileInfo lastUploadedFile = _ftpService.ListFilesExtended() @@ -50,37 +51,67 @@ namespace PartSource.Automation.Jobs string file = _ftpService.Download($"{lastUploadedFile.Filename}"); using SqlConnection connection = new SqlConnection(_connectionString); - connection.Open(); + connection.Open(); using StreamReader reader = new StreamReader(file); string line = reader.ReadLine(); // Burn the header row + IDictionary parameters = new Dictionary(); + string command = string.Empty; + int i = 0; + while (reader.Peek() > 0) { line = reader.ReadLine(); string[] columns = line.Split("|"); - for (int i = 0; i < columns.Length; i++) + for (int j = 0; j < columns.Length; j++) { - columns[i] = columns[i].Replace("\"", string.Empty); + columns[j] = columns[j].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)) + && int.TryParse(columns[2], out int quantity) + && DateTime.TryParse(columns[3], out DateTime updated)) { - 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)); + command += $"UPDATE PartAvailability SET QTY = @qty_{i}, Updated = @updated_{i} WHERE SKU = @sku_{i} AND Store = @store_{i};"; - await sqlCommand.ExecuteNonQueryAsync(); + parameters.Add($"qty_{i}", quantity); + parameters.Add($"store_{i}", store); + parameters.Add($"sku_{i}", sku); + parameters.Add($"updated_{i}", updated); + + i++; + } + + if (i == 250) + { + using SqlCommand nested = new SqlCommand(command, connection); + foreach (KeyValuePair parameter in parameters) + { + nested.Parameters.Add(new SqlParameter(parameter.Key, parameter.Value)); + } + + await nested.ExecuteNonQueryAsync(token); + + parameters.Clear(); + command = string.Empty; + i = 0; } } + using SqlCommand sqlCommand = new SqlCommand(command, connection); + foreach (KeyValuePair parameter in parameters) + { + sqlCommand.Parameters.Add(new SqlParameter(parameter.Key, parameter.Value)); + } + + await sqlCommand.ExecuteNonQueryAsync(token); + _ftpService.Delete(lastUploadedFile.Filename); return; } - } + } } diff --git a/PartSource.Automation/Program.cs b/PartSource.Automation/Program.cs index 2b276df..a6ead01 100644 --- a/PartSource.Automation/Program.cs +++ b/PartSource.Automation/Program.cs @@ -75,7 +75,7 @@ namespace PartSource.Automation .AddAutomation(options => { options.HasBaseInterval(new TimeSpan(0, 5, 0)) - .HasMaxFailures(1) + .HasMaxFailures(5) //.HasJob(options => options.HasInterval(new TimeSpan(7, 0, 0, 0))); // //.HasJob(options => options.HasInterval(new TimeSpan(24, 0, 0))) diff --git a/PartSource.Data/Contexts/PartSourceContext.cs b/PartSource.Data/Contexts/PartSourceContext.cs index 1ec414e..3c355b1 100644 --- a/PartSource.Data/Contexts/PartSourceContext.cs +++ b/PartSource.Data/Contexts/PartSourceContext.cs @@ -32,7 +32,7 @@ namespace PartSource.Data.Contexts public DbSet Vehicles { get; set; } - public DbSet PartAvailabilities { get; set; } + public DbSet PartAvailabilities { get; set; } public DbSet BaseVehicles { get; set; } @@ -48,7 +48,7 @@ namespace PartSource.Data.Contexts { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().HasKey(p => new { p.Store, p.SKU }); + modelBuilder.Entity().HasKey(p => new { p.Store, p.SKU }); modelBuilder.Entity().HasKey(d => new { d.LineCode, d.WhiCode }); modelBuilder.Entity() diff --git a/PartSource.Data/Models/PartsAvailability.cs b/PartSource.Data/Models/PartAvailability.cs similarity index 57% rename from PartSource.Data/Models/PartsAvailability.cs rename to PartSource.Data/Models/PartAvailability.cs index 1edacb1..3435ebb 100644 --- a/PartSource.Data/Models/PartsAvailability.cs +++ b/PartSource.Data/Models/PartAvailability.cs @@ -1,9 +1,10 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace PartSource.Data.Models { - public class PartsAvailability + public class PartAvailability { [Column(Order = 0)] [DatabaseGenerated(DatabaseGeneratedOption.None)] @@ -13,14 +14,8 @@ namespace PartSource.Data.Models [DatabaseGenerated(DatabaseGeneratedOption.None)] public int SKU { get; set; } - [Column("Line Code")] - [StringLength(50)] - public string Line_Code { get; set; } - - [Column("Part Number")] - [StringLength(50)] - public string Part_Number { get; set; } - public int? QTY { get; set; } + + public DateTime? Updated { get; set; } } } diff --git a/PartSource.Services/PartService.cs b/PartSource.Services/PartService.cs index fda4ffb..2c92b68 100644 --- a/PartSource.Services/PartService.cs +++ b/PartSource.Services/PartService.cs @@ -21,7 +21,7 @@ namespace PartSource.Services _fitmentContext = fitmentContext; } - public async Task GetInventory(int sku, int storeNumber) + public async Task GetInventory(int sku, int storeNumber) { return await _partSourceContext.PartAvailabilities.FirstOrDefaultAsync(s => s.Store == storeNumber && s.SKU == sku); }