From 6fe0a5e92b071411a0408d59d54d0de78e55d75c Mon Sep 17 00:00:00 2001 From: Serghei Cebotari Date: Sun, 21 Jul 2024 16:25:59 +0300 Subject: Squashed commit of the following: commit 688c5426e8793b808b9c75c9a19733af0a402fcb Author: Serghei Cebotari Date: Sun Jul 21 16:25:14 2024 +0300 Switch to port 8080 commit c39249f6528ec76686a9382d1dc375c07d1d5044 Author: Serghei Cebotari Date: Sun Jul 21 16:24:59 2024 +0300 Switch to alpine image commit 5318d7ec3f4f3d205549cf6732fa5b066a1d0a36 Author: Serghei Cebotari Date: Sun Jul 21 15:40:14 2024 +0300 Add docker commit b6cd60a973da26bc92cf1fb45b4d2396b7ce56ea Author: Serghei Cebotari Date: Sun Jul 21 15:00:12 2024 +0300 Delete asynchrony commit 44a194e6d27312f3b8dd0b9c9c02d873e06e0b22 Author: Serghei Cebotari Date: Sun Jul 21 14:59:29 2024 +0300 Add Equals and GetHasCode methods overrides to ProductQuantity class commit a274eadd313e12f11cc84d32e5030bbc5b187f8c Author: Serghei Cebotari Date: Sun Jul 21 14:58:37 2024 +0300 Add parsers tests commit 4f969e70d9716d8ddb4f4efedd466846289d7e2b Author: Serghei Cebotari Date: Sun Jul 21 14:57:55 2024 +0300 Update product tests commit 2485e20d0e93bed562f929055b6867dc2574a95b Author: Serghei Cebotari Date: Sat Jul 20 19:34:19 2024 +0300 Implement Excel parser commit 30f2e28c87a4d961c1f1fc48fbd72334905bf4ed Author: Serghei Cebotari Date: Sat Jul 20 16:58:35 2024 +0300 Implement csv parser commit 08e86b43c0829de341dc3d24fbe01aadbed2e173 Author: Serghei Cebotari Date: Thu Jul 18 21:01:28 2024 +0300 Edit port number --- RhSolutions.SkuParser.Api/Services/CsvParser.cs | 24 +++++++ RhSolutions.SkuParser.Api/Services/ExcelParser.cs | 76 +++++++++++++++++++++++ RhSolutions.SkuParser.Api/Services/ISkuParser.cs | 7 +++ 3 files changed, 107 insertions(+) create mode 100644 RhSolutions.SkuParser.Api/Services/CsvParser.cs create mode 100644 RhSolutions.SkuParser.Api/Services/ExcelParser.cs create mode 100644 RhSolutions.SkuParser.Api/Services/ISkuParser.cs (limited to 'RhSolutions.SkuParser.Api/Services') diff --git a/RhSolutions.SkuParser.Api/Services/CsvParser.cs b/RhSolutions.SkuParser.Api/Services/CsvParser.cs new file mode 100644 index 0000000..436a949 --- /dev/null +++ b/RhSolutions.SkuParser.Api/Services/CsvParser.cs @@ -0,0 +1,24 @@ +using System.Globalization; +using CsvHelper; +using CsvHelper.Configuration; +using RhSolutions.SkuParser.Models; + +namespace RhSolutions.SkuParser.Services; + +/// +/// Парсер артикулов и их количества из файлов *.csv +/// +public class CsvParser : ISkuParser +{ + public IEnumerable ParseProducts(IFormFile file) + { + using StreamReader reader = new(file.OpenReadStream()); + var config = new CsvConfiguration(CultureInfo.GetCultureInfo("ru-RU")) + { + HasHeaderRecord = false, + }; + using CsvReader csvReader = new(reader, config); + + return csvReader.GetRecords().ToList(); + } +} diff --git a/RhSolutions.SkuParser.Api/Services/ExcelParser.cs b/RhSolutions.SkuParser.Api/Services/ExcelParser.cs new file mode 100644 index 0000000..27b10bd --- /dev/null +++ b/RhSolutions.SkuParser.Api/Services/ExcelParser.cs @@ -0,0 +1,76 @@ +using ClosedXML.Excel; +using RhSolutions.SkuParser.Models; + +namespace RhSolutions.SkuParser.Services; + +public class ExcelParser : ISkuParser +{ + public IEnumerable ParseProducts(IFormFile file) + { + using XLWorkbook workbook = new(file.OpenReadStream()); + IXLWorksheet ws = workbook.Worksheet(1); + + var leftTop = ws.FirstCellUsed()?.Address; + var rightBottom = ws.LastCellUsed()?.Address; + if (new object?[] { leftTop, rightBottom }.Any(x => x == null)) + { + throw new ArgumentException($"Таблица пуста: {file.FileName}"); + } + + var lookupRange = ws.Range(leftTop, rightBottom).RangeUsed(); + var columns = lookupRange.Columns(); + + var skuColumnQuantity = columns + .Select(column => new + { + Column = column, + Products = column.CellsUsed() + .Select(cell => !cell.HasFormula && Product.TryParse(cell.Value.ToString(), out Product? p) ? p : null) + }) + .Select(c => new { c.Column, SkuCount = c.Products.Count(p => p != null) }) + .Aggregate((l, r) => l.SkuCount > r.SkuCount ? l : r); + var skuColumn = skuColumnQuantity.SkuCount > 0 ? skuColumnQuantity.Column : null; + + if (skuColumn == null) + { + throw new ArgumentException($"Столбец с артикулом не определен: {file.FileName}"); + } + + var quantityColumn = lookupRange.Columns().Skip(skuColumn.ColumnNumber()) + .Select(column => new + { + Column = column, + IsColumnWithNumbers = column.CellsUsed() + .Count(cell => cell.Value.IsNumber == true) > column.CellsUsed().Count() / 4 + }) + .First(x => x.IsColumnWithNumbers) + .Column; + + if (quantityColumn == null) + { + throw new ArgumentException($"Столбец с количеством не определен: {file.FileName}"); + } + + List result = new(); + var rows = quantityColumn.CellsUsed().Select(x => x.Address.RowNumber); + + foreach (var row in rows) + { + var quantity = quantityColumn.Cell(row).Value; + var sku = skuColumn.Cell(row).Value; + + if (quantity.IsNumber + && Product.TryParse(sku.ToString(), out Product? p)) + { + ProductQuantity pq = new() + { + Product = p!, + Quantity = quantity.GetNumber() + }; + result.Add(pq); + } + } + + return result; + } +} diff --git a/RhSolutions.SkuParser.Api/Services/ISkuParser.cs b/RhSolutions.SkuParser.Api/Services/ISkuParser.cs new file mode 100644 index 0000000..98b9d9c --- /dev/null +++ b/RhSolutions.SkuParser.Api/Services/ISkuParser.cs @@ -0,0 +1,7 @@ +using RhSolutions.SkuParser.Models; + +namespace RhSolutions.SkuParser.Services; +public interface ISkuParser +{ + public IEnumerable ParseProducts(IFormFile file); +} -- cgit v1.2.3