mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 17:24:26 +08:00
support importing transaction data from alipay export file
This commit is contained in:
@@ -0,0 +1,323 @@
|
|||||||
|
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, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*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, 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, 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, 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 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 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, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
data[datatable.DATA_TABLE_TRANSACTION_TYPE] = alipayTransactionTypeFundStatusNameMapping[models.TRANSACTION_TYPE_EXPENSE]
|
||||||
|
data[datatable.DATA_TABLE_AMOUNT] = "-" + data[datatable.DATA_TABLE_AMOUNT]
|
||||||
|
dataTable.Add(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTableImporter := datatable.CreateNewSimpleImporterFromWritableDataTable(
|
||||||
|
dataTable,
|
||||||
|
alipayTransactionTypeFundStatusNameMapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
return dataTableImporter.ParseImportedData(ctx, user, dataTable, defaultTimezoneOffset, accountMap, categoryMap, 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
package alipay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
data, 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 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")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, allNewAccounts, allNewSubCategories, allNewTags, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 3, len(allNewTransactions))
|
||||||
|
assert.Equal(t, 2, len(allNewAccounts))
|
||||||
|
assert.Equal(t, 1, len(allNewSubCategories))
|
||||||
|
assert.Equal(t, 0, len(allNewTags))
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||||
|
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[0].Type)
|
||||||
|
assert.Equal(t, "2024-09-01 01:23:45", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime), time.UTC))
|
||||||
|
assert.Equal(t, int64(12), allNewTransactions[0].Amount)
|
||||||
|
assert.Equal(t, "Alipay", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalCategoryName)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewTransactions[1].Uid)
|
||||||
|
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[1].Type)
|
||||||
|
assert.Equal(t, "2024-09-01 12:34:56", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[1].TransactionTime), time.UTC))
|
||||||
|
assert.Equal(t, int64(12345), allNewTransactions[1].Amount)
|
||||||
|
assert.Equal(t, "", allNewTransactions[1].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "", allNewTransactions[1].OriginalCategoryName)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewTransactions[2].Uid)
|
||||||
|
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[2].Type)
|
||||||
|
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, "", allNewTransactions[2].OriginalCategoryName)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid)
|
||||||
|
assert.Equal(t, "Alipay", 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), allNewSubCategories[0].Uid)
|
||||||
|
assert.Equal(t, "", allNewSubCategories[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
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" +
|
||||||
|
"2024-09-01 01:23:45 ,0.12 ,退款成功 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||||
|
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[0].Type)
|
||||||
|
assert.Equal(t, "2024-09-01 01:23:45", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime), time.UTC))
|
||||||
|
assert.Equal(t, int64(-12), allNewTransactions[0].Amount)
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalCategoryName)
|
||||||
|
|
||||||
|
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 01:23:45 ,0.12 ,退税成功 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||||
|
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[0].Type)
|
||||||
|
assert.Equal(t, "2024-09-01 01:23:45", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime), time.UTC))
|
||||||
|
assert.Equal(t, int64(-12), allNewTransactions[0].Amount)
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalCategoryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
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" +
|
||||||
|
"2024-09-01T12:34:56 ,0.12 ,交易成功 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
|
||||||
|
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" +
|
||||||
|
"09/01/2024 12:34:56 ,0.12 ,交易成功 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
data, 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 ,0.12 ,交易成功 , ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
// income to alipay wallet
|
||||||
|
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" +
|
||||||
|
"2024-09-01 12:34:56 ,test ,0.12 ,交易成功 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "Alipay", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
|
||||||
|
// income 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")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
|
||||||
|
// transfer to alipay wallet
|
||||||
|
data3, 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")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "Alipay", allNewTransactions[0].OriginalDestinationAccountName)
|
||||||
|
|
||||||
|
// transfer from alipay wallet
|
||||||
|
data4, 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")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "Alipay", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "test", allNewTransactions[0].OriginalDestinationAccountName)
|
||||||
|
|
||||||
|
// transfer in
|
||||||
|
data5, 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 ,xx-转入 ,0.12 ,交易成功 ,资金转移 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "test", allNewTransactions[0].OriginalDestinationAccountName)
|
||||||
|
|
||||||
|
// transfer out
|
||||||
|
data6, 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 ,xx-转出 ,0.12 ,交易成功 ,资金转移 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data6), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "test", allNewTransactions[0].OriginalDestinationAccountName)
|
||||||
|
|
||||||
|
// repayment
|
||||||
|
data7, 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 ,xx还款 ,0.12 ,交易成功 ,资金转移 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data7), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "", allNewTransactions[0].OriginalSourceAccountName)
|
||||||
|
assert.Equal(t, "test", allNewTransactions[0].OriginalDestinationAccountName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1234567890,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
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" +
|
||||||
|
"2024-09-01 12:34:56 ,test ,0.12 ,交易成功 ,test2 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "test2", allNewTransactions[0].Comment)
|
||||||
|
|
||||||
|
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")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
allNewTransactions, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
|
assert.Equal(t, "test", allNewTransactions[0].Comment)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
context := core.NewNullContext()
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Uid: 1,
|
||||||
|
DefaultCurrency: "CNY",
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := simplifiedchinese.GB18030.NewEncoder().String(
|
||||||
|
"交易创建时间 ,金额(元),交易状态 ,资金状态 ,\n" +
|
||||||
|
"2024-09-01 12:34:56 ,0.12 ,交易成功 ,Type ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||||
|
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlipayCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
|
converter := AlipayTransactionDataCsvImporter
|
||||||
|
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" +
|
||||||
|
"0.12 ,交易成功 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
|
// Missing Amount Column
|
||||||
|
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 ,交易成功 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
|
// Missing Status Column
|
||||||
|
data3, 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 ,0.12 ,已收入 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
|
||||||
|
// Missing Fund Status 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" +
|
||||||
|
"---------------------------------交易记录明细列表------------------------------------\n" +
|
||||||
|
"交易创建时间 ,金额(元),交易状态 ,\n" +
|
||||||
|
"2024-09-01 12:34:56 ,0.12 ,交易成功 ,\n" +
|
||||||
|
"------------------------------------------------------------------------------------\n")
|
||||||
|
_, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil)
|
||||||
|
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||||
|
}
|
||||||
@@ -84,6 +84,14 @@ func CreateNewSimpleImporterWithPostProcessFunc(dataColumnMapping map[DataTableC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateNewSimpleImporterFromWritableDataTable returns a new data table transaction data importer according to the specified arguments
|
||||||
|
func CreateNewSimpleImporterFromWritableDataTable(writableDataTable *WritableDataTable, transactionTypeMapping map[models.TransactionType]string) *DataTableTransactionDataImporter {
|
||||||
|
return &DataTableTransactionDataImporter{
|
||||||
|
dataColumnMapping: writableDataTable.GetDataColumnMapping(),
|
||||||
|
transactionTypeMapping: transactionTypeMapping,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CreateNewSimpleImporterFromWritableDataTableWithPostProcessFunc returns a new data table transaction data importer according to the specified arguments
|
// CreateNewSimpleImporterFromWritableDataTableWithPostProcessFunc returns a new data table transaction data importer according to the specified arguments
|
||||||
func CreateNewSimpleImporterFromWritableDataTableWithPostProcessFunc(writableDataTable *WritableDataTable, transactionTypeMapping map[models.TransactionType]string, postProcessFunc DataTableTransactionDataImporterPostProcessFunc) *DataTableTransactionDataImporter {
|
func CreateNewSimpleImporterFromWritableDataTableWithPostProcessFunc(writableDataTable *WritableDataTable, transactionTypeMapping map[models.TransactionType]string, postProcessFunc DataTableTransactionDataImporterPostProcessFunc) *DataTableTransactionDataImporter {
|
||||||
return &DataTableTransactionDataImporter{
|
return &DataTableTransactionDataImporter{
|
||||||
@@ -93,7 +101,7 @@ func CreateNewSimpleImporterFromWritableDataTableWithPostProcessFunc(writableDat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildExportedContent writes the exported transaction data to the data table builder
|
// BuildExportedContent writes the exported transaction data to the data table builder
|
||||||
func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context, dataTableBuilder DataTableBuilder, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) error {
|
func (c *DataTableTransactionDataExporter) BuildExportedContent(ctx core.Context, dataTableBuilder DataTableBuilder, uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) error {
|
||||||
for i := 0; i < len(transactions); i++ {
|
for i := 0; i < len(transactions); i++ {
|
||||||
transaction := transactions[i]
|
transaction := transactions[i]
|
||||||
@@ -356,12 +364,6 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
accountName := dataRow.GetData(accountColumnIdx)
|
accountName := dataRow.GetData(accountColumnIdx)
|
||||||
|
|
||||||
if accountName == "" {
|
|
||||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] account name is empty in data row \"index:%d\" for user \"uid:%d\"", dataRowIndex, user.Uid)
|
|
||||||
return nil, nil, nil, nil, errs.ErrAccountNameCannotBeBlank
|
|
||||||
}
|
|
||||||
|
|
||||||
accountCurrency := user.DefaultCurrency
|
accountCurrency := user.DefaultCurrency
|
||||||
|
|
||||||
if accountCurrencyColumnExists {
|
if accountCurrencyColumnExists {
|
||||||
@@ -382,7 +384,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
if accountCurrencyColumnExists {
|
if accountCurrencyColumnExists {
|
||||||
if account.Currency != accountCurrency {
|
if account.Name != "" && account.Currency != accountCurrency {
|
||||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] currency \"%s\" in data row \"index:%d\" not equals currency \"%s\" of the account for user \"uid:%d\"", accountCurrency, dataRowIndex, account.Currency, user.Uid)
|
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] currency \"%s\" in data row \"index:%d\" not equals currency \"%s\" of the account for user \"uid:%d\"", accountCurrency, dataRowIndex, account.Currency, user.Uid)
|
||||||
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||||
}
|
}
|
||||||
@@ -404,12 +406,6 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
|
|
||||||
if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
account2Name = dataRow.GetData(account2ColumnIdx)
|
account2Name = dataRow.GetData(account2ColumnIdx)
|
||||||
|
|
||||||
if account2Name == "" {
|
|
||||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] account2 name is empty in data row \"index:%d\" for user \"uid:%d\"", dataRowIndex, user.Uid)
|
|
||||||
return nil, nil, nil, nil, errs.ErrDestinationAccountNameCannotBeBlank
|
|
||||||
}
|
|
||||||
|
|
||||||
account2Currency = user.DefaultCurrency
|
account2Currency = user.DefaultCurrency
|
||||||
|
|
||||||
if account2CurrencyColumnExists {
|
if account2CurrencyColumnExists {
|
||||||
@@ -430,7 +426,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
if account2CurrencyColumnExists {
|
if account2CurrencyColumnExists {
|
||||||
if account2.Currency != account2Currency {
|
if account2.Name != "" && account2.Currency != account2Currency {
|
||||||
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] currency \"%s\" in data row \"index:%d\" not equals currency \"%s\" of the account2 for user \"uid:%d\"", account2Currency, dataRowIndex, account2.Currency, user.Uid)
|
log.Errorf(ctx, "[data_table_transaction_data_converter.parseImportedData] currency \"%s\" in data row \"index:%d\" not equals currency \"%s\" of the account2 for user \"uid:%d\"", account2Currency, dataRowIndex, account2.Currency, user.Uid)
|
||||||
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,24 +255,6 @@ func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidTimezone(t
|
|||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAccountName(t *testing.T) {
|
|
||||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
|
||||||
context := core.NewNullContext()
|
|
||||||
|
|
||||||
user := &models.User{
|
|
||||||
Uid: 1234567890,
|
|
||||||
DefaultCurrency: "CNY",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
|
||||||
"2024-09-01 12:34:56,Expense,Test Category,,123.45,,"), 0, nil, nil, nil)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
|
||||||
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,,123.45"), 0, nil, nil, nil)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||||
converter := EzBookKeepingTransactionDataCSVFileConverter
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|||||||
@@ -135,33 +135,6 @@ func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidType(t *testi
|
|||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseInvalidAccountName(t *testing.T) {
|
|
||||||
converter := FeideeMymoneyTransactionDataCsvImporter
|
|
||||||
context := core.NewNullContext()
|
|
||||||
|
|
||||||
user := &models.User{
|
|
||||||
Uid: 1234567890,
|
|
||||||
DefaultCurrency: "CNY",
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, _, err := converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
|
||||||
"\"支出\",\"2024-09-01 12:34:56\",\"Test Category\",\"\",\"123.45\",\"\",\"\""), 0, nil, nil, nil)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
|
||||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
|
||||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account2\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
|
|
||||||
_, _, _, _, err = converter.ParseImportedData(context, user, []byte("随手记导出文件(headers:v5;xxxxx)\n"+
|
|
||||||
"\"交易类型\",\"日期\",\"子类别\",\"账户\",\"金额\",\"备注\",\"关联Id\"\n"+
|
|
||||||
"\"转出\",\"2024-09-01 12:34:56\",\"Test Category\",\"Test Account\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\"\n"+
|
|
||||||
"\"转入\",\"2024-09-01 12:34:56\",\"Test Category\",\"\",\"123.45\",\"\",\"00000000-0000-0000-0000-000000000001\""), 0, nil, nil, nil)
|
|
||||||
assert.NotNil(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
func TestFeideeMymoneyCsvFileImporterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||||
converter := FeideeMymoneyTransactionDataCsvImporter
|
converter := FeideeMymoneyTransactionDataCsvImporter
|
||||||
context := core.NewNullContext()
|
context := core.NewNullContext()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package converters
|
package converters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/converters/alipay"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/base"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/base"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/default"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/default"
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/converters/feidee"
|
"github.com/mayswind/ezbookkeeping/pkg/converters/feidee"
|
||||||
@@ -28,6 +29,8 @@ func GetTransactionDataImporter(fileType string) (base.TransactionDataImporter,
|
|||||||
return feidee.FeideeMymoneyTransactionDataCsvImporter, nil
|
return feidee.FeideeMymoneyTransactionDataCsvImporter, nil
|
||||||
} else if fileType == "feidee_mymoney_xls" {
|
} else if fileType == "feidee_mymoney_xls" {
|
||||||
return feidee.FeideeMymoneyTransactionDataXlsImporter, nil
|
return feidee.FeideeMymoneyTransactionDataXlsImporter, nil
|
||||||
|
} else if fileType == "alipay_csv" {
|
||||||
|
return alipay.AlipayTransactionDataCsvImporter, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, errs.ErrImportFileTypeNotSupported
|
return nil, errs.ErrImportFileTypeNotSupported
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ package core
|
|||||||
// Context is the base context of ezBookkeeping
|
// Context is the base context of ezBookkeeping
|
||||||
type Context interface {
|
type Context interface {
|
||||||
GetContextId() string
|
GetContextId() string
|
||||||
|
GetClientLocale() string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ func (c *CliContext) GetContextId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClientLocale returns the client locale name
|
||||||
|
func (c *CliContext) GetClientLocale() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// WrapCliContext returns a context wrapped by this file
|
// WrapCliContext returns a context wrapped by this file
|
||||||
func WrapCilContext(cliCtx *cli.Context) *CliContext {
|
func WrapCilContext(cliCtx *cli.Context) *CliContext {
|
||||||
return &CliContext{
|
return &CliContext{
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ func (c *CronContext) GetContextId() string {
|
|||||||
return c.contextId
|
return c.contextId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClientLocale returns the client locale name
|
||||||
|
func (c *CronContext) GetClientLocale() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// GetInterval returns the current cron job interval
|
// GetInterval returns the current cron job interval
|
||||||
func (c *CronContext) GetInterval() time.Duration {
|
func (c *CronContext) GetInterval() time.Duration {
|
||||||
return c.cronJobInterval
|
return c.cronJobInterval
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ func (c *NullContext) GetContextId() string {
|
|||||||
return nullContextId
|
return nullContextId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetClientLocale returns the client locale name
|
||||||
|
func (c *NullContext) GetClientLocale() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// NewCronJobContext returns a new null context
|
// NewCronJobContext returns a new null context
|
||||||
func NewNullContext() *NullContext {
|
func NewNullContext() *NullContext {
|
||||||
return &NullContext{
|
return &NullContext{
|
||||||
|
|||||||
+7
-11
@@ -9,15 +9,11 @@ var (
|
|||||||
ErrFewerFieldsInDataRowThanInHeaderRow = NewNormalError(NormalSubcategoryConverter, 2, http.StatusBadRequest, "fewer fields in data row than in header row")
|
ErrFewerFieldsInDataRowThanInHeaderRow = NewNormalError(NormalSubcategoryConverter, 2, http.StatusBadRequest, "fewer fields in data row than in header row")
|
||||||
ErrTransactionTimeInvalid = NewNormalError(NormalSubcategoryConverter, 3, http.StatusBadRequest, "transaction time is invalid")
|
ErrTransactionTimeInvalid = NewNormalError(NormalSubcategoryConverter, 3, http.StatusBadRequest, "transaction time is invalid")
|
||||||
ErrTransactionTimeZoneInvalid = NewNormalError(NormalSubcategoryConverter, 4, http.StatusBadRequest, "transaction time zone is invalid")
|
ErrTransactionTimeZoneInvalid = NewNormalError(NormalSubcategoryConverter, 4, http.StatusBadRequest, "transaction time zone is invalid")
|
||||||
ErrCategoryNameCannotBeBlank = NewNormalError(NormalSubcategoryConverter, 5, http.StatusBadRequest, "category name cannot be blank")
|
ErrAmountInvalid = NewNormalError(NormalSubcategoryConverter, 5, http.StatusBadRequest, "transaction amount is invalid")
|
||||||
ErrSubCategoryNameCannotBeBlank = NewNormalError(NormalSubcategoryConverter, 6, http.StatusBadRequest, "secondary category name cannot be blank")
|
ErrGeographicLocationInvalid = NewNormalError(NormalSubcategoryConverter, 6, http.StatusBadRequest, "geographic location is invalid")
|
||||||
ErrAccountNameCannotBeBlank = NewNormalError(NormalSubcategoryConverter, 7, http.StatusBadRequest, "account name cannot be blank")
|
ErrFieldsInMultiTableAreDifferent = NewNormalError(NormalSubcategoryConverter, 7, http.StatusBadRequest, "fields in multiple table headers are different")
|
||||||
ErrDestinationAccountNameCannotBeBlank = NewNormalError(NormalSubcategoryConverter, 8, http.StatusBadRequest, "destination account name cannot be blank")
|
ErrInvalidFileHeader = NewNormalError(NormalSubcategoryConverter, 8, http.StatusBadRequest, "invalid file header")
|
||||||
ErrAmountInvalid = NewNormalError(NormalSubcategoryConverter, 9, http.StatusBadRequest, "transaction amount is invalid")
|
ErrInvalidCSVFile = NewNormalError(NormalSubcategoryConverter, 9, http.StatusBadRequest, "invalid csv file")
|
||||||
ErrGeographicLocationInvalid = NewNormalError(NormalSubcategoryConverter, 10, http.StatusBadRequest, "geographic location is invalid")
|
ErrRelatedIdCannotBeBlank = NewNormalError(NormalSubcategoryConverter, 10, http.StatusBadRequest, "related id cannot be blank")
|
||||||
ErrFieldsInMultiTableAreDifferent = NewNormalError(NormalSubcategoryConverter, 11, http.StatusBadRequest, "fields in multiple table headers are different")
|
ErrFoundRecordNotHasRelatedRecord = NewNormalError(NormalSubcategoryConverter, 11, http.StatusBadRequest, "found some transactions without related records")
|
||||||
ErrInvalidFileHeader = NewNormalError(NormalSubcategoryConverter, 12, http.StatusBadRequest, "invalid file header")
|
|
||||||
ErrInvalidCSVFile = NewNormalError(NormalSubcategoryConverter, 13, http.StatusBadRequest, "invalid csv file")
|
|
||||||
ErrRelatedIdCannotBeBlank = NewNormalError(NormalSubcategoryConverter, 14, http.StatusBadRequest, "related id cannot be blank")
|
|
||||||
ErrFoundRecordNotHasRelatedRecord = NewNormalError(NormalSubcategoryConverter, 15, http.StatusBadRequest, "found some transactions without related records")
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
// LocaleTextItems represents all text items need to be translated
|
// LocaleTextItems represents all text items need to be translated
|
||||||
type LocaleTextItems struct {
|
type LocaleTextItems struct {
|
||||||
DefaultTypes *DefaultTypes
|
DefaultTypes *DefaultTypes
|
||||||
|
DataConverterTextItems *DataConverterTextItems
|
||||||
VerifyEmailTextItems *VerifyEmailTextItems
|
VerifyEmailTextItems *VerifyEmailTextItems
|
||||||
ForgetPasswordMailTextItems *ForgetPasswordMailTextItems
|
ForgetPasswordMailTextItems *ForgetPasswordMailTextItems
|
||||||
}
|
}
|
||||||
@@ -17,6 +18,11 @@ type DefaultTypes struct {
|
|||||||
DigitGroupingSymbol core.DigitGroupingSymbol
|
DigitGroupingSymbol core.DigitGroupingSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataConverterTextItems represents text items need to be translated in data converter
|
||||||
|
type DataConverterTextItems struct {
|
||||||
|
Alipay string
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyEmailTextItems represents text items need to be translated in verify mail
|
// VerifyEmailTextItems represents text items need to be translated in verify mail
|
||||||
type VerifyEmailTextItems struct {
|
type VerifyEmailTextItems struct {
|
||||||
Title string
|
Title string
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ var en = &LocaleTextItems{
|
|||||||
DecimalSeparator: core.DECIMAL_SEPARATOR_DOT,
|
DecimalSeparator: core.DECIMAL_SEPARATOR_DOT,
|
||||||
DigitGroupingSymbol: core.DIGIT_GROUPING_SYMBOL_COMMA,
|
DigitGroupingSymbol: core.DIGIT_GROUPING_SYMBOL_COMMA,
|
||||||
},
|
},
|
||||||
|
DataConverterTextItems: &DataConverterTextItems{
|
||||||
|
Alipay: "Alipay",
|
||||||
|
},
|
||||||
VerifyEmailTextItems: &VerifyEmailTextItems{
|
VerifyEmailTextItems: &VerifyEmailTextItems{
|
||||||
Title: "Verify Email",
|
Title: "Verify Email",
|
||||||
SalutationFormat: "Hi %s,",
|
SalutationFormat: "Hi %s,",
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ var zhHans = &LocaleTextItems{
|
|||||||
DecimalSeparator: core.DECIMAL_SEPARATOR_DOT,
|
DecimalSeparator: core.DECIMAL_SEPARATOR_DOT,
|
||||||
DigitGroupingSymbol: core.DIGIT_GROUPING_SYMBOL_COMMA,
|
DigitGroupingSymbol: core.DIGIT_GROUPING_SYMBOL_COMMA,
|
||||||
},
|
},
|
||||||
|
DataConverterTextItems: &DataConverterTextItems{
|
||||||
|
Alipay: "支付宝",
|
||||||
|
},
|
||||||
VerifyEmailTextItems: &VerifyEmailTextItems{
|
VerifyEmailTextItems: &VerifyEmailTextItems{
|
||||||
Title: "验证邮箱",
|
Title: "验证邮箱",
|
||||||
SalutationFormat: "%s 您好,",
|
SalutationFormat: "%s 您好,",
|
||||||
|
|||||||
@@ -76,6 +76,21 @@ func GetFirstLowerCharString(s string) string {
|
|||||||
return string(chars)
|
return string(chars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainsOnlyOneRune returns the source string only contains one character
|
||||||
|
func ContainsOnlyOneRune(s string, r rune) bool {
|
||||||
|
if len(s) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if rune(s[i]) != r {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// GetRandomString returns a random string of which length is n
|
// GetRandomString returns a random string of which length is n
|
||||||
func GetRandomString(n int) (string, error) {
|
func GetRandomString(n int) (string, error) {
|
||||||
var result = make([]byte, n)
|
var result = make([]byte, n)
|
||||||
|
|||||||
@@ -82,6 +82,14 @@ func TestGetFirstLowerCharString(t *testing.T) {
|
|||||||
assert.Equal(t, expectedValue, actualValue)
|
assert.Equal(t, expectedValue, actualValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContainsOnlyOneRune(t *testing.T) {
|
||||||
|
actualValue := ContainsOnlyOneRune("-------", '-')
|
||||||
|
assert.Equal(t, true, actualValue)
|
||||||
|
|
||||||
|
actualValue = ContainsOnlyOneRune(" -------", '-')
|
||||||
|
assert.Equal(t, false, actualValue)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetRandomString(t *testing.T) {
|
func TestGetRandomString(t *testing.T) {
|
||||||
actualValue, err := GetRandomString(10)
|
actualValue, err := GetRandomString(10)
|
||||||
assert.Equal(t, nil, err)
|
assert.Equal(t, nil, err)
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ const supportedImportFileTypes = [
|
|||||||
type: 'feidee_mymoney_xls',
|
type: 'feidee_mymoney_xls',
|
||||||
name: 'Feidee MyMoney (Web) Data Export File',
|
name: 'Feidee MyMoney (Web) Data Export File',
|
||||||
extensions: '.xls'
|
extensions: '.xls'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'alipay_csv',
|
||||||
|
name: 'Alipay Data Export File',
|
||||||
|
extensions: '.csv'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -1112,10 +1112,6 @@
|
|||||||
"fewer fields in data row than in header row": "There are fewer fields in the data row than in the header row",
|
"fewer fields in data row than in header row": "There are fewer fields in the data row than in the header row",
|
||||||
"transaction time is invalid": "Transaction time is invalid",
|
"transaction time is invalid": "Transaction time is invalid",
|
||||||
"transaction time zone is invalid": "Transaction time zone is invalid",
|
"transaction time zone is invalid": "Transaction time zone is invalid",
|
||||||
"category name cannot be blank": "Category name cannot be blank",
|
|
||||||
"secondary category name cannot be blank": "secondary category name cannot be blank",
|
|
||||||
"account name cannot be blank": "Account name cannot be blank",
|
|
||||||
"destination account name cannot be blank": "Destination account name cannot be blank",
|
|
||||||
"transaction amount is invalid": "Transaction amount is invalid",
|
"transaction amount is invalid": "Transaction amount is invalid",
|
||||||
"geographic location is invalid": "Geographic location is invalid",
|
"geographic location is invalid": "Geographic location is invalid",
|
||||||
"fields in multiple table headers are different": "Fields in multiple table headers are different",
|
"fields in multiple table headers are different": "Fields in multiple table headers are different",
|
||||||
@@ -1505,6 +1501,7 @@
|
|||||||
"ezbookkeeping Data Export File (TSV)": "ezbookkeeping Data Export File (TSV)",
|
"ezbookkeeping Data Export File (TSV)": "ezbookkeeping Data Export File (TSV)",
|
||||||
"Feidee MyMoney (Web) Data Export File": "Feidee MyMoney (Web) Data Export File",
|
"Feidee MyMoney (Web) Data Export File": "Feidee MyMoney (Web) Data Export File",
|
||||||
"Feidee MyMoney (App) Data Export File": "Feidee MyMoney (App) Data Export File",
|
"Feidee MyMoney (App) Data Export File": "Feidee MyMoney (App) Data Export File",
|
||||||
|
"Alipay Data Export File": "Alipay Data Export File",
|
||||||
"Data File": "Data File",
|
"Data File": "Data File",
|
||||||
"No data to import": "No data to import",
|
"No data to import": "No data to import",
|
||||||
"Unable to parse import file": "Unable to parse import file",
|
"Unable to parse import file": "Unable to parse import file",
|
||||||
|
|||||||
@@ -1112,10 +1112,6 @@
|
|||||||
"fewer fields in data row than in header row": "数据行中的字段少于比标题行中的字段",
|
"fewer fields in data row than in header row": "数据行中的字段少于比标题行中的字段",
|
||||||
"transaction time is invalid": "交易时间无效",
|
"transaction time is invalid": "交易时间无效",
|
||||||
"transaction time zone is invalid": "交易时区无效",
|
"transaction time zone is invalid": "交易时区无效",
|
||||||
"category name cannot be blank": "分类名称不能为空",
|
|
||||||
"secondary category name cannot be blank": "二级分类名称不能为空",
|
|
||||||
"account name cannot be blank": "账户名不能为空",
|
|
||||||
"destination account name cannot be blank": "目标账户名不能为空",
|
|
||||||
"transaction amount is invalid": "交易金额无效",
|
"transaction amount is invalid": "交易金额无效",
|
||||||
"geographic location is invalid": "地理位置无效",
|
"geographic location is invalid": "地理位置无效",
|
||||||
"fields in multiple table headers are different": "多个表头中的字段不同",
|
"fields in multiple table headers are different": "多个表头中的字段不同",
|
||||||
@@ -1505,6 +1501,7 @@
|
|||||||
"ezbookkeeping Data Export File (TSV)": "ezbookkeeping 数据导出文件 (TSV)",
|
"ezbookkeeping Data Export File (TSV)": "ezbookkeeping 数据导出文件 (TSV)",
|
||||||
"Feidee MyMoney (Web) Data Export File": "金蝶随手记 (Web版) 数据导出文件",
|
"Feidee MyMoney (Web) Data Export File": "金蝶随手记 (Web版) 数据导出文件",
|
||||||
"Feidee MyMoney (App) Data Export File": "金蝶随手记 (App) 数据导出文件",
|
"Feidee MyMoney (App) Data Export File": "金蝶随手记 (App) 数据导出文件",
|
||||||
|
"Alipay Data Export File": "支付宝数据导出文件",
|
||||||
"Data File": "数据文件",
|
"Data File": "数据文件",
|
||||||
"No data to import": "没有可以导入的数据",
|
"No data to import": "没有可以导入的数据",
|
||||||
"Unable to parse import file": "无法解析导入的文件",
|
"Unable to parse import file": "无法解析导入的文件",
|
||||||
|
|||||||
Reference in New Issue
Block a user