import transaction from custom delimiter-separated values file

This commit is contained in:
MaysWind
2025-03-03 23:55:25 +08:00
parent 703ceb44e4
commit 9430f57a0b
24 changed files with 3374 additions and 91 deletions
+89
View File
@@ -8,6 +8,59 @@ export const SUPPORTED_DOCUMENT_LANGUAGES_FOR_IMPORT_FILE: Record<string, boolea
'zh-Hans': true
};
export const SUPPORTED_FILE_ENCODINGS: string[] = [
'utf-8', // UTF-8
'utf-8-bom', // UTF-8 with BOM
'utf-16le', // UTF-16 Little Endian
'utf-16be', // UTF-16 Big Endian
'cp437', // OEM United States (CP-437)
'cp863', // OEM Canadian French (CP-863)
'cp037', // IBM EBCDIC US/Canada (CP-037)
'cp1047', // IBM EBCDIC Open Systems (CP-1047)
'cp1140', // IBM EBCDIC US/Canada with Euro (CP-1140)
"iso-8859-1", // Western European (ISO-8859-1)
'cp850', // Western European (CP-850)
'cp858', // Western European with Euro (CP-858)
'windows-1252', // Western European (Windows-1252)
'iso-8859-15', // Western European (ISO-8859-15)
'iso-8859-4', // North European (ISO-8859-4)
'iso-8859-10', // North European (ISO-8859-10)
'cp865', // North European (CP-865)
'iso-8859-2', // Central European (ISO-8859-2)
'cp852', // Central European (CP-852)
'windows-1250', // Central European (Windows-1250)
'iso-8859-14', // Celtic (ISO-8859-14)
'iso-8859-3', // South European (ISO-8859-3)
'cp860', // Portuguese (CP-860)
'iso-8859-7', // Greek (ISO-8859-7)
'windows-1253', // Greek (Windows-1253)
'iso-8859-9', // Turkish (ISO-8859-9)
'windows-1254', // Turkish (Windows-1254)
'iso-8859-13', // Baltic (ISO-8859-13)
'windows-1257', // Baltic (Windows-1257)
'iso-8859-16', // South-Eastern European (ISO-8859-16)
'iso-8859-5', // Cyrillic (ISO-8859-5)
'cp855', // Cyrillic (CP-855)
'cp866', // Cyrillic (CP-866)
'windows-1251', // Cyrillic (Windows-1251)
'koi8r', // Cyrillic (KOI8-R)
'koi8u', // Cyrillic (KOI8-U)
'iso-8859-6', // Arabic (ISO-8859-6)
'windows-1256', // Arabic (Windows-1256)
'iso-8859-8', // Hebrew (ISO-8859-8)
'cp862', // Hebrew (CP-862)
'windows-1255', // Hebrew (Windows-1255)
'windows-874', // Thai (Windows-874)
'windows-1258', // Vietnamese (Windows-1258)
'gb18030', // Simplified Chinese (GB18030)
'gbk', // Simplified Chinese (GBK)
'big5', // Traditional Chinese (Big5)
'euc-kr', // Korean (EUC-KR)
'euc-jp', // Japanese (EUC-JP)
'iso-2022-jp', // Japanese (ISO-2022-JP)
'shift_jis', // Japanese (Shift_JIS)
];
export const SUPPORTED_IMPORT_FILE_TYPES: ImportFileType[] = [
{
type: 'ezbookkeeping',
@@ -64,6 +117,42 @@ export const SUPPORTED_IMPORT_FILE_TYPES: ImportFileType[] = [
name: 'Intuit Interchange Format (IIF) File',
extensions: '.iif'
},
{
type: 'dsv',
name: 'Delimiter-separated Values (DSV) File',
extensions: '.csv,.tsv',
subTypes: [
{
type: 'custom_csv',
name: 'CSV (Comma-separated values) File',
extensions: '.csv',
},
{
type: 'custom_tsv',
name: 'TSV (Tab-separated values) File',
extensions: '.tsv,.txt',
}
],
supportedEncodings: SUPPORTED_FILE_ENCODINGS
},
{
type: 'dsv_data',
name: 'Delimiter-separated Values (DSV) Data',
extensions: '.csv,.tsv',
subTypes: [
{
type: 'custom_csv',
name: 'CSV (Comma-separated values) File',
extensions: '.csv',
},
{
type: 'custom_tsv',
name: 'TSV (Tab-separated values) File',
extensions: '.tsv,.txt',
}
],
dataFromTextbox: true
},
{
type: 'gnucash',
name: 'GnuCash XML Database File',
+72
View File
@@ -230,6 +230,78 @@ export class MeridiemIndicator {
}
}
export class KnownDateTimeFormat {
private static readonly allInstances: KnownDateTimeFormat[] = [];
public static readonly DefaultDateTime = new KnownDateTimeFormat('YYYY-MM-DD HH:mm:ss', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/);
public static readonly DefaultDateTimeWithTimezone = new KnownDateTimeFormat('YYYY-MM-DD HH:mm:ssZ', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$/);
public static readonly DefaultDateTimeWithoutSecond = new KnownDateTimeFormat('YYYY-MM-DD HH:mm', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]$/);
public static readonly DefaultDate = new KnownDateTimeFormat('YYYY-MM-DD', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/);
public static readonly RFC3339 = new KnownDateTimeFormat('YYYY-MM-DDTHH:mm:ssZ', /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$/);
public static readonly YYYYMMDDSlashWithTime = new KnownDateTimeFormat('YYYY/MM/DD HH:mm:ss', /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1]) ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/);
public static readonly MMDDYYSlashWithTime = new KnownDateTimeFormat('MM/DD/YYYY HH:mm:ss', /^(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])\/\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/);
public static readonly DDMMYYSlashWithTime = new KnownDateTimeFormat('DD/MM/YYYY HH:mm:ss', /^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/\d{4} ([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/);
public static readonly YYYYMMDDSlash = new KnownDateTimeFormat('YYYY/MM/DD', /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])$/);
public static readonly MMDDYYSlash = new KnownDateTimeFormat('MM/DD/YYYY', /^(0[1-9]|1[0-2])\/(0[1-9]|[1-2][0-9]|3[0-1])\/\d{4}$/);
public static readonly DDMMYYSlash = new KnownDateTimeFormat('DD/MM/YYYY', /^(0[1-9]|[1-2][0-9]|3[0-1])\/(0[1-9]|1[0-2])\/\d{4}$/);
public readonly format: string;
private readonly regex: RegExp;
private constructor(format: string, regex: RegExp) {
this.format = format;
this.regex = regex;
KnownDateTimeFormat.allInstances.push(this);
}
public isValid(dateTime: string): boolean {
return this.regex.test(dateTime);
}
public static values(): KnownDateTimeFormat[] {
return KnownDateTimeFormat.allInstances;
}
public static detect(dateTime: string): KnownDateTimeFormat[] | undefined {
const result: KnownDateTimeFormat[] = [];
for (const format of KnownDateTimeFormat.allInstances) {
if (format.isValid(dateTime)) {
result.push(format);
}
}
return result.length > 0 ? result : undefined;
}
public static detectMany(dateTimes: string[]): KnownDateTimeFormat[] | undefined {
const detectedCounts: Record<string, number> = {};
for (const dateTime of dateTimes) {
const detectedFormats = KnownDateTimeFormat.detect(dateTime);
if (detectedFormats) {
for (const format of detectedFormats) {
detectedCounts[format.format] = (detectedCounts[format.format] || 0) + 1;
}
} else {
return undefined;
}
}
const result: KnownDateTimeFormat[] = [];
for (const format of KnownDateTimeFormat.allInstances) {
if (detectedCounts[format.format] === dateTimes.length) {
result.push(format);
}
}
return result.length > 0 ? result : undefined;
}
}
export const LANGUAGE_DEFAULT_DATE_TIME_FORMAT_VALUE: number = 0;
export interface DateFormat {
+9
View File
@@ -8,6 +8,8 @@ export interface ImportFileType extends ImportFileTypeAndExtensions {
readonly name: string;
readonly extensions: string;
readonly subTypes?: ImportFileTypeSubType[];
readonly supportedEncodings?: string[];
readonly dataFromTextbox?: boolean;
readonly document?: {
readonly supportMultiLanguages: boolean | string;
readonly anchor: string;
@@ -25,6 +27,8 @@ export interface LocalizedImportFileType extends ImportFileTypeAndExtensions {
readonly displayName: string;
readonly extensions: string;
readonly subTypes?: LocalizedImportFileTypeSubType[];
readonly supportedEncodings?: LocalizedImportFileTypeSupportedEncodings[];
readonly dataFromTextbox?: boolean;
readonly document?: LocalizedImportFileDocument;
}
@@ -34,6 +38,11 @@ export interface LocalizedImportFileTypeSubType extends ImportFileTypeAndExtensi
readonly extensions?: string;
}
export interface LocalizedImportFileTypeSupportedEncodings {
readonly encoding: string;
readonly displayName: string;
}
export interface LocalizedImportFileDocument {
readonly language: string;
readonly displayLanguageName: string;
+72 -1
View File
@@ -1,4 +1,4 @@
import type { TypeAndName } from './base.ts';
import type { NameValue, TypeAndName } from './base.ts';
export interface TimezoneInfo {
readonly displayName: string;
@@ -13,6 +13,77 @@ export interface LocalizedTimezoneInfo {
readonly displayNameWithUtcOffset: string;
}
export class KnownDateTimezoneFormat implements NameValue {
private static readonly allInstances: KnownDateTimezoneFormat[] = [];
private static readonly allInstancesByValue: Record<string, KnownDateTimezoneFormat> = {};
public static readonly HHColonMM = new KnownDateTimezoneFormat('±HH:mm', 'Z', /^[+-]?([0-1][0-9]|2[0-3]):[0-5][0-9]$/);
public static readonly HHMM = new KnownDateTimezoneFormat('±HHmm', 'ZZ', /^[+-]?([0-1][0-9]|2[0-3])[0-5][0-9]$/);
public readonly name: string;
public readonly value: string;
private readonly regex: RegExp;
private constructor(name: string, value: string, regex: RegExp) {
this.name = name;
this.value = value;
this.regex = regex;
KnownDateTimezoneFormat.allInstances.push(this);
KnownDateTimezoneFormat.allInstancesByValue[value] = this;
}
public isValid(dateTime: string): boolean {
return this.regex.test(dateTime);
}
public static values(): KnownDateTimezoneFormat[] {
return KnownDateTimezoneFormat.allInstances;
}
public static valueOf(value: string): KnownDateTimezoneFormat | undefined {
return KnownDateTimezoneFormat.allInstancesByValue[value];
}
public static detect(dateTime: string): KnownDateTimezoneFormat[] | undefined {
const result: KnownDateTimezoneFormat[] = [];
for (const format of KnownDateTimezoneFormat.allInstances) {
if (format.isValid(dateTime)) {
result.push(format);
}
}
return result.length > 0 ? result : undefined;
}
public static detectMany(dateTimes: string[]): KnownDateTimezoneFormat[] | undefined {
const detectedCounts: Record<string, number> = {};
for (const dateTime of dateTimes) {
const detectedFormats = KnownDateTimezoneFormat.detect(dateTime);
if (detectedFormats) {
for (const format of detectedFormats) {
detectedCounts[format.value] = (detectedCounts[format.value] || 0) + 1;
}
} else {
return undefined;
}
}
const result: KnownDateTimezoneFormat[] = [];
for (const format of KnownDateTimezoneFormat.allInstances) {
if (detectedCounts[format.value] === dateTimes.length) {
result.push(format);
}
}
return result.length > 0 ? result : undefined;
}
}
export class TimezoneTypeForStatistics implements TypeAndName {
public static readonly ApplicationTimezone = new TimezoneTypeForStatistics(0, 'Application Timezone');
public static readonly TransactionTimezone = new TimezoneTypeForStatistics(1, 'Transaction Timezone');
+33
View File
@@ -57,3 +57,36 @@ export class TransactionTagFilterType implements TypeAndName {
return TransactionTagFilterType.allInstances;
}
}
export class ImportTransactionColumnType implements TypeAndName {
private static readonly allInstances: ImportTransactionColumnType[] = [];
public static readonly TransactionTime = new ImportTransactionColumnType(1, 'Transaction Time');
public static readonly TransactionTimezone = new ImportTransactionColumnType(2, 'Transaction Timezone');
public static readonly TransactionType = new ImportTransactionColumnType(3, 'Transaction Type');
public static readonly Category = new ImportTransactionColumnType(4, 'Category');
public static readonly SubCategory = new ImportTransactionColumnType(5, 'Secondary Category');
public static readonly AccountName = new ImportTransactionColumnType(6, 'Account Name');
public static readonly AccountCurrency = new ImportTransactionColumnType(7, 'Currency');
public static readonly Amount = new ImportTransactionColumnType(8, 'Amount');
public static readonly RelatedAccountName = new ImportTransactionColumnType(9, 'Transfer In Account Name');
public static readonly RelatedAccountCurrency = new ImportTransactionColumnType(10, 'Transfer In Currency');
public static readonly RelatedAmount = new ImportTransactionColumnType(11, 'Transfer In Amount');
public static readonly GeographicLocation = new ImportTransactionColumnType(12, 'Geographic Location');
public static readonly Tags = new ImportTransactionColumnType(13, 'Tags');
public static readonly Description = new ImportTransactionColumnType(14, 'Description');
public readonly type: number;
public readonly name: string;
private constructor(type: number, name: string) {
this.type = type;
this.name = name;
ImportTransactionColumnType.allInstances.push(this);
}
public static values(): ImportTransactionColumnType[] {
return ImportTransactionColumnType.allInstances;
}
}
+39 -2
View File
@@ -2,6 +2,10 @@ import axios, { type AxiosRequestConfig, type AxiosRequestHeaders, type AxiosRes
import type { ApiResponse } from '@/core/api.ts';
import {
TransactionType
} from '@/core/transaction.ts';
import {
BASE_API_URL_PATH,
BASE_QRCODE_PATH,
@@ -426,10 +430,43 @@ export default {
deleteTransaction: (req: TransactionDeleteRequest): ApiResponsePromise<boolean> => {
return axios.post<ApiResponse<boolean>>('v1/transactions/delete.json', req);
},
parseImportTransaction: ({ fileType, importFile }: { fileType: string, importFile: File }): ApiResponsePromise<ImportTransactionResponsePageWrapper> => {
parseImportDsvFile: ({ fileType, fileEncoding, importFile }: { fileType: string, fileEncoding?: string, importFile: File }): ApiResponsePromise<string[][]> => {
return axios.postForm<ApiResponse<string[][]>>('v1/transactions/parse_dsv_file.json', {
fileType: fileType,
fileEncoding: fileEncoding,
file: importFile
}, {
timeout: DEFAULT_UPLOAD_API_TIMEOUT
} as ApiRequestConfig);
},
parseImportTransaction: ({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, geoSeparator, tagSeparator }: { fileType: string, fileEncoding?: string, importFile: File, columnMapping?: Record<number, number>, transactionTypeMapping?: Record<string, TransactionType>, hasHeaderLine?: boolean, timeFormat?: string, timezoneFormat?: string, geoSeparator?: string, tagSeparator?: string }): ApiResponsePromise<ImportTransactionResponsePageWrapper> => {
let textualColumnMapping: string | undefined = undefined;
let textualTransactionTypeMapping: string | undefined = undefined;
let textualHasHeaderLine: string | undefined = undefined;
if (columnMapping) {
textualColumnMapping = JSON.stringify(columnMapping);
}
if (transactionTypeMapping) {
textualTransactionTypeMapping = JSON.stringify(transactionTypeMapping);
}
if (hasHeaderLine) {
textualHasHeaderLine = 'true';
}
return axios.postForm<ApiResponse<ImportTransactionResponsePageWrapper>>('v1/transactions/parse_import.json', {
fileType: fileType,
file: importFile
fileEncoding: fileEncoding,
file: importFile,
columnMapping: textualColumnMapping,
transactionTypeMapping: textualTransactionTypeMapping,
hasHeaderLine: textualHasHeaderLine,
timeFormat: timeFormat,
timezoneFormat: timezoneFormat,
geoSeparator: geoSeparator,
tagSeparator: tagSeparator
}, {
timeout: DEFAULT_UPLOAD_API_TIMEOUT
} as ApiRequestConfig);
+85
View File
@@ -1097,6 +1097,12 @@
"cannot add transaction before balance modification transaction": "Transaktion kann nicht vor der Saldoänderungstransaktion hinzugefügt werden",
"balance modification transaction cannot modify transaction time": "Transaktionszeit kann für Saldoänderungstransaktion nicht geändert werden",
"transfer transaction amount cannot be less than zero": "Betrag für Überweisungstransaktion darf nicht kleiner als 0 sein",
"import file encoding is empty": "Import file encoding is empty",
"import file encoding not supported": "import file encoding is not supported",
"column mapping invalid": "Column mapping is invalid",
"transaction type mapping invalid": "Transaction type mapping is invalid",
"transaction time format invalid": "Transaction time format is invalid",
"transaction time zone format invalid": "Transaction time zone format is invalid",
"transaction category id is invalid": "Transaktionskategorie-ID ist ungültig",
"transaction category not found": "Transaktionskategorie nicht gefunden",
"transaction category type is invalid": "Transaktionskategorietyp ist ungültig",
@@ -1214,6 +1220,58 @@
"parameter invalid color": "{parameter} hat ein ungültiges Format",
"parameter invalid amount filter": "{parameter} hat ein ungültiges Format"
},
"encoding": {
"utf-8": "UTF-8",
"utf-8-bom": "UTF-8 with BOM",
"utf-16le": "UTF-16 Little Endian",
"utf-16be": "UTF-16 Big Endian",
"cp437": "OEM United States (CP-437)",
"cp863": "OEM Canadian French (CP-863)",
"cp037": "IBM EBCDIC US/Canada (CP-037)",
"cp1047": "IBM EBCDIC Open Systems (CP-1047)",
"cp1140": "IBM EBCDIC US/Canada with Euro (CP-1140)",
"iso-8859-1": "Western European (ISO-8859-1)",
"cp850": "Western European (CP-850)",
"cp858": "Western European with Euro (CP-858)",
"windows-1252": "Western European (Windows-1252)",
"iso-8859-15": "Western European (ISO-8859-15)",
"iso-8859-4": "North European (ISO-8859-4)",
"iso-8859-10": "Nordic (ISO-8859-10)",
"cp865": "Nordic (CP-865)",
"iso-8859-2": "Central European (ISO-8859-2)",
"cp852": "Central European (CP-852)",
"windows-1250": "Central European (Windows-1250)",
"iso-8859-14": "Celtic (ISO-8859-14)",
"iso-8859-3": "South European (ISO-8859-3)",
"cp860": "Portuguese (CP-860)",
"iso-8859-7": "Greek (ISO-8859-7)",
"windows-1253": "Greek (Windows-1253)",
"iso-8859-9": "Turkish (ISO-8859-9)",
"windows-1254": "Turkish (Windows-1254)",
"iso-8859-13": "Baltic (ISO-8859-13)",
"windows-1257": "Baltic (Windows-1257)",
"iso-8859-16": "South-Eastern European (ISO-8859-16)",
"iso-8859-5": "Cyrillic (ISO-8859-5)",
"cp855": "Cyrillic (CP-855)",
"cp866": "Cyrillic (CP-866)",
"windows-1251": "Cyrillic (Windows-1251)",
"koi8r": "Cyrillic (KOI8-R)",
"koi8u": "Cyrillic (KOI8-U)",
"iso-8859-6": "Arabic (ISO-8859-6)",
"windows-1256": "Arabic (Windows-1256)",
"iso-8859-8": "Hebrew (ISO-8859-8)",
"cp862": "Hebrew (CP-862)",
"windows-1255": "Hebrew (Windows-1255)",
"windows-874": "Thai (Windows-874)",
"windows-1258": "Vietnamese (Windows-1258)",
"gb18030": "Simplified Chinese (GB18030)",
"gbk": "Simplified Chinese (GBK)",
"big5": "Traditional Chinese (Big5)",
"euc-kr": "Korean (EUC-KR)",
"euc-jp": "Japanese (EUC-JP)",
"iso-2022-jp": "Japanese (ISO-2022-JP)",
"shift_jis": "Japanese (Shift_JIS)"
},
"document": {
"anchor": {
"export_and_import": {
@@ -1242,6 +1300,7 @@
"Not set": "Nicht festgelegt",
"No results": "Keine Ergebnisse",
"Unknown": "Unbekannt",
"Auto detect": "Auto detect",
"Miscellaneous": "Verschiedenes",
"Default": "Standard",
"Done": "Fertig",
@@ -1268,6 +1327,13 @@
"Color": "Farbe",
"Type": "Typ",
"Format": "Format",
"File Encoding": "File Encoding",
"Space": "Space",
"Comma": "Comma",
"Semicolon": "Semicolon",
"Tab": "Tab",
"Vertical Bar": "Vertical Bar",
"Slash": "Slash",
"All Types": "Alle Typen",
"More": "Mehr",
"All": "Alle",
@@ -1521,6 +1587,8 @@
"Income Amount": "Einnahmenbetrag",
"Transfer Out Amount": "Überweisungsbetrag (Ausgang)",
"Transfer In Amount": "Überweisungsbetrag (Eingang)",
"Transfer In Account Name": "Transfer In Account Name",
"Transfer In Currency": "Transfer In Currency",
"Show Amount": "Betrag anzeigen",
"Hide Amount": "Betrag verbergen",
"Swap Account": "Konto tauschen",
@@ -1530,6 +1598,7 @@
"Duplicate (With Geographic Location)": "Duplicate (With Geographic Location)",
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Kategorie",
"Secondary Category": "Secondary Category",
"Multiple Categories": "Mehrere Kategorien",
"Account": "Konto",
"Multiple Accounts": "Mehrere Konten",
@@ -1545,6 +1614,7 @@
"Scheduled Transaction Frequency": "Häufigkeit der geplanten Transaktion",
"Transaction Timezone": "Transaktionszeitzone",
"Same time as default timezone": "Gleiche Zeit wie Standardzeitzone",
"Transaction Type": "Transaction Type",
"Geographic Location": "Geografischer Standort",
"No Location": "Kein Standort",
"Getting Location...": "Standort wird ermittelt...",
@@ -1559,6 +1629,8 @@
"Import Transactions": "Transaktionen importieren",
"Upload File": "Datei hochladen",
"Upload Transaction Data File": "Transaktionsdatendatei hochladen",
"Define Column": "Define Column",
"Define and Check Column Mapping": "Define and Check Column Mapping",
"Check & Modify": "Überprüfen & Ändern",
"Check and Modify Your Data": "Überprüfen und Ändern Sie Ihre Daten",
"Data Import Completed": "Datenimport abgeschlossen",
@@ -1573,6 +1645,8 @@
"Month-day-year format": "Monat-Tag-Jahr-Format",
"Day-month-year format": "Tag-Monat-Jahr-Format",
"Intuit Interchange Format (IIF) File": "Intuit Interchange Format (IIF)-Datei",
"Delimiter-separated Values (DSV) File": "Delimiter-separated Values (DSV) File",
"Delimiter-separated Values (DSV) Data": "Delimiter-separated Values (DSV) Data",
"GnuCash XML Database File": "GnuCash XML-Datenbankdatei",
"Firefly III Data Export File": "Firefly III-Datenexportdatei",
"Feidee MyMoney (App) Data Export File": "Feidee MyMoney (App)-Datenexportdatei",
@@ -1581,8 +1655,19 @@
"Alipay (Web) Transaction Flow File": "Alipay (Web)-Transaktionsflussdatei",
"WeChat Pay Billing File": "WeChat Pay-Abrechnungsdatei",
"Data File": "Datendatei",
"Data to import": "Data to import",
"Please select a file to import": "Bitte wählen Sie eine Datei zum Importieren aus",
"Include Header Line": "Include Header Line",
"Time Format": "Time Format",
"Transaction Type Mapping": "Transaction Type Mapping",
"Timezone Format": "Timezone Format",
"Geographic Location Separator": "Geographic Location Separator",
"Transaction Tags Separator": "Transaction Tags Separator",
"Lines Per Page": "Lines Per Page",
"No data to import": "Keine Daten zum Importieren",
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
"Transaction type mapping is not set": "Transaction type mapping is not set",
"Transaction time format is not set": "Transaction time format is not set",
"Cannot import invalid transactions": "Ungültige Transaktionen können nicht importiert werden",
"Unable to parse import file": "Importdatei kann nicht geparst werden",
"Batch Replace Selected Expense Categories": "Ausgewählte Ausgabenkategorien im Batch ersetzen",
+85
View File
@@ -1097,6 +1097,12 @@
"cannot add transaction before balance modification transaction": "You cannot add transaction before the balance modification transaction",
"balance modification transaction cannot modify transaction time": "You cannot modify transaction time for balance modification transaction",
"transfer transaction amount cannot be less than zero": "Amount cannot be less than 0 for transfer transaction",
"import file encoding is empty": "Import file encoding is empty",
"import file encoding not supported": "import file encoding is not supported",
"column mapping invalid": "Column mapping is invalid",
"transaction type mapping invalid": "Transaction type mapping is invalid",
"transaction time format invalid": "Transaction time format is invalid",
"transaction time zone format invalid": "Transaction time zone format is invalid",
"transaction category id is invalid": "Transaction category ID is invalid",
"transaction category not found": "Transaction category is not found",
"transaction category type is invalid": "Transaction category type is invalid",
@@ -1214,6 +1220,58 @@
"parameter invalid color": "{parameter} is invalid format",
"parameter invalid amount filter": "{parameter} is invalid format"
},
"encoding": {
"utf-8": "UTF-8",
"utf-8-bom": "UTF-8 with BOM",
"utf-16le": "UTF-16 Little Endian",
"utf-16be": "UTF-16 Big Endian",
"cp437": "OEM United States (CP-437)",
"cp863": "OEM Canadian French (CP-863)",
"cp037": "IBM EBCDIC US/Canada (CP-037)",
"cp1047": "IBM EBCDIC Open Systems (CP-1047)",
"cp1140": "IBM EBCDIC US/Canada with Euro (CP-1140)",
"iso-8859-1": "Western European (ISO-8859-1)",
"cp850": "Western European (CP-850)",
"cp858": "Western European with Euro (CP-858)",
"windows-1252": "Western European (Windows-1252)",
"iso-8859-15": "Western European (ISO-8859-15)",
"iso-8859-4": "North European (ISO-8859-4)",
"iso-8859-10": "Nordic (ISO-8859-10)",
"cp865": "Nordic (CP-865)",
"iso-8859-2": "Central European (ISO-8859-2)",
"cp852": "Central European (CP-852)",
"windows-1250": "Central European (Windows-1250)",
"iso-8859-14": "Celtic (ISO-8859-14)",
"iso-8859-3": "South European (ISO-8859-3)",
"cp860": "Portuguese (CP-860)",
"iso-8859-7": "Greek (ISO-8859-7)",
"windows-1253": "Greek (Windows-1253)",
"iso-8859-9": "Turkish (ISO-8859-9)",
"windows-1254": "Turkish (Windows-1254)",
"iso-8859-13": "Baltic (ISO-8859-13)",
"windows-1257": "Baltic (Windows-1257)",
"iso-8859-16": "South-Eastern European (ISO-8859-16)",
"iso-8859-5": "Cyrillic (ISO-8859-5)",
"cp855": "Cyrillic (CP-855)",
"cp866": "Cyrillic (CP-866)",
"windows-1251": "Cyrillic (Windows-1251)",
"koi8r": "Cyrillic (KOI8-R)",
"koi8u": "Cyrillic (KOI8-U)",
"iso-8859-6": "Arabic (ISO-8859-6)",
"windows-1256": "Arabic (Windows-1256)",
"iso-8859-8": "Hebrew (ISO-8859-8)",
"cp862": "Hebrew (CP-862)",
"windows-1255": "Hebrew (Windows-1255)",
"windows-874": "Thai (Windows-874)",
"windows-1258": "Vietnamese (Windows-1258)",
"gb18030": "Simplified Chinese (GB18030)",
"gbk": "Simplified Chinese (GBK)",
"big5": "Traditional Chinese (Big5)",
"euc-kr": "Korean (EUC-KR)",
"euc-jp": "Japanese (EUC-JP)",
"iso-2022-jp": "Japanese (ISO-2022-JP)",
"shift_jis": "Japanese (Shift_JIS)"
},
"document": {
"anchor": {
"export_and_import": {
@@ -1242,6 +1300,7 @@
"Not set": "Not set",
"No results": "No results",
"Unknown": "Unknown",
"Auto detect": "Auto detect",
"Miscellaneous": "Miscellaneous",
"Default": "Default",
"Done": "Done",
@@ -1268,6 +1327,13 @@
"Color": "Color",
"Type": "Type",
"Format": "Format",
"File Encoding": "File Encoding",
"Space": "Space",
"Comma": "Comma",
"Semicolon": "Semicolon",
"Tab": "Tab",
"Vertical Bar": "Vertical Bar",
"Slash": "Slash",
"All Types": "All Types",
"More": "More",
"All": "All",
@@ -1521,6 +1587,8 @@
"Income Amount": "Income Amount",
"Transfer Out Amount": "Transfer Out Amount",
"Transfer In Amount": "Transfer In Amount",
"Transfer In Account Name": "Transfer In Account Name",
"Transfer In Currency": "Transfer In Currency",
"Show Amount": "Show Amount",
"Hide Amount": "Hide Amount",
"Swap Account": "Swap Account",
@@ -1530,6 +1598,7 @@
"Duplicate (With Geographic Location)": "Duplicate (With Geographic Location)",
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Category",
"Secondary Category": "Secondary Category",
"Multiple Categories": "Multiple Categories",
"Account": "Account",
"Multiple Accounts": "Multiple Accounts",
@@ -1545,6 +1614,7 @@
"Scheduled Transaction Frequency": "Scheduled Transaction Frequency",
"Transaction Timezone": "Transaction Timezone",
"Same time as default timezone": "Same time as default timezone",
"Transaction Type": "Transaction Type",
"Geographic Location": "Geographic Location",
"No Location": "No Location",
"Getting Location...": "Getting Location...",
@@ -1559,6 +1629,8 @@
"Import Transactions": "Import Transactions",
"Upload File": "Upload File",
"Upload Transaction Data File": "Upload Transaction Data File",
"Define Column": "Define Column",
"Define and Check Column Mapping": "Define and Check Column Mapping",
"Check & Modify": "Check & Modify",
"Check and Modify Your Data": "Check and Modify Your Data",
"Data Import Completed": "Data Import Completed",
@@ -1573,6 +1645,8 @@
"Month-day-year format": "Month-day-year format",
"Day-month-year format": "Day-month-year format",
"Intuit Interchange Format (IIF) File": "Intuit Interchange Format (IIF) File",
"Delimiter-separated Values (DSV) File": "Delimiter-separated Values (DSV) File",
"Delimiter-separated Values (DSV) Data": "Delimiter-separated Values (DSV) Data",
"GnuCash XML Database File": "GnuCash XML Database File",
"Firefly III Data Export File": "Firefly III Data Export File",
"Feidee MyMoney (App) Data Export File": "Feidee MyMoney (App) Data Export File",
@@ -1581,8 +1655,19 @@
"Alipay (Web) Transaction Flow File": "Alipay (Web) Transaction Flow File",
"WeChat Pay Billing File": "WeChat Pay Billing File",
"Data File": "Data File",
"Data to import": "Data to import",
"Please select a file to import": "Please select a file to import",
"Include Header Line": "Include Header Line",
"Time Format": "Time Format",
"Transaction Type Mapping": "Transaction Type Mapping",
"Timezone Format": "Timezone Format",
"Geographic Location Separator": "Geographic Location Separator",
"Transaction Tags Separator": "Transaction Tags Separator",
"Lines Per Page": "Lines Per Page",
"No data to import": "No data to import",
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
"Transaction type mapping is not set": "Transaction type mapping is not set",
"Transaction time format is not set": "Transaction time format is not set",
"Cannot import invalid transactions": "Cannot import invalid transactions",
"Unable to parse import file": "Unable to parse import file",
"Batch Replace Selected Expense Categories": "Batch Replace Selected Expense Categories",
+85
View File
@@ -1097,6 +1097,12 @@
"cannot add transaction before balance modification transaction": "No puede agregar una transacción antes de la transacción de modificación del saldo",
"balance modification transaction cannot modify transaction time": "No puede modificar el tiempo de transacción para la transacción de modificación de saldo",
"transfer transaction amount cannot be less than zero": "El Importe no puede ser menor que 0 para la transacción de transferencia",
"import file encoding is empty": "Import file encoding is empty",
"import file encoding not supported": "import file encoding is not supported",
"column mapping invalid": "Column mapping is invalid",
"transaction type mapping invalid": "Transaction type mapping is invalid",
"transaction time format invalid": "Transaction time format is invalid",
"transaction time zone format invalid": "Transaction time zone format is invalid",
"transaction category id is invalid": "El ID de categoría de transacción no es válido",
"transaction category not found": "No se encuentra la categoría de transacción",
"transaction category type is invalid": "El tipo de categoría de transacción no es válido",
@@ -1214,6 +1220,58 @@
"parameter invalid color": "{parameter} es un formato no válido",
"parameter invalid amount filter": "{parameter} es un formato no válido"
},
"encoding": {
"utf-8": "UTF-8",
"utf-8-bom": "UTF-8 with BOM",
"utf-16le": "UTF-16 Little Endian",
"utf-16be": "UTF-16 Big Endian",
"cp437": "OEM United States (CP-437)",
"cp863": "OEM Canadian French (CP-863)",
"cp037": "IBM EBCDIC US/Canada (CP-037)",
"cp1047": "IBM EBCDIC Open Systems (CP-1047)",
"cp1140": "IBM EBCDIC US/Canada with Euro (CP-1140)",
"iso-8859-1": "Western European (ISO-8859-1)",
"cp850": "Western European (CP-850)",
"cp858": "Western European with Euro (CP-858)",
"windows-1252": "Western European (Windows-1252)",
"iso-8859-15": "Western European (ISO-8859-15)",
"iso-8859-4": "North European (ISO-8859-4)",
"iso-8859-10": "Nordic (ISO-8859-10)",
"cp865": "Nordic (CP-865)",
"iso-8859-2": "Central European (ISO-8859-2)",
"cp852": "Central European (CP-852)",
"windows-1250": "Central European (Windows-1250)",
"iso-8859-14": "Celtic (ISO-8859-14)",
"iso-8859-3": "South European (ISO-8859-3)",
"cp860": "Portuguese (CP-860)",
"iso-8859-7": "Greek (ISO-8859-7)",
"windows-1253": "Greek (Windows-1253)",
"iso-8859-9": "Turkish (ISO-8859-9)",
"windows-1254": "Turkish (Windows-1254)",
"iso-8859-13": "Baltic (ISO-8859-13)",
"windows-1257": "Baltic (Windows-1257)",
"iso-8859-16": "South-Eastern European (ISO-8859-16)",
"iso-8859-5": "Cyrillic (ISO-8859-5)",
"cp855": "Cyrillic (CP-855)",
"cp866": "Cyrillic (CP-866)",
"windows-1251": "Cyrillic (Windows-1251)",
"koi8r": "Cyrillic (KOI8-R)",
"koi8u": "Cyrillic (KOI8-U)",
"iso-8859-6": "Arabic (ISO-8859-6)",
"windows-1256": "Arabic (Windows-1256)",
"iso-8859-8": "Hebrew (ISO-8859-8)",
"cp862": "Hebrew (CP-862)",
"windows-1255": "Hebrew (Windows-1255)",
"windows-874": "Thai (Windows-874)",
"windows-1258": "Vietnamese (Windows-1258)",
"gb18030": "Simplified Chinese (GB18030)",
"gbk": "Simplified Chinese (GBK)",
"big5": "Traditional Chinese (Big5)",
"euc-kr": "Korean (EUC-KR)",
"euc-jp": "Japanese (EUC-JP)",
"iso-2022-jp": "Japanese (ISO-2022-JP)",
"shift_jis": "Japanese (Shift_JIS)"
},
"document": {
"anchor": {
"export_and_import": {
@@ -1242,6 +1300,7 @@
"Not set": "No establecido",
"No results": "Sin resultados",
"Unknown": "Desconocido",
"Auto detect": "Auto detect",
"Miscellaneous": "Misceláneas",
"Default": "Por defecto",
"Done": "Hecho",
@@ -1268,6 +1327,13 @@
"Color": "Color",
"Type": "Tipo",
"Format": "Formato",
"File Encoding": "File Encoding",
"Space": "Space",
"Comma": "Comma",
"Semicolon": "Semicolon",
"Tab": "Tab",
"Vertical Bar": "Vertical Bar",
"Slash": "Slash",
"All Types": "Todos los tipos",
"More": "Más",
"All": "Todo",
@@ -1521,6 +1587,8 @@
"Income Amount": "Importe de ingresos",
"Transfer Out Amount": "Importe de transferencias enviadas",
"Transfer In Amount": "Importe de transferencias recibidas",
"Transfer In Account Name": "Transfer In Account Name",
"Transfer In Currency": "Transfer In Currency",
"Show Amount": "Mostrar importe",
"Hide Amount": "Ocultar importe",
"Swap Account": "Intercambiar cuenta",
@@ -1530,6 +1598,7 @@
"Duplicate (With Geographic Location)": "Duplicate (With Geographic Location)",
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Categoría",
"Secondary Category": "Secondary Category",
"Multiple Categories": "Múltiples categorías",
"Account": "Cuenta",
"Multiple Accounts": "Varias cuentas",
@@ -1545,6 +1614,7 @@
"Scheduled Transaction Frequency": "Frecuencia de transacciones programadas",
"Transaction Timezone": "Zona horaria de transacción",
"Same time as default timezone": "Misma hora que la zona horaria predeterminada",
"Transaction Type": "Transaction Type",
"Geographic Location": "Ubicación geográfica",
"No Location": "Sin ubicación",
"Getting Location...": "Obteniendo ubicación...",
@@ -1559,6 +1629,8 @@
"Import Transactions": "Importar transacciones",
"Upload File": "Cargar archivo",
"Upload Transaction Data File": "Cargar archivo de datos de transacción",
"Define Column": "Define Column",
"Define and Check Column Mapping": "Define and Check Column Mapping",
"Check & Modify": "Verificar y modificar",
"Check and Modify Your Data": "Verifique y modifique sus datos",
"Data Import Completed": "Importación de datos completada",
@@ -1573,6 +1645,8 @@
"Month-day-year format": "Formato mes-día-año",
"Day-month-year format": "Formato día-mes-año",
"Intuit Interchange Format (IIF) File": "Archivo de formato de intercambio Intuit (IIF)",
"Delimiter-separated Values (DSV) File": "Delimiter-separated Values (DSV) File",
"Delimiter-separated Values (DSV) Data": "Delimiter-separated Values (DSV) Data",
"GnuCash XML Database File": "Archivo de base de datos XML GnuCash",
"Firefly III Data Export File": "Archivo de exportación de datos de Firefly III",
"Feidee MyMoney (App) Data Export File": "Archivo de exportación de datos Feidee MyMoney (aplicación)",
@@ -1581,8 +1655,19 @@
"Alipay (Web) Transaction Flow File": "Archivo de flujo de transacciones de Alipay (web)",
"WeChat Pay Billing File": "Archivo de facturación de pago de WeChat",
"Data File": "Archivo de datos",
"Data to import": "Data to import",
"Please select a file to import": "Please select a file to import",
"Include Header Line": "Include Header Line",
"Time Format": "Time Format",
"Transaction Type Mapping": "Transaction Type Mapping",
"Timezone Format": "Timezone Format",
"Geographic Location Separator": "Geographic Location Separator",
"Transaction Tags Separator": "Transaction Tags Separator",
"Lines Per Page": "Lines Per Page",
"No data to import": "No hay datos para importar",
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
"Transaction type mapping is not set": "Transaction type mapping is not set",
"Transaction time format is not set": "Transaction time format is not set",
"Cannot import invalid transactions": "No se pueden importar transacciones no válidas",
"Unable to parse import file": "No se puede analizar el archivo de importación",
"Batch Replace Selected Expense Categories": "Reemplazar por lotes categorías de gastos seleccionadas",
+20 -1
View File
@@ -66,7 +66,8 @@ import {
import {
TransactionEditScopeType,
TransactionTagFilterType
TransactionTagFilterType,
ImportTransactionColumnType
} from '@/core/transaction.ts';
import {
@@ -85,6 +86,7 @@ import {
import {
type LocalizedImportFileType,
type LocalizedImportFileTypeSubType,
type LocalizedImportFileTypeSupportedEncodings,
type LocalizedImportFileDocument,
} from '@/core/file.ts';
@@ -1138,11 +1140,27 @@ export function useI18n() {
}
}
const supportedEncodings: LocalizedImportFileTypeSupportedEncodings[] = [];
if (fileType.supportedEncodings) {
for (let i = 0; i < fileType.supportedEncodings.length; i++) {
const encoding = fileType.supportedEncodings[i];
const localizedEncoding: LocalizedImportFileTypeSupportedEncodings = {
encoding: encoding,
displayName: t(`encoding.${encoding}`)
};
supportedEncodings.push(localizedEncoding);
}
}
const localizedFileType: LocalizedImportFileType = {
type: fileType.type,
displayName: t(fileType.name),
extensions: fileType.extensions,
subTypes: subTypes.length ? subTypes : undefined,
supportedEncodings: supportedEncodings.length ? supportedEncodings : undefined,
dataFromTextbox: fileType.dataFromTextbox,
document: document
};
allSupportedImportFileTypes.push(localizedFileType);
@@ -1680,6 +1698,7 @@ export function useI18n() {
getAllTransactionEditScopeTypes: () => getLocalizedDisplayNameAndType(TransactionEditScopeType.values()),
getAllTransactionTagFilterTypes: () => getLocalizedDisplayNameAndType(TransactionTagFilterType.values()),
getAllTransactionScheduledFrequencyTypes: () => getLocalizedDisplayNameAndType(ScheduledTemplateFrequencyType.values()),
getAllImportTransactionColumnTypes: () => getLocalizedDisplayNameAndType(ImportTransactionColumnType.values()),
getAllTransactionDefaultCategories,
getAllDisplayExchangeRates,
getAllSupportedImportFileTypes,
+85
View File
@@ -1097,6 +1097,12 @@
"cannot add transaction before balance modification transaction": "Нельзя добавить транзакцию до транзакции изменения баланса",
"balance modification transaction cannot modify transaction time": "Нельзя изменить время транзакции для транзакции изменения баланса",
"transfer transaction amount cannot be less than zero": "Сумма перевода не может быть меньше нуля",
"import file encoding is empty": "Import file encoding is empty",
"import file encoding not supported": "import file encoding is not supported",
"column mapping invalid": "Column mapping is invalid",
"transaction type mapping invalid": "Transaction type mapping is invalid",
"transaction time format invalid": "Transaction time format is invalid",
"transaction time zone format invalid": "Transaction time zone format is invalid",
"transaction category id is invalid": "ID категории транзакции недействителен",
"transaction category not found": "Категория транзакции не найдена",
"transaction category type is invalid": "Тип категории транзакции недействителен",
@@ -1214,6 +1220,58 @@
"parameter invalid color": "{parameter} имеет неверный формат",
"parameter invalid amount filter": "{parameter} имеет неверный формат"
},
"encoding": {
"utf-8": "UTF-8",
"utf-8-bom": "UTF-8 with BOM",
"utf-16le": "UTF-16 Little Endian",
"utf-16be": "UTF-16 Big Endian",
"cp437": "OEM United States (CP-437)",
"cp863": "OEM Canadian French (CP-863)",
"cp037": "IBM EBCDIC US/Canada (CP-037)",
"cp1047": "IBM EBCDIC Open Systems (CP-1047)",
"cp1140": "IBM EBCDIC US/Canada with Euro (CP-1140)",
"iso-8859-1": "Western European (ISO-8859-1)",
"cp850": "Western European (CP-850)",
"cp858": "Western European with Euro (CP-858)",
"windows-1252": "Western European (Windows-1252)",
"iso-8859-15": "Western European (ISO-8859-15)",
"iso-8859-4": "North European (ISO-8859-4)",
"iso-8859-10": "Nordic (ISO-8859-10)",
"cp865": "Nordic (CP-865)",
"iso-8859-2": "Central European (ISO-8859-2)",
"cp852": "Central European (CP-852)",
"windows-1250": "Central European (Windows-1250)",
"iso-8859-14": "Celtic (ISO-8859-14)",
"iso-8859-3": "South European (ISO-8859-3)",
"cp860": "Portuguese (CP-860)",
"iso-8859-7": "Greek (ISO-8859-7)",
"windows-1253": "Greek (Windows-1253)",
"iso-8859-9": "Turkish (ISO-8859-9)",
"windows-1254": "Turkish (Windows-1254)",
"iso-8859-13": "Baltic (ISO-8859-13)",
"windows-1257": "Baltic (Windows-1257)",
"iso-8859-16": "South-Eastern European (ISO-8859-16)",
"iso-8859-5": "Cyrillic (ISO-8859-5)",
"cp855": "Cyrillic (CP-855)",
"cp866": "Cyrillic (CP-866)",
"windows-1251": "Cyrillic (Windows-1251)",
"koi8r": "Cyrillic (KOI8-R)",
"koi8u": "Cyrillic (KOI8-U)",
"iso-8859-6": "Arabic (ISO-8859-6)",
"windows-1256": "Arabic (Windows-1256)",
"iso-8859-8": "Hebrew (ISO-8859-8)",
"cp862": "Hebrew (CP-862)",
"windows-1255": "Hebrew (Windows-1255)",
"windows-874": "Thai (Windows-874)",
"windows-1258": "Vietnamese (Windows-1258)",
"gb18030": "Simplified Chinese (GB18030)",
"gbk": "Simplified Chinese (GBK)",
"big5": "Traditional Chinese (Big5)",
"euc-kr": "Korean (EUC-KR)",
"euc-jp": "Japanese (EUC-JP)",
"iso-2022-jp": "Japanese (ISO-2022-JP)",
"shift_jis": "Japanese (Shift_JIS)"
},
"document": {
"anchor": {
"export_and_import": {
@@ -1242,6 +1300,7 @@
"Not set": "Не установлено",
"No results": "Нет результатов",
"Unknown": "Неизвестно",
"Auto detect": "Auto detect",
"Miscellaneous": "Разное",
"Default": "По умолчанию",
"Done": "Готово",
@@ -1268,6 +1327,13 @@
"Color": "Цвет",
"Type": "Тип",
"Format": "Формат",
"File Encoding": "File Encoding",
"Space": "Space",
"Comma": "Comma",
"Semicolon": "Semicolon",
"Tab": "Tab",
"Vertical Bar": "Vertical Bar",
"Slash": "Slash",
"All Types": "Все типы",
"More": "Еще",
"All": "Все",
@@ -1521,6 +1587,8 @@
"Income Amount": "Сумма дохода",
"Transfer Out Amount": "Сумма перевода (исходящий)",
"Transfer In Amount": "Сумма перевода (входящий)",
"Transfer In Account Name": "Transfer In Account Name",
"Transfer In Currency": "Transfer In Currency",
"Show Amount": "Показать сумму",
"Hide Amount": "Скрыть сумму",
"Swap Account": "Поменять счет",
@@ -1530,6 +1598,7 @@
"Duplicate (With Geographic Location)": "Duplicate (With Geographic Location)",
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Категория",
"Secondary Category": "Secondary Category",
"Multiple Categories": "Несколько категорий",
"Account": "Счет",
"Multiple Accounts": "Несколько счетов",
@@ -1545,6 +1614,7 @@
"Scheduled Transaction Frequency": "Частота запланированных транзакций",
"Transaction Timezone": "Часовой пояс транзакции",
"Same time as default timezone": "То же время, что и в часовом поясе по умолчанию",
"Transaction Type": "Transaction Type",
"Geographic Location": "Географическое местоположение",
"No Location": "Нет местоположения",
"Getting Location...": "Получение местоположения...",
@@ -1559,6 +1629,8 @@
"Import Transactions": "Импорт транзакций",
"Upload File": "Загрузить файл",
"Upload Transaction Data File": "Загрузить файл данных транзакций",
"Define Column": "Define Column",
"Define and Check Column Mapping": "Define and Check Column Mapping",
"Check & Modify": "Проверить и изменить",
"Check and Modify Your Data": "Проверьте и измените свои данные",
"Data Import Completed": "Импорт данных завершен",
@@ -1573,6 +1645,8 @@
"Month-day-year format": "Формат месяц-день-год",
"Day-month-year format": "Формат день-месяц-год",
"Intuit Interchange Format (IIF) File": "Файл Intuit Interchange Format (IIF)",
"Delimiter-separated Values (DSV) File": "Delimiter-separated Values (DSV) File",
"Delimiter-separated Values (DSV) Data": "Delimiter-separated Values (DSV) Data",
"GnuCash XML Database File": "Файл базы данных GnuCash XML",
"Firefly III Data Export File": "Файл экспорта данных Firefly III",
"Feidee MyMoney (App) Data Export File": "Файл экспорта данных Feidee MyMoney (приложение)",
@@ -1581,8 +1655,19 @@
"Alipay (Web) Transaction Flow File": "Файл потока транзакций Alipay (веб)",
"WeChat Pay Billing File": "Файл выставления счетов WeChat Pay",
"Data File": "Файл данных",
"Data to import": "Data to import",
"Please select a file to import": "Please select a file to import",
"Include Header Line": "Include Header Line",
"Time Format": "Time Format",
"Transaction Type Mapping": "Transaction Type Mapping",
"Timezone Format": "Timezone Format",
"Geographic Location Separator": "Geographic Location Separator",
"Transaction Tags Separator": "Transaction Tags Separator",
"Lines Per Page": "Lines Per Page",
"No data to import": "Нет данных для импорта",
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
"Transaction type mapping is not set": "Transaction type mapping is not set",
"Transaction time format is not set": "Transaction time format is not set",
"Cannot import invalid transactions": "Невозможно импортировать недействительные транзакции",
"Unable to parse import file": "Не удалось обработать файл импорта",
"Batch Replace Selected Expense Categories": "Пакетная замена выбранных категорий расходов",
+85
View File
@@ -1097,6 +1097,12 @@
"cannot add transaction before balance modification transaction": "Bạn không thể thêm giao dịch trước giao dịch sửa đổi số dư",
"balance modification transaction cannot modify transaction time": "Bạn không thể sửa đổi thời gian giao dịch cho giao dịch sửa đổi số dư",
"transfer transaction amount cannot be less than zero": "Số tiền không thể nhỏ hơn 0 đối với giao dịch chuyển khoản",
"import file encoding is empty": "Import file encoding is empty",
"import file encoding not supported": "import file encoding is not supported",
"column mapping invalid": "Column mapping is invalid",
"transaction type mapping invalid": "Transaction type mapping is invalid",
"transaction time format invalid": "Transaction time format is invalid",
"transaction time zone format invalid": "Transaction time zone format is invalid",
"transaction category id is invalid": "ID danh mục giao dịch không hợp lệ",
"transaction category not found": "Không tìm thấy danh mục giao dịch",
"transaction category type is invalid": "Loại danh mục giao dịch không hợp lệ",
@@ -1214,6 +1220,58 @@
"parameter invalid color": "{parameter} có định dạng không hợp lệ",
"parameter invalid amount filter": "{parameter} có định dạng không hợp lệ"
},
"encoding": {
"utf-8": "UTF-8",
"utf-8-bom": "UTF-8 with BOM",
"utf-16le": "UTF-16 Little Endian",
"utf-16be": "UTF-16 Big Endian",
"cp437": "OEM United States (CP-437)",
"cp863": "OEM Canadian French (CP-863)",
"cp037": "IBM EBCDIC US/Canada (CP-037)",
"cp1047": "IBM EBCDIC Open Systems (CP-1047)",
"cp1140": "IBM EBCDIC US/Canada with Euro (CP-1140)",
"iso-8859-1": "Western European (ISO-8859-1)",
"cp850": "Western European (CP-850)",
"cp858": "Western European with Euro (CP-858)",
"windows-1252": "Western European (Windows-1252)",
"iso-8859-15": "Western European (ISO-8859-15)",
"iso-8859-4": "North European (ISO-8859-4)",
"iso-8859-10": "Nordic (ISO-8859-10)",
"cp865": "Nordic (CP-865)",
"iso-8859-2": "Central European (ISO-8859-2)",
"cp852": "Central European (CP-852)",
"windows-1250": "Central European (Windows-1250)",
"iso-8859-14": "Celtic (ISO-8859-14)",
"iso-8859-3": "South European (ISO-8859-3)",
"cp860": "Portuguese (CP-860)",
"iso-8859-7": "Greek (ISO-8859-7)",
"windows-1253": "Greek (Windows-1253)",
"iso-8859-9": "Turkish (ISO-8859-9)",
"windows-1254": "Turkish (Windows-1254)",
"iso-8859-13": "Baltic (ISO-8859-13)",
"windows-1257": "Baltic (Windows-1257)",
"iso-8859-16": "South-Eastern European (ISO-8859-16)",
"iso-8859-5": "Cyrillic (ISO-8859-5)",
"cp855": "Cyrillic (CP-855)",
"cp866": "Cyrillic (CP-866)",
"windows-1251": "Cyrillic (Windows-1251)",
"koi8r": "Cyrillic (KOI8-R)",
"koi8u": "Cyrillic (KOI8-U)",
"iso-8859-6": "Arabic (ISO-8859-6)",
"windows-1256": "Arabic (Windows-1256)",
"iso-8859-8": "Hebrew (ISO-8859-8)",
"cp862": "Hebrew (CP-862)",
"windows-1255": "Hebrew (Windows-1255)",
"windows-874": "Thai (Windows-874)",
"windows-1258": "Vietnamese (Windows-1258)",
"gb18030": "Simplified Chinese (GB18030)",
"gbk": "Simplified Chinese (GBK)",
"big5": "Traditional Chinese (Big5)",
"euc-kr": "Korean (EUC-KR)",
"euc-jp": "Japanese (EUC-JP)",
"iso-2022-jp": "Japanese (ISO-2022-JP)",
"shift_jis": "Japanese (Shift_JIS)"
},
"document": {
"anchor": {
"export_and_import": {
@@ -1242,6 +1300,7 @@
"Not set": "Not set",
"No results": "Không có kết quả",
"Unknown": "Không rõ",
"Auto detect": "Auto detect",
"Miscellaneous": "Linh tinh",
"Default": "Mặc định",
"Done": "Hoàn tất",
@@ -1268,6 +1327,13 @@
"Color": "Màu sắc",
"Type": "Loại",
"Format": "Định dạng",
"File Encoding": "File Encoding",
"Space": "Space",
"Comma": "Comma",
"Semicolon": "Semicolon",
"Tab": "Tab",
"Vertical Bar": "Vertical Bar",
"Slash": "Slash",
"All Types": "Tất cả các loại",
"More": "Thêm",
"All": "Tất cả",
@@ -1521,6 +1587,8 @@
"Income Amount": "Số tiền thu nhập",
"Transfer Out Amount": "Số tiền chuyển ra",
"Transfer In Amount": "Số tiền chuyển vào",
"Transfer In Account Name": "Transfer In Account Name",
"Transfer In Currency": "Transfer In Currency",
"Show Amount": "Hiển thị số tiền",
"Hide Amount": "Ẩn số tiền",
"Swap Account": "Hoán đổi tài khoản",
@@ -1530,6 +1598,7 @@
"Duplicate (With Geographic Location)": "Duplicate (With Geographic Location)",
"Duplicate (With Time and Geographic Location)": "Duplicate (With Time and Geographic Location)",
"Category": "Danh mục",
"Secondary Category": "Secondary Category",
"Multiple Categories": "Nhiều danh mục",
"Account": "Tài khoản",
"Multiple Accounts": "Nhiều tài khoản",
@@ -1545,6 +1614,7 @@
"Scheduled Transaction Frequency": "Tần suất giao dịch theo lịch trình",
"Transaction Timezone": "Múi giờ giao dịch",
"Same time as default timezone": "Cùng thời gian với múi giờ mặc định",
"Transaction Type": "Transaction Type",
"Geographic Location": "Vị trí địa lý",
"No Location": "Không có vị trí",
"Getting Location...": "Đang lấy vị trí...",
@@ -1559,6 +1629,8 @@
"Import Transactions": "Nhập giao dịch",
"Upload File": "Tải lên tệp",
"Upload Transaction Data File": "Tải lên tệp dữ liệu giao dịch",
"Define Column": "Define Column",
"Define and Check Column Mapping": "Define and Check Column Mapping",
"Check & Modify": "Kiểm tra & Sửa đổi",
"Check and Modify Your Data": "Kiểm tra và sửa đổi dữ liệu của bạn",
"Data Import Completed": "Nhập dữ liệu hoàn tất",
@@ -1573,6 +1645,8 @@
"Month-day-year format": "Định dạng tháng-ngày-năm",
"Day-month-year format": "Định dạng ngày-tháng-năm",
"Intuit Interchange Format (IIF) File": "Tệp Intuit Interchange Format (IIF)",
"Delimiter-separated Values (DSV) File": "Delimiter-separated Values (DSV) File",
"Delimiter-separated Values (DSV) Data": "Delimiter-separated Values (DSV) Data",
"GnuCash XML Database File": "Tệp cơ sở dữ liệu XML GnuCash",
"Firefly III Data Export File": "Tệp xuất dữ liệu Firefly III",
"Feidee MyMoney (App) Data Export File": "Tệp xuất dữ liệu Feidee MyMoney (Ứng dụng)",
@@ -1581,8 +1655,19 @@
"Alipay (Web) Transaction Flow File": "Tệp luồng giao dịch Alipay (Web)",
"WeChat Pay Billing File": "Tệp thanh toán WeChat Pay",
"Data File": "Tệp dữ liệu",
"Data to import": "Data to import",
"Please select a file to import": "Please select a file to import",
"Include Header Line": "Include Header Line",
"Time Format": "Time Format",
"Transaction Type Mapping": "Transaction Type Mapping",
"Timezone Format": "Timezone Format",
"Geographic Location Separator": "Geographic Location Separator",
"Transaction Tags Separator": "Transaction Tags Separator",
"Lines Per Page": "Lines Per Page",
"No data to import": "Không có dữ liệu để nhập",
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
"Transaction type mapping is not set": "Transaction type mapping is not set",
"Transaction time format is not set": "Transaction time format is not set",
"Cannot import invalid transactions": "Không thể nhập giao dịch không hợp lệ",
"Unable to parse import file": "Không thể phân tích tệp nhập",
"Batch Replace Selected Expense Categories": "Thay thế hàng loạt các danh mục chi phí đã chọn",
+85
View File
@@ -1097,6 +1097,12 @@
"cannot add transaction before balance modification transaction": "不能添加早于修改余额的交易",
"balance modification transaction cannot modify transaction time": "您无法对修改余额的交易修改交易时间",
"transfer transaction amount cannot be less than zero": "转账交易的金额不能小于0",
"import file encoding is empty": "导入文件编码为空",
"import file encoding not supported": "导入文件编码不支持",
"column mapping invalid": "列映射无效",
"transaction type mapping invalid": "交易类型映射无效",
"transaction time format invalid": "交易时间格式无效",
"transaction time zone format invalid": "交易时区格式无效",
"transaction category id is invalid": "交易分类ID无效",
"transaction category not found": "交易分类不存在",
"transaction category type is invalid": "交易分类类型无效",
@@ -1214,6 +1220,58 @@
"parameter invalid color": "{parameter}格式错误",
"parameter invalid amount filter": "{parameter}格式错误"
},
"encoding": {
"utf-8": "UTF-8",
"utf-8-bom": "UTF-8 带签名",
"utf-16le": "UTF-16 Little Endian",
"utf-16be": "UTF-16 Big Endian",
"cp437": "OEM 美国 (CP-437)",
"cp863": "OEM 加拿大法语 (CP-863)",
"cp037": "IBM EBCDIC 美国/加拿大 (CP-037)",
"cp1047": "IBM EBCDIC 开放系统 (CP-1047)",
"cp1140": "IBM EBCDIC 美国/加拿大 含欧元 (CP-1140)",
"iso-8859-1": "西欧 (ISO-8859-1)",
"cp850": "西欧 (CP-850)",
"cp858": "西欧 含欧元 (CP-858)",
"windows-1252": "西欧 (Windows-1252)",
"iso-8859-15": "西欧 (ISO-8859-15)",
"iso-8859-4": "北欧 (ISO-8859-4)",
"iso-8859-10": "北欧 (ISO-8859-10)",
"cp865": "北欧 (CP-865)",
"iso-8859-2": "中欧 (ISO-8859-2)",
"cp852": "中欧 (CP-852)",
"windows-1250": "中欧 (Windows-1250)",
"iso-8859-14": "凯尔特语族 (ISO-8859-14)",
"iso-8859-3": "南欧 (ISO-8859-3)",
"cp860": "葡萄牙语 (CP-860)",
"iso-8859-7": "希腊语 (ISO-8859-7)",
"windows-1253": "希腊语 (Windows-1253)",
"iso-8859-9": "土耳其语 (ISO-8859-9)",
"windows-1254": "土耳其语 (Windows-1254)",
"iso-8859-13": "波罗的语族 (ISO-8859-13)",
"windows-1257": "波罗的语族 (Windows-1257)",
"iso-8859-16": "东南欧 (ISO-8859-16)",
"iso-8859-5": "西里尔文 (ISO-8859-5)",
"cp855": "西里尔文 (CP-855)",
"cp866": "西里尔文 (CP-866)",
"windows-1251": "西里尔文 (Windows-1251)",
"koi8r": "西里尔文 (KOI8-R)",
"koi8u": "西里尔文 (KOI8-U)",
"iso-8859-6": "阿拉伯语 (ISO-8859-6)",
"windows-1256": "阿拉伯语 (Windows-1256)",
"iso-8859-8": "希伯来语 (ISO-8859-8)",
"cp862": "希伯来语 (CP-862)",
"windows-1255": "希伯来语 (Windows-1255)",
"windows-874": "泰语 (Windows-874)",
"windows-1258": "越南语 (Windows-1258)",
"gb18030": "简体中文 (GB18030)",
"gbk": "简体中文 (GBK)",
"big5": "繁体中文 (Big5)",
"euc-kr": "韩语 (EUC-KR)",
"euc-jp": "日语 (EUC-JP)",
"iso-2022-jp": "日语 (ISO-2022-JP)",
"shift_jis": "日语 (Shift_JIS)"
},
"document": {
"anchor": {
"export_and_import": {
@@ -1242,6 +1300,7 @@
"Not set": "未设置",
"No results": "无结果",
"Unknown": "未知",
"Auto detect": "自动检测",
"Miscellaneous": "杂项",
"Default": "默认",
"Done": "完成",
@@ -1268,6 +1327,13 @@
"Color": "颜色",
"Type": "类型",
"Format": "格式",
"File Encoding": "文件编码",
"Space": "空格",
"Comma": "逗号",
"Semicolon": "分号",
"Tab": "制表符 Tab",
"Vertical Bar": "竖线",
"Slash": "Slash",
"All Types": "全部类型",
"More": "更多",
"All": "全部",
@@ -1521,6 +1587,8 @@
"Income Amount": "收入金额",
"Transfer Out Amount": "转出金额",
"Transfer In Amount": "转入金额",
"Transfer In Account Name": "转入账户名",
"Transfer In Currency": "转入货币",
"Show Amount": "显示金额",
"Hide Amount": "隐藏金额",
"Swap Account": "交换账户",
@@ -1530,6 +1598,7 @@
"Duplicate (With Geographic Location)": "复制 (含地理位置)",
"Duplicate (With Time and Geographic Location)": "复制 (含时间和地理位置)",
"Category": "分类",
"Secondary Category": "二级分类",
"Multiple Categories": "多个分类",
"Account": "账户",
"Multiple Accounts": "多个账户",
@@ -1545,6 +1614,7 @@
"Scheduled Transaction Frequency": "定时交易周期",
"Transaction Timezone": "交易时区",
"Same time as default timezone": "与默认时区时间相同",
"Transaction Type": "交易类型",
"Geographic Location": "地理位置",
"No Location": "没有位置",
"Getting Location...": "正在获取位置...",
@@ -1559,6 +1629,8 @@
"Import Transactions": "导入交易",
"Upload File": "上传文件",
"Upload Transaction Data File": "上传交易数据文件",
"Define Column": "定义列",
"Define and Check Column Mapping": "定义及检查列映射",
"Check & Modify": "检查及修改",
"Check and Modify Your Data": "检查及修改您的数据",
"Data Import Completed": "数据导入完成",
@@ -1573,6 +1645,8 @@
"Month-day-year format": "月-日-年 格式",
"Day-month-year format": "日-月-年 格式",
"Intuit Interchange Format (IIF) File": "Intuit Interchange Format (IIF) 文件",
"Delimiter-separated Values (DSV) File": "分隔符分隔值 (DSV) 文件",
"Delimiter-separated Values (DSV) Data": "分隔符分隔值 (DSV) 数据",
"GnuCash XML Database File": "GnuCash XML 数据库文件",
"Firefly III Data Export File": "Firefly III 数据导出文件",
"Feidee MyMoney (App) Data Export File": "随手记 (App) 数据导出文件",
@@ -1581,8 +1655,19 @@
"Alipay (Web) Transaction Flow File": "支付宝 (网页版) 交易流水文件",
"WeChat Pay Billing File": "微信支付账单文件",
"Data File": "数据文件",
"Data to import": "要导入的数据",
"Please select a file to import": "请选择要导入的文件",
"Include Header Line": "包含标题行",
"Time Format": "时间格式",
"Transaction Type Mapping": "交易类型映射",
"Timezone Format": "时区格式",
"Geographic Location Separator": "地理位置分隔符",
"Transaction Tags Separator": "交易标签分隔符",
"Lines Per Page": "每页行数",
"No data to import": "没有可以导入的数据",
"Missing transaction time, transaction type, or amount column mapping": "缺少交易时间、交易类型或金额列映射",
"Transaction type mapping is not set": "交易类型映射没有设置",
"Transaction time format is not set": "交易时间格式没有设置",
"Cannot import invalid transactions": "不能导入无效的交易",
"Unable to parse import file": "无法解析导入的文件",
"Batch Replace Selected Expense Categories": "批量替换选中的支出分类",
+28 -2
View File
@@ -1056,9 +1056,34 @@ export const useTransactionsStore = defineStore('transactions', () => {
});
}
function parseImportTransaction({ fileType, importFile }: { fileType: string, importFile: File }): Promise<ImportTransactionResponsePageWrapper> {
function parseImportDsvFile({ fileType, fileEncoding, importFile }: { fileType: string, fileEncoding?: string, importFile: File }): Promise<string[][]> {
return new Promise((resolve, reject) => {
services.parseImportTransaction({ fileType, importFile }).then(response => {
services.parseImportDsvFile({ fileType, fileEncoding, importFile }).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to parse import file' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('Unable to parse import file', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to parse import file' });
} else {
reject(error);
}
});
});
}
function parseImportTransaction({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, geoSeparator, tagSeparator }: { fileType: string, fileEncoding?: string, importFile: File, columnMapping?: Record<number, number>, transactionTypeMapping?: Record<string, TransactionType>, hasHeaderLine?: boolean, timeFormat?: string, timezoneFormat?: string, geoSeparator?: string, tagSeparator?: string }): Promise<ImportTransactionResponsePageWrapper> {
return new Promise((resolve, reject) => {
services.parseImportTransaction({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, geoSeparator, tagSeparator }).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
@@ -1215,6 +1240,7 @@ export const useTransactionsStore = defineStore('transactions', () => {
getTransaction,
saveTransaction,
deleteTransaction,
parseImportDsvFile,
parseImportTransaction,
importTransactions,
uploadTransactionPicture,
@@ -179,7 +179,19 @@
/>
</v-col>
<v-col cols="12" md="12">
<v-col cols="12" md="12" v-if="!isImportDataFromTextbox && allSupportedEncodings">
<v-select
item-title="displayName"
item-value="encoding"
:disabled="submitting"
:label="tt('File Encoding')"
:placeholder="tt('File Encoding')"
:items="allSupportedEncodings"
v-model="fileEncoding"
/>
</v-col>
<v-col cols="12" md="12" v-if="!isImportDataFromTextbox">
<v-text-field
readonly
persistent-placeholder
@@ -193,6 +205,17 @@
/>
</v-col>
<v-col cols="12" md="12" v-if="isImportDataFromTextbox">
<v-textarea
type="text"
persistent-placeholder
rows="5"
:disabled="submitting"
:placeholder="tt('Data to import')"
v-model="importData"
/>
</v-col>
<v-col cols="12" md="12" class="mb-0 pb-0" v-if="exportFileGuideDocumentUrl">
<a :href="exportFileGuideDocumentUrl" :class="{ 'disabled': submitting }" target="_blank">
<v-icon :icon="mdiHelpCircleOutline" size="16" />
@@ -202,6 +225,184 @@
</v-col>
</v-row>
</v-window-item>
<v-window-item value="defineColumn">
<v-data-table
fixed-header
fixed-footer
density="compact"
item-value="index"
:class="{ 'import-transaction-table': true, 'disabled': loading || submitting }"
:height="parsedFileLinesTableHeight"
:disable-sort="true"
:headers="parsedFileLinesHeaders"
:items="parsedFileLines"
:no-data-text="tt('No data to import')"
v-model:items-per-page="countPerPage"
v-model:page="currentPage"
>
<template #headers="{ columns }">
<tr>
<th class="text-no-wrap" :key="column.key ?? undefined" v-for="column in columns">
<span v-if="!column.key || column.key === 'index'">{{ column.title }}</span>
<div class="py-1" v-if="column.key && column.key !== 'index'">
<span>{{ getParseDataMappedColumnDisplayName(parseInt(column.key)) }}</span>
<br/>
<span>({{ column.title }})</span>
<v-menu activator="parent" location="bottom" max-height="500">
<v-list>
<v-list-item :key="columnType.type"
:append-icon="parsedFileDataColumnMapping[columnType.type] === parseInt(column.key) ? mdiCheck : undefined"
v-for="columnType in allImportTransactionColumnTypes"
@click="updateParseDataMappedColumn(parseInt(column.key), columnType.type)">
<v-list-item-title class="cursor-pointer">
{{ columnType.displayName }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</th>
</tr>
</template>
<template #bottom>
<div class="title-and-toolbar d-flex align-center text-no-wrap mt-2" v-if="parsedFileData">
<v-btn color="secondary" density="compact" variant="outlined"
:append-icon="parsedFileIncludeHeader ? mdiCheck : mdiClose"
@click="parsedFileIncludeHeader = !parsedFileIncludeHeader">{{ tt('Include Header Line') }}</v-btn>
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
:disabled="!parsedFileDataColumnMapping || !isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionType.type]) || !parsedFileAllTransactionTypes">
<span>{{ tt('Transaction Type Mapping') }}</span>
<span class="ml-1" v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionType.type]) && parsedFileAllTransactionTypes">({{ getObjectOwnFieldCount(parsedFileValidMappedTransactionTypes) || tt('None') }})</span>
<v-menu eager activator="parent" location="bottom" max-height="500"
:close-on-content-click="false">
<v-list class="pa-0">
<v-list-item class="pa-0">
<v-table class="transaction-types-popup-menu">
<tbody>
<tr :key="typeName"
v-for="typeName in parsedFileAllTransactionTypes">
<td>{{ typeName }}</td>
<td>
<v-btn-toggle class="transaction-types-toggle" density="compact" variant="outlined"
mandatory="force" divided
v-model="parsedFileTransactionTypeMapping[typeName]">
<v-btn :value="undefined">{{ tt('None') }}</v-btn>
<v-btn :value="TransactionType.ModifyBalance">{{ tt('Modify Balance') }}</v-btn>
<v-btn :value="TransactionType.Income">{{ tt('Income') }}</v-btn>
<v-btn :value="TransactionType.Expense">{{ tt('Expense') }}</v-btn>
<v-btn :value="TransactionType.Transfer">{{ tt('Transfer') }}</v-btn>
</v-btn-toggle>
</td>
</tr>
</tbody>
</v-table>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
:disabled="!parsedFileDataColumnMapping || !isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionTime.type])">
<span>{{ tt('Time Format') }}</span>
<span class="ml-1" v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionTime.type])">({{ parsedFileTimeFormat || parsedFileAutoDetectedTimeFormat || tt('Unknown') }})</span>
<v-menu eager activator="parent" location="bottom" max-height="500">
<v-list>
<v-list-item key="auto"
:append-icon="parsedFileTimeFormat === '' ? mdiCheck : undefined"
@click="parsedFileTimeFormat = ''">
<v-list-item-title class="cursor-pointer">
<span>{{ tt('Auto detect') }}</span>
<span class="ml-1" v-if="parsedFileAutoDetectedTimeFormat">({{ parsedFileAutoDetectedTimeFormat }})</span>
<span class="ml-1" v-if="!parsedFileAutoDetectedTimeFormat">({{ tt('Unknown') }})</span>
</v-list-item-title>
</v-list-item>
<v-list-item :key="dateTimeFormat.format"
:append-icon="parsedFileTimeFormat === dateTimeFormat.format ? mdiCheck : undefined"
v-for="dateTimeFormat in KnownDateTimeFormat.values()"
@click="parsedFileTimeFormat = dateTimeFormat.format">
<v-list-item-title class="cursor-pointer">
{{ dateTimeFormat.format }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionTimezone.type])">
<span>{{ tt('Timezone Format') }}</span>
<span class="ml-1" v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionTimezone.type])">({{ KnownDateTimezoneFormat.valueOf(parsedFileTimezoneFormat || parsedFileAutoDetectedTimezoneFormat || '')?.name || tt('Unknown') }})</span>
<v-menu eager activator="parent" location="bottom" max-height="500">
<v-list>
<v-list-item key="auto"
:append-icon="parsedFileTimezoneFormat === '' ? mdiCheck : undefined"
@click="parsedFileTimezoneFormat = ''">
<v-list-item-title class="cursor-pointer">
<span>{{ tt('Auto detect') }}</span>
<span class="ml-1" v-if="parsedFileAutoDetectedTimezoneFormat && KnownDateTimezoneFormat.valueOf(parsedFileAutoDetectedTimezoneFormat || '')">({{ KnownDateTimezoneFormat.valueOf(parsedFileAutoDetectedTimezoneFormat || '')?.name }})</span>
<span class="ml-1" v-if="!parsedFileAutoDetectedTimezoneFormat || !KnownDateTimezoneFormat.valueOf(parsedFileAutoDetectedTimezoneFormat || '')">({{ tt('Unknown') }})</span>
</v-list-item-title>
</v-list-item>
<v-list-item :key="timezoneFormat.value"
:append-icon="parsedFileTimezoneFormat === timezoneFormat.value ? mdiCheck : undefined"
v-for="timezoneFormat in KnownDateTimezoneFormat.values()"
@click="parsedFileTimezoneFormat = timezoneFormat.value">
<v-list-item-title class="cursor-pointer">
{{ timezoneFormat.name }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.GeographicLocation.type])">
<span>{{ tt('Geographic Location Separator') }}</span>
<span class="ml-1" v-if="parsedFileGeoLocationSeparator">({{ parsedFileGeoLocationSeparator }})</span>
<v-menu eager activator="parent" location="bottom" max-height="500">
<v-list>
<v-list-item :key="separator.value"
:append-icon="parsedFileGeoLocationSeparator === separator.value ? mdiCheck : undefined"
v-for="separator in allSeparators"
@click="parsedFileGeoLocationSeparator = separator.value">
<v-list-item-title class="cursor-pointer">
{{ separator.name }} ({{separator.value}})
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.Tags.type])">
<span>{{ tt('Transaction Tags Separator') }}</span>
<span class="ml-1" v-if="parsedFileTagSeparator">({{ parsedFileTagSeparator }})</span>
<v-menu eager activator="parent" location="bottom" max-height="500">
<v-list>
<v-list-item :key="separator.value"
:append-icon="parsedFileTagSeparator === separator.value ? mdiCheck : undefined"
v-for="separator in allSeparators"
@click="parsedFileTagSeparator = separator.value">
<v-list-item-title class="cursor-pointer">
{{ separator.name }} ({{separator.value}})
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-spacer/>
<span>{{ tt('Lines Per Page') }}</span>
<v-select class="ml-2" density="compact" max-width="100"
item-title="title"
item-value="value"
:disabled="loading || submitting"
:items="parsedFileLinesTablePageOptions"
v-model="countPerPage"
/>
<pagination-buttons density="compact"
:disabled="loading || submitting"
:totalPageCount="Math.ceil((parsedFileLines ? parsedFileLines.length : 0) / countPerPage)"
v-model="currentPage"></pagination-buttons>
</div>
</template>
</v-data-table>
</v-window-item>
<v-window-item value="checkData">
<v-data-table
fixed-header
@@ -508,9 +709,9 @@
<v-btn color="secondary" variant="tonal" :disabled="loading || submitting"
:prepend-icon="mdiClose" @click="close(false)"
v-if="currentStep !== 'finalResult'">{{ tt('Cancel') }}</v-btn>
<v-btn color="primary" :disabled="loading || submitting || !importFile"
<v-btn color="primary" :disabled="loading || submitting || (!isImportDataFromTextbox && !importFile) || (isImportDataFromTextbox && !importData)"
:append-icon="!submitting ? mdiArrowRight : undefined" @click="parseData"
v-if="currentStep === 'uploadFile'">
v-if="currentStep === 'defineColumn' || currentStep === 'uploadFile'">
{{ tt('Next') }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="submitting"></v-progress-circular>
</v-btn>
@@ -585,10 +786,12 @@ import { useTransactionsStore } from '@/stores/transaction.ts';
import { useOverviewStore } from '@/stores/overview.ts';
import { useStatisticsStore } from '@/stores/statistics.ts';
import type { NameValue } from '@/core/base.ts';
import type { NameValue, TypeAndDisplayName } from '@/core/base.ts';
import { KnownDateTimeFormat } from '@/core/datetime.ts';
import { KnownDateTimezoneFormat } from '@/core/timezone.ts';
import { CategoryType } from '@/core/category.ts';
import { TransactionType } from '@/core/transaction.ts';
import type { LocalizedImportFileType, LocalizedImportFileTypeSubType } from '@/core/file.ts';
import { TransactionType, ImportTransactionColumnType } from '@/core/transaction.ts';
import type { LocalizedImportFileType, LocalizedImportFileTypeSubType, LocalizedImportFileTypeSupportedEncodings } from '@/core/file.ts';
import { Account, type CategorizedAccountWithDisplayBalance } from '@/models/account.ts';
import type { TransactionCategory } from '@/models/transaction_category.ts';
import type { TransactionTag } from '@/models/transaction_tag.ts';
@@ -597,6 +800,9 @@ import { ImportTransaction } from '@/models/imported_transaction.ts';
import {
isString,
isNumber,
isObjectEmpty,
getObjectOwnFieldCount,
findDisplayNameByType,
objectFieldToArrayItem
} from '@/lib/common.ts';
import {
@@ -636,6 +842,8 @@ type ConfirmDialogType = InstanceType<typeof ConfirmDialog>;
type SnackBarType = InstanceType<typeof SnackBar>;
type BatchReplaceDialogType = InstanceType<typeof BatchReplaceDialog>;
type ImportTransactionDialogStep = 'uploadFile' | 'defineColumn' | 'checkData' | 'finalResult';
interface ImportTransactionDialogFilter {
minDatetime: number | null; // minDatetime or maxDatetime is null for 'All Date Range', all are not null for 'Custom Date Range'
maxDatetime: number | null;
@@ -657,6 +865,7 @@ defineProps<{
const {
tt,
getAllImportTransactionColumnTypes,
getAllSupportedImportFileTypes,
formatUnixTimeToLongDateTime,
formatAmountWithCurrency,
@@ -679,10 +888,20 @@ const fileInput = useTemplateRef<HTMLInputElement>('fileInput');
const showState = ref<boolean>(false);
const clientSessionId = ref<string>('');
const currentStep = ref<string>('uploadFile');
const currentStep = ref<ImportTransactionDialogStep>('uploadFile');
const fileType = ref<string>('ezbookkeeping');
const fileSubType = ref<string>('ezbookkeeping_csv');
const fileEncoding = ref<string>('utf-8');
const importFile = ref<File | null>(null);
const importData = ref<string>('');
const parsedFileData = ref<string[][] | undefined>(undefined);
const parsedFileIncludeHeader = ref<boolean>(true);
const parsedFileDataColumnMapping = ref<Record<number, number>>({});
const parsedFileTransactionTypeMapping = ref<Record<string, TransactionType>>({});
const parsedFileTimeFormat = ref<string>('');
const parsedFileTimezoneFormat = ref<string>('');
const parsedFileGeoLocationSeparator = ref<string>(' ');
const parsedFileTagSeparator = ref<string>(';');
const importTransactions = ref<ImportTransaction[] | undefined>(undefined);
const editingTransaction = ref<ImportTransaction | null>(null);
const editingTags = ref<string[]>([]);
@@ -713,26 +932,79 @@ const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMin
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const allSteps = computed<StepBarItem[]>(() => [
{
name: 'uploadFile',
title: tt('Upload File'),
subTitle: tt('Upload Transaction Data File')
},
{
name: 'checkData',
title: tt('Check & Modify'),
subTitle: tt('Check and Modify Your Data')
},
{
name: 'finalResult',
title: tt('Complete'),
subTitle: tt('Data Import Completed')
}
]);
const allSteps = computed<StepBarItem[]>(() => {
const steps: StepBarItem[] = [
{
name: 'uploadFile',
title: tt('Upload File'),
subTitle: tt('Upload Transaction Data File')
}
];
if (fileType.value === 'dsv' || fileType.value === 'dsv_data') {
steps.push({
name: 'defineColumn',
title: tt('Define Column'),
subTitle: tt('Define and Check Column Mapping')
});
}
steps.push(...[
{
name: 'checkData',
title: tt('Check & Modify'),
subTitle: tt('Check and Modify Your Data')
},
{
name: 'finalResult',
title: tt('Complete'),
subTitle: tt('Data Import Completed')
}
]);
return steps;
});
const allImportTransactionColumnTypes = computed<TypeAndDisplayName[]>(() => getAllImportTransactionColumnTypes());
const allSupportedImportFileTypes = computed<LocalizedImportFileType[]>(() => getAllSupportedImportFileTypes());
const allSeparators = computed<NameValue[]>(() => {
const separators: NameValue[] = [
{
name: tt('Space'),
value: ' '
},
{
name: tt('Comma'),
value: ','
},
{
name: tt('Semicolon'),
value: ';'
},
{
name: tt('Tab'),
value: '\t'
},
{
name: tt('Vertical Bar'),
value: '|'
}
];
return separators;
});
const isImportDataFromTextbox = computed<boolean>(() => {
for (const importFileType of allSupportedImportFileTypes.value) {
if (importFileType.type === fileType.value) {
return !!importFileType.dataFromTextbox;
}
}
return false;
});
const allFileSubTypes = computed<LocalizedImportFileTypeSubType[] | undefined>(() => {
for (const importFileType of allSupportedImportFileTypes.value) {
if (importFileType.type === fileType.value) {
@@ -743,6 +1015,16 @@ const allFileSubTypes = computed<LocalizedImportFileTypeSubType[] | undefined>((
return undefined;
});
const allSupportedEncodings = computed<LocalizedImportFileTypeSupportedEncodings[] | undefined>(() => {
for (const importFileType of allSupportedImportFileTypes.value) {
if (importFileType.type === fileType.value) {
return importFileType.supportedEncodings;
}
}
return undefined;
});
const allAccounts = computed<Account[]>(() => accountsStore.allPlainAccounts);
const allVisibleAccounts = computed<Account[]>(() => accountsStore.allVisiblePlainAccounts);
const allVisibleCategorizedAccounts = computed<CategorizedAccountWithDisplayBalance[]>(() => getCategorizedAccountsWithDisplayBalance(allVisibleAccounts.value, showAccountBalance.value));
@@ -799,6 +1081,182 @@ const exportFileGuideDocumentLanguageName = computed<string | undefined>(() => {
const fileName = computed<string>(() => importFile.value?.name || '');
const parsedFileLines = computed<Record<string, string>[] | undefined>(() => {
if (!parsedFileData.value) {
return undefined;
}
const allLines: Record<string, string>[] = [];
const startIndex = parsedFileIncludeHeader.value ? 1 : 0;
for (let i = startIndex, index = 1; i < parsedFileData.value.length; i++, index++) {
const line: Record<string, string> = {};
const columns = parsedFileData.value[i];
for (let j = 0; j < columns.length; j++) {
line['index'] = index.toString();
line[`column${j + 1}`] = columns[j];
}
allLines.push(line);
}
return allLines;
});
const parsedFileLinesTableHeight = computed<number | undefined>(() => {
if (countPerPage.value <= 10 || !parsedFileLines.value || parsedFileLines.value.length <= 10) {
return undefined;
} else {
return 400;
}
});
const parsedFileLinesHeaders = computed<object[]>(() => {
let maxColumnCount = 0;
if (parsedFileData.value) {
for (let i = 0; i < parsedFileData.value.length; i++) {
if (parsedFileData.value[i].length > maxColumnCount) {
maxColumnCount = parsedFileData.value[i].length;
}
}
}
const headers: object[] = [];
headers.push({ key: 'index', value: 'index', title: '#', sortable: true, nowrap: true });
for (let i = 0; i < maxColumnCount; i++) {
let title = `#${i + 1}`;
if (parsedFileIncludeHeader.value && parsedFileData.value && parsedFileData.value[0][i]) {
title = parsedFileData.value[0][i];
}
headers.push({ key: i.toString(), value: `column${i + 1}`, title: title, sortable: true, nowrap: true });
}
return headers;
});
const parsedFileLinesTablePageOptions = computed<ImportTransactionsDialogTablePageOption[]>(() => getTablePageOptions(parsedFileLines.value?.length));
const parsedFileAllTransactionTypes = computed<string[]>(() => {
if (!parsedFileData.value || !parsedFileData.value.length || !isNumber(parsedFileDataColumnMapping.value[ImportTransactionColumnType.TransactionType.type])) {
return [];
}
const allTypeMap: Record<string, boolean> = {};
const allTypes: string[] = [];
const typeColumnIndex = parsedFileDataColumnMapping.value[ImportTransactionColumnType.TransactionType.type];
const startIndex = parsedFileIncludeHeader.value ? 1 : 0;
for (let i = startIndex; i < parsedFileData.value.length; i++) {
if (parsedFileData.value[i].length <= typeColumnIndex) {
continue;
}
const type = parsedFileData.value[i][typeColumnIndex];
if (type && !allTypeMap[type]) {
allTypes.push(type);
allTypeMap[type] = true;
}
}
return allTypes;
});
const parsedFileValidMappedTransactionTypes = computed<Record<string, TransactionType>>(() => {
if (!parsedFileData.value || !parsedFileData.value.length || !isNumber(parsedFileDataColumnMapping.value[ImportTransactionColumnType.TransactionType.type])) {
return {};
}
const result: Record<string, TransactionType> = {};
if (!parsedFileTransactionTypeMapping.value) {
return result;
}
for (const name in parsedFileTransactionTypeMapping.value) {
if (!Object.prototype.hasOwnProperty.call(parsedFileTransactionTypeMapping.value, name)) {
continue;
}
const type = parsedFileTransactionTypeMapping.value[name];
if (TransactionType.ModifyBalance <= type && type <= TransactionType.Transfer) {
result[name] = type;
}
}
return result;
});
const parsedFileAutoDetectedTimeFormat = computed<string | undefined>(() => {
if (!parsedFileData.value || !parsedFileData.value.length || !isNumber(parsedFileDataColumnMapping.value[ImportTransactionColumnType.TransactionTime.type])) {
return undefined;
}
const allDateTimes: string[] = [];
const dateTimeColumnIndex = parsedFileDataColumnMapping.value[ImportTransactionColumnType.TransactionTime.type];
const startIndex = parsedFileIncludeHeader.value ? 1 : 0;
for (let i = startIndex; i < parsedFileData.value.length; i++) {
if (parsedFileData.value[i].length <= dateTimeColumnIndex) {
continue;
}
const dateTime = parsedFileData.value[i][dateTimeColumnIndex];
if (dateTime) {
allDateTimes.push(dateTime);
}
}
const detectedFormats = KnownDateTimeFormat.detectMany(allDateTimes);
if (!detectedFormats || !detectedFormats.length || detectedFormats.length > 1) {
return undefined;
}
return detectedFormats[0].format;
});
const parsedFileAutoDetectedTimezoneFormat = computed<string | undefined>(() => {
if (!parsedFileData.value || !parsedFileData.value.length || !isNumber(parsedFileDataColumnMapping.value[ImportTransactionColumnType.TransactionTimezone.type])) {
return undefined;
}
const allTimezones: string[] = [];
const timezoneColumnIndex = parsedFileDataColumnMapping.value[ImportTransactionColumnType.TransactionTimezone.type];
const startIndex = parsedFileIncludeHeader.value ? 1 : 0;
for (let i = startIndex; i < parsedFileData.value.length; i++) {
if (parsedFileData.value[i].length <= timezoneColumnIndex) {
continue;
}
const timezone = parsedFileData.value[i][timezoneColumnIndex];
if (timezone) {
allTimezones.push(timezone);
}
}
const detectedFormats = KnownDateTimezoneFormat.detectMany(allTimezones);
if (!detectedFormats || !detectedFormats.length || detectedFormats.length > 1) {
return undefined;
}
return detectedFormats[0].value;
});
const importTransactionsTableHeight = computed<number | undefined>(() => {
if (countPerPage.value <= 10 || !importTransactions.value || importTransactions.value.length <= 10) {
return undefined;
@@ -821,30 +1279,7 @@ const importTransactionHeaders = computed<object[]>(() => {
];
});
const importTransactionsTablePageOptions = computed<ImportTransactionsDialogTablePageOption[]>(() => {
const pageOptions: ImportTransactionsDialogTablePageOption[] = [];
if (!importTransactions.value || importTransactions.value.length < 1) {
pageOptions.push({ value: -1, title: tt('All') });
return pageOptions;
}
const availableCountPerPage = [ 5, 10, 15, 20, 25, 30, 50 ];
for (let i = 0; i < availableCountPerPage.length; i++) {
const count = availableCountPerPage[i];
if (importTransactions.value.length < count) {
break;
}
pageOptions.push({ value: count, title: count.toString() });
}
pageOptions.push({ value: -1, title: tt('All') });
return pageOptions;
});
const importTransactionsTablePageOptions = computed<ImportTransactionsDialogTablePageOption[]>(() => getTablePageOptions(importTransactions.value?.length));
const totalPageCount = computed<number>(() => {
if (!importTransactions.value || importTransactions.value.length < 1) {
@@ -1052,6 +1487,55 @@ const displayFilterCustomDateRange = computed<string>(() => {
return `${minDisplayTime} - ${maxDisplayTime}`
});
function getTablePageOptions(linesCount?: number): ImportTransactionsDialogTablePageOption[] {
const pageOptions: ImportTransactionsDialogTablePageOption[] = [];
if (!linesCount || linesCount < 1) {
pageOptions.push({ value: -1, title: tt('All') });
return pageOptions;
}
const availableCountPerPage = [ 5, 10, 15, 20, 25, 30, 50 ];
for (let i = 0; i < availableCountPerPage.length; i++) {
const count = availableCountPerPage[i];
if (linesCount < count) {
break;
}
pageOptions.push({ value: count, title: count.toString() });
}
pageOptions.push({ value: -1, title: tt('All') });
return pageOptions;
}
function getParseDataMappedColumnDisplayName(columnIndex: number): string {
for (const columnType in parsedFileDataColumnMapping.value) {
if (parsedFileDataColumnMapping.value[columnType] === columnIndex) {
return findDisplayNameByType(allImportTransactionColumnTypes.value, parseInt(columnType)) || tt('Unspecified');
}
}
return tt('Unspecified');
}
function updateParseDataMappedColumn(columnIndex: number, columnType: number): void {
if (parsedFileDataColumnMapping.value[columnType] === columnIndex) {
delete parsedFileDataColumnMapping.value[columnType];
} else {
parsedFileDataColumnMapping.value[columnType] = columnIndex;
}
for (const otherColumnType in parsedFileDataColumnMapping.value) {
if (otherColumnType !== columnType.toString() && parsedFileDataColumnMapping.value[otherColumnType] === columnIndex) {
delete parsedFileDataColumnMapping.value[otherColumnType];
}
}
}
function isTransactionDisplayed(transaction: ImportTransaction): boolean {
if (isNumber(filters.value.minDatetime) && isNumber(filters.value.maxDatetime) && (transaction.time < filters.value.minDatetime || transaction.time > filters.value.maxDatetime)) {
return false;
@@ -1328,8 +1812,18 @@ function getCurrentInvalidTagNames(): NameValue[] {
function open(): Promise<void> {
fileType.value = 'ezbookkeeping';
fileSubType.value = 'ezbookkeeping_csv';
fileEncoding.value = 'utf-8';
currentStep.value = 'uploadFile';
importFile.value = null;
importData.value = '';
parsedFileData.value = undefined;
parsedFileIncludeHeader.value = true;
parsedFileDataColumnMapping.value = {};
parsedFileTransactionTypeMapping.value = {};
parsedFileTimeFormat.value = '';
parsedFileTimezoneFormat.value = '';
parsedFileGeoLocationSeparator.value = ' ';
parsedFileTagSeparator.value = ';';
importTransactions.value = undefined;
editingTransaction.value = null;
editingTags.value = [];
@@ -1396,52 +1890,165 @@ function setImportFile(event: Event): void {
}
function parseData(): void {
if (!importFile.value) {
snackbar.value?.showError('Please select a file to import');
return;
}
submitting.value = true;
let uploadFile: File;
let type: string = fileType.value;
let encoding: string | undefined = undefined;
if (allFileSubTypes.value) {
type = fileSubType.value;
}
transactionsStore.parseImportTransaction({
fileType: type,
importFile: importFile.value
}).then(response => {
const parsedTransactions: ImportTransaction[] = [];
if (allSupportedEncodings.value) {
encoding = fileEncoding.value;
}
if (response.items) {
for (let i = 0; i < response.items.length; i++) {
const parsedTransaction = ImportTransaction.of(response.items[i], i);
parsedTransactions.push(parsedTransaction);
if (!isImportDataFromTextbox.value) {
if (!importFile.value) {
snackbar.value?.showError('Please select a file to import');
return;
}
uploadFile = importFile.value;
} else if (isImportDataFromTextbox.value) {
if (!importData.value) {
snackbar.value?.showError('No data to import');
return;
}
if (type === 'custom_csv') {
uploadFile = new File([importData.value], 'import.csv', { type: 'text/csv' });
} else if (type === 'custom_tsv') {
uploadFile = new File([importData.value], 'import.tsv', { type: 'text/tab-separated-values' });
} else {
snackbar.value?.showError('Parameter Invalid');
return;
}
encoding = 'utf-8';
} else { // should not happen, but ts would check whether uploadFile has been assigned a value
snackbar.value?.showMessage('An error occurred');
return;
}
const isDsvFileType: boolean = fileType.value === 'dsv' || fileType.value === 'dsv_data';
if (isDsvFileType && currentStep.value === 'uploadFile') {
submitting.value = true;
transactionsStore.parseImportDsvFile({
fileType: type,
fileEncoding: encoding,
importFile: uploadFile
}).then(response => {
if (response && response.length) {
parsedFileData.value = response;
currentPage.value = 1;
countPerPage.value = 10;
currentStep.value = 'defineColumn';
} else {
parsedFileData.value = undefined;
snackbar.value?.showError('No data to import');
}
submitting.value = false;
}).catch(error => {
submitting.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
} else {
let columnMapping: Record<number, number> | undefined = undefined;
let transactionTypeMapping: Record<string, TransactionType> | undefined = undefined;
let hasHeaderLine: boolean | undefined = undefined;
let timeFormat: string | undefined = undefined;
let timezoneFormat: string | undefined = undefined;
let geoLocationSeparator: string | undefined = undefined;
let tagSeparator: string | undefined = undefined;
if (isDsvFileType) {
columnMapping = parsedFileDataColumnMapping.value;
transactionTypeMapping = parsedFileValidMappedTransactionTypes.value;
hasHeaderLine = parsedFileIncludeHeader.value;
timeFormat = parsedFileTimeFormat.value;
timezoneFormat = parsedFileTimezoneFormat.value;
geoLocationSeparator = parsedFileGeoLocationSeparator.value;
tagSeparator = parsedFileTagSeparator.value;
if (!columnMapping
|| !isNumber(columnMapping[ImportTransactionColumnType.TransactionTime.type])
|| !isNumber(columnMapping[ImportTransactionColumnType.TransactionType.type])
|| !isNumber(columnMapping[ImportTransactionColumnType.Amount.type])) {
snackbar.value?.showError('Missing transaction time, transaction type, or amount column mapping');
return;
}
if (!transactionTypeMapping || isObjectEmpty(transactionTypeMapping)) {
snackbar.value?.showError('Transaction type mapping is not set');
return;
}
if (!parsedFileTimeFormat.value) {
timeFormat = parsedFileAutoDetectedTimeFormat.value;
}
if (!parsedFileTimezoneFormat.value) {
timezoneFormat = parsedFileAutoDetectedTimezoneFormat.value;
}
if (!timeFormat) {
snackbar.value?.showError('Transaction time format is not set');
return;
}
}
importTransactions.value = parsedTransactions;
editingTransaction.value = null;
editingTags.value = [];
currentPage.value = 1;
submitting.value = true;
if (importTransactions.value && importTransactions.value.length >= 0 && importTransactions.value.length < 10) {
countPerPage.value = -1;
} else {
transactionsStore.parseImportTransaction({
fileType: type,
fileEncoding: encoding,
importFile: uploadFile,
columnMapping: columnMapping,
transactionTypeMapping: transactionTypeMapping,
hasHeaderLine: hasHeaderLine,
timeFormat: timeFormat,
timezoneFormat: timezoneFormat,
geoSeparator: geoLocationSeparator,
tagSeparator: tagSeparator
}).then(response => {
const parsedTransactions: ImportTransaction[] = [];
if (response.items) {
for (let i = 0; i < response.items.length; i++) {
const parsedTransaction = ImportTransaction.of(response.items[i], i);
parsedTransactions.push(parsedTransaction);
}
}
importTransactions.value = parsedTransactions;
editingTransaction.value = null;
editingTags.value = [];
currentPage.value = 1;
if (importTransactions.value && importTransactions.value.length >= 0 && importTransactions.value.length < 10) {
countPerPage.value = -1;
} else {
countPerPage.value = 10;
}
currentPage.value = 1;
countPerPage.value = 10;
}
currentStep.value = 'checkData';
submitting.value = false;
}).catch(error => {
submitting.value = false;
currentStep.value = 'checkData';
submitting.value = false;
}).catch(error => {
submitting.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
}
function submit(): void {
@@ -1820,6 +2427,40 @@ defineExpose({
</script>
<style>
.transaction-types-popup-menu .transaction-types-toggle {
overflow-x: auto;
white-space: nowrap;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle {
height: auto !important;
padding: 0;
border: none;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn {
border-color: rgba(var(--v-border-color), var(--v-border-opacity));
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: none;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle > .v-btn {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));
}
.transaction-types-popup-menu .transaction-types-toggle.v-btn-toggle button.v-btn {
width: auto !important;
}
.import-transaction-table .v-autocomplete.v-input.v-input--density-compact:not(.v-textarea) .v-field__input,
.import-transaction-table .v-select.v-input.v-input--density-compact:not(.v-textarea) .v-field__input {
min-height: inherit;