diff --git a/pkg/converters/default/default_transaction_data_json_file_importer.go b/pkg/converters/default/default_transaction_data_json_file_importer.go new file mode 100644 index 00000000..aa986f29 --- /dev/null +++ b/pkg/converters/default/default_transaction_data_json_file_importer.go @@ -0,0 +1,96 @@ +package _default + +import ( + "encoding/json" + "time" + + "github.com/mayswind/ezbookkeeping/pkg/converters/converter" + "github.com/mayswind/ezbookkeeping/pkg/converters/datatable" + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/models" + "github.com/mayswind/ezbookkeeping/pkg/utils" +) + +var allJsonDataSupportedColumns = []datatable.TransactionDataTableColumn{ + datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME, + datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE, + datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE, + datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY, + datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME, + datatable.TRANSACTION_DATA_TABLE_AMOUNT, + datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME, + datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT, + datatable.TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION, + datatable.TRANSACTION_DATA_TABLE_TAGS, + datatable.TRANSACTION_DATA_TABLE_DESCRIPTION, +} + +// defaultTransactionDataJsonImporter defines the structure of ezbookkeeping default json importer for transaction data +type defaultTransactionDataJsonImporter struct{} + +// Initialize an ezbookkeeping default transaction data json file importer singleton instance +var ( + DefaultTransactionDataJsonFileImporter = &defaultTransactionDataJsonImporter{} +) + +// ParseImportedData returns the imported data by parsing the transaction json data +func (c *defaultTransactionDataJsonImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { + var importRequest models.ImportTransactionRequest + + if err := json.Unmarshal(data, &importRequest); err != nil { + return nil, nil, nil, nil, nil, nil, errs.ErrInvalidJSONFile + } + + transactionDataTable, err := c.createNewDefaultTransactionDataTable(importRequest) + + if err != nil { + return nil, nil, nil, nil, nil, nil, err + } + + dataTableImporter := converter.CreateNewImporterWithTypeNameMapping( + ezbookkeepingTransactionTypeNameMapping, + ezbookkeepingGeoLocationSeparator, + ezbookkeepingGeoLocationOrder, + ezbookkeepingTagSeparator, + ) + + return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) +} + +func (c *defaultTransactionDataJsonImporter) createNewDefaultTransactionDataTable(importRequest models.ImportTransactionRequest) (datatable.TransactionDataTable, error) { + transactionDataTable := datatable.CreateNewWritableTransactionDataTable(allJsonDataSupportedColumns) + + if importRequest.Transactions == nil || len(importRequest.Transactions) < 1 { + return nil, errs.ErrNotFoundTransactionDataInFile + } + + for i := 0; i < len(importRequest.Transactions); i++ { + transaction := importRequest.Transactions[i] + + utcOffset, err := utils.StringToInt(transaction.UtcOffset) + + if err != nil { + return nil, errs.ErrTransactionTimeZoneInvalid + } + + timezone := time.FixedZone("Transaction Timezone", utcOffset*60) + + row := make(map[datatable.TransactionDataTableColumn]string, len(allJsonDataSupportedColumns)) + row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transaction.Time + row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(timezone) + row[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = transaction.Type + row[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = transaction.CategoryName + row[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = transaction.SourceAccountName + row[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = transaction.SourceAmount + row[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = transaction.DestinationAccountName + row[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = transaction.DestinationAmount + row[datatable.TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION] = transaction.GeoLocation + row[datatable.TRANSACTION_DATA_TABLE_TAGS] = transaction.TagNames + row[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = transaction.Comment + + transactionDataTable.Add(row) + } + + return transactionDataTable, nil +} diff --git a/pkg/converters/transaction_data_converters.go b/pkg/converters/transaction_data_converters.go index 0c90115c..003e4dc3 100644 --- a/pkg/converters/transaction_data_converters.go +++ b/pkg/converters/transaction_data_converters.go @@ -38,6 +38,8 @@ func GetTransactionDataImporter(fileType string) (converter.TransactionDataImpor return _default.DefaultTransactionDataCSVFileConverter, nil } else if fileType == "ezbookkeeping_tsv" { return _default.DefaultTransactionDataTSVFileConverter, nil + } else if fileType == "ezbookkeeping_json" { + return _default.DefaultTransactionDataJsonFileImporter, nil } else if fileType == "ofx" { return ofx.OFXTransactionDataImporter, nil } else if fileType == "qfx" { diff --git a/pkg/errs/converter.go b/pkg/errs/converter.go index 0156b08d..12b6d847 100644 --- a/pkg/errs/converter.go +++ b/pkg/errs/converter.go @@ -30,4 +30,5 @@ var ( ErrInvalidAmountExpression = NewNormalError(NormalSubcategoryConverter, 23, http.StatusBadRequest, "invalid amount expression") ErrInvalidXmlFile = NewNormalError(NormalSubcategoryConverter, 24, http.StatusBadRequest, "invalid xml file") ErrInvalidMT940File = NewNormalError(NormalSubcategoryConverter, 25, http.StatusBadRequest, "invalid mt940 file") + ErrInvalidJSONFile = NewNormalError(NormalSubcategoryConverter, 26, http.StatusBadRequest, "invalid json file") ) diff --git a/pkg/models/imported_transaction.go b/pkg/models/imported_transaction.go index bad827c7..2fa5a35d 100644 --- a/pkg/models/imported_transaction.go +++ b/pkg/models/imported_transaction.go @@ -14,6 +14,26 @@ type ImportTransaction struct { OriginalTagNames []string } +// ImportTransactionRequest represents all parameters of the imported transaction data +type ImportTransactionRequest struct { + Transactions []*ImportTransactionRequestItem +} + +// ImportTransactionRequestItem represents a single item of the imported transaction data +type ImportTransactionRequestItem struct { + Time string `json:"time"` + UtcOffset string `json:"utcOffset"` + Type string `json:"type"` + CategoryName string `json:"categoryName,omitempty"` + SourceAccountName string `json:"sourceAccountName,omitempty"` + DestinationAccountName string `json:"destinationAccountName,omitempty"` + SourceAmount string `json:"sourceAmount"` + DestinationAmount string `json:"destinationAmount,omitempty"` + GeoLocation string `json:"geoLocation,omitempty"` + TagNames string `json:"tagNames,omitempty"` + Comment string `json:"comment,omitempty"` +} + // ImportTransactionResponse represents a view-object of the imported transaction data type ImportTransactionResponse struct { Type TransactionType `json:"type"` diff --git a/src/core/file.ts b/src/core/file.ts index b1d5985b..be86c5f2 100644 --- a/src/core/file.ts +++ b/src/core/file.ts @@ -5,6 +5,7 @@ export class KnownFileType { 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'); + public static readonly JS = new KnownFileType('js', 'application/javascript'); public readonly extension: string; public readonly contentType: string; @@ -30,6 +31,12 @@ export class KnownFileType { }); } + public createFile(content: string, fileName: string): File { + return new File([content], this.formatFileName(fileName), { + type: this.contentType, + }); + } + public static parse(extension: string): KnownFileType | undefined { return KnownFileType.allInstancesByExtension[extension]; } diff --git a/src/locales/de.json b/src/locales/de.json index d40bc676..fc786e18 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -117,6 +117,7 @@ "youHaveAccounts": "Sie haben {count} Konten erfasst", "addNewTag": "Neuen Tag \"{tag}\" hinzufügen", "clickToSelectedFile": "Klicken Sie, um die Importdatei auszuwählen ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "{count} von {totalCount} ausgewählt", "youHaveUpdatedTransactions": "Sie haben {count} Transaktionen aktualisiert", "confirmImportTransactions": "Sind Sie sicher, dass Sie {count} Transaktionen importieren möchten?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Amount expression is invalid", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "OK", "Cancel": "Abbrechen", "Operation": "Vorgang", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "Datendatei", "Data to import": "Data to import", "Please select a file to import": "Bitte wählen Sie eine Datei zum Importieren aus", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "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", diff --git a/src/locales/en.json b/src/locales/en.json index 4113af69..9c5a292a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -117,6 +117,7 @@ "youHaveAccounts": "You have recorded {count} accounts", "addNewTag": "Add new tag \"{tag}\"", "clickToSelectedFile": "Click to select import file ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "Selected {count} of {totalCount}", "youHaveUpdatedTransactions": "You have updated {count} transactions", "confirmImportTransactions": "Are you sure you want to import {count} transactions?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Amount expression is invalid", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "OK", "Cancel": "Cancel", "Operation": "Operation", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "Data File", "Data to import": "Data to import", "Please select a file to import": "Please select a file to import", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "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", diff --git a/src/locales/es.json b/src/locales/es.json index fd63fdae..0cd4e603 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -117,6 +117,7 @@ "youHaveAccounts": "Has registrado {count} cuentas", "addNewTag": "Agregar nueva etiqueta \"{tag}\"", "clickToSelectedFile": "Haga clic para seleccionar el archivo de importación ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "Seleccionado {count} de {totalCount}", "youHaveUpdatedTransactions": "Has actualizado {count} transacciones", "confirmImportTransactions": "¿Está seguro de que desea importar {count} transacciones?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Amount expression is invalid", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "Aceptar", "Cancel": "Cancelar", "Operation": "Operación", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "Archivo de datos", "Data to import": "Data to import", "Please select a file to import": "Please select a file to import", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "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", diff --git a/src/locales/it.json b/src/locales/it.json index d3ec8f3f..49733fcd 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -117,6 +117,7 @@ "youHaveAccounts": "Hai registrato {count} profili", "addNewTag": "Aggiungi nuovo giorno \"{tag}\"", "clickToSelectedFile": "Carica un file ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "{count} selezionati su {totalCount}", "youHaveUpdatedTransactions": "Hai aggiornato {count} transazioni", "confirmImportTransactions": "Sei sicuro di voler importare {count} transazioni?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Espressione dell'importo non valida", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "OK", "Cancel": "Annulla", "Operation": "Operazione", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "File dati", "Data to import": "Dati da importare", "Please select a file to import": "Seleziona un file da importare", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "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", diff --git a/src/locales/ja.json b/src/locales/ja.json index a3cc3445..3013d9c3 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -117,6 +117,7 @@ "youHaveAccounts": "{count}アカウントを記録しました", "addNewTag": "新しいタグ\"{tag}\"を追加しました", "clickToSelectedFile": "クリックしてインポートファイルを選択します({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "{count} / {totalCount}を選択", "youHaveUpdatedTransactions": "{count}件の取引を更新しました", "confirmImportTransactions": "本当に{count}件の取引をインポートしますか?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Amount expression is invalid", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "OK", "Cancel": "キャンセル", "Operation": "操作", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "データファイル", "Data to import": "インポートするデータ", "Please select a file to import": "インポートするファイルを選択してください", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "Batch Replace Selected Expense Categories": "バッチは選択した支出カテゴリを置き換えます", "Batch Replace Selected Income Categories": "バッチは選択した収入カテゴリを置き換えます", "Batch Replace Selected Transfer Categories": "バッチは選択した振替カテゴリを置き換えます", diff --git a/src/locales/nl.json b/src/locales/nl.json index 02a3d3e4..6bf1f95c 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -117,6 +117,7 @@ "youHaveAccounts": "Je hebt {count} rekeningen geregistreerd", "addNewTag": "Nieuwe tag toevoegen \"{tag}\"", "clickToSelectedFile": "Klik om importbestand te selecteren ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "{count} van {totalCount} geselecteerd", "youHaveUpdatedTransactions": "Je hebt {count} transacties bijgewerkt", "confirmImportTransactions": "Weet je zeker dat je {count} transacties wilt importeren?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Bedragsexpressie is ongeldig", "invalid xml file": "Ongeldig XML-bestand", "invalid mt940 file": "Ongeldig MT940-bestand", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "Aangepaste wisselkoersgegevens niet gevonden", "cannot update exchange rate data for base currency": "Wisselkoersgegevens voor basisvaluta kunnen niet worden bijgewerkt", "cannot delete exchange rate data for base currency": "Wisselkoersgegevens voor basisvaluta kunnen niet worden verwijderd", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "OK", "Cancel": "Annuleren", "Operation": "Bewerking", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "Gegevensbestand", "Data to import": "Te importeren gegevens", "Please select a file to import": "Selecteer een bestand om te importeren", @@ -1799,6 +1827,14 @@ "Load Data Mapping File": "Datatoewijzingsbestand laden", "Save Data Mapping File": "Datatoewijzingsbestand opslaan", "Data mapping file is invalid": "Datatoewijzingsbestand is ongeldig", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "Batch Replace Selected Expense Categories": "Geselecteerde uitgavecategorieën batchgewijs vervangen", "Batch Replace Selected Income Categories": "Geselecteerde inkomencategorieën batchgewijs vervangen", "Batch Replace Selected Transfer Categories": "Geselecteerde overboekingscategorieën batchgewijs vervangen", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 1fa2269b..554a5f60 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -117,6 +117,7 @@ "youHaveAccounts": "Você registrou {count} contas", "addNewTag": "Adicionar nova etiqueta \"{tag}\"", "clickToSelectedFile": "Clique para selecionar arquivo de importação ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "Selecionado {count} de {totalCount}", "youHaveUpdatedTransactions": "Você atualizou {count} transações", "confirmImportTransactions": "Tem certeza de que deseja importar {count} transações?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Expressão de valor é inválida", "invalid xml file": "Arquivo XML inválido", "invalid mt940 file": "Arquivo MT940 inválido", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "Dados de taxa de câmbio personalizados do usuário não encontrados", "cannot update exchange rate data for base currency": "Não é possível atualizar dados de taxa de câmbio para a moeda base", "cannot delete exchange rate data for base currency": "Não é possível excluir dados de taxa de câmbio para a moeda base", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "OK", "Cancel": "Cancelar", "Operation": "Operação", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "Arquivo de Dados", "Data to import": "Dados para importar", "Please select a file to import": "Por favor, selecione um arquivo para importar", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "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", diff --git a/src/locales/ru.json b/src/locales/ru.json index 131c2424..471980cc 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -117,6 +117,7 @@ "youHaveAccounts": "У вас зарегистрировано {count} учетных записей", "addNewTag": "Добавить новый тег \"{tag}\"", "clickToSelectedFile": "Нажмите, чтобы выбрать файл для импорта ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "Выбрано {count} из {totalCount}", "youHaveUpdatedTransactions": "Вы обновили {count} транзакций", "confirmImportTransactions": "Вы уверены, что хотите импортировать {count} транзакций?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Amount expression is invalid", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "ОК", "Cancel": "Отмена", "Operation": "Операция", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "Файл данных", "Data to import": "Data to import", "Please select a file to import": "Please select a file to import", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "Batch Replace Selected Expense Categories": "Пакетная замена выбранных категорий расходов", "Batch Replace Selected Income Categories": "Пакетная замена выбранных категорий доходов", "Batch Replace Selected Transfer Categories": "Пакетная замена выбранных категорий переводов", diff --git a/src/locales/uk.json b/src/locales/uk.json index 8ef8175d..18a49c04 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -117,6 +117,7 @@ "youHaveAccounts": "У вас зареєстровано {count} рахунків", "addNewTag": "Додати новий тег \"{tag}\"", "clickToSelectedFile": "Натисніть, щоб вибрати файл для імпорту ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "Вибрано {count} з {totalCount}", "youHaveUpdatedTransactions": "Ви оновили {count} транзакцій", "confirmImportTransactions": "Ви впевнені, що хочете імпортувати {count} транзакцій?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Недійсний вираз суми", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "ОК", "Cancel": "Скасувати", "Operation": "Дія", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "Data File": "Файл даних", "Data to import": "Дані для імпорту", "Please select a file to import": "Будь ласка, виберіть файл для імпорту", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "Batch Replace Selected Expense Categories": "Пакетна заміна вибраних категорій витрат", "Batch Replace Selected Income Categories": "Пакетна заміна вибраних категорій доходів", "Batch Replace Selected Transfer Categories": "Пакетна заміна вибраних категорій переказів", diff --git a/src/locales/vi.json b/src/locales/vi.json index 14f5bb97..bb8ad310 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -117,6 +117,7 @@ "youHaveAccounts": "Bạn đã ghi nhận {count} tài khoản", "addNewTag": "Add new tag \"{tag}\"", "clickToSelectedFile": "Nhấp để chọn tệp nhập khẩu ({extensions})", + "previewCount": "Preview Count: {count}", "selectedCount": "Đã chọn {count} trên {totalCount}", "youHaveUpdatedTransactions": "Bạn đã cập nhật {count} giao dịch", "confirmImportTransactions": "Bạn có chắc chắn muốn nhập {count} giao dịch không?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements", "defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping", + "defaultImportHandlingScript": "ezBookkeeping_handling_script", "defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "Amount expression is invalid", "invalid xml file": "Invalid XML file", "invalid mt940 file": "Invalid MT940 file", + "invalid json file": "Invalid JSON file", "user custom exchange rate data not found": "User custom exchange rate data is not found", "cannot update exchange rate data for base currency": "Cannot update exchange rate data for base currency", "cannot delete exchange rate data for base currency": "Cannot delete exchange rate data for base currency", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "Example script:", + "functionDescription": "The parse function will be called for each row of the parsed file data, and the function name must be 'parse'", + "functionParamRowDescription": "An array of string, each element is a column value", + "functionParamIndexDescription": "The row index (0-based)", + "functionReturnDescription": "An object representing a transaction (with the fields defined below), or null to skip this row", + "fieldTimeDescription": "[required] Transaction time, format: YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[required] Transaction timezone offset in minutes, e.g. '480' for UTC+8, '-300' for UTC-5", + "fieldTypeDescription": "[required] Transaction type, must be one of 'TransactionType.Income', 'TransactionType.Expense' or 'TransactionType.Transfer'", + "fieldCategoryNameDescription": "[optional] Category name", + "fieldSourceAccountNameDescription": "[optional] Source account name", + "fieldDestinationAccountNameDescription": "[optional] Destination account name (for transfer type only)", + "fieldSourceAmountDescription": "[required] Source amount", + "fieldDestinationAmountDescription": "[optional] Destination amount (for transfer type only)", + "fieldGeoLocationDescription": "[optional] Geolocation, format: 'longitude latitude', e.g. '116.3912972 39.9057136'", + "fieldTagNamesDescription": "[optional] Comma separated tag names, e.g. 'tag1;tag2;tag3'", + "fieldCommentDescription": "[optional] Description" + } + }, "OK": "OK", "Cancel": "Hủy", "Operation": "Thao tác", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "Alipay (Web) Statement File", "WeChat Pay Statement File": "WeChat Pay Statement File", "JD.com Finance Statement File": "JD.com Finance Statement File", + "Handling Method": "Handling Method", + "Column Mapping": "Column Mapping", + "Custom Script": "Custom Script", + "Execute Custom Script": "Execute Custom Script", + "Execute Custom Script to Parse Data": "Execute Custom Script to Parse Data", "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", @@ -1799,6 +1827,14 @@ "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", + "Load Script File": "Load Script File", + "Save Script File": "Save Script File", + "Cannot load script file": "Cannot load script file", + "No Preview Result": "No Preview Result", + "Please execute the custom script first": "Please execute the custom script first", + "Executing Script...": "Executing Script...", + "No parse function defined": "No parse function defined", + "Failed to execute custom script": "Failed to execute custom script", "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", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index dd69172e..3e189533 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -117,6 +117,7 @@ "youHaveAccounts": "您已经记录了 {count} 个账户", "addNewTag": "添加新标签 \"{tag}\"", "clickToSelectedFile": "点击选择导入文件 ({extensions})", + "previewCount": "预览数量: {count}", "selectedCount": "已选择 {count} / {totalCount}", "youHaveUpdatedTransactions": "您已经更新 {count} 个交易", "confirmImportTransactions": "您确定要导入 {count} 个交易?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_对账单", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_对账单", "defaultImportDataMappingFileName": "ezBookkeeping_导入数据映射文件", + "defaultImportHandlingScript": "ezBookkeeping_导入处理脚本", "defaultImportReplaceRuleFileName": "ezBookkeeping_导入替换规则文件" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "金额表达式无效", "invalid xml file": "无效的 XML 文件", "invalid mt940 file": "无效的 MT940 文件", + "invalid json file": "无效的 JSON 文件", "user custom exchange rate data not found": "用户自定义汇率数据不存在", "cannot update exchange rate data for base currency": "不能更新默认货币的汇率数据", "cannot delete exchange rate data for base currency": "不能删除默认货币的汇率数据", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "示例脚本:", + "functionDescription": "解析函数会在解析文件数据的每一行调用,函数名必须为 'parse'", + "functionParamRowDescription": "字符串数组,每个元素为一列的值", + "functionParamIndexDescription": "行索引 (从0开始)", + "functionReturnDescription": "表示一笔交易的对象 (包含下面定义的字段),返回 null 时跳过此行", + "fieldTimeDescription": "[必填] 交易时间,格式:YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[必填] 交易时区的分钟偏移量,例如:'480' 表示 UTC+8,'-300' 表示 UTC-5", + "fieldTypeDescription": "[必填] 交易类型,必须为 'TransactionType.Income' (收入)、'TransactionType.Expense' (支出) 或 'TransactionType.Transfer' (转账)", + "fieldCategoryNameDescription": "[可选] 分类名称", + "fieldSourceAccountNameDescription": "[可选] 来源账户名称", + "fieldDestinationAccountNameDescription": "[可选] 目标账户名称 (仅用于转账类型)", + "fieldSourceAmountDescription": "[必填] 来源金额", + "fieldDestinationAmountDescription": "[可选] 目标金额 (仅用于转账类型)", + "fieldGeoLocationDescription": "[可选] 地理位置,格式:'经度 纬度',例如:'116.3912972 39.9057136'", + "fieldTagNamesDescription": "[可选] 以分号分隔的标签名称,例如:'标签1;标签2;标签3'", + "fieldCommentDescription": "[可选] 描述" + } + }, "OK": "确定", "Cancel": "取消", "Operation": "操作", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "支付宝 (网页版) 交易流水文件", "WeChat Pay Statement File": "微信支付账单文件", "JD.com Finance Statement File": "京东金融账单文件", + "Handling Method": "处理方法", + "Column Mapping": "列映射", + "Custom Script": "自定义脚本", + "Execute Custom Script": "执行自定义脚本", + "Execute Custom Script to Parse Data": "执行自定义脚本解析数据", "Data File": "数据文件", "Data to import": "要导入的数据", "Please select a file to import": "请选择要导入的文件", @@ -1799,6 +1827,14 @@ "Load Data Mapping File": "加载数据映射文件", "Save Data Mapping File": "保存数据映射文件", "Data mapping file is invalid": "数据映射文件无效", + "Load Script File": "加载脚本文件", + "Save Script File": "保存脚本文件", + "Cannot load script file": "无法加载脚本文件", + "No Preview Result": "没有预览结果", + "Please execute the custom script first": "请先执行自定义脚本", + "Executing Script...": "正在执行脚本...", + "No parse function defined": "没有定义解析函数 parse", + "Failed to execute custom script": "执行自定义脚本失败", "Batch Replace Selected Expense Categories": "批量替换选中的支出分类", "Batch Replace Selected Income Categories": "批量替换选中的收入分类", "Batch Replace Selected Transfer Categories": "批量替换选中的转账分类", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index 134f3e4e..3a46a759 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -117,6 +117,7 @@ "youHaveAccounts": "您已經記錄了 {count} 個帳戶", "addNewTag": "新增標籤 \"{tag}\"", "clickToSelectedFile": "點擊選擇匯入檔案 ({extensions})", + "previewCount": "預覽數量: {count}", "selectedCount": "已選擇 {count} / {totalCount}", "youHaveUpdatedTransactions": "您已經更新 {count} 個交易", "confirmImportTransactions": "您確定要匯入 {count} 個交易?", @@ -134,6 +135,7 @@ "defaultExportReconciliationStatementsFileName": "ezBookkeeping_對帳單", "exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_對帳單", "defaultImportDataMappingFileName": "ezBookkeeping_匯入資料對應檔案", + "defaultImportHandlingScript": "ezBookkeeping_匯入處理腳本", "defaultImportReplaceRuleFileName": "ezBookkeeping_匯入替換規則檔案" }, "calendar": { @@ -1217,6 +1219,7 @@ "invalid amount expression": "金額表達式無效", "invalid xml file": "無效的 XML 檔案", "invalid mt940 file": "無效的 MT940 檔案", + "invalid json file": "無效的 JSON 檔案", "user custom exchange rate data not found": "使用者自訂匯率資料不存在", "cannot update exchange rate data for base currency": "不能更新基準貨幣的匯率資料", "cannot delete exchange rate data for base currency": "不能刪除基準貨幣的匯率資料", @@ -1349,6 +1352,26 @@ } } }, + "sample": { + "importTransactionCustomScript": { + "headerComment": "範例腳本:", + "functionDescription": "解析函式會在解析檔案資料的每一行呼叫,函式名稱必須為 'parse'", + "functionParamRowDescription": "字串陣列,每個元素為一列的值", + "functionParamIndexDescription": "行索引 (從0開始)", + "functionReturnDescription": "表示一筆交易的物件 (包含下面定義的欄位),返回 null 時跳過此行", + "fieldTimeDescription": "[必填] 交易時間,格式:YYYY-MM-DD HH:mm:ss", + "fieldUtcOffsetDescription": "[必填] 交易時區的分鐘偏移量,例如:'480' 表示 UTC+8,'-300' 表示 UTC-5", + "fieldTypeDescription": "[必填] 交易類型,必須為 'TransactionType.Income' (收入)、'TransactionType.Expense' (支出) 或 'TransactionType.Transfer' (轉帳)", + "fieldCategoryNameDescription": "[可選] 分類名稱", + "fieldSourceAccountNameDescription": "[可選] 來源帳戶名稱", + "fieldDestinationAccountNameDescription": "[可選] 目標帳戶名稱 (僅用於轉帳類型)", + "fieldSourceAmountDescription": "[必填] 來源金額", + "fieldDestinationAmountDescription": "[可選] 目標金額 (僅用於轉帳類型)", + "fieldGeoLocationDescription": "[可選] 地理位置,格式:'經度 緯度',例如:'116.3912972 39.9057136'", + "fieldTagNamesDescription": "[可選] 以分號分隔的標籤名稱,例如:'標籤1;標籤2;標籤3'", + "fieldCommentDescription": "[可選] 描述" + } + }, "OK": "確定", "Cancel": "取消", "Operation": "操作", @@ -1777,6 +1800,11 @@ "Alipay (Web) Statement File": "支付寶 (網頁版) 交易流水檔案", "WeChat Pay Statement File": "微信支付帳單檔案", "JD.com Finance Statement File": "京東金融帳單檔案", + "Handling Method": "處理方法", + "Column Mapping": "欄位對應", + "Custom Script": "自訂腳本", + "Execute Custom Script": "執行自訂腳本", + "Execute Custom Script to Parse Data": "執行自訂腳本來解析資料", "Data File": "資料檔案", "Data to import": "要匯入的資料", "Please select a file to import": "請選擇要匯入的檔案", @@ -1799,6 +1827,14 @@ "Load Data Mapping File": "載入資料對應檔案", "Save Data Mapping File": "儲存資料對應檔案", "Data mapping file is invalid": "資料對應檔案無效", + "Load Script File": "載入腳本檔案", + "Save Script File": "儲存腳本檔案", + "Cannot load script file": "無法載入腳本檔案", + "No Preview Result": "沒有預覽結果", + "Please execute the custom script first": "請先執行自訂腳本", + "Executing Script...": "正在執行腳本...", + "No parse function defined": "沒有定義解析函式 parse", + "Failed to execute custom script": "執行自訂腳本失敗", "Batch Replace Selected Expense Categories": "批次替換選中的支出分類", "Batch Replace Selected Income Categories": "批次替換選中的收入分類", "Batch Replace Selected Transfer Categories": "批次替換選中的轉帳分類", diff --git a/src/models/imported_transaction.ts b/src/models/imported_transaction.ts index e5ca60cd..af404b32 100644 --- a/src/models/imported_transaction.ts +++ b/src/models/imported_transaction.ts @@ -103,6 +103,24 @@ export class ImportTransaction implements ImportTransactionResponse { } } +export interface ImportTransactionRequest { + readonly transactions: ImportTransactionRequestItem[]; +} + +export interface ImportTransactionRequestItem { + readonly time: string; + readonly utcOffset: string; + readonly type: string; + readonly categoryName?: string; + readonly sourceAccountName?: string; + readonly destinationAccountName?: string; + readonly sourceAmount: string; + readonly destinationAmount?: string; + readonly geoLocation?: string; + readonly tagNames?: string; + readonly comment?: string; +} + export interface ImportTransactionResponse { readonly type: number; readonly categoryId: string; diff --git a/src/views/desktop/transactions/import/ImportDialog.vue b/src/views/desktop/transactions/import/ImportDialog.vue index e34755d7..921caa06 100644 --- a/src/views/desktop/transactions/import/ImportDialog.vue +++ b/src/views/desktop/transactions/import/ImportDialog.vue @@ -22,6 +22,21 @@ + + + + + + + + @@ -115,6 +130,21 @@ /> + + + + + + + + v-if="currentStep === 'defineColumn' || currentStep === 'executeCustomScript' || currentStep === 'uploadFile'"> {{ tt('Next') }} @@ -206,6 +243,7 @@ import type { StepBarItem } from '@/components/desktop/StepsBar.vue'; import ConfirmDialog from '@/components/desktop/ConfirmDialog.vue'; import SnackBar from '@/components/desktop/SnackBar.vue'; import ImportTransactionDefineColumnTab from './tabs/ImportTransactionDefineColumnTab.vue'; +import ImportTransactionExecuteCustomScriptTab from './tabs/ImportTransactionExecuteCustomScriptTab.vue'; import ImportTransactionCheckDataTab from './tabs/ImportTransactionCheckDataTab.vue'; import { ref, computed, useTemplateRef, watch } from 'vue'; @@ -222,6 +260,7 @@ import { useStatisticsStore } from '@/stores/statistics.ts'; import { itemAndIndex } from '@/core/base.ts'; import { type NumeralSystem } from '@/core/numeral.ts'; import { TransactionType } from '@/core/transaction.ts'; +import { KnownFileType } from '@/core/file.ts'; import type { LocalizedImportFileCategoryAndTypes, LocalizedImportFileType, LocalizedImportFileTypeSubType, LocalizedImportFileTypeSupportedEncodings } from '@/core/file.ts'; import { ImportTransaction } from '@/models/imported_transaction.ts'; @@ -243,9 +282,14 @@ import { type ConfirmDialogType = InstanceType; type SnackBarType = InstanceType; type ImportTransactionDefineColumnTabType = InstanceType; +type ImportTransactionExecuteCustomScriptTabType = InstanceType; type ImportTransactionCheckDataTabType = InstanceType; -type ImportTransactionDialogStep = 'uploadFile' | 'defineColumn' | 'checkData' | 'finalResult'; +type ImportTransactionDialogStep = 'uploadFile' | 'defineColumn' | 'executeCustomScript' | 'checkData' | 'finalResult'; +enum ImportDSVProcessMethod { + ColumnMapping, + CustomScript +}; defineProps<{ persistent?: boolean; @@ -268,6 +312,7 @@ const statisticsStore = useStatisticsStore(); const confirmDialog = useTemplateRef('confirmDialog'); const snackbar = useTemplateRef('snackbar'); const importTransactionDefineColumnTab = useTemplateRef('importTransactionDefineColumnTab'); +const importTransactionExecuteCustomScriptTab = useTemplateRef('importTransactionExecuteCustomScriptTab'); const importTransactionCheckDataTab = useTemplateRef('importTransactionCheckDataTab'); const fileInput = useTemplateRef('fileInput'); @@ -278,6 +323,7 @@ const importProcess = ref(0); const fileType = ref('ezbookkeeping'); const fileSubType = ref('ezbookkeeping_csv'); const fileEncoding = ref('utf-8'); +const processDSVMethod = ref(ImportDSVProcessMethod.ColumnMapping); const importFile = ref(null); const importData = ref(''); const parsedFileData = ref(undefined); @@ -307,11 +353,19 @@ const allSteps = computed(() => { ]; if (fileType.value === 'dsv' || fileType.value === 'dsv_data') { - steps.push({ - name: 'defineColumn', - title: tt('Define Column'), - subTitle: tt('Define and Check Column Mapping') - }); + if (processDSVMethod.value === ImportDSVProcessMethod.CustomScript) { + steps.push({ + name: 'executeCustomScript', + title: tt('Execute Custom Script'), + subTitle: tt('Execute Custom Script to Parse Data') + }); + } else { + steps.push({ + name: 'defineColumn', + title: tt('Define Column'), + subTitle: tt('Define and Check Column Mapping') + }); + } } steps.push(...[ @@ -378,12 +432,14 @@ function open(): Promise { fileType.value = 'ezbookkeeping'; fileSubType.value = 'ezbookkeeping_csv'; fileEncoding.value = 'utf-8'; + processDSVMethod.value = ImportDSVProcessMethod.ColumnMapping; currentStep.value = 'uploadFile'; importProcess.value = 0; importFile.value = null; importData.value = ''; parsedFileData.value = undefined; importTransactionDefineColumnTab.value?.reset(); + importTransactionExecuteCustomScriptTab.value?.reset(); importTransactions.value = undefined; importTransactionCheckDataTab.value?.reset(); showState.value = true; @@ -466,9 +522,9 @@ function parseData(): void { } if (type === 'custom_csv') { - uploadFile = new File([importData.value], 'import.csv', { type: 'text/csv' }); + uploadFile = KnownFileType.CSV.createFile(importData.value, 'import'); } else if (type === 'custom_tsv') { - uploadFile = new File([importData.value], 'import.tsv', { type: 'text/tab-separated-values' }); + uploadFile = KnownFileType.TSV.createFile(importData.value, 'import'); } else { snackbar.value?.showError('Parameter Invalid'); return; @@ -491,9 +547,15 @@ function parseData(): void { importFile: uploadFile }).then(response => { if (response && response.length) { - importTransactionDefineColumnTab.value?.reset(); - parsedFileData.value = response; - currentStep.value = 'defineColumn'; + if (processDSVMethod.value === ImportDSVProcessMethod.CustomScript) { + importTransactionExecuteCustomScriptTab.value?.reset(); + parsedFileData.value = response; + currentStep.value = 'executeCustomScript'; + } else { + importTransactionDefineColumnTab.value?.reset(); + parsedFileData.value = response; + currentStep.value = 'defineColumn'; + } } else { parsedFileData.value = undefined; snackbar.value?.showError('No data to import'); @@ -519,7 +581,7 @@ function parseData(): void { let geoLocationOrder: string | undefined = undefined; let tagSeparator: string | undefined = undefined; - if (isDsvFileType) { + if (isDsvFileType && processDSVMethod.value === ImportDSVProcessMethod.ColumnMapping) { const defineColumnResult = importTransactionDefineColumnTab.value?.generateResult(); if (!defineColumnResult) { @@ -536,6 +598,16 @@ function parseData(): void { geoLocationSeparator = defineColumnResult.geoLocationSeparator; geoLocationOrder = defineColumnResult.geoLocationOrder; tagSeparator = defineColumnResult.tagSeparator; + } else if (isDsvFileType && processDSVMethod.value === ImportDSVProcessMethod.CustomScript) { + const executeCustomScriptResult = importTransactionExecuteCustomScriptTab.value?.generateResult(); + + if (!executeCustomScriptResult) { + return; + } + + type = 'ezbookkeeping_json'; + encoding = undefined; + uploadFile = KnownFileType.JSON.createFile(executeCustomScriptResult, 'import'); } submitting.value = true; diff --git a/src/views/desktop/transactions/import/tabs/ImportTransactionExecuteCustomScriptTab.vue b/src/views/desktop/transactions/import/tabs/ImportTransactionExecuteCustomScriptTab.vue new file mode 100644 index 00000000..558b95ba --- /dev/null +++ b/src/views/desktop/transactions/import/tabs/ImportTransactionExecuteCustomScriptTab.vue @@ -0,0 +1,346 @@ + + +