mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-14 06:57:35 +08:00
save / load column mapping file for delimiter-separated values (dsv) file
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
export class KnownFileType {
|
||||
private static readonly allInstancesByExtension: Record<string, KnownFileType> = {};
|
||||
|
||||
public static readonly JSON = new KnownFileType('json', 'application/json');
|
||||
public static readonly CSV = new KnownFileType('csv', 'text/csv');
|
||||
public static readonly TSV = new KnownFileType('tsv', 'text/tab-separated-values');
|
||||
public static readonly MARKDOWN = new KnownFileType('md', 'text/markdown');
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
import type { TypeAndName, TypeAndDisplayName } from './base.ts';
|
||||
import { KnownAmountFormat } from './numeral.ts';
|
||||
import { KnownDateTimeFormat } from './datetime.ts';
|
||||
import { KnownDateTimezoneFormat } from './timezone.ts';
|
||||
import { TransactionType } from './transaction.ts';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export class ImportTransactionDataMapping {
|
||||
private static readonly JSON_ROOT_FIELD = 'ezBookkeepingImportTransactionDataMapping';
|
||||
private static readonly DEFAULT_INCLUDE_HEADER = true;
|
||||
private static readonly DEFAULT_TIME_FORMAT = '';
|
||||
private static readonly DEFAULT_TIMEZONE_FORMAT = '';
|
||||
private static readonly DEFAULT_AMOUNT_FORMAT = '';
|
||||
private static readonly DEFAULT_GEO_LOCATION_SEPARATOR = ' ';
|
||||
private static readonly DEFAULT_GEO_LOCATION_ORDER = 'lonlat';
|
||||
private static readonly DEFAULT_TAG_SEPARATOR = ';';
|
||||
|
||||
public includeHeader: boolean;
|
||||
public dataColumnMapping: Record<number, number>;
|
||||
public transactionTypeMapping: Record<string, TransactionType>;
|
||||
public timeFormat: string;
|
||||
public timezoneFormat: string;
|
||||
public amountFormat: string;
|
||||
public geoLocationSeparator: string;
|
||||
public geoLocationOrder: string;
|
||||
public tagSeparator: string;
|
||||
|
||||
private constructor(includeHeader: boolean,
|
||||
dataColumnMapping: Record<number, number>,
|
||||
transactionTypeMapping: Record<string, TransactionType>,
|
||||
timeFormat: string,
|
||||
timezoneFormat: string,
|
||||
amountFormat: string,
|
||||
geoLocationSeparator: string,
|
||||
geoLocationOrder: string,
|
||||
tagSeparator: string) {
|
||||
this.includeHeader = includeHeader;
|
||||
this.dataColumnMapping = dataColumnMapping;
|
||||
this.transactionTypeMapping = transactionTypeMapping;
|
||||
this.timeFormat = timeFormat;
|
||||
this.timezoneFormat = timezoneFormat;
|
||||
this.amountFormat = amountFormat;
|
||||
this.geoLocationSeparator = geoLocationSeparator;
|
||||
this.geoLocationOrder = geoLocationOrder;
|
||||
this.tagSeparator = tagSeparator;
|
||||
}
|
||||
|
||||
public isColumnMappingSet(column: ImportTransactionColumnType | TypeAndDisplayName): boolean {
|
||||
return this.dataColumnMapping.hasOwnProperty(column.type) && typeof(this.dataColumnMapping[column.type]) === 'number';
|
||||
}
|
||||
|
||||
public toggleIncludeHeader(): void {
|
||||
this.includeHeader = !this.includeHeader;
|
||||
}
|
||||
|
||||
public toggleDataMappingColumn(columnIndex: number, columnType: number): void {
|
||||
if (this.dataColumnMapping[columnType] === columnIndex) {
|
||||
delete this.dataColumnMapping[columnType];
|
||||
} else {
|
||||
this.dataColumnMapping[columnType] = columnIndex;
|
||||
}
|
||||
|
||||
for (const otherColumnType in this.dataColumnMapping) {
|
||||
if (otherColumnType !== columnType.toString() && this.dataColumnMapping[otherColumnType] === columnIndex) {
|
||||
delete this.dataColumnMapping[otherColumnType];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setGeoLocationFormat(separator: string, order: string): void {
|
||||
this.geoLocationSeparator = separator;
|
||||
this.geoLocationOrder = order;
|
||||
}
|
||||
|
||||
public formatGeoLocation(latitude: string, longitude: string): string {
|
||||
if (this.geoLocationOrder === 'latlon') {
|
||||
return `${latitude}${this.geoLocationSeparator}${longitude}`;
|
||||
} else {
|
||||
return `${longitude}${this.geoLocationSeparator}${latitude}`;
|
||||
}
|
||||
}
|
||||
|
||||
public parseFileAllTransactionTypes(fileData: string[][] | undefined): string[] {
|
||||
if (!fileData || !fileData.length || !this.isColumnMappingSet(ImportTransactionColumnType.TransactionType)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allTypeMap: Record<string, boolean> = {};
|
||||
const allTypes: string[] = [];
|
||||
const typeColumnIndex = this.dataColumnMapping[ImportTransactionColumnType.TransactionType.type];
|
||||
|
||||
const startIndex = this.includeHeader ? 1 : 0;
|
||||
|
||||
for (let i = startIndex; i < fileData.length; i++) {
|
||||
if (fileData[i].length <= typeColumnIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = fileData[i][typeColumnIndex];
|
||||
|
||||
if (type && !allTypeMap[type]) {
|
||||
allTypes.push(type);
|
||||
allTypeMap[type] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return allTypes;
|
||||
}
|
||||
|
||||
public parseFileValidMappedTransactionTypes(fileData: string[][] | undefined): Record<string, TransactionType> {
|
||||
if (!fileData || !fileData.length || !this.isColumnMappingSet(ImportTransactionColumnType.TransactionType)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const result: Record<string, TransactionType> = {};
|
||||
|
||||
if (!this.transactionTypeMapping) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const name in this.transactionTypeMapping) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.transactionTypeMapping, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = this.transactionTypeMapping[name];
|
||||
|
||||
if (TransactionType.ModifyBalance <= type && type <= TransactionType.Transfer) {
|
||||
result[name] = type;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public parseFileAutoDetectedTimeFormat(fileData: string[][] | undefined): string | undefined {
|
||||
if (!fileData || !fileData.length || !this.isColumnMappingSet(ImportTransactionColumnType.TransactionTime)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allDateTimes: string[] = [];
|
||||
const dateTimeColumnIndex = this.dataColumnMapping[ImportTransactionColumnType.TransactionTime.type];
|
||||
|
||||
const startIndex = this.includeHeader ? 1 : 0;
|
||||
|
||||
for (let i = startIndex; i < fileData.length; i++) {
|
||||
if (fileData[i].length <= dateTimeColumnIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dateTime = fileData[i][dateTimeColumnIndex];
|
||||
|
||||
if (dateTime) {
|
||||
allDateTimes.push(dateTime);
|
||||
}
|
||||
}
|
||||
|
||||
const detectedFormats = KnownDateTimeFormat.detectMulti(allDateTimes);
|
||||
|
||||
if (!detectedFormats || !detectedFormats.length || detectedFormats.length > 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return detectedFormats[0].format;
|
||||
}
|
||||
|
||||
public parseFileAutoDetectedTimezoneFormat(fileData: string[][] | undefined): string | undefined {
|
||||
if (!fileData || !fileData.length || !this.isColumnMappingSet(ImportTransactionColumnType.TransactionTimezone)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allTimezones: string[] = [];
|
||||
const timezoneColumnIndex = this.dataColumnMapping[ImportTransactionColumnType.TransactionTimezone.type];
|
||||
|
||||
const startIndex = this.includeHeader ? 1 : 0;
|
||||
|
||||
for (let i = startIndex; i < fileData.length; i++) {
|
||||
if (fileData[i].length <= timezoneColumnIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const timezone = fileData[i][timezoneColumnIndex];
|
||||
|
||||
if (timezone) {
|
||||
allTimezones.push(timezone);
|
||||
}
|
||||
}
|
||||
|
||||
const detectedFormats = KnownDateTimezoneFormat.detectMulti(allTimezones);
|
||||
|
||||
if (!detectedFormats || !detectedFormats.length || detectedFormats.length > 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return detectedFormats[0].value;
|
||||
}
|
||||
|
||||
public parseFileAutoDetectedAmountFormat(fileData: string[][] | undefined): string | undefined {
|
||||
if (!fileData || !fileData.length || !this.isColumnMappingSet(ImportTransactionColumnType.TransactionTimezone)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allAmounts: string[] = [];
|
||||
const amountColumnIndex = this.dataColumnMapping[ImportTransactionColumnType.Amount.type];
|
||||
|
||||
const startIndex = this.includeHeader ? 1 : 0;
|
||||
|
||||
for (let i = startIndex; i < fileData.length; i++) {
|
||||
if (fileData[i].length <= amountColumnIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const amount = fileData[i][amountColumnIndex];
|
||||
|
||||
if (amount) {
|
||||
allAmounts.push(amount);
|
||||
}
|
||||
}
|
||||
|
||||
const detectedFormats = KnownAmountFormat.detectMulti(allAmounts);
|
||||
|
||||
if (!detectedFormats || !detectedFormats.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return detectedFormats[0].type;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.includeHeader = ImportTransactionDataMapping.DEFAULT_INCLUDE_HEADER;
|
||||
this.dataColumnMapping = {};
|
||||
this.transactionTypeMapping = {};
|
||||
this.timeFormat = ImportTransactionDataMapping.DEFAULT_TIME_FORMAT;
|
||||
this.timezoneFormat = ImportTransactionDataMapping.DEFAULT_TIMEZONE_FORMAT;
|
||||
this.amountFormat = ImportTransactionDataMapping.DEFAULT_AMOUNT_FORMAT;
|
||||
this.geoLocationSeparator = ImportTransactionDataMapping.DEFAULT_GEO_LOCATION_SEPARATOR;
|
||||
this.geoLocationOrder = ImportTransactionDataMapping.DEFAULT_GEO_LOCATION_ORDER;
|
||||
this.tagSeparator = ImportTransactionDataMapping.DEFAULT_TAG_SEPARATOR;
|
||||
}
|
||||
|
||||
public toJson(): string {
|
||||
return JSON.stringify({
|
||||
[ImportTransactionDataMapping.JSON_ROOT_FIELD]: {
|
||||
includeHeader: this.includeHeader,
|
||||
dataColumnMapping: this.dataColumnMapping,
|
||||
transactionTypeMapping: this.transactionTypeMapping,
|
||||
timeFormat: this.timeFormat,
|
||||
timezoneFormat: this.timezoneFormat,
|
||||
amountFormat: this.amountFormat,
|
||||
geoLocationSeparator: this.geoLocationSeparator,
|
||||
geoLocationOrder: this.geoLocationOrder,
|
||||
tagSeparator: this.tagSeparator
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static createEmpty(): ImportTransactionDataMapping {
|
||||
return new ImportTransactionDataMapping(
|
||||
ImportTransactionDataMapping.DEFAULT_INCLUDE_HEADER,
|
||||
{},
|
||||
{},
|
||||
ImportTransactionDataMapping.DEFAULT_TIME_FORMAT,
|
||||
ImportTransactionDataMapping.DEFAULT_TIMEZONE_FORMAT,
|
||||
ImportTransactionDataMapping.DEFAULT_AMOUNT_FORMAT,
|
||||
ImportTransactionDataMapping.DEFAULT_GEO_LOCATION_SEPARATOR,
|
||||
ImportTransactionDataMapping.DEFAULT_GEO_LOCATION_ORDER,
|
||||
ImportTransactionDataMapping.DEFAULT_TAG_SEPARATOR
|
||||
);
|
||||
}
|
||||
|
||||
public static parseFromJson(json: string): ImportTransactionDataMapping | null {
|
||||
try {
|
||||
const parsed = JSON.parse(json);
|
||||
const root = parsed[ImportTransactionDataMapping.JSON_ROOT_FIELD];
|
||||
|
||||
if (!root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ImportTransactionDataMapping(
|
||||
root.includeHeader ?? ImportTransactionDataMapping.DEFAULT_INCLUDE_HEADER,
|
||||
root.dataColumnMapping ?? {},
|
||||
root.transactionTypeMapping ?? {},
|
||||
root.timeFormat ?? ImportTransactionDataMapping.DEFAULT_TIME_FORMAT,
|
||||
root.timezoneFormat ?? ImportTransactionDataMapping.DEFAULT_TIMEZONE_FORMAT,
|
||||
root.amountFormat ?? ImportTransactionDataMapping.DEFAULT_AMOUNT_FORMAT,
|
||||
root.geoLocationSeparator ?? ImportTransactionDataMapping.DEFAULT_GEO_LOCATION_SEPARATOR,
|
||||
root.geoLocationOrder ?? ImportTransactionDataMapping.DEFAULT_GEO_LOCATION_ORDER,
|
||||
root.tagSeparator ?? ImportTransactionDataMapping.DEFAULT_TAG_SEPARATOR
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,36 +57,3 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { ThemeType } from '@/core/theme.ts';
|
||||
|
||||
import { type AmountColor, PresetAmountColor } from '@/core/color.ts';
|
||||
|
||||
import logger from '../logger.ts';
|
||||
|
||||
export function getSystemTheme(): ThemeType {
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return ThemeType.Dark;
|
||||
@@ -84,6 +86,43 @@ export function copyTextToClipboard(text: string, container?: Element | null): v
|
||||
});
|
||||
}
|
||||
|
||||
export function openTextFileContent({ allowedExtensions }: { allowedExtensions: string }): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileInput = document.createElement('input');
|
||||
|
||||
fileInput.style.display = 'none';
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = allowedExtensions;
|
||||
|
||||
fileInput.onchange = (event) => {
|
||||
const el = event.target as HTMLInputElement;
|
||||
|
||||
if (el.files && el.files.length > 0) {
|
||||
const file = el.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (e) => {
|
||||
const content = e.target?.result;
|
||||
|
||||
if (typeof content === 'string') {
|
||||
resolve(content);
|
||||
} else {
|
||||
reject(new Error('failed to load file, because file reader result is not string'));
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = (error) => {
|
||||
logger.error('failed to load file', error);
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
fileInput.click();
|
||||
});
|
||||
}
|
||||
|
||||
export function startDownloadFile(fileName: string, fileData: Blob): void {
|
||||
const dataObjectUrl = URL.createObjectURL(fileData);
|
||||
const dataLink = document.createElement('a');
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "Ungültige Transaktionen können nicht importiert werden",
|
||||
"Unable to parse import file": "Importdatei kann nicht geparst werden",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Ausgewählte Ausgabenkategorien im Batch ersetzen",
|
||||
"Batch Replace Selected Income Categories": "Ausgewählte Einnahmenkategorien im Batch ersetzen",
|
||||
"Batch Replace Selected Transfer Categories": "Ausgewählte Überweisungskategorien im Batch ersetzen",
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "Cannot import invalid transactions",
|
||||
"Unable to parse import file": "Unable to parse import file",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Batch Replace Selected Expense Categories",
|
||||
"Batch Replace Selected Income Categories": "Batch Replace Selected Income Categories",
|
||||
"Batch Replace Selected Transfer Categories": "Batch Replace Selected Transfer Categories",
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"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",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Reemplazar por lotes categorías de gastos seleccionadas",
|
||||
"Batch Replace Selected Income Categories": "Reemplazo por lotes de categorías de ingresos seleccionadas",
|
||||
"Batch Replace Selected Transfer Categories": "Reemplazar por lotes las categorías de transferencia seleccionadas",
|
||||
|
||||
@@ -79,10 +79,13 @@ import {
|
||||
|
||||
import {
|
||||
TransactionEditScopeType,
|
||||
TransactionTagFilterType,
|
||||
ImportTransactionColumnType
|
||||
TransactionTagFilterType
|
||||
} from '@/core/transaction.ts';
|
||||
|
||||
import {
|
||||
ImportTransactionColumnType
|
||||
} from '@/core/import_transaction.ts';
|
||||
|
||||
import {
|
||||
ScheduledTemplateFrequencyType
|
||||
} from '@/core/template.ts';
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "Impossibile importare transazioni non valide",
|
||||
"Unable to parse import file": "Impossibile analizzare il file di importazione",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Sostituisci in blocco categorie di spesa selezionate",
|
||||
"Batch Replace Selected Income Categories": "Sostituisci in blocco categorie di entrata selezionate",
|
||||
"Batch Replace Selected Transfer Categories": "Sostituisci in blocco categorie di trasferimento selezionate",
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_エクスポートデータ",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_エクスポートデータ",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "無効な取引をインポートできません",
|
||||
"Unable to parse import file": "インポートファイルを解析できません",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "バッチは選択した支出カテゴリを置き換えます",
|
||||
"Batch Replace Selected Income Categories": "バッチは選択した収入カテゴリを置き換えます",
|
||||
"Batch Replace Selected Transfer Categories": "バッチは選択した振替カテゴリを置き換えます",
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_dados_estatísticos",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_dados_estatísticos"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_dados_estatísticos",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "Não é possível importar transações inválidas",
|
||||
"Unable to parse import file": "Não foi possível analisar o arquivo de importação",
|
||||
"Unable to import transactions": "Não foi possível importar transações",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Substituir em Lote as Categorias de Despesas Selecionadas",
|
||||
"Batch Replace Selected Income Categories": "Substituir em Lote as Categorias de Renda Selecionadas",
|
||||
"Batch Replace Selected Transfer Categories": "Substituir em Lote as Categorias de Transferência Selecionadas",
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "Невозможно импортировать недействительные транзакции",
|
||||
"Unable to parse import file": "Не удалось обработать файл импорта",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Пакетная замена выбранных категорий расходов",
|
||||
"Batch Replace Selected Income Categories": "Пакетная замена выбранных категорий доходов",
|
||||
"Batch Replace Selected Transfer Categories": "Пакетная замена выбранных категорий переводов",
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "Неможливо імпортувати недійсні транзакції",
|
||||
"Unable to parse import file": "Не вдалося обробити файл імпорту",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Пакетна заміна вибраних категорій витрат",
|
||||
"Batch Replace Selected Income Categories": "Пакетна заміна вибраних категорій доходів",
|
||||
"Batch Replace Selected Transfer Categories": "Пакетна заміна вибраних категорій переказів",
|
||||
|
||||
+5
-1
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_export_data",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_export_data",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"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",
|
||||
"Unable to import transactions": "Unable to import transactions",
|
||||
"Load Data Mapping File": "Load Data Mapping File",
|
||||
"Save Data Mapping File": "Save Data Mapping File",
|
||||
"Data mapping file is invalid": "Data mapping file is invalid",
|
||||
"Batch Replace Selected Expense Categories": "Thay thế hàng loạt các danh mục chi phí đã chọn",
|
||||
"Batch Replace Selected Income Categories": "Thay thế hàng loạt các danh mục thu nhập đã chọn",
|
||||
"Batch Replace Selected Transfer Categories": "Thay thế hàng loạt các danh mục chuyển khoản đã chọn",
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_导出数据",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_导出数据",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_统计数据",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_统计数据"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_统计数据",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_导入数据映射文件"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "不能导入无效的交易",
|
||||
"Unable to parse import file": "无法解析导入的文件",
|
||||
"Unable to import transactions": "无法导入交易",
|
||||
"Load Data Mapping File": "加载数据映射文件",
|
||||
"Save Data Mapping File": "保存数据映射文件",
|
||||
"Data mapping file is invalid": "数据映射文件无效",
|
||||
"Batch Replace Selected Expense Categories": "批量替换选中的支出分类",
|
||||
"Batch Replace Selected Income Categories": "批量替换选中的收入分类",
|
||||
"Batch Replace Selected Transfer Categories": "批量替换选中的转账分类",
|
||||
|
||||
@@ -121,7 +121,8 @@
|
||||
"defaultExportFilename": "ezBookkeeping_匯出資料",
|
||||
"exportFilename": "ezBookkeeping_{nickname}_匯出資料",
|
||||
"defaultExportStatisticsFileName": "ezBookkeeping_統計資料",
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_統計資料"
|
||||
"exportStatisticsFileName": "ezBookkeeping_{nickname}_統計資料",
|
||||
"defaultImportDataMappingFileName": "ezBookkeeping_匯入資料對應檔案"
|
||||
},
|
||||
"datetime": {
|
||||
"AM": {
|
||||
@@ -1728,6 +1729,9 @@
|
||||
"Cannot import invalid transactions": "無法匯入無效的交易",
|
||||
"Unable to parse import file": "無法解析匯入的檔案",
|
||||
"Unable to import transactions": "無法匯入交易",
|
||||
"Load Data Mapping File": "載入資料對應檔案",
|
||||
"Save Data Mapping File": "儲存資料對應檔案",
|
||||
"Data mapping file is invalid": "資料對應檔案無效",
|
||||
"Batch Replace Selected Expense Categories": "批次替換選中的支出分類",
|
||||
"Batch Replace Selected Income Categories": "批次替換選中的收入分類",
|
||||
"Batch Replace Selected Transfer Categories": "批次替換選中的轉帳分類",
|
||||
|
||||
@@ -7,6 +7,21 @@
|
||||
<h4 class="text-h4">{{ tt('Import Transactions') }}</h4>
|
||||
<v-progress-circular indeterminate size="22" class="ml-2" v-if="loading"></v-progress-circular>
|
||||
</div>
|
||||
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
|
||||
:icon="true" :disabled="loading || submitting"
|
||||
v-if="currentStep === 'defineColumn'">
|
||||
<v-icon :icon="mdiDotsVertical" />
|
||||
<v-menu activator="parent" max-height="500">
|
||||
<v-list>
|
||||
<v-list-item :prepend-icon="mdiFolderOpenOutline"
|
||||
:title="tt('Load Data Mapping File')"
|
||||
@click="loadColumnMappingFile()"></v-list-item>
|
||||
<v-list-item :prepend-icon="mdiContentSaveOutline"
|
||||
:title="tt('Save Data Mapping File')"
|
||||
@click="saveColumnMappingFile()"></v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
|
||||
:icon="true" :disabled="loading || submitting"
|
||||
v-if="currentStep === 'checkData'">
|
||||
@@ -294,9 +309,9 @@
|
||||
<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"
|
||||
:append-icon="parsedFileDataColumnMapping.dataColumnMapping[columnType.type] === parseInt(column.key) ? mdiCheck : undefined"
|
||||
v-for="columnType in allImportTransactionColumnTypes"
|
||||
@click="updateParseDataMappedColumn(parseInt(column.key), columnType.type)">
|
||||
@click="parsedFileDataColumnMapping.toggleDataMappingColumn(parseInt(column.key), columnType.type)">
|
||||
<v-list-item-title class="cursor-pointer">
|
||||
{{ columnType.displayName }}
|
||||
</v-list-item-title>
|
||||
@@ -310,12 +325,12 @@
|
||||
<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>
|
||||
:append-icon="parsedFileDataColumnMapping.includeHeader ? mdiCheck : mdiClose"
|
||||
@click="parsedFileDataColumnMapping.toggleIncludeHeader()">{{ 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">
|
||||
:disabled="!parsedFileDataColumnMapping || !parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.TransactionType) || !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>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping && parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.TransactionType) && 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">
|
||||
@@ -328,7 +343,7 @@
|
||||
<td>
|
||||
<v-btn-toggle class="transaction-types-toggle" density="compact" variant="outlined"
|
||||
mandatory="force" divided
|
||||
v-model="parsedFileTransactionTypeMapping[typeName]">
|
||||
v-model="parsedFileDataColumnMapping.transactionTypeMapping[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>
|
||||
@@ -344,14 +359,14 @@
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
||||
:disabled="!parsedFileDataColumnMapping || !isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionTime.type])">
|
||||
:disabled="!parsedFileDataColumnMapping || !parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.TransactionTime)">
|
||||
<span>{{ tt('Time Format') }}</span>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionTime.type])">({{ parsedFileTimeFormat || parsedFileAutoDetectedTimeFormat || tt('Unknown') }})</span>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping && parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.TransactionTime)">({{ parsedFileDataColumnMapping.timeFormat || 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 = ''">
|
||||
:append-icon="parsedFileDataColumnMapping.timeFormat === '' ? mdiCheck : undefined"
|
||||
@click="parsedFileDataColumnMapping.timeFormat = ''">
|
||||
<v-list-item-title class="cursor-pointer">
|
||||
<span>{{ tt('Auto detect') }}</span>
|
||||
<span class="ml-1" v-if="parsedFileAutoDetectedTimeFormat">({{ parsedFileAutoDetectedTimeFormat }})</span>
|
||||
@@ -359,9 +374,9 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item :key="dateTimeFormat.format"
|
||||
:append-icon="parsedFileTimeFormat === dateTimeFormat.format ? mdiCheck : undefined"
|
||||
:append-icon="parsedFileDataColumnMapping.timeFormat === dateTimeFormat.format ? mdiCheck : undefined"
|
||||
v-for="dateTimeFormat in KnownDateTimeFormat.values()"
|
||||
@click="parsedFileTimeFormat = dateTimeFormat.format">
|
||||
@click="parsedFileDataColumnMapping.timeFormat = dateTimeFormat.format">
|
||||
<v-list-item-title class="cursor-pointer">
|
||||
{{ dateTimeFormat.format }}
|
||||
</v-list-item-title>
|
||||
@@ -370,14 +385,14 @@
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
||||
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.TransactionTimezone.type])">
|
||||
v-if="parsedFileDataColumnMapping && parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.TransactionTimezone)">
|
||||
<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>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping && parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.TransactionTimezone)">({{ KnownDateTimezoneFormat.valueOf(parsedFileDataColumnMapping.timezoneFormat || 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 = ''">
|
||||
:append-icon="parsedFileDataColumnMapping.timezoneFormat === '' ? mdiCheck : undefined"
|
||||
@click="parsedFileDataColumnMapping.timezoneFormat = ''">
|
||||
<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>
|
||||
@@ -385,9 +400,9 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item :key="timezoneFormat.value"
|
||||
:append-icon="parsedFileTimezoneFormat === timezoneFormat.value ? mdiCheck : undefined"
|
||||
:append-icon="parsedFileDataColumnMapping.timezoneFormat === timezoneFormat.value ? mdiCheck : undefined"
|
||||
v-for="timezoneFormat in KnownDateTimezoneFormat.values()"
|
||||
@click="parsedFileTimezoneFormat = timezoneFormat.value">
|
||||
@click="parsedFileDataColumnMapping.timezoneFormat = timezoneFormat.value">
|
||||
<v-list-item-title class="cursor-pointer">
|
||||
{{ timezoneFormat.name }}
|
||||
</v-list-item-title>
|
||||
@@ -396,14 +411,14 @@
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
||||
:disabled="!parsedFileDataColumnMapping || !isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.Amount.type])">
|
||||
:disabled="!parsedFileDataColumnMapping || !parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.Amount)">
|
||||
<span>{{ tt('Amount Format') }}</span>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.Amount.type])">({{ KnownAmountFormat.valueOf(parsedFileAmountFormat || parsedFileAutoDetectedAmountFormat || '')?.format || tt('Unknown') }})</span>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping && parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.Amount)">({{ KnownAmountFormat.valueOf(parsedFileDataColumnMapping.amountFormat || parsedFileAutoDetectedAmountFormat || '')?.format || tt('Unknown') }})</span>
|
||||
<v-menu eager activator="parent" location="bottom" max-height="500">
|
||||
<v-list>
|
||||
<v-list-item key="auto"
|
||||
:append-icon="parsedFileAmountFormat === '' ? mdiCheck : undefined"
|
||||
@click="parsedFileAmountFormat = ''">
|
||||
:append-icon="parsedFileDataColumnMapping.amountFormat === '' ? mdiCheck : undefined"
|
||||
@click="parsedFileDataColumnMapping.amountFormat = ''">
|
||||
<v-list-item-title class="cursor-pointer">
|
||||
<span>{{ tt('Auto detect') }}</span>
|
||||
<span class="ml-1" v-if="parsedFileAutoDetectedAmountFormat && KnownAmountFormat.valueOf(parsedFileAutoDetectedAmountFormat || '')">({{ KnownAmountFormat.valueOf(parsedFileAutoDetectedAmountFormat || '')?.format }})</span>
|
||||
@@ -411,9 +426,9 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item :key="amountFormat.type"
|
||||
:append-icon="parsedFileAmountFormat === amountFormat.type ? mdiCheck : undefined"
|
||||
:append-icon="parsedFileDataColumnMapping.amountFormat === amountFormat.type ? mdiCheck : undefined"
|
||||
v-for="amountFormat in KnownAmountFormat.values()"
|
||||
@click="parsedFileAmountFormat = amountFormat.type">
|
||||
@click="parsedFileDataColumnMapping.amountFormat = amountFormat.type">
|
||||
<v-list-item-title class="cursor-pointer">
|
||||
{{ amountFormat.format }}
|
||||
</v-list-item-title>
|
||||
@@ -422,9 +437,9 @@
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
||||
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.GeographicLocation.type])">
|
||||
v-if="parsedFileDataColumnMapping && parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.GeographicLocation)">
|
||||
<span>{{ tt('Geographic Location Separator') }}</span>
|
||||
<span class="ml-1" v-if="parsedFileGeoLocationSeparator">({{ parsedFileGeoLocationOrder === 'latlon' ? `${tt('Latitude')}${parsedFileGeoLocationSeparator}${tt('Longitude')}` : `${tt('Longitude')}${parsedFileGeoLocationSeparator}${tt('Latitude')}` }})</span>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping.geoLocationOrder">({{ parsedFileDataColumnMapping.formatGeoLocation(tt('Latitude'), tt('Longitude')) }})</span>
|
||||
<v-menu eager activator="parent" location="bottom" max-height="500"
|
||||
:close-on-content-click="false">
|
||||
<v-list class="pa-0">
|
||||
@@ -437,15 +452,15 @@
|
||||
<td>
|
||||
<v-btn-toggle class="transaction-types-toggle" density="compact" variant="outlined"
|
||||
mandatory="force" divided
|
||||
v-model="parsedFileGeoLocationOrder"
|
||||
v-if="parsedFileGeoLocationSeparator === separator.value">
|
||||
v-model="parsedFileDataColumnMapping.geoLocationOrder"
|
||||
v-if="parsedFileDataColumnMapping.geoLocationSeparator === separator.value">
|
||||
<v-btn value="latlon">{{ `${tt('Latitude')}${separator.value}${tt('Longitude')}` }}</v-btn>
|
||||
<v-btn value="lonlat">{{ `${tt('Longitude')}${separator.value}${tt('Latitude')}` }}</v-btn>
|
||||
</v-btn-toggle>
|
||||
<v-btn-group class="transaction-types-toggle" density="compact" variant="outlined"
|
||||
divided v-if="parsedFileGeoLocationSeparator !== separator.value">
|
||||
<v-btn @click="parsedFileGeoLocationSeparator = separator.value; parsedFileGeoLocationOrder = 'latlon'">{{ `${tt('Latitude')}${separator.value}${tt('Longitude')}` }}</v-btn>
|
||||
<v-btn @click="parsedFileGeoLocationSeparator = separator.value; parsedFileGeoLocationOrder = 'lonlat'">{{ `${tt('Longitude')}${separator.value}${tt('Latitude')}` }}</v-btn>
|
||||
divided v-if="parsedFileDataColumnMapping.geoLocationSeparator !== separator.value">
|
||||
<v-btn @click="parsedFileDataColumnMapping.setGeoLocationFormat(separator.value, 'latlon')">{{ `${tt('Latitude')}${separator.value}${tt('Longitude')}` }}</v-btn>
|
||||
<v-btn @click="parsedFileDataColumnMapping.setGeoLocationFormat(separator.value, 'lonlat')">{{ `${tt('Longitude')}${separator.value}${tt('Latitude')}` }}</v-btn>
|
||||
</v-btn-group>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -456,15 +471,15 @@
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
||||
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.Tags.type])">
|
||||
v-if="parsedFileDataColumnMapping && parsedFileDataColumnMapping.isColumnMappingSet(ImportTransactionColumnType.Tags)">
|
||||
<span>{{ tt('Transaction Tags Separator') }}</span>
|
||||
<span class="ml-1" v-if="parsedFileTagSeparator">({{ parsedFileTagSeparator }})</span>
|
||||
<span class="ml-1" v-if="parsedFileDataColumnMapping.tagSeparator">({{ parsedFileDataColumnMapping.tagSeparator }})</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"
|
||||
:append-icon="parsedFileDataColumnMapping.tagSeparator === separator.value ? mdiCheck : undefined"
|
||||
v-for="separator in allSeparators"
|
||||
@click="parsedFileTagSeparator = separator.value">
|
||||
@click="parsedFileDataColumnMapping.tagSeparator = separator.value">
|
||||
<v-list-item-title class="cursor-pointer">
|
||||
{{ separator.name }} ({{separator.value}})
|
||||
</v-list-item-title>
|
||||
@@ -879,7 +894,10 @@ import { KnownAmountFormat } from '@/core/numeral.ts';
|
||||
import { KnownDateTimeFormat } from '@/core/datetime.ts';
|
||||
import { KnownDateTimezoneFormat } from '@/core/timezone.ts';
|
||||
import { CategoryType } from '@/core/category.ts';
|
||||
import { TransactionType, ImportTransactionColumnType } from '@/core/transaction.ts';
|
||||
import { TransactionType } from '@/core/transaction.ts';
|
||||
import { ImportTransactionColumnType, ImportTransactionDataMapping } from '@/core/import_transaction.ts';
|
||||
import { KnownFileType } from '@/core/file.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';
|
||||
@@ -916,12 +934,18 @@ import {
|
||||
getTransactionPrimaryCategoryName,
|
||||
getTransactionSecondaryCategoryName
|
||||
} from '@/lib/category.ts';
|
||||
import {
|
||||
openTextFileContent,
|
||||
startDownloadFile
|
||||
} from '@/lib/ui/common.ts';
|
||||
|
||||
import {
|
||||
mdiFilterOutline,
|
||||
mdiCheck,
|
||||
mdiDotsVertical,
|
||||
mdiHelpCircleOutline,
|
||||
mdiFolderOpenOutline,
|
||||
mdiContentSaveOutline,
|
||||
mdiFindReplace,
|
||||
mdiShapePlusOutline,
|
||||
mdiTransfer,
|
||||
@@ -996,15 +1020,7 @@ 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 parsedFileAmountFormat = ref<string>('');
|
||||
const parsedFileGeoLocationSeparator = ref<string>(' ');
|
||||
const parsedFileGeoLocationOrder = ref<string>('lonlat');
|
||||
const parsedFileTagSeparator = ref<string>(';');
|
||||
const parsedFileDataColumnMapping = ref<ImportTransactionDataMapping>(ImportTransactionDataMapping.createEmpty());
|
||||
const importTransactions = ref<ImportTransaction[] | undefined>(undefined);
|
||||
const editingTransaction = ref<ImportTransaction | null>(null);
|
||||
const editingTags = ref<string[]>([]);
|
||||
@@ -1192,7 +1208,7 @@ const parsedFileLines = computed<Record<string, string>[] | undefined>(() => {
|
||||
}
|
||||
|
||||
const allLines: Record<string, string>[] = [];
|
||||
const startIndex = parsedFileIncludeHeader.value ? 1 : 0;
|
||||
const startIndex = parsedFileDataColumnMapping.value.includeHeader ? 1 : 0;
|
||||
|
||||
for (let i = startIndex, index = 1; i < parsedFileData.value.length; i++, index++) {
|
||||
const line: Record<string, string> = {};
|
||||
@@ -1235,7 +1251,7 @@ const parsedFileLinesHeaders = computed<object[]>(() => {
|
||||
for (let i = 0; i < maxColumnCount; i++) {
|
||||
let title = `#${i + 1}`;
|
||||
|
||||
if (parsedFileIncludeHeader.value && parsedFileData.value && parsedFileData.value[0][i]) {
|
||||
if (parsedFileDataColumnMapping.value.includeHeader && parsedFileData.value && parsedFileData.value[0][i]) {
|
||||
title = parsedFileData.value[0][i];
|
||||
}
|
||||
|
||||
@@ -1247,151 +1263,11 @@ const parsedFileLinesHeaders = computed<object[]>(() => {
|
||||
|
||||
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.detectMulti(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.detectMulti(allTimezones);
|
||||
|
||||
if (!detectedFormats || !detectedFormats.length || detectedFormats.length > 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return detectedFormats[0].value;
|
||||
});
|
||||
|
||||
const parsedFileAutoDetectedAmountFormat = computed<string | undefined>(() => {
|
||||
if (!parsedFileData.value || !parsedFileData.value.length || !isNumber(parsedFileDataColumnMapping.value[ImportTransactionColumnType.Amount.type])) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const allAmounts: string[] = [];
|
||||
const amountColumnIndex = parsedFileDataColumnMapping.value[ImportTransactionColumnType.Amount.type];
|
||||
|
||||
const startIndex = parsedFileIncludeHeader.value ? 1 : 0;
|
||||
|
||||
for (let i = startIndex; i < parsedFileData.value.length; i++) {
|
||||
if (parsedFileData.value[i].length <= amountColumnIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const amount = parsedFileData.value[i][amountColumnIndex];
|
||||
|
||||
if (amount) {
|
||||
allAmounts.push(amount);
|
||||
}
|
||||
}
|
||||
|
||||
const detectedFormats = KnownAmountFormat.detectMulti(allAmounts);
|
||||
|
||||
if (!detectedFormats || !detectedFormats.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return detectedFormats[0].type;
|
||||
});
|
||||
const parsedFileAllTransactionTypes = computed<string[]>(() => parsedFileDataColumnMapping.value.parseFileAllTransactionTypes(parsedFileData.value));
|
||||
const parsedFileValidMappedTransactionTypes = computed<Record<string, TransactionType>>(() => parsedFileDataColumnMapping.value.parseFileValidMappedTransactionTypes(parsedFileData.value));
|
||||
const parsedFileAutoDetectedTimeFormat = computed<string | undefined>(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedTimeFormat(parsedFileData.value));
|
||||
const parsedFileAutoDetectedTimezoneFormat = computed<string | undefined>(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedTimezoneFormat(parsedFileData.value));
|
||||
const parsedFileAutoDetectedAmountFormat = computed<string | undefined>(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedAmountFormat(parsedFileData.value));
|
||||
|
||||
const importTransactionsTableHeight = computed<number | undefined>(() => {
|
||||
if (countPerPage.value <= 10 || !importTransactions.value || importTransactions.value.length <= 10) {
|
||||
@@ -1649,8 +1525,8 @@ function getTablePageOptions(linesCount?: number): ImportTransactionsDialogTable
|
||||
}
|
||||
|
||||
function getParseDataMappedColumnDisplayName(columnIndex: number): string {
|
||||
for (const columnType in parsedFileDataColumnMapping.value) {
|
||||
if (parsedFileDataColumnMapping.value[columnType] === columnIndex) {
|
||||
for (const columnType in parsedFileDataColumnMapping.value.dataColumnMapping) {
|
||||
if (parsedFileDataColumnMapping.value.dataColumnMapping[columnType] === columnIndex) {
|
||||
return findDisplayNameByType(allImportTransactionColumnTypes.value, parseInt(columnType)) || tt('Unspecified');
|
||||
}
|
||||
}
|
||||
@@ -1658,18 +1534,27 @@ function getParseDataMappedColumnDisplayName(columnIndex: number): string {
|
||||
return tt('Unspecified');
|
||||
}
|
||||
|
||||
function updateParseDataMappedColumn(columnIndex: number, columnType: number): void {
|
||||
if (parsedFileDataColumnMapping.value[columnType] === columnIndex) {
|
||||
delete parsedFileDataColumnMapping.value[columnType];
|
||||
} else {
|
||||
parsedFileDataColumnMapping.value[columnType] = columnIndex;
|
||||
}
|
||||
function loadColumnMappingFile(): void {
|
||||
openTextFileContent({
|
||||
allowedExtensions: KnownFileType.JSON.contentType
|
||||
}).then(content => {
|
||||
const result = ImportTransactionDataMapping.parseFromJson(content);
|
||||
|
||||
for (const otherColumnType in parsedFileDataColumnMapping.value) {
|
||||
if (otherColumnType !== columnType.toString() && parsedFileDataColumnMapping.value[otherColumnType] === columnIndex) {
|
||||
delete parsedFileDataColumnMapping.value[otherColumnType];
|
||||
if (result) {
|
||||
parsedFileDataColumnMapping.value = result;
|
||||
} else {
|
||||
logger.error('Failed to parse data mapping file');
|
||||
snackbar.value?.showError('Data mapping file is invalid');
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
logger.error('Failed to open data mapping file', error);
|
||||
snackbar.value?.showError('Data mapping file is invalid');
|
||||
});
|
||||
}
|
||||
|
||||
function saveColumnMappingFile(): void {
|
||||
const fileName = KnownFileType.JSON.formatFileName(tt('dataExport.defaultImportDataMappingFileName'));
|
||||
startDownloadFile(fileName, KnownFileType.JSON.createBlob(parsedFileDataColumnMapping.value.toJson()));
|
||||
}
|
||||
|
||||
function isTransactionDisplayed(transaction: ImportTransaction): boolean {
|
||||
@@ -1954,15 +1839,7 @@ function open(): Promise<void> {
|
||||
importFile.value = null;
|
||||
importData.value = '';
|
||||
parsedFileData.value = undefined;
|
||||
parsedFileIncludeHeader.value = true;
|
||||
parsedFileDataColumnMapping.value = {};
|
||||
parsedFileTransactionTypeMapping.value = {};
|
||||
parsedFileTimeFormat.value = '';
|
||||
parsedFileTimezoneFormat.value = '';
|
||||
parsedFileAmountFormat.value = '';
|
||||
parsedFileGeoLocationSeparator.value = ' ';
|
||||
parsedFileGeoLocationOrder.value = 'lonlat';
|
||||
parsedFileTagSeparator.value = ';';
|
||||
parsedFileDataColumnMapping.value.reset();
|
||||
importTransactions.value = undefined;
|
||||
editingTransaction.value = null;
|
||||
editingTags.value = [];
|
||||
@@ -2111,15 +1988,15 @@ function parseData(): void {
|
||||
let tagSeparator: string | undefined = undefined;
|
||||
|
||||
if (isDsvFileType) {
|
||||
columnMapping = parsedFileDataColumnMapping.value;
|
||||
columnMapping = parsedFileDataColumnMapping.value.dataColumnMapping;
|
||||
transactionTypeMapping = parsedFileValidMappedTransactionTypes.value;
|
||||
hasHeaderLine = parsedFileIncludeHeader.value;
|
||||
timeFormat = parsedFileTimeFormat.value;
|
||||
timezoneFormat = parsedFileTimezoneFormat.value;
|
||||
amountFormat = parsedFileAmountFormat.value;
|
||||
geoLocationSeparator = parsedFileGeoLocationSeparator.value;
|
||||
geoLocationOrder = parsedFileGeoLocationOrder.value;
|
||||
tagSeparator = parsedFileTagSeparator.value;
|
||||
hasHeaderLine = parsedFileDataColumnMapping.value.includeHeader;
|
||||
timeFormat = parsedFileDataColumnMapping.value.timeFormat;
|
||||
timezoneFormat = parsedFileDataColumnMapping.value.timezoneFormat;
|
||||
amountFormat = parsedFileDataColumnMapping.value.amountFormat;
|
||||
geoLocationSeparator = parsedFileDataColumnMapping.value.geoLocationSeparator;
|
||||
geoLocationOrder = parsedFileDataColumnMapping.value.geoLocationOrder;
|
||||
tagSeparator = parsedFileDataColumnMapping.value.tagSeparator;
|
||||
|
||||
if (!columnMapping
|
||||
|| !isNumber(columnMapping[ImportTransactionColumnType.TransactionTime.type])
|
||||
@@ -2134,15 +2011,15 @@ function parseData(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parsedFileTimeFormat.value) {
|
||||
if (!parsedFileDataColumnMapping.value.timeFormat) {
|
||||
timeFormat = parsedFileAutoDetectedTimeFormat.value;
|
||||
}
|
||||
|
||||
if (!parsedFileTimezoneFormat.value) {
|
||||
if (!parsedFileDataColumnMapping.value.timezoneFormat) {
|
||||
timezoneFormat = parsedFileAutoDetectedTimezoneFormat.value;
|
||||
}
|
||||
|
||||
if (!parsedFileAmountFormat.value) {
|
||||
if (!parsedFileDataColumnMapping.value.amountFormat) {
|
||||
amountFormat = parsedFileAutoDetectedAmountFormat.value;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user