diff --git a/pkg/converters/datatable/writable_transaction_data_table.go b/pkg/converters/datatable/writable_transaction_data_table.go index b88dae7c..8875ccb4 100644 --- a/pkg/converters/datatable/writable_transaction_data_table.go +++ b/pkg/converters/datatable/writable_transaction_data_table.go @@ -43,17 +43,28 @@ func (t *WritableTransactionDataTable) Add(data map[TransactionDataTableColumn]s } // Get returns the record in the specified index -func (t *WritableTransactionDataTable) Get(index int) *WritableTransactionDataRow { +func (t *WritableTransactionDataTable) Get(index int) (*WritableTransactionDataRow, error) { if index >= len(t.allData) { - return nil + return nil, nil } rowData := t.allData[index] + rowDataValid := true + + if t.rowParser != nil { + var err error + rowData, rowDataValid, err = t.rowParser.Parse(rowData) + + if err != nil { + return nil, err + } + } return &WritableTransactionDataRow{ - dataTable: t, - rowData: rowData, - } + dataTable: t, + rowData: rowData, + rowDataValid: rowDataValid, + }, nil } // HasColumn returns whether the data table has specified column @@ -90,7 +101,19 @@ func (t *WritableTransactionDataTable) TransactionRowIterator() TransactionDataR // ColumnCount returns the total count of column in this data row func (r *WritableTransactionDataRow) ColumnCount() int { - return len(r.rowData) + if !r.rowDataValid { + return 0 + } + + columnCount := 0 + + for column := range r.rowData { + if r.dataTable.supportedColumns[column] || r.dataTable.addedColumns[column] { + columnCount++ + } + } + + return columnCount } // IsValid returns whether this row is valid data for importing @@ -100,7 +123,25 @@ func (r *WritableTransactionDataRow) IsValid() bool { // GetData returns the data in the specified column type func (r *WritableTransactionDataRow) GetData(column TransactionDataTableColumn) string { - return r.rowData[column] + if !r.rowDataValid { + return "" + } + + _, exists := r.dataTable.supportedColumns[column] + + if exists { + return r.rowData[column] + } + + if r.dataTable.addedColumns != nil { + _, exists = r.dataTable.addedColumns[column] + + if exists { + return r.rowData[column] + } + } + + return "" } // HasNext returns whether the iterator does not reach the end diff --git a/pkg/converters/datatable/writable_transaction_data_table_test.go b/pkg/converters/datatable/writable_transaction_data_table_test.go index 19873353..985e746a 100644 --- a/pkg/converters/datatable/writable_transaction_data_table_test.go +++ b/pkg/converters/datatable/writable_transaction_data_table_test.go @@ -1,16 +1,67 @@ package datatable import ( - "github.com/mayswind/ezbookkeeping/pkg/core" - "github.com/mayswind/ezbookkeeping/pkg/models" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/utils" ) +// testDataRowParser defines the structure of test transaction data row parser +type testDataRowParser struct { +} + +// GetAddedColumns returns the added columns after converting the data row +func (p *testDataRowParser) GetAddedColumns() []TransactionDataTableColumn { + return []TransactionDataTableColumn{ + TRANSACTION_DATA_TABLE_DESCRIPTION, + } +} + +// Parse returns the converted transaction data row +func (p *testDataRowParser) Parse(data map[TransactionDataTableColumn]string) (rowData map[TransactionDataTableColumn]string, rowDataValid bool, err error) { + rowData = make(map[TransactionDataTableColumn]string, len(data)) + + for column, value := range data { + rowData[column] = value + } + + if _, exists := rowData[TRANSACTION_DATA_TABLE_SUB_CATEGORY]; exists { + rowData[TRANSACTION_DATA_TABLE_SUB_CATEGORY] = "foo" + } else { + return nil, false, nil + } + + rowData[TRANSACTION_DATA_TABLE_TAGS] = "test" + rowData[TRANSACTION_DATA_TABLE_DESCRIPTION] = "bar" + + return rowData, true, nil +} + +func TestWritableDataTableCreate(t *testing.T) { + columns := make([]TransactionDataTableColumn, 5) + columns[0] = TRANSACTION_DATA_TABLE_TRANSACTION_TIME + columns[1] = TRANSACTION_DATA_TABLE_TRANSACTION_TYPE + columns[2] = TRANSACTION_DATA_TABLE_SUB_CATEGORY + columns[3] = TRANSACTION_DATA_TABLE_ACCOUNT_NAME + columns[4] = TRANSACTION_DATA_TABLE_AMOUNT + + writableDataTable := CreateNewWritableTransactionDataTable(columns) + + assert.Equal(t, 0, writableDataTable.TransactionRowCount()) + assert.True(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TIME)) + assert.True(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE)) + assert.True(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_SUB_CATEGORY)) + assert.True(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_ACCOUNT_NAME)) + assert.True(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_AMOUNT)) + assert.False(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE)) + assert.False(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY)) +} + func TestWritableDataTableAdd(t *testing.T) { columns := make([]TransactionDataTableColumn, 5) columns[0] = TRANSACTION_DATA_TABLE_TRANSACTION_TIME @@ -38,7 +89,10 @@ func TestWritableDataTableAdd(t *testing.T) { }) assert.Equal(t, 1, writableDataTable.TransactionRowCount()) - dataRow := writableDataTable.Get(0) + dataRow, err := writableDataTable.Get(0) + assert.Nil(t, err) + + assert.True(t, dataRow.IsValid()) actualTransactionTime := dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME) assert.Equal(t, expectedTransactionTime, actualTransactionTime) @@ -72,7 +126,8 @@ func TestWritableDataTableAdd_NotExistsColumn(t *testing.T) { }) assert.Equal(t, 1, writableDataTable.TransactionRowCount()) - dataRow := writableDataTable.Get(0) + dataRow, err := writableDataTable.Get(0) + assert.Nil(t, err) assert.Equal(t, 1, dataRow.ColumnCount()) } @@ -83,7 +138,8 @@ func TestWritableDataTableGet_NotExistsRow(t *testing.T) { writableDataTable := CreateNewWritableTransactionDataTable(columns) assert.Equal(t, 0, writableDataTable.TransactionRowCount()) - dataRow := writableDataTable.Get(0) + dataRow, err := writableDataTable.Get(0) + assert.Nil(t, err) assert.Nil(t, dataRow) } @@ -101,7 +157,8 @@ func TestWritableDataRowGetData_NotExistsColumn(t *testing.T) { }) assert.Equal(t, 1, writableDataTable.TransactionRowCount()) - dataRow := writableDataTable.Get(0) + dataRow, err := writableDataTable.Get(0) + assert.Nil(t, err) assert.Equal(t, 1, dataRow.ColumnCount()) assert.Equal(t, "", dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE)) } @@ -194,3 +251,130 @@ func TestWritableDataTableDataRowIterator(t *testing.T) { assert.Equal(t, 3, index) } + +func TestWritableDataTableWithRowParser(t *testing.T) { + columns := make([]TransactionDataTableColumn, 5) + columns[0] = TRANSACTION_DATA_TABLE_TRANSACTION_TIME + columns[1] = TRANSACTION_DATA_TABLE_TRANSACTION_TYPE + columns[2] = TRANSACTION_DATA_TABLE_SUB_CATEGORY + columns[3] = TRANSACTION_DATA_TABLE_ACCOUNT_NAME + columns[4] = TRANSACTION_DATA_TABLE_AMOUNT + + writableDataTable := CreateNewWritableTransactionDataTableWithRowParser(columns, &testDataRowParser{}) + + assert.True(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_DESCRIPTION)) + assert.False(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_TAGS)) + assert.Equal(t, 0, writableDataTable.TransactionRowCount()) + + writableDataTable.Add(map[TransactionDataTableColumn]string{ + TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "2024-09-01 01:23:45", + TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "Expense", + TRANSACTION_DATA_TABLE_SUB_CATEGORY: "Test Category", + TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "Test Account", + TRANSACTION_DATA_TABLE_AMOUNT: "123.45", + }) + assert.Equal(t, 1, writableDataTable.TransactionRowCount()) + + // first row + dataRow, err := writableDataTable.Get(0) + assert.Nil(t, err) + assert.True(t, dataRow.IsValid()) + assert.Equal(t, 6, dataRow.ColumnCount()) + + actualSubCategory := dataRow.GetData(TRANSACTION_DATA_TABLE_SUB_CATEGORY) + assert.Equal(t, "foo", actualSubCategory) + + actualTags := dataRow.GetData(TRANSACTION_DATA_TABLE_TAGS) + assert.Equal(t, "", actualTags) + + actualDescription := dataRow.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION) + assert.Equal(t, "bar", actualDescription) + + writableDataTable.Add(map[TransactionDataTableColumn]string{ + TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "2024-09-01 12:34:56", + TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "Income", + TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "Test Account2", + TRANSACTION_DATA_TABLE_AMOUNT: "0.12", + }) + assert.Equal(t, 2, writableDataTable.TransactionRowCount()) + + // second row + dataRow, err = writableDataTable.Get(1) + assert.Nil(t, err) + assert.False(t, dataRow.IsValid()) + assert.Equal(t, 0, dataRow.ColumnCount()) + + actualSubCategory = dataRow.GetData(TRANSACTION_DATA_TABLE_SUB_CATEGORY) + assert.Equal(t, "", actualSubCategory) + + actualTags = dataRow.GetData(TRANSACTION_DATA_TABLE_TAGS) + assert.Equal(t, "", actualTags) + + actualDescription = dataRow.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION) + assert.Equal(t, "", actualDescription) +} + +func TestWritableDataTableDataRowIteratorWithRowParser(t *testing.T) { + columns := make([]TransactionDataTableColumn, 5) + columns[0] = TRANSACTION_DATA_TABLE_TRANSACTION_TIME + columns[1] = TRANSACTION_DATA_TABLE_TRANSACTION_TYPE + columns[2] = TRANSACTION_DATA_TABLE_SUB_CATEGORY + columns[3] = TRANSACTION_DATA_TABLE_ACCOUNT_NAME + columns[4] = TRANSACTION_DATA_TABLE_AMOUNT + + writableDataTable := CreateNewWritableTransactionDataTableWithRowParser(columns, &testDataRowParser{}) + + assert.True(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_DESCRIPTION)) + assert.False(t, writableDataTable.HasColumn(TRANSACTION_DATA_TABLE_TAGS)) + assert.Equal(t, 0, writableDataTable.TransactionRowCount()) + + writableDataTable.Add(map[TransactionDataTableColumn]string{ + TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "2024-09-01 01:23:45", + TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "Expense", + TRANSACTION_DATA_TABLE_SUB_CATEGORY: "Test Category", + TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "Test Account", + TRANSACTION_DATA_TABLE_AMOUNT: "123.45", + }) + + writableDataTable.Add(map[TransactionDataTableColumn]string{ + TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "2024-09-01 12:34:56", + TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "Income", + TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "Test Account2", + TRANSACTION_DATA_TABLE_AMOUNT: "0.12", + }) + + iterator := writableDataTable.TransactionRowIterator() + assert.True(t, iterator.HasNext()) + + // first row + dataRow, err := iterator.Next(core.NewNullContext(), &models.User{}) + assert.Nil(t, err) + assert.True(t, dataRow.IsValid()) + + actualSubCategory := dataRow.GetData(TRANSACTION_DATA_TABLE_SUB_CATEGORY) + assert.Equal(t, "foo", actualSubCategory) + + actualTags := dataRow.GetData(TRANSACTION_DATA_TABLE_TAGS) + assert.Equal(t, "", actualTags) + + actualDescription := dataRow.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION) + assert.Equal(t, "bar", actualDescription) + + assert.True(t, iterator.HasNext()) + + // second row + dataRow, err = iterator.Next(core.NewNullContext(), &models.User{}) + assert.Nil(t, err) + assert.False(t, dataRow.IsValid()) + + actualSubCategory = dataRow.GetData(TRANSACTION_DATA_TABLE_SUB_CATEGORY) + assert.Equal(t, "", actualSubCategory) + + actualTags = dataRow.GetData(TRANSACTION_DATA_TABLE_TAGS) + assert.Equal(t, "", actualTags) + + actualDescription = dataRow.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION) + assert.Equal(t, "", actualDescription) + + assert.False(t, iterator.HasNext()) +}