From be127319e27d630ce14c793fc50bccd576e5fb7b Mon Sep 17 00:00:00 2001 From: Serghei Cebotari Date: Wed, 15 Jan 2025 15:06:04 +0300 Subject: [PATCH] Implement BS parser --- ...nParseController.cs => ParseController.cs} | 103 ++++++++++-------- RhSolutions.SkuParser.Api/Models/Product.cs | 12 +- .../Models/ProductLine.cs | 7 ++ RhSolutions.SkuParser.Api/Program.cs | 6 +- .../Services/BsExcelParser.cs | 72 ++++++++++++ .../appsettings.Development.json | 8 -- RhSolutions.SkuParser.Api/appsettings.json | 11 +- 7 files changed, 157 insertions(+), 62 deletions(-) rename RhSolutions.SkuParser.Api/Controllers/{CommonParseController.cs => ParseController.cs} (63%) create mode 100644 RhSolutions.SkuParser.Api/Models/ProductLine.cs create mode 100644 RhSolutions.SkuParser.Api/Services/BsExcelParser.cs delete mode 100644 RhSolutions.SkuParser.Api/appsettings.Development.json diff --git a/RhSolutions.SkuParser.Api/Controllers/CommonParseController.cs b/RhSolutions.SkuParser.Api/Controllers/ParseController.cs similarity index 63% rename from RhSolutions.SkuParser.Api/Controllers/CommonParseController.cs rename to RhSolutions.SkuParser.Api/Controllers/ParseController.cs index c819851..ca1d0d4 100644 --- a/RhSolutions.SkuParser.Api/Controllers/CommonParseController.cs +++ b/RhSolutions.SkuParser.Api/Controllers/ParseController.cs @@ -1,48 +1,57 @@ -using Microsoft.AspNetCore.Mvc; -using RhSolutions.SkuParser.Models; -using RhSolutions.SkuParser.Abstractions; - -namespace RhSolutions.SkuParser.Controllers; - -[ApiController] -[Route("/api/[controller]")] -public class CommonParseController : ControllerBase -{ - private IServiceProvider _provider; - private Dictionary _result; - public CommonParseController(IServiceProvider provider) - { - _provider = provider; - _result = new(); - } - - [HttpPost] - public IActionResult PostFiles() - { - IFormFileCollection files = Request.Form.Files; - try - { - foreach (var file in files) - { - ISkuParser parser = _provider.GetRequiredKeyedService(file.ContentType); - var dict = parser.ParseProducts(file); - foreach (var kvp in dict) - { - if (_result.ContainsKey(kvp.Key)) - { - _result[kvp.Key] += kvp.Value; - } - else - { - _result.Add(kvp.Key, kvp.Value); - } - } - } - } - catch (Exception ex) - { - return BadRequest(error: $"{ex.Message}\n\n{ex.Source}\n{ex.StackTrace}"); - } - return new JsonResult(_result.Select(x => new { x.Key.Sku, x.Value })); - } +using Microsoft.AspNetCore.Mvc; +using RhSolutions.SkuParser.Models; +using RhSolutions.SkuParser.Abstractions; + +namespace RhSolutions.SkuParser.Controllers; + +[ApiController] +[Route("/api/[controller]")] +public class ParseController : ControllerBase +{ + private IServiceProvider _provider; + private Dictionary _result; + public ParseController(IServiceProvider provider) + { + _provider = provider; + _result = new(); + } + + [HttpPost] + public IActionResult PostFiles([FromQuery] bool bs = false) + { + IFormFileCollection files = Request.Form.Files; + try + { + foreach (var file in files) + { + ISkuParser parser = bs ? _provider.GetRequiredKeyedService("BS") + : _provider.GetRequiredKeyedService(file.ContentType); + var dict = parser.ParseProducts(file); + + foreach (var kvp in dict) + { + if (_result.ContainsKey(kvp.Key)) + { + _result[kvp.Key] += kvp.Value; + } + else + { + _result.Add(kvp.Key, kvp.Value); + } + } + } + } + catch (Exception ex) + { + return BadRequest(error: $"{ex.Message}\n\n{ex.Source}\n{ex.StackTrace}"); + } + return new JsonResult(_result.Select(x => new + { + x.Key.Sku, + x.Key.ProductLine, + x.Key.Name, + x.Key.Price, + quantity = x.Value + })); + } } \ No newline at end of file diff --git a/RhSolutions.SkuParser.Api/Models/Product.cs b/RhSolutions.SkuParser.Api/Models/Product.cs index 26a7392..6aba7f0 100644 --- a/RhSolutions.SkuParser.Api/Models/Product.cs +++ b/RhSolutions.SkuParser.Api/Models/Product.cs @@ -4,6 +4,10 @@ namespace RhSolutions.SkuParser.Models; public record Product { + private string _sku = string.Empty; + private const string _parsePattern = @"(?[1\s]|^|\b)(?
\d{6})(?[\s13-])(?\d{3})(\b|$)"; + private const string _validnessPattern = @"^1\d{6}[1|3]\d{3}$"; + /// /// Артикул РЕХАУ в заданном формате /// @@ -17,9 +21,9 @@ public record Product : throw new ArgumentException("$Неверный артикул: {value}"); } } - private string _sku = string.Empty; - private const string _parsePattern = @"(?[1\s]|^|\b)(?
\d{6})(?[\s13-])(?\d{3})(\b|$)"; - private const string _validnessPattern = @"^1\d{6}[1|3]\d{3}$"; + public ProductLine? ProductLine { get; set; } + public string? Name { get; set; } + public decimal? Price { get; set; } private static bool IsValudSku(string value) { @@ -62,4 +66,4 @@ public record Product } public override int GetHashCode() => Sku.GetHashCode(); public override string ToString() => Sku; -} \ No newline at end of file +} diff --git a/RhSolutions.SkuParser.Api/Models/ProductLine.cs b/RhSolutions.SkuParser.Api/Models/ProductLine.cs new file mode 100644 index 0000000..61df758 --- /dev/null +++ b/RhSolutions.SkuParser.Api/Models/ProductLine.cs @@ -0,0 +1,7 @@ +namespace RhSolutions.SkuParser.Models; + +public enum ProductLine +{ + RAUTITAN, + RAUTHERMS +} \ No newline at end of file diff --git a/RhSolutions.SkuParser.Api/Program.cs b/RhSolutions.SkuParser.Api/Program.cs index 13e0b90..731344a 100644 --- a/RhSolutions.SkuParser.Api/Program.cs +++ b/RhSolutions.SkuParser.Api/Program.cs @@ -1,11 +1,15 @@ using RhSolutions.SkuParser.Abstractions; +using RhSolutions.SkuParser.Api.Services; using RhSolutions.SkuParser.Services; var builder = WebApplication.CreateBuilder(args); +builder.Configuration + .AddJsonFile("appsettings.json"); builder.Services .AddKeyedScoped("text/csv") .AddKeyedScoped("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") - .AddKeyedScoped("application/vnd.ms-excel.sheet.macroenabled.12"); + .AddKeyedScoped("application/vnd.ms-excel.sheet.macroenabled.12") + .AddKeyedScoped("BS"); builder.Services.AddControllers(); var app = builder.Build(); diff --git a/RhSolutions.SkuParser.Api/Services/BsExcelParser.cs b/RhSolutions.SkuParser.Api/Services/BsExcelParser.cs new file mode 100644 index 0000000..684c4c2 --- /dev/null +++ b/RhSolutions.SkuParser.Api/Services/BsExcelParser.cs @@ -0,0 +1,72 @@ +using System.Data; +using ClosedXML.Excel; +using RhSolutions.SkuParser.Abstractions; +using RhSolutions.SkuParser.Models; + +namespace RhSolutions.SkuParser.Api.Services; + +public class BsExcelParser : ISkuParser +{ + private IConfiguration configuration; + // private Dictionary result; + private const int rowsLookupCount = 20; + private const decimal vat = 1.2M; + public BsExcelParser(IConfiguration configuration) + { + this.configuration = configuration; + // result = new(); + } + + public Dictionary ParseProducts(IFormFile file) + { + using XLWorkbook workbook = new(file.OpenReadStream()); + IXLWorksheet ws = workbook.Worksheet(1); + var headers = configuration.GetSection("Headers"); + ws.AutoFilter.Clear(); + var cells = configuration.GetSection("Headers") + .GetChildren() + .ToDictionary(x => x.Path, x => ws.Range(1, 1, rowsLookupCount, ws.LastCellUsed().Address.ColumnNumber) + .Search(x.Value) + .FirstOrDefault()) + .OrderBy(x => x.Value?.Address.ColumnNumber ?? int.MaxValue); + if (cells == null || cells.Any(x => x.Value == null)) + { + throw new DataException($"В рабочей книге отсуствует таблица с необходимыми заголовками: {file.Name}"); + } + + var firstCell = cells.First().Value; + var lastCell = cells.Last().Value?.WorksheetColumn().LastCellUsed(); + var table = ws.Range(firstCell, lastCell).AsTable(); + var rows = table.DataRange.Rows(); + Dictionary result = new(); + + foreach (var row in rows.Where(r => r.Field(headers["Quantity"]).Value.IsNumber)) + { + Product product = new() + { + Sku = row.Field(headers["Sku"]).GetString(), + ProductLine = row.Field(headers["ProductLine"]).GetString() switch + { + "RAUTITAN" => ProductLine.RAUTITAN, + "RAUTHERM S" => ProductLine.RAUTHERMS, + _ => null + }, + Name = row.Field(headers["Name"]).GetString(), + Price = decimal.TryParse(row.Field(headers["Price"]).GetString(), out decimal value) + ? Math.Round(value * vat, 2) + : null + }; + + double quantity = double.TryParse(row.Field(headers["Quantity"]).GetString(), out double q) ? q : 0; + if (result.ContainsKey(product)) + { + result[product] += quantity; + } + else + { + result.Add(product, quantity); + } + } + return result; + } +} diff --git a/RhSolutions.SkuParser.Api/appsettings.Development.json b/RhSolutions.SkuParser.Api/appsettings.Development.json deleted file mode 100644 index ff66ba6..0000000 --- a/RhSolutions.SkuParser.Api/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/RhSolutions.SkuParser.Api/appsettings.json b/RhSolutions.SkuParser.Api/appsettings.json index 4d56694..ad0f760 100644 --- a/RhSolutions.SkuParser.Api/appsettings.json +++ b/RhSolutions.SkuParser.Api/appsettings.json @@ -5,5 +5,12 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" -} + "AllowedHosts": "*", + "Headers": { + "ProductLine": "Программа", + "Sku": "Актуальный материал", + "Name": "Наименование", + "Quantity": "Кол-во", + "Price": "Цена брутто \nЕВРО\nбез НДС" + } +} \ No newline at end of file