Files
declaratrions-automatization/DeclarationAutomatization/Services/ExcelImportService.cs
T
Dianaka123 059895d1c3 feat: WPF-приложение для автоматизации оформления деклараций
- Чтение СПРАВКИ из Excel (ClosedXML), поддержка нескольких файлов
- Группировка по ТН ВЭД: схлопывание строк с суммированием кол-ва/веса/суммы
- Автоназначение кодов деклараций по справочнику ТН ВЭД (87 пар)
- Цветовая маркировка: зелёный/жёлтый/красный по уровню уверенности
- Самообучение: ручной выбор кода сохраняется в tnved_codes.json
- Формирование Лист3 с разворачиванием строк по рег. номерам (ключевая функция)
- Экспорт Лист2 + Лист3 в Excel
2026-04-05 23:19:58 +03:00

108 lines
3.9 KiB
C#

using System;
using System.Collections.Generic;
using ClosedXML.Excel;
using DeclarationAutomatization.Models;
namespace DeclarationAutomatization.Services;
public class ExcelImportService
{
// Читает лист СПРАВКА из файла Excel.
// startingNumber — первый п/п для строк этого файла.
// Возвращает список SpravkaItem с назначенными п/п.
public List<SpravkaItem> ReadSpravka(string filePath, int startingNumber)
{
var result = new List<SpravkaItem>();
using var workbook = new XLWorkbook(filePath);
// Ищем лист "СПРАВКА" (регистронезависимо)
IXLWorksheet? sheet = null;
foreach (var ws in workbook.Worksheets)
{
if (ws.Name.Equals("СПРАВКА", StringComparison.OrdinalIgnoreCase))
{
sheet = ws;
break;
}
}
if (sheet == null)
throw new InvalidOperationException($"Лист 'СПРАВКА' не найден в файле: {filePath}");
// Ищем строку заголовка: первая строка, где ячейка B содержит "Наименование"
int headerRow = FindHeaderRow(sheet);
if (headerRow < 0)
throw new InvalidOperationException("Не найдена строка заголовка в листе СПРАВКА (ожидалась ячейка с 'Наименование')");
int sequentialNumber = startingNumber;
for (int row = headerRow + 1; row <= sheet.LastRowUsed()?.RowNumber(); row++)
{
var cellB = sheet.Cell(row, 2).GetString().Trim();
if (string.IsNullOrWhiteSpace(cellB))
continue; // пропускаем пустые строки
var item = new SpravkaItem
{
SequentialNumber = sequentialNumber++,
Description = cellB,
ProductCode = sheet.Cell(row, 5).GetString().Trim(),
TnVed = sheet.Cell(row, 8).GetString().Trim(),
CountryId = sheet.Cell(row, 11).GetString().Trim(),
Country = sheet.Cell(row, 12).GetString().Trim(),
Unit = sheet.Cell(row, 13).GetString().Trim(),
Quantity = ParseDecimal(sheet.Cell(row, 14)),
AmountWithVat = ParseDecimal(sheet.Cell(row, 15)),
GrossWeight = ParseDecimal(sheet.Cell(row, 16)),
NetWeight = ParseDecimal(sheet.Cell(row, 17)),
RegNumber = sheet.Cell(row, 18).GetString().Trim(),
RegDate = FormatDate(sheet.Cell(row, 19)),
};
result.Add(item);
}
return result;
}
private static int FindHeaderRow(IXLWorksheet sheet)
{
int lastRow = Math.Min(sheet.LastRowUsed()?.RowNumber() ?? 200, 200);
for (int r = 1; r <= lastRow; r++)
{
var val = sheet.Cell(r, 2).GetString();
if (val.Contains("Наименование", StringComparison.OrdinalIgnoreCase))
return r;
}
return -1;
}
private static decimal ParseDecimal(IXLCell cell)
{
try
{
if (cell.IsEmpty()) return 0;
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;
}
}
private static string FormatDate(IXLCell cell)
{
if (cell.IsEmpty()) return "";
try
{
if (cell.DataType == XLDataType.DateTime)
return cell.GetDateTime().ToString("dd.MM.yyyy");
}
catch { }
return cell.GetString().Trim();
}
}