mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-20 01:34:24 +08:00
set amount format in import dialog
This commit is contained in:
+15
-1
@@ -1197,6 +1197,20 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
|
|||||||
timezoneFormat = timezoneFormats[0]
|
timezoneFormat = timezoneFormats[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
amountDecimalSeparators := form.Value["amountDecimalSeparator"]
|
||||||
|
amountDecimalSeparator := ""
|
||||||
|
|
||||||
|
if len(amountDecimalSeparators) > 0 {
|
||||||
|
amountDecimalSeparator = amountDecimalSeparators[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
amountDigitGroupingSymbols := form.Value["amountDigitGroupingSymbol"]
|
||||||
|
amountDigitGroupingSymbol := ""
|
||||||
|
|
||||||
|
if len(amountDigitGroupingSymbols) > 0 {
|
||||||
|
amountDigitGroupingSymbol = amountDigitGroupingSymbols[0]
|
||||||
|
}
|
||||||
|
|
||||||
geoLocationSeparators := form.Value["geoSeparator"]
|
geoLocationSeparators := form.Value["geoSeparator"]
|
||||||
geoLocationSeparator := ""
|
geoLocationSeparator := ""
|
||||||
|
|
||||||
@@ -1211,7 +1225,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
|
|||||||
transactionTagSeparator = transactionTagSeparators[0]
|
transactionTagSeparator = transactionTagSeparators[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
dataImporter, err = converters.CreateNewDelimiterSeparatedValuesDataImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormats[0], timezoneFormat, geoLocationSeparator, transactionTagSeparator)
|
dataImporter, err = converters.CreateNewDelimiterSeparatedValuesDataImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormats[0], timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoLocationSeparator, transactionTagSeparator)
|
||||||
} else {
|
} else {
|
||||||
dataImporter, err = converters.GetTransactionDataImporter(fileType)
|
dataImporter, err = converters.GetTransactionDataImporter(fileType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ type customTransactionDataDsvFileImporter struct {
|
|||||||
hasHeaderLine bool
|
hasHeaderLine bool
|
||||||
timeFormat string
|
timeFormat string
|
||||||
timezoneFormat string
|
timezoneFormat string
|
||||||
|
amountDecimalSeparator string
|
||||||
|
amountDigitGroupingSymbol string
|
||||||
geoLocationSeparator string
|
geoLocationSeparator string
|
||||||
transactionTagSeparator string
|
transactionTagSeparator string
|
||||||
}
|
}
|
||||||
@@ -155,7 +157,7 @@ func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
dataTable := csvconverter.CreateNewCustomCsvImportedDataTable(allLines)
|
dataTable := csvconverter.CreateNewCustomCsvImportedDataTable(allLines)
|
||||||
transactionDataTable := CreateNewCustomPlainTextDataTable(dataTable, c.columnIndexMapping, c.transactionTypeNameMapping, c.timeFormat, c.timezoneFormat)
|
transactionDataTable := CreateNewCustomPlainTextDataTable(dataTable, c.columnIndexMapping, c.transactionTypeNameMapping, c.timeFormat, c.timezoneFormat, c.amountDecimalSeparator, c.amountDigitGroupingSymbol)
|
||||||
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(customTransactionTypeNameMapping, c.geoLocationSeparator, c.transactionTagSeparator)
|
dataTableImporter := converter.CreateNewImporterWithTypeNameMapping(customTransactionTypeNameMapping, c.geoLocationSeparator, c.transactionTagSeparator)
|
||||||
|
|
||||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||||
@@ -188,7 +190,7 @@ func CreateNewCustomTransactionDataDsvFileParser(fileType string, fileEncoding s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateNewCustomTransactionDataDsvFileImporter returns a new custom dsv importer for transaction data
|
// CreateNewCustomTransactionDataDsvFileImporter returns a new custom dsv importer for transaction data
|
||||||
func CreateNewCustomTransactionDataDsvFileImporter(fileType string, fileEncoding string, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, hasHeaderLine bool, timeFormat string, timezoneFormat string, geoLocationSeparator string, transactionTagSeparator string) (converter.TransactionDataImporter, error) {
|
func CreateNewCustomTransactionDataDsvFileImporter(fileType string, fileEncoding string, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, hasHeaderLine bool, timeFormat string, timezoneFormat string, amountDecimalSeparator string, amountDigitGroupingSymbol string, geoLocationSeparator string, transactionTagSeparator string) (converter.TransactionDataImporter, error) {
|
||||||
separator, exists := supportedFileTypeSeparators[fileType]
|
separator, exists := supportedFileTypeSeparators[fileType]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -221,6 +223,8 @@ func CreateNewCustomTransactionDataDsvFileImporter(fileType string, fileEncoding
|
|||||||
hasHeaderLine: hasHeaderLine,
|
hasHeaderLine: hasHeaderLine,
|
||||||
timeFormat: timeFormat,
|
timeFormat: timeFormat,
|
||||||
timezoneFormat: timezoneFormat,
|
timezoneFormat: timezoneFormat,
|
||||||
|
amountDecimalSeparator: amountDecimalSeparator,
|
||||||
|
amountDigitGroupingSymbol: amountDigitGroupingSymbol,
|
||||||
geoLocationSeparator: geoLocationSeparator,
|
geoLocationSeparator: geoLocationSeparator,
|
||||||
transactionTagSeparator: transactionTagSeparator,
|
transactionTagSeparator: transactionTagSeparator,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func TestCustomTransactionDataDsvFileImporter_MinimumValidData(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", ".", "", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -168,7 +168,7 @@ func TestCustomTransactionDataDsvFileImporter_WithAllSupportedColumns(t *testing
|
|||||||
"Expense": models.TRANSACTION_TYPE_EXPENSE,
|
"Expense": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"Transfer": models.TRANSACTION_TYPE_TRANSFER,
|
"Transfer": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, true, "YYYY-MM-DD HH:mm:ss", "", " ", ";")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, true, "YYYY-MM-DD HH:mm:ss", "", ".", "", " ", ";")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -261,7 +261,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTime(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -292,7 +292,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTransactionWithoutType(t *tes
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -316,7 +316,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidType(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"B": 0,
|
"B": 0,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -340,7 +340,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone(t *testing.T
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZ", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZ", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -378,7 +378,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTimeWithTimezone2(t *testing.
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZZ", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ssZZ", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -417,7 +417,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -456,7 +456,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidTimezone2(t *testing.T)
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -495,7 +495,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezoneFormat(t *test
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "z", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "z", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -520,7 +520,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone(t *testing.T)
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -549,7 +549,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "ZZ", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -568,6 +568,80 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidTimezone2(t *testing.T
|
|||||||
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
assert.EqualError(t, err, errs.ErrTransactionTimeZoneInvalid.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomTransactionDataDsvFileImporter_ParseAmountWithCustomFormat(t *testing.T) {
|
||||||
|
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
||||||
|
}
|
||||||
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
|
}
|
||||||
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ",", ".", "", "")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||||
|
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, int64(123456), allNewTransactions[0].Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat(t *testing.T) {
|
||||||
|
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
||||||
|
}
|
||||||
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
|
}
|
||||||
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", ",", "", "")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||||
|
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmountWithCustomFormat2(t *testing.T) {
|
||||||
|
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
||||||
|
}
|
||||||
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
|
}
|
||||||
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_tsv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ",", "", "", "")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||||
|
"2024-09-01 12:34:56\tE\t1.234,56"), 0, nil, nil, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T) {
|
func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T) {
|
||||||
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
columnIndexMapping := map[datatable.TransactionDataTableColumn]int{
|
||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||||
@@ -581,7 +655,7 @@ func TestCustomTransactionDataDsvFileImporter_ParsePrimaryCategory(t *testing.T)
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -650,7 +724,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAccountCurrency(t *testi
|
|||||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -693,7 +767,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAccountCurrency(t *tes
|
|||||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -729,7 +803,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseNotSupportedCurrency(t *testi
|
|||||||
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
"B": models.TRANSACTION_TYPE_MODIFY_BALANCE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -761,7 +835,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidAmount(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -812,7 +886,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidAmount(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -843,7 +917,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseNoAmount2(t *testing.T) {
|
|||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -878,7 +952,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseValidGeographicLocation(t *te
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ";", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", ";", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -907,7 +981,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseInvalidGeographicLocation(t *
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", " ", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", " ", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -939,7 +1013,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTag(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", ";")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", ";")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -979,7 +1053,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseTagWithoutSeparator(t *testin
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"E": models.TRANSACTION_TYPE_EXPENSE,
|
"E": models.TRANSACTION_TYPE_EXPENSE,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -1010,7 +1084,7 @@ func TestCustomTransactionDataDsvFileImporter_ParseDescription(t *testing.T) {
|
|||||||
transactionTypeMapping := map[string]models.TransactionType{
|
transactionTypeMapping := map[string]models.TransactionType{
|
||||||
"T": models.TRANSACTION_TYPE_TRANSFER,
|
"T": models.TRANSACTION_TYPE_TRANSFER,
|
||||||
}
|
}
|
||||||
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
converter, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -1037,7 +1111,7 @@ func TestCustomTransactionDataDsvFileImporter_InvalidSeparator(t *testing.T) {
|
|||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
||||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
||||||
}
|
}
|
||||||
_, err := CreateNewCustomTransactionDataDsvFileImporter("test", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
_, err := CreateNewCustomTransactionDataDsvFileImporter("test", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.EqualError(t, err, errs.ErrImportFileTypeNotSupported.Message)
|
assert.EqualError(t, err, errs.ErrImportFileTypeNotSupported.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1050,7 +1124,7 @@ func TestCustomTransactionDataDsvFileImporter_InvalidFileEncoding(t *testing.T)
|
|||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
||||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 2,
|
||||||
}
|
}
|
||||||
_, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "ascii", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
_, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "ascii", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.EqualError(t, err, errs.ErrImportFileEncodingNotSupported.Message)
|
assert.EqualError(t, err, errs.ErrImportFileEncodingNotSupported.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1064,7 +1138,7 @@ func TestCustomTransactionDataDsvFileImporter_MissingRequiredColumn(t *testing.T
|
|||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 0,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 0,
|
||||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 1,
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 1,
|
||||||
}
|
}
|
||||||
_, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
_, err := CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Type Column
|
// Missing Type Column
|
||||||
@@ -1072,7 +1146,7 @@ func TestCustomTransactionDataDsvFileImporter_MissingRequiredColumn(t *testing.T
|
|||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 1,
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: 1,
|
||||||
}
|
}
|
||||||
_, err = CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
_, err = CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
// Missing Amount Column
|
// Missing Amount Column
|
||||||
@@ -1080,6 +1154,6 @@ func TestCustomTransactionDataDsvFileImporter_MissingRequiredColumn(t *testing.T
|
|||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: 0,
|
||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: 1,
|
||||||
}
|
}
|
||||||
_, err = CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", "", "")
|
_, err = CreateNewCustomTransactionDataDsvFileImporter("custom_csv", "utf-8", columnIndexMapping, transactionTypeMapping, false, "YYYY-MM-DD HH:mm:ss", "", ".", "", "", "")
|
||||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ type customPlainTextDataTable struct {
|
|||||||
timeFormat string
|
timeFormat string
|
||||||
timezoneFormat string
|
timezoneFormat string
|
||||||
timeFormatIncludeTimezone bool
|
timeFormatIncludeTimezone bool
|
||||||
|
amountDecimalSeparator string
|
||||||
|
amountDigitGroupingSymbol string
|
||||||
}
|
}
|
||||||
|
|
||||||
// customPlainTextDataRow defines the structure of custom plain text transaction data row
|
// customPlainTextDataRow defines the structure of custom plain text transaction data row
|
||||||
@@ -174,27 +176,25 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user
|
|||||||
|
|
||||||
// trim trailing zero in decimal
|
// trim trailing zero in decimal
|
||||||
if rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] != "" {
|
if rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] != "" {
|
||||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.TrimTrailingZerosInDecimal(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
|
amount, err := t.parseAmount(ctx, rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
|
||||||
amount, err := utils.ParseAmount(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(ctx, "[custom_transaction_plain_text_data_table.parseTransaction] cannot parsing transaction amount \"%s\", because %s", rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT], err.Error())
|
log.Errorf(ctx, "[custom_transaction_plain_text_data_table.parseTransaction] cannot parsing transaction amount \"%s\", because %s", rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT], err.Error())
|
||||||
return nil, false, errs.ErrAmountInvalid
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
|
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] != "" {
|
if rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] != "" {
|
||||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.TrimTrailingZerosInDecimal(rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT])
|
amount, err := t.parseAmount(ctx, rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT])
|
||||||
amount, err := utils.ParseAmount(rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT])
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(ctx, "[custom_transaction_plain_text_data_table.parseTransaction] cannot parsing transaction related amount \"%s\", because %s", rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT], err.Error())
|
log.Errorf(ctx, "[custom_transaction_plain_text_data_table.parseTransaction] cannot parsing transaction related amount \"%s\", because %s", rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT], err.Error())
|
||||||
return nil, false, errs.ErrAmountInvalid
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(amount)
|
rowData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY]; !exists {
|
if _, exists := rowData[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY]; !exists {
|
||||||
@@ -212,8 +212,31 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user
|
|||||||
return rowData, true, nil
|
return rowData, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *customPlainTextDataRowIterator) parseAmount(ctx core.Context, amountValue string) (string, error) {
|
||||||
|
if t.transactionDataTable.amountDigitGroupingSymbol != "" {
|
||||||
|
amountValue = strings.ReplaceAll(amountValue, t.transactionDataTable.amountDigitGroupingSymbol, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.transactionDataTable.amountDecimalSeparator != "" && t.transactionDataTable.amountDecimalSeparator != "." {
|
||||||
|
if strings.Contains(amountValue, ".") {
|
||||||
|
return "", errs.ErrAmountInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
amountValue = strings.ReplaceAll(amountValue, t.transactionDataTable.amountDecimalSeparator, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
amountValue = utils.TrimTrailingZerosInDecimal(amountValue)
|
||||||
|
amount, err := utils.ParseAmount(amountValue)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", errs.ErrAmountInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.FormatAmount(amount), nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateNewCustomPlainTextDataTable returns transaction data table from imported data table
|
// CreateNewCustomPlainTextDataTable returns transaction data table from imported data table
|
||||||
func CreateNewCustomPlainTextDataTable(dataTable datatable.ImportedDataTable, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, timeFormat string, timezoneFormat string) *customPlainTextDataTable {
|
func CreateNewCustomPlainTextDataTable(dataTable datatable.ImportedDataTable, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, timeFormat string, timezoneFormat string, amountDecimalSeparator string, amountDigitGroupingSymbol string) *customPlainTextDataTable {
|
||||||
timeFormatIncludeTimezone := strings.Contains(timeFormat, "z") || strings.Contains(timeFormat, "Z")
|
timeFormatIncludeTimezone := strings.Contains(timeFormat, "z") || strings.Contains(timeFormat, "Z")
|
||||||
|
|
||||||
return &customPlainTextDataTable{
|
return &customPlainTextDataTable{
|
||||||
@@ -223,6 +246,8 @@ func CreateNewCustomPlainTextDataTable(dataTable datatable.ImportedDataTable, co
|
|||||||
timeFormat: getDateTimeFormat(timeFormat),
|
timeFormat: getDateTimeFormat(timeFormat),
|
||||||
timezoneFormat: timezoneFormat,
|
timezoneFormat: timezoneFormat,
|
||||||
timeFormatIncludeTimezone: timeFormatIncludeTimezone,
|
timeFormatIncludeTimezone: timeFormatIncludeTimezone,
|
||||||
|
amountDecimalSeparator: amountDecimalSeparator,
|
||||||
|
amountDigitGroupingSymbol: amountDigitGroupingSymbol,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,6 @@ func CreateNewDelimiterSeparatedValuesDataParser(fileType string, fileEncoding s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateNewDelimiterSeparatedValuesDataImporter returns a new delimiter-separated values data importer according to the file type and encoding
|
// CreateNewDelimiterSeparatedValuesDataImporter returns a new delimiter-separated values data importer according to the file type and encoding
|
||||||
func CreateNewDelimiterSeparatedValuesDataImporter(fileType string, fileEncoding string, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, hasHeaderLine bool, timeFormat string, timezoneFormat string, geoLocationSeparator string, transactionTagSeparator string) (converter.TransactionDataImporter, error) {
|
func CreateNewDelimiterSeparatedValuesDataImporter(fileType string, fileEncoding string, columnIndexMapping map[datatable.TransactionDataTableColumn]int, transactionTypeNameMapping map[string]models.TransactionType, hasHeaderLine bool, timeFormat string, timezoneFormat string, amountDecimalSeparator string, amountDigitGroupingSymbol string, geoLocationSeparator string, transactionTagSeparator string) (converter.TransactionDataImporter, error) {
|
||||||
return dsv.CreateNewCustomTransactionDataDsvFileImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormat, timezoneFormat, geoLocationSeparator, transactionTagSeparator)
|
return dsv.CreateNewCustomTransactionDataDsvFileImporter(fileType, fileEncoding, columnIndexMapping, transactionTypeNameMapping, hasHeaderLine, timeFormat, timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoLocationSeparator, transactionTagSeparator)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,87 @@ export class DigitGroupingType implements TypeAndName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class KnownAmountFormat {
|
||||||
|
private static readonly allInstances: KnownAmountFormat[] = [];
|
||||||
|
private static readonly allInstancesByType: Record<string, KnownAmountFormat> = {};
|
||||||
|
|
||||||
|
public static readonly DotDecimalSeparator = new KnownAmountFormat('1234.56', DecimalSeparator.Dot, undefined, /^-?[0-9]+(\.[0-9]+)?$/);
|
||||||
|
public static readonly CommaDecimalSeparator = new KnownAmountFormat('1234,56', DecimalSeparator.Comma, undefined, /^-?[0-9]+(,[0-9]+)?$/);
|
||||||
|
public static readonly DotDecimalSeparatorWithCommaGroupingSymbol = new KnownAmountFormat('1,234.56', DecimalSeparator.Dot, DigitGroupingSymbol.Comma, /^-?([0-9]+,)*[0-9]+(\.[0-9]+)?$/);
|
||||||
|
public static readonly CommaDecimalSeparatorWithDotGroupingSymbol = new KnownAmountFormat('1.234,56', DecimalSeparator.Comma, DigitGroupingSymbol.Dot, /^-?([0-9]+\.)*[0-9]+(,[0-9]+)?$/);
|
||||||
|
public static readonly DotDecimalSeparatorWithSpaceGroupingSymbol = new KnownAmountFormat('1 234.56', DecimalSeparator.Dot, DigitGroupingSymbol.Space, /^-?([0-9]+ )*[0-9]+(\.[0-9]+)?$/);
|
||||||
|
public static readonly CommaDecimalSeparatorWithSpaceGroupingSymbol = new KnownAmountFormat('1 234,56', DecimalSeparator.Comma, DigitGroupingSymbol.Space, /^-?([0-9]+ )*[0-9]+(,[0-9]+)?$/);
|
||||||
|
public static readonly DotDecimalSeparatorWithApostropheGroupingSymbol = new KnownAmountFormat('1\'234.56', DecimalSeparator.Dot, DigitGroupingSymbol.Apostrophe, /^-?([0-9]+')*[0-9]+(\.[0-9]+)?$/);
|
||||||
|
public static readonly CommaDecimalSeparatorWithApostropheGroupingSymbol = new KnownAmountFormat('1\'234,56', DecimalSeparator.Comma, DigitGroupingSymbol.Apostrophe, /^-?([0-9]+')*[0-9]+(,[0-9]+)?$/);
|
||||||
|
|
||||||
|
public readonly format: string;
|
||||||
|
public readonly decimalSeparator: DecimalSeparator;
|
||||||
|
public readonly digitGroupingSymbol?: DigitGroupingSymbol;
|
||||||
|
public readonly type: string;
|
||||||
|
private readonly regex: RegExp;
|
||||||
|
|
||||||
|
private constructor(format: string, decimalSeparator: DecimalSeparator, digitGroupingSymbol: DigitGroupingSymbol | undefined, regex: RegExp) {
|
||||||
|
this.format = format;
|
||||||
|
this.decimalSeparator = decimalSeparator;
|
||||||
|
this.digitGroupingSymbol = digitGroupingSymbol;
|
||||||
|
this.type = this.decimalSeparator.type + '-' + (this.digitGroupingSymbol ? this.digitGroupingSymbol.type : 0).toString();
|
||||||
|
this.regex = regex;
|
||||||
|
|
||||||
|
KnownAmountFormat.allInstances.push(this);
|
||||||
|
KnownAmountFormat.allInstancesByType[this.type] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isValid(amount: string): boolean {
|
||||||
|
return this.regex.test(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static values(): KnownAmountFormat[] {
|
||||||
|
return KnownAmountFormat.allInstances;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static valueOf(type: string): KnownAmountFormat | undefined {
|
||||||
|
return KnownAmountFormat.allInstancesByType[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static detect(amount: string): KnownAmountFormat[] | undefined {
|
||||||
|
const result: KnownAmountFormat[] = [];
|
||||||
|
|
||||||
|
for (const format of KnownAmountFormat.allInstances) {
|
||||||
|
if (format.isValid(amount)) {
|
||||||
|
result.push(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.length > 0 ? result : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static detectMulti(amounts: string[]): KnownAmountFormat[] | undefined {
|
||||||
|
const detectedCounts: Record<string, number> = {};
|
||||||
|
|
||||||
|
for (const amount of amounts) {
|
||||||
|
const detectedFormats = KnownAmountFormat.detect(amount);
|
||||||
|
|
||||||
|
if (detectedFormats) {
|
||||||
|
for (const format of detectedFormats) {
|
||||||
|
detectedCounts[format.type] = (detectedCounts[format.type] || 0) + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: KnownAmountFormat[] = [];
|
||||||
|
|
||||||
|
for (const format of KnownAmountFormat.allInstances) {
|
||||||
|
if (detectedCounts[format.type] === amounts.length) {
|
||||||
|
result.push(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.length > 0 ? result : undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class AmountFilterType {
|
export class AmountFilterType {
|
||||||
private static readonly allInstances: AmountFilterType[] = [];
|
private static readonly allInstances: AmountFilterType[] = [];
|
||||||
private static readonly allInstancesByType: Record<string, AmountFilterType> = {};
|
private static readonly allInstancesByType: Record<string, AmountFilterType> = {};
|
||||||
|
|||||||
+3
-1
@@ -440,7 +440,7 @@ export default {
|
|||||||
timeout: DEFAULT_UPLOAD_API_TIMEOUT
|
timeout: DEFAULT_UPLOAD_API_TIMEOUT
|
||||||
} as ApiRequestConfig);
|
} as ApiRequestConfig);
|
||||||
},
|
},
|
||||||
parseImportTransaction: ({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, geoSeparator, tagSeparator }: { fileType: string, fileEncoding?: string, importFile: File, columnMapping?: Record<number, number>, transactionTypeMapping?: Record<string, TransactionType>, hasHeaderLine?: boolean, timeFormat?: string, timezoneFormat?: string, geoSeparator?: string, tagSeparator?: string }): ApiResponsePromise<ImportTransactionResponsePageWrapper> => {
|
parseImportTransaction: ({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoSeparator, tagSeparator }: { fileType: string, fileEncoding?: string, importFile: File, columnMapping?: Record<number, number>, transactionTypeMapping?: Record<string, TransactionType>, hasHeaderLine?: boolean, timeFormat?: string, timezoneFormat?: string, amountDecimalSeparator?: string, amountDigitGroupingSymbol?: string, geoSeparator?: string, tagSeparator?: string }): ApiResponsePromise<ImportTransactionResponsePageWrapper> => {
|
||||||
let textualColumnMapping: string | undefined = undefined;
|
let textualColumnMapping: string | undefined = undefined;
|
||||||
let textualTransactionTypeMapping: string | undefined = undefined;
|
let textualTransactionTypeMapping: string | undefined = undefined;
|
||||||
let textualHasHeaderLine: string | undefined = undefined;
|
let textualHasHeaderLine: string | undefined = undefined;
|
||||||
@@ -466,6 +466,8 @@ export default {
|
|||||||
hasHeaderLine: textualHasHeaderLine,
|
hasHeaderLine: textualHasHeaderLine,
|
||||||
timeFormat: timeFormat,
|
timeFormat: timeFormat,
|
||||||
timezoneFormat: timezoneFormat,
|
timezoneFormat: timezoneFormat,
|
||||||
|
amountDecimalSeparator: amountDecimalSeparator,
|
||||||
|
amountDigitGroupingSymbol: amountDigitGroupingSymbol,
|
||||||
geoSeparator: geoSeparator,
|
geoSeparator: geoSeparator,
|
||||||
tagSeparator: tagSeparator
|
tagSeparator: tagSeparator
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -1668,6 +1668,7 @@
|
|||||||
"Time Format": "Time Format",
|
"Time Format": "Time Format",
|
||||||
"Transaction Type Mapping": "Transaction Type Mapping",
|
"Transaction Type Mapping": "Transaction Type Mapping",
|
||||||
"Timezone Format": "Timezone Format",
|
"Timezone Format": "Timezone Format",
|
||||||
|
"Amount Format": "Amount Format",
|
||||||
"Geographic Location Separator": "Geographic Location Separator",
|
"Geographic Location Separator": "Geographic Location Separator",
|
||||||
"Transaction Tags Separator": "Transaction Tags Separator",
|
"Transaction Tags Separator": "Transaction Tags Separator",
|
||||||
"Lines Per Page": "Lines Per Page",
|
"Lines Per Page": "Lines Per Page",
|
||||||
@@ -1675,6 +1676,7 @@
|
|||||||
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
||||||
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
||||||
"Transaction time format is not set": "Transaction time format is not set",
|
"Transaction time format is not set": "Transaction time format is not set",
|
||||||
|
"Transaction amount format is not set": "Transaction amount format is not set",
|
||||||
"Cannot import invalid transactions": "Ungültige Transaktionen können nicht importiert werden",
|
"Cannot import invalid transactions": "Ungültige Transaktionen können nicht importiert werden",
|
||||||
"Unable to parse import file": "Importdatei kann nicht geparst werden",
|
"Unable to parse import file": "Importdatei kann nicht geparst werden",
|
||||||
"Batch Replace Selected Expense Categories": "Ausgewählte Ausgabenkategorien im Batch ersetzen",
|
"Batch Replace Selected Expense Categories": "Ausgewählte Ausgabenkategorien im Batch ersetzen",
|
||||||
|
|||||||
@@ -1668,6 +1668,7 @@
|
|||||||
"Time Format": "Time Format",
|
"Time Format": "Time Format",
|
||||||
"Transaction Type Mapping": "Transaction Type Mapping",
|
"Transaction Type Mapping": "Transaction Type Mapping",
|
||||||
"Timezone Format": "Timezone Format",
|
"Timezone Format": "Timezone Format",
|
||||||
|
"Amount Format": "Amount Format",
|
||||||
"Geographic Location Separator": "Geographic Location Separator",
|
"Geographic Location Separator": "Geographic Location Separator",
|
||||||
"Transaction Tags Separator": "Transaction Tags Separator",
|
"Transaction Tags Separator": "Transaction Tags Separator",
|
||||||
"Lines Per Page": "Lines Per Page",
|
"Lines Per Page": "Lines Per Page",
|
||||||
@@ -1675,6 +1676,7 @@
|
|||||||
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
||||||
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
||||||
"Transaction time format is not set": "Transaction time format is not set",
|
"Transaction time format is not set": "Transaction time format is not set",
|
||||||
|
"Transaction amount format is not set": "Transaction amount format is not set",
|
||||||
"Cannot import invalid transactions": "Cannot import invalid transactions",
|
"Cannot import invalid transactions": "Cannot import invalid transactions",
|
||||||
"Unable to parse import file": "Unable to parse import file",
|
"Unable to parse import file": "Unable to parse import file",
|
||||||
"Batch Replace Selected Expense Categories": "Batch Replace Selected Expense Categories",
|
"Batch Replace Selected Expense Categories": "Batch Replace Selected Expense Categories",
|
||||||
|
|||||||
@@ -1668,6 +1668,7 @@
|
|||||||
"Time Format": "Time Format",
|
"Time Format": "Time Format",
|
||||||
"Transaction Type Mapping": "Transaction Type Mapping",
|
"Transaction Type Mapping": "Transaction Type Mapping",
|
||||||
"Timezone Format": "Timezone Format",
|
"Timezone Format": "Timezone Format",
|
||||||
|
"Amount Format": "Amount Format",
|
||||||
"Geographic Location Separator": "Geographic Location Separator",
|
"Geographic Location Separator": "Geographic Location Separator",
|
||||||
"Transaction Tags Separator": "Transaction Tags Separator",
|
"Transaction Tags Separator": "Transaction Tags Separator",
|
||||||
"Lines Per Page": "Lines Per Page",
|
"Lines Per Page": "Lines Per Page",
|
||||||
@@ -1675,6 +1676,7 @@
|
|||||||
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
||||||
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
||||||
"Transaction time format is not set": "Transaction time format is not set",
|
"Transaction time format is not set": "Transaction time format is not set",
|
||||||
|
"Transaction amount format is not set": "Transaction amount format is not set",
|
||||||
"Cannot import invalid transactions": "No se pueden importar transacciones no válidas",
|
"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 parse import file": "No se puede analizar el archivo de importación",
|
||||||
"Batch Replace Selected Expense Categories": "Reemplazar por lotes categorías de gastos seleccionadas",
|
"Batch Replace Selected Expense Categories": "Reemplazar por lotes categorías de gastos seleccionadas",
|
||||||
|
|||||||
@@ -1668,6 +1668,7 @@
|
|||||||
"Time Format": "時刻形式",
|
"Time Format": "時刻形式",
|
||||||
"Transaction Type Mapping": "取引タイプのマッピング",
|
"Transaction Type Mapping": "取引タイプのマッピング",
|
||||||
"Timezone Format": "タイムゾーン形式",
|
"Timezone Format": "タイムゾーン形式",
|
||||||
|
"Amount Format": "Amount Format",
|
||||||
"Geographic Location Separator": "地理座標の区切り",
|
"Geographic Location Separator": "地理座標の区切り",
|
||||||
"Transaction Tags Separator": "取引タグの区切り",
|
"Transaction Tags Separator": "取引タグの区切り",
|
||||||
"Lines Per Page": "ページあたりの行数",
|
"Lines Per Page": "ページあたりの行数",
|
||||||
@@ -1675,6 +1676,7 @@
|
|||||||
"Missing transaction time, transaction type, or amount column mapping": "取引時間、取引タイプ、または金額の列マッピングがありません",
|
"Missing transaction time, transaction type, or amount column mapping": "取引時間、取引タイプ、または金額の列マッピングがありません",
|
||||||
"Transaction type mapping is not set": "取引タイプのマッピングが設定されていません",
|
"Transaction type mapping is not set": "取引タイプのマッピングが設定されていません",
|
||||||
"Transaction time format is not set": "取引時間の形式が設定されていません",
|
"Transaction time format is not set": "取引時間の形式が設定されていません",
|
||||||
|
"Transaction amount format is not set": "Transaction amount format is not set",
|
||||||
"Cannot import invalid transactions": "無効な取引をインポートできません",
|
"Cannot import invalid transactions": "無効な取引をインポートできません",
|
||||||
"Unable to parse import file": "インポートファイルを解析できません",
|
"Unable to parse import file": "インポートファイルを解析できません",
|
||||||
"Batch Replace Selected Expense Categories": "バッチは選択した支出カテゴリを置き換えます",
|
"Batch Replace Selected Expense Categories": "バッチは選択した支出カテゴリを置き換えます",
|
||||||
|
|||||||
@@ -1668,6 +1668,7 @@
|
|||||||
"Time Format": "Time Format",
|
"Time Format": "Time Format",
|
||||||
"Transaction Type Mapping": "Transaction Type Mapping",
|
"Transaction Type Mapping": "Transaction Type Mapping",
|
||||||
"Timezone Format": "Timezone Format",
|
"Timezone Format": "Timezone Format",
|
||||||
|
"Amount Format": "Amount Format",
|
||||||
"Geographic Location Separator": "Geographic Location Separator",
|
"Geographic Location Separator": "Geographic Location Separator",
|
||||||
"Transaction Tags Separator": "Transaction Tags Separator",
|
"Transaction Tags Separator": "Transaction Tags Separator",
|
||||||
"Lines Per Page": "Lines Per Page",
|
"Lines Per Page": "Lines Per Page",
|
||||||
@@ -1675,6 +1676,7 @@
|
|||||||
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
||||||
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
||||||
"Transaction time format is not set": "Transaction time format is not set",
|
"Transaction time format is not set": "Transaction time format is not set",
|
||||||
|
"Transaction amount format is not set": "Transaction amount format is not set",
|
||||||
"Cannot import invalid transactions": "Невозможно импортировать недействительные транзакции",
|
"Cannot import invalid transactions": "Невозможно импортировать недействительные транзакции",
|
||||||
"Unable to parse import file": "Не удалось обработать файл импорта",
|
"Unable to parse import file": "Не удалось обработать файл импорта",
|
||||||
"Batch Replace Selected Expense Categories": "Пакетная замена выбранных категорий расходов",
|
"Batch Replace Selected Expense Categories": "Пакетная замена выбранных категорий расходов",
|
||||||
|
|||||||
@@ -1668,6 +1668,7 @@
|
|||||||
"Time Format": "Time Format",
|
"Time Format": "Time Format",
|
||||||
"Transaction Type Mapping": "Transaction Type Mapping",
|
"Transaction Type Mapping": "Transaction Type Mapping",
|
||||||
"Timezone Format": "Timezone Format",
|
"Timezone Format": "Timezone Format",
|
||||||
|
"Amount Format": "Amount Format",
|
||||||
"Geographic Location Separator": "Geographic Location Separator",
|
"Geographic Location Separator": "Geographic Location Separator",
|
||||||
"Transaction Tags Separator": "Transaction Tags Separator",
|
"Transaction Tags Separator": "Transaction Tags Separator",
|
||||||
"Lines Per Page": "Lines Per Page",
|
"Lines Per Page": "Lines Per Page",
|
||||||
@@ -1675,6 +1676,7 @@
|
|||||||
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
"Missing transaction time, transaction type, or amount column mapping": "Missing transaction time, transaction type, or amount column mapping",
|
||||||
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
"Transaction type mapping is not set": "Transaction type mapping is not set",
|
||||||
"Transaction time format is not set": "Transaction time format is not set",
|
"Transaction time format is not set": "Transaction time format is not set",
|
||||||
|
"Transaction amount format is not set": "Transaction amount format is not set",
|
||||||
"Cannot import invalid transactions": "Không thể nhập giao dịch không hợp lệ",
|
"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 parse import file": "Không thể phân tích tệp nhập",
|
||||||
"Batch Replace Selected Expense Categories": "Thay thế hàng loạt các danh mục chi phí đã chọn",
|
"Batch Replace Selected Expense Categories": "Thay thế hàng loạt các danh mục chi phí đã chọn",
|
||||||
|
|||||||
@@ -1666,6 +1666,7 @@
|
|||||||
"Please select a file to import": "请选择要导入的文件",
|
"Please select a file to import": "请选择要导入的文件",
|
||||||
"Include Header Line": "包含标题行",
|
"Include Header Line": "包含标题行",
|
||||||
"Time Format": "时间格式",
|
"Time Format": "时间格式",
|
||||||
|
"Amount Format": "金额格式",
|
||||||
"Transaction Type Mapping": "交易类型映射",
|
"Transaction Type Mapping": "交易类型映射",
|
||||||
"Timezone Format": "时区格式",
|
"Timezone Format": "时区格式",
|
||||||
"Geographic Location Separator": "地理位置分隔符",
|
"Geographic Location Separator": "地理位置分隔符",
|
||||||
@@ -1675,6 +1676,7 @@
|
|||||||
"Missing transaction time, transaction type, or amount column mapping": "缺少交易时间、交易类型或金额列映射",
|
"Missing transaction time, transaction type, or amount column mapping": "缺少交易时间、交易类型或金额列映射",
|
||||||
"Transaction type mapping is not set": "交易类型映射没有设置",
|
"Transaction type mapping is not set": "交易类型映射没有设置",
|
||||||
"Transaction time format is not set": "交易时间格式没有设置",
|
"Transaction time format is not set": "交易时间格式没有设置",
|
||||||
|
"Transaction amount format is not set": "交易金额格式没有设置",
|
||||||
"Cannot import invalid transactions": "不能导入无效的交易",
|
"Cannot import invalid transactions": "不能导入无效的交易",
|
||||||
"Unable to parse import file": "无法解析导入的文件",
|
"Unable to parse import file": "无法解析导入的文件",
|
||||||
"Batch Replace Selected Expense Categories": "批量替换选中的支出分类",
|
"Batch Replace Selected Expense Categories": "批量替换选中的支出分类",
|
||||||
|
|||||||
@@ -1081,9 +1081,9 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseImportTransaction({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, geoSeparator, tagSeparator }: { fileType: string, fileEncoding?: string, importFile: File, columnMapping?: Record<number, number>, transactionTypeMapping?: Record<string, TransactionType>, hasHeaderLine?: boolean, timeFormat?: string, timezoneFormat?: string, geoSeparator?: string, tagSeparator?: string }): Promise<ImportTransactionResponsePageWrapper> {
|
function parseImportTransaction({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoSeparator, tagSeparator }: { fileType: string, fileEncoding?: string, importFile: File, columnMapping?: Record<number, number>, transactionTypeMapping?: Record<string, TransactionType>, hasHeaderLine?: boolean, timeFormat?: string, timezoneFormat?: string, amountDecimalSeparator?: string, amountDigitGroupingSymbol?: string, geoSeparator?: string, tagSeparator?: string }): Promise<ImportTransactionResponsePageWrapper> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
services.parseImportTransaction({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, geoSeparator, tagSeparator }).then(response => {
|
services.parseImportTransaction({ fileType, fileEncoding, importFile, columnMapping, transactionTypeMapping, hasHeaderLine, timeFormat, timezoneFormat, amountDecimalSeparator, amountDigitGroupingSymbol, geoSeparator, tagSeparator }).then(response => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
|
||||||
if (!data || !data.success || !data.result) {
|
if (!data || !data.success || !data.result) {
|
||||||
|
|||||||
@@ -377,6 +377,32 @@
|
|||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
||||||
|
:disabled="!parsedFileDataColumnMapping || !isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.Amount.type])">
|
||||||
|
<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>
|
||||||
|
<v-menu eager activator="parent" location="bottom" max-height="500">
|
||||||
|
<v-list>
|
||||||
|
<v-list-item key="auto"
|
||||||
|
:append-icon="parsedFileAmountFormat === '' ? mdiCheck : undefined"
|
||||||
|
@click="parsedFileAmountFormat = ''">
|
||||||
|
<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>
|
||||||
|
<span class="ml-1" v-if="!parsedFileAutoDetectedAmountFormat || !KnownAmountFormat.valueOf(parsedFileAutoDetectedAmountFormat || '')">({{ tt('Unknown') }})</span>
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item :key="amountFormat.type"
|
||||||
|
:append-icon="parsedFileAmountFormat === amountFormat.type ? mdiCheck : undefined"
|
||||||
|
v-for="amountFormat in KnownAmountFormat.values()"
|
||||||
|
@click="parsedFileAmountFormat = amountFormat.type">
|
||||||
|
<v-list-item-title class="cursor-pointer">
|
||||||
|
{{ amountFormat.format }}
|
||||||
|
</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-btn>
|
||||||
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
<v-btn class="ml-2" color="secondary" density="compact" variant="outlined"
|
||||||
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.GeographicLocation.type])">
|
v-if="parsedFileDataColumnMapping && isNumber(parsedFileDataColumnMapping[ImportTransactionColumnType.GeographicLocation.type])">
|
||||||
<span>{{ tt('Geographic Location Separator') }}</span>
|
<span>{{ tt('Geographic Location Separator') }}</span>
|
||||||
@@ -812,6 +838,7 @@ import { useOverviewStore } from '@/stores/overview.ts';
|
|||||||
import { useStatisticsStore } from '@/stores/statistics.ts';
|
import { useStatisticsStore } from '@/stores/statistics.ts';
|
||||||
|
|
||||||
import type { NameValue, TypeAndDisplayName } from '@/core/base.ts';
|
import type { NameValue, TypeAndDisplayName } from '@/core/base.ts';
|
||||||
|
import { KnownAmountFormat } from '@/core/numeral.ts';
|
||||||
import { KnownDateTimeFormat } from '@/core/datetime.ts';
|
import { KnownDateTimeFormat } from '@/core/datetime.ts';
|
||||||
import { KnownDateTimezoneFormat } from '@/core/timezone.ts';
|
import { KnownDateTimezoneFormat } from '@/core/timezone.ts';
|
||||||
import { CategoryType } from '@/core/category.ts';
|
import { CategoryType } from '@/core/category.ts';
|
||||||
@@ -930,6 +957,7 @@ const parsedFileDataColumnMapping = ref<Record<number, number>>({});
|
|||||||
const parsedFileTransactionTypeMapping = ref<Record<string, TransactionType>>({});
|
const parsedFileTransactionTypeMapping = ref<Record<string, TransactionType>>({});
|
||||||
const parsedFileTimeFormat = ref<string>('');
|
const parsedFileTimeFormat = ref<string>('');
|
||||||
const parsedFileTimezoneFormat = ref<string>('');
|
const parsedFileTimezoneFormat = ref<string>('');
|
||||||
|
const parsedFileAmountFormat = ref<string>('');
|
||||||
const parsedFileGeoLocationSeparator = ref<string>(' ');
|
const parsedFileGeoLocationSeparator = ref<string>(' ');
|
||||||
const parsedFileTagSeparator = ref<string>(';');
|
const parsedFileTagSeparator = ref<string>(';');
|
||||||
const importTransactions = ref<ImportTransaction[] | undefined>(undefined);
|
const importTransactions = ref<ImportTransaction[] | undefined>(undefined);
|
||||||
@@ -1288,6 +1316,37 @@ const parsedFileAutoDetectedTimezoneFormat = computed<string | undefined>(() =>
|
|||||||
return detectedFormats[0].value;
|
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 importTransactionsTableHeight = computed<number | undefined>(() => {
|
const importTransactionsTableHeight = computed<number | undefined>(() => {
|
||||||
if (countPerPage.value <= 10 || !importTransactions.value || importTransactions.value.length <= 10) {
|
if (countPerPage.value <= 10 || !importTransactions.value || importTransactions.value.length <= 10) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -1853,6 +1912,7 @@ function open(): Promise<void> {
|
|||||||
parsedFileTransactionTypeMapping.value = {};
|
parsedFileTransactionTypeMapping.value = {};
|
||||||
parsedFileTimeFormat.value = '';
|
parsedFileTimeFormat.value = '';
|
||||||
parsedFileTimezoneFormat.value = '';
|
parsedFileTimezoneFormat.value = '';
|
||||||
|
parsedFileAmountFormat.value = '';
|
||||||
parsedFileGeoLocationSeparator.value = ' ';
|
parsedFileGeoLocationSeparator.value = ' ';
|
||||||
parsedFileTagSeparator.value = ';';
|
parsedFileTagSeparator.value = ';';
|
||||||
importTransactions.value = undefined;
|
importTransactions.value = undefined;
|
||||||
@@ -1995,6 +2055,9 @@ function parseData(): void {
|
|||||||
let hasHeaderLine: boolean | undefined = undefined;
|
let hasHeaderLine: boolean | undefined = undefined;
|
||||||
let timeFormat: string | undefined = undefined;
|
let timeFormat: string | undefined = undefined;
|
||||||
let timezoneFormat: string | undefined = undefined;
|
let timezoneFormat: string | undefined = undefined;
|
||||||
|
let amountFormat: string | undefined = undefined;
|
||||||
|
let amountDecimalSeparator: string | undefined = undefined;
|
||||||
|
let amountDigitGroupingSymbol: string | undefined = undefined;
|
||||||
let geoLocationSeparator: string | undefined = undefined;
|
let geoLocationSeparator: string | undefined = undefined;
|
||||||
let tagSeparator: string | undefined = undefined;
|
let tagSeparator: string | undefined = undefined;
|
||||||
|
|
||||||
@@ -2004,6 +2067,7 @@ function parseData(): void {
|
|||||||
hasHeaderLine = parsedFileIncludeHeader.value;
|
hasHeaderLine = parsedFileIncludeHeader.value;
|
||||||
timeFormat = parsedFileTimeFormat.value;
|
timeFormat = parsedFileTimeFormat.value;
|
||||||
timezoneFormat = parsedFileTimezoneFormat.value;
|
timezoneFormat = parsedFileTimezoneFormat.value;
|
||||||
|
amountFormat = parsedFileAmountFormat.value;
|
||||||
geoLocationSeparator = parsedFileGeoLocationSeparator.value;
|
geoLocationSeparator = parsedFileGeoLocationSeparator.value;
|
||||||
tagSeparator = parsedFileTagSeparator.value;
|
tagSeparator = parsedFileTagSeparator.value;
|
||||||
|
|
||||||
@@ -2028,10 +2092,28 @@ function parseData(): void {
|
|||||||
timezoneFormat = parsedFileAutoDetectedTimezoneFormat.value;
|
timezoneFormat = parsedFileAutoDetectedTimezoneFormat.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parsedFileAmountFormat.value) {
|
||||||
|
amountFormat = parsedFileAutoDetectedAmountFormat.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountFormat) {
|
||||||
|
const knownAmountFormat = KnownAmountFormat.valueOf(amountFormat);
|
||||||
|
|
||||||
|
if (knownAmountFormat) {
|
||||||
|
amountDecimalSeparator = knownAmountFormat.decimalSeparator.symbol;
|
||||||
|
amountDigitGroupingSymbol = knownAmountFormat.digitGroupingSymbol?.symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!timeFormat) {
|
if (!timeFormat) {
|
||||||
snackbar.value?.showError('Transaction time format is not set');
|
snackbar.value?.showError('Transaction time format is not set');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!amountDecimalSeparator) {
|
||||||
|
snackbar.value?.showError('Transaction amount format is not set');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
@@ -2045,6 +2127,8 @@ function parseData(): void {
|
|||||||
hasHeaderLine: hasHeaderLine,
|
hasHeaderLine: hasHeaderLine,
|
||||||
timeFormat: timeFormat,
|
timeFormat: timeFormat,
|
||||||
timezoneFormat: timezoneFormat,
|
timezoneFormat: timezoneFormat,
|
||||||
|
amountDecimalSeparator: amountDecimalSeparator,
|
||||||
|
amountDigitGroupingSymbol: amountDigitGroupingSymbol,
|
||||||
geoSeparator: geoLocationSeparator,
|
geoSeparator: geoLocationSeparator,
|
||||||
tagSeparator: tagSeparator
|
tagSeparator: tagSeparator
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
|
|||||||
Reference in New Issue
Block a user