From addf55e3b243b92fd57e82a7a2af417219b1b960 Mon Sep 17 00:00:00 2001 From: Dianaka123 Date: Mon, 6 Apr 2026 00:21:25 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20Sheet1ImportService=20=D0=B8=20Sheet1Group=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=9B=D0=B8=D1=81=D1=821?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Начало перехода с СПРАВКИ на Лист1 как источник данных: - Sheet1Group — модель одной группы (строка ИТОГО + все рег. номера группы) - Sheet1ImportService — читает Лист1, находит ИТОГО-строки, динамически собирает все рег. номера по regex-паттерну из всех колонок группы WIP: TransformService, Sheet3ExpandService и ViewModel ещё не переключены --- .../Models/Sheet1Group.cs | 19 ++ .../Services/Sheet1ImportService.cs | 166 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 DeclarationAutomatization/Models/Sheet1Group.cs create mode 100644 DeclarationAutomatization/Services/Sheet1ImportService.cs diff --git a/DeclarationAutomatization/Models/Sheet1Group.cs b/DeclarationAutomatization/Models/Sheet1Group.cs new file mode 100644 index 0000000..7c99fc4 --- /dev/null +++ b/DeclarationAutomatization/Models/Sheet1Group.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace DeclarationAutomatization.Models; + +// Одна группа из Листа1: строка ИТОГО + все рег. номера из строк группы +public class Sheet1Group +{ + public int SequentialNumber { get; set; } + public string Description { get; set; } = ""; + public string TnVed { get; set; } = ""; + public string CountryId { get; set; } = ""; + public decimal Quantity { get; set; } + public decimal AmountWithVat { get; set; } + public decimal GrossWeight { get; set; } + public decimal NetWeight { get; set; } + + // Все уникальные рег. номера из всех строк группы (включая строку ИТОГО) + public List RegNumbers { get; set; } = new(); +} diff --git a/DeclarationAutomatization/Services/Sheet1ImportService.cs b/DeclarationAutomatization/Services/Sheet1ImportService.cs new file mode 100644 index 0000000..9d7cbc5 --- /dev/null +++ b/DeclarationAutomatization/Services/Sheet1ImportService.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using ClosedXML.Excel; +using DeclarationAutomatization.Models; + +namespace DeclarationAutomatization.Services; + +public class Sheet1ImportService +{ + // Паттерн регистрационного номера: 8цифр/6цифр/буква?7+цифр + private static readonly Regex RegPattern = + new(@"\d{8}/\d{6}/[A-ZА-Я]?\d+", RegexOptions.Compiled); + + public List ReadSheet1(string filePath) + { + using var workbook = new XLWorkbook(filePath); + + IXLWorksheet? sheet = null; + foreach (var ws in workbook.Worksheets) + { + if (ws.Name.Equals("Лист1", StringComparison.OrdinalIgnoreCase)) + { sheet = ws; break; } + } + + if (sheet == null) + throw new InvalidOperationException($"Лист 'Лист1' не найден в файле: {filePath}"); + + var groups = new List(); + var groupRows = new List(); // строки текущей группы (детальные) + int sequentialNumber = 1; + + int lastRow = sheet.LastRowUsed()?.RowNumber() ?? 1; + + for (int r = 1; r <= lastRow; r++) + { + var row = sheet.Row(r); + + if (IsItogoRow(row)) + { + var group = BuildGroup(row, groupRows, sequentialNumber++); + if (group != null) + groups.Add(group); + + groupRows = new List(); + } + else + { + groupRows.Add(row); + } + } + + return groups; + } + + private static bool IsItogoRow(IXLRow row) + { + // ИТОГО-строка: ячейка C содержит "ИТОГО" (или B) + foreach (var cell in row.CellsUsed()) + { + var val = cell.GetString(); + if (val.Contains("ИТОГО", StringComparison.OrdinalIgnoreCase)) + return true; + } + return false; + } + + private Sheet1Group? BuildGroup(IXLRow itogoRow, List detailRows, int sequentialNumber) + { + // Берём ТН ВЭД из строки ИТОГО — ищем первую ячейку с 10-значным кодом + string tnVed = FindTnVed(itogoRow); + if (string.IsNullOrWhiteSpace(tnVed)) + return null; + + // Описание — столбец B строки ИТОГО + string description = itogoRow.Cell(2).GetString().Trim(); + if (string.IsNullOrWhiteSpace(description)) + description = itogoRow.Cell(3).GetString().Trim(); // иногда в C + + // Страна — первый двухбуквенный код в строке + string countryId = FindCountryId(itogoRow); + + // Числа: Количество(I=9), Сумма(J=10), Брутто(K=11), Нетто(L=12) + decimal qty = ParseDecimal(itogoRow.Cell(9)); + decimal amount = ParseDecimal(itogoRow.Cell(10)); + decimal gross = ParseDecimal(itogoRow.Cell(11)); + decimal net = ParseDecimal(itogoRow.Cell(12)); + + // Собираем все уникальные рег. номера из ВСЕХ строк группы + строки ИТОГО + var allRows = new List(detailRows) { itogoRow }; + var regNumbers = CollectRegNumbers(allRows); + + return new Sheet1Group + { + SequentialNumber = sequentialNumber, + Description = description, + TnVed = tnVed, + CountryId = countryId, + Quantity = qty, + AmountWithVat = amount, + GrossWeight = gross, + NetWeight = net, + RegNumbers = regNumbers, + }; + } + + private static string FindTnVed(IXLRow row) + { + foreach (var cell in row.CellsUsed()) + { + var val = cell.GetString().Trim(); + // ТН ВЭД = 10 цифр подряд + if (Regex.IsMatch(val, @"^\d{10}$")) + return val; + } + return ""; + } + + private static string FindCountryId(IXLRow row) + { + foreach (var cell in row.CellsUsed()) + { + var val = cell.GetString().Trim(); + // Двухбуквенный код страны (AM, BD, CN, ...) + if (Regex.IsMatch(val, @"^[A-Z]{2}$")) + return val; + } + return ""; + } + + private List CollectRegNumbers(IEnumerable rows) + { + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + var result = new List(); + + foreach (var row in rows) + { + foreach (var cell in row.CellsUsed()) + { + var val = cell.GetString().Trim(); + var match = RegPattern.Match(val); + if (match.Success) + { + var regNum = match.Value; + if (seen.Add(regNum)) + result.Add(regNum); + } + } + } + + return result; + } + + private static decimal ParseDecimal(IXLCell cell) + { + if (cell.IsEmpty()) return 0; + try + { + var val = cell.GetString().Trim().Replace(',', '.'); + return decimal.TryParse(val, System.Globalization.NumberStyles.Any, + System.Globalization.CultureInfo.InvariantCulture, out var d) ? d : 0; + } + catch { return 0; } + } +}