mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-17 16:24:25 +08:00
code refactor
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
// ImportedDataTable defines the structure of imported data table
|
||||
type ImportedDataTable interface {
|
||||
// DataRowCount returns the total count of data row
|
||||
DataRowCount() int
|
||||
|
||||
// HeaderLineColumnNames returns the header column name list
|
||||
HeaderLineColumnNames() []string
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
DataRowIterator() ImportedDataRowIterator
|
||||
}
|
||||
|
||||
// ImportedDataRow defines the structure of imported data row
|
||||
type ImportedDataRow interface {
|
||||
// IsValid returns whether this row contains valid data for importing
|
||||
IsValid() bool
|
||||
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
ColumnCount() int
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
GetData(columnIndex int) string
|
||||
|
||||
// GetTime returns the time in the specified column index
|
||||
GetTime(columnIndex int, timezoneOffset int16) (time.Time, error)
|
||||
|
||||
// GetTimezoneOffset returns the time zone offset in the specified column index
|
||||
GetTimezoneOffset(columnIndex int) (*time.Location, error)
|
||||
}
|
||||
|
||||
// ImportedDataRowIterator defines the structure of imported data row iterator
|
||||
type ImportedDataRowIterator interface {
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
HasNext() bool
|
||||
|
||||
// Next returns the next imported data row
|
||||
Next(ctx core.Context, user *models.User) ImportedDataRow
|
||||
}
|
||||
|
||||
// DataTableBuilder defines the structure of data table builder
|
||||
type DataTableBuilder interface {
|
||||
// AppendTransaction appends the specified transaction to data builder
|
||||
AppendTransaction(data map[DataTableColumn]string)
|
||||
|
||||
// ReplaceDelimiters returns the text after removing the delimiters
|
||||
ReplaceDelimiters(text string) string
|
||||
}
|
||||
@@ -14,30 +14,8 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||
)
|
||||
|
||||
// DataTableColumn represents the data column type of data table
|
||||
type DataTableColumn byte
|
||||
|
||||
// Data table columns
|
||||
const (
|
||||
DATA_TABLE_TRANSACTION_TIME DataTableColumn = 1
|
||||
DATA_TABLE_TRANSACTION_TIMEZONE DataTableColumn = 2
|
||||
DATA_TABLE_TRANSACTION_TYPE DataTableColumn = 3
|
||||
DATA_TABLE_CATEGORY DataTableColumn = 4
|
||||
DATA_TABLE_SUB_CATEGORY DataTableColumn = 5
|
||||
DATA_TABLE_ACCOUNT_NAME DataTableColumn = 6
|
||||
DATA_TABLE_ACCOUNT_CURRENCY DataTableColumn = 7
|
||||
DATA_TABLE_AMOUNT DataTableColumn = 8
|
||||
DATA_TABLE_RELATED_ACCOUNT_NAME DataTableColumn = 9
|
||||
DATA_TABLE_RELATED_ACCOUNT_CURRENCY DataTableColumn = 10
|
||||
DATA_TABLE_RELATED_AMOUNT DataTableColumn = 11
|
||||
DATA_TABLE_GEOGRAPHIC_LOCATION DataTableColumn = 12
|
||||
DATA_TABLE_TAGS DataTableColumn = 13
|
||||
DATA_TABLE_DESCRIPTION DataTableColumn = 14
|
||||
)
|
||||
|
||||
// DataTableTransactionDataExporter defines the structure of plain text data table exporter for transaction data
|
||||
type DataTableTransactionDataExporter struct {
|
||||
dataColumnMapping map[DataTableColumn]string
|
||||
transactionTypeMapping map[models.TransactionType]string
|
||||
geoLocationSeparator string
|
||||
transactionTagSeparator string
|
||||
@@ -45,20 +23,14 @@ type DataTableTransactionDataExporter struct {
|
||||
|
||||
// DataTableTransactionDataImporter defines the structure of plain text data table importer for transaction data
|
||||
type DataTableTransactionDataImporter struct {
|
||||
dataColumnMapping map[DataTableColumn]string
|
||||
transactionTypeMapping map[models.TransactionType]string
|
||||
geoLocationSeparator string
|
||||
transactionTagSeparator string
|
||||
postProcessFunc DataTableTransactionDataImporterPostProcessFunc
|
||||
}
|
||||
|
||||
// DataTableTransactionDataImporterPostProcessFunc represents item post process function of DataTableTransactionDataImporter
|
||||
type DataTableTransactionDataImporterPostProcessFunc func(core.Context, *models.ImportTransaction) error
|
||||
|
||||
// CreateNewExporter returns a new data table transaction data exporter according to the specified arguments
|
||||
func CreateNewExporter(dataColumnMapping map[DataTableColumn]string, transactionTypeMapping map[models.TransactionType]string, geoLocationSeparator string, transactionTagSeparator string) *DataTableTransactionDataExporter {
|
||||
func CreateNewExporter(transactionTypeMapping map[models.TransactionType]string, geoLocationSeparator string, transactionTagSeparator string) *DataTableTransactionDataExporter {
|
||||
return &DataTableTransactionDataExporter{
|
||||
dataColumnMapping: dataColumnMapping,
|
||||
transactionTypeMapping: transactionTypeMapping,
|
||||
geoLocationSeparator: geoLocationSeparator,
|
||||
transactionTagSeparator: transactionTagSeparator,
|
||||
@@ -66,9 +38,8 @@ func CreateNewExporter(dataColumnMapping map[DataTableColumn]string, transaction
|
||||
}
|
||||
|
||||
// CreateNewImporter returns a new data table transaction data importer according to the specified arguments
|
||||
func CreateNewImporter(dataColumnMapping map[DataTableColumn]string, transactionTypeMapping map[models.TransactionType]string, geoLocationSeparator string, transactionTagSeparator string) *DataTableTransactionDataImporter {
|
||||
func CreateNewImporter(transactionTypeMapping map[models.TransactionType]string, geoLocationSeparator string, transactionTagSeparator string) *DataTableTransactionDataImporter {
|
||||
return &DataTableTransactionDataImporter{
|
||||
dataColumnMapping: dataColumnMapping,
|
||||
transactionTypeMapping: transactionTypeMapping,
|
||||
geoLocationSeparator: geoLocationSeparator,
|
||||
transactionTagSeparator: transactionTagSeparator,
|
||||
@@ -76,41 +47,14 @@ func CreateNewImporter(dataColumnMapping map[DataTableColumn]string, transaction
|
||||
}
|
||||
|
||||
// CreateNewSimpleImporter returns a new data table transaction data importer according to the specified arguments
|
||||
func CreateNewSimpleImporter(dataColumnMapping map[DataTableColumn]string, transactionTypeMapping map[models.TransactionType]string) *DataTableTransactionDataImporter {
|
||||
func CreateNewSimpleImporter(transactionTypeMapping map[models.TransactionType]string) *DataTableTransactionDataImporter {
|
||||
return &DataTableTransactionDataImporter{
|
||||
dataColumnMapping: dataColumnMapping,
|
||||
transactionTypeMapping: transactionTypeMapping,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewSimpleImporterWithPostProcessFunc returns a new data table transaction data importer according to the specified arguments
|
||||
func CreateNewSimpleImporterWithPostProcessFunc(dataColumnMapping map[DataTableColumn]string, transactionTypeMapping map[models.TransactionType]string, postProcessFunc DataTableTransactionDataImporterPostProcessFunc) *DataTableTransactionDataImporter {
|
||||
return &DataTableTransactionDataImporter{
|
||||
dataColumnMapping: dataColumnMapping,
|
||||
transactionTypeMapping: transactionTypeMapping,
|
||||
postProcessFunc: postProcessFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewSimpleImporterFromWritableDataTable returns a new data table transaction data importer according to the specified arguments
|
||||
func CreateNewSimpleImporterFromWritableDataTable(writableDataTable *WritableDataTable, transactionTypeMapping map[models.TransactionType]string) *DataTableTransactionDataImporter {
|
||||
return &DataTableTransactionDataImporter{
|
||||
dataColumnMapping: writableDataTable.GetDataColumnMapping(),
|
||||
transactionTypeMapping: transactionTypeMapping,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewSimpleImporterFromWritableDataTableWithPostProcessFunc returns a new data table transaction data importer according to the specified arguments
|
||||
func CreateNewSimpleImporterFromWritableDataTableWithPostProcessFunc(writableDataTable *WritableDataTable, transactionTypeMapping map[models.TransactionType]string, postProcessFunc DataTableTransactionDataImporterPostProcessFunc) *DataTableTransactionDataImporter {
|
||||
return &DataTableTransactionDataImporter{
|
||||
dataColumnMapping: writableDataTable.GetDataColumnMapping(),
|
||||
transactionTypeMapping: transactionTypeMapping,
|
||||
postProcessFunc: postProcessFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// BuildExportedContent writes the exported transaction data to the data table builder
|
||||
func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context, dataTableBuilder DataTableBuilder, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) error {
|
||||
func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context, dataTableBuilder TransactionDataTableBuilder, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) error {
|
||||
for i := 0; i < len(transactions); i++ {
|
||||
transaction := transactions[i]
|
||||
|
||||
@@ -118,27 +62,27 @@ func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context
|
||||
continue
|
||||
}
|
||||
|
||||
dataRowMap := make(map[DataTableColumn]string, 15)
|
||||
dataRowMap := make(map[TransactionDataTableColumn]string, 15)
|
||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||
|
||||
dataRowMap[DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
||||
dataRowMap[DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionTimeZone)
|
||||
dataRowMap[DATA_TABLE_TRANSACTION_TYPE] = dataTableBuilder.ReplaceDelimiters(c.getDisplayTransactionTypeName(transaction.Type))
|
||||
dataRowMap[DATA_TABLE_CATEGORY] = c.getExportedTransactionCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||
dataRowMap[DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||
dataRowMap[DATA_TABLE_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.AccountId, accountMap)
|
||||
dataRowMap[DATA_TABLE_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.AccountId, accountMap)
|
||||
dataRowMap[DATA_TABLE_AMOUNT] = utils.FormatAmount(transaction.Amount)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionTimeZone)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataTableBuilder.ReplaceDelimiters(c.getDisplayTransactionTypeName(transaction.Type))
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_CATEGORY] = c.getExportedTransactionCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(dataTableBuilder, transaction.CategoryId, categoryMap)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.AccountId, accountMap)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.AccountId, accountMap)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(transaction.Amount)
|
||||
|
||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||
dataRowMap[DATA_TABLE_RELATED_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||
dataRowMap[DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||
dataRowMap[DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(transaction.RelatedAccountAmount)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = c.getExportedAccountName(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = c.getAccountCurrency(dataTableBuilder, transaction.RelatedAccountId, accountMap)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(transaction.RelatedAccountAmount)
|
||||
}
|
||||
|
||||
dataRowMap[DATA_TABLE_GEOGRAPHIC_LOCATION] = c.getExportedGeographicLocation(transaction)
|
||||
dataRowMap[DATA_TABLE_TAGS] = c.getExportedTags(dataTableBuilder, transaction.TransactionId, allTagIndexes, tagMap)
|
||||
dataRowMap[DATA_TABLE_DESCRIPTION] = dataTableBuilder.ReplaceDelimiters(transaction.Comment)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION] = c.getExportedGeographicLocation(transaction)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_TAGS] = c.getExportedTags(dataTableBuilder, transaction.TransactionId, allTagIndexes, tagMap)
|
||||
dataRowMap[TRANSACTION_DATA_TABLE_DESCRIPTION] = dataTableBuilder.ReplaceDelimiters(transaction.Comment)
|
||||
|
||||
dataTableBuilder.AppendTransaction(dataRowMap)
|
||||
}
|
||||
@@ -162,7 +106,7 @@ func (c *DataTableTransactionDataExporter) getDisplayTransactionTypeName(transac
|
||||
return transactionTypeName
|
||||
}
|
||||
|
||||
func (c *DataTableTransactionDataExporter) getExportedTransactionCategoryName(dataTableBuilder DataTableBuilder, categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||
func (c *DataTableTransactionDataExporter) getExportedTransactionCategoryName(dataTableBuilder TransactionDataTableBuilder, categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||
category, exists := categoryMap[categoryId]
|
||||
|
||||
if !exists {
|
||||
@@ -182,7 +126,7 @@ func (c *DataTableTransactionDataExporter) getExportedTransactionCategoryName(da
|
||||
return dataTableBuilder.ReplaceDelimiters(parentCategory.Name)
|
||||
}
|
||||
|
||||
func (c *DataTableTransactionDataExporter) getExportedTransactionSubCategoryName(dataTableBuilder DataTableBuilder, categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||
func (c *DataTableTransactionDataExporter) getExportedTransactionSubCategoryName(dataTableBuilder TransactionDataTableBuilder, categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||
category, exists := categoryMap[categoryId]
|
||||
|
||||
if exists {
|
||||
@@ -192,7 +136,7 @@ func (c *DataTableTransactionDataExporter) getExportedTransactionSubCategoryName
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DataTableTransactionDataExporter) getExportedAccountName(dataTableBuilder DataTableBuilder, accountId int64, accountMap map[int64]*models.Account) string {
|
||||
func (c *DataTableTransactionDataExporter) getExportedAccountName(dataTableBuilder TransactionDataTableBuilder, accountId int64, accountMap map[int64]*models.Account) string {
|
||||
account, exists := accountMap[accountId]
|
||||
|
||||
if exists {
|
||||
@@ -202,7 +146,7 @@ func (c *DataTableTransactionDataExporter) getExportedAccountName(dataTableBuild
|
||||
}
|
||||
}
|
||||
|
||||
func (c *DataTableTransactionDataExporter) getAccountCurrency(dataTableBuilder DataTableBuilder, accountId int64, accountMap map[int64]*models.Account) string {
|
||||
func (c *DataTableTransactionDataExporter) getAccountCurrency(dataTableBuilder TransactionDataTableBuilder, accountId int64, accountMap map[int64]*models.Account) string {
|
||||
account, exists := accountMap[accountId]
|
||||
|
||||
if exists {
|
||||
@@ -220,7 +164,7 @@ func (c *DataTableTransactionDataExporter) getExportedGeographicLocation(transac
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *DataTableTransactionDataExporter) getExportedTags(dataTableBuilder DataTableBuilder, transactionId int64, allTagIndexes map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
||||
func (c *DataTableTransactionDataExporter) getExportedTags(dataTableBuilder TransactionDataTableBuilder, transactionId int64, allTagIndexes map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
||||
tagIndexes, exists := allTagIndexes[transactionId]
|
||||
|
||||
if !exists {
|
||||
@@ -248,8 +192,8 @@ func (c *DataTableTransactionDataExporter) getExportedTags(dataTableBuilder Data
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported transaction data
|
||||
func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable ImportedDataTable, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
if dataTable.DataRowCount() < 1 {
|
||||
func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable TransactionDataTable, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
if dataTable.TransactionRowCount() < 1 {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse import data for user \"uid:%d\", because data table row count is less 1", user.Uid)
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
@@ -260,29 +204,12 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
headerLineItems := dataTable.HeaderLineColumnNames()
|
||||
headerItemMap := make(map[string]int)
|
||||
|
||||
for i := 0; i < len(headerLineItems); i++ {
|
||||
headerItemMap[headerLineItems[i]] = i
|
||||
}
|
||||
|
||||
timeColumnIdx, timeColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TRANSACTION_TIME]]
|
||||
timezoneColumnIdx, timezoneColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TRANSACTION_TIMEZONE]]
|
||||
typeColumnIdx, typeColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TRANSACTION_TYPE]]
|
||||
subCategoryColumnIdx, subCategoryColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_SUB_CATEGORY]]
|
||||
accountColumnIdx, accountColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_ACCOUNT_NAME]]
|
||||
accountCurrencyColumnIdx, accountCurrencyColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_ACCOUNT_CURRENCY]]
|
||||
amountColumnIdx, amountColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_AMOUNT]]
|
||||
account2ColumnIdx, account2ColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_RELATED_ACCOUNT_NAME]]
|
||||
account2CurrencyColumnIdx, account2CurrencyColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_RELATED_ACCOUNT_CURRENCY]]
|
||||
amount2ColumnIdx, amount2ColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_RELATED_AMOUNT]]
|
||||
geoLocationIdx, geoLocationExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_GEOGRAPHIC_LOCATION]]
|
||||
tagsColumnIdx, tagsColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TAGS]]
|
||||
descriptionColumnIdx, descriptionColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_DESCRIPTION]]
|
||||
|
||||
if !timeColumnExists || !typeColumnExists || !subCategoryColumnExists ||
|
||||
!accountColumnExists || !amountColumnExists || !account2ColumnExists {
|
||||
if !dataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TIME) ||
|
||||
!dataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE) ||
|
||||
!dataTable.HasColumn(TRANSACTION_DATA_TABLE_SUB_CATEGORY) ||
|
||||
!dataTable.HasColumn(TRANSACTION_DATA_TABLE_ACCOUNT_NAME) ||
|
||||
!dataTable.HasColumn(TRANSACTION_DATA_TABLE_AMOUNT) ||
|
||||
!dataTable.HasColumn(TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME) {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse import data for user \"uid:%d\", because missing essential columns in header row", user.Uid)
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||
}
|
||||
@@ -307,59 +234,53 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
tagMap = make(map[string]*models.TransactionTag)
|
||||
}
|
||||
|
||||
allNewTransactions := make(models.ImportedTransactionSlice, 0, dataTable.DataRowCount())
|
||||
allNewTransactions := make(models.ImportedTransactionSlice, 0, dataTable.TransactionRowCount())
|
||||
allNewAccounts := make([]*models.Account, 0)
|
||||
allNewSubExpenseCategories := make([]*models.TransactionCategory, 0)
|
||||
allNewSubIncomeCategories := make([]*models.TransactionCategory, 0)
|
||||
allNewSubTransferCategories := make([]*models.TransactionCategory, 0)
|
||||
allNewTags := make([]*models.TransactionTag, 0)
|
||||
|
||||
dataRowIterator := dataTable.DataRowIterator()
|
||||
dataRowIterator := dataTable.TransactionRowIterator()
|
||||
dataRowIndex := 0
|
||||
|
||||
for dataRowIterator.HasNext() {
|
||||
dataRowIndex++
|
||||
dataRow := dataRowIterator.Next(ctx, user)
|
||||
dataRow, err := dataRowIterator.Next(ctx, user)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse data row \"index:%d\" for user \"uid:%d\", because %s", dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
if !dataRow.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
columnCount := dataRow.ColumnCount()
|
||||
|
||||
if columnCount < 1 || (columnCount == 1 && dataRow.GetData(0) == "") {
|
||||
continue
|
||||
}
|
||||
|
||||
if columnCount < len(headerLineItems) {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse data row \"index:%d\" for user \"uid:%d\", because may missing some columns (column count %d in data row is less than header column count %d)", dataRowIndex, user.Uid, columnCount, len(headerLineItems))
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||
}
|
||||
|
||||
timezoneOffset := defaultTimezoneOffset
|
||||
|
||||
if timezoneColumnExists {
|
||||
transactionTimezone, err := dataRow.GetTimezoneOffset(timezoneColumnIdx)
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE) {
|
||||
transactionTimezone, err := utils.ParseFromTimezoneOffset(dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse time zone \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(timezoneColumnIdx), dataRowIndex, user.Uid, err.Error())
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse time zone \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE), dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrTransactionTimeZoneInvalid
|
||||
}
|
||||
|
||||
timezoneOffset = utils.GetTimezoneOffsetMinutes(transactionTimezone)
|
||||
}
|
||||
|
||||
transactionTime, err := dataRow.GetTime(timeColumnIdx, timezoneOffset)
|
||||
transactionTime, err := utils.ParseFromLongDateTime(dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME), timezoneOffset)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse time \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(timeColumnIdx), dataRowIndex, user.Uid, err.Error())
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse time \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME), dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrTransactionTimeInvalid
|
||||
}
|
||||
|
||||
transactionDbType, err := c.getTransactionDbType(nameDbTypeMap, dataRow.GetData(typeColumnIdx))
|
||||
transactionDbType, err := c.getTransactionDbType(nameDbTypeMap, dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse transaction type \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(typeColumnIdx), dataRowIndex, user.Uid, err.Error())
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse transaction type \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE), dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, errs.Or(err, errs.ErrTransactionTypeInvalid)
|
||||
}
|
||||
|
||||
@@ -374,7 +295,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
return nil, nil, nil, nil, nil, nil, errs.Or(err, errs.ErrTransactionTypeInvalid)
|
||||
}
|
||||
|
||||
subCategoryName = dataRow.GetData(subCategoryColumnIdx)
|
||||
subCategoryName = dataRow.GetData(TRANSACTION_DATA_TABLE_SUB_CATEGORY)
|
||||
|
||||
if transactionDbType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||
subCategory, exists := expenseCategoryMap[subCategoryName]
|
||||
@@ -409,11 +330,11 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
}
|
||||
}
|
||||
|
||||
accountName := dataRow.GetData(accountColumnIdx)
|
||||
accountName := dataRow.GetData(TRANSACTION_DATA_TABLE_ACCOUNT_NAME)
|
||||
accountCurrency := user.DefaultCurrency
|
||||
|
||||
if accountCurrencyColumnExists {
|
||||
accountCurrency = dataRow.GetData(accountCurrencyColumnIdx)
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY) {
|
||||
accountCurrency = dataRow.GetData(TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY)
|
||||
|
||||
if _, ok := validators.AllCurrencyNames[accountCurrency]; !ok {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] account currency \"%s\" is not supported in data row \"index:%d\" for user \"uid:%d\"", accountCurrency, dataRowIndex, user.Uid)
|
||||
@@ -429,7 +350,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
accountMap[accountName] = account
|
||||
}
|
||||
|
||||
if accountCurrencyColumnExists {
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY) {
|
||||
if account.Name != "" && account.Currency != accountCurrency {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] currency \"%s\" in data row \"index:%d\" not equals currency \"%s\" of the account for user \"uid:%d\"", accountCurrency, dataRowIndex, account.Currency, user.Uid)
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||
@@ -438,10 +359,10 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
accountCurrency = account.Currency
|
||||
}
|
||||
|
||||
amount, err := utils.ParseAmount(dataRow.GetData(amountColumnIdx))
|
||||
amount, err := utils.ParseAmount(dataRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse acmount \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(amountColumnIdx), dataRowIndex, user.Uid, err.Error())
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse acmount \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT), dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrAmountInvalid
|
||||
}
|
||||
|
||||
@@ -451,11 +372,11 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
account2Currency := ""
|
||||
|
||||
if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||
account2Name = dataRow.GetData(account2ColumnIdx)
|
||||
account2Name = dataRow.GetData(TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME)
|
||||
account2Currency = user.DefaultCurrency
|
||||
|
||||
if account2CurrencyColumnExists {
|
||||
account2Currency = dataRow.GetData(account2CurrencyColumnIdx)
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY) {
|
||||
account2Currency = dataRow.GetData(TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY)
|
||||
|
||||
if _, ok := validators.AllCurrencyNames[account2Currency]; !ok {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] account2 currency \"%s\" is not supported in data row \"index:%d\" for user \"uid:%d\"", account2Currency, dataRowIndex, user.Uid)
|
||||
@@ -471,7 +392,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
accountMap[account2Name] = account2
|
||||
}
|
||||
|
||||
if account2CurrencyColumnExists {
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY) {
|
||||
if account2.Name != "" && account2.Currency != account2Currency {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] currency \"%s\" in data row \"index:%d\" not equals currency \"%s\" of the account2 for user \"uid:%d\"", account2Currency, dataRowIndex, account2.Currency, user.Uid)
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||
@@ -482,11 +403,11 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
|
||||
relatedAccountId = account2.AccountId
|
||||
|
||||
if amount2ColumnExists {
|
||||
relatedAccountAmount, err = utils.ParseAmount(dataRow.GetData(amount2ColumnIdx))
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_RELATED_AMOUNT) {
|
||||
relatedAccountAmount, err = utils.ParseAmount(dataRow.GetData(TRANSACTION_DATA_TABLE_RELATED_AMOUNT))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse acmount2 \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(amount2ColumnIdx), dataRowIndex, user.Uid, err.Error())
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse acmount2 \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(TRANSACTION_DATA_TABLE_RELATED_AMOUNT), dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrAmountInvalid
|
||||
}
|
||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||
@@ -497,21 +418,21 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
geoLongitude := float64(0)
|
||||
geoLatitude := float64(0)
|
||||
|
||||
if geoLocationExists {
|
||||
geoLocationItems := strings.Split(dataRow.GetData(geoLocationIdx), c.geoLocationSeparator)
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION) {
|
||||
geoLocationItems := strings.Split(dataRow.GetData(TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION), c.geoLocationSeparator)
|
||||
|
||||
if len(geoLocationItems) == 2 {
|
||||
geoLongitude, err = utils.StringToFloat64(geoLocationItems[0])
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse geographic location \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(geoLocationIdx), dataRowIndex, user.Uid, err.Error())
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse geographic location \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION), dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrGeographicLocationInvalid
|
||||
}
|
||||
|
||||
geoLatitude, err = utils.StringToFloat64(geoLocationItems[1])
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse geographic location \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(geoLocationIdx), dataRowIndex, user.Uid, err.Error())
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot parse geographic location \"%s\" in data row \"index:%d\" for user \"uid:%d\", because %s", dataRow.GetData(TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION), dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrGeographicLocationInvalid
|
||||
}
|
||||
}
|
||||
@@ -520,8 +441,8 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
var tagIds []string
|
||||
var tagNames []string
|
||||
|
||||
if tagsColumnExists {
|
||||
tagNameItems := strings.Split(dataRow.GetData(tagsColumnIdx), c.transactionTagSeparator)
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_TAGS) {
|
||||
tagNameItems := strings.Split(dataRow.GetData(TRANSACTION_DATA_TABLE_TAGS), c.transactionTagSeparator)
|
||||
|
||||
for i := 0; i < len(tagNameItems); i++ {
|
||||
tagName := tagNameItems[i]
|
||||
@@ -548,8 +469,8 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
|
||||
description := ""
|
||||
|
||||
if descriptionColumnExists {
|
||||
description = dataRow.GetData(descriptionColumnIdx)
|
||||
if dataTable.HasColumn(TRANSACTION_DATA_TABLE_DESCRIPTION) {
|
||||
description = dataRow.GetData(TRANSACTION_DATA_TABLE_DESCRIPTION)
|
||||
}
|
||||
|
||||
transaction := &models.ImportTransaction{
|
||||
@@ -578,15 +499,6 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
OriginalTagNames: tagNames,
|
||||
}
|
||||
|
||||
if c.postProcessFunc != nil {
|
||||
err = c.postProcessFunc(ctx, transaction)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] cannot post process data row \"index:%d\" for user \"uid:%d\", because %s", dataRowIndex, user.Uid, err.Error())
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
allNewTransactions = append(allNewTransactions, transaction)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/shakinm/xlsReader/xls"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
// DefaultExcelFileImportedDataTable defines the structure of default excel file data table
|
||||
type DefaultExcelFileImportedDataTable struct {
|
||||
workbook *xls.Workbook
|
||||
headerLineColumnNames []string
|
||||
}
|
||||
|
||||
// DefaultExcelFileDataRow defines the structure of default excel file data table row
|
||||
type DefaultExcelFileDataRow struct {
|
||||
sheet *xls.Sheet
|
||||
rowIndex int
|
||||
}
|
||||
|
||||
// DefaultExcelFileDataRowIterator defines the structure of default excel file data table row iterator
|
||||
type DefaultExcelFileDataRowIterator struct {
|
||||
dataTable *DefaultExcelFileImportedDataTable
|
||||
currentTableIndex int
|
||||
currentRowIndexInTable int
|
||||
}
|
||||
|
||||
// DataRowCount returns the total count of data row
|
||||
func (t *DefaultExcelFileImportedDataTable) DataRowCount() int {
|
||||
allSheets := t.workbook.GetSheets()
|
||||
totalDataRowCount := 0
|
||||
|
||||
for i := 0; i < len(allSheets); i++ {
|
||||
sheet := allSheets[i]
|
||||
|
||||
if sheet.GetNumberRows() <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDataRowCount += sheet.GetNumberRows() - 1
|
||||
}
|
||||
|
||||
return totalDataRowCount
|
||||
}
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
func (t *DefaultExcelFileImportedDataTable) HeaderColumnNames() []string {
|
||||
return t.headerLineColumnNames
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *DefaultExcelFileImportedDataTable) DataRowIterator() ImportedDataRowIterator {
|
||||
return &DefaultExcelFileDataRowIterator{
|
||||
dataTable: t,
|
||||
currentTableIndex: 0,
|
||||
currentRowIndexInTable: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
func (r *DefaultExcelFileDataRow) ColumnCount() int {
|
||||
row, err := r.sheet.GetRow(r.rowIndex)
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(row.GetCols())
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
func (r *DefaultExcelFileDataRow) GetData(columnIndex int) string {
|
||||
row, err := r.sheet.GetRow(r.rowIndex)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
cell, err := row.GetCol(columnIndex)
|
||||
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return cell.GetString()
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *DefaultExcelFileDataRowIterator) HasNext() bool {
|
||||
allSheets := t.dataTable.workbook.GetSheets()
|
||||
|
||||
if t.currentTableIndex >= len(allSheets) {
|
||||
return false
|
||||
}
|
||||
|
||||
currentSheet := allSheets[t.currentTableIndex]
|
||||
|
||||
if t.currentRowIndexInTable+1 < currentSheet.GetNumberRows() {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := t.currentTableIndex + 1; i < len(allSheets); i++ {
|
||||
sheet := allSheets[i]
|
||||
|
||||
if sheet.GetNumberRows() <= 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Next returns the next imported data row
|
||||
func (t *DefaultExcelFileDataRowIterator) Next() ImportedDataRow {
|
||||
allSheets := t.dataTable.workbook.GetSheets()
|
||||
currentRowIndexInTable := t.currentRowIndexInTable
|
||||
|
||||
for i := t.currentTableIndex; i < len(allSheets); i++ {
|
||||
sheet := allSheets[i]
|
||||
|
||||
if currentRowIndexInTable+1 < sheet.GetNumberRows() {
|
||||
t.currentRowIndexInTable++
|
||||
currentRowIndexInTable = t.currentRowIndexInTable
|
||||
break
|
||||
}
|
||||
|
||||
t.currentTableIndex++
|
||||
t.currentRowIndexInTable = 0
|
||||
currentRowIndexInTable = 0
|
||||
}
|
||||
|
||||
if t.currentTableIndex >= len(allSheets) {
|
||||
return nil
|
||||
}
|
||||
|
||||
currentSheet := allSheets[t.currentTableIndex]
|
||||
|
||||
if t.currentRowIndexInTable >= currentSheet.GetNumberRows() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &DefaultExcelFileDataRow{
|
||||
sheet: ¤tSheet,
|
||||
rowIndex: t.currentRowIndexInTable,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewDefaultExcelFileImportedDataTable returns default excel xls data table by file binary data
|
||||
func CreateNewDefaultExcelFileImportedDataTable(data []byte) (*DefaultExcelFileImportedDataTable, error) {
|
||||
reader := bytes.NewReader(data)
|
||||
workbook, err := xls.OpenReader(reader)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allSheets := workbook.GetSheets()
|
||||
var headerRowItems []string
|
||||
|
||||
for i := 0; i < len(allSheets); i++ {
|
||||
sheet := allSheets[i]
|
||||
|
||||
if sheet.GetNumberRows() < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
row, err := sheet.GetRow(0)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cells := row.GetCols()
|
||||
|
||||
if i == 0 {
|
||||
for j := 0; j < len(cells); j++ {
|
||||
headerItem := cells[j].GetString()
|
||||
|
||||
if headerItem == "" {
|
||||
break
|
||||
}
|
||||
|
||||
headerRowItems = append(headerRowItems, headerItem)
|
||||
}
|
||||
} else {
|
||||
for j := 0; j < min(len(cells), len(headerRowItems)); j++ {
|
||||
headerItem := cells[j].GetString()
|
||||
|
||||
if headerItem != headerRowItems[j] {
|
||||
return nil, errs.ErrFieldsInMultiTableAreDifferent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &DefaultExcelFileImportedDataTable{
|
||||
workbook: &workbook,
|
||||
headerLineColumnNames: headerRowItems,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
)
|
||||
|
||||
// DefaultPlainTextImportedDataTable defines the structure of default plain text data table
|
||||
type DefaultPlainTextImportedDataTable struct {
|
||||
allLines [][]string
|
||||
}
|
||||
|
||||
// DefaultPlainTextImportedDataRow defines the structure of default plain text data table row
|
||||
type DefaultPlainTextImportedDataRow struct {
|
||||
dataTable *DefaultPlainTextImportedDataTable
|
||||
allItems []string
|
||||
}
|
||||
|
||||
// DefaultPlainTextImportedDataRowIterator defines the structure of default plain text data table row iterator
|
||||
type DefaultPlainTextImportedDataRowIterator struct {
|
||||
dataTable *DefaultPlainTextImportedDataTable
|
||||
currentIndex int
|
||||
}
|
||||
|
||||
// DataRowCount returns the total count of data row
|
||||
func (t *DefaultPlainTextImportedDataTable) DataRowCount() int {
|
||||
if len(t.allLines) < 1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(t.allLines) - 1
|
||||
}
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
func (t *DefaultPlainTextImportedDataTable) HeaderColumnNames() []string {
|
||||
if len(t.allLines) < 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return t.allLines[0]
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *DefaultPlainTextImportedDataTable) DataRowIterator() ImportedDataRowIterator {
|
||||
return &DefaultPlainTextImportedDataRowIterator{
|
||||
dataTable: t,
|
||||
currentIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
func (r *DefaultPlainTextImportedDataRow) ColumnCount() int {
|
||||
return len(r.allItems)
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
func (r *DefaultPlainTextImportedDataRow) GetData(columnIndex int) string {
|
||||
if columnIndex >= len(r.allItems) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return r.allItems[columnIndex]
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *DefaultPlainTextImportedDataRowIterator) HasNext() bool {
|
||||
return t.currentIndex+1 < len(t.dataTable.allLines)
|
||||
}
|
||||
|
||||
// Next returns the next imported data row
|
||||
func (t *DefaultPlainTextImportedDataRowIterator) Next() ImportedDataRow {
|
||||
if t.currentIndex+1 >= len(t.dataTable.allLines) {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.currentIndex++
|
||||
|
||||
rowItems := t.dataTable.allLines[t.currentIndex]
|
||||
|
||||
return &DefaultPlainTextImportedDataRow{
|
||||
dataTable: t.dataTable,
|
||||
allItems: rowItems,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewDefaultCsvDataTable returns default csv data table by io readers
|
||||
func CreateNewDefaultCsvDataTable(ctx core.Context, reader io.Reader) (*DefaultPlainTextImportedDataTable, error) {
|
||||
return createNewDefaultPlainTextDataTable(ctx, reader, ',')
|
||||
}
|
||||
|
||||
func createNewDefaultPlainTextDataTable(ctx core.Context, reader io.Reader, comma rune) (*DefaultPlainTextImportedDataTable, error) {
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.Comma = comma
|
||||
csvReader.FieldsPerRecord = -1
|
||||
|
||||
allLines := make([][]string, 0)
|
||||
|
||||
for {
|
||||
items, err := csvReader.Read()
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[Default_plain_text_imported_data_table.createNewDefaultPlainTextDataTable] cannot parse plain text data, because %s", err.Error())
|
||||
return nil, errs.ErrInvalidCSVFile
|
||||
}
|
||||
|
||||
if len(items) == 0 && items[0] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
allLines = append(allLines, items)
|
||||
}
|
||||
|
||||
return &DefaultPlainTextImportedDataTable{
|
||||
allLines: allLines,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package datatable
|
||||
|
||||
// ImportedDataTable defines the structure of imported data table
|
||||
type ImportedDataTable interface {
|
||||
// DataRowCount returns the total count of data row
|
||||
DataRowCount() int
|
||||
|
||||
// HeaderColumnNames returns the header column name list
|
||||
HeaderColumnNames() []string
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
DataRowIterator() ImportedDataRowIterator
|
||||
}
|
||||
|
||||
// ImportedDataRow defines the structure of imported data row
|
||||
type ImportedDataRow interface {
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
ColumnCount() int
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
GetData(columnIndex int) string
|
||||
}
|
||||
|
||||
// ImportedDataRowIterator defines the structure of imported data row iterator
|
||||
type ImportedDataRowIterator interface {
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
HasNext() bool
|
||||
|
||||
// Next returns the next imported data row
|
||||
Next() ImportedDataRow
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
// ImportedTransactionDataTable defines the structure of imported transaction data table
|
||||
type ImportedTransactionDataTable struct {
|
||||
innerDataTable ImportedDataTable
|
||||
dataColumnMapping map[TransactionDataTableColumn]string
|
||||
dataColumnIndexes map[TransactionDataTableColumn]int
|
||||
rowParser TransactionDataRowParser
|
||||
addedColumns map[TransactionDataTableColumn]any
|
||||
}
|
||||
|
||||
// ImportedTransactionDataRow defines the structure of imported transaction data row
|
||||
type ImportedTransactionDataRow struct {
|
||||
transactionDataTable *ImportedTransactionDataTable
|
||||
rowData map[TransactionDataTableColumn]string
|
||||
rowDataValid bool
|
||||
}
|
||||
|
||||
// ImportedTransactionDataRowIterator defines the structure of imported transaction data row iterator
|
||||
type ImportedTransactionDataRowIterator struct {
|
||||
transactionDataTable *ImportedTransactionDataTable
|
||||
innerIterator ImportedDataRowIterator
|
||||
}
|
||||
|
||||
// HasColumn returns whether the data table has specified column
|
||||
func (t *ImportedTransactionDataTable) HasColumn(column TransactionDataTableColumn) bool {
|
||||
index, exists := t.dataColumnIndexes[column]
|
||||
|
||||
if exists && index >= 0 {
|
||||
return exists
|
||||
}
|
||||
|
||||
if t.addedColumns != nil {
|
||||
_, exists = t.addedColumns[column]
|
||||
|
||||
if exists {
|
||||
return exists
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TransactionRowCount returns the total count of transaction data row
|
||||
func (t *ImportedTransactionDataTable) TransactionRowCount() int {
|
||||
return t.innerDataTable.DataRowCount()
|
||||
}
|
||||
|
||||
// TransactionRowIterator returns the iterator of transaction data row
|
||||
func (t *ImportedTransactionDataTable) TransactionRowIterator() TransactionDataRowIterator {
|
||||
return &ImportedTransactionDataRowIterator{
|
||||
transactionDataTable: t,
|
||||
innerIterator: t.innerDataTable.DataRowIterator(),
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid returns whether this row is valid data for importing
|
||||
func (r *ImportedTransactionDataRow) IsValid() bool {
|
||||
return r.rowDataValid
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column type
|
||||
func (r *ImportedTransactionDataRow) GetData(column TransactionDataTableColumn) string {
|
||||
if !r.rowDataValid {
|
||||
return ""
|
||||
}
|
||||
|
||||
_, exists := r.transactionDataTable.dataColumnIndexes[column]
|
||||
|
||||
if exists {
|
||||
return r.rowData[column]
|
||||
}
|
||||
|
||||
if r.transactionDataTable.addedColumns != nil {
|
||||
_, exists = r.transactionDataTable.addedColumns[column]
|
||||
|
||||
if exists {
|
||||
return r.rowData[column]
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *ImportedTransactionDataRowIterator) HasNext() bool {
|
||||
return t.innerIterator.HasNext()
|
||||
}
|
||||
|
||||
// Next returns the next transaction data row
|
||||
func (t *ImportedTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow TransactionDataRow, err error) {
|
||||
importedRow := t.innerIterator.Next()
|
||||
|
||||
if importedRow == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if importedRow.ColumnCount() == 1 && importedRow.GetData(0) == "" {
|
||||
return &ImportedTransactionDataRow{
|
||||
transactionDataTable: t.transactionDataTable,
|
||||
rowData: nil,
|
||||
rowDataValid: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if importedRow.ColumnCount() < len(t.transactionDataTable.dataColumnIndexes) {
|
||||
log.Errorf(ctx, "[imported_transaction_data_table.Next] cannot parse data row, because may missing some columns (column count %d in data row is less than header column count %d)", importedRow.ColumnCount(), len(t.transactionDataTable.dataColumnIndexes))
|
||||
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||
}
|
||||
|
||||
rowData := make(map[TransactionDataTableColumn]string, len(t.transactionDataTable.dataColumnIndexes))
|
||||
rowDataValid := true
|
||||
|
||||
for column, columnIndex := range t.transactionDataTable.dataColumnIndexes {
|
||||
if columnIndex < 0 || columnIndex >= importedRow.ColumnCount() {
|
||||
continue
|
||||
}
|
||||
|
||||
value := importedRow.GetData(columnIndex)
|
||||
rowData[column] = value
|
||||
}
|
||||
|
||||
if t.transactionDataTable.rowParser != nil {
|
||||
rowData, rowDataValid, err = t.transactionDataTable.rowParser.Parse(rowData)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[imported_transaction_data_table.Next] cannot parse data row, because %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &ImportedTransactionDataRow{
|
||||
transactionDataTable: t.transactionDataTable,
|
||||
rowData: rowData,
|
||||
rowDataValid: rowDataValid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateImportedTransactionDataTable returns transaction data table from imported data table
|
||||
func CreateImportedTransactionDataTable(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string) *ImportedTransactionDataTable {
|
||||
return CreateImportedTransactionDataTableWithRowParser(dataTable, dataColumnMapping, nil)
|
||||
}
|
||||
|
||||
// CreateImportedTransactionDataTableWithRowParser returns transaction data table from imported data table
|
||||
func CreateImportedTransactionDataTableWithRowParser(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string, rowParser TransactionDataRowParser) *ImportedTransactionDataTable {
|
||||
headerLineItems := dataTable.HeaderColumnNames()
|
||||
headerItemMap := make(map[string]int, len(headerLineItems))
|
||||
|
||||
for i := 0; i < len(headerLineItems); i++ {
|
||||
headerItemMap[headerLineItems[i]] = i
|
||||
}
|
||||
|
||||
dataColumnIndexes := make(map[TransactionDataTableColumn]int, len(headerLineItems))
|
||||
|
||||
for column, columnName := range dataColumnMapping {
|
||||
columnIndex, exists := headerItemMap[columnName]
|
||||
|
||||
if exists {
|
||||
dataColumnIndexes[column] = columnIndex
|
||||
}
|
||||
}
|
||||
|
||||
var addedColumns map[TransactionDataTableColumn]any
|
||||
|
||||
if rowParser != nil {
|
||||
addedColumnsByParser := rowParser.GetAddedColumns()
|
||||
addedColumns = make(map[TransactionDataTableColumn]any, len(addedColumnsByParser))
|
||||
|
||||
for i := 0; i < len(addedColumnsByParser); i++ {
|
||||
addedColumns[addedColumnsByParser[i]] = true
|
||||
}
|
||||
}
|
||||
|
||||
return &ImportedTransactionDataTable{
|
||||
innerDataTable: dataTable,
|
||||
dataColumnMapping: dataColumnMapping,
|
||||
dataColumnIndexes: dataColumnIndexes,
|
||||
rowParser: rowParser,
|
||||
addedColumns: addedColumns,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
// TransactionDataTable defines the structure of transaction data table
|
||||
type TransactionDataTable interface {
|
||||
// HasColumn returns whether the transaction data table has specified column
|
||||
HasColumn(column TransactionDataTableColumn) bool
|
||||
|
||||
// TransactionRowCount returns the total count of transaction data row
|
||||
TransactionRowCount() int
|
||||
|
||||
// TransactionRowIterator returns the iterator of transaction data row
|
||||
TransactionRowIterator() TransactionDataRowIterator
|
||||
}
|
||||
|
||||
// TransactionDataRow defines the structure of transaction data row
|
||||
type TransactionDataRow interface {
|
||||
// IsValid returns whether this row is valid data for importing
|
||||
IsValid() bool
|
||||
|
||||
// GetData returns the data in the specified column type
|
||||
GetData(column TransactionDataTableColumn) string
|
||||
}
|
||||
|
||||
// TransactionDataRowIterator defines the structure of transaction data row iterator
|
||||
type TransactionDataRowIterator interface {
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
HasNext() bool
|
||||
|
||||
// Next returns the next transaction data row
|
||||
Next(ctx core.Context, user *models.User) (daraRow TransactionDataRow, err error)
|
||||
}
|
||||
|
||||
// TransactionDataRowParser defines the structure of transaction data row parser
|
||||
type TransactionDataRowParser interface {
|
||||
// GetAddedColumns returns the added columns after converting the data row
|
||||
GetAddedColumns() []TransactionDataTableColumn
|
||||
|
||||
// Parse returns the converted transaction data row
|
||||
Parse(data map[TransactionDataTableColumn]string) (rowData map[TransactionDataTableColumn]string, rowDataValid bool, err error)
|
||||
}
|
||||
|
||||
// TransactionDataTableBuilder defines the structure of data table builder
|
||||
type TransactionDataTableBuilder interface {
|
||||
// AppendTransaction appends the specified transaction to data builder
|
||||
AppendTransaction(data map[TransactionDataTableColumn]string)
|
||||
|
||||
// ReplaceDelimiters returns the text after removing the delimiters
|
||||
ReplaceDelimiters(text string) string
|
||||
}
|
||||
|
||||
// TransactionDataTableColumn represents the data column type of data table
|
||||
type TransactionDataTableColumn byte
|
||||
|
||||
// Transaction data table columns
|
||||
const (
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME TransactionDataTableColumn = 1
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE TransactionDataTableColumn = 2
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE TransactionDataTableColumn = 3
|
||||
TRANSACTION_DATA_TABLE_CATEGORY TransactionDataTableColumn = 4
|
||||
TRANSACTION_DATA_TABLE_SUB_CATEGORY TransactionDataTableColumn = 5
|
||||
TRANSACTION_DATA_TABLE_ACCOUNT_NAME TransactionDataTableColumn = 6
|
||||
TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY TransactionDataTableColumn = 7
|
||||
TRANSACTION_DATA_TABLE_AMOUNT TransactionDataTableColumn = 8
|
||||
TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME TransactionDataTableColumn = 9
|
||||
TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY TransactionDataTableColumn = 10
|
||||
TRANSACTION_DATA_TABLE_RELATED_AMOUNT TransactionDataTableColumn = 11
|
||||
TRANSACTION_DATA_TABLE_GEOGRAPHIC_LOCATION TransactionDataTableColumn = 12
|
||||
TRANSACTION_DATA_TABLE_TAGS TransactionDataTableColumn = 13
|
||||
TRANSACTION_DATA_TABLE_DESCRIPTION TransactionDataTableColumn = 14
|
||||
)
|
||||
@@ -1,152 +0,0 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// WritableDataTable defines the structure of writable data table
|
||||
type WritableDataTable struct {
|
||||
allData []map[DataTableColumn]string
|
||||
columns []DataTableColumn
|
||||
}
|
||||
|
||||
// WritableDataRow defines the structure of data row of writable data table
|
||||
type WritableDataRow struct {
|
||||
dataTable *WritableDataTable
|
||||
rowData map[DataTableColumn]string
|
||||
}
|
||||
|
||||
// WritableDataRowIterator defines the structure of data row iterator of writable data table
|
||||
type WritableDataRowIterator struct {
|
||||
dataTable *WritableDataTable
|
||||
nextIndex int
|
||||
}
|
||||
|
||||
// Add appends a new record to data table
|
||||
func (t *WritableDataTable) Add(data map[DataTableColumn]string) {
|
||||
finalData := make(map[DataTableColumn]string, len(data))
|
||||
|
||||
for i := 0; i < len(t.columns); i++ {
|
||||
column := t.columns[i]
|
||||
|
||||
if value, exists := data[column]; exists {
|
||||
finalData[column] = value
|
||||
}
|
||||
}
|
||||
|
||||
t.allData = append(t.allData, finalData)
|
||||
}
|
||||
|
||||
// Get returns the record in the specified index
|
||||
func (t *WritableDataTable) Get(index int) ImportedDataRow {
|
||||
if index >= len(t.allData) {
|
||||
return nil
|
||||
}
|
||||
|
||||
rowData := t.allData[index]
|
||||
|
||||
return &WritableDataRow{
|
||||
dataTable: t,
|
||||
rowData: rowData,
|
||||
}
|
||||
}
|
||||
|
||||
// DataRowCount returns the total count of data row
|
||||
func (t *WritableDataTable) DataRowCount() int {
|
||||
return len(t.allData)
|
||||
}
|
||||
|
||||
// GetDataColumnMapping returns data column map for data importer
|
||||
func (t *WritableDataTable) GetDataColumnMapping() map[DataTableColumn]string {
|
||||
dataColumnMapping := make(map[DataTableColumn]string, len(t.columns))
|
||||
|
||||
for i := 0; i < len(t.columns); i++ {
|
||||
column := t.columns[i]
|
||||
dataColumnMapping[column] = utils.IntToString(int(column))
|
||||
}
|
||||
|
||||
return dataColumnMapping
|
||||
}
|
||||
|
||||
// HeaderLineColumnNames returns the header column name list
|
||||
func (t *WritableDataTable) HeaderLineColumnNames() []string {
|
||||
columnIndexes := make([]string, len(t.columns))
|
||||
|
||||
for i := 0; i < len(t.columns); i++ {
|
||||
columnIndexes[i] = utils.IntToString(int(t.columns[i]))
|
||||
}
|
||||
|
||||
return columnIndexes
|
||||
}
|
||||
|
||||
// DataRowIterator returns the iterator of data row
|
||||
func (t *WritableDataTable) DataRowIterator() ImportedDataRowIterator {
|
||||
return &WritableDataRowIterator{
|
||||
dataTable: t,
|
||||
nextIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid returns whether this row contains valid data for importing
|
||||
func (r *WritableDataRow) IsValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
func (r *WritableDataRow) ColumnCount() int {
|
||||
return len(r.rowData)
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column index
|
||||
func (r *WritableDataRow) GetData(columnIndex int) string {
|
||||
if columnIndex >= len(r.dataTable.columns) {
|
||||
return ""
|
||||
}
|
||||
|
||||
dataColumn := r.dataTable.columns[columnIndex]
|
||||
|
||||
return r.rowData[dataColumn]
|
||||
}
|
||||
|
||||
// GetTime returns the time in the specified column index
|
||||
func (r *WritableDataRow) 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 *WritableDataRow) GetTimezoneOffset(columnIndex int) (*time.Location, error) {
|
||||
return utils.ParseFromTimezoneOffset(r.GetData(columnIndex))
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *WritableDataRowIterator) HasNext() bool {
|
||||
return t.nextIndex < len(t.dataTable.allData)
|
||||
}
|
||||
|
||||
// Next returns the next imported data row
|
||||
func (t *WritableDataRowIterator) Next(ctx core.Context, user *models.User) ImportedDataRow {
|
||||
if t.nextIndex >= len(t.dataTable.allData) {
|
||||
return nil
|
||||
}
|
||||
|
||||
rowData := t.dataTable.allData[t.nextIndex]
|
||||
|
||||
t.nextIndex++
|
||||
|
||||
return &WritableDataRow{
|
||||
dataTable: t.dataTable,
|
||||
rowData: rowData,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNewWritableDataTable returns a new writable data table according to the specified columns
|
||||
func CreateNewWritableDataTable(columns []DataTableColumn) *WritableDataTable {
|
||||
return &WritableDataTable{
|
||||
allData: make([]map[DataTableColumn]string, 0),
|
||||
columns: columns,
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestWritableDataTableAdd(t *testing.T) {
|
||||
columns := make([]DataTableColumn, 5)
|
||||
columns[0] = DATA_TABLE_TRANSACTION_TIME
|
||||
columns[1] = DATA_TABLE_TRANSACTION_TYPE
|
||||
columns[2] = DATA_TABLE_SUB_CATEGORY
|
||||
columns[3] = DATA_TABLE_ACCOUNT_NAME
|
||||
columns[4] = DATA_TABLE_AMOUNT
|
||||
|
||||
writableDataTable := CreateNewWritableDataTable(columns)
|
||||
|
||||
assert.Equal(t, 0, writableDataTable.DataRowCount())
|
||||
|
||||
expectedTransactionUnixTime := time.Now().Unix()
|
||||
expectedTextualTransactionTime := utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTime, time.Local)
|
||||
expectedTransactionType := "Expense"
|
||||
expectedSubCategory := "Test Category"
|
||||
expectedAccountName := "Test Account"
|
||||
expectedAmount := "123.45"
|
||||
|
||||
writableDataTable.Add(map[DataTableColumn]string{
|
||||
DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTime,
|
||||
DATA_TABLE_TRANSACTION_TYPE: expectedTransactionType,
|
||||
DATA_TABLE_SUB_CATEGORY: expectedSubCategory,
|
||||
DATA_TABLE_ACCOUNT_NAME: expectedAccountName,
|
||||
DATA_TABLE_AMOUNT: expectedAmount,
|
||||
})
|
||||
assert.Equal(t, 1, writableDataTable.DataRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
|
||||
actualTransactionTime, err := dataRow.GetTime(0, utils.GetTimezoneOffsetMinutes(time.Local))
|
||||
assert.Nil(t, err)
|
||||
|
||||
actualTransactionUnixTime := actualTransactionTime.Unix()
|
||||
assert.Equal(t, expectedTransactionUnixTime, actualTransactionUnixTime)
|
||||
|
||||
actualTextualTransactionTime := dataRow.GetData(0)
|
||||
assert.Equal(t, expectedTextualTransactionTime, actualTextualTransactionTime)
|
||||
|
||||
actualTransactionType := dataRow.GetData(1)
|
||||
assert.Equal(t, expectedTransactionType, actualTransactionType)
|
||||
|
||||
actualSubCategory := dataRow.GetData(2)
|
||||
assert.Equal(t, expectedSubCategory, actualSubCategory)
|
||||
|
||||
actualAccountName := dataRow.GetData(3)
|
||||
assert.Equal(t, expectedAccountName, actualAccountName)
|
||||
|
||||
actualAmount := dataRow.GetData(4)
|
||||
assert.Equal(t, expectedAmount, actualAmount)
|
||||
}
|
||||
|
||||
func TestWritableDataTableAdd_NotExistsColumn(t *testing.T) {
|
||||
columns := make([]DataTableColumn, 1)
|
||||
columns[0] = DATA_TABLE_TRANSACTION_TIME
|
||||
|
||||
writableDataTable := CreateNewWritableDataTable(columns)
|
||||
|
||||
expectedTransactionUnixTime := time.Now().Unix()
|
||||
expectedTextualTransactionTime := utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTime, time.Local)
|
||||
expectedTransactionType := "Expense"
|
||||
|
||||
writableDataTable.Add(map[DataTableColumn]string{
|
||||
DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTime,
|
||||
DATA_TABLE_TRANSACTION_TYPE: expectedTransactionType,
|
||||
})
|
||||
assert.Equal(t, 1, writableDataTable.DataRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
assert.Equal(t, 1, dataRow.ColumnCount())
|
||||
}
|
||||
|
||||
func TestWritableDataTableGet_NotExistsRow(t *testing.T) {
|
||||
columns := make([]DataTableColumn, 1)
|
||||
columns[0] = DATA_TABLE_TRANSACTION_TIME
|
||||
|
||||
writableDataTable := CreateNewWritableDataTable(columns)
|
||||
assert.Equal(t, 0, writableDataTable.DataRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
assert.Nil(t, dataRow)
|
||||
}
|
||||
|
||||
func TestWritableDataRowGetData_NotExistsColumn(t *testing.T) {
|
||||
columns := make([]DataTableColumn, 1)
|
||||
columns[0] = DATA_TABLE_TRANSACTION_TIME
|
||||
|
||||
writableDataTable := CreateNewWritableDataTable(columns)
|
||||
|
||||
expectedTransactionUnixTime := time.Now().Unix()
|
||||
expectedTextualTransactionTime := utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTime, time.Local)
|
||||
|
||||
writableDataTable.Add(map[DataTableColumn]string{
|
||||
DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTime,
|
||||
})
|
||||
assert.Equal(t, 1, writableDataTable.DataRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
assert.Equal(t, 1, dataRow.ColumnCount())
|
||||
assert.Equal(t, "", dataRow.GetData(1))
|
||||
}
|
||||
|
||||
func TestWritableDataTableDataRowIterator(t *testing.T) {
|
||||
columns := make([]DataTableColumn, 5)
|
||||
columns[0] = DATA_TABLE_TRANSACTION_TIME
|
||||
columns[1] = DATA_TABLE_TRANSACTION_TYPE
|
||||
columns[2] = DATA_TABLE_SUB_CATEGORY
|
||||
columns[3] = DATA_TABLE_ACCOUNT_NAME
|
||||
columns[4] = DATA_TABLE_AMOUNT
|
||||
|
||||
writableDataTable := CreateNewWritableDataTable(columns)
|
||||
assert.Equal(t, 0, writableDataTable.DataRowCount())
|
||||
|
||||
expectedTransactionUnixTimes := make([]int64, 3)
|
||||
expectedTextualTransactionTimes := make([]string, 3)
|
||||
expectedTransactionTypes := make([]string, 3)
|
||||
expectedSubCategories := make([]string, 3)
|
||||
expectedAccountNames := make([]string, 3)
|
||||
expectedAmounts := make([]string, 3)
|
||||
|
||||
expectedTransactionUnixTimes[0] = time.Now().Add(-5 * time.Hour).Unix()
|
||||
expectedTextualTransactionTimes[0] = utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTimes[0], time.Local)
|
||||
expectedTransactionTypes[0] = "Balance Modification"
|
||||
expectedSubCategories[0] = ""
|
||||
expectedAccountNames[0] = "Test Account"
|
||||
expectedAmounts[0] = "123.45"
|
||||
writableDataTable.Add(map[DataTableColumn]string{
|
||||
DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTimes[0],
|
||||
DATA_TABLE_TRANSACTION_TYPE: expectedTransactionTypes[0],
|
||||
DATA_TABLE_SUB_CATEGORY: expectedSubCategories[0],
|
||||
DATA_TABLE_ACCOUNT_NAME: expectedAccountNames[0],
|
||||
DATA_TABLE_AMOUNT: expectedAmounts[0],
|
||||
})
|
||||
|
||||
expectedTransactionUnixTimes[1] = time.Now().Add(-45 * time.Minute).Unix()
|
||||
expectedTextualTransactionTimes[1] = utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTimes[1], time.Local)
|
||||
expectedTransactionTypes[1] = "Expense"
|
||||
expectedSubCategories[1] = "Test Category2"
|
||||
expectedAccountNames[1] = "Test Account"
|
||||
expectedAmounts[1] = "-23.4"
|
||||
writableDataTable.Add(map[DataTableColumn]string{
|
||||
DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTimes[1],
|
||||
DATA_TABLE_TRANSACTION_TYPE: expectedTransactionTypes[1],
|
||||
DATA_TABLE_SUB_CATEGORY: expectedSubCategories[1],
|
||||
DATA_TABLE_ACCOUNT_NAME: expectedAccountNames[1],
|
||||
DATA_TABLE_AMOUNT: expectedAmounts[1],
|
||||
})
|
||||
|
||||
expectedTransactionUnixTimes[2] = time.Now().Unix()
|
||||
expectedTextualTransactionTimes[2] = utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTimes[2], time.Local)
|
||||
expectedTransactionTypes[2] = "Income"
|
||||
expectedSubCategories[2] = "Test Category3"
|
||||
expectedAccountNames[2] = "Test Account2"
|
||||
expectedAmounts[2] = "123"
|
||||
writableDataTable.Add(map[DataTableColumn]string{
|
||||
DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTimes[2],
|
||||
DATA_TABLE_TRANSACTION_TYPE: expectedTransactionTypes[2],
|
||||
DATA_TABLE_SUB_CATEGORY: expectedSubCategories[2],
|
||||
DATA_TABLE_ACCOUNT_NAME: expectedAccountNames[2],
|
||||
DATA_TABLE_AMOUNT: expectedAmounts[2],
|
||||
})
|
||||
assert.Equal(t, 3, writableDataTable.DataRowCount())
|
||||
|
||||
index := 0
|
||||
iterator := writableDataTable.DataRowIterator()
|
||||
|
||||
for iterator.HasNext() {
|
||||
dataRow := iterator.Next(core.NewNullContext(), &models.User{})
|
||||
|
||||
actualTransactionTime, err := dataRow.GetTime(0, utils.GetTimezoneOffsetMinutes(time.Local))
|
||||
assert.Nil(t, err)
|
||||
|
||||
actualTransactionUnixTime := actualTransactionTime.Unix()
|
||||
assert.Equal(t, expectedTransactionUnixTimes[index], actualTransactionUnixTime)
|
||||
|
||||
actualTextualTransactionTime := dataRow.GetData(0)
|
||||
assert.Equal(t, expectedTextualTransactionTimes[index], actualTextualTransactionTime)
|
||||
|
||||
actualTransactionType := dataRow.GetData(1)
|
||||
assert.Equal(t, expectedTransactionTypes[index], actualTransactionType)
|
||||
|
||||
actualSubCategory := dataRow.GetData(2)
|
||||
assert.Equal(t, expectedSubCategories[index], actualSubCategory)
|
||||
|
||||
actualAccountName := dataRow.GetData(3)
|
||||
assert.Equal(t, expectedAccountNames[index], actualAccountName)
|
||||
|
||||
actualAmount := dataRow.GetData(4)
|
||||
assert.Equal(t, expectedAmounts[index], actualAmount)
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
assert.Equal(t, 3, index)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package datatable
|
||||
|
||||
import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
// WritableTransactionDataTable defines the structure of writable transaction data table
|
||||
type WritableTransactionDataTable struct {
|
||||
allData []map[TransactionDataTableColumn]string
|
||||
supportedColumns map[TransactionDataTableColumn]any
|
||||
rowParser TransactionDataRowParser
|
||||
addedColumns map[TransactionDataTableColumn]any
|
||||
}
|
||||
|
||||
// WritableTransactionDataRow defines the structure of transaction data row of writable data table
|
||||
type WritableTransactionDataRow struct {
|
||||
dataTable *WritableTransactionDataTable
|
||||
rowData map[TransactionDataTableColumn]string
|
||||
rowDataValid bool
|
||||
}
|
||||
|
||||
// WritableTransactionDataRowIterator defines the structure of transaction data row iterator of writable data table
|
||||
type WritableTransactionDataRowIterator struct {
|
||||
dataTable *WritableTransactionDataTable
|
||||
nextIndex int
|
||||
}
|
||||
|
||||
// Add appends a new record to data table
|
||||
func (t *WritableTransactionDataTable) Add(data map[TransactionDataTableColumn]string) {
|
||||
finalData := make(map[TransactionDataTableColumn]string, len(data))
|
||||
|
||||
for column, value := range data {
|
||||
_, exists := t.supportedColumns[column]
|
||||
|
||||
if exists {
|
||||
finalData[column] = value
|
||||
}
|
||||
}
|
||||
|
||||
t.allData = append(t.allData, finalData)
|
||||
}
|
||||
|
||||
// Get returns the record in the specified index
|
||||
func (t *WritableTransactionDataTable) Get(index int) *WritableTransactionDataRow {
|
||||
if index >= len(t.allData) {
|
||||
return nil
|
||||
}
|
||||
|
||||
rowData := t.allData[index]
|
||||
|
||||
return &WritableTransactionDataRow{
|
||||
dataTable: t,
|
||||
rowData: rowData,
|
||||
}
|
||||
}
|
||||
|
||||
// HasColumn returns whether the data table has specified column
|
||||
func (t *WritableTransactionDataTable) HasColumn(column TransactionDataTableColumn) bool {
|
||||
_, exists := t.supportedColumns[column]
|
||||
|
||||
if exists {
|
||||
return exists
|
||||
}
|
||||
|
||||
if t.addedColumns != nil {
|
||||
_, exists = t.addedColumns[column]
|
||||
|
||||
if exists {
|
||||
return exists
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TransactionRowCount returns the total count of transaction data row
|
||||
func (t *WritableTransactionDataTable) TransactionRowCount() int {
|
||||
return len(t.allData)
|
||||
}
|
||||
|
||||
// TransactionRowIterator returns the iterator of transaction data row
|
||||
func (t *WritableTransactionDataTable) TransactionRowIterator() TransactionDataRowIterator {
|
||||
return &WritableTransactionDataRowIterator{
|
||||
dataTable: t,
|
||||
nextIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ColumnCount returns the total count of column in this data row
|
||||
func (r *WritableTransactionDataRow) ColumnCount() int {
|
||||
return len(r.rowData)
|
||||
}
|
||||
|
||||
// IsValid returns whether this row is valid data for importing
|
||||
func (r *WritableTransactionDataRow) IsValid() bool {
|
||||
return r.rowDataValid
|
||||
}
|
||||
|
||||
// GetData returns the data in the specified column type
|
||||
func (r *WritableTransactionDataRow) GetData(column TransactionDataTableColumn) string {
|
||||
return r.rowData[column]
|
||||
}
|
||||
|
||||
// HasNext returns whether the iterator does not reach the end
|
||||
func (t *WritableTransactionDataRowIterator) HasNext() bool {
|
||||
return t.nextIndex < len(t.dataTable.allData)
|
||||
}
|
||||
|
||||
// Next returns the next transaction data row
|
||||
func (t *WritableTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow TransactionDataRow, err error) {
|
||||
if t.nextIndex >= len(t.dataTable.allData) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rowData := t.dataTable.allData[t.nextIndex]
|
||||
rowDataValid := true
|
||||
|
||||
if t.dataTable.rowParser != nil {
|
||||
rowData, rowDataValid, err = t.dataTable.rowParser.Parse(rowData)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[writable_transaction_data_table.Next] cannot parse data row, because %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
t.nextIndex++
|
||||
|
||||
return &WritableTransactionDataRow{
|
||||
dataTable: t.dataTable,
|
||||
rowData: rowData,
|
||||
rowDataValid: rowDataValid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateNewWritableTransactionDataTable returns a new writable transaction data table according to the specified columns
|
||||
func CreateNewWritableTransactionDataTable(columns []TransactionDataTableColumn) *WritableTransactionDataTable {
|
||||
return CreateNewWritableTransactionDataTableWithRowParser(columns, nil)
|
||||
}
|
||||
|
||||
// CreateNewWritableTransactionDataTableWithRowParser returns a new writable transaction data table according to the specified columns
|
||||
func CreateNewWritableTransactionDataTableWithRowParser(columns []TransactionDataTableColumn, rowParser TransactionDataRowParser) *WritableTransactionDataTable {
|
||||
supportedColumns := make(map[TransactionDataTableColumn]any, len(columns))
|
||||
|
||||
for i := 0; i < len(columns); i++ {
|
||||
column := columns[i]
|
||||
supportedColumns[column] = true
|
||||
}
|
||||
|
||||
var addedColumns map[TransactionDataTableColumn]any
|
||||
|
||||
if rowParser != nil {
|
||||
addedColumnsByParser := rowParser.GetAddedColumns()
|
||||
addedColumns = make(map[TransactionDataTableColumn]any, len(addedColumnsByParser))
|
||||
|
||||
for i := 0; i < len(addedColumnsByParser); i++ {
|
||||
addedColumns[addedColumnsByParser[i]] = true
|
||||
}
|
||||
}
|
||||
|
||||
return &WritableTransactionDataTable{
|
||||
allData: make([]map[TransactionDataTableColumn]string, 0),
|
||||
supportedColumns: supportedColumns,
|
||||
rowParser: rowParser,
|
||||
addedColumns: addedColumns,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
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/utils"
|
||||
)
|
||||
|
||||
func TestWritableDataTableAdd(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())
|
||||
|
||||
expectedTransactionTime := "2024-09-01 01:23:45"
|
||||
expectedTransactionType := "Expense"
|
||||
expectedSubCategory := "Test Category"
|
||||
expectedAccountName := "Test Account"
|
||||
expectedAmount := "123.45"
|
||||
|
||||
writableDataTable.Add(map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: expectedTransactionTime,
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: expectedTransactionType,
|
||||
TRANSACTION_DATA_TABLE_SUB_CATEGORY: expectedSubCategory,
|
||||
TRANSACTION_DATA_TABLE_ACCOUNT_NAME: expectedAccountName,
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: expectedAmount,
|
||||
})
|
||||
assert.Equal(t, 1, writableDataTable.TransactionRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
|
||||
actualTransactionTime := dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME)
|
||||
assert.Equal(t, expectedTransactionTime, actualTransactionTime)
|
||||
|
||||
actualTransactionType := dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE)
|
||||
assert.Equal(t, expectedTransactionType, actualTransactionType)
|
||||
|
||||
actualSubCategory := dataRow.GetData(TRANSACTION_DATA_TABLE_SUB_CATEGORY)
|
||||
assert.Equal(t, expectedSubCategory, actualSubCategory)
|
||||
|
||||
actualAccountName := dataRow.GetData(TRANSACTION_DATA_TABLE_ACCOUNT_NAME)
|
||||
assert.Equal(t, expectedAccountName, actualAccountName)
|
||||
|
||||
actualAmount := dataRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT)
|
||||
assert.Equal(t, expectedAmount, actualAmount)
|
||||
}
|
||||
|
||||
func TestWritableDataTableAdd_NotExistsColumn(t *testing.T) {
|
||||
columns := make([]TransactionDataTableColumn, 1)
|
||||
columns[0] = TRANSACTION_DATA_TABLE_TRANSACTION_TIME
|
||||
|
||||
writableDataTable := CreateNewWritableTransactionDataTable(columns)
|
||||
|
||||
expectedTransactionUnixTime := time.Now().Unix()
|
||||
expectedTextualTransactionTime := utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTime, time.Local)
|
||||
expectedTransactionType := "Expense"
|
||||
|
||||
writableDataTable.Add(map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTime,
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: expectedTransactionType,
|
||||
})
|
||||
assert.Equal(t, 1, writableDataTable.TransactionRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
assert.Equal(t, 1, dataRow.ColumnCount())
|
||||
}
|
||||
|
||||
func TestWritableDataTableGet_NotExistsRow(t *testing.T) {
|
||||
columns := make([]TransactionDataTableColumn, 1)
|
||||
columns[0] = TRANSACTION_DATA_TABLE_TRANSACTION_TIME
|
||||
|
||||
writableDataTable := CreateNewWritableTransactionDataTable(columns)
|
||||
assert.Equal(t, 0, writableDataTable.TransactionRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
assert.Nil(t, dataRow)
|
||||
}
|
||||
|
||||
func TestWritableDataRowGetData_NotExistsColumn(t *testing.T) {
|
||||
columns := make([]TransactionDataTableColumn, 1)
|
||||
columns[0] = TRANSACTION_DATA_TABLE_TRANSACTION_TIME
|
||||
|
||||
writableDataTable := CreateNewWritableTransactionDataTable(columns)
|
||||
|
||||
expectedTransactionUnixTime := time.Now().Unix()
|
||||
expectedTextualTransactionTime := utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTime, time.Local)
|
||||
|
||||
writableDataTable.Add(map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: expectedTextualTransactionTime,
|
||||
})
|
||||
assert.Equal(t, 1, writableDataTable.TransactionRowCount())
|
||||
|
||||
dataRow := writableDataTable.Get(0)
|
||||
assert.Equal(t, 1, dataRow.ColumnCount())
|
||||
assert.Equal(t, "", dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE))
|
||||
}
|
||||
|
||||
func TestWritableDataTableDataRowIterator(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())
|
||||
|
||||
expectedTransactionUnixTimes := make([]int64, 3)
|
||||
expectedTransactionTimes := make([]string, 3)
|
||||
expectedTransactionTypes := make([]string, 3)
|
||||
expectedSubCategories := make([]string, 3)
|
||||
expectedAccountNames := make([]string, 3)
|
||||
expectedAmounts := make([]string, 3)
|
||||
|
||||
expectedTransactionUnixTimes[0] = time.Now().Add(-5 * time.Hour).Unix()
|
||||
expectedTransactionTimes[0] = utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTimes[0], time.Local)
|
||||
expectedTransactionTypes[0] = "Balance Modification"
|
||||
expectedSubCategories[0] = ""
|
||||
expectedAccountNames[0] = "Test Account"
|
||||
expectedAmounts[0] = "123.45"
|
||||
writableDataTable.Add(map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: expectedTransactionTimes[0],
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: expectedTransactionTypes[0],
|
||||
TRANSACTION_DATA_TABLE_SUB_CATEGORY: expectedSubCategories[0],
|
||||
TRANSACTION_DATA_TABLE_ACCOUNT_NAME: expectedAccountNames[0],
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: expectedAmounts[0],
|
||||
})
|
||||
|
||||
expectedTransactionUnixTimes[1] = time.Now().Add(-45 * time.Minute).Unix()
|
||||
expectedTransactionTimes[1] = utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTimes[1], time.Local)
|
||||
expectedTransactionTypes[1] = "Expense"
|
||||
expectedSubCategories[1] = "Test Category2"
|
||||
expectedAccountNames[1] = "Test Account"
|
||||
expectedAmounts[1] = "-23.4"
|
||||
writableDataTable.Add(map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: expectedTransactionTimes[1],
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: expectedTransactionTypes[1],
|
||||
TRANSACTION_DATA_TABLE_SUB_CATEGORY: expectedSubCategories[1],
|
||||
TRANSACTION_DATA_TABLE_ACCOUNT_NAME: expectedAccountNames[1],
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: expectedAmounts[1],
|
||||
})
|
||||
|
||||
expectedTransactionUnixTimes[2] = time.Now().Unix()
|
||||
expectedTransactionTimes[2] = utils.FormatUnixTimeToLongDateTime(expectedTransactionUnixTimes[2], time.Local)
|
||||
expectedTransactionTypes[2] = "Income"
|
||||
expectedSubCategories[2] = "Test Category3"
|
||||
expectedAccountNames[2] = "Test Account2"
|
||||
expectedAmounts[2] = "123"
|
||||
writableDataTable.Add(map[TransactionDataTableColumn]string{
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TIME: expectedTransactionTimes[2],
|
||||
TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: expectedTransactionTypes[2],
|
||||
TRANSACTION_DATA_TABLE_SUB_CATEGORY: expectedSubCategories[2],
|
||||
TRANSACTION_DATA_TABLE_ACCOUNT_NAME: expectedAccountNames[2],
|
||||
TRANSACTION_DATA_TABLE_AMOUNT: expectedAmounts[2],
|
||||
})
|
||||
assert.Equal(t, 3, writableDataTable.TransactionRowCount())
|
||||
|
||||
index := 0
|
||||
iterator := writableDataTable.TransactionRowIterator()
|
||||
|
||||
for iterator.HasNext() {
|
||||
dataRow, err := iterator.Next(core.NewNullContext(), &models.User{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
actualTransactionTime := dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TIME)
|
||||
assert.Equal(t, expectedTransactionTimes[index], actualTransactionTime)
|
||||
|
||||
actualTransactionType := dataRow.GetData(TRANSACTION_DATA_TABLE_TRANSACTION_TYPE)
|
||||
assert.Equal(t, expectedTransactionTypes[index], actualTransactionType)
|
||||
|
||||
actualSubCategory := dataRow.GetData(TRANSACTION_DATA_TABLE_SUB_CATEGORY)
|
||||
assert.Equal(t, expectedSubCategories[index], actualSubCategory)
|
||||
|
||||
actualAccountName := dataRow.GetData(TRANSACTION_DATA_TABLE_ACCOUNT_NAME)
|
||||
assert.Equal(t, expectedAccountNames[index], actualAccountName)
|
||||
|
||||
actualAmount := dataRow.GetData(TRANSACTION_DATA_TABLE_AMOUNT)
|
||||
assert.Equal(t, expectedAmounts[index], actualAmount)
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
assert.Equal(t, 3, index)
|
||||
}
|
||||
Reference in New Issue
Block a user