059895d1c3
- Чтение СПРАВКИ из Excel (ClosedXML), поддержка нескольких файлов - Группировка по ТН ВЭД: схлопывание строк с суммированием кол-ва/веса/суммы - Автоназначение кодов деклараций по справочнику ТН ВЭД (87 пар) - Цветовая маркировка: зелёный/жёлтый/красный по уровню уверенности - Самообучение: ручной выбор кода сохраняется в tnved_codes.json - Формирование Лист3 с разворачиванием строк по рег. номерам (ключевая функция) - Экспорт Лист2 + Лист3 в Excel
219 lines
7.8 KiB
C#
219 lines
7.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using DeclarationAutomatization.Models;
|
|
using DeclarationAutomatization.Services;
|
|
|
|
namespace DeclarationAutomatization.ViewModels;
|
|
|
|
public partial class MainViewModel : ObservableObject
|
|
{
|
|
private readonly ExcelImportService _importService;
|
|
private readonly TransformService _transformService;
|
|
private readonly CodeLookupService _codeLookupService;
|
|
private readonly Sheet3ExpandService _expandService;
|
|
private readonly ExcelExportService _exportService;
|
|
|
|
// Загруженные файлы СПРАВОК
|
|
public ObservableCollection<SpravkaFileEntry> SpravkaFiles { get; } = new();
|
|
|
|
// Все строки из всех загруженных СПРАВОК
|
|
private List<SpravkaItem> _allSpravkaItems = new();
|
|
|
|
// Позиции для Листа2 после обработки
|
|
public ObservableCollection<DeclarationItem> DeclarationItems { get; } = new();
|
|
|
|
[ObservableProperty]
|
|
[NotifyPropertyChangedFor(nameof(AutoCount))]
|
|
[NotifyPropertyChangedFor(nameof(ReviewCount))]
|
|
[NotifyPropertyChangedFor(nameof(MissingCount))]
|
|
private int _totalItems;
|
|
|
|
public int AutoCount => DeclarationItems.Count(i => i.Confidence == ConfidenceLevel.Auto);
|
|
public int ReviewCount => DeclarationItems.Count(i => i.Confidence == ConfidenceLevel.Review);
|
|
public int MissingCount => DeclarationItems.Count(i => i.Confidence == ConfidenceLevel.Missing);
|
|
|
|
[ObservableProperty] private string _statusMessage = "Загрузите файл СПРАВКИ для начала работы";
|
|
[ObservableProperty] private bool _isProcessing;
|
|
[ObservableProperty] private bool _hasResults;
|
|
|
|
public MainViewModel(
|
|
ExcelImportService importService,
|
|
TransformService transformService,
|
|
CodeLookupService codeLookupService,
|
|
Sheet3ExpandService expandService,
|
|
ExcelExportService exportService)
|
|
{
|
|
_importService = importService;
|
|
_transformService = transformService;
|
|
_codeLookupService = codeLookupService;
|
|
_expandService = expandService;
|
|
_exportService = exportService;
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void AddSpravkaFile()
|
|
{
|
|
var dialog = new Microsoft.Win32.OpenFileDialog
|
|
{
|
|
Title = "Выберите файл СПРАВКИ",
|
|
Filter = "Excel файлы (*.xlsx)|*.xlsx|Все файлы (*.*)|*.*",
|
|
Multiselect = true
|
|
};
|
|
|
|
if (dialog.ShowDialog() != true) return;
|
|
|
|
foreach (var path in dialog.FileNames)
|
|
{
|
|
// Определяем начальный п/п: продолжаем с последнего
|
|
int nextStart = SpravkaFiles.Count == 0 ? 1
|
|
: SpravkaFiles.Max(f => f.StartingNumber) + EstimateRowCount(f => f.FilePath == path ? 0 : 1);
|
|
|
|
// Простая эвристика: берём последний стартовый номер + 1000
|
|
int startingNumber = SpravkaFiles.Count == 0 ? 1
|
|
: SpravkaFiles.Last().StartingNumber + 1000;
|
|
|
|
SpravkaFiles.Add(new SpravkaFileEntry
|
|
{
|
|
FilePath = path,
|
|
StartingNumber = startingNumber
|
|
});
|
|
}
|
|
}
|
|
|
|
private int EstimateRowCount(Func<SpravkaFileEntry, int> _) => 1000;
|
|
|
|
[RelayCommand]
|
|
private void RemoveSpravkaFile(SpravkaFileEntry? entry)
|
|
{
|
|
if (entry != null)
|
|
SpravkaFiles.Remove(entry);
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task ProcessAsync()
|
|
{
|
|
if (SpravkaFiles.Count == 0)
|
|
{
|
|
StatusMessage = "Добавьте хотя бы один файл СПРАВКИ";
|
|
return;
|
|
}
|
|
|
|
IsProcessing = true;
|
|
HasResults = false;
|
|
StatusMessage = "Обработка...";
|
|
|
|
try
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
_allSpravkaItems = new List<SpravkaItem>();
|
|
|
|
foreach (var entry in SpravkaFiles)
|
|
{
|
|
var items = _importService.ReadSpravka(entry.FilePath, entry.StartingNumber);
|
|
_allSpravkaItems.AddRange(items);
|
|
}
|
|
|
|
var declItems = _transformService.BuildDeclarationItems(_allSpravkaItems);
|
|
|
|
_codeLookupService.AssignCodes(declItems);
|
|
|
|
Application.Current.Dispatcher.Invoke(() =>
|
|
{
|
|
DeclarationItems.Clear();
|
|
foreach (var item in declItems)
|
|
DeclarationItems.Add(item);
|
|
|
|
TotalItems = DeclarationItems.Count;
|
|
OnPropertyChanged(nameof(AutoCount));
|
|
OnPropertyChanged(nameof(ReviewCount));
|
|
OnPropertyChanged(nameof(MissingCount));
|
|
HasResults = DeclarationItems.Count > 0;
|
|
StatusMessage = $"Обработано {DeclarationItems.Count} позиций: " +
|
|
$"✅ {AutoCount} авто, ⚠️ {ReviewCount} проверить, 🔴 {MissingCount} не найдено";
|
|
});
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"Ошибка: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
IsProcessing = false;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void ApproveAllAuto()
|
|
{
|
|
// Кнопка «Принять все авто» — уже зелёные, действий не требуется.
|
|
// Можно добавить визуальное подтверждение.
|
|
StatusMessage = $"Подтверждено {AutoCount} авто-позиций. Проверьте жёлтые и красные.";
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task ExportAsync()
|
|
{
|
|
if (!HasResults)
|
|
{
|
|
StatusMessage = "Нет данных для экспорта. Сначала выполните обработку.";
|
|
return;
|
|
}
|
|
|
|
var dialog = new Microsoft.Win32.SaveFileDialog
|
|
{
|
|
Title = "Сохранить результат",
|
|
Filter = "Excel файлы (*.xlsx)|*.xlsx",
|
|
FileName = $"Декларация_{DateTime.Now:yyyyMMdd_HHmm}.xlsx"
|
|
};
|
|
|
|
if (dialog.ShowDialog() != true) return;
|
|
|
|
IsProcessing = true;
|
|
StatusMessage = "Экспорт...";
|
|
|
|
try
|
|
{
|
|
var sheet3Rows = await Task.Run(() =>
|
|
_expandService.Expand(DeclarationItems, _allSpravkaItems));
|
|
|
|
await Task.Run(() =>
|
|
_exportService.Export(dialog.FileName, DeclarationItems, sheet3Rows));
|
|
|
|
StatusMessage = $"Файл сохранён: {Path.GetFileName(dialog.FileName)} " +
|
|
$"(Лист2: {DeclarationItems.Count} строк, Лист3: {sheet3Rows.Count} строк)";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
StatusMessage = $"Ошибка экспорта: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
IsProcessing = false;
|
|
}
|
|
}
|
|
|
|
// Вызывается из UI при ручном изменении кода в таблице
|
|
public void OnCodeManuallyChanged(DeclarationItem item, string newCode)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(item.TnVed) || string.IsNullOrWhiteSpace(newCode))
|
|
return;
|
|
|
|
item.DeclarationCode = newCode;
|
|
item.Confidence = ConfidenceLevel.Auto;
|
|
_codeLookupService.LearnFromManualEdit(item.TnVed, newCode);
|
|
|
|
OnPropertyChanged(nameof(AutoCount));
|
|
OnPropertyChanged(nameof(ReviewCount));
|
|
OnPropertyChanged(nameof(MissingCount));
|
|
}
|
|
}
|