From 7bc9a0357ef2d5ab1ddb32f18ee654a7c36eb511 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Thu, 10 Oct 2024 23:53:11 +0800 Subject: [PATCH] code refactor --- ..._app_transaction_data_csv_file_importer.go | 24 +- ...ipay_transaction_data_csv_file_importer.go | 351 +------------ ..._transaction_data_plain_text_data_table.go | 466 ++++++++++++++++++ ..._web_transaction_data_csv_file_importer.go | 26 +- pkg/converters/datatable/data_table.go | 12 +- .../data_table_transaction_data_converter.go | 20 +- .../datatable/writable_data_table.go | 9 +- .../datatable/writable_data_table_test.go | 4 +- ..._transaction_data_plain_text_data_table.go | 9 +- ..._transaction_data_excel_file_data_table.go | 9 +- 10 files changed, 564 insertions(+), 366 deletions(-) create mode 100644 pkg/converters/alipay/alipay_transaction_data_plain_text_data_table.go diff --git a/pkg/converters/alipay/alipay_app_transaction_data_csv_file_importer.go b/pkg/converters/alipay/alipay_app_transaction_data_csv_file_importer.go index aa80e134..975d2400 100644 --- a/pkg/converters/alipay/alipay_app_transaction_data_csv_file_importer.go +++ b/pkg/converters/alipay/alipay_app_transaction_data_csv_file_importer.go @@ -9,17 +9,19 @@ type alipayAppTransactionDataCsvImporter struct { var ( AlipayAppTransactionDataCsvImporter = &alipayAppTransactionDataCsvImporter{ alipayTransactionDataCsvImporter{ - fileHeaderLine: "------------------------------------------------------------------------------------", - dataHeaderStartContent: "支付宝(中国)网络技术有限公司 电子客户回单", - timeColumnName: "交易时间", - categoryColumnName: "交易分类", - targetNameColumnName: "交易对方", - productNameColumnName: "商品说明", - amountColumnName: "金额", - typeColumnName: "收/支", - relatedAccountColumnName: "收/付款方式", - statusColumnName: "交易状态", - descriptionColumnName: "备注", + fileHeaderLine: "------------------------------------------------------------------------------------", + dataHeaderStartContent: "支付宝(中国)网络技术有限公司 电子客户回单", + originalColumnNames: alipayTransactionColumnNames{ + timeColumnName: "交易时间", + categoryColumnName: "交易分类", + targetNameColumnName: "交易对方", + productNameColumnName: "商品说明", + amountColumnName: "金额", + typeColumnName: "收/支", + relatedAccountColumnName: "收/付款方式", + statusColumnName: "交易状态", + descriptionColumnName: "备注", + }, }, } ) diff --git a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go b/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go index bf82029e..19d966d4 100644 --- a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go +++ b/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go @@ -2,36 +2,15 @@ package alipay import ( "bytes" - "encoding/csv" - "fmt" - "io" - "strings" "golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/transform" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable" "github.com/mayswind/ezbookkeeping/pkg/core" - "github.com/mayswind/ezbookkeeping/pkg/errs" - "github.com/mayswind/ezbookkeeping/pkg/locales" - "github.com/mayswind/ezbookkeeping/pkg/log" "github.com/mayswind/ezbookkeeping/pkg/models" - "github.com/mayswind/ezbookkeeping/pkg/utils" ) -const alipayTransactionDataStatusSuccessName = "交易成功" -const alipayTransactionDataStatusPaymentSuccessName = "支付成功" -const alipayTransactionDataStatusRepaymentSuccessName = "还款成功" -const alipayTransactionDataStatusClosedName = "交易关闭" -const alipayTransactionDataStatusRefundSuccessName = "退款成功" -const alipayTransactionDataStatusTaxRefundSuccessName = "退税成功" - -const alipayTransactionDataProductNameRechargePrefix = "充值-" -const alipayTransactionDataProductNameCashWithdrawalPrefix = "提现-" -const alipayTransactionDataProductNameTransferInText = "转入" -const alipayTransactionDataProductNameTransferOutText = "转出" -const alipayTransactionDataProductNameRepaymentText = "还款" - var alipayTransactionTypeNameMapping = map[models.TransactionType]string{ models.TRANSACTION_TYPE_INCOME: "收入", models.TRANSACTION_TYPE_EXPENSE: "支出", @@ -40,334 +19,34 @@ var alipayTransactionTypeNameMapping = map[models.TransactionType]string{ // alipayTransactionDataCsvImporter defines the structure of alipay csv importer for transaction data type alipayTransactionDataCsvImporter struct { - fileHeaderLine string - dataHeaderStartContent string - dataBottomEndLineRune rune - timeColumnName string - categoryColumnName string - targetNameColumnName string - productNameColumnName string - amountColumnName string - typeColumnName string - relatedAccountColumnName string - statusColumnName string - descriptionColumnName string + fileHeaderLine string + dataHeaderStartContent string + dataBottomEndLineRune rune + originalColumnNames alipayTransactionColumnNames } // ParseImportedData returns the imported data by parsing the alipay transaction csv data func (c *alipayTransactionDataCsvImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, 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) { enc := simplifiedchinese.GB18030 reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder()) - allLines, err := c.parseAllLinesFromCsvData(ctx, reader) + + dataTable, err := createNewAlipayTransactionPlainTextDataTable( + ctx, + reader, + c.fileHeaderLine, + c.dataHeaderStartContent, + c.dataBottomEndLineRune, + c.originalColumnNames, + ) if err != nil { return nil, nil, nil, nil, nil, nil, err } - if len(allLines) <= 1 { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.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 - } - - headerLineItems := allLines[0] - headerItemMap := make(map[string]int) - - for i := 0; i < len(headerLineItems); i++ { - headerItemMap[headerLineItems[i]] = i - } - - timeColumnIdx, timeColumnExists := headerItemMap[c.timeColumnName] - categoryColumnIdx, categoryColumnExists := headerItemMap[c.categoryColumnName] - targetNameColumnIdx, targetNameColumnExists := headerItemMap[c.targetNameColumnName] - productNameColumnIdx, productNameColumnExists := headerItemMap[c.productNameColumnName] - amountColumnIdx, amountColumnExists := headerItemMap[c.amountColumnName] - typeColumnIdx, typeColumnExists := headerItemMap[c.typeColumnName] - relatedAccountColumnIdx, relatedAccountColumnExists := headerItemMap[c.relatedAccountColumnName] - statusColumnIdx, statusColumnExists := headerItemMap[c.statusColumnName] - descriptionColumnIdx, descriptionColumnExists := headerItemMap[c.descriptionColumnName] - - if !timeColumnExists || !amountColumnExists || !typeColumnExists || !statusColumnExists { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.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 - } - - newColumns := make([]datatable.DataTableColumn, 0, 7) - newColumns = append(newColumns, datatable.DATA_TABLE_TRANSACTION_TYPE) - newColumns = append(newColumns, datatable.DATA_TABLE_TRANSACTION_TIME) - newColumns = append(newColumns, datatable.DATA_TABLE_SUB_CATEGORY) - newColumns = append(newColumns, datatable.DATA_TABLE_ACCOUNT_NAME) - newColumns = append(newColumns, datatable.DATA_TABLE_AMOUNT) - newColumns = append(newColumns, datatable.DATA_TABLE_RELATED_ACCOUNT_NAME) - newColumns = append(newColumns, datatable.DATA_TABLE_DESCRIPTION) - - dataTable := datatable.CreateNewWritableDataTable(newColumns) - - for i := 1; i < len(allLines); i++ { - items := allLines[i] - - if len(items) < len(headerLineItems) { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] cannot parse 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)", i, user.Uid, len(items), len(headerLineItems)) - return nil, nil, nil, nil, nil, nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow - } - - if items[typeColumnIdx] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] && - items[typeColumnIdx] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] && - items[typeColumnIdx] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] { - log.Warnf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] skip parsing transaction in row \"index:%d\" for user \"uid:%d\", because type is \"%s\"", i, user.Uid, items[typeColumnIdx]) - continue - } - - if items[statusColumnIdx] != alipayTransactionDataStatusSuccessName && - items[statusColumnIdx] != alipayTransactionDataStatusPaymentSuccessName && - items[statusColumnIdx] != alipayTransactionDataStatusRepaymentSuccessName && - items[statusColumnIdx] != alipayTransactionDataStatusClosedName && - items[statusColumnIdx] != alipayTransactionDataStatusRefundSuccessName && - items[statusColumnIdx] != alipayTransactionDataStatusTaxRefundSuccessName { - log.Warnf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] skip parsing transaction in row \"index:%d\" for user \"uid:%d\", because status is \"%s\"", i, user.Uid, items[statusColumnIdx]) - continue - } - - data, errMsg := c.parseTransactionData(ctx, - user, - items, - timeColumnIdx, - timeColumnExists, - categoryColumnIdx, - categoryColumnExists, - targetNameColumnIdx, - targetNameColumnExists, - productNameColumnIdx, - productNameColumnExists, - amountColumnIdx, - amountColumnExists, - typeColumnIdx, - typeColumnExists, - relatedAccountColumnIdx, - relatedAccountColumnExists, - statusColumnIdx, - statusColumnExists, - descriptionColumnIdx, - descriptionColumnExists, - ) - - if data == nil { - log.Warnf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] skip parsing transaction in row \"index:%d\" for user \"uid:%d\", because %s", i, user.Uid, errMsg) - continue - } - - dataTable.Add(data) - } - - dataTableImporter := datatable.CreateNewSimpleImporterFromWritableDataTable( - dataTable, + dataTableImporter := datatable.CreateNewSimpleImporter( + dataTable.GetDataColumnMapping(), alipayTransactionTypeNameMapping, ) return dataTableImporter.ParseImportedData(ctx, user, dataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) } - -func (c *alipayTransactionDataCsvImporter) parseAllLinesFromCsvData(ctx core.Context, reader io.Reader) ([][]string, error) { - csvReader := csv.NewReader(reader) - csvReader.FieldsPerRecord = -1 - - allLines := make([][]string, 0) - hasFileHeader := false - foundContentBeforeDataHeaderLine := false - - for { - items, err := csvReader.Read() - - if err == io.EOF { - break - } - - if err != nil { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.parseAllLinesFromCsvData] cannot parse alipay csv data, because %s", err.Error()) - return nil, errs.ErrInvalidCSVFile - } - - if !hasFileHeader { - if len(items) <= 0 { - continue - } else if strings.Index(items[0], c.fileHeaderLine) == 0 { - hasFileHeader = true - continue - } else { - log.Warnf(ctx, "[alipayTransactionDataCsvImporter.parseAllLinesFromCsvData] read unexpected line before read file header, line content is %s", strings.Join(items, ",")) - } - } - - if !foundContentBeforeDataHeaderLine { - if len(items) <= 0 { - continue - } else if strings.Index(items[0], c.dataHeaderStartContent) >= 0 { - foundContentBeforeDataHeaderLine = true - continue - } else { - continue - } - } - - if foundContentBeforeDataHeaderLine { - if len(items) <= 0 { - continue - } else if len(items) == 1 && c.dataBottomEndLineRune > 0 && utils.ContainsOnlyOneRune(items[0], c.dataBottomEndLineRune) { - break - } - - for i := 0; i < len(items); i++ { - items[i] = strings.Trim(items[i], " ") - } - - allLines = append(allLines, items) - } - } - - if !hasFileHeader || !foundContentBeforeDataHeaderLine { - return nil, errs.ErrInvalidFileHeader - } - - return allLines, nil -} - -func (c *alipayTransactionDataCsvImporter) parseTransactionData( - ctx core.Context, - user *models.User, - items []string, - timeColumnIdx int, - timeColumnExists bool, - categoryColumnIdx int, - categoryColumnExists bool, - targetNameColumnIdx int, - targetNameColumnExists bool, - productNameColumnIdx int, - productNameColumnExists bool, - amountColumnIdx int, - amountColumnExists bool, - typeColumnIdx int, - typeColumnExists bool, - relatedAccountColumnIdx int, - relatedAccountColumnExists bool, - statusColumnIdx int, - statusColumnExists bool, - descriptionColumnIdx int, - descriptionColumnExists bool, -) (map[datatable.DataTableColumn]string, string) { - data := make(map[datatable.DataTableColumn]string, 11) - - if timeColumnExists && timeColumnIdx < len(items) { - data[datatable.DATA_TABLE_TRANSACTION_TIME] = items[timeColumnIdx] - } - - if categoryColumnExists && categoryColumnIdx < len(items) { - data[datatable.DATA_TABLE_SUB_CATEGORY] = items[categoryColumnIdx] - } else { - data[datatable.DATA_TABLE_SUB_CATEGORY] = "" - } - - if amountColumnExists && amountColumnIdx < len(items) { - data[datatable.DATA_TABLE_AMOUNT] = items[amountColumnIdx] - } - - if descriptionColumnExists && descriptionColumnIdx < len(items) && items[descriptionColumnIdx] != "" { - data[datatable.DATA_TABLE_DESCRIPTION] = items[descriptionColumnIdx] - } else if productNameColumnExists && productNameColumnIdx < len(items) && items[productNameColumnIdx] != "" { - data[datatable.DATA_TABLE_DESCRIPTION] = items[productNameColumnIdx] - } else { - data[datatable.DATA_TABLE_DESCRIPTION] = "" - } - - relatedAccountName := "" - - if relatedAccountColumnExists && relatedAccountColumnIdx < len(items) { - relatedAccountName = items[relatedAccountColumnIdx] - } - - statusName := "" - - if statusColumnExists && statusColumnIdx < len(items) { - statusName = items[statusColumnIdx] - } - - locale := user.Language - - if locale == "" { - locale = ctx.GetClientLocale() - } - - localeTextItems := locales.GetLocaleTextItems(locale) - - if typeColumnExists && typeColumnIdx < len(items) { - data[datatable.DATA_TABLE_TRANSACTION_TYPE] = items[typeColumnIdx] - - if items[typeColumnIdx] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] { - if statusName == alipayTransactionDataStatusClosedName { - return nil, fmt.Sprintf("income transaction is closed") - } - - if statusName == alipayTransactionDataStatusSuccessName { - data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } else { - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } - } else if items[typeColumnIdx] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] { - if statusName == alipayTransactionDataStatusClosedName { - return nil, fmt.Sprintf("non-income/expense transaction is closed") - } - - targetName := "" - productName := "" - - if targetNameColumnExists && targetNameColumnIdx < len(items) { - targetName = items[targetNameColumnIdx] - } - - if productNameColumnExists && productNameColumnIdx < len(items) { - productName = items[productNameColumnIdx] - } - - if statusName == alipayTransactionDataStatusRefundSuccessName { - data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] - data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } else { - if strings.Index(productName, alipayTransactionDataProductNameRechargePrefix) == 0 { // transfer to alipay wallet - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay - } else if strings.Index(productName, alipayTransactionDataProductNameCashWithdrawalPrefix) == 0 { // transfer from alipay wallet - data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else if strings.Index(productName, alipayTransactionDataProductNameTransferInText) >= 0 { // transfer in - data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else if strings.Index(productName, alipayTransactionDataProductNameTransferOutText) >= 0 { // transfer out - data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else if strings.Index(productName, alipayTransactionDataProductNameRepaymentText) >= 0 { // repayment - data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else { - return nil, fmt.Sprintf("product name (\"%s\") is unknown", productName) - } - } - } else { - data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } - } - - if data[datatable.DATA_TABLE_TRANSACTION_TYPE] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] && statusName != "" { - if statusName == alipayTransactionDataStatusRefundSuccessName || statusName == alipayTransactionDataStatusTaxRefundSuccessName { - amount, err := utils.ParseAmount(data[datatable.DATA_TABLE_AMOUNT]) - - if err == nil { - data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] - data[datatable.DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount) - } - } - } - - return data, "" -} diff --git a/pkg/converters/alipay/alipay_transaction_data_plain_text_data_table.go b/pkg/converters/alipay/alipay_transaction_data_plain_text_data_table.go new file mode 100644 index 00000000..aec464d3 --- /dev/null +++ b/pkg/converters/alipay/alipay_transaction_data_plain_text_data_table.go @@ -0,0 +1,466 @@ +package alipay + +import ( + "encoding/csv" + "fmt" + "io" + "strings" + "time" + + "github.com/mayswind/ezbookkeeping/pkg/converters/datatable" + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/locales" + "github.com/mayswind/ezbookkeeping/pkg/log" + "github.com/mayswind/ezbookkeeping/pkg/models" + "github.com/mayswind/ezbookkeeping/pkg/utils" +) + +const alipayTransactionDataStatusSuccessName = "交易成功" +const alipayTransactionDataStatusPaymentSuccessName = "支付成功" +const alipayTransactionDataStatusRepaymentSuccessName = "还款成功" +const alipayTransactionDataStatusClosedName = "交易关闭" +const alipayTransactionDataStatusRefundSuccessName = "退款成功" +const alipayTransactionDataStatusTaxRefundSuccessName = "退税成功" + +const alipayTransactionDataProductNameRechargePrefix = "充值-" +const alipayTransactionDataProductNameCashWithdrawalPrefix = "提现-" +const alipayTransactionDataProductNameTransferInText = "转入" +const alipayTransactionDataProductNameTransferOutText = "转出" +const alipayTransactionDataProductNameRepaymentText = "还款" + +var alipayTransactionSupportedColumns = []datatable.DataTableColumn{ + datatable.DATA_TABLE_TRANSACTION_TYPE, + datatable.DATA_TABLE_TRANSACTION_TIME, + datatable.DATA_TABLE_SUB_CATEGORY, + datatable.DATA_TABLE_ACCOUNT_NAME, + datatable.DATA_TABLE_AMOUNT, + datatable.DATA_TABLE_RELATED_ACCOUNT_NAME, + datatable.DATA_TABLE_DESCRIPTION, +} + +// alipayTransactionColumnNames defines the structure of alipay transaction plain text header names +type alipayTransactionColumnNames struct { + timeColumnName string + categoryColumnName string + targetNameColumnName string + productNameColumnName string + amountColumnName string + typeColumnName string + relatedAccountColumnName string + statusColumnName string + descriptionColumnName string +} + +// alipayTransactionPlainTextDataTable defines the structure of alipay transaction plain text data table +type alipayTransactionPlainTextDataTable struct { + allOriginalLines [][]string + originalHeaderLineColumnNames []string + originalTimeColumnIndex int + originalCategoryColumnIndex int + originalTargetNameColumnIndex int + originalProductNameColumnIndex int + originalAmountColumnIndex int + originalTypeColumnIndex int + originalRelatedAccountColumnIndex int + originalStatusColumnIndex int + originalDescriptionColumnIndex int +} + +// alipayTransactionPlainTextDataRow defines the structure of alipay transaction plain text data row +type alipayTransactionPlainTextDataRow struct { + dataTable *alipayTransactionPlainTextDataTable + isValid bool + originalItems []string + finalItems map[datatable.DataTableColumn]string +} + +// alipayTransactionPlainTextDataRowIterator defines the structure of alipay transaction plain text data row iterator +type alipayTransactionPlainTextDataRowIterator struct { + dataTable *alipayTransactionPlainTextDataTable + currentIndex int +} + +// DataRowCount returns the total count of data row +func (t *alipayTransactionPlainTextDataTable) DataRowCount() int { + if len(t.allOriginalLines) < 1 { + return 0 + } + + return len(t.allOriginalLines) - 1 +} + +// GetDataColumnMapping returns data column map for data importer +func (t *alipayTransactionPlainTextDataTable) GetDataColumnMapping() map[datatable.DataTableColumn]string { + dataColumnMapping := make(map[datatable.DataTableColumn]string, len(alipayTransactionSupportedColumns)) + + for i := 0; i < len(alipayTransactionSupportedColumns); i++ { + column := alipayTransactionSupportedColumns[i] + dataColumnMapping[column] = utils.IntToString(int(column)) + } + + return dataColumnMapping +} + +// HeaderLineColumnNames returns the header column name list +func (t *alipayTransactionPlainTextDataTable) HeaderLineColumnNames() []string { + columnIndexes := make([]string, len(alipayTransactionSupportedColumns)) + + for i := 0; i < len(alipayTransactionSupportedColumns); i++ { + columnIndexes[i] = utils.IntToString(int(alipayTransactionSupportedColumns[i])) + } + + return columnIndexes +} + +// DataRowIterator returns the iterator of data row +func (t *alipayTransactionPlainTextDataTable) DataRowIterator() datatable.ImportedDataRowIterator { + return &alipayTransactionPlainTextDataRowIterator{ + dataTable: t, + currentIndex: 0, + } +} + +// IsValid returns whether this row contains valid data for importing +func (r *alipayTransactionPlainTextDataRow) IsValid() bool { + return r.isValid +} + +// ColumnCount returns the total count of column in this data row +func (r *alipayTransactionPlainTextDataRow) ColumnCount() int { + return len(r.finalItems) +} + +// GetData returns the data in the specified column index +func (r *alipayTransactionPlainTextDataRow) GetData(columnIndex int) string { + if columnIndex >= len(alipayTransactionSupportedColumns) { + return "" + } + + dataColumn := alipayTransactionSupportedColumns[columnIndex] + + return r.finalItems[dataColumn] +} + +// GetTime returns the time in the specified column index +func (r *alipayTransactionPlainTextDataRow) 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 *alipayTransactionPlainTextDataRow) GetTimezoneOffset(columnIndex int) (*time.Location, error) { + return nil, errs.ErrNotSupported +} + +// HasNext returns whether the iterator does not reach the end +func (t *alipayTransactionPlainTextDataRowIterator) HasNext() bool { + return t.currentIndex+1 < len(t.dataTable.allOriginalLines) +} + +// Next returns the next imported data row +func (t *alipayTransactionPlainTextDataRowIterator) Next(ctx core.Context, user *models.User) datatable.ImportedDataRow { + if t.currentIndex+1 >= len(t.dataTable.allOriginalLines) { + return nil + } + + t.currentIndex++ + + rowItems := t.dataTable.allOriginalLines[t.currentIndex] + isValid := true + + if t.dataTable.originalTypeColumnIndex >= 0 && + rowItems[t.dataTable.originalTypeColumnIndex] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] && + rowItems[t.dataTable.originalTypeColumnIndex] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] && + rowItems[t.dataTable.originalTypeColumnIndex] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] { + log.Warnf(ctx, "[alipay_transaction_data_plain_text_data_table.Next] skip parsing transaction in row \"index:%d\", because type is \"%s\"", t.currentIndex, rowItems[t.dataTable.originalTypeColumnIndex]) + isValid = false + } + + if t.dataTable.originalStatusColumnIndex >= 0 && + rowItems[t.dataTable.originalStatusColumnIndex] != alipayTransactionDataStatusSuccessName && + rowItems[t.dataTable.originalStatusColumnIndex] != alipayTransactionDataStatusPaymentSuccessName && + rowItems[t.dataTable.originalStatusColumnIndex] != alipayTransactionDataStatusRepaymentSuccessName && + rowItems[t.dataTable.originalStatusColumnIndex] != alipayTransactionDataStatusClosedName && + rowItems[t.dataTable.originalStatusColumnIndex] != alipayTransactionDataStatusRefundSuccessName && + rowItems[t.dataTable.originalStatusColumnIndex] != alipayTransactionDataStatusTaxRefundSuccessName { + log.Warnf(ctx, "[alipay_transaction_data_plain_text_data_table.Next] skip parsing transaction in row \"index:%d\", because status is \"%s\"", t.currentIndex, rowItems[t.dataTable.originalStatusColumnIndex]) + isValid = false + } + + var finalItems map[datatable.DataTableColumn]string + var errMsg string + + if isValid { + finalItems, errMsg = t.dataTable.parseTransactionData(ctx, user, rowItems) + + if finalItems == nil { + log.Warnf(ctx, "[alipay_transaction_data_plain_text_data_table.Next] skip parsing transaction in row \"index:%d\", because %s", t.currentIndex, errMsg) + isValid = false + } + } + + return &alipayTransactionPlainTextDataRow{ + dataTable: t.dataTable, + isValid: isValid, + originalItems: rowItems, + finalItems: finalItems, + } +} + +func (t *alipayTransactionPlainTextDataTable) parseTransactionData(ctx core.Context, user *models.User, items []string) (map[datatable.DataTableColumn]string, string) { + data := make(map[datatable.DataTableColumn]string, 7) + + if t.originalTimeColumnIndex >= 0 && t.originalTimeColumnIndex < len(items) { + data[datatable.DATA_TABLE_TRANSACTION_TIME] = items[t.originalTimeColumnIndex] + } + + if t.originalCategoryColumnIndex >= 0 && t.originalCategoryColumnIndex < len(items) { + data[datatable.DATA_TABLE_SUB_CATEGORY] = items[t.originalCategoryColumnIndex] + } else { + data[datatable.DATA_TABLE_SUB_CATEGORY] = "" + } + + if t.originalAmountColumnIndex >= 0 && t.originalAmountColumnIndex < len(items) { + data[datatable.DATA_TABLE_AMOUNT] = items[t.originalAmountColumnIndex] + } + + if t.originalDescriptionColumnIndex >= 0 && t.originalDescriptionColumnIndex < len(items) && items[t.originalDescriptionColumnIndex] != "" { + data[datatable.DATA_TABLE_DESCRIPTION] = items[t.originalDescriptionColumnIndex] + } else if t.originalProductNameColumnIndex >= 0 && t.originalProductNameColumnIndex < len(items) && items[t.originalProductNameColumnIndex] != "" { + data[datatable.DATA_TABLE_DESCRIPTION] = items[t.originalProductNameColumnIndex] + } else { + data[datatable.DATA_TABLE_DESCRIPTION] = "" + } + + relatedAccountName := "" + + if t.originalRelatedAccountColumnIndex >= 0 && t.originalRelatedAccountColumnIndex < len(items) { + relatedAccountName = items[t.originalRelatedAccountColumnIndex] + } + + statusName := "" + + if t.originalStatusColumnIndex >= 0 && t.originalStatusColumnIndex < len(items) { + statusName = items[t.originalStatusColumnIndex] + } + + locale := user.Language + + if locale == "" { + locale = ctx.GetClientLocale() + } + + localeTextItems := locales.GetLocaleTextItems(locale) + + if t.originalTypeColumnIndex >= 0 && t.originalTypeColumnIndex < len(items) { + data[datatable.DATA_TABLE_TRANSACTION_TYPE] = items[t.originalTypeColumnIndex] + + if items[t.originalTypeColumnIndex] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] { + if statusName == alipayTransactionDataStatusClosedName { + return nil, fmt.Sprintf("income transaction is closed") + } + + if statusName == alipayTransactionDataStatusSuccessName { + data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } else { + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } + } else if items[t.originalTypeColumnIndex] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] { + if statusName == alipayTransactionDataStatusClosedName { + return nil, fmt.Sprintf("non-income/expense transaction is closed") + } + + targetName := "" + productName := "" + + if t.originalTargetNameColumnIndex >= 0 && t.originalTargetNameColumnIndex < len(items) { + targetName = items[t.originalTargetNameColumnIndex] + } + + if t.originalProductNameColumnIndex >= 0 && t.originalProductNameColumnIndex < len(items) { + productName = items[t.originalProductNameColumnIndex] + } + + if statusName == alipayTransactionDataStatusRefundSuccessName { + data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] + data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } else { + if strings.Index(productName, alipayTransactionDataProductNameRechargePrefix) == 0 { // transfer to alipay wallet + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay + } else if strings.Index(productName, alipayTransactionDataProductNameCashWithdrawalPrefix) == 0 { // transfer from alipay wallet + data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else if strings.Index(productName, alipayTransactionDataProductNameTransferInText) >= 0 { // transfer in + data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else if strings.Index(productName, alipayTransactionDataProductNameTransferOutText) >= 0 { // transfer out + data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else if strings.Index(productName, alipayTransactionDataProductNameRepaymentText) >= 0 { // repayment + data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else { + return nil, fmt.Sprintf("product name (\"%s\") is unknown", productName) + } + } + } else { + data[datatable.DATA_TABLE_ACCOUNT_NAME] = relatedAccountName + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } + } + + if data[datatable.DATA_TABLE_TRANSACTION_TYPE] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] && statusName != "" { + if statusName == alipayTransactionDataStatusRefundSuccessName || statusName == alipayTransactionDataStatusTaxRefundSuccessName { + amount, err := utils.ParseAmount(data[datatable.DATA_TABLE_AMOUNT]) + + if err == nil { + data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] + data[datatable.DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount) + } + } + } + + return data, "" +} + +func createNewAlipayTransactionPlainTextDataTable(ctx core.Context, reader io.Reader, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune, originalColumnNames alipayTransactionColumnNames) (*alipayTransactionPlainTextDataTable, error) { + allOriginalLines, err := parseAllLinesFromAlipayTransactionPlainText(ctx, reader, fileHeaderLine, dataHeaderStartContent, dataBottomEndLineRune) + + if err != nil { + return nil, err + } + + if len(allOriginalLines) < 2 { + log.Errorf(ctx, "[alipay_transaction_data_plain_text_data_table.createNewAlipayTransactionPlainTextDataTable] cannot parse import data, because data table row count is less 1") + return nil, errs.ErrNotFoundTransactionDataInFile + } + + originalHeaderItems := allOriginalLines[0] + originalHeaderItemMap := make(map[string]int) + + for i := 0; i < len(originalHeaderItems); i++ { + originalHeaderItemMap[originalHeaderItems[i]] = i + } + + timeColumnIdx, timeColumnExists := originalHeaderItemMap[originalColumnNames.timeColumnName] + categoryColumnIdx, categoryColumnExists := originalHeaderItemMap[originalColumnNames.categoryColumnName] + targetNameColumnIdx, targetNameColumnExists := originalHeaderItemMap[originalColumnNames.targetNameColumnName] + productNameColumnIdx, productNameColumnExists := originalHeaderItemMap[originalColumnNames.productNameColumnName] + amountColumnIdx, amountColumnExists := originalHeaderItemMap[originalColumnNames.amountColumnName] + typeColumnIdx, typeColumnExists := originalHeaderItemMap[originalColumnNames.typeColumnName] + relatedAccountColumnIdx, relatedAccountColumnExists := originalHeaderItemMap[originalColumnNames.relatedAccountColumnName] + statusColumnIdx, statusColumnExists := originalHeaderItemMap[originalColumnNames.statusColumnName] + descriptionColumnIdx, descriptionColumnExists := originalHeaderItemMap[originalColumnNames.descriptionColumnName] + + if !timeColumnExists || !amountColumnExists || !typeColumnExists || !statusColumnExists { + log.Errorf(ctx, "[alipay_transaction_data_plain_text_data_table.createNewAlipayTransactionPlainTextDataTable] cannot parse alipay csv data, because missing essential columns in header row") + return nil, errs.ErrMissingRequiredFieldInHeaderRow + } + + if originalColumnNames.categoryColumnName == "" || !categoryColumnExists { + categoryColumnIdx = -1 + } + + if originalColumnNames.targetNameColumnName == "" || !targetNameColumnExists { + targetNameColumnIdx = -1 + } + + if originalColumnNames.productNameColumnName == "" || !productNameColumnExists { + productNameColumnIdx = -1 + } + + if originalColumnNames.relatedAccountColumnName == "" || !relatedAccountColumnExists { + relatedAccountColumnIdx = -1 + } + + if originalColumnNames.descriptionColumnName == "" || !descriptionColumnExists { + descriptionColumnIdx = -1 + } + + return &alipayTransactionPlainTextDataTable{ + allOriginalLines: allOriginalLines, + originalHeaderLineColumnNames: originalHeaderItems, + originalTimeColumnIndex: timeColumnIdx, + originalCategoryColumnIndex: categoryColumnIdx, + originalTargetNameColumnIndex: targetNameColumnIdx, + originalProductNameColumnIndex: productNameColumnIdx, + originalAmountColumnIndex: amountColumnIdx, + originalTypeColumnIndex: typeColumnIdx, + originalRelatedAccountColumnIndex: relatedAccountColumnIdx, + originalStatusColumnIndex: statusColumnIdx, + originalDescriptionColumnIndex: descriptionColumnIdx, + }, nil +} + +func parseAllLinesFromAlipayTransactionPlainText(ctx core.Context, reader io.Reader, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune) ([][]string, error) { + csvReader := csv.NewReader(reader) + csvReader.FieldsPerRecord = -1 + + allOriginalLines := make([][]string, 0) + hasFileHeader := false + foundContentBeforeDataHeaderLine := false + + for { + items, err := csvReader.Read() + + if err == io.EOF { + break + } + + if err != nil { + log.Errorf(ctx, "[alipay_transaction_data_plain_text_data_table.parseAllLinesFromAlipayTransactionPlainText] cannot parse alipay csv data, because %s", err.Error()) + return nil, errs.ErrInvalidCSVFile + } + + if !hasFileHeader { + if len(items) <= 0 { + continue + } else if strings.Index(items[0], fileHeaderLine) == 0 { + hasFileHeader = true + continue + } else { + log.Warnf(ctx, "[alipay_transaction_data_plain_text_data_table.parseAllLinesFromAlipayTransactionPlainText] read unexpected line before read file header, line content is %s", strings.Join(items, ",")) + } + } + + if !foundContentBeforeDataHeaderLine { + if len(items) <= 0 { + continue + } else if strings.Index(items[0], dataHeaderStartContent) >= 0 { + foundContentBeforeDataHeaderLine = true + continue + } else { + continue + } + } + + if foundContentBeforeDataHeaderLine { + if len(items) <= 0 { + continue + } else if len(items) == 1 && dataBottomEndLineRune > 0 && utils.ContainsOnlyOneRune(items[0], dataBottomEndLineRune) { + break + } + + for i := 0; i < len(items); i++ { + items[i] = strings.Trim(items[i], " ") + } + + if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) { + log.Errorf(ctx, "[alipay_transaction_data_plain_text_data_table.parseAllLinesFromAlipayTransactionPlainText] cannot parse row \"index:%d\", because may missing some columns (column count %d in data row is less than header column count %d)", len(allOriginalLines), len(items), len(allOriginalLines[0])) + return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow + } + + allOriginalLines = append(allOriginalLines, items) + } + } + + if !hasFileHeader || !foundContentBeforeDataHeaderLine { + return nil, errs.ErrInvalidFileHeader + } + + return allOriginalLines, nil +} diff --git a/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer.go b/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer.go index 27904ae5..5c59dc07 100644 --- a/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer.go +++ b/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer.go @@ -9,18 +9,20 @@ type alipayWebTransactionDataCsvImporter struct { var ( AlipayWebTransactionDataCsvImporter = &alipayWebTransactionDataCsvImporter{ alipayTransactionDataCsvImporter{ - fileHeaderLine: "支付宝交易记录明细查询", - dataHeaderStartContent: "交易记录明细列表", - dataBottomEndLineRune: '-', - timeColumnName: "交易创建时间", - categoryColumnName: "", - targetNameColumnName: "交易对方", - productNameColumnName: "商品名称", - amountColumnName: "金额(元)", - typeColumnName: "收/支", - relatedAccountColumnName: "", - statusColumnName: "交易状态", - descriptionColumnName: "备注", + fileHeaderLine: "支付宝交易记录明细查询", + dataHeaderStartContent: "交易记录明细列表", + dataBottomEndLineRune: '-', + originalColumnNames: alipayTransactionColumnNames{ + timeColumnName: "交易创建时间", + categoryColumnName: "", + targetNameColumnName: "交易对方", + productNameColumnName: "商品名称", + amountColumnName: "金额(元)", + typeColumnName: "收/支", + relatedAccountColumnName: "", + statusColumnName: "交易状态", + descriptionColumnName: "备注", + }, }, } ) diff --git a/pkg/converters/datatable/data_table.go b/pkg/converters/datatable/data_table.go index 3f817cfd..effdd0ee 100644 --- a/pkg/converters/datatable/data_table.go +++ b/pkg/converters/datatable/data_table.go @@ -1,6 +1,11 @@ package datatable -import "time" +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 { @@ -16,6 +21,9 @@ type ImportedDataTable interface { // 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 @@ -35,7 +43,7 @@ type ImportedDataRowIterator interface { HasNext() bool // Next returns the next imported data row - Next() ImportedDataRow + Next(ctx core.Context, user *models.User) ImportedDataRow } // DataTableBuilder defines the structure of data table builder diff --git a/pkg/converters/datatable/data_table_transaction_data_converter.go b/pkg/converters/datatable/data_table_transaction_data_converter.go index 1fd2f577..862e749b 100644 --- a/pkg/converters/datatable/data_table_transaction_data_converter.go +++ b/pkg/converters/datatable/data_table_transaction_data_converter.go @@ -75,6 +75,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 { + 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{ @@ -311,7 +319,12 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u for dataRowIterator.HasNext() { dataRowIndex++ - dataRow := dataRowIterator.Next() + dataRow := dataRowIterator.Next(ctx, user) + + if !dataRow.IsValid() { + continue + } + columnCount := dataRow.ColumnCount() if columnCount < 1 || (columnCount == 1 && dataRow.GetData(0) == "") { @@ -577,6 +590,11 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u allNewTransactions = append(allNewTransactions, transaction) } + if len(allNewTransactions) < 1 { + log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] no transaction data parsed for \"uid:%d\"", user.Uid) + return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile + } + sort.Sort(allNewTransactions) return allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, nil diff --git a/pkg/converters/datatable/writable_data_table.go b/pkg/converters/datatable/writable_data_table.go index 56a97f10..cc2554d8 100644 --- a/pkg/converters/datatable/writable_data_table.go +++ b/pkg/converters/datatable/writable_data_table.go @@ -3,6 +3,8 @@ package datatable import ( "time" + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/utils" ) @@ -89,6 +91,11 @@ func (t *WritableDataTable) DataRowIterator() ImportedDataRowIterator { } } +// 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) @@ -121,7 +128,7 @@ func (t *WritableDataRowIterator) HasNext() bool { } // Next returns the next imported data row -func (t *WritableDataRowIterator) Next() ImportedDataRow { +func (t *WritableDataRowIterator) Next(ctx core.Context, user *models.User) ImportedDataRow { if t.nextIndex >= len(t.dataTable.allData) { return nil } diff --git a/pkg/converters/datatable/writable_data_table_test.go b/pkg/converters/datatable/writable_data_table_test.go index 8303e2db..d3266792 100644 --- a/pkg/converters/datatable/writable_data_table_test.go +++ b/pkg/converters/datatable/writable_data_table_test.go @@ -6,6 +6,8 @@ import ( "github.com/stretchr/testify/assert" + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/utils" ) @@ -176,7 +178,7 @@ func TestWritableDataTableDataRowIterator(t *testing.T) { iterator := writableDataTable.DataRowIterator() for iterator.HasNext() { - dataRow := iterator.Next() + dataRow := iterator.Next(core.NewNullContext(), &models.User{}) actualTransactionTime, err := dataRow.GetTime(0, utils.GetTimezoneOffsetMinutes(time.Local)) assert.Nil(t, err) diff --git a/pkg/converters/default/ezbookkeeping_transaction_data_plain_text_data_table.go b/pkg/converters/default/ezbookkeeping_transaction_data_plain_text_data_table.go index d93b4c63..06da1ec5 100644 --- a/pkg/converters/default/ezbookkeeping_transaction_data_plain_text_data_table.go +++ b/pkg/converters/default/ezbookkeeping_transaction_data_plain_text_data_table.go @@ -6,7 +6,9 @@ import ( "time" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable" + "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/utils" ) @@ -61,6 +63,11 @@ func (t *ezBookKeepingTransactionPlainTextDataTable) DataRowIterator() datatable } } +// IsValid returns whether this row contains valid data for importing +func (r *ezBookKeepingTransactionPlainTextDataRow) IsValid() bool { + return true +} + // ColumnCount returns the total count of column in this data row func (r *ezBookKeepingTransactionPlainTextDataRow) ColumnCount() int { return len(r.allItems) @@ -91,7 +98,7 @@ func (t *ezBookKeepingTransactionPlainTextDataRowIterator) HasNext() bool { } // Next returns the next imported data row -func (t *ezBookKeepingTransactionPlainTextDataRowIterator) Next() datatable.ImportedDataRow { +func (t *ezBookKeepingTransactionPlainTextDataRowIterator) Next(ctx core.Context, user *models.User) datatable.ImportedDataRow { if t.currentIndex+1 >= len(t.dataTable.allLines) { return nil } diff --git a/pkg/converters/feidee/feidee_mymoney_transaction_data_excel_file_data_table.go b/pkg/converters/feidee/feidee_mymoney_transaction_data_excel_file_data_table.go index 68db3ed5..e5cb78c4 100644 --- a/pkg/converters/feidee/feidee_mymoney_transaction_data_excel_file_data_table.go +++ b/pkg/converters/feidee/feidee_mymoney_transaction_data_excel_file_data_table.go @@ -7,7 +7,9 @@ import ( "github.com/shakinm/xlsReader/xls" "github.com/mayswind/ezbookkeeping/pkg/converters/datatable" + "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/models" "github.com/mayswind/ezbookkeeping/pkg/utils" ) @@ -62,6 +64,11 @@ func (t *feideeMymoneyTransactionExcelFileDataTable) DataRowIterator() datatable } } +// IsValid returns whether this row contains valid data for importing +func (r *feideeMymoneyTransactionExcelFileDataRow) IsValid() bool { + return true +} + // ColumnCount returns the total count of column in this data row func (r *feideeMymoneyTransactionExcelFileDataRow) ColumnCount() int { row, err := r.sheet.GetRow(r.rowIndex) @@ -142,7 +149,7 @@ func (t *feideeMymoneyTransactionExcelFileDataRowIterator) HasNext() bool { } // Next returns the next imported data row -func (t *feideeMymoneyTransactionExcelFileDataRowIterator) Next() datatable.ImportedDataRow { +func (t *feideeMymoneyTransactionExcelFileDataRowIterator) Next(ctx core.Context, user *models.User) datatable.ImportedDataRow { allSheets := t.dataTable.workbook.GetSheets() currentRowIndexInTable := t.currentRowIndexInTable