mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-18 00:34:28 +08:00
code refactor
This commit is contained in:
@@ -2,10 +2,10 @@ package alipay
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
csvdatatable "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
@@ -53,31 +53,20 @@ type alipayTransactionColumnNames struct {
|
|||||||
|
|
||||||
// alipayTransactionDataTable defines the structure of alipay transaction plain text data table
|
// alipayTransactionDataTable defines the structure of alipay transaction plain text data table
|
||||||
type alipayTransactionDataTable struct {
|
type alipayTransactionDataTable struct {
|
||||||
allOriginalLines [][]string
|
innerDataTable datatable.CommonDataTable
|
||||||
originalHeaderLineColumnNames []string
|
columns alipayTransactionColumnNames
|
||||||
originalTimeColumnIndex int
|
|
||||||
originalCategoryColumnIndex int
|
|
||||||
originalTargetNameColumnIndex int
|
|
||||||
originalProductNameColumnIndex int
|
|
||||||
originalAmountColumnIndex int
|
|
||||||
originalTypeColumnIndex int
|
|
||||||
originalRelatedAccountColumnIndex int
|
|
||||||
originalStatusColumnIndex int
|
|
||||||
originalDescriptionColumnIndex int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// alipayTransactionDataRow defines the structure of alipay transaction plain text data row
|
// alipayTransactionDataRow defines the structure of alipay transaction plain text data row
|
||||||
type alipayTransactionDataRow struct {
|
type alipayTransactionDataRow struct {
|
||||||
dataTable *alipayTransactionDataTable
|
isValid bool
|
||||||
isValid bool
|
finalItems map[datatable.TransactionDataTableColumn]string
|
||||||
originalItems []string
|
|
||||||
finalItems map[datatable.TransactionDataTableColumn]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// alipayTransactionDataRowIterator defines the structure of alipay transaction plain text data row iterator
|
// alipayTransactionDataRowIterator defines the structure of alipay transaction plain text data row iterator
|
||||||
type alipayTransactionDataRowIterator struct {
|
type alipayTransactionDataRowIterator struct {
|
||||||
dataTable *alipayTransactionDataTable
|
dataTable *alipayTransactionDataTable
|
||||||
currentIndex int
|
innerIterator datatable.CommonDataRowIterator
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasColumn returns whether the transaction data table has specified column
|
// HasColumn returns whether the transaction data table has specified column
|
||||||
@@ -88,18 +77,14 @@ func (t *alipayTransactionDataTable) HasColumn(column datatable.TransactionDataT
|
|||||||
|
|
||||||
// TransactionRowCount returns the total count of transaction data row
|
// TransactionRowCount returns the total count of transaction data row
|
||||||
func (t *alipayTransactionDataTable) TransactionRowCount() int {
|
func (t *alipayTransactionDataTable) TransactionRowCount() int {
|
||||||
if len(t.allOriginalLines) < 1 {
|
return t.innerDataTable.DataRowCount()
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(t.allOriginalLines) - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionRowIterator returns the iterator of transaction data row
|
// TransactionRowIterator returns the iterator of transaction data row
|
||||||
func (t *alipayTransactionDataTable) TransactionRowIterator() datatable.TransactionDataRowIterator {
|
func (t *alipayTransactionDataTable) TransactionRowIterator() datatable.TransactionDataRowIterator {
|
||||||
return &alipayTransactionDataRowIterator{
|
return &alipayTransactionDataRowIterator{
|
||||||
dataTable: t,
|
dataTable: t,
|
||||||
currentIndex: 0,
|
innerIterator: t.innerDataTable.DataRowIterator(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,94 +106,85 @@ func (r *alipayTransactionDataRow) GetData(column datatable.TransactionDataTable
|
|||||||
|
|
||||||
// HasNext returns whether the iterator does not reach the end
|
// HasNext returns whether the iterator does not reach the end
|
||||||
func (t *alipayTransactionDataRowIterator) HasNext() bool {
|
func (t *alipayTransactionDataRowIterator) HasNext() bool {
|
||||||
return t.currentIndex+1 < len(t.dataTable.allOriginalLines)
|
return t.innerIterator.HasNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next imported data row
|
// Next returns the next imported data row
|
||||||
func (t *alipayTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow datatable.TransactionDataRow, err error) {
|
func (t *alipayTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow datatable.TransactionDataRow, err error) {
|
||||||
if t.currentIndex+1 >= len(t.dataTable.allOriginalLines) {
|
importedRow := t.innerIterator.Next()
|
||||||
|
|
||||||
|
if importedRow == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
t.currentIndex++
|
finalItems, isValid, err := t.dataTable.parseTransactionData(ctx, user, importedRow, t.innerIterator.CurrentRowId())
|
||||||
|
|
||||||
rowItems := t.dataTable.allOriginalLines[t.currentIndex]
|
if err != nil {
|
||||||
isValid := true
|
return nil, err
|
||||||
|
|
||||||
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_csv_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_csv_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.TransactionDataTableColumn]string
|
|
||||||
var errMsg string
|
|
||||||
|
|
||||||
if isValid {
|
|
||||||
finalItems, errMsg = t.dataTable.parseTransactionData(ctx, user, rowItems)
|
|
||||||
|
|
||||||
if finalItems == nil {
|
|
||||||
log.Warnf(ctx, "[alipay_transaction_csv_data_table.Next] skip parsing transaction in row \"index:%d\", because %s", t.currentIndex, errMsg)
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &alipayTransactionDataRow{
|
return &alipayTransactionDataRow{
|
||||||
dataTable: t.dataTable,
|
isValid: isValid,
|
||||||
isValid: isValid,
|
finalItems: finalItems,
|
||||||
originalItems: rowItems,
|
|
||||||
finalItems: finalItems,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *alipayTransactionDataTable) parseTransactionData(ctx core.Context, user *models.User, items []string) (map[datatable.TransactionDataTableColumn]string, string) {
|
func (t *alipayTransactionDataTable) hasOriginalColumn(columnName string) bool {
|
||||||
data := make(map[datatable.TransactionDataTableColumn]string, len(alipayTransactionSupportedColumns))
|
return columnName != "" && t.innerDataTable.HasColumn(columnName)
|
||||||
|
}
|
||||||
|
|
||||||
if t.originalTimeColumnIndex >= 0 && t.originalTimeColumnIndex < len(items) {
|
func (t *alipayTransactionDataTable) parseTransactionData(ctx core.Context, user *models.User, dataRow datatable.CommonDataRow, rowId string) (map[datatable.TransactionDataTableColumn]string, bool, error) {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = items[t.originalTimeColumnIndex]
|
if dataRow.GetData(t.columns.typeColumnName) != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] &&
|
||||||
|
dataRow.GetData(t.columns.typeColumnName) != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] &&
|
||||||
|
dataRow.GetData(t.columns.typeColumnName) != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
||||||
|
log.Warnf(ctx, "[alipay_transaction_csv_data_table.parseTransactionData] skip parsing transaction in row \"%s\", because type is \"%s\"", rowId, dataRow.GetData(t.columns.typeColumnName))
|
||||||
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.originalCategoryColumnIndex >= 0 && t.originalCategoryColumnIndex < len(items) {
|
if dataRow.GetData(t.columns.statusColumnName) != alipayTransactionDataStatusSuccessName &&
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = items[t.originalCategoryColumnIndex]
|
dataRow.GetData(t.columns.statusColumnName) != alipayTransactionDataStatusPaymentSuccessName &&
|
||||||
|
dataRow.GetData(t.columns.statusColumnName) != alipayTransactionDataStatusRepaymentSuccessName &&
|
||||||
|
dataRow.GetData(t.columns.statusColumnName) != alipayTransactionDataStatusClosedName &&
|
||||||
|
dataRow.GetData(t.columns.statusColumnName) != alipayTransactionDataStatusRefundSuccessName &&
|
||||||
|
dataRow.GetData(t.columns.statusColumnName) != alipayTransactionDataStatusTaxRefundSuccessName {
|
||||||
|
log.Warnf(ctx, "[alipay_transaction_csv_data_table.parseTransactionData] skip parsing transaction in row \"%s\", because status is \"%s\"", rowId, dataRow.GetData(t.columns.statusColumnName))
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[datatable.TransactionDataTableColumn]string, len(alipayTransactionSupportedColumns))
|
||||||
|
|
||||||
|
if t.hasOriginalColumn(t.columns.timeColumnName) {
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = dataRow.GetData(t.columns.timeColumnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.hasOriginalColumn(t.columns.categoryColumnName) {
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(t.columns.categoryColumnName)
|
||||||
} else {
|
} else {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = ""
|
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.originalAmountColumnIndex >= 0 && t.originalAmountColumnIndex < len(items) {
|
if t.hasOriginalColumn(t.columns.amountColumnName) {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = items[t.originalAmountColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = dataRow.GetData(t.columns.amountColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.originalDescriptionColumnIndex >= 0 && t.originalDescriptionColumnIndex < len(items) && items[t.originalDescriptionColumnIndex] != "" {
|
if t.hasOriginalColumn(t.columns.descriptionColumnName) && dataRow.GetData(t.columns.descriptionColumnName) != "" {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = items[t.originalDescriptionColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = dataRow.GetData(t.columns.descriptionColumnName)
|
||||||
} else if t.originalProductNameColumnIndex >= 0 && t.originalProductNameColumnIndex < len(items) && items[t.originalProductNameColumnIndex] != "" {
|
} else if t.hasOriginalColumn(t.columns.productNameColumnName) && dataRow.GetData(t.columns.productNameColumnName) != "" {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = items[t.originalProductNameColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = dataRow.GetData(t.columns.productNameColumnName)
|
||||||
} else {
|
} else {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = ""
|
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
relatedAccountName := ""
|
relatedAccountName := ""
|
||||||
|
|
||||||
if t.originalRelatedAccountColumnIndex >= 0 && t.originalRelatedAccountColumnIndex < len(items) {
|
if t.hasOriginalColumn(t.columns.relatedAccountColumnName) {
|
||||||
relatedAccountName = items[t.originalRelatedAccountColumnIndex]
|
relatedAccountName = dataRow.GetData(t.columns.relatedAccountColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusName := ""
|
statusName := ""
|
||||||
|
|
||||||
if t.originalStatusColumnIndex >= 0 && t.originalStatusColumnIndex < len(items) {
|
if t.hasOriginalColumn(t.columns.statusColumnName) {
|
||||||
statusName = items[t.originalStatusColumnIndex]
|
statusName = dataRow.GetData(t.columns.statusColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
locale := user.Language
|
locale := user.Language
|
||||||
@@ -219,12 +195,13 @@ func (t *alipayTransactionDataTable) parseTransactionData(ctx core.Context, user
|
|||||||
|
|
||||||
localeTextItems := locales.GetLocaleTextItems(locale)
|
localeTextItems := locales.GetLocaleTextItems(locale)
|
||||||
|
|
||||||
if t.originalTypeColumnIndex >= 0 && t.originalTypeColumnIndex < len(items) {
|
if t.hasOriginalColumn(t.columns.typeColumnName) {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = items[t.originalTypeColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataRow.GetData(t.columns.typeColumnName)
|
||||||
|
|
||||||
if items[t.originalTypeColumnIndex] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] {
|
if dataRow.GetData(t.columns.typeColumnName) == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] {
|
||||||
if statusName == alipayTransactionDataStatusClosedName {
|
if statusName == alipayTransactionDataStatusClosedName {
|
||||||
return nil, fmt.Sprintf("income transaction is closed")
|
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.parseTransactionData] skip parsing transaction in row \"%s\", because income transaction is closed", rowId)
|
||||||
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusName == alipayTransactionDataStatusSuccessName {
|
if statusName == alipayTransactionDataStatusSuccessName {
|
||||||
@@ -234,20 +211,21 @@ func (t *alipayTransactionDataTable) parseTransactionData(ctx core.Context, user
|
|||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = ""
|
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = ""
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = ""
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = ""
|
||||||
}
|
}
|
||||||
} else if items[t.originalTypeColumnIndex] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
} else if dataRow.GetData(t.columns.typeColumnName) == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
||||||
if statusName == alipayTransactionDataStatusClosedName {
|
if statusName == alipayTransactionDataStatusClosedName {
|
||||||
return nil, fmt.Sprintf("non-income/expense transaction is closed")
|
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.parseTransactionData] skip parsing transaction in row \"%s\", because non-income/expense transaction is closed", rowId)
|
||||||
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
targetName := ""
|
targetName := ""
|
||||||
productName := ""
|
productName := ""
|
||||||
|
|
||||||
if t.originalTargetNameColumnIndex >= 0 && t.originalTargetNameColumnIndex < len(items) {
|
if t.hasOriginalColumn(t.columns.targetNameColumnName) {
|
||||||
targetName = items[t.originalTargetNameColumnIndex]
|
targetName = dataRow.GetData(t.columns.targetNameColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.originalProductNameColumnIndex >= 0 && t.originalProductNameColumnIndex < len(items) {
|
if t.hasOriginalColumn(t.columns.productNameColumnName) {
|
||||||
productName = items[t.originalProductNameColumnIndex]
|
productName = dataRow.GetData(t.columns.productNameColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusName == alipayTransactionDataStatusRefundSuccessName {
|
if statusName == alipayTransactionDataStatusRefundSuccessName {
|
||||||
@@ -271,7 +249,8 @@ func (t *alipayTransactionDataTable) parseTransactionData(ctx core.Context, user
|
|||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Sprintf("product name (\"%s\") is unknown", productName)
|
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.parseTransactionData] skip parsing transaction in row \"%s\", because product name (\"%s\") is unknown", rowId, productName)
|
||||||
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -291,79 +270,33 @@ func (t *alipayTransactionDataTable) parseTransactionData(ctx core.Context, user
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, ""
|
return data, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNewAlipayTransactionDataTable(ctx core.Context, reader io.Reader, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune, originalColumnNames alipayTransactionColumnNames) (*alipayTransactionDataTable, error) {
|
func createNewAlipayTransactionDataTable(ctx core.Context, reader io.Reader, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune, originalColumnNames alipayTransactionColumnNames) (*alipayTransactionDataTable, error) {
|
||||||
allOriginalLines, err := parseAllLinesFromAlipayTransactionPlainText(ctx, reader, fileHeaderLine, dataHeaderStartContent, dataBottomEndLineRune)
|
dataTable, err := createNewAlipayImportedDataTable(ctx, reader, fileHeaderLine, dataHeaderStartContent, dataBottomEndLineRune)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allOriginalLines) < 2 {
|
commonDataTable := datatable.CreateNewImportedCommonDataTable(dataTable)
|
||||||
log.Errorf(ctx, "[alipay_transaction_csv_data_table.createNewAlipayTransactionPlainTextDataTable] cannot parse import data, because data table row count is less 1")
|
|
||||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
|
||||||
}
|
|
||||||
|
|
||||||
originalHeaderItems := allOriginalLines[0]
|
if !commonDataTable.HasColumn(originalColumnNames.timeColumnName) ||
|
||||||
originalHeaderItemMap := make(map[string]int)
|
!commonDataTable.HasColumn(originalColumnNames.amountColumnName) ||
|
||||||
|
!commonDataTable.HasColumn(originalColumnNames.typeColumnName) ||
|
||||||
for i := 0; i < len(originalHeaderItems); i++ {
|
!commonDataTable.HasColumn(originalColumnNames.statusColumnName) {
|
||||||
originalHeaderItemMap[originalHeaderItems[i]] = i
|
log.Errorf(ctx, "[alipay_transaction_csv_data_table.createNewAlipayTransactionDataTable] cannot parse alipay csv data, because missing essential columns in header row")
|
||||||
}
|
|
||||||
|
|
||||||
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_csv_data_table.createNewAlipayTransactionPlainTextDataTable] cannot parse alipay csv data, because missing essential columns in header row")
|
|
||||||
return nil, errs.ErrMissingRequiredFieldInHeaderRow
|
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 &alipayTransactionDataTable{
|
return &alipayTransactionDataTable{
|
||||||
allOriginalLines: allOriginalLines,
|
innerDataTable: commonDataTable,
|
||||||
originalHeaderLineColumnNames: originalHeaderItems,
|
columns: originalColumnNames,
|
||||||
originalTimeColumnIndex: timeColumnIdx,
|
|
||||||
originalCategoryColumnIndex: categoryColumnIdx,
|
|
||||||
originalTargetNameColumnIndex: targetNameColumnIdx,
|
|
||||||
originalProductNameColumnIndex: productNameColumnIdx,
|
|
||||||
originalAmountColumnIndex: amountColumnIdx,
|
|
||||||
originalTypeColumnIndex: typeColumnIdx,
|
|
||||||
originalRelatedAccountColumnIndex: relatedAccountColumnIdx,
|
|
||||||
originalStatusColumnIndex: statusColumnIdx,
|
|
||||||
originalDescriptionColumnIndex: descriptionColumnIdx,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAllLinesFromAlipayTransactionPlainText(ctx core.Context, reader io.Reader, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune) ([][]string, error) {
|
func createNewAlipayImportedDataTable(ctx core.Context, reader io.Reader, fileHeaderLine string, dataHeaderStartContent string, dataBottomEndLineRune rune) (datatable.ImportedDataTable, error) {
|
||||||
csvReader := csv.NewReader(reader)
|
csvReader := csv.NewReader(reader)
|
||||||
csvReader.FieldsPerRecord = -1
|
csvReader.FieldsPerRecord = -1
|
||||||
|
|
||||||
@@ -379,7 +312,7 @@ func parseAllLinesFromAlipayTransactionPlainText(ctx core.Context, reader io.Rea
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(ctx, "[alipay_transaction_csv_data_table.parseAllLinesFromAlipayTransactionPlainText] cannot parse alipay csv data, because %s", err.Error())
|
log.Errorf(ctx, "[alipay_transaction_csv_data_table.createNewAlipayImportedDataTable] cannot parse alipay csv data, because %s", err.Error())
|
||||||
return nil, errs.ErrInvalidCSVFile
|
return nil, errs.ErrInvalidCSVFile
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +323,7 @@ func parseAllLinesFromAlipayTransactionPlainText(ctx core.Context, reader io.Rea
|
|||||||
hasFileHeader = true
|
hasFileHeader = true
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
log.Warnf(ctx, "[alipay_transaction_csv_data_table.parseAllLinesFromAlipayTransactionPlainText] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
log.Warnf(ctx, "[alipay_transaction_csv_data_table.createNewAlipayImportedDataTable] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,7 +351,7 @@ func parseAllLinesFromAlipayTransactionPlainText(ctx core.Context, reader io.Rea
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
||||||
log.Errorf(ctx, "[alipay_transaction_csv_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]))
|
log.Errorf(ctx, "[alipay_transaction_csv_data_table.createNewAlipayImportedDataTable] 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
|
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,5 +363,12 @@ func parseAllLinesFromAlipayTransactionPlainText(ctx core.Context, reader io.Rea
|
|||||||
return nil, errs.ErrInvalidFileHeader
|
return nil, errs.ErrInvalidFileHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
return allOriginalLines, nil
|
if len(allOriginalLines) < 2 {
|
||||||
|
log.Errorf(ctx, "[alipay_transaction_csv_data_table.createNewAlipayImportedDataTable] cannot parse import data, because data table row count is less 1")
|
||||||
|
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTable := csvdatatable.CreateNewCustomCsvImportedDataTable(allOriginalLines)
|
||||||
|
|
||||||
|
return dataTable, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,6 +364,56 @@ func TestAlipayCsvFileImporterParseImportedData_ParseCategory(t *testing.T) {
|
|||||||
assert.Equal(t, "Test Category3", allNewSubTransferCategories[0].Name)
|
assert.Equal(t, "Test Category3", allNewSubTransferCategories[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_ParseRelatedAccount(t *testing.T) {
|
||||||
|
converter := AlipayAppTransactionDataCsvFileImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
data1, err := simplifiedchinese.GB18030.NewEncoder().String("------------------------------------------------------------------------------------\n" +
|
||||||
|
"导出信息:\n" +
|
||||||
|
"姓名:xxx\n" +
|
||||||
|
"支付宝账户:xxx@xxx.xxx\n" +
|
||||||
|
"起始时间:[2024-01-01 00:00:00] 终止时间:[2024-09-01 23:59:59]\n" +
|
||||||
|
"导出交易类型:[全部]\n" +
|
||||||
|
"------------------------支付宝(中国)网络技术有限公司 电子客户回单------------------------\n" +
|
||||||
|
"交易时间,商品说明,收/支,金额,收/付款方式,交易状态,\n" +
|
||||||
|
"2024-09-01 03:45:07,余额宝-单次转入,不计收支,0.01,Test Account,交易成功,\n" +
|
||||||
|
"2024-09-01 05:07:29,信用卡还款,不计收支,0.02,Test Account2,交易成功,\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(allNewTransactions))
|
||||||
|
assert.Equal(t, 3, len(allNewAccounts))
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||||
|
assert.Equal(t, int64(1), allNewTransactions[0].Amount)
|
||||||
|
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalDestinationAccountName)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewTransactions[1].Uid)
|
||||||
|
assert.Equal(t, int64(2), allNewTransactions[1].Amount)
|
||||||
|
assert.Equal(t, "Test Account2", allNewTransactions[1].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "", allNewTransactions[1].OriginalDestinationAccountName)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid)
|
||||||
|
assert.Equal(t, "Test Account", allNewAccounts[0].Name)
|
||||||
|
assert.Equal(t, "CNY", allNewAccounts[0].Currency)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewAccounts[1].Uid)
|
||||||
|
assert.Equal(t, "", allNewAccounts[1].Name)
|
||||||
|
assert.Equal(t, "CNY", allNewAccounts[1].Currency)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewAccounts[2].Uid)
|
||||||
|
assert.Equal(t, "Test Account2", allNewAccounts[2].Name)
|
||||||
|
assert.Equal(t, "CNY", allNewAccounts[2].Currency)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := AlipayWebTransactionDataCsvFileImporter
|
converter := AlipayWebTransactionDataCsvFileImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
@@ -479,3 +529,23 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing
|
|||||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
||||||
|
converter := AlipayWebTransactionDataCsvFileImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Missing Time Column
|
||||||
|
data1, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" +
|
||||||
|
"账号:[xxx@xxx.xxx]\n" +
|
||||||
|
"起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" +
|
||||||
|
"---------------------------------交易记录明细列表------------------------------------\n" +
|
||||||
|
"交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package csv
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
@@ -72,6 +73,11 @@ func (t *CsvFileImportedDataRowIterator) HasNext() bool {
|
|||||||
return t.currentIndex+1 < len(t.dataTable.allLines)
|
return t.currentIndex+1 < len(t.dataTable.allLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CurrentRowId returns current index
|
||||||
|
func (t *CsvFileImportedDataRowIterator) CurrentRowId() string {
|
||||||
|
return fmt.Sprintf("line#%d", t.currentIndex)
|
||||||
|
}
|
||||||
|
|
||||||
// Next returns the next imported data row
|
// Next returns the next imported data row
|
||||||
func (t *CsvFileImportedDataRowIterator) Next() datatable.ImportedDataRow {
|
func (t *CsvFileImportedDataRowIterator) Next() datatable.ImportedDataRow {
|
||||||
if t.currentIndex+1 >= len(t.dataTable.allLines) {
|
if t.currentIndex+1 >= len(t.dataTable.allLines) {
|
||||||
@@ -88,11 +94,18 @@ func (t *CsvFileImportedDataRowIterator) Next() datatable.ImportedDataRow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNewCsvDataTable returns comma separated values data table by io readers
|
// CreateNewCsvImportedDataTable returns comma separated values data table by io readers
|
||||||
func CreateNewCsvDataTable(ctx core.Context, reader io.Reader) (*CsvFileImportedDataTable, error) {
|
func CreateNewCsvImportedDataTable(ctx core.Context, reader io.Reader) (*CsvFileImportedDataTable, error) {
|
||||||
return createNewCsvFileDataTable(ctx, reader, ',')
|
return createNewCsvFileDataTable(ctx, reader, ',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateNewCustomCsvImportedDataTable returns character separated values data table by io readers
|
||||||
|
func CreateNewCustomCsvImportedDataTable(allLines [][]string) *CsvFileImportedDataTable {
|
||||||
|
return &CsvFileImportedDataTable{
|
||||||
|
allLines: allLines,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createNewCsvFileDataTable(ctx core.Context, reader io.Reader, separator rune) (*CsvFileImportedDataTable, error) {
|
func createNewCsvFileDataTable(ctx core.Context, reader io.Reader, separator rune) (*CsvFileImportedDataTable, error) {
|
||||||
csvReader := csv.NewReader(reader)
|
csvReader := csv.NewReader(reader)
|
||||||
csvReader.Comma = separator
|
csvReader.Comma = separator
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package datatable
|
||||||
|
|
||||||
|
// CommonDataTable defines the structure of common data table
|
||||||
|
type CommonDataTable interface {
|
||||||
|
// HeaderColumnCount returns the total count of column in header row
|
||||||
|
HeaderColumnCount() int
|
||||||
|
|
||||||
|
// HasColumn returns whether the common data table has specified column name
|
||||||
|
HasColumn(columnName string) bool
|
||||||
|
|
||||||
|
// DataRowCount returns the total count of common data row
|
||||||
|
DataRowCount() int
|
||||||
|
|
||||||
|
// DataRowIterator returns the iterator of common data row
|
||||||
|
DataRowIterator() CommonDataRowIterator
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonDataRow defines the structure of common data row
|
||||||
|
type CommonDataRow interface {
|
||||||
|
// ColumnCount returns the total count of column in this data row
|
||||||
|
ColumnCount() int
|
||||||
|
|
||||||
|
// HasData returns whether the common data row has specified column data
|
||||||
|
HasData(columnName string) bool
|
||||||
|
|
||||||
|
// GetData returns the data in the specified column name
|
||||||
|
GetData(columnName string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonDataRowIterator defines the structure of common data row iterator
|
||||||
|
type CommonDataRowIterator interface {
|
||||||
|
// HasNext returns whether the iterator does not reach the end
|
||||||
|
HasNext() bool
|
||||||
|
|
||||||
|
// CurrentRowId returns current row id
|
||||||
|
CurrentRowId() string
|
||||||
|
|
||||||
|
// Next returns the next common data row
|
||||||
|
Next() CommonDataRow
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package datatable
|
||||||
|
|
||||||
|
// ImportedCommonDataTable defines the structure of imported common data table
|
||||||
|
type ImportedCommonDataTable struct {
|
||||||
|
innerDataTable ImportedDataTable
|
||||||
|
dataColumnIndexes map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportedCommonDataRow defines the structure of imported common data row
|
||||||
|
type ImportedCommonDataRow struct {
|
||||||
|
rowData map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportedCommonDataRowIterator defines the structure of imported common data row iterator
|
||||||
|
type ImportedCommonDataRowIterator struct {
|
||||||
|
commonDataTable *ImportedCommonDataTable
|
||||||
|
innerIterator ImportedDataRowIterator
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderColumnCount returns the total count of column in header row
|
||||||
|
func (t *ImportedCommonDataTable) HeaderColumnCount() int {
|
||||||
|
return len(t.innerDataTable.HeaderColumnNames())
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasColumn returns whether the data table has specified column name
|
||||||
|
func (t *ImportedCommonDataTable) HasColumn(columnName string) bool {
|
||||||
|
index, exists := t.dataColumnIndexes[columnName]
|
||||||
|
return exists && index >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataRowCount returns the total count of common data row
|
||||||
|
func (t *ImportedCommonDataTable) DataRowCount() int {
|
||||||
|
return t.innerDataTable.DataRowCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataRowIterator returns the iterator of common data row
|
||||||
|
func (t *ImportedCommonDataTable) DataRowIterator() CommonDataRowIterator {
|
||||||
|
return &ImportedCommonDataRowIterator{
|
||||||
|
commonDataTable: t,
|
||||||
|
innerIterator: t.innerDataTable.DataRowIterator(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasData returns whether the common data row has specified column data
|
||||||
|
func (r *ImportedCommonDataRow) HasData(columnName string) bool {
|
||||||
|
_, exists := r.rowData[columnName]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnCount returns the total count of column in this data row
|
||||||
|
func (r *ImportedCommonDataRow) ColumnCount() int {
|
||||||
|
return len(r.rowData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetData returns the data in the specified column name
|
||||||
|
func (r *ImportedCommonDataRow) GetData(columnName string) string {
|
||||||
|
return r.rowData[columnName]
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNext returns whether the iterator does not reach the end
|
||||||
|
func (t *ImportedCommonDataRowIterator) HasNext() bool {
|
||||||
|
return t.innerIterator.HasNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentRowId returns current row id
|
||||||
|
func (t *ImportedCommonDataRowIterator) CurrentRowId() string {
|
||||||
|
return t.innerIterator.CurrentRowId()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next common data row
|
||||||
|
func (t *ImportedCommonDataRowIterator) Next() CommonDataRow {
|
||||||
|
importedRow := t.innerIterator.Next()
|
||||||
|
|
||||||
|
if importedRow == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rowData := make(map[string]string, len(t.commonDataTable.dataColumnIndexes))
|
||||||
|
|
||||||
|
for column, columnIndex := range t.commonDataTable.dataColumnIndexes {
|
||||||
|
if columnIndex < 0 || columnIndex >= importedRow.ColumnCount() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
value := importedRow.GetData(columnIndex)
|
||||||
|
rowData[column] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ImportedCommonDataRow{
|
||||||
|
rowData: rowData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNewImportedCommonDataTable returns common data table from imported data table
|
||||||
|
func CreateNewImportedCommonDataTable(dataTable ImportedDataTable) *ImportedCommonDataTable {
|
||||||
|
headerLineItems := dataTable.HeaderColumnNames()
|
||||||
|
dataColumnIndexes := make(map[string]int, len(headerLineItems))
|
||||||
|
|
||||||
|
for i := 0; i < len(headerLineItems); i++ {
|
||||||
|
dataColumnIndexes[headerLineItems[i]] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ImportedCommonDataTable{
|
||||||
|
innerDataTable: dataTable,
|
||||||
|
dataColumnIndexes: dataColumnIndexes,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,9 @@ type ImportedDataRowIterator interface {
|
|||||||
// HasNext returns whether the iterator does not reach the end
|
// HasNext returns whether the iterator does not reach the end
|
||||||
HasNext() bool
|
HasNext() bool
|
||||||
|
|
||||||
|
// CurrentRowId returns current row id
|
||||||
|
CurrentRowId() string
|
||||||
|
|
||||||
// Next returns the next imported data row
|
// Next returns the next imported data row
|
||||||
Next() ImportedDataRow
|
Next() ImportedDataRow
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,13 +143,13 @@ func (t *ImportedTransactionDataRowIterator) Next(ctx core.Context, user *models
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateImportedTransactionDataTable returns transaction data table from imported data table
|
// CreateNewImportedTransactionDataTable returns transaction data table from imported data table
|
||||||
func CreateImportedTransactionDataTable(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string) *ImportedTransactionDataTable {
|
func CreateNewImportedTransactionDataTable(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string) *ImportedTransactionDataTable {
|
||||||
return CreateImportedTransactionDataTableWithRowParser(dataTable, dataColumnMapping, nil)
|
return CreateNewImportedTransactionDataTableWithRowParser(dataTable, dataColumnMapping, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateImportedTransactionDataTableWithRowParser returns transaction data table from imported data table
|
// CreateNewImportedTransactionDataTableWithRowParser returns transaction data table from imported data table
|
||||||
func CreateImportedTransactionDataTableWithRowParser(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string, rowParser TransactionDataRowParser) *ImportedTransactionDataTable {
|
func CreateNewImportedTransactionDataTableWithRowParser(dataTable ImportedDataTable, dataColumnMapping map[TransactionDataTableColumn]string, rowParser TransactionDataRowParser) *ImportedTransactionDataTable {
|
||||||
headerLineItems := dataTable.HeaderColumnNames()
|
headerLineItems := dataTable.HeaderColumnNames()
|
||||||
headerItemMap := make(map[string]int, len(headerLineItems))
|
headerItemMap := make(map[string]int, len(headerLineItems))
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Co
|
|||||||
return nil, nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionDataTable := datatable.CreateImportedTransactionDataTable(dataTable, ezbookkeepingDataColumnNameMapping)
|
transactionDataTable := datatable.CreateNewImportedTransactionDataTable(dataTable, ezbookkeepingDataColumnNameMapping)
|
||||||
|
|
||||||
dataTableImporter := datatable.CreateNewImporter(
|
dataTableImporter := datatable.CreateNewImporter(
|
||||||
ezbookkeepingTransactionTypeNameMapping,
|
ezbookkeepingTransactionTypeNameMapping,
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ func (t *defaultPlainTextDataRowIterator) HasNext() bool {
|
|||||||
return t.currentIndex+1 < len(t.dataTable.allLines)
|
return t.currentIndex+1 < len(t.dataTable.allLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CurrentRowId returns current index
|
||||||
|
func (t *defaultPlainTextDataRowIterator) CurrentRowId() string {
|
||||||
|
return fmt.Sprintf("line#%d", t.currentIndex)
|
||||||
|
}
|
||||||
|
|
||||||
// Next returns the next imported data row
|
// Next returns the next imported data row
|
||||||
func (t *defaultPlainTextDataRowIterator) Next() datatable.ImportedDataRow {
|
func (t *defaultPlainTextDataRowIterator) Next() datatable.ImportedDataRow {
|
||||||
if t.currentIndex+1 >= len(t.dataTable.allLines) {
|
if t.currentIndex+1 >= len(t.dataTable.allLines) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package excel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/shakinm/xlsReader/xls"
|
"github.com/shakinm/xlsReader/xls"
|
||||||
|
|
||||||
@@ -115,6 +116,11 @@ func (t *ExcelFileDataRowIterator) HasNext() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CurrentRowId returns current index
|
||||||
|
func (t *ExcelFileDataRowIterator) CurrentRowId() string {
|
||||||
|
return fmt.Sprintf("table#%d-row#%d", t.currentTableIndex, t.currentRowIndexInTable)
|
||||||
|
}
|
||||||
|
|
||||||
// Next returns the next imported data row
|
// Next returns the next imported data row
|
||||||
func (t *ExcelFileDataRowIterator) Next() datatable.ImportedDataRow {
|
func (t *ExcelFileDataRowIterator) Next() datatable.ImportedDataRow {
|
||||||
allSheets := t.dataTable.workbook.GetSheets()
|
allSheets := t.dataTable.workbook.GetSheets()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
csvdatatable "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
@@ -12,14 +13,35 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const feideeMymoneyTransactionDataCsvFileHeader = "随手记导出文件(headers:v5;"
|
const feideeMymoneyAppTransactionDataCsvFileHeader = "随手记导出文件(headers:v5;"
|
||||||
const feideeMymoneyTransactionDataCsvFileHeaderWithUtf8Bom = "\xEF\xBB\xBF" + feideeMymoneyTransactionDataCsvFileHeader
|
const feideeMymoneyAppTransactionDataCsvFileHeaderWithUtf8Bom = "\xEF\xBB\xBF" + feideeMymoneyAppTransactionDataCsvFileHeader
|
||||||
|
|
||||||
const feideeMymoneyCsvFileTransactionTypeModifyBalanceText = "余额变更"
|
const feideeMymoneyAppTransactionTimeColumnName = "日期"
|
||||||
const feideeMymoneyCsvFileTransactionTypeIncomeText = "收入"
|
const feideeMymoneyAppTransactionTypeColumnName = "交易类型"
|
||||||
const feideeMymoneyCsvFileTransactionTypeExpenseText = "支出"
|
const feideeMymoneyAppTransactionCategoryColumnName = "类别"
|
||||||
const feideeMymoneyCsvFileTransactionTypeTransferInText = "转入"
|
const feideeMymoneyAppTransactionSubCategoryColumnName = "子类别"
|
||||||
const feideeMymoneyCsvFileTransactionTypeTransferOutText = "转出"
|
const feideeMymoneyAppTransactionAccountNameColumnName = "账户"
|
||||||
|
const feideeMymoneyAppTransactionAccountCurrencyColumnName = "账户币种"
|
||||||
|
const feideeMymoneyAppTransactionAmountColumnName = "金额"
|
||||||
|
const feideeMymoneyAppTransactionDescriptionColumnName = "备注"
|
||||||
|
const feideeMymoneyAppTransactionRelatedIdColumnName = "关联Id"
|
||||||
|
|
||||||
|
const feideeMymoneyAppTransactionTypeModifyBalanceText = "余额变更"
|
||||||
|
const feideeMymoneyAppTransactionTypeIncomeText = "收入"
|
||||||
|
const feideeMymoneyAppTransactionTypeExpenseText = "支出"
|
||||||
|
const feideeMymoneyAppTransactionTypeTransferInText = "转入"
|
||||||
|
const feideeMymoneyAppTransactionTypeTransferOutText = "转出"
|
||||||
|
|
||||||
|
var feideeMymoneyAppDataColumnNameMapping = map[datatable.TransactionDataTableColumn]string{
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: feideeMymoneyAppTransactionTimeColumnName,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: feideeMymoneyAppTransactionTypeColumnName,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_CATEGORY: feideeMymoneyAppTransactionCategoryColumnName,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: feideeMymoneyAppTransactionSubCategoryColumnName,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: feideeMymoneyAppTransactionAccountNameColumnName,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY: feideeMymoneyAppTransactionAccountCurrencyColumnName,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: feideeMymoneyAppTransactionAmountColumnName,
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: feideeMymoneyAppTransactionDescriptionColumnName,
|
||||||
|
}
|
||||||
|
|
||||||
// feideeMymoneyAppTransactionDataCsvFileImporter defines the structure of feidee mymoney app csv importer for transaction data
|
// feideeMymoneyAppTransactionDataCsvFileImporter defines the structure of feidee mymoney app csv importer for transaction data
|
||||||
type feideeMymoneyAppTransactionDataCsvFileImporter struct{}
|
type feideeMymoneyAppTransactionDataCsvFileImporter struct{}
|
||||||
@@ -32,173 +54,44 @@ var (
|
|||||||
// ParseImportedData returns the imported data by parsing the feidee mymoney app transaction csv data
|
// ParseImportedData returns the imported data by parsing the feidee mymoney app transaction csv data
|
||||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) 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) {
|
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) 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) {
|
||||||
content := string(data)
|
content := string(data)
|
||||||
|
dataTable, err := c.createNewFeideeMymoneyAppImportedDataTable(ctx, content)
|
||||||
if strings.Index(content, feideeMymoneyTransactionDataCsvFileHeader) != 0 && strings.Index(content, feideeMymoneyTransactionDataCsvFileHeaderWithUtf8Bom) != 0 {
|
|
||||||
return nil, nil, nil, nil, nil, nil, errs.ErrInvalidFileHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
allLines, err := c.parseAllLinesFromCsvData(ctx, content)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allLines) < 2 {
|
commonDataTable := datatable.CreateNewImportedCommonDataTable(dataTable)
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.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]
|
if !commonDataTable.HasColumn(feideeMymoneyAppTransactionTimeColumnName) ||
|
||||||
headerItemMap := make(map[string]int)
|
!commonDataTable.HasColumn(feideeMymoneyAppTransactionTypeColumnName) ||
|
||||||
|
!commonDataTable.HasColumn(feideeMymoneyAppTransactionSubCategoryColumnName) ||
|
||||||
for i := 0; i < len(headerLineItems); i++ {
|
!commonDataTable.HasColumn(feideeMymoneyAppTransactionAccountNameColumnName) ||
|
||||||
headerItemMap[headerLineItems[i]] = i
|
!commonDataTable.HasColumn(feideeMymoneyAppTransactionAmountColumnName) ||
|
||||||
}
|
!commonDataTable.HasColumn(feideeMymoneyAppTransactionRelatedIdColumnName) {
|
||||||
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.ParseImportedData] cannot parse import data, because missing essential columns in header row")
|
||||||
timeColumnIdx, timeColumnExists := headerItemMap["日期"]
|
|
||||||
typeColumnIdx, typeColumnExists := headerItemMap["交易类型"]
|
|
||||||
categoryColumnIdx, categoryColumnExists := headerItemMap["类别"]
|
|
||||||
subCategoryColumnIdx, subCategoryColumnExists := headerItemMap["子类别"]
|
|
||||||
accountColumnIdx, accountColumnExists := headerItemMap["账户"]
|
|
||||||
accountCurrencyColumnIdx, accountCurrencyColumnExists := headerItemMap["账户币种"]
|
|
||||||
amountColumnIdx, amountColumnExists := headerItemMap["金额"]
|
|
||||||
descriptionColumnIdx, descriptionColumnExists := headerItemMap["备注"]
|
|
||||||
relatedIdColumnIdx, relatedIdColumnExists := headerItemMap["关联Id"]
|
|
||||||
|
|
||||||
if !timeColumnExists || !typeColumnExists || !subCategoryColumnExists ||
|
|
||||||
!accountColumnExists || !amountColumnExists || !relatedIdColumnExists {
|
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.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
|
return nil, nil, nil, nil, nil, nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||||
}
|
}
|
||||||
|
|
||||||
newColumns := make([]datatable.TransactionDataTableColumn, 0, 11)
|
transactionDataTable, err := c.createNewFeideeMymoneyAppTransactionDataTable(ctx, commonDataTable)
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE)
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME)
|
|
||||||
|
|
||||||
if categoryColumnExists {
|
if err != nil {
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_CATEGORY)
|
return nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
|
||||||
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY)
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME)
|
|
||||||
|
|
||||||
if accountCurrencyColumnExists {
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY)
|
|
||||||
}
|
|
||||||
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_AMOUNT)
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME)
|
|
||||||
|
|
||||||
if accountCurrencyColumnExists {
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY)
|
|
||||||
}
|
|
||||||
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT)
|
|
||||||
|
|
||||||
if descriptionColumnExists {
|
|
||||||
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_DESCRIPTION)
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionRowParser := createFeideeMymoneyTransactionDataRowParser()
|
|
||||||
dataTable := datatable.CreateNewWritableTransactionDataTableWithRowParser(newColumns, transactionRowParser)
|
|
||||||
transferTransactionsMap := make(map[string]map[datatable.TransactionDataTableColumn]string, 0)
|
|
||||||
|
|
||||||
for i := 1; i < len(allLines); i++ {
|
|
||||||
items := allLines[i]
|
|
||||||
|
|
||||||
if len(items) < len(headerLineItems) {
|
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.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
|
|
||||||
}
|
|
||||||
|
|
||||||
data, relatedId := c.parseTransactionData(items,
|
|
||||||
timeColumnIdx,
|
|
||||||
timeColumnExists,
|
|
||||||
typeColumnIdx,
|
|
||||||
typeColumnExists,
|
|
||||||
categoryColumnIdx,
|
|
||||||
categoryColumnExists,
|
|
||||||
subCategoryColumnIdx,
|
|
||||||
subCategoryColumnExists,
|
|
||||||
accountColumnIdx,
|
|
||||||
accountColumnExists,
|
|
||||||
accountCurrencyColumnIdx,
|
|
||||||
accountCurrencyColumnExists,
|
|
||||||
amountColumnIdx,
|
|
||||||
amountColumnExists,
|
|
||||||
descriptionColumnIdx,
|
|
||||||
descriptionColumnExists,
|
|
||||||
relatedIdColumnIdx,
|
|
||||||
relatedIdColumnExists,
|
|
||||||
)
|
|
||||||
|
|
||||||
transactionType := data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE]
|
|
||||||
|
|
||||||
if transactionType == feideeMymoneyCsvFileTransactionTypeModifyBalanceText || transactionType == feideeMymoneyCsvFileTransactionTypeIncomeText || transactionType == feideeMymoneyCsvFileTransactionTypeExpenseText {
|
|
||||||
if transactionType == feideeMymoneyCsvFileTransactionTypeModifyBalanceText {
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE]
|
|
||||||
} else if transactionType == feideeMymoneyCsvFileTransactionTypeIncomeText {
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME]
|
|
||||||
} else if transactionType == feideeMymoneyCsvFileTransactionTypeExpenseText {
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE]
|
|
||||||
}
|
|
||||||
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = ""
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = ""
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = ""
|
|
||||||
dataTable.Add(data)
|
|
||||||
} else if transactionType == feideeMymoneyCsvFileTransactionTypeTransferInText || transactionType == feideeMymoneyCsvFileTransactionTypeTransferOutText {
|
|
||||||
if relatedId == "" {
|
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.ParseImportedData] transfer transaction has blank related id in row \"index:%d\" for user \"uid:%d\"", i, user.Uid)
|
|
||||||
return nil, nil, nil, nil, nil, nil, errs.ErrRelatedIdCannotBeBlank
|
|
||||||
}
|
|
||||||
|
|
||||||
relatedData, exists := transferTransactionsMap[relatedId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
transferTransactionsMap[relatedId] = data
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if transactionType == feideeMymoneyCsvFileTransactionTypeTransferInText && relatedData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == feideeMymoneyCsvFileTransactionTypeTransferOutText {
|
|
||||||
relatedData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
|
|
||||||
relatedData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
|
|
||||||
relatedData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY]
|
|
||||||
relatedData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = data[datatable.TRANSACTION_DATA_TABLE_AMOUNT]
|
|
||||||
dataTable.Add(relatedData)
|
|
||||||
delete(transferTransactionsMap, relatedId)
|
|
||||||
} else if transactionType == feideeMymoneyCsvFileTransactionTypeTransferOutText && relatedData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == feideeMymoneyCsvFileTransactionTypeTransferInText {
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = relatedData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = relatedData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY]
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = relatedData[datatable.TRANSACTION_DATA_TABLE_AMOUNT]
|
|
||||||
dataTable.Add(data)
|
|
||||||
delete(transferTransactionsMap, relatedId)
|
|
||||||
} else {
|
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.ParseImportedData] transfer transaction type \"%s\" is not expected in row \"index:%d\" for user \"uid:%d\"", transactionType, i, user.Uid)
|
|
||||||
return nil, nil, nil, nil, nil, nil, errs.ErrTransactionTypeInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.ParseImportedData] cannot parse transaction type \"%s\" in row \"index:%d\" for user \"uid:%d\"", transactionType, i, user.Uid)
|
|
||||||
return nil, nil, nil, nil, nil, nil, errs.ErrTransactionTypeInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(transferTransactionsMap) > 0 {
|
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.ParseImportedData] there are %d transactions (related id is %s) which don't have related records", len(transferTransactionsMap), c.getRelatedIds(transferTransactionsMap))
|
|
||||||
return nil, nil, nil, nil, nil, nil, errs.ErrFoundRecordNotHasRelatedRecord
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dataTableImporter := datatable.CreateNewSimpleImporter(feideeMymoneyTransactionTypeNameMapping)
|
dataTableImporter := datatable.CreateNewSimpleImporter(feideeMymoneyTransactionTypeNameMapping)
|
||||||
|
|
||||||
return dataTableImporter.ParseImportedData(ctx, user, dataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) parseAllLinesFromCsvData(ctx core.Context, content string) ([][]string, error) {
|
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyAppImportedDataTable(ctx core.Context, content string) (datatable.ImportedDataTable, error) {
|
||||||
|
if strings.Index(content, feideeMymoneyAppTransactionDataCsvFileHeader) != 0 && strings.Index(content, feideeMymoneyAppTransactionDataCsvFileHeaderWithUtf8Bom) != 0 {
|
||||||
|
return nil, errs.ErrInvalidFileHeader
|
||||||
|
}
|
||||||
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(content))
|
csvReader := csv.NewReader(strings.NewReader(content))
|
||||||
csvReader.FieldsPerRecord = -1
|
csvReader.FieldsPerRecord = -1
|
||||||
|
|
||||||
allLines := make([][]string, 0)
|
allOriginalLines := make([][]string, 0)
|
||||||
hasFileHeader := false
|
hasFileHeader := false
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -209,91 +102,156 @@ func (c *feideeMymoneyAppTransactionDataCsvFileImporter) parseAllLinesFromCsvDat
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.parseAllLinesFromCsvData] cannot parse feidee mymoney csv data, because %s", err.Error())
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] cannot parse feidee mymoney csv data, because %s", err.Error())
|
||||||
return nil, errs.ErrInvalidCSVFile
|
return nil, errs.ErrInvalidCSVFile
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasFileHeader {
|
if !hasFileHeader {
|
||||||
if len(items) <= 0 {
|
if len(items) <= 0 {
|
||||||
continue
|
continue
|
||||||
} else if strings.Index(items[0], feideeMymoneyTransactionDataCsvFileHeader) == 0 || strings.Index(items[0], feideeMymoneyTransactionDataCsvFileHeaderWithUtf8Bom) == 0 {
|
} else if strings.Index(items[0], feideeMymoneyAppTransactionDataCsvFileHeader) == 0 || strings.Index(items[0], feideeMymoneyAppTransactionDataCsvFileHeaderWithUtf8Bom) == 0 {
|
||||||
hasFileHeader = true
|
hasFileHeader = true
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
log.Warnf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.parseAllLinesFromCsvData] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
log.Warnf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allLines = append(allLines, items)
|
allOriginalLines = append(allOriginalLines, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
return allLines, nil
|
if !hasFileHeader {
|
||||||
|
return nil, errs.ErrInvalidFileHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allOriginalLines) < 2 {
|
||||||
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] cannot parse import data, because data table row count is less 1")
|
||||||
|
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTable := csvdatatable.CreateNewCustomCsvImportedDataTable(allOriginalLines)
|
||||||
|
|
||||||
|
return dataTable, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) parseTransactionData(
|
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) createNewFeideeMymoneyAppTransactionDataTable(ctx core.Context, commonDataTable datatable.CommonDataTable) (datatable.TransactionDataTable, error) {
|
||||||
items []string,
|
newColumns := make([]datatable.TransactionDataTableColumn, 0, 11)
|
||||||
timeColumnIdx int,
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE)
|
||||||
timeColumnExists bool,
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME)
|
||||||
typeColumnIdx int,
|
|
||||||
typeColumnExists bool,
|
|
||||||
categoryColumnIdx int,
|
|
||||||
categoryColumnExists bool,
|
|
||||||
subCategoryColumnIdx int,
|
|
||||||
subCategoryColumnExists bool,
|
|
||||||
accountColumnIdx int,
|
|
||||||
accountColumnExists bool,
|
|
||||||
accountCurrencyColumnIdx int,
|
|
||||||
accountCurrencyColumnExists bool,
|
|
||||||
amountColumnIdx int,
|
|
||||||
amountColumnExists bool,
|
|
||||||
descriptionColumnIdx int,
|
|
||||||
descriptionColumnExists bool,
|
|
||||||
relatedIdColumnIdx int,
|
|
||||||
relatedIdColumnExists bool,
|
|
||||||
) (map[datatable.TransactionDataTableColumn]string, string) {
|
|
||||||
data := make(map[datatable.TransactionDataTableColumn]string, 11)
|
|
||||||
relatedId := ""
|
|
||||||
|
|
||||||
if timeColumnExists && timeColumnIdx < len(items) {
|
if commonDataTable.HasColumn(feideeMymoneyAppTransactionCategoryColumnName) {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = items[timeColumnIdx]
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_CATEGORY)
|
||||||
}
|
}
|
||||||
|
|
||||||
if typeColumnExists && typeColumnIdx < len(items) {
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY)
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = items[typeColumnIdx]
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME)
|
||||||
|
|
||||||
|
if commonDataTable.HasColumn(feideeMymoneyAppTransactionAccountCurrencyColumnName) {
|
||||||
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY)
|
||||||
}
|
}
|
||||||
|
|
||||||
if categoryColumnExists && categoryColumnIdx < len(items) {
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_AMOUNT)
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = items[categoryColumnIdx]
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME)
|
||||||
|
|
||||||
|
if commonDataTable.HasColumn(feideeMymoneyAppTransactionAccountCurrencyColumnName) {
|
||||||
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY)
|
||||||
}
|
}
|
||||||
|
|
||||||
if subCategoryColumnExists && subCategoryColumnIdx < len(items) {
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT)
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = items[subCategoryColumnIdx]
|
|
||||||
|
if commonDataTable.HasColumn(feideeMymoneyAppTransactionDescriptionColumnName) {
|
||||||
|
newColumns = append(newColumns, datatable.TRANSACTION_DATA_TABLE_DESCRIPTION)
|
||||||
}
|
}
|
||||||
|
|
||||||
if accountColumnExists && accountColumnIdx < len(items) {
|
transactionRowParser := createFeideeMymoneyTransactionDataRowParser()
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = items[accountColumnIdx]
|
transactionDataTable := datatable.CreateNewWritableTransactionDataTableWithRowParser(newColumns, transactionRowParser)
|
||||||
|
transferTransactionsMap := make(map[string]map[datatable.TransactionDataTableColumn]string, 0)
|
||||||
|
|
||||||
|
commonDataTableIterator := commonDataTable.DataRowIterator()
|
||||||
|
|
||||||
|
for commonDataTableIterator.HasNext() {
|
||||||
|
dataRow := commonDataTableIterator.Next()
|
||||||
|
rowId := commonDataTableIterator.CurrentRowId()
|
||||||
|
|
||||||
|
if dataRow.ColumnCount() < commonDataTable.HeaderColumnCount() {
|
||||||
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] cannot parse row \"%s\", because may missing some columns (column count %d in data row is less than header column count %d)", rowId, dataRow.ColumnCount(), commonDataTable.HeaderColumnCount())
|
||||||
|
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[datatable.TransactionDataTableColumn]string, 11)
|
||||||
|
relatedId := ""
|
||||||
|
|
||||||
|
for columnType, columnName := range feideeMymoneyAppDataColumnNameMapping {
|
||||||
|
if dataRow.HasData(columnName) {
|
||||||
|
data[columnType] = dataRow.GetData(columnName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataRow.HasData(feideeMymoneyAppTransactionRelatedIdColumnName) {
|
||||||
|
relatedId = dataRow.GetData(feideeMymoneyAppTransactionRelatedIdColumnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionType := data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE]
|
||||||
|
|
||||||
|
if transactionType == feideeMymoneyAppTransactionTypeModifyBalanceText || transactionType == feideeMymoneyAppTransactionTypeIncomeText || transactionType == feideeMymoneyAppTransactionTypeExpenseText {
|
||||||
|
if transactionType == feideeMymoneyAppTransactionTypeModifyBalanceText {
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE]
|
||||||
|
} else if transactionType == feideeMymoneyAppTransactionTypeIncomeText {
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME]
|
||||||
|
} else if transactionType == feideeMymoneyAppTransactionTypeExpenseText {
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE]
|
||||||
|
}
|
||||||
|
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = ""
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = ""
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = ""
|
||||||
|
transactionDataTable.Add(data)
|
||||||
|
} else if transactionType == feideeMymoneyAppTransactionTypeTransferInText || transactionType == feideeMymoneyAppTransactionTypeTransferOutText {
|
||||||
|
if relatedId == "" {
|
||||||
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] transfer transaction has blank related id in row \"%s\"", rowId)
|
||||||
|
return nil, errs.ErrRelatedIdCannotBeBlank
|
||||||
|
}
|
||||||
|
|
||||||
|
relatedData, exists := transferTransactionsMap[relatedId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
transferTransactionsMap[relatedId] = data
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if transactionType == feideeMymoneyAppTransactionTypeTransferInText && relatedData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == feideeMymoneyAppTransactionTypeTransferOutText {
|
||||||
|
relatedData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
|
||||||
|
relatedData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
|
||||||
|
relatedData[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY]
|
||||||
|
relatedData[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = data[datatable.TRANSACTION_DATA_TABLE_AMOUNT]
|
||||||
|
transactionDataTable.Add(relatedData)
|
||||||
|
delete(transferTransactionsMap, relatedId)
|
||||||
|
} else if transactionType == feideeMymoneyAppTransactionTypeTransferOutText && relatedData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == feideeMymoneyAppTransactionTypeTransferInText {
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = feideeMymoneyTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = relatedData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = relatedData[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY]
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = relatedData[datatable.TRANSACTION_DATA_TABLE_AMOUNT]
|
||||||
|
transactionDataTable.Add(data)
|
||||||
|
delete(transferTransactionsMap, relatedId)
|
||||||
|
} else {
|
||||||
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] transfer transaction type \"%s\" is not expected in row \"%s\"", transactionType, rowId)
|
||||||
|
return nil, errs.ErrTransactionTypeInvalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] cannot parse transaction type \"%s\" in row \"%s\"", transactionType, rowId)
|
||||||
|
return nil, errs.ErrTransactionTypeInvalid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if accountCurrencyColumnExists && accountCurrencyColumnIdx < len(items) {
|
if len(transferTransactionsMap) > 0 {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = items[accountCurrencyColumnIdx]
|
log.Errorf(ctx, "[feidee_mymoney_app_transaction_data_csv_file_importer.createNewFeideeMymoneyAppTransactionDataTable] there are %d transactions (related id is %s) which don't have related records", len(transferTransactionsMap), c.getFeideeMymoneyAppRelatedTransactionIds(transferTransactionsMap))
|
||||||
|
return nil, errs.ErrFoundRecordNotHasRelatedRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
if amountColumnExists && amountColumnIdx < len(items) {
|
return transactionDataTable, nil
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = items[amountColumnIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
if descriptionColumnExists && descriptionColumnIdx < len(items) {
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = items[descriptionColumnIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
if relatedIdColumnExists && relatedIdColumnIdx < len(items) {
|
|
||||||
relatedId = items[relatedIdColumnIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, relatedId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) getRelatedIds(transferTransactionsMap map[string]map[datatable.TransactionDataTableColumn]string) string {
|
func (c *feideeMymoneyAppTransactionDataCsvFileImporter) getFeideeMymoneyAppRelatedTransactionIds(transferTransactionsMap map[string]map[datatable.TransactionDataTableColumn]string) string {
|
||||||
builder := strings.Builder{}
|
builder := strings.Builder{}
|
||||||
|
|
||||||
for relatedId := range transferTransactionsMap {
|
for relatedId := range transferTransactionsMap {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package feidee
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
var feideeMymoneyDataColumnNameMapping = map[datatable.TransactionDataTableColumn]string{
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "日期",
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "交易类型",
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_CATEGORY: "分类",
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: "子分类",
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "账户1",
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: "金额",
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
|
||||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "备注",
|
|
||||||
}
|
|
||||||
|
|
||||||
var feideeMymoneyTransactionTypeNameMapping = map[models.TransactionType]string{
|
|
||||||
models.TRANSACTION_TYPE_MODIFY_BALANCE: "余额变更",
|
|
||||||
models.TRANSACTION_TYPE_INCOME: "收入",
|
|
||||||
models.TRANSACTION_TYPE_EXPENSE: "支出",
|
|
||||||
models.TRANSACTION_TYPE_TRANSFER: "转账",
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,13 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var feideeMymoneyTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||||
|
models.TRANSACTION_TYPE_MODIFY_BALANCE: "余额变更",
|
||||||
|
models.TRANSACTION_TYPE_INCOME: "收入",
|
||||||
|
models.TRANSACTION_TYPE_EXPENSE: "支出",
|
||||||
|
models.TRANSACTION_TYPE_TRANSFER: "转账",
|
||||||
|
}
|
||||||
|
|
||||||
// feideeMymoneyTransactionDataRowParser defines the structure of feidee mymoney transaction data row parser
|
// feideeMymoneyTransactionDataRowParser defines the structure of feidee mymoney transaction data row parser
|
||||||
type feideeMymoneyTransactionDataRowParser struct {
|
type feideeMymoneyTransactionDataRowParser struct {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,17 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var feideeMymoneyWebDataColumnNameMapping = map[datatable.TransactionDataTableColumn]string{
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "日期",
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "交易类型",
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_CATEGORY: "分类",
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: "子分类",
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "账户1",
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_AMOUNT: "金额",
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
|
||||||
|
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "备注",
|
||||||
|
}
|
||||||
|
|
||||||
// feideeMymoneyWebTransactionDataXlsFileImporter defines the structure of feidee mymoney (web) xls importer for transaction data
|
// feideeMymoneyWebTransactionDataXlsFileImporter defines the structure of feidee mymoney (web) xls importer for transaction data
|
||||||
type feideeMymoneyWebTransactionDataXlsFileImporter struct {
|
type feideeMymoneyWebTransactionDataXlsFileImporter struct {
|
||||||
datatable.DataTableTransactionDataImporter
|
datatable.DataTableTransactionDataImporter
|
||||||
@@ -26,7 +37,7 @@ func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx c
|
|||||||
}
|
}
|
||||||
|
|
||||||
transactionRowParser := createFeideeMymoneyTransactionDataRowParser()
|
transactionRowParser := createFeideeMymoneyTransactionDataRowParser()
|
||||||
transactionDataTable := datatable.CreateImportedTransactionDataTableWithRowParser(dataTable, feideeMymoneyDataColumnNameMapping, transactionRowParser)
|
transactionDataTable := datatable.CreateNewImportedTransactionDataTableWithRowParser(dataTable, feideeMymoneyWebDataColumnNameMapping, transactionRowParser)
|
||||||
dataTableImporter := datatable.CreateNewSimpleImporter(feideeMymoneyTransactionTypeNameMapping)
|
dataTableImporter := datatable.CreateNewSimpleImporter(feideeMymoneyTransactionTypeNameMapping)
|
||||||
|
|
||||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ var (
|
|||||||
// ParseImportedData returns the imported data by parsing the firefly III transaction csv data
|
// ParseImportedData returns the imported data by parsing the firefly III transaction csv data
|
||||||
func (c *fireflyIIITransactionDataCsvFileImporter) 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) {
|
func (c *fireflyIIITransactionDataCsvFileImporter) 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) {
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
dataTable, err := csv.CreateNewCsvDataTable(ctx, reader)
|
dataTable, err := csv.CreateNewCsvImportedDataTable(ctx, reader)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionRowParser := createFireflyIIITransactionDataRowParser()
|
transactionRowParser := createFireflyIIITransactionDataRowParser()
|
||||||
transactionDataTable := datatable.CreateImportedTransactionDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
|
transactionDataTable := datatable.CreateNewImportedTransactionDataTableWithRowParser(dataTable, fireflyIIITransactionDataColumnNameMapping, transactionRowParser)
|
||||||
dataTableImporter := datatable.CreateNewImporter(fireflyIIITransactionTypeNameMapping, "", ",")
|
dataTableImporter := datatable.CreateNewImporter(fireflyIIITransactionTypeNameMapping, "", ",")
|
||||||
|
|
||||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package wechat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
csvdatatable "github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
@@ -19,6 +19,15 @@ const wechatPayTransactionDataCsvFileHeader = "微信支付账单明细"
|
|||||||
const wechatPayTransactionDataCsvFileHeaderWithUtf8Bom = "\xEF\xBB\xBF" + wechatPayTransactionDataCsvFileHeader
|
const wechatPayTransactionDataCsvFileHeaderWithUtf8Bom = "\xEF\xBB\xBF" + wechatPayTransactionDataCsvFileHeader
|
||||||
const wechatPayTransactionDataHeaderStartContentBeginning = "----------------------微信支付账单明细列表--------------------"
|
const wechatPayTransactionDataHeaderStartContentBeginning = "----------------------微信支付账单明细列表--------------------"
|
||||||
|
|
||||||
|
const wechatPayTransactionTimeColumnName = "交易时间"
|
||||||
|
const wechatPayTransactionCategoryColumnName = "交易类型"
|
||||||
|
const wechatPayTransactionProductNameColumnName = "商品"
|
||||||
|
const wechatPayTransactionTypeColumnName = "收/支"
|
||||||
|
const wechatPayTransactionAmountColumnName = "金额(元)"
|
||||||
|
const wechatPayTransactionRelatedAccountColumnName = "支付方式"
|
||||||
|
const wechatPayTransactionStatusColumnName = "当前状态"
|
||||||
|
const wechatPayTransactionDescriptionColumnName = "备注"
|
||||||
|
|
||||||
const wechatPayTransactionDataCategoryTransferToWeChatWallet = "零钱充值"
|
const wechatPayTransactionDataCategoryTransferToWeChatWallet = "零钱充值"
|
||||||
const wechatPayTransactionDataCategoryTransferFromWeChatWallet = "零钱提现"
|
const wechatPayTransactionDataCategoryTransferFromWeChatWallet = "零钱提现"
|
||||||
|
|
||||||
@@ -34,33 +43,21 @@ var wechatPayTransactionSupportedColumns = map[datatable.TransactionDataTableCol
|
|||||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: true,
|
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// wechatPayTransactionDataTable defines the structure of wechatPay transaction plain text data table
|
// wechatPayTransactionDataTable defines the structure of wechat pay transaction plain text data table
|
||||||
type wechatPayTransactionDataTable struct {
|
type wechatPayTransactionDataTable struct {
|
||||||
allOriginalLines [][]string
|
innerDataTable datatable.CommonDataTable
|
||||||
originalHeaderLineColumnNames []string
|
|
||||||
originalTimeColumnIndex int
|
|
||||||
originalCategoryColumnIndex int
|
|
||||||
originalTargetNameColumnIndex int
|
|
||||||
originalProductNameColumnIndex int
|
|
||||||
originalTypeColumnIndex int
|
|
||||||
originalAmountColumnIndex int
|
|
||||||
originalRelatedAccountColumnIndex int
|
|
||||||
originalStatusColumnIndex int
|
|
||||||
originalDescriptionColumnIndex int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wechatPayTransactionDataRow defines the structure of wechatPay transaction plain text data row
|
// wechatPayTransactionDataRow defines the structure of wechat pay transaction plain text data row
|
||||||
type wechatPayTransactionDataRow struct {
|
type wechatPayTransactionDataRow struct {
|
||||||
dataTable *wechatPayTransactionDataTable
|
isValid bool
|
||||||
isValid bool
|
finalItems map[datatable.TransactionDataTableColumn]string
|
||||||
originalItems []string
|
|
||||||
finalItems map[datatable.TransactionDataTableColumn]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wechatPayTransactionDataRowIterator defines the structure of wechatPay transaction plain text data row iterator
|
// wechatPayTransactionDataRowIterator defines the structure of wechat pay transaction plain text data row iterator
|
||||||
type wechatPayTransactionDataRowIterator struct {
|
type wechatPayTransactionDataRowIterator struct {
|
||||||
dataTable *wechatPayTransactionDataTable
|
dataTable *wechatPayTransactionDataTable
|
||||||
currentIndex int
|
innerIterator datatable.CommonDataRowIterator
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasColumn returns whether the transaction data table has specified column
|
// HasColumn returns whether the transaction data table has specified column
|
||||||
@@ -71,18 +68,14 @@ func (t *wechatPayTransactionDataTable) HasColumn(column datatable.TransactionDa
|
|||||||
|
|
||||||
// TransactionRowCount returns the total count of transaction data row
|
// TransactionRowCount returns the total count of transaction data row
|
||||||
func (t *wechatPayTransactionDataTable) TransactionRowCount() int {
|
func (t *wechatPayTransactionDataTable) TransactionRowCount() int {
|
||||||
if len(t.allOriginalLines) < 1 {
|
return t.innerDataTable.DataRowCount()
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(t.allOriginalLines) - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionRowIterator returns the iterator of transaction data row
|
// TransactionRowIterator returns the iterator of transaction data row
|
||||||
func (t *wechatPayTransactionDataTable) TransactionRowIterator() datatable.TransactionDataRowIterator {
|
func (t *wechatPayTransactionDataTable) TransactionRowIterator() datatable.TransactionDataRowIterator {
|
||||||
return &wechatPayTransactionDataRowIterator{
|
return &wechatPayTransactionDataRowIterator{
|
||||||
dataTable: t,
|
dataTable: t,
|
||||||
currentIndex: 0,
|
innerIterator: t.innerDataTable.DataRowIterator(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,89 +97,80 @@ func (r *wechatPayTransactionDataRow) GetData(column datatable.TransactionDataTa
|
|||||||
|
|
||||||
// HasNext returns whether the iterator does not reach the end
|
// HasNext returns whether the iterator does not reach the end
|
||||||
func (t *wechatPayTransactionDataRowIterator) HasNext() bool {
|
func (t *wechatPayTransactionDataRowIterator) HasNext() bool {
|
||||||
return t.currentIndex+1 < len(t.dataTable.allOriginalLines)
|
return t.innerIterator.HasNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next imported data row
|
// Next returns the next imported data row
|
||||||
func (t *wechatPayTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow datatable.TransactionDataRow, err error) {
|
func (t *wechatPayTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow datatable.TransactionDataRow, err error) {
|
||||||
if t.currentIndex+1 >= len(t.dataTable.allOriginalLines) {
|
importedRow := t.innerIterator.Next()
|
||||||
|
|
||||||
|
if importedRow == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
t.currentIndex++
|
finalItems, isValid, err := t.dataTable.parseTransactionData(ctx, user, importedRow, t.innerIterator.CurrentRowId())
|
||||||
|
|
||||||
rowItems := t.dataTable.allOriginalLines[t.currentIndex]
|
if err != nil {
|
||||||
isValid := true
|
return nil, err
|
||||||
|
|
||||||
if t.dataTable.originalTypeColumnIndex >= 0 &&
|
|
||||||
rowItems[t.dataTable.originalTypeColumnIndex] != wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] &&
|
|
||||||
rowItems[t.dataTable.originalTypeColumnIndex] != wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] &&
|
|
||||||
rowItems[t.dataTable.originalTypeColumnIndex] != wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
|
||||||
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.Next] skip parsing transaction in row \"index:%d\", because type is \"%s\"", t.currentIndex, rowItems[t.dataTable.originalTypeColumnIndex])
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
|
|
||||||
var finalItems map[datatable.TransactionDataTableColumn]string
|
|
||||||
var errMsg string
|
|
||||||
|
|
||||||
if isValid {
|
|
||||||
finalItems, errMsg = t.dataTable.parseTransactionData(ctx, user, rowItems)
|
|
||||||
|
|
||||||
if finalItems == nil {
|
|
||||||
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.Next] skip parsing transaction in row \"index:%d\", because %s", t.currentIndex, errMsg)
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &wechatPayTransactionDataRow{
|
return &wechatPayTransactionDataRow{
|
||||||
dataTable: t.dataTable,
|
isValid: isValid,
|
||||||
isValid: isValid,
|
finalItems: finalItems,
|
||||||
originalItems: rowItems,
|
|
||||||
finalItems: finalItems,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *wechatPayTransactionDataTable) parseTransactionData(ctx core.Context, user *models.User, items []string) (map[datatable.TransactionDataTableColumn]string, string) {
|
func (t *wechatPayTransactionDataTable) hasOriginalColumn(columnName string) bool {
|
||||||
|
return columnName != "" && t.innerDataTable.HasColumn(columnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *wechatPayTransactionDataTable) parseTransactionData(ctx core.Context, user *models.User, dataRow datatable.CommonDataRow, rowId string) (map[datatable.TransactionDataTableColumn]string, bool, error) {
|
||||||
|
if dataRow.GetData(wechatPayTransactionTypeColumnName) != wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] &&
|
||||||
|
dataRow.GetData(wechatPayTransactionTypeColumnName) != wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] &&
|
||||||
|
dataRow.GetData(wechatPayTransactionTypeColumnName) != wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
||||||
|
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.parseTransactionData] skip parsing transaction in row \"%s\", because type is \"%s\"", rowId, dataRow.GetData(wechatPayTransactionTypeColumnName))
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
data := make(map[datatable.TransactionDataTableColumn]string, len(wechatPayTransactionSupportedColumns))
|
data := make(map[datatable.TransactionDataTableColumn]string, len(wechatPayTransactionSupportedColumns))
|
||||||
|
|
||||||
if t.originalTimeColumnIndex >= 0 && t.originalTimeColumnIndex < len(items) {
|
if t.hasOriginalColumn(wechatPayTransactionTimeColumnName) {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = items[t.originalTimeColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = dataRow.GetData(wechatPayTransactionTimeColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.originalCategoryColumnIndex >= 0 && t.originalCategoryColumnIndex < len(items) {
|
if t.hasOriginalColumn(wechatPayTransactionCategoryColumnName) {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = items[t.originalCategoryColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(wechatPayTransactionCategoryColumnName)
|
||||||
} else {
|
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.originalAmountColumnIndex >= 0 && t.originalAmountColumnIndex < len(items) {
|
if t.hasOriginalColumn(wechatPayTransactionAmountColumnName) {
|
||||||
amount, success := utils.ParseFirstConsecutiveNumber(items[t.originalAmountColumnIndex])
|
amount, success := utils.ParseFirstConsecutiveNumber(dataRow.GetData(wechatPayTransactionAmountColumnName))
|
||||||
|
|
||||||
if success {
|
if !success {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = amount
|
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.parseTransactionData] cannot parse amount \"%s\" of transaction in row \"%s\"", dataRow.GetData(wechatPayTransactionAmountColumnName), rowId)
|
||||||
} else {
|
return nil, false, errs.ErrAmountInvalid
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = items[t.originalAmountColumnIndex]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.originalDescriptionColumnIndex >= 0 && t.originalDescriptionColumnIndex < len(items) && items[t.originalDescriptionColumnIndex] != "" && items[t.originalDescriptionColumnIndex] != "/" {
|
if t.hasOriginalColumn(wechatPayTransactionDescriptionColumnName) && dataRow.GetData(wechatPayTransactionDescriptionColumnName) != "" && dataRow.GetData(wechatPayTransactionDescriptionColumnName) != "/" {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = items[t.originalDescriptionColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = dataRow.GetData(wechatPayTransactionDescriptionColumnName)
|
||||||
} else if t.originalProductNameColumnIndex >= 0 && t.originalProductNameColumnIndex < len(items) && items[t.originalProductNameColumnIndex] != "" && items[t.originalProductNameColumnIndex] != "/" {
|
} else if t.hasOriginalColumn(wechatPayTransactionProductNameColumnName) && dataRow.GetData(wechatPayTransactionProductNameColumnName) != "" && dataRow.GetData(wechatPayTransactionProductNameColumnName) != "/" {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = items[t.originalProductNameColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = dataRow.GetData(wechatPayTransactionProductNameColumnName)
|
||||||
} else {
|
} else {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = ""
|
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
relatedAccountName := ""
|
relatedAccountName := ""
|
||||||
|
|
||||||
if t.originalRelatedAccountColumnIndex >= 0 && t.originalRelatedAccountColumnIndex < len(items) {
|
if t.hasOriginalColumn(wechatPayTransactionRelatedAccountColumnName) {
|
||||||
relatedAccountName = items[t.originalRelatedAccountColumnIndex]
|
relatedAccountName = dataRow.GetData(wechatPayTransactionRelatedAccountColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusName := ""
|
statusName := ""
|
||||||
|
|
||||||
if t.originalStatusColumnIndex >= 0 && t.originalStatusColumnIndex < len(items) {
|
if t.hasOriginalColumn(wechatPayTransactionStatusColumnName) {
|
||||||
statusName = items[t.originalStatusColumnIndex]
|
statusName = dataRow.GetData(wechatPayTransactionStatusColumnName)
|
||||||
}
|
}
|
||||||
|
|
||||||
locale := user.Language
|
locale := user.Language
|
||||||
@@ -197,17 +181,17 @@ func (t *wechatPayTransactionDataTable) parseTransactionData(ctx core.Context, u
|
|||||||
|
|
||||||
localeTextItems := locales.GetLocaleTextItems(locale)
|
localeTextItems := locales.GetLocaleTextItems(locale)
|
||||||
|
|
||||||
if t.originalTypeColumnIndex >= 0 && t.originalTypeColumnIndex < len(items) {
|
if t.hasOriginalColumn(wechatPayTransactionTypeColumnName) {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = items[t.originalTypeColumnIndex]
|
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataRow.GetData(wechatPayTransactionTypeColumnName)
|
||||||
|
|
||||||
if items[t.originalTypeColumnIndex] == wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] {
|
if dataRow.GetData(wechatPayTransactionTypeColumnName) == wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] {
|
||||||
if relatedAccountName == "" || relatedAccountName == "/" {
|
if relatedAccountName == "" || relatedAccountName == "/" {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.WeChatWallet
|
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.WeChatWallet
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = ""
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = ""
|
||||||
} else {
|
} else {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
||||||
}
|
}
|
||||||
} else if items[t.originalTypeColumnIndex] == wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
} else if dataRow.GetData(wechatPayTransactionTypeColumnName) == wechatPayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
||||||
if data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] == wechatPayTransactionDataCategoryTransferToWeChatWallet {
|
if data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] == wechatPayTransactionDataCategoryTransferToWeChatWallet {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.WeChatWallet
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.WeChatWallet
|
||||||
@@ -215,7 +199,8 @@ func (t *wechatPayTransactionDataTable) parseTransactionData(ctx core.Context, u
|
|||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.WeChatWallet
|
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.WeChatWallet
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = relatedAccountName
|
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = relatedAccountName
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Sprintf("unkown transfer transaction category")
|
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.parseTransactionData] skip parsing transaction in row \"%s\", because unkown transfer transaction category \"%s\"", rowId, data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY])
|
||||||
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = relatedAccountName
|
||||||
@@ -234,75 +219,33 @@ func (t *wechatPayTransactionDataTable) parseTransactionData(ctx core.Context, u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, ""
|
return data, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNewWeChatPayTransactionDataTable(ctx core.Context, reader io.Reader) (*wechatPayTransactionDataTable, error) {
|
func createNewWeChatPayTransactionDataTable(ctx core.Context, reader io.Reader) (*wechatPayTransactionDataTable, error) {
|
||||||
allOriginalLines, err := parseAllLinesFromWechatPayTransactionPlainText(ctx, reader)
|
dataTable, err := createNewWeChatPayImportedDataTable(ctx, reader)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allOriginalLines) < 2 {
|
commonDataTable := datatable.CreateNewImportedCommonDataTable(dataTable)
|
||||||
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.createNewwechatPayTransactionPlainTextDataTable] cannot parse import data, because data table row count is less 1")
|
|
||||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
|
||||||
}
|
|
||||||
|
|
||||||
originalHeaderItems := allOriginalLines[0]
|
if !commonDataTable.HasColumn(wechatPayTransactionTimeColumnName) ||
|
||||||
originalHeaderItemMap := make(map[string]int)
|
!commonDataTable.HasColumn(wechatPayTransactionCategoryColumnName) ||
|
||||||
|
!commonDataTable.HasColumn(wechatPayTransactionTypeColumnName) ||
|
||||||
for i := 0; i < len(originalHeaderItems); i++ {
|
!commonDataTable.HasColumn(wechatPayTransactionAmountColumnName) ||
|
||||||
originalHeaderItemMap[originalHeaderItems[i]] = i
|
!commonDataTable.HasColumn(wechatPayTransactionStatusColumnName) {
|
||||||
}
|
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.createNewWeChatPayTransactionDataTable] cannot parse wechat pay csv data, because missing essential columns in header row")
|
||||||
|
|
||||||
timeColumnIdx, timeColumnExists := originalHeaderItemMap["交易时间"]
|
|
||||||
categoryColumnIdx, categoryColumnExists := originalHeaderItemMap["交易类型"]
|
|
||||||
targetNameColumnIdx, targetNameColumnExists := originalHeaderItemMap["交易对方"]
|
|
||||||
productNameColumnIdx, productNameColumnExists := originalHeaderItemMap["商品"]
|
|
||||||
typeColumnIdx, typeColumnExists := originalHeaderItemMap["收/支"]
|
|
||||||
amountColumnIdx, amountColumnExists := originalHeaderItemMap["金额(元)"]
|
|
||||||
relatedAccountColumnIdx, relatedAccountColumnExists := originalHeaderItemMap["支付方式"]
|
|
||||||
statusColumnIdx, statusColumnExists := originalHeaderItemMap["当前状态"]
|
|
||||||
descriptionColumnIdx, descriptionColumnExists := originalHeaderItemMap["备注"]
|
|
||||||
|
|
||||||
if !timeColumnExists || !categoryColumnExists || !typeColumnExists || !amountColumnExists || !statusColumnExists {
|
|
||||||
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.createNewwechatPayTransactionPlainTextDataTable] cannot parse wechat pay csv data, because missing essential columns in header row")
|
|
||||||
return nil, errs.ErrMissingRequiredFieldInHeaderRow
|
return nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||||
}
|
}
|
||||||
|
|
||||||
if !targetNameColumnExists {
|
|
||||||
targetNameColumnIdx = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if !productNameColumnExists {
|
|
||||||
productNameColumnIdx = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if !relatedAccountColumnExists {
|
|
||||||
relatedAccountColumnIdx = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if !descriptionColumnExists {
|
|
||||||
descriptionColumnIdx = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return &wechatPayTransactionDataTable{
|
return &wechatPayTransactionDataTable{
|
||||||
allOriginalLines: allOriginalLines,
|
innerDataTable: commonDataTable,
|
||||||
originalHeaderLineColumnNames: originalHeaderItems,
|
|
||||||
originalTimeColumnIndex: timeColumnIdx,
|
|
||||||
originalCategoryColumnIndex: categoryColumnIdx,
|
|
||||||
originalTargetNameColumnIndex: targetNameColumnIdx,
|
|
||||||
originalProductNameColumnIndex: productNameColumnIdx,
|
|
||||||
originalAmountColumnIndex: amountColumnIdx,
|
|
||||||
originalTypeColumnIndex: typeColumnIdx,
|
|
||||||
originalRelatedAccountColumnIndex: relatedAccountColumnIdx,
|
|
||||||
originalStatusColumnIndex: statusColumnIdx,
|
|
||||||
originalDescriptionColumnIndex: descriptionColumnIdx,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAllLinesFromWechatPayTransactionPlainText(ctx core.Context, reader io.Reader) ([][]string, error) {
|
func createNewWeChatPayImportedDataTable(ctx core.Context, reader io.Reader) (datatable.ImportedDataTable, error) {
|
||||||
csvReader := csv.NewReader(reader)
|
csvReader := csv.NewReader(reader)
|
||||||
csvReader.FieldsPerRecord = -1
|
csvReader.FieldsPerRecord = -1
|
||||||
|
|
||||||
@@ -318,7 +261,7 @@ func parseAllLinesFromWechatPayTransactionPlainText(ctx core.Context, reader io.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.parseAllLinesFromWechatPayTransactionPlainText] cannot parse wechat pay csv data, because %s", err.Error())
|
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.createNewWeChatPayImportedDataTable] cannot parse wechat pay csv data, because %s", err.Error())
|
||||||
return nil, errs.ErrInvalidCSVFile
|
return nil, errs.ErrInvalidCSVFile
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +272,7 @@ func parseAllLinesFromWechatPayTransactionPlainText(ctx core.Context, reader io.
|
|||||||
hasFileHeader = true
|
hasFileHeader = true
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.parseAllLinesFromWechatPayTransactionPlainText] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
log.Warnf(ctx, "[wechat_pay_transaction_csv_data_table.createNewWeChatPayImportedDataTable] read unexpected line before read file header, line content is %s", strings.Join(items, ","))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +298,7 @@ func parseAllLinesFromWechatPayTransactionPlainText(ctx core.Context, reader io.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
||||||
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.parseAllLinesFromWechatPayTransactionPlainText] 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]))
|
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.createNewWeChatPayImportedDataTable] 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
|
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,5 +310,12 @@ func parseAllLinesFromWechatPayTransactionPlainText(ctx core.Context, reader io.
|
|||||||
return nil, errs.ErrInvalidFileHeader
|
return nil, errs.ErrInvalidFileHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
return allOriginalLines, nil
|
if len(allOriginalLines) < 2 {
|
||||||
|
log.Errorf(ctx, "[wechat_pay_transaction_csv_data_table.createNewWeChatPayImportedDataTable] cannot parse import data, because data table row count is less 1")
|
||||||
|
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTable := csvdatatable.CreateNewCustomCsvImportedDataTable(allOriginalLines)
|
||||||
|
|
||||||
|
return dataTable, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user