mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-16 16:07:33 +08:00
code refactor
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package _default
|
||||
|
||||
// ezBookKeepingTransactionDataCSVFileConverter defines the structure of CSV file converter
|
||||
type ezBookKeepingTransactionDataCSVFileConverter struct {
|
||||
ezBookKeepingTransactionDataPlainTextConverter
|
||||
}
|
||||
|
||||
// Initialize an ezbookkeeping transaction data csv file converter singleton instance
|
||||
var (
|
||||
EzBookKeepingTransactionDataCSVFileConverter = &ezBookKeepingTransactionDataCSVFileConverter{
|
||||
ezBookKeepingTransactionDataPlainTextConverter{
|
||||
columnSeparator: ",",
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,105 @@
|
||||
package _default
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
// ezBookKeepingTransactionDataPlainTextConverter defines the structure of ezbookkeeping plain text converter for transaction data
|
||||
type ezBookKeepingTransactionDataPlainTextConverter struct {
|
||||
columnSeparator string
|
||||
}
|
||||
|
||||
const ezbookkeepingLineSeparator = "\n"
|
||||
const ezbookkeepingGeoLocationSeparator = " "
|
||||
const ezbookkeepingTagSeparator = ";"
|
||||
|
||||
var ezbookkeepingDataColumnNameMapping = map[datatable.DataTableColumn]string{
|
||||
datatable.DATA_TABLE_TRANSACTION_TIME: "Time",
|
||||
datatable.DATA_TABLE_TRANSACTION_TIMEZONE: "Timezone",
|
||||
datatable.DATA_TABLE_TRANSACTION_TYPE: "Type",
|
||||
datatable.DATA_TABLE_CATEGORY: "Category",
|
||||
datatable.DATA_TABLE_SUB_CATEGORY: "Sub Category",
|
||||
datatable.DATA_TABLE_ACCOUNT_NAME: "Account",
|
||||
datatable.DATA_TABLE_ACCOUNT_CURRENCY: "Account Currency",
|
||||
datatable.DATA_TABLE_AMOUNT: "Amount",
|
||||
datatable.DATA_TABLE_RELATED_ACCOUNT_NAME: "Account2",
|
||||
datatable.DATA_TABLE_RELATED_ACCOUNT_CURRENCY: "Account2 Currency",
|
||||
datatable.DATA_TABLE_RELATED_AMOUNT: "Account2 Amount",
|
||||
datatable.DATA_TABLE_GEOGRAPHIC_LOCATION: "Geographic Location",
|
||||
datatable.DATA_TABLE_TAGS: "Tags",
|
||||
datatable.DATA_TABLE_DESCRIPTION: "Description",
|
||||
}
|
||||
|
||||
var ezbookkeepingTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
models.TRANSACTION_TYPE_MODIFY_BALANCE: "Balance Modification",
|
||||
models.TRANSACTION_TYPE_INCOME: "Income",
|
||||
models.TRANSACTION_TYPE_EXPENSE: "Expense",
|
||||
models.TRANSACTION_TYPE_TRANSFER: "Transfer",
|
||||
}
|
||||
|
||||
var ezbookkeepingDataColumns = []datatable.DataTableColumn{
|
||||
datatable.DATA_TABLE_TRANSACTION_TIME,
|
||||
datatable.DATA_TABLE_TRANSACTION_TIMEZONE,
|
||||
datatable.DATA_TABLE_TRANSACTION_TYPE,
|
||||
datatable.DATA_TABLE_CATEGORY,
|
||||
datatable.DATA_TABLE_SUB_CATEGORY,
|
||||
datatable.DATA_TABLE_ACCOUNT_NAME,
|
||||
datatable.DATA_TABLE_ACCOUNT_CURRENCY,
|
||||
datatable.DATA_TABLE_AMOUNT,
|
||||
datatable.DATA_TABLE_RELATED_ACCOUNT_NAME,
|
||||
datatable.DATA_TABLE_RELATED_ACCOUNT_CURRENCY,
|
||||
datatable.DATA_TABLE_RELATED_AMOUNT,
|
||||
datatable.DATA_TABLE_GEOGRAPHIC_LOCATION,
|
||||
datatable.DATA_TABLE_TAGS,
|
||||
datatable.DATA_TABLE_DESCRIPTION,
|
||||
}
|
||||
|
||||
// ToExportedContent returns the exported transaction plain text data
|
||||
func (c *ezBookKeepingTransactionDataPlainTextConverter) ToExportedContent(ctx core.Context, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
||||
dataTableBuilder := createNewezbookkeepingTransactionPlainTextDataTableBuilder(
|
||||
len(transactions),
|
||||
ezbookkeepingDataColumns,
|
||||
ezbookkeepingDataColumnNameMapping,
|
||||
c.columnSeparator,
|
||||
ezbookkeepingLineSeparator,
|
||||
)
|
||||
|
||||
dataTableExporter := datatable.CreateNewDataTableTransactionDataExporter(
|
||||
ezbookkeepingDataColumnNameMapping,
|
||||
ezbookkeepingTransactionTypeNameMapping,
|
||||
ezbookkeepingGeoLocationSeparator,
|
||||
ezbookkeepingTagSeparator,
|
||||
)
|
||||
|
||||
err := dataTableExporter.BuildExportedContent(ctx, dataTableBuilder, uid, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(dataTableBuilder.String()), nil
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the transaction plain text data
|
||||
func (c *ezBookKeepingTransactionDataPlainTextConverter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
dataTable, err := createNewezbookkeepingTransactionPlainTextDataTable(
|
||||
string(data),
|
||||
c.columnSeparator,
|
||||
ezbookkeepingLineSeparator,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTableImporter := datatable.CreateNewDataTableTransactionDataImporter(
|
||||
ezbookkeepingDataColumnNameMapping,
|
||||
ezbookkeepingTransactionTypeNameMapping,
|
||||
ezbookkeepingGeoLocationSeparator,
|
||||
ezbookkeepingTagSeparator,
|
||||
)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, dataTable, defaultTimezoneOffset, accountMap, categoryMap, tagMap)
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
package _default
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterToExportedContent(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
transactions := make([]*models.Transaction, 3)
|
||||
transactions[0] = &models.Transaction{
|
||||
TransactionId: 1,
|
||||
TransactionTime: 1725165296000,
|
||||
Type: models.TRANSACTION_DB_TYPE_INCOME,
|
||||
TimezoneUtcOffset: 480,
|
||||
CategoryId: 2,
|
||||
AccountId: 1,
|
||||
Amount: 12345,
|
||||
GeoLongitude: 123.45,
|
||||
GeoLatitude: 45.67,
|
||||
Comment: "Hello,World",
|
||||
}
|
||||
transactions[1] = &models.Transaction{
|
||||
TransactionId: 2,
|
||||
TransactionTime: 1725194096000,
|
||||
Type: models.TRANSACTION_DB_TYPE_EXPENSE,
|
||||
TimezoneUtcOffset: 0,
|
||||
CategoryId: 4,
|
||||
AccountId: 1,
|
||||
Amount: -10,
|
||||
GeoLongitude: 0,
|
||||
GeoLatitude: 0,
|
||||
Comment: "Foo#Bar",
|
||||
}
|
||||
transactions[2] = &models.Transaction{
|
||||
TransactionId: 3,
|
||||
TransactionTime: 1725212096000,
|
||||
Type: models.TRANSACTION_DB_TYPE_TRANSFER_OUT,
|
||||
TimezoneUtcOffset: -300,
|
||||
CategoryId: 6,
|
||||
AccountId: 1,
|
||||
Amount: 12345,
|
||||
RelatedAccountId: 2,
|
||||
RelatedAccountAmount: 1735,
|
||||
Comment: "T\te\rs\nt\r\ntest",
|
||||
}
|
||||
|
||||
accountMap := make(map[int64]*models.Account, 2)
|
||||
accountMap[1] = &models.Account{
|
||||
AccountId: 1,
|
||||
Name: "Test Account",
|
||||
Currency: "CNY",
|
||||
}
|
||||
accountMap[2] = &models.Account{
|
||||
AccountId: 2,
|
||||
Name: "Test Account2",
|
||||
Currency: "USD",
|
||||
}
|
||||
|
||||
categoryMap := make(map[int64]*models.TransactionCategory, 6)
|
||||
categoryMap[1] = &models.TransactionCategory{
|
||||
CategoryId: 1,
|
||||
Type: models.CATEGORY_TYPE_INCOME,
|
||||
Name: "Test Category",
|
||||
}
|
||||
categoryMap[2] = &models.TransactionCategory{
|
||||
CategoryId: 2,
|
||||
Type: models.CATEGORY_TYPE_INCOME,
|
||||
ParentCategoryId: 1,
|
||||
Name: "Test Sub Category",
|
||||
}
|
||||
categoryMap[3] = &models.TransactionCategory{
|
||||
CategoryId: 3,
|
||||
Type: models.CATEGORY_TYPE_EXPENSE,
|
||||
Name: "Test Category2",
|
||||
}
|
||||
categoryMap[4] = &models.TransactionCategory{
|
||||
CategoryId: 4,
|
||||
Type: models.CATEGORY_TYPE_EXPENSE,
|
||||
ParentCategoryId: 3,
|
||||
Name: "Test Sub Category2",
|
||||
}
|
||||
categoryMap[5] = &models.TransactionCategory{
|
||||
CategoryId: 5,
|
||||
Type: models.CATEGORY_TYPE_TRANSFER,
|
||||
Name: "Test Category3",
|
||||
}
|
||||
categoryMap[6] = &models.TransactionCategory{
|
||||
CategoryId: 6,
|
||||
Type: models.CATEGORY_TYPE_TRANSFER,
|
||||
ParentCategoryId: 5,
|
||||
Name: "Test Sub Category3",
|
||||
}
|
||||
|
||||
tagMap := make(map[int64]*models.TransactionTag, 2)
|
||||
tagMap[1] = &models.TransactionTag{
|
||||
TagId: 1,
|
||||
Name: "Test,Tag",
|
||||
}
|
||||
tagMap[2] = &models.TransactionTag{
|
||||
TagId: 2,
|
||||
Name: "Test;Tag2",
|
||||
}
|
||||
|
||||
allTagIndexes := make(map[int64][]int64, 2)
|
||||
allTagIndexes[1] = []int64{1, 2}
|
||||
allTagIndexes[2] = []int64{3, 1, 4}
|
||||
allTagIndexes[3] = []int64{2, 3}
|
||||
|
||||
expectedContent := "Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n" +
|
||||
"2024-09-01 12:34:56,+08:00,Income,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,123.450000 45.670000,Test Tag;Test Tag2,Hello World\n" +
|
||||
"2024-09-01 12:34:56,+00:00,Expense,Test Category2,Test Sub Category2,Test Account,CNY,-0.10,,,,,Test Tag,Foo#Bar\n" +
|
||||
"2024-09-01 12:34:56,-05:00,Transfer,Test Category3,Test Sub Category3,Test Account,CNY,123.45,Test Account2,USD,17.35,,Test Tag2,T\te s t test\n"
|
||||
actualContent, err := converter.ToExportedContent(context, 123, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expectedContent, string(actualContent))
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, allNewSubCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,\n"+
|
||||
"2024-09-01 01:23:45,Income,Test Category,Test Account,0.12,,\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category2,Test Account,1.00,,\n"+
|
||||
"2024-09-01 23:59:59,Transfer,Test Category3,Test Account,0.05,Test Account2,0.05"), 0, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 4, len(allNewTransactions))
|
||||
assert.Equal(t, 2, len(allNewAccounts))
|
||||
assert.Equal(t, 3, len(allNewSubCategories))
|
||||
assert.Equal(t, 0, len(allNewTags))
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_MODIFY_BALANCE, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1725148800), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "", allNewTransactions[0].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[1].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[1].Type)
|
||||
assert.Equal(t, int64(1725153825), utils.GetUnixTimeFromTransactionTime(allNewTransactions[1].TransactionTime))
|
||||
assert.Equal(t, int64(12), allNewTransactions[1].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[1].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category", allNewTransactions[1].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[2].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[2].Type)
|
||||
assert.Equal(t, int64(1725194096), utils.GetUnixTimeFromTransactionTime(allNewTransactions[2].TransactionTime))
|
||||
assert.Equal(t, int64(100), allNewTransactions[2].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[2].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category2", allNewTransactions[2].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[3].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[3].Type)
|
||||
assert.Equal(t, int64(1725235199), utils.GetUnixTimeFromTransactionTime(allNewTransactions[3].TransactionTime))
|
||||
assert.Equal(t, int64(5), allNewTransactions[3].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[3].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[3].OriginalDestinationAccountName)
|
||||
assert.Equal(t, "Test Category3", allNewTransactions[3].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid)
|
||||
assert.Equal(t, "Test Account", allNewAccounts[0].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[0].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[1].Uid)
|
||||
assert.Equal(t, "Test Account2", allNewAccounts[1].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[1].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubCategories[0].Uid)
|
||||
assert.Equal(t, "Test Category", allNewSubCategories[0].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubCategories[1].Uid)
|
||||
assert.Equal(t, "Test Category2", allNewSubCategories[1].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubCategories[2].Uid)
|
||||
assert.Equal(t, "Test Category3", allNewSubCategories[2].Name)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01T12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"09/01/2024 12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Type,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,+08:00,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, int64(1725165296), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidTimezone(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Asia/Shanghai,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAccountName(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,,123.45,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,,123.45"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category2,Test Account,USD,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
assert.Equal(t, 2, len(allNewAccounts))
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid)
|
||||
assert.Equal(t, "Test Account", allNewAccounts[0].Name)
|
||||
assert.Equal(t, "USD", allNewAccounts[0].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[1].Uid)
|
||||
assert.Equal(t, "Test Account2", allNewAccounts[1].Name)
|
||||
assert.Equal(t, "EUR", allNewAccounts[1].Currency)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account,CNY,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account2,CNY,1.23,Test Account,EUR,1.10"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,XXX,123.45,,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||
"2024-09-01 01:23:45,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123 45,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2,123 45"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseNoAmount2(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,"), 0, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, int64(0), allNewTransactions[0].RelatedAccountAmount)
|
||||
|
||||
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2\n"+
|
||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2"), 0, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, int64(12345), allNewTransactions[0].RelatedAccountAmount)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidGeographicLocation(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,123.45 45.56"), 0, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, 123.45, allNewTransactions[0].GeoLongitude)
|
||||
assert.Equal(t, 45.56, allNewTransactions[0].GeoLatitude)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidGeographicLocation(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1"), 0, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
||||
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,a b"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1 "), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseTag(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, allNewTags, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+
|
||||
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 4, len(allNewTags))
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTags[0].Uid)
|
||||
assert.Equal(t, "foo", allNewTags[0].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTags[1].Uid)
|
||||
assert.Equal(t, "bar.", allNewTags[1].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTags[2].Uid)
|
||||
assert.Equal(t, "#test", allNewTags[2].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTags[3].Uid)
|
||||
assert.Equal(t, "hello\tworld", allNewTags[3].Name)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+
|
||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,foo bar\t#test"), 0, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[0].Comment)
|
||||
}
|
||||
|
||||
func TestEzBookKeepingPlainFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Missing Time Column
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Missing Type Column
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Missing Sub Category Column
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Missing Account Name Column
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Missing Amount Column
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,,,,,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// Missing Account2 Name Column
|
||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||
"2024-09-01 00:00:00,+08:00,Balance Modification,,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package _default
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// ezBookKeepingTransactionPlainTextDataTable defines the structure of ezbookkeeping transaction plain text data table
|
||||
type ezBookKeepingTransactionPlainTextDataTable struct {
|
||||
columnSeparator string
|
||||
lineSeparator string
|
||||
allLines []string
|
||||
headerLineColumnNames []string
|
||||
}
|
||||
|
||||
// ezBookKeepingTransactionPlainTextDataRow defines the structure of ezbookkeeping transaction plain text data row
|
||||
type ezBookKeepingTransactionPlainTextDataRow struct {
|
||||
allItems []string
|
||||
}
|
||||
|
||||
// ezBookKeepingTransactionPlainTextDataRowIterator defines the structure of ezbookkeeping transaction plain text data row iterator
|
||||
type ezBookKeepingTransactionPlainTextDataRowIterator struct {
|
||||
dataTable *ezBookKeepingTransactionPlainTextDataTable
|
||||
currentIndex int
|
||||
}
|
||||
|
||||
// ezBookKeepingTransactionPlainTextDataTableBuilder defines the structure of ezbookkeeping transaction plain text data table builder
|
||||
type ezBookKeepingTransactionPlainTextDataTableBuilder struct {
|
||||
columnSeparator string
|
||||
lineSeparator string
|
||||
columns []datatable.DataTableColumn
|
||||
dataColumnNameMapping map[datatable.DataTableColumn]string
|
||||
dataLineFormat string
|
||||
builder *strings.Builder
|
||||
}
|
||||
|
||||
// DataRowCount returns the total count of data row
|
||||
func (t *ezBookKeepingTransactionPlainTextDataTable) DataRowCount() int {
|
||||
if len(t.allLines) < 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(t.allLines) - 1
|
||||
}
|
||||
|
||||
// HeaderLineColumnNames returns the header column name list
|
||||
func (t *ezBookKeepingTransactionPlainTextDataTable) HeaderLineColumnNames() []string {
|
||||
return t.headerLineColumnNames
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *ezBookKeepingTransactionPlainTextDataTable) DataRowIterator() datatable.ImportedDataRowIterator {
|
||||
return &ezBookKeepingTransactionPlainTextDataRowIterator{
|
||||
dataTable: t,
|
||||
currentIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
func (r *ezBookKeepingTransactionPlainTextDataRow) ColumnCount() int {
|
||||
return len(r.allItems)
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
func (r *ezBookKeepingTransactionPlainTextDataRow) GetData(columnIndex int) string {
|
||||
if columnIndex >= len(r.allItems) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return r.allItems[columnIndex]
|
||||
}
|
||||
|
||||
// GetTime returns the time in the specified column index
|
||||
func (r *ezBookKeepingTransactionPlainTextDataRow) GetTime(columnIndex int, timezoneOffset int16) (time.Time, error) {
|
||||
return utils.ParseFromLongDateTime(r.GetData(columnIndex), timezoneOffset)
|
||||
}
|
||||
|
||||
// GetTimezoneOffset returns the time zone offset in the specified column index
|
||||
func (r *ezBookKeepingTransactionPlainTextDataRow) GetTimezoneOffset(columnIndex int) (*time.Location, error) {
|
||||
return utils.ParseFromTimezoneOffset(r.GetData(columnIndex))
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *ezBookKeepingTransactionPlainTextDataRowIterator) HasNext() bool {
|
||||
return t.currentIndex+1 < len(t.dataTable.allLines)
|
||||
}
|
||||
|
||||
// Next returns the next imported data row
|
||||
func (t *ezBookKeepingTransactionPlainTextDataRowIterator) Next() datatable.ImportedDataRow {
|
||||
if t.currentIndex+1 >= len(t.dataTable.allLines) {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.currentIndex++
|
||||
|
||||
rowContent := t.dataTable.allLines[t.currentIndex]
|
||||
rowItems := strings.Split(rowContent, t.dataTable.columnSeparator)
|
||||
|
||||
return &ezBookKeepingTransactionPlainTextDataRow{
|
||||
allItems: rowItems,
|
||||
}
|
||||
}
|
||||
|
||||
// AppendTransaction appends the specified transaction to data builder
|
||||
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) AppendTransaction(data map[datatable.DataTableColumn]string) {
|
||||
dataRowParams := make([]any, len(b.columns))
|
||||
|
||||
for i := 0; i < len(b.columns); i++ {
|
||||
dataRowParams[i] = data[b.columns[i]]
|
||||
}
|
||||
|
||||
b.builder.WriteString(fmt.Sprintf(b.dataLineFormat, dataRowParams...))
|
||||
}
|
||||
|
||||
// ReplaceDelimiters returns the text after removing the delimiters
|
||||
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) ReplaceDelimiters(text string) string {
|
||||
text = strings.Replace(text, "\r\n", " ", -1)
|
||||
text = strings.Replace(text, "\r", " ", -1)
|
||||
text = strings.Replace(text, "\n", " ", -1)
|
||||
text = strings.Replace(text, b.columnSeparator, " ", -1)
|
||||
text = strings.Replace(text, b.lineSeparator, " ", -1)
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
// String returns the textual representation of this data
|
||||
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) String() string {
|
||||
return b.builder.String()
|
||||
}
|
||||
|
||||
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) generateHeaderLine() string {
|
||||
var ret strings.Builder
|
||||
|
||||
for i := 0; i < len(b.columns); i++ {
|
||||
if ret.Len() > 0 {
|
||||
ret.WriteString(b.columnSeparator)
|
||||
}
|
||||
|
||||
dataColumn := b.columns[i]
|
||||
columnName := b.dataColumnNameMapping[dataColumn]
|
||||
|
||||
ret.WriteString(columnName)
|
||||
}
|
||||
|
||||
ret.WriteString(b.lineSeparator)
|
||||
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) generateDataLineFormat() string {
|
||||
var ret strings.Builder
|
||||
|
||||
for i := 0; i < len(b.columns); i++ {
|
||||
if ret.Len() > 0 {
|
||||
ret.WriteString(b.columnSeparator)
|
||||
}
|
||||
|
||||
ret.WriteString("%s")
|
||||
}
|
||||
|
||||
ret.WriteString(b.lineSeparator)
|
||||
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
func createNewezbookkeepingTransactionPlainTextDataTable(content string, columnSeparator string, lineSeparator string) (*ezBookKeepingTransactionPlainTextDataTable, error) {
|
||||
allLines := strings.Split(content, lineSeparator)
|
||||
|
||||
if len(allLines) < 2 {
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
headerLine := allLines[0]
|
||||
headerLine = strings.ReplaceAll(headerLine, "\r", "")
|
||||
headerLineItems := strings.Split(headerLine, columnSeparator)
|
||||
|
||||
return &ezBookKeepingTransactionPlainTextDataTable{
|
||||
columnSeparator: columnSeparator,
|
||||
lineSeparator: lineSeparator,
|
||||
allLines: allLines,
|
||||
headerLineColumnNames: headerLineItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createNewezbookkeepingTransactionPlainTextDataTableBuilder(transactionCount int, columns []datatable.DataTableColumn, dataColumnNameMapping map[datatable.DataTableColumn]string, columnSeparator string, lineSeparator string) *ezBookKeepingTransactionPlainTextDataTableBuilder {
|
||||
var builder strings.Builder
|
||||
builder.Grow(transactionCount * 100)
|
||||
|
||||
dataTableBuilder := &ezBookKeepingTransactionPlainTextDataTableBuilder{
|
||||
columnSeparator: columnSeparator,
|
||||
lineSeparator: lineSeparator,
|
||||
columns: columns,
|
||||
dataColumnNameMapping: dataColumnNameMapping,
|
||||
builder: &builder,
|
||||
}
|
||||
|
||||
headerLine := dataTableBuilder.generateHeaderLine()
|
||||
dataLineFormat := dataTableBuilder.generateDataLineFormat()
|
||||
|
||||
dataTableBuilder.builder.WriteString(headerLine)
|
||||
dataTableBuilder.dataLineFormat = dataLineFormat
|
||||
|
||||
return dataTableBuilder
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package _default
|
||||
|
||||
// ezBookKeepingTransactionDataTSVFileConverter defines the structure of TSV file converter
|
||||
type ezBookKeepingTransactionDataTSVFileConverter struct {
|
||||
ezBookKeepingTransactionDataPlainTextConverter
|
||||
}
|
||||
|
||||
// Initialize an ezbookkeeping transaction data tsv file converter singleton instance
|
||||
var (
|
||||
EzBookKeepingTransactionDataTSVFileConverter = &ezBookKeepingTransactionDataTSVFileConverter{
|
||||
ezBookKeepingTransactionDataPlainTextConverter{
|
||||
columnSeparator: "\t",
|
||||
},
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user