import transactions from JD.com finance statement file (#240)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package jdcom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
// jdComFinanceTransactionDataCsvFileImporter defines the structure of jd.com finance csv importer for transaction data
|
||||
type jdComFinanceTransactionDataCsvFileImporter struct {
|
||||
fileHeaderLineBeginning string
|
||||
dataHeaderStartContentBeginning string
|
||||
}
|
||||
|
||||
// Initialize a jd.com finance transaction data csv file importer singleton instance
|
||||
var (
|
||||
JDComFinanceTransactionDataCsvFileImporter = &jdComFinanceTransactionDataCsvFileImporter{}
|
||||
)
|
||||
|
||||
// ParseImportedData returns the imported data by parsing the jd.com finance transaction csv data
|
||||
func (c *jdComFinanceTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
fallback := unicode.UTF8.NewDecoder()
|
||||
reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback))
|
||||
|
||||
csvDataTable, err := csv.CreateNewCsvBasicDataTable(ctx, reader, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
dataTable, err := createNewJDComFinanceTransactionBasicDataTable(ctx, csvDataTable)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
commonDataTable := datatable.CreateNewCommonDataTableFromBasicDataTable(dataTable)
|
||||
|
||||
if !commonDataTable.HasColumn(jdComFinanceTransactionTimeColumnName) ||
|
||||
!commonDataTable.HasColumn(jdComFinanceTransactionMerchantNameColumnName) ||
|
||||
!commonDataTable.HasColumn(jdComFinanceTransactionMemoColumnName) ||
|
||||
!commonDataTable.HasColumn(jdComFinanceTransactionAmountColumnName) ||
|
||||
!commonDataTable.HasColumn(jdComFinanceTransactionRelatedAccountColumnName) ||
|
||||
!commonDataTable.HasColumn(jdComFinanceTransactionStatusColumnName) ||
|
||||
!commonDataTable.HasColumn(jdComFinanceTransactionTypeColumnName) {
|
||||
log.Errorf(ctx, "[jdcom_finance_transaction_data_csv_file_importer.ParseImportedData] cannot parse jd.com finance csv data, because missing essential columns in header row")
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrMissingRequiredFieldInHeaderRow
|
||||
}
|
||||
|
||||
transactionRowParser := createJDComFinanceTransactionDataRowParser(dataTable.HeaderColumnNames())
|
||||
transactionDataTable := datatable.CreateNewTransactionDataTableFromCommonDataTable(commonDataTable, jdComFinanceTransactionSupportedColumns, transactionRowParser)
|
||||
dataTableImporter := converter.CreateNewSimpleImporterWithTypeNameMapping(jdComFinanceTransactionTypeNameMapping)
|
||||
|
||||
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
package jdcom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"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 TestJDComFinanceCsvFileImporterParseImportedData_MinimumValidData(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,余额,交易成功,收入,其他\n" +
|
||||
"2025-09-01 12:34:56,xxx,xxx,123.45,银行卡,交易成功,支出,其他网购\n" +
|
||||
"2025-09-01 23:59:59,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n" +
|
||||
"2025-09-02 23:59:59,xxx,京东余额提现,0.03,银行卡,交易成功,不计收支,余额\n"
|
||||
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, 4, len(allNewTransactions))
|
||||
assert.Equal(t, 3, len(allNewAccounts))
|
||||
assert.Equal(t, 1, len(allNewSubExpenseCategories))
|
||||
assert.Equal(t, 1, len(allNewSubIncomeCategories))
|
||||
assert.Equal(t, 1, len(allNewSubTransferCategories))
|
||||
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, "2025-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)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[1].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[1].Type)
|
||||
assert.Equal(t, "2025-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, "2025-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, "xxx", 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, "2025-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, "xxx", 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, "余额", allNewAccounts[0].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[0].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[1].Uid)
|
||||
assert.Equal(t, "银行卡", allNewAccounts[1].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[1].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[2].Uid)
|
||||
assert.Equal(t, "xxx", allNewAccounts[2].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[2].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubExpenseCategories[0].Uid)
|
||||
assert.Equal(t, "其他网购", allNewSubExpenseCategories[0].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubIncomeCategories[0].Uid)
|
||||
assert.Equal(t, "其他", allNewSubIncomeCategories[0].Name)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewSubTransferCategories[0].Uid)
|
||||
assert.Equal(t, "余额", allNewSubTransferCategories[0].Name)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseRefundTransaction(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data1 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,退款成功,不计收支\n" +
|
||||
"2025-09-01 02:34:56,xxx,xxx,0.12(已全额退款),银行卡,交易成功,不计收支\n" +
|
||||
"2025-09-02 01:23:45,xxx,xxx,3.45,银行卡,退款成功,不计收支\n" +
|
||||
"2025-09-02 02:34:56,xxx,xxx,123.45(已退款3.45),银行卡,交易成功,支出\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, 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, "2025-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, int64(1234567890), allNewTransactions[1].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[1].Type)
|
||||
assert.Equal(t, "2025-09-01 02:34:56", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[1].TransactionTime), time.UTC))
|
||||
assert.Equal(t, int64(12), allNewTransactions[1].Amount)
|
||||
assert.Equal(t, "银行卡", allNewTransactions[1].OriginalSourceAccountName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[2].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[2].Type)
|
||||
assert.Equal(t, "2025-09-02 01:23:45", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[2].TransactionTime), time.UTC))
|
||||
assert.Equal(t, int64(-345), allNewTransactions[2].Amount)
|
||||
assert.Equal(t, "银行卡", allNewTransactions[2].OriginalSourceAccountName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[3].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[3].Type)
|
||||
assert.Equal(t, "2025-09-02 02:34:56", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[3].TransactionTime), time.UTC))
|
||||
assert.Equal(t, int64(12345), allNewTransactions[3].Amount)
|
||||
assert.Equal(t, "银行卡", allNewTransactions[3].OriginalSourceAccountName)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data1 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01T01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
|
||||
data2 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"09/01/2025 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrTransactionTimeInvalid.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,转账\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,¥0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseAccountName(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
// transfer to jd.com finance wallet
|
||||
data1 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,京东钱包余额充值,0.05,银行卡,交易成功,不计收支,余额\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "银行卡", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "xxx", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
|
||||
// transfer from jd.com finance wallet
|
||||
data2 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,京东余额提现,0.05,银行卡,交易成功,不计收支,余额\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "xxx", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "银行卡", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
|
||||
// transfer from other account
|
||||
data3 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,京东小金库-转入,0.05,余额,交易成功,不计收支,小金库\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "余额", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "xxx", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
|
||||
// transfer to other account
|
||||
data4 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,京东小金库-转出,0.05,余额,交易成功,不计收支,小金库\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "xxx", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "余额", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
|
||||
// refund
|
||||
data5 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,价保退款,0.05,银行卡,交易成功,不计收支,其他\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "银行卡", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[0].Type)
|
||||
|
||||
// repayment
|
||||
data6 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支,交易分类\n" +
|
||||
"2025-09-01 01:23:45,xxx,白条主动还款,0.05,银行卡,交易成功,不计收支,白条\n"
|
||||
assert.Nil(t, err)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data6), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "银行卡", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "xxx", allNewTransactions[0].OriginalDestinationAccountName)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[0].Type)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_ParseDescription(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data1 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,,0.12,银行卡,交易成功,支出\n"
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "", allNewTransactions[0].Comment)
|
||||
|
||||
data2 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\"foo\"\"bar,\ntest\"\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "foo\"bar,\ntest", allNewTransactions[0].Comment)
|
||||
|
||||
data3 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,交易说明,金额,收/付款方式,交易状态,收/支,备注\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,Test,0.12,银行卡,交易成功,支出,\n"
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
assert.Equal(t, 1, len(allNewTransactions))
|
||||
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownStatusTransaction(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,xxxx,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_SkipUnknownMemoTransferTransaction(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,不计收支\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_MissingFileHeader(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data := "交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(""), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
// Missing Time Column
|
||||
data1 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"xxx,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data1), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidFileHeader.Message)
|
||||
|
||||
// Missing Merchant Name Column
|
||||
data2 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,交易说明,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data2), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Transaction Memo Column
|
||||
data3 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,金额,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,0.12,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data3), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Amount Column
|
||||
data4 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,收/付款方式,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,银行卡,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data4), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Related Account Column
|
||||
data5 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,交易状态,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,交易成功,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data5), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Status Column
|
||||
data6 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,收/支\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,支出\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data6), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
|
||||
// Missing Type Column
|
||||
data7 := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态\n" +
|
||||
"2025-09-01 01:23:45,xxx,xxx,0.12,银行卡,交易成功\n"
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(data7), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrMissingRequiredFieldInHeaderRow.Message)
|
||||
}
|
||||
|
||||
func TestJDComFinanceCsvFileImporterParseImportedData_NoTransactionData(t *testing.T) {
|
||||
converter := JDComFinanceTransactionDataCsvFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
data := "导出信息:\n" +
|
||||
"京东账号名:xxxxxx\n" +
|
||||
"日期区间:2025-01-01 至 2025-09-01\n" +
|
||||
"\n" +
|
||||
"交易时间,商户名称,交易说明,金额,收/付款方式,交易状态,收/支\n"
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(data), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotFoundTransactionDataInFile.Message)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package jdcom
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/csv"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
)
|
||||
|
||||
func createNewJDComFinanceTransactionBasicDataTable(ctx core.Context, originalDataTable datatable.BasicDataTable) (datatable.BasicDataTable, error) {
|
||||
iterator := originalDataTable.DataRowIterator()
|
||||
allOriginalLines := make([][]string, 0)
|
||||
hasFileHeader := false
|
||||
foundDataHeaderLine := false
|
||||
|
||||
for iterator.HasNext() {
|
||||
row := iterator.Next()
|
||||
|
||||
if !hasFileHeader {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if strings.Index(row.GetData(0), jdComFinanceTransactionDataCsvFileHeader) == 0 {
|
||||
hasFileHeader = true
|
||||
continue
|
||||
} else {
|
||||
log.Warnf(ctx, "[jdcom_finance_transaction_data_extrator.createNewJDComFinanceTransactionBasicDataTable] read unexpected line in row \"%s\" before read file header", iterator.CurrentRowId())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !foundDataHeaderLine {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
} else if row.GetData(0) == jdComFinanceTransactionTimeColumnName {
|
||||
foundDataHeaderLine = true
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if foundDataHeaderLine {
|
||||
if row.ColumnCount() <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
items := make([]string, row.ColumnCount())
|
||||
|
||||
for i := 0; i < row.ColumnCount(); i++ {
|
||||
items[i] = strings.TrimRight(strings.Trim(row.GetData(i), " "), "\t")
|
||||
}
|
||||
|
||||
if len(allOriginalLines) > 0 && len(items) < len(allOriginalLines[0]) {
|
||||
log.Errorf(ctx, "[jdcom_finance_transaction_data_extrator.createNewJDComFinanceTransactionBasicDataTable] cannot parse row \"%s\", because may missing some columns (column count %d in data row is less than header column count %d)", iterator.CurrentRowId(), len(items), len(allOriginalLines[0]))
|
||||
return nil, errs.ErrFewerFieldsInDataRowThanInHeaderRow
|
||||
}
|
||||
|
||||
allOriginalLines = append(allOriginalLines, items)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasFileHeader || !foundDataHeaderLine {
|
||||
return nil, errs.ErrInvalidFileHeader
|
||||
}
|
||||
|
||||
if len(allOriginalLines) < 2 {
|
||||
log.Errorf(ctx, "[jdcom_finance_transaction_data_extrator.createNewJDComFinanceTransactionBasicDataTable] cannot parse import data, because data table row count is less 1")
|
||||
return nil, errs.ErrNotFoundTransactionDataInFile
|
||||
}
|
||||
|
||||
return csv.CreateNewCustomCsvBasicDataTable(allOriginalLines, true), nil
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package jdcom
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
const jdComFinanceTransactionDataCsvFileHeader = "导出信息:"
|
||||
|
||||
const jdComFinanceTransactionTimeColumnName = "交易时间"
|
||||
const jdComFinanceTransactionMerchantNameColumnName = "商户名称"
|
||||
const jdComFinanceTransactionMemoColumnName = "交易说明"
|
||||
const jdComFinanceTransactionAmountColumnName = "金额"
|
||||
const jdComFinanceTransactionRelatedAccountColumnName = "收/付款方式"
|
||||
const jdComFinanceTransactionStatusColumnName = "交易状态"
|
||||
const jdComFinanceTransactionTypeColumnName = "收/支"
|
||||
const jdComFinanceTransactionCategoryColumnName = "交易分类"
|
||||
const jdComFinanceTransactionDescriptionColumnName = "备注"
|
||||
|
||||
const jdComFinanceTransactionAmountRefundAll = "(已全额退款)"
|
||||
|
||||
const jdComFinanceTransactionMemoTransferToWalletPrefix = "充值"
|
||||
const jdComFinanceTransactionMemoTransferFromWalletPrefix = "提现"
|
||||
const jdComFinanceTransactionMemoTransferInText = "转入"
|
||||
const jdComFinanceTransactionMemoTransferOutText = "转出"
|
||||
const jdComFinanceTransactionMemoRepaymentText = "还款"
|
||||
const jdComFinanceTransactionMemoRefundText = "退款"
|
||||
|
||||
const jdComFinanceTransactionDataStatusSuccessName = "交易成功"
|
||||
const jdComFinanceTransactionDataStatusRefundSuccessName = "退款成功"
|
||||
|
||||
var jdComFinanceTransactionSupportedColumns = map[datatable.TransactionDataTableColumn]bool{
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_AMOUNT: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: true,
|
||||
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: true,
|
||||
}
|
||||
|
||||
var jdComFinanceTransactionTypeNameMapping = map[models.TransactionType]string{
|
||||
models.TRANSACTION_TYPE_INCOME: "收入",
|
||||
models.TRANSACTION_TYPE_EXPENSE: "支出",
|
||||
models.TRANSACTION_TYPE_TRANSFER: "不计收支",
|
||||
}
|
||||
|
||||
// jdComFinanceTransactionDataRowParser defines the structure of jd.com finance transaction data row parser
|
||||
type jdComFinanceTransactionDataRowParser struct {
|
||||
existedOriginalDataColumns map[string]bool
|
||||
}
|
||||
|
||||
// Parse returns the converted transaction data row
|
||||
func (p *jdComFinanceTransactionDataRowParser) Parse(ctx core.Context, user *models.User, dataRow datatable.CommonDataTableRow, rowId string) (rowData map[datatable.TransactionDataTableColumn]string, rowDataValid bool, err error) {
|
||||
if dataRow.GetData(jdComFinanceTransactionTypeColumnName) != jdComFinanceTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME] &&
|
||||
dataRow.GetData(jdComFinanceTransactionTypeColumnName) != jdComFinanceTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE] &&
|
||||
dataRow.GetData(jdComFinanceTransactionTypeColumnName) != jdComFinanceTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
||||
log.Warnf(ctx, "[jdcom_finance_transaction_data_row_parser.Parse] skip parsing transaction in row \"%s\", because type is \"%s\"", rowId, dataRow.GetData(jdComFinanceTransactionTypeColumnName))
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
statusName := dataRow.GetData(jdComFinanceTransactionStatusColumnName)
|
||||
|
||||
if statusName != jdComFinanceTransactionDataStatusSuccessName &&
|
||||
statusName != jdComFinanceTransactionDataStatusRefundSuccessName {
|
||||
log.Warnf(ctx, "[jdcom_finance_transaction_data_row_parser.Parse] skip parsing transaction in row \"%s\", because status is \"%s\"", rowId, statusName)
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
data := make(map[datatable.TransactionDataTableColumn]string, len(jdComFinanceTransactionSupportedColumns))
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = dataRow.GetData(jdComFinanceTransactionTimeColumnName)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = dataRow.GetData(jdComFinanceTransactionTypeColumnName)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = dataRow.GetData(jdComFinanceTransactionCategoryColumnName)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(jdComFinanceTransactionRelatedAccountColumnName)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = ""
|
||||
|
||||
if strings.Index(dataRow.GetData(jdComFinanceTransactionAmountColumnName), "(") >= 0 {
|
||||
// If a transaction includes a refund, the original transaction amount will like "-xx.xx(已全额退款)" or "-xx.xx(已退款yy.yy)", along with another refund transaction
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = strings.Split(dataRow.GetData(jdComFinanceTransactionAmountColumnName), "(")[0]
|
||||
} else {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = dataRow.GetData(jdComFinanceTransactionAmountColumnName)
|
||||
}
|
||||
|
||||
if p.hasOriginalColumn(jdComFinanceTransactionDescriptionColumnName) && dataRow.GetData(jdComFinanceTransactionDescriptionColumnName) != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = dataRow.GetData(jdComFinanceTransactionDescriptionColumnName)
|
||||
} else if p.hasOriginalColumn(jdComFinanceTransactionMemoColumnName) && dataRow.GetData(jdComFinanceTransactionMemoColumnName) != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = dataRow.GetData(jdComFinanceTransactionMemoColumnName)
|
||||
} else {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = ""
|
||||
}
|
||||
|
||||
if data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == jdComFinanceTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] {
|
||||
memo := dataRow.GetData(jdComFinanceTransactionMemoColumnName)
|
||||
|
||||
if statusName == jdComFinanceTransactionDataStatusRefundSuccessName || strings.Index(memo, jdComFinanceTransactionMemoRefundText) >= 0 { // refund
|
||||
amount, err := utils.ParseAmount(data[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
|
||||
|
||||
if err == nil {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = jdComFinanceTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE]
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
|
||||
}
|
||||
} else if strings.Index(dataRow.GetData(jdComFinanceTransactionAmountColumnName), jdComFinanceTransactionAmountRefundAll) > 0 { // expense transaction (but include a full refund)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = jdComFinanceTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE]
|
||||
} else { // transfer
|
||||
if strings.Index(memo, jdComFinanceTransactionMemoTransferToWalletPrefix) >= 0 { // transfer to jd.com finance wallet
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = dataRow.GetData(jdComFinanceTransactionMerchantNameColumnName)
|
||||
} else if strings.Index(memo, jdComFinanceTransactionMemoTransferFromWalletPrefix) >= 0 { // transfer from jd.com finance wallet
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(jdComFinanceTransactionMerchantNameColumnName)
|
||||
} else if strings.Index(memo, jdComFinanceTransactionMemoTransferInText) >= 0 { // transfer in
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = dataRow.GetData(jdComFinanceTransactionMerchantNameColumnName)
|
||||
} else if strings.Index(memo, jdComFinanceTransactionMemoTransferOutText) >= 0 { // transfer out
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = dataRow.GetData(jdComFinanceTransactionMerchantNameColumnName)
|
||||
} else if strings.Index(memo, jdComFinanceTransactionMemoRepaymentText) >= 0 { // repayment
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = dataRow.GetData(jdComFinanceTransactionMerchantNameColumnName)
|
||||
} else {
|
||||
log.Warnf(ctx, "[jdcom_finance_transaction_data_row_parser.Parse] skip parsing transaction in row \"%s\", because memo (\"%s\") of this transfer transaction is unknown", rowId, memo)
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
func (p *jdComFinanceTransactionDataRowParser) hasOriginalColumn(columnName string) bool {
|
||||
_, exists := p.existedOriginalDataColumns[columnName]
|
||||
return exists
|
||||
}
|
||||
|
||||
// createJDComFinanceTransactionDataRowParser returns jd.com finance transaction data row parser
|
||||
func createJDComFinanceTransactionDataRowParser(headerColumnNames []string) datatable.CommonTransactionDataRowParser {
|
||||
existedOriginalDataColumns := make(map[string]bool, len(headerColumnNames))
|
||||
|
||||
for i := 0; i < len(headerColumnNames); i++ {
|
||||
existedOriginalDataColumns[headerColumnNames[i]] = true
|
||||
}
|
||||
|
||||
return &jdComFinanceTransactionDataRowParser{
|
||||
existedOriginalDataColumns: existedOriginalDataColumns,
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/fireflyIII"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/gnucash"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/iif"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/jdcom"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/mt"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/ofx"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/converters/qif"
|
||||
@@ -73,6 +74,8 @@ func GetTransactionDataImporter(fileType string) (converter.TransactionDataImpor
|
||||
return wechat.WeChatPayTransactionDataXlsxFileImporter, nil
|
||||
} else if fileType == "wechat_pay_app_csv" {
|
||||
return wechat.WeChatPayTransactionDataCsvFileImporter, nil
|
||||
} else if fileType == "jdcom_finance_app_csv" {
|
||||
return jdcom.JDComFinanceTransactionDataCsvFileImporter, nil
|
||||
} else {
|
||||
return nil, errs.ErrImportFileTypeNotSupported
|
||||
}
|
||||
|
||||
@@ -233,6 +233,15 @@ export const SUPPORTED_IMPORT_FILE_CATEGORY_AND_TYPES: ImportFileCategoryAndType
|
||||
supportMultiLanguages: 'zh-Hans',
|
||||
anchor: '如何获取微信支付账单文件'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'jdcom_finance_app_csv',
|
||||
name: 'JD.com Finance Statement File',
|
||||
extensions: '.csv',
|
||||
document: {
|
||||
supportMultiLanguages: 'zh-Hans',
|
||||
anchor: '如何获取京东金融账单文件'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Datendatei",
|
||||
"Data to import": "Data to import",
|
||||
"Please select a file to import": "Bitte wählen Sie eine Datei zum Importieren aus",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Data File",
|
||||
"Data to import": "Data to import",
|
||||
"Please select a file to import": "Please select a file to import",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Archivo de datos",
|
||||
"Data to import": "Data to import",
|
||||
"Please select a file to import": "Please select a file to import",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "File dati",
|
||||
"Data to import": "Dati da importare",
|
||||
"Please select a file to import": "Seleziona un file da importare",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "データファイル",
|
||||
"Data to import": "インポートするデータ",
|
||||
"Please select a file to import": "インポートするファイルを選択してください",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Gegevensbestand",
|
||||
"Data to import": "Te importeren gegevens",
|
||||
"Please select a file to import": "Selecteer een bestand om te importeren",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Arquivo de Dados",
|
||||
"Data to import": "Dados para importar",
|
||||
"Please select a file to import": "Por favor, selecione um arquivo para importar",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Файл данных",
|
||||
"Data to import": "Data to import",
|
||||
"Please select a file to import": "Please select a file to import",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Файл даних",
|
||||
"Data to import": "Дані для імпорту",
|
||||
"Please select a file to import": "Будь ласка, виберіть файл для імпорту",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "Alipay (App) Statement File",
|
||||
"Alipay (Web) Statement File": "Alipay (Web) Statement File",
|
||||
"WeChat Pay Statement File": "WeChat Pay Statement File",
|
||||
"JD.com Finance Statement File": "JD.com Finance Statement File",
|
||||
"Data File": "Tệp dữ liệu",
|
||||
"Data to import": "Data to import",
|
||||
"Please select a file to import": "Please select a file to import",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "支付宝 (App) 交易流水文件",
|
||||
"Alipay (Web) Statement File": "支付宝 (网页版) 交易流水文件",
|
||||
"WeChat Pay Statement File": "微信支付账单文件",
|
||||
"JD.com Finance Statement File": "京东金融账单文件",
|
||||
"Data File": "数据文件",
|
||||
"Data to import": "要导入的数据",
|
||||
"Please select a file to import": "请选择要导入的文件",
|
||||
|
||||
@@ -1776,6 +1776,7 @@
|
||||
"Alipay (App) Statement File": "支付寶 (App) 交易流水檔案",
|
||||
"Alipay (Web) Statement File": "支付寶 (網頁版) 交易流水檔案",
|
||||
"WeChat Pay Statement File": "微信支付帳單檔案",
|
||||
"JD.com Finance Statement File": "京東金融帳單檔案",
|
||||
"Data File": "資料檔案",
|
||||
"Data to import": "要匯入的資料",
|
||||
"Please select a file to import": "請選擇要匯入的檔案",
|
||||
|
||||
Reference in New Issue
Block a user