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; } } }