feat: улучшить импорт CSV — дедупликация, кодировка, парсинг сумм

Пять доработок импорта транзакций из CSV:
1. Проверка дублей при повторном импорте — сверка с уже существующими
   транзакциями на счёте за диапазон дат файла (по дате+типу+сумме+
   описанию), похожие исключаются из импорта по умолчанию с опцией
   всё равно их добавить.
2. Автоопределение кодировки файла (UTF-8 с откатом на Windows-1251)
   вместо жёстко зашитого UTF-8 — раньше кириллица из банковских
   выгрузок превращалась в кракозябры. Есть ручной выбор на случай
   неверного автоопределения.
3. Исправлен баг в parseAmount: суммы с несколькими разделителями
   тысяч и без дробной части (например "1,234,567") теряли цифры и
   отбрасывались как нераспознанные — теперь распознаются верно.
4. Добавлен явный выбор разделителя дробной части (точка/запятая) на
   случай, когда автоопределение не может разрешить неоднозначность.
5. Пример значения из файла показывается прямо под каждым выбором
   колонки при сопоставлении — не нужно доходить до превью, чтобы
   понять, что в колонке.
This commit is contained in:
ZProger
2026-09-10 20:06:41 +03:00
parent 78826db33e
commit 777e3af725
5 changed files with 310 additions and 52 deletions

View File

@@ -252,6 +252,16 @@ const ru = {
"transactions.import.errorBadDate": "не удалось распознать дату «{{value}}»",
"transactions.import.errorBadAmount": "не удалось распознать сумму «{{value}}»",
"transactions.import.errorNoDescription": "нет описания",
"transactions.import.encodingLabel": "Кодировка файла",
"transactions.import.encodingHint": "Определяется автоматически; если вместо русских букв — кракозябры, попробуйте выбрать вручную.",
"transactions.import.sampleValueLabel": "Пример из файла: {{value}}",
"transactions.import.amountFormatLabel": "Разделитель дробной части",
"transactions.import.amountFormat.auto": "Автоматически",
"transactions.import.amountFormat.dot-decimal": "Точка (1234.56), запятая — тысячи",
"transactions.import.amountFormat.comma-decimal": "Запятая (1234,56), точка — тысячи",
"transactions.import.duplicateCheckLoading": "Проверяем, нет ли уже таких транзакций…",
"transactions.import.duplicatesFound": "Найдено похожих на уже существующие: {{count}}. Они не будут импортированы.",
"transactions.import.includeDuplicates": "Всё равно импортировать их",
"transactions.allTypes": "Все типы",
"transactions.expense": "Расходы",
"transactions.income": "Доходы",
@@ -759,6 +769,16 @@ const en: Record<keyof typeof ru, string> = {
"transactions.import.errorBadDate": "couldn't parse the date \"{{value}}\"",
"transactions.import.errorBadAmount": "couldn't parse the amount \"{{value}}\"",
"transactions.import.errorNoDescription": "no description",
"transactions.import.encodingLabel": "File encoding",
"transactions.import.encodingHint": "Auto-detected; if you see garbled characters instead of letters, try picking one manually.",
"transactions.import.sampleValueLabel": "Example from file: {{value}}",
"transactions.import.amountFormatLabel": "Decimal separator",
"transactions.import.amountFormat.auto": "Automatic",
"transactions.import.amountFormat.dot-decimal": "Dot (1234.56), comma is thousands",
"transactions.import.amountFormat.comma-decimal": "Comma (1234,56), dot is thousands",
"transactions.import.duplicateCheckLoading": "Checking for transactions already on this account…",
"transactions.import.duplicatesFound": "Found {{count}} that look like duplicates of existing transactions. They won't be imported.",
"transactions.import.includeDuplicates": "Import them anyway",
"transactions.allTypes": "All types",
"transactions.expense": "Expenses",
"transactions.income": "Income",