diff --git a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go b/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go deleted file mode 100644 index dab80149..00000000 --- a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go +++ /dev/null @@ -1,328 +0,0 @@ -package alipay - -import ( - "bytes" - "encoding/csv" - "io" - "strings" - - "golang.org/x/text/encoding/simplifiedchinese" - "golang.org/x/text/transform" - - "github.com/mayswind/ezbookkeeping/pkg/converters/datatable" - "github.com/mayswind/ezbookkeeping/pkg/core" - "github.com/mayswind/ezbookkeeping/pkg/errs" - "github.com/mayswind/ezbookkeeping/pkg/locales" - "github.com/mayswind/ezbookkeeping/pkg/log" - "github.com/mayswind/ezbookkeeping/pkg/models" - "github.com/mayswind/ezbookkeeping/pkg/utils" -) - -const alipayTransactionDataCsvFileHeader = "支付宝交易记录明细查询" -const alipayTransactionDataCsvDataHeaderLineStartContent = "交易记录明细列表" -const alipayTransactionDataCsvDataHeaderLineEndLineRune = '-' - -const alipayTransactionDataStatusSuccessName = "交易成功" -const alipayTransactionDataStatusPaymentSuccessName = "支付成功" -const alipayTransactionDataStatusRepaymentSuccessName = "还款成功" -const alipayTransactionDataStatusClosedName = "交易关闭" -const alipayTransactionDataStatusRefundSuccessName = "退款成功" -const alipayTransactionDataStatusTaxRefundSuccessName = "退税成功" - -const alipayTransactionDataProductNameRechargePrefix = "充值-" -const alipayTransactionDataProductNameCashWithdrawalPrefix = "提现-" -const alipayTransactionDataProductNameTransferInText = "转入" -const alipayTransactionDataProductNameTransferOutText = "转出" -const alipayTransactionDataProductNameRepaymentText = "还款" - -var alipayTransactionTypeFundStatusNameMapping = map[models.TransactionType]string{ - models.TRANSACTION_TYPE_INCOME: "已收入", - models.TRANSACTION_TYPE_EXPENSE: "已支出", - models.TRANSACTION_TYPE_TRANSFER: "资金转移", -} - -// alipayTransactionDataCsvImporter defines the structure of alipay csv importer for transaction data -type alipayTransactionDataCsvImporter struct{} - -// Initialize a alipay transaction data csv file importer singleton instance -var ( - AlipayTransactionDataCsvImporter = &alipayTransactionDataCsvImporter{} -) - -// ParseImportedData returns the imported data by parsing the alipay transaction csv data -func (c *alipayTransactionDataCsvImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { - enc := simplifiedchinese.GB18030 - reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder()) - allLines, err := c.parseAllLinesFromCsvData(ctx, reader) - - if err != nil { - return nil, nil, nil, nil, nil, nil, err - } - - if len(allLines) <= 1 { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] cannot parse import data for user \"uid:%d\", because data table row count is less 1", user.Uid) - return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile - } - - headerLineItems := allLines[0] - headerItemMap := make(map[string]int) - - for i := 0; i < len(headerLineItems); i++ { - headerItemMap[headerLineItems[i]] = i - } - - timeColumnIdx, timeColumnExists := headerItemMap["交易创建时间"] - targetNameColumnIdx, targetNameColumnExists := headerItemMap["交易对方"] - productNameColumnIdx, productNameColumnExists := headerItemMap["商品名称"] - amountColumnIdx, amountColumnExists := headerItemMap["金额(元)"] - statusColumnIdx, statusColumnExists := headerItemMap["交易状态"] - descriptionColumnIdx, descriptionColumnExists := headerItemMap["备注"] - fundStatusColumnIdx, fundStatusColumnExists := headerItemMap["资金状态"] - - if !timeColumnExists || !amountColumnExists || !statusColumnExists || !fundStatusColumnExists { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] cannot parse import data for user \"uid:%d\", because missing essential columns in header row", user.Uid) - return nil, nil, nil, nil, nil, nil, errs.ErrMissingRequiredFieldInHeaderRow - } - - newColumns := make([]datatable.DataTableColumn, 0, 7) - newColumns = append(newColumns, datatable.DATA_TABLE_TRANSACTION_TYPE) - newColumns = append(newColumns, datatable.DATA_TABLE_TRANSACTION_TIME) - newColumns = append(newColumns, datatable.DATA_TABLE_SUB_CATEGORY) - newColumns = append(newColumns, datatable.DATA_TABLE_ACCOUNT_NAME) - newColumns = append(newColumns, datatable.DATA_TABLE_AMOUNT) - newColumns = append(newColumns, datatable.DATA_TABLE_RELATED_ACCOUNT_NAME) - newColumns = append(newColumns, datatable.DATA_TABLE_DESCRIPTION) - - dataTable := datatable.CreateNewWritableDataTable(newColumns) - - for i := 1; i < len(allLines); i++ { - items := allLines[i] - - if len(items) < len(headerLineItems) { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] cannot parse row \"index:%d\" for user \"uid:%d\", because may missing some columns (column count %d in data row is less than header column count %d)", i, user.Uid, len(items), len(headerLineItems)) - return nil, nil, nil, nil, nil, nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow - } - - if items[fundStatusColumnIdx] != alipayTransactionTypeFundStatusNameMapping[models.TRANSACTION_TYPE_INCOME] && - items[fundStatusColumnIdx] != alipayTransactionTypeFundStatusNameMapping[models.TRANSACTION_TYPE_EXPENSE] && - items[fundStatusColumnIdx] != alipayTransactionTypeFundStatusNameMapping[models.TRANSACTION_TYPE_TRANSFER] { - log.Warnf(ctx, "[alipayTransactionDataCsvImporter.ParseImportedData] skip parsing transaction in row \"index:%d\" for user \"uid:%d\", because fund status is \"%s\"", i, user.Uid, items[fundStatusColumnIdx]) - continue - } - - data := c.parseTransactionData(ctx, - user, - items, - timeColumnIdx, - timeColumnExists, - targetNameColumnIdx, - targetNameColumnExists, - productNameColumnIdx, - productNameColumnExists, - amountColumnIdx, - amountColumnExists, - statusColumnIdx, - statusColumnExists, - descriptionColumnIdx, - descriptionColumnExists, - fundStatusColumnIdx, - fundStatusColumnExists, - ) - - if items[statusColumnIdx] == alipayTransactionDataStatusSuccessName || items[statusColumnIdx] == alipayTransactionDataStatusPaymentSuccessName || items[statusColumnIdx] == alipayTransactionDataStatusRepaymentSuccessName { - dataTable.Add(data) - } else if items[statusColumnIdx] == alipayTransactionDataStatusClosedName { - dataTable.Add(data) - } else if items[statusColumnIdx] == alipayTransactionDataStatusRefundSuccessName || items[statusColumnIdx] == alipayTransactionDataStatusTaxRefundSuccessName { - amount, err := utils.ParseAmount(data[datatable.DATA_TABLE_AMOUNT]) - - if err == nil { - data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeFundStatusNameMapping[models.TRANSACTION_TYPE_EXPENSE] - data[datatable.DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount) - } - - dataTable.Add(data) - } - } - - dataTableImporter := datatable.CreateNewSimpleImporterFromWritableDataTable( - dataTable, - alipayTransactionTypeFundStatusNameMapping, - ) - - return dataTableImporter.ParseImportedData(ctx, user, dataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) -} - -func (c *alipayTransactionDataCsvImporter) parseAllLinesFromCsvData(ctx core.Context, reader io.Reader) ([][]string, error) { - csvReader := csv.NewReader(reader) - csvReader.FieldsPerRecord = -1 - - allLines := make([][]string, 0) - hasFileHeader := false - foundContentBeforeDataHeaderLine := false - - for { - items, err := csvReader.Read() - - if err == io.EOF { - break - } - - if err != nil { - log.Errorf(ctx, "[alipayTransactionDataCsvImporter.parseAllLinesFromCsvData] cannot parse alipay csv data, because %s", err.Error()) - return nil, errs.ErrInvalidCSVFile - } - - if !hasFileHeader { - if len(items) <= 0 { - continue - } else if strings.Index(items[0], alipayTransactionDataCsvFileHeader) == 0 { - hasFileHeader = true - continue - } else { - log.Warnf(ctx, "[alipayTransactionDataCsvImporter.parseAllLinesFromCsvData] read unexpected line before read file header, line content is %s", strings.Join(items, ",")) - } - } - - if !foundContentBeforeDataHeaderLine { - if len(items) <= 0 { - continue - } else if strings.Index(items[0], alipayTransactionDataCsvDataHeaderLineStartContent) >= 0 { - foundContentBeforeDataHeaderLine = true - continue - } else { - continue - } - } - - if foundContentBeforeDataHeaderLine { - if len(items) <= 0 { - continue - } else if len(items) == 1 && utils.ContainsOnlyOneRune(items[0], alipayTransactionDataCsvDataHeaderLineEndLineRune) { - break - } - - for i := 0; i < len(items); i++ { - items[i] = strings.Trim(items[i], " ") - } - - allLines = append(allLines, items) - } - } - - if !hasFileHeader || !foundContentBeforeDataHeaderLine { - return nil, errs.ErrInvalidFileHeader - } - - return allLines, nil -} - -func (c *alipayTransactionDataCsvImporter) parseTransactionData( - ctx core.Context, - user *models.User, - items []string, - timeColumnIdx int, - timeColumnExists bool, - targetNameColumnIdx int, - targetNameColumnExists bool, - productNameColumnIdx int, - productNameColumnExists bool, - amountColumnIdx int, - amountColumnExists bool, - statusColumnIdx int, - statusColumnExists bool, - descriptionColumnIdx int, - descriptionColumnExists bool, - fundStatusColumnIdx int, - fundStatusColumnExists bool, -) map[datatable.DataTableColumn]string { - data := make(map[datatable.DataTableColumn]string, 11) - - if timeColumnExists && timeColumnIdx < len(items) { - data[datatable.DATA_TABLE_TRANSACTION_TIME] = items[timeColumnIdx] - } - - if amountColumnExists && amountColumnIdx < len(items) { - data[datatable.DATA_TABLE_AMOUNT] = items[amountColumnIdx] - } - - data[datatable.DATA_TABLE_SUB_CATEGORY] = "" - - if descriptionColumnExists && descriptionColumnIdx < len(items) && items[descriptionColumnIdx] != "" { - data[datatable.DATA_TABLE_DESCRIPTION] = items[descriptionColumnIdx] - } else if productNameColumnExists && productNameColumnIdx < len(items) && items[productNameColumnIdx] != "" { - data[datatable.DATA_TABLE_DESCRIPTION] = items[productNameColumnIdx] - } else { - data[datatable.DATA_TABLE_DESCRIPTION] = "" - } - - if fundStatusColumnExists && fundStatusColumnIdx < len(items) { - data[datatable.DATA_TABLE_TRANSACTION_TYPE] = items[fundStatusColumnIdx] - - if items[fundStatusColumnIdx] == alipayTransactionTypeFundStatusNameMapping[models.TRANSACTION_TYPE_INCOME] { - locale := user.Language - - if locale == "" { - locale = ctx.GetClientLocale() - } - - localeTextItems := locales.GetLocaleTextItems(locale) - statusName := "" - - if statusColumnExists && statusColumnIdx < len(items) { - statusName = items[statusColumnIdx] - } - - if statusName == alipayTransactionDataStatusSuccessName { - data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } else { - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } - } else if items[fundStatusColumnIdx] == alipayTransactionTypeFundStatusNameMapping[models.TRANSACTION_TYPE_TRANSFER] { - locale := user.Language - - if locale == "" { - locale = ctx.GetClientLocale() - } - - localeTextItems := locales.GetLocaleTextItems(locale) - targetName := "" - productName := "" - - if targetNameColumnExists && targetNameColumnIdx < len(items) { - targetName = items[targetNameColumnIdx] - } - - if productNameColumnExists && productNameColumnIdx < len(items) { - productName = items[productNameColumnIdx] - } - - if strings.Index(productName, alipayTransactionDataProductNameRechargePrefix) == 0 { // transfer to alipay wallet - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay - } else if strings.Index(productName, alipayTransactionDataProductNameCashWithdrawalPrefix) == 0 { // transfer from alipay wallet - data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else if strings.Index(productName, alipayTransactionDataProductNameTransferInText) >= 0 { // transfer in - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else if strings.Index(productName, alipayTransactionDataProductNameTransferOutText) >= 0 { // transfer out - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else if strings.Index(productName, alipayTransactionDataProductNameRepaymentText) >= 0 { // repayment - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName - } else { - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } - } else { - data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" - data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" - } - } - - return data -} diff --git a/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer.go b/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer.go new file mode 100644 index 00000000..e015555b --- /dev/null +++ b/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer.go @@ -0,0 +1,349 @@ +package alipay + +import ( + "bytes" + "encoding/csv" + "fmt" + "io" + "strings" + + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" + + "github.com/mayswind/ezbookkeeping/pkg/converters/datatable" + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/errs" + "github.com/mayswind/ezbookkeeping/pkg/locales" + "github.com/mayswind/ezbookkeeping/pkg/log" + "github.com/mayswind/ezbookkeeping/pkg/models" + "github.com/mayswind/ezbookkeeping/pkg/utils" +) + +const alipayWebTransactionDataCsvFileHeader = "支付宝交易记录明细查询" +const alipayWebTransactionDataCsvDataHeaderLineStartContent = "交易记录明细列表" +const alipayWebTransactionDataCsvDataDataLineEndLineRune = '-' + +const alipayWebTransactionDataStatusSuccessName = "交易成功" +const alipayWebTransactionDataStatusPaymentSuccessName = "支付成功" +const alipayWebTransactionDataStatusRepaymentSuccessName = "还款成功" +const alipayWebTransactionDataStatusClosedName = "交易关闭" +const alipayWebTransactionDataStatusRefundSuccessName = "退款成功" +const alipayWebTransactionDataStatusTaxRefundSuccessName = "退税成功" + +const alipayWebTransactionDataProductNameRechargePrefix = "充值-" +const alipayWebTransactionDataProductNameCashWithdrawalPrefix = "提现-" +const alipayWebTransactionDataProductNameTransferInText = "转入" +const alipayWebTransactionDataProductNameTransferOutText = "转出" +const alipayWebTransactionDataProductNameRepaymentText = "还款" + +var alipayTransactionTypeNameMapping = map[models.TransactionType]string{ + models.TRANSACTION_TYPE_INCOME: "收入", + models.TRANSACTION_TYPE_EXPENSE: "支出", + models.TRANSACTION_TYPE_TRANSFER: "不计收支", +} + +// alipayWebTransactionDataCsvImporter defines the structure of alipay (web) csv importer for transaction data +type alipayWebTransactionDataCsvImporter struct{} + +// Initialize a alipay (web) transaction data csv file importer singleton instance +var ( + AlipayWebTransactionDataCsvImporter = &alipayWebTransactionDataCsvImporter{} +) + +// ParseImportedData returns the imported data by parsing the alipay (web) transaction csv data +func (c *alipayWebTransactionDataCsvImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { + enc := simplifiedchinese.GB18030 + reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder()) + allLines, err := c.parseAllLinesFromCsvData(ctx, reader) + + if err != nil { + return nil, nil, nil, nil, nil, nil, err + } + + if len(allLines) <= 1 { + log.Errorf(ctx, "[alipayWebTransactionDataCsvImporter.ParseImportedData] cannot parse import data for user \"uid:%d\", because data table row count is less 1", user.Uid) + return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile + } + + headerLineItems := allLines[0] + headerItemMap := make(map[string]int) + + for i := 0; i < len(headerLineItems); i++ { + headerItemMap[headerLineItems[i]] = i + } + + timeColumnIdx, timeColumnExists := headerItemMap["交易创建时间"] + targetNameColumnIdx, targetNameColumnExists := headerItemMap["交易对方"] + productNameColumnIdx, productNameColumnExists := headerItemMap["商品名称"] + amountColumnIdx, amountColumnExists := headerItemMap["金额(元)"] + typeColumnIdx, typeColumnExists := headerItemMap["收/支"] + statusColumnIdx, statusColumnExists := headerItemMap["交易状态"] + descriptionColumnIdx, descriptionColumnExists := headerItemMap["备注"] + + if !timeColumnExists || !amountColumnExists || !typeColumnExists || !statusColumnExists { + log.Errorf(ctx, "[alipayWebTransactionDataCsvImporter.ParseImportedData] cannot parse import data for user \"uid:%d\", because missing essential columns in header row", user.Uid) + return nil, nil, nil, nil, nil, nil, errs.ErrMissingRequiredFieldInHeaderRow + } + + newColumns := make([]datatable.DataTableColumn, 0, 7) + newColumns = append(newColumns, datatable.DATA_TABLE_TRANSACTION_TYPE) + newColumns = append(newColumns, datatable.DATA_TABLE_TRANSACTION_TIME) + newColumns = append(newColumns, datatable.DATA_TABLE_SUB_CATEGORY) + newColumns = append(newColumns, datatable.DATA_TABLE_ACCOUNT_NAME) + newColumns = append(newColumns, datatable.DATA_TABLE_AMOUNT) + newColumns = append(newColumns, datatable.DATA_TABLE_RELATED_ACCOUNT_NAME) + newColumns = append(newColumns, datatable.DATA_TABLE_DESCRIPTION) + + dataTable := datatable.CreateNewWritableDataTable(newColumns) + + for i := 1; i < len(allLines); i++ { + items := allLines[i] + + if len(items) < len(headerLineItems) { + log.Errorf(ctx, "[alipayWebTransactionDataCsvImporter.ParseImportedData] cannot parse row \"index:%d\" for user \"uid:%d\", because may missing some columns (column count %d in data row is less than header column count %d)", i, user.Uid, len(items), len(headerLineItems)) + return nil, nil, nil, nil, nil, nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow + } + + if items[typeColumnIdx] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] && + items[typeColumnIdx] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] && + items[typeColumnIdx] != alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] { + log.Warnf(ctx, "[alipayWebTransactionDataCsvImporter.ParseImportedData] skip parsing transaction in row \"index:%d\" for user \"uid:%d\", because type is \"%s\"", i, user.Uid, items[typeColumnIdx]) + continue + } + + if items[statusColumnIdx] != alipayWebTransactionDataStatusSuccessName && + items[statusColumnIdx] != alipayWebTransactionDataStatusPaymentSuccessName && + items[statusColumnIdx] != alipayWebTransactionDataStatusRepaymentSuccessName && + items[statusColumnIdx] != alipayWebTransactionDataStatusClosedName && + items[statusColumnIdx] != alipayWebTransactionDataStatusRefundSuccessName && + items[statusColumnIdx] != alipayWebTransactionDataStatusTaxRefundSuccessName { + log.Warnf(ctx, "[alipayWebTransactionDataCsvImporter.ParseImportedData] skip parsing transaction in row \"index:%d\" for user \"uid:%d\", because status is \"%s\"", i, user.Uid, items[statusColumnIdx]) + continue + } + + data, errMsg := c.parseTransactionData(ctx, + user, + items, + timeColumnIdx, + timeColumnExists, + targetNameColumnIdx, + targetNameColumnExists, + productNameColumnIdx, + productNameColumnExists, + amountColumnIdx, + amountColumnExists, + typeColumnIdx, + typeColumnExists, + statusColumnIdx, + statusColumnExists, + descriptionColumnIdx, + descriptionColumnExists, + ) + + if data == nil { + log.Warnf(ctx, "[alipayWebTransactionDataCsvImporter.ParseImportedData] skip parsing transaction in row \"index:%d\" for user \"uid:%d\", because %s", i, user.Uid, errMsg) + continue + } + + dataTable.Add(data) + } + + dataTableImporter := datatable.CreateNewSimpleImporterFromWritableDataTable( + dataTable, + alipayTransactionTypeNameMapping, + ) + + return dataTableImporter.ParseImportedData(ctx, user, dataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap) +} + +func (c *alipayWebTransactionDataCsvImporter) parseAllLinesFromCsvData(ctx core.Context, reader io.Reader) ([][]string, error) { + csvReader := csv.NewReader(reader) + csvReader.FieldsPerRecord = -1 + + allLines := make([][]string, 0) + hasFileHeader := false + foundContentBeforeDataHeaderLine := false + + for { + items, err := csvReader.Read() + + if err == io.EOF { + break + } + + if err != nil { + log.Errorf(ctx, "[alipayWebTransactionDataCsvImporter.parseAllLinesFromCsvData] cannot parse alipay csv data, because %s", err.Error()) + return nil, errs.ErrInvalidCSVFile + } + + if !hasFileHeader { + if len(items) <= 0 { + continue + } else if strings.Index(items[0], alipayWebTransactionDataCsvFileHeader) == 0 { + hasFileHeader = true + continue + } else { + log.Warnf(ctx, "[alipayWebTransactionDataCsvImporter.parseAllLinesFromCsvData] read unexpected line before read file header, line content is %s", strings.Join(items, ",")) + } + } + + if !foundContentBeforeDataHeaderLine { + if len(items) <= 0 { + continue + } else if strings.Index(items[0], alipayWebTransactionDataCsvDataHeaderLineStartContent) >= 0 { + foundContentBeforeDataHeaderLine = true + continue + } else { + continue + } + } + + if foundContentBeforeDataHeaderLine { + if len(items) <= 0 { + continue + } else if len(items) == 1 && utils.ContainsOnlyOneRune(items[0], alipayWebTransactionDataCsvDataDataLineEndLineRune) { + break + } + + for i := 0; i < len(items); i++ { + items[i] = strings.Trim(items[i], " ") + } + + allLines = append(allLines, items) + } + } + + if !hasFileHeader || !foundContentBeforeDataHeaderLine { + return nil, errs.ErrInvalidFileHeader + } + + return allLines, nil +} + +func (c *alipayWebTransactionDataCsvImporter) parseTransactionData( + ctx core.Context, + user *models.User, + items []string, + timeColumnIdx int, + timeColumnExists bool, + targetNameColumnIdx int, + targetNameColumnExists bool, + productNameColumnIdx int, + productNameColumnExists bool, + amountColumnIdx int, + amountColumnExists bool, + typeColumnIdx int, + typeColumnExists bool, + statusColumnIdx int, + statusColumnExists bool, + descriptionColumnIdx int, + descriptionColumnExists bool, +) (map[datatable.DataTableColumn]string, string) { + data := make(map[datatable.DataTableColumn]string, 11) + + if timeColumnExists && timeColumnIdx < len(items) { + data[datatable.DATA_TABLE_TRANSACTION_TIME] = items[timeColumnIdx] + } + + if amountColumnExists && amountColumnIdx < len(items) { + data[datatable.DATA_TABLE_AMOUNT] = items[amountColumnIdx] + } + + data[datatable.DATA_TABLE_SUB_CATEGORY] = "" + + if descriptionColumnExists && descriptionColumnIdx < len(items) && items[descriptionColumnIdx] != "" { + data[datatable.DATA_TABLE_DESCRIPTION] = items[descriptionColumnIdx] + } else if productNameColumnExists && productNameColumnIdx < len(items) && items[productNameColumnIdx] != "" { + data[datatable.DATA_TABLE_DESCRIPTION] = items[productNameColumnIdx] + } else { + data[datatable.DATA_TABLE_DESCRIPTION] = "" + } + + statusName := "" + + if statusColumnExists && statusColumnIdx < len(items) { + statusName = items[statusColumnIdx] + } + + locale := user.Language + + if locale == "" { + locale = ctx.GetClientLocale() + } + + localeTextItems := locales.GetLocaleTextItems(locale) + + if typeColumnExists && typeColumnIdx < len(items) { + data[datatable.DATA_TABLE_TRANSACTION_TYPE] = items[typeColumnIdx] + + if items[typeColumnIdx] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] { + if statusName == alipayWebTransactionDataStatusClosedName { + return nil, fmt.Sprintf("income transaction is closed") + } + + if statusName == alipayWebTransactionDataStatusSuccessName { + data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } else { + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } + } else if items[typeColumnIdx] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] { + if statusName == alipayWebTransactionDataStatusClosedName { + return nil, fmt.Sprintf("non-income/expense transaction is closed") + } + + targetName := "" + productName := "" + + if targetNameColumnExists && targetNameColumnIdx < len(items) { + targetName = items[targetNameColumnIdx] + } + + if productNameColumnExists && productNameColumnIdx < len(items) { + productName = items[productNameColumnIdx] + } + + if statusName == alipayWebTransactionDataStatusRefundSuccessName { + data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } else { + if strings.Index(productName, alipayWebTransactionDataProductNameRechargePrefix) == 0 { // transfer to alipay wallet + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay + } else if strings.Index(productName, alipayWebTransactionDataProductNameCashWithdrawalPrefix) == 0 { // transfer from alipay wallet + data[datatable.DATA_TABLE_ACCOUNT_NAME] = localeTextItems.DataConverterTextItems.Alipay + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else if strings.Index(productName, alipayWebTransactionDataProductNameTransferInText) >= 0 { // transfer in + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else if strings.Index(productName, alipayWebTransactionDataProductNameTransferOutText) >= 0 { // transfer out + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else if strings.Index(productName, alipayWebTransactionDataProductNameRepaymentText) >= 0 { // repayment + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = targetName + } else { + return nil, fmt.Sprintf("product name (\"%s\") is unknown", productName) + } + } + } else { + data[datatable.DATA_TABLE_ACCOUNT_NAME] = "" + data[datatable.DATA_TABLE_RELATED_ACCOUNT_NAME] = "" + } + } + + if data[datatable.DATA_TABLE_TRANSACTION_TYPE] == alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] && statusName != "" { + if statusName == alipayWebTransactionDataStatusRefundSuccessName || statusName == alipayWebTransactionDataStatusTaxRefundSuccessName { + amount, err := utils.ParseAmount(data[datatable.DATA_TABLE_AMOUNT]) + + if err == nil { + data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] + data[datatable.DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount) + } + } + } + + return data, "" +} diff --git a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer_test.go b/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer_test.go similarity index 82% rename from pkg/converters/alipay/alipay_transaction_data_csv_file_importer_test.go rename to pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer_test.go index 31d55939..3f0e51c0 100644 --- a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer_test.go +++ b/pkg/converters/alipay/alipay_web_transaction_data_csv_file_importer_test.go @@ -15,7 +15,7 @@ import ( ) func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -27,17 +27,18 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 01:23:45 ,0.12 ,交易成功 ,已收入 ,\n" + - "2024-09-01 12:34:56 ,123.45 ,交易成功 ,已支出 ,\n" + - "2024-09-01 23:59:59 ,0.05 ,交易成功 ,资金转移 ,\n" + + "交易创建时间 ,商品名称 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 01:23:45 ,xxxx ,0.12 ,收入 ,交易成功 ,\n" + + "2024-09-01 12:34:56 ,xxxx ,123.45 ,支出 ,交易成功 ,\n" + + "2024-09-01 23:59:59 ,充值-普通充值 ,0.05 ,不计收支 ,交易成功 ,\n" + + "2024-09-02 23:59:59 ,提现-普通提现 ,0.03 ,不计收支 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil) assert.Nil(t, err) - assert.Equal(t, 3, len(allNewTransactions)) + assert.Equal(t, 4, len(allNewTransactions)) assert.Equal(t, 2, len(allNewAccounts)) assert.Equal(t, 1, len(allNewSubExpenseCategories)) assert.Equal(t, 1, len(allNewSubIncomeCategories)) @@ -63,9 +64,17 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) { assert.Equal(t, "2024-09-01 23:59:59", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[2].TransactionTime), time.UTC)) assert.Equal(t, int64(5), allNewTransactions[2].Amount) assert.Equal(t, "", allNewTransactions[2].OriginalSourceAccountName) - assert.Equal(t, "", allNewTransactions[2].OriginalDestinationAccountName) + assert.Equal(t, "Alipay", allNewTransactions[2].OriginalDestinationAccountName) assert.Equal(t, "", allNewTransactions[2].OriginalCategoryName) + assert.Equal(t, int64(1234567890), allNewTransactions[3].Uid) + assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[3].Type) + assert.Equal(t, "2024-09-02 23:59:59", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[3].TransactionTime), time.UTC)) + assert.Equal(t, int64(3), allNewTransactions[3].Amount) + assert.Equal(t, "Alipay", allNewTransactions[3].OriginalSourceAccountName) + assert.Equal(t, "", allNewTransactions[3].OriginalDestinationAccountName) + assert.Equal(t, "", allNewTransactions[3].OriginalCategoryName) + assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid) assert.Equal(t, "Alipay", allNewAccounts[0].Name) assert.Equal(t, "CNY", allNewAccounts[0].Currency) @@ -85,7 +94,7 @@ func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) { } func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -97,8 +106,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 01:23:45 ,0.12 ,退款成功 ,已收入 ,\n" + + "交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 01:23:45 ,0.12 ,不计收支 ,退款成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -116,8 +125,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 01:23:45 ,0.12 ,退税成功 ,已收入 ,\n" + + "交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 01:23:45 ,0.12 ,收入 ,退税成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -133,7 +142,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testin } func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -145,8 +154,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01T12:34:56 ,0.12 ,交易成功 ,已收入 ,\n" + + "交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01T12:34:56 ,0.12 ,收入 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -157,8 +166,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" + - "09/01/2024 12:34:56 ,0.12 ,交易成功 ,已收入 ,\n" + + "交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" + + "09/01/2024 12:34:56 ,0.12 ,收入 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -167,7 +176,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) { } func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -179,8 +188,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,0.12 ,交易成功 , ,\n" + + "交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,0.12 , ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -189,7 +198,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) { } func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -202,8 +211,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,交易对方 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,0.12 ,交易成功 ,已收入 ,\n" + + "交易创建时间 ,交易对方 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,test ,0.12 ,收入 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -213,13 +222,13 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { assert.Equal(t, 1, len(allNewTransactions)) assert.Equal(t, "Alipay", allNewTransactions[0].OriginalSourceAccountName) - // income to other account + // refund to other account data2, 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" + - "2024-09-01 12:34:56 ,test ,0.12 ,退款成功 ,已收入 ,\n" + + "交易创建时间 ,交易对方 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,test ,0.12 ,不计收支 ,退款成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -234,8 +243,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,交易对方 ,商品名称 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,充值-普通充值 ,0.12 ,交易成功 ,资金转移 ,\n" + + "交易创建时间 ,交易对方 ,商品名称 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,test ,充值-普通充值 ,0.12 ,不计收支 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -251,8 +260,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,交易对方 ,商品名称 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,提现-实时提现 ,0.12 ,交易成功 ,资金转移 ,\n" + + "交易创建时间 ,交易对方 ,商品名称 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,test ,提现-实时提现 ,0.12 ,不计收支 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -268,8 +277,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,交易对方 ,商品名称 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,xx-转入 ,0.12 ,交易成功 ,资金转移 ,\n" + + "交易创建时间 ,交易对方 ,商品名称 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,test ,xx-转入 ,0.12 ,不计收支 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -285,8 +294,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,交易对方 ,商品名称 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,xx-转出 ,0.12 ,交易成功 ,资金转移 ,\n" + + "交易创建时间 ,交易对方 ,商品名称 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,test ,xx-转出 ,0.12 ,不计收支 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -302,8 +311,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,交易对方 ,商品名称 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,xx还款 ,0.12 ,交易成功 ,资金转移 ,\n" + + "交易创建时间 ,交易对方 ,商品名称 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,test ,xx还款 ,0.12 ,不计收支 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -316,7 +325,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) { } func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -328,8 +337,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,商品名称 ,金额(元),交易状态 ,备注 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,0.12 ,交易成功 ,test2 ,已收入 ,\n" + + "交易创建时间 ,商品名称 ,金额(元),收/支 ,交易状态 ,备注 ,\n" + + "2024-09-01 12:34:56 ,test ,0.12 ,收入 ,交易成功 ,test2 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -343,8 +352,8 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) { "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,商品名称 ,金额(元),交易状态 ,备注 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,test ,0.12 ,交易成功 , ,已收入 ,\n" + + "交易创建时间 ,商品名称 ,金额(元),收/支 ,交易状态 ,备注 ,\n" + + "2024-09-01 12:34:56 ,test ,0.12 ,收入 ,交易成功 , ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -356,7 +365,7 @@ func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) { } func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -365,8 +374,8 @@ func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) } data, err := simplifiedchinese.GB18030.NewEncoder().String( - "交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,0.12 ,交易成功 ,Type ,\n" + + "交易创建时间 ,金额(元),收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,0.12 ,收入 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") assert.Nil(t, err) @@ -378,7 +387,7 @@ func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) } func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) { - converter := AlipayTransactionDataCsvImporter + converter := AlipayWebTransactionDataCsvImporter context := core.NewNullContext() user := &models.User{ @@ -391,8 +400,8 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "金额(元),交易状态 ,资金状态 ,\n" + - "0.12 ,交易成功 ,已收入 ,\n" + + "金额(元),收/支 ,交易状态 ,\n" + + "0.12 ,收入 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) @@ -402,8 +411,8 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,交易状态 ,资金状态 ,\n" + - "2024-09-01 12:34:56 ,交易成功 ,已收入 ,\n" + + "交易创建时间 ,收/支 ,交易状态 ,\n" + + "2024-09-01 12:34:56 ,收入 ,交易成功 ,\n" + "------------------------------------------------------------------------------------\n") _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) @@ -413,13 +422,13 @@ func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + "---------------------------------交易记录明细列表------------------------------------\n" + - "交易创建时间 ,金额(元),资金状态 ,\n" + - "2024-09-01 12:34:56 ,0.12 ,已收入 ,\n" + + "交易创建时间 ,金额(元),收/支 ,\n" + + "2024-09-01 12:34:56 ,0.12 ,收入 ,\n" + "------------------------------------------------------------------------------------\n") _, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil) assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message) - // Missing Fund Status Column + // Missing Type Column data4, err := simplifiedchinese.GB18030.NewEncoder().String("支付宝交易记录明细查询\n" + "账号:[xxx@xxx.xxx]\n" + "起始日期:[2024-01-01 00:00:00] 终止日期:[2024-09-01 23:59:59]\n" + diff --git a/pkg/converters/transaction_data_converters.go b/pkg/converters/transaction_data_converters.go index 10c04111..4d429567 100644 --- a/pkg/converters/transaction_data_converters.go +++ b/pkg/converters/transaction_data_converters.go @@ -29,8 +29,8 @@ func GetTransactionDataImporter(fileType string) (base.TransactionDataImporter, return feidee.FeideeMymoneyTransactionDataCsvImporter, nil } else if fileType == "feidee_mymoney_xls" { return feidee.FeideeMymoneyTransactionDataXlsImporter, nil - } else if fileType == "alipay_csv" { - return alipay.AlipayTransactionDataCsvImporter, nil + } else if fileType == "alipay_web_csv" { + return alipay.AlipayWebTransactionDataCsvImporter, nil } else { return nil, errs.ErrImportFileTypeNotSupported } diff --git a/src/consts/file.js b/src/consts/file.js index a2e35587..291fa30c 100644 --- a/src/consts/file.js +++ b/src/consts/file.js @@ -38,7 +38,7 @@ const supportedImportFileTypes = [ } }, { - type: 'alipay_csv', + type: 'alipay_web_csv', name: 'Alipay (Web) Data Export File', extensions: '.csv', document: {