import transactions from Feidee Mymoney (Elecloud)

This commit is contained in:
MaysWind
2025-03-15 21:00:53 +08:00
parent 00f8b6d950
commit 78c5b1704a
23 changed files with 734 additions and 0 deletions
@@ -0,0 +1,211 @@
package excel
import (
"bytes"
"fmt"
"github.com/xuri/excelize/v2"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/errs"
)
// excelOOXMLSheet defines the structure of excel (Office Open XML) file sheet
type excelOOXMLSheet struct {
sheetName string
allData [][]string
}
// ExcelOOXMLFileImportedDataTable defines the structure of excel (Office Open XML) file data table
type ExcelOOXMLFileImportedDataTable struct {
sheets []*excelOOXMLSheet
headerLineColumnNames []string
}
// ExcelOOXMLFileDataRow defines the structure of excel (Office Open XML) file data table row
type ExcelOOXMLFileDataRow struct {
sheet *excelOOXMLSheet
rowData []string
rowIndex int
}
// ExcelOOXMLFileDataRowIterator defines the structure of excel (Office Open XML) file data table row iterator
type ExcelOOXMLFileDataRowIterator struct {
dataTable *ExcelOOXMLFileImportedDataTable
currentSheetIndex int
currentRowIndexInSheet int
}
// DataRowCount returns the total count of data row
func (t *ExcelOOXMLFileImportedDataTable) DataRowCount() int {
totalDataRowCount := 0
for i := 0; i < len(t.sheets); i++ {
sheet := t.sheets[i]
if len(sheet.allData) < 1 {
continue
}
totalDataRowCount += len(sheet.allData) - 1
}
return totalDataRowCount
}
// HeaderColumnNames returns the header column name list
func (t *ExcelOOXMLFileImportedDataTable) HeaderColumnNames() []string {
return t.headerLineColumnNames
}
// DataRowIterator returns the iterator of data row
func (t *ExcelOOXMLFileImportedDataTable) DataRowIterator() datatable.ImportedDataRowIterator {
return &ExcelOOXMLFileDataRowIterator{
dataTable: t,
currentSheetIndex: 0,
currentRowIndexInSheet: 0,
}
}
// ColumnCount returns the total count of column in this data row
func (r *ExcelOOXMLFileDataRow) ColumnCount() int {
return len(r.rowData)
}
// GetData returns the data in the specified column index
func (r *ExcelOOXMLFileDataRow) GetData(columnIndex int) string {
if columnIndex < 0 || columnIndex >= len(r.rowData) {
return ""
}
return r.rowData[columnIndex]
}
// HasNext returns whether the iterator does not reach the end
func (t *ExcelOOXMLFileDataRowIterator) HasNext() bool {
sheets := t.dataTable.sheets
if t.currentSheetIndex >= len(sheets) {
return false
}
currentSheet := sheets[t.currentSheetIndex]
if t.currentRowIndexInSheet+1 < len(currentSheet.allData) {
return true
}
for i := t.currentSheetIndex + 1; i < len(sheets); i++ {
sheet := sheets[i]
if len(sheet.allData) <= 1 {
continue
}
return true
}
return false
}
// CurrentRowId returns current index
func (t *ExcelOOXMLFileDataRowIterator) CurrentRowId() string {
return fmt.Sprintf("table#%d-row#%d", t.currentSheetIndex, t.currentRowIndexInSheet)
}
// Next returns the next imported data row
func (t *ExcelOOXMLFileDataRowIterator) Next() datatable.ImportedDataRow {
sheets := t.dataTable.sheets
currentRowIndexInTable := t.currentRowIndexInSheet
for i := t.currentSheetIndex; i < len(sheets); i++ {
sheet := sheets[i]
if currentRowIndexInTable+1 < len(sheet.allData) {
t.currentRowIndexInSheet++
currentRowIndexInTable = t.currentRowIndexInSheet
break
}
t.currentSheetIndex++
t.currentRowIndexInSheet = 0
currentRowIndexInTable = 0
}
if t.currentSheetIndex >= len(sheets) {
return nil
}
currentSheet := sheets[t.currentSheetIndex]
if t.currentRowIndexInSheet >= len(currentSheet.allData) {
return nil
}
return &ExcelOOXMLFileDataRow{
sheet: currentSheet,
rowData: currentSheet.allData[t.currentRowIndexInSheet],
rowIndex: t.currentRowIndexInSheet,
}
}
// CreateNewExcelOOXMLFileImportedDataTable returns excel (Office Open XML) data table by file binary data
func CreateNewExcelOOXMLFileImportedDataTable(data []byte) (*ExcelOOXMLFileImportedDataTable, error) {
reader := bytes.NewReader(data)
file, err := excelize.OpenReader(reader)
defer file.Close()
if err != nil {
return nil, err
}
sheetNames := file.GetSheetList()
var headerRowItems []string
var sheets []*excelOOXMLSheet
for i := 0; i < len(sheetNames); i++ {
sheetName := sheetNames[i]
allData, err := file.GetRows(sheetName)
if err != nil {
return nil, err
}
if allData == nil || len(allData) < 1 {
continue
}
row := allData[0]
if i == 0 {
for j := 0; j < len(row); j++ {
headerItem := row[j]
if headerItem == "" {
break
}
headerRowItems = append(headerRowItems, headerItem)
}
} else {
for j := 0; j < min(len(row), len(headerRowItems)); j++ {
headerItem := row[j]
if headerItem != headerRowItems[j] {
return nil, errs.ErrFieldsInMultiTableAreDifferent
}
}
}
sheets = append(sheets, &excelOOXMLSheet{
sheetName: sheetName,
allData: allData,
})
}
return &ExcelOOXMLFileImportedDataTable{
sheets: sheets,
headerLineColumnNames: headerRowItems,
}, nil
}
@@ -0,0 +1,246 @@
package excel
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/mayswind/ezbookkeeping/pkg/errs"
)
func TestExcelOOXMLFileImportedDataTableDataRowCount(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
assert.Nil(t, err)
assert.Equal(t, 2, datatable.DataRowCount())
}
func TestExcelOOXMLFileImportedDataTableDataRowCount_MultipleSheets(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
assert.Nil(t, err)
assert.Equal(t, 5, datatable.DataRowCount())
}
func TestExcelOOXMLFileImportedDataTableDataRowCount_OnlyHeaderLine(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
assert.Nil(t, err)
assert.Equal(t, 0, datatable.DataRowCount())
}
func TestExcelOOXMLFileImportedDataTableDataRowCount_EmptyContent(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
assert.Nil(t, err)
assert.Equal(t, 0, datatable.DataRowCount())
}
func TestExcelOOXMLFileImportedDataTableHeaderColumnNames(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
assert.EqualValues(t, []string{"A1", "B1", "C1"}, datatable.HeaderColumnNames())
}
func TestExcelOOXMLFileImportedDataTableHeaderColumnNames_EmptyContent(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
assert.Nil(t, datatable.HeaderColumnNames())
}
func TestExcelOOXMLFileDataRowIterator(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
assert.True(t, iterator.HasNext())
// data row 1
assert.NotNil(t, iterator.Next())
assert.True(t, iterator.HasNext())
// data row 2
assert.NotNil(t, iterator.Next())
assert.False(t, iterator.HasNext())
// not existed data row 3
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
// not existed data row 4
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
}
func TestExcelOOXMLFileDataRowIterator_MultipleSheets(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
assert.True(t, iterator.HasNext())
// sheet 1 data row 1
assert.NotNil(t, iterator.Next())
assert.True(t, iterator.HasNext())
// sheet 1 data row 2
assert.NotNil(t, iterator.Next())
assert.True(t, iterator.HasNext())
// sheet 3 data row 1
assert.NotNil(t, iterator.Next())
assert.True(t, iterator.HasNext())
// sheet 5 data row 1
assert.NotNil(t, iterator.Next())
assert.True(t, iterator.HasNext())
// sheet 5 data row 2
assert.NotNil(t, iterator.Next())
assert.False(t, iterator.HasNext())
// not existed data row
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
// not existed data row
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
}
func TestExcelOOXMLFileDataRowIterator_OnlyHeaderLine(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/only_one_row_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
assert.False(t, iterator.HasNext())
// not existed data row 1
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
// not existed data row 2
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
}
func TestExcelOOXMLFileDataRowIterator_EmptyContent(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/empty_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
assert.False(t, iterator.HasNext())
// not existed data row 1
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
// not existed data row 2
assert.Nil(t, iterator.Next())
assert.False(t, iterator.HasNext())
}
func TestExcelOOXMLFileDataRowColumnCount(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
row1 := iterator.Next()
assert.EqualValues(t, 3, row1.ColumnCount())
row2 := iterator.Next()
assert.EqualValues(t, 3, row2.ColumnCount())
}
func TestExcelOOXMLFileDataRowGetData(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
row1 := iterator.Next()
assert.Equal(t, "A2", row1.GetData(0))
assert.Equal(t, "B2", row1.GetData(1))
assert.Equal(t, "C2", row1.GetData(2))
row2 := iterator.Next()
assert.Equal(t, "A3", row2.GetData(0))
assert.Equal(t, "B3", row2.GetData(1))
assert.Equal(t, "C3", row2.GetData(2))
}
func TestExcelOOXMLFileDataRowGetData_GetNotExistedColumnData(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/simple_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
row1 := iterator.Next()
assert.Equal(t, "", row1.GetData(3))
}
func TestExcelOOXMLFileDataRowGetData_MultipleSheets(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_excel_file.xlsx")
assert.Nil(t, err)
datatable, err := CreateNewExcelOOXMLFileImportedDataTable(testdata)
iterator := datatable.DataRowIterator()
sheet1Row1 := iterator.Next()
assert.Equal(t, "1-A2", sheet1Row1.GetData(0))
assert.Equal(t, "1-B2", sheet1Row1.GetData(1))
assert.Equal(t, "1-C2", sheet1Row1.GetData(2))
sheet1Row2 := iterator.Next()
assert.Equal(t, "1-A3", sheet1Row2.GetData(0))
assert.Equal(t, "1-B3", sheet1Row2.GetData(1))
assert.Equal(t, "1-C3", sheet1Row2.GetData(2))
// skip empty sheet2
sheet3Row1 := iterator.Next()
assert.Equal(t, "3-A2", sheet3Row1.GetData(0))
assert.Equal(t, "3-B2", sheet3Row1.GetData(1))
assert.Equal(t, "", sheet3Row1.GetData(2))
// skip no data row sheet4
sheet5Row1 := iterator.Next()
assert.Equal(t, "5-A2", sheet5Row1.GetData(0))
assert.Equal(t, "5-B2", sheet5Row1.GetData(1))
assert.Equal(t, "5-C2", sheet5Row1.GetData(2))
sheet5Row2 := iterator.Next()
assert.Equal(t, "5-A3", sheet5Row2.GetData(0))
assert.Equal(t, "5-B3", sheet5Row2.GetData(1))
assert.Equal(t, "5-C3", sheet5Row2.GetData(2))
}
func TestCreateNewExcelOOXMLFileImportedDataTable_MultipleSheetsWithDifferentHeaders(t *testing.T) {
testdata, err := os.ReadFile("../../../testdata/multiple_sheets_with_different_header_row_excel_file.xlsx")
assert.Nil(t, err)
_, err = CreateNewExcelOOXMLFileImportedDataTable(testdata)
assert.EqualError(t, err, errs.ErrFieldsInMultiTableAreDifferent.Message)
}
@@ -0,0 +1,71 @@
package feidee
import (
"strings"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
var FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_MODIFY_BALANCE_NAME = "余额变更"
var FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_INCOME_NAME = "收入"
var FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_EXPENSE_NAME = "支出"
var feideeMymoneyElecloudTransactionTypeNameMapping = map[string]models.TransactionType{
FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_MODIFY_BALANCE_NAME: models.TRANSACTION_TYPE_MODIFY_BALANCE,
FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_INCOME_NAME: models.TRANSACTION_TYPE_INCOME,
FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_EXPENSE_NAME: models.TRANSACTION_TYPE_EXPENSE,
"转账": models.TRANSACTION_TYPE_TRANSFER,
"借入": models.TRANSACTION_TYPE_TRANSFER,
"借出": models.TRANSACTION_TYPE_TRANSFER,
"收债": models.TRANSACTION_TYPE_TRANSFER,
"还债": models.TRANSACTION_TYPE_TRANSFER,
"代付": models.TRANSACTION_TYPE_TRANSFER,
"报销": models.TRANSACTION_TYPE_TRANSFER,
"退款": models.TRANSACTION_TYPE_EXPENSE,
}
// feideeMymoneyElecloudTransactionDataRowParser defines the structure of feidee mymoney (elecloud) transaction data row parser
type feideeMymoneyElecloudTransactionDataRowParser struct {
}
// GetAddedColumns returns the added columns after converting the data row
func (p *feideeMymoneyElecloudTransactionDataRowParser) GetAddedColumns() []datatable.TransactionDataTableColumn {
return nil
}
// Parse returns the converted transaction data row
func (p *feideeMymoneyElecloudTransactionDataRowParser) Parse(data map[datatable.TransactionDataTableColumn]string) (rowData map[datatable.TransactionDataTableColumn]string, rowDataValid bool, err error) {
rowData = make(map[datatable.TransactionDataTableColumn]string, len(data))
for column, value := range data {
rowData[column] = value
}
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = strings.ReplaceAll(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT], ",", "") // remove thousand separator
if rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] == FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_MODIFY_BALANCE_NAME {
amount, err := utils.ParseAmount(rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT])
if err != nil {
return nil, false, errs.ErrAmountInvalid
}
// balance modification transaction in feidee mymoney (elecloud) is not the opening balance transaction, it can be added many times
if amount >= 0 {
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_INCOME_NAME
} else {
rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = FEIDEE_MYMONEY_ELECLOUD_TRANSACTION_TYPE_EXPENSE_NAME
rowData[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
}
}
return rowData, true, nil
}
// createFeideeMymoneyElecloudTransactionDataRowParser returns feidee mymoney (elecloud) transaction data row parser
func createFeideeMymoneyElecloudTransactionDataRowParser() datatable.TransactionDataRowParser {
return &feideeMymoneyElecloudTransactionDataRowParser{}
}
@@ -0,0 +1,46 @@
package feidee
import (
"github.com/mayswind/ezbookkeeping/pkg/converters/converter"
"github.com/mayswind/ezbookkeeping/pkg/converters/datatable"
"github.com/mayswind/ezbookkeeping/pkg/converters/excel"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
)
var feideeMymoneyElecloudDataColumnNameMapping = map[datatable.TransactionDataTableColumn]string{
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME: "日期",
datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE: "交易类型",
datatable.TRANSACTION_DATA_TABLE_CATEGORY: "分类",
datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY: "子分类",
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME: "账户1",
datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY: "账户币种",
datatable.TRANSACTION_DATA_TABLE_AMOUNT: "金额",
datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME: "账户2",
datatable.TRANSACTION_DATA_TABLE_DESCRIPTION: "备注",
}
// feideeMymoneyElecloudTransactionDataXlsxFileImporter defines the structure of feidee mymoney (elecloud) xlsx importer for transaction data
type feideeMymoneyElecloudTransactionDataXlsxFileImporter struct {
converter.DataTableTransactionDataImporter
}
// Initialize a feidee mymoney (elecloud) transaction data xlsx file importer singleton instance
var (
FeideeMymoneyElecloudTransactionDataXlsxFileImporter = &feideeMymoneyElecloudTransactionDataXlsxFileImporter{}
)
// ParseImportedData returns the imported data by parsing the feidee mymoney (elecloud) transaction xlsx data
func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
dataTable, err := excel.CreateNewExcelOOXMLFileImportedDataTable(data)
if err != nil {
return nil, nil, nil, nil, nil, nil, err
}
transactionRowParser := createFeideeMymoneyElecloudTransactionDataRowParser()
transactionDataTable := datatable.CreateNewImportedTransactionDataTableWithRowParser(dataTable, feideeMymoneyElecloudDataColumnNameMapping, transactionRowParser)
dataTableImporter := converter.CreateNewSimpleImporter(feideeMymoneyElecloudTransactionTypeNameMapping)
return dataTableImporter.ParseImportedData(ctx, user, transactionDataTable, defaultTimezoneOffset, accountMap, expenseCategoryMap, incomeCategoryMap, transferCategoryMap, tagMap)
}
@@ -0,0 +1,117 @@
package feidee
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/models"
"github.com/mayswind/ezbookkeeping/pkg/utils"
)
func TestFeideeMymoneyElecloudTransactionDataXlsxImporterParseImportedData_MinimumValidData(t *testing.T) {
converter := FeideeMymoneyElecloudTransactionDataXlsxFileImporter
context := core.NewNullContext()
user := &models.User{
Uid: 1234567890,
DefaultCurrency: "USD",
}
testdata, err := os.ReadFile("../../../testdata/feidee_mymoney_elecloud_test_file.xlsx")
assert.Nil(t, err)
allNewTransactions, allNewAccounts, allNewSubExpenseCategories, allNewSubIncomeCategories, allNewSubTransferCategories, allNewTags, err := converter.ParseImportedData(context, user, testdata, 0, nil, nil, nil, nil, nil)
assert.Nil(t, err)
assert.Equal(t, 7, len(allNewTransactions))
assert.Equal(t, 2, len(allNewAccounts))
assert.Equal(t, 3, len(allNewSubExpenseCategories))
assert.Equal(t, 3, 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, "2024-09-01 00:00:00", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime), time.UTC))
assert.Equal(t, int64(12345), allNewTransactions[0].Amount)
assert.Equal(t, "Test Account", 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 01:00:00", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[1].TransactionTime), time.UTC))
assert.Equal(t, int64(12), allNewTransactions[1].Amount)
assert.Equal(t, "Test Account2", allNewTransactions[1].OriginalSourceAccountName)
assert.Equal(t, "", allNewTransactions[1].OriginalCategoryName)
assert.Equal(t, int64(1234567890), allNewTransactions[2].Uid)
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[2].Type)
assert.Equal(t, "2024-09-01 01:23:45", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[2].TransactionTime), time.UTC))
assert.Equal(t, int64(12), allNewTransactions[2].Amount)
assert.Equal(t, "Test Account", allNewTransactions[2].OriginalSourceAccountName)
assert.Equal(t, "Test Category", allNewTransactions[2].OriginalCategoryName)
assert.Equal(t, int64(1234567890), allNewTransactions[3].Uid)
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[3].Type)
assert.Equal(t, "2024-09-01 12:34:56", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[3].TransactionTime), time.UTC))
assert.Equal(t, int64(100), allNewTransactions[3].Amount)
assert.Equal(t, "Test Account2", allNewTransactions[3].OriginalSourceAccountName)
assert.Equal(t, "Test Category2", allNewTransactions[3].OriginalCategoryName)
assert.Equal(t, int64(1234567890), allNewTransactions[4].Uid)
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[4].Type)
assert.Equal(t, "2024-09-01 23:59:59", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[4].TransactionTime), time.UTC))
assert.Equal(t, int64(5), allNewTransactions[4].Amount)
assert.Equal(t, "Test Comment5", allNewTransactions[4].Comment)
assert.Equal(t, "Test Account", allNewTransactions[4].OriginalSourceAccountName)
assert.Equal(t, "Test Account2", allNewTransactions[4].OriginalDestinationAccountName)
assert.Equal(t, "", allNewTransactions[4].OriginalCategoryName)
assert.Equal(t, int64(1234567890), allNewTransactions[5].Uid)
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[5].Type)
assert.Equal(t, "2024-09-10 00:00:00", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[5].TransactionTime), time.UTC))
assert.Equal(t, int64(-654300), allNewTransactions[5].Amount)
assert.Equal(t, "Test Account2", allNewTransactions[5].OriginalSourceAccountName)
assert.Equal(t, "Test Category5", allNewTransactions[5].OriginalCategoryName)
assert.Equal(t, int64(1234567890), allNewTransactions[6].Uid)
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[6].Type)
assert.Equal(t, "2024-09-11 05:06:00", utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(allNewTransactions[6].TransactionTime), time.UTC))
assert.Equal(t, int64(-112340), allNewTransactions[6].Amount)
assert.Equal(t, "Foo#\\r\\nBar", allNewTransactions[6].Comment)
assert.Equal(t, "Test Account", allNewTransactions[6].OriginalSourceAccountName)
assert.Equal(t, "Test Category4", allNewTransactions[6].OriginalCategoryName)
assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid)
assert.Equal(t, "Test Account2", allNewAccounts[0].Name)
assert.Equal(t, "CNY", allNewAccounts[0].Currency)
assert.Equal(t, int64(1234567890), allNewAccounts[1].Uid)
assert.Equal(t, "Test Account", allNewAccounts[1].Name)
assert.Equal(t, "CNY", allNewAccounts[1].Currency)
assert.Equal(t, int64(1234567890), allNewSubExpenseCategories[0].Uid)
assert.Equal(t, "", allNewSubExpenseCategories[0].Name)
assert.Equal(t, int64(1234567890), allNewSubExpenseCategories[1].Uid)
assert.Equal(t, "Test Category4", allNewSubExpenseCategories[1].Name)
assert.Equal(t, int64(1234567890), allNewSubExpenseCategories[2].Uid)
assert.Equal(t, "Test Category2", allNewSubExpenseCategories[2].Name)
assert.Equal(t, int64(1234567890), allNewSubIncomeCategories[0].Uid)
assert.Equal(t, "", allNewSubIncomeCategories[0].Name)
assert.Equal(t, int64(1234567890), allNewSubIncomeCategories[1].Uid)
assert.Equal(t, "Test Category5", allNewSubIncomeCategories[1].Name)
assert.Equal(t, int64(1234567890), allNewSubIncomeCategories[2].Uid)
assert.Equal(t, "Test Category", allNewSubIncomeCategories[2].Name)
assert.Equal(t, int64(1234567890), allNewSubTransferCategories[0].Uid)
assert.Equal(t, "", allNewSubTransferCategories[0].Name)
}
@@ -54,6 +54,8 @@ func GetTransactionDataImporter(fileType string) (converter.TransactionDataImpor
return feidee.FeideeMymoneyAppTransactionDataCsvFileImporter, nil
} else if fileType == "feidee_mymoney_xls" {
return feidee.FeideeMymoneyWebTransactionDataXlsFileImporter, nil
} else if fileType == "feidee_mymoney_elecloud_xlsx" {
return feidee.FeideeMymoneyElecloudTransactionDataXlsxFileImporter, nil
} else if fileType == "alipay_app_csv" {
return alipay.AlipayAppTransactionDataCsvFileImporter, nil
} else if fileType == "alipay_web_csv" {