mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-19 01:04:25 +08:00
code refactor
This commit is contained in:
+23
-23
@@ -20,16 +20,16 @@ const pageCountForDataExport = 1000
|
|||||||
// DataManagementsApi represents data management api
|
// DataManagementsApi represents data management api
|
||||||
type DataManagementsApi struct {
|
type DataManagementsApi struct {
|
||||||
ApiUsingConfig
|
ApiUsingConfig
|
||||||
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileConverter
|
ezBookKeepingCsvConverter converters.TransactionDataConverter
|
||||||
ezBookKeepingTsvExporter *converters.EzBookKeepingTSVFileConverter
|
ezBookKeepingTsvConverter converters.TransactionDataConverter
|
||||||
tokens *services.TokenService
|
tokens *services.TokenService
|
||||||
users *services.UserService
|
users *services.UserService
|
||||||
accounts *services.AccountService
|
accounts *services.AccountService
|
||||||
transactions *services.TransactionService
|
transactions *services.TransactionService
|
||||||
categories *services.TransactionCategoryService
|
categories *services.TransactionCategoryService
|
||||||
tags *services.TransactionTagService
|
tags *services.TransactionTagService
|
||||||
pictures *services.TransactionPictureService
|
pictures *services.TransactionPictureService
|
||||||
templates *services.TransactionTemplateService
|
templates *services.TransactionTemplateService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize a data management api singleton instance
|
// Initialize a data management api singleton instance
|
||||||
@@ -38,16 +38,16 @@ var (
|
|||||||
ApiUsingConfig: ApiUsingConfig{
|
ApiUsingConfig: ApiUsingConfig{
|
||||||
container: settings.Container,
|
container: settings.Container,
|
||||||
},
|
},
|
||||||
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileConverter{},
|
ezBookKeepingCsvConverter: converters.EzBookKeepingTransactionDataCSVFileConverter,
|
||||||
ezBookKeepingTsvExporter: &converters.EzBookKeepingTSVFileConverter{},
|
ezBookKeepingTsvConverter: converters.EzBookKeepingTransactionDataTSVFileConverter,
|
||||||
tokens: services.Tokens,
|
tokens: services.Tokens,
|
||||||
users: services.Users,
|
users: services.Users,
|
||||||
accounts: services.Accounts,
|
accounts: services.Accounts,
|
||||||
transactions: services.Transactions,
|
transactions: services.Transactions,
|
||||||
categories: services.TransactionCategories,
|
categories: services.TransactionCategories,
|
||||||
tags: services.TransactionTags,
|
tags: services.TransactionTags,
|
||||||
pictures: services.TransactionPictures,
|
pictures: services.TransactionPictures,
|
||||||
templates: services.TransactionTemplates,
|
templates: services.TransactionTemplates,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -247,12 +247,12 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
|
|||||||
return nil, "", errs.ErrOperationFailed
|
return nil, "", errs.ErrOperationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataExporter converters.DataConverter
|
var dataExporter converters.TransactionDataExporter
|
||||||
|
|
||||||
if fileType == "tsv" {
|
if fileType == "tsv" {
|
||||||
dataExporter = a.ezBookKeepingTsvExporter
|
dataExporter = a.ezBookKeepingTsvConverter
|
||||||
} else {
|
} else {
|
||||||
dataExporter = a.ezBookKeepingCsvExporter
|
dataExporter = a.ezBookKeepingCsvConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := dataExporter.ToExportedContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexes)
|
result, err := dataExporter.ToExportedContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexes)
|
||||||
|
|||||||
+26
-26
@@ -20,16 +20,16 @@ const pageCountForDataExport = 1000
|
|||||||
// UserDataCli represents user data cli
|
// UserDataCli represents user data cli
|
||||||
type UserDataCli struct {
|
type UserDataCli struct {
|
||||||
CliUsingConfig
|
CliUsingConfig
|
||||||
ezBookKeepingCsvExporter *converters.EzBookKeepingCSVFileConverter
|
ezBookKeepingCsvConverter converters.TransactionDataConverter
|
||||||
ezBookKeepingTsvExporter *converters.EzBookKeepingTSVFileConverter
|
ezBookKeepingTsvConverter converters.TransactionDataConverter
|
||||||
accounts *services.AccountService
|
accounts *services.AccountService
|
||||||
transactions *services.TransactionService
|
transactions *services.TransactionService
|
||||||
categories *services.TransactionCategoryService
|
categories *services.TransactionCategoryService
|
||||||
tags *services.TransactionTagService
|
tags *services.TransactionTagService
|
||||||
users *services.UserService
|
users *services.UserService
|
||||||
twoFactorAuthorizations *services.TwoFactorAuthorizationService
|
twoFactorAuthorizations *services.TwoFactorAuthorizationService
|
||||||
tokens *services.TokenService
|
tokens *services.TokenService
|
||||||
forgetPasswords *services.ForgetPasswordService
|
forgetPasswords *services.ForgetPasswordService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize an user data cli singleton instance
|
// Initialize an user data cli singleton instance
|
||||||
@@ -38,16 +38,16 @@ var (
|
|||||||
CliUsingConfig: CliUsingConfig{
|
CliUsingConfig: CliUsingConfig{
|
||||||
container: settings.Container,
|
container: settings.Container,
|
||||||
},
|
},
|
||||||
ezBookKeepingCsvExporter: &converters.EzBookKeepingCSVFileConverter{},
|
ezBookKeepingCsvConverter: converters.EzBookKeepingTransactionDataCSVFileConverter,
|
||||||
ezBookKeepingTsvExporter: &converters.EzBookKeepingTSVFileConverter{},
|
ezBookKeepingTsvConverter: converters.EzBookKeepingTransactionDataTSVFileConverter,
|
||||||
accounts: services.Accounts,
|
accounts: services.Accounts,
|
||||||
transactions: services.Transactions,
|
transactions: services.Transactions,
|
||||||
categories: services.TransactionCategories,
|
categories: services.TransactionCategories,
|
||||||
tags: services.TransactionTags,
|
tags: services.TransactionTags,
|
||||||
users: services.Users,
|
users: services.Users,
|
||||||
twoFactorAuthorizations: services.TwoFactorAuthorizations,
|
twoFactorAuthorizations: services.TwoFactorAuthorizations,
|
||||||
tokens: services.Tokens,
|
tokens: services.Tokens,
|
||||||
forgetPasswords: services.ForgetPasswords,
|
forgetPasswords: services.ForgetPasswords,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -645,12 +645,12 @@ func (l *UserDataCli) ExportTransaction(c *core.CliContext, username string, fil
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataExporter converters.DataConverter
|
var dataExporter converters.TransactionDataExporter
|
||||||
|
|
||||||
if fileType == "tsv" {
|
if fileType == "tsv" {
|
||||||
dataExporter = l.ezBookKeepingTsvExporter
|
dataExporter = l.ezBookKeepingTsvConverter
|
||||||
} else {
|
} else {
|
||||||
dataExporter = l.ezBookKeepingCsvExporter
|
dataExporter = l.ezBookKeepingCsvConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := dataExporter.ToExportedContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexesMap)
|
result, err := dataExporter.ToExportedContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexesMap)
|
||||||
@@ -669,12 +669,12 @@ func (l *UserDataCli) ImportTransaction(c *core.CliContext, username string, fil
|
|||||||
return errs.ErrUsernameIsEmpty
|
return errs.ErrUsernameIsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataImporter converters.DataConverter
|
var dataImporter converters.TransactionDataImporter
|
||||||
|
|
||||||
if fileType == "ezbookkeeping_csv" {
|
if fileType == "ezbookkeeping_csv" {
|
||||||
dataImporter = l.ezBookKeepingCsvExporter
|
dataImporter = l.ezBookKeepingCsvConverter
|
||||||
} else if fileType == "ezbookkeeping_tsv" {
|
} else if fileType == "ezbookkeeping_tsv" {
|
||||||
dataImporter = l.ezBookKeepingTsvExporter
|
dataImporter = l.ezBookKeepingTsvConverter
|
||||||
} else {
|
} else {
|
||||||
return errs.ErrImportFileTypeNotSupported
|
return errs.ErrImportFileTypeNotSupported
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
// ImportedDataTable defines the structure of imported data table
|
||||||
|
type ImportedDataTable interface {
|
||||||
|
// DataRowCount returns the total count of data row
|
||||||
|
DataRowCount() int
|
||||||
|
|
||||||
|
// HeaderLineColumnNames returns the header column name list
|
||||||
|
HeaderLineColumnNames() []string
|
||||||
|
|
||||||
|
// DataRowIterator returns the iterator of data row
|
||||||
|
DataRowIterator() ImportedDataRowIterator
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportedDataRow defines the structure of imported data row
|
||||||
|
type ImportedDataRow interface {
|
||||||
|
// ColumnCount returns the total count of column in this data row
|
||||||
|
ColumnCount() int
|
||||||
|
|
||||||
|
// GetData returns the data in the specified column index
|
||||||
|
GetData(columnIndex int) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportedDataRowIterator defines the structure of imported data row iterator
|
||||||
|
type ImportedDataRowIterator interface {
|
||||||
|
// HasNext returns whether the iterator does not reach the end
|
||||||
|
HasNext() bool
|
||||||
|
|
||||||
|
// Next returns the next imported data row
|
||||||
|
Next() ImportedDataRow
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataTableBuilder defines the structure of data table builder
|
||||||
|
type DataTableBuilder interface {
|
||||||
|
// AppendTransaction appends the specified transaction to data builder
|
||||||
|
AppendTransaction(data map[DataTableColumn]string)
|
||||||
|
}
|
||||||
@@ -0,0 +1,493 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataTableColumn represents the data column type of data table
|
||||||
|
type DataTableColumn byte
|
||||||
|
|
||||||
|
// Data table columns
|
||||||
|
const (
|
||||||
|
DATA_TABLE_TRANSACTION_TIME DataTableColumn = 1
|
||||||
|
DATA_TABLE_TRANSACTION_TIMEZONE DataTableColumn = 2
|
||||||
|
DATA_TABLE_TRANSACTION_TYPE DataTableColumn = 3
|
||||||
|
DATA_TABLE_CATEGORY DataTableColumn = 4
|
||||||
|
DATA_TABLE_SUB_CATEGORY DataTableColumn = 5
|
||||||
|
DATA_TABLE_ACCOUNT_NAME DataTableColumn = 6
|
||||||
|
DATA_TABLE_ACCOUNT_CURRENCY DataTableColumn = 7
|
||||||
|
DATA_TABLE_AMOUNT DataTableColumn = 8
|
||||||
|
DATA_TABLE_RELATED_ACCOUNT_NAME DataTableColumn = 9
|
||||||
|
DATA_TABLE_RELATED_ACCOUNT_CURRENCY DataTableColumn = 10
|
||||||
|
DATA_TABLE_RELATED_AMOUNT DataTableColumn = 11
|
||||||
|
DATA_TABLE_GEOGRAPHIC_LOCATION DataTableColumn = 12
|
||||||
|
DATA_TABLE_TAGS DataTableColumn = 13
|
||||||
|
DATA_TABLE_DESCRIPTION DataTableColumn = 14
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataTableTransactionDataConverter defines the structure of data table importer for transaction data
|
||||||
|
type DataTableTransactionDataConverter struct {
|
||||||
|
dataColumnMapping map[DataTableColumn]string
|
||||||
|
transactionTypeMapping map[models.TransactionDbType]string
|
||||||
|
transactionTypeNameMapping map[string]models.TransactionDbType
|
||||||
|
columnSeparator string
|
||||||
|
lineSeparator string
|
||||||
|
geoLocationSeparator string
|
||||||
|
transactionTagSeparator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) buildExportedContent(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++ {
|
||||||
|
transaction := transactions[i]
|
||||||
|
|
||||||
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dataRowMap := make(map[DataTableColumn]string, 15)
|
||||||
|
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
||||||
|
|
||||||
|
dataRowMap[DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
||||||
|
dataRowMap[DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionTimeZone)
|
||||||
|
dataRowMap[DATA_TABLE_TRANSACTION_TYPE] = c.replaceDelimiters(c.getDisplayTransactionTypeName(transaction.Type))
|
||||||
|
dataRowMap[DATA_TABLE_CATEGORY] = c.getExportedTransactionCategoryName(transaction.CategoryId, categoryMap)
|
||||||
|
dataRowMap[DATA_TABLE_SUB_CATEGORY] = c.getExportedTransactionSubCategoryName(transaction.CategoryId, categoryMap)
|
||||||
|
dataRowMap[DATA_TABLE_ACCOUNT_NAME] = c.getExportedAccountName(transaction.AccountId, accountMap)
|
||||||
|
dataRowMap[DATA_TABLE_ACCOUNT_CURRENCY] = c.getAccountCurrency(transaction.AccountId, accountMap)
|
||||||
|
dataRowMap[DATA_TABLE_AMOUNT] = utils.FormatAmount(transaction.Amount)
|
||||||
|
|
||||||
|
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
|
dataRowMap[DATA_TABLE_RELATED_ACCOUNT_NAME] = c.getExportedAccountName(transaction.RelatedAccountId, accountMap)
|
||||||
|
dataRowMap[DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = c.getAccountCurrency(transaction.RelatedAccountId, accountMap)
|
||||||
|
dataRowMap[DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(transaction.RelatedAccountAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataRowMap[DATA_TABLE_GEOGRAPHIC_LOCATION] = c.getExportedGeographicLocation(transaction)
|
||||||
|
dataRowMap[DATA_TABLE_TAGS] = c.getExportedTags(transaction.TransactionId, allTagIndexes, tagMap)
|
||||||
|
dataRowMap[DATA_TABLE_DESCRIPTION] = c.replaceDelimiters(transaction.Comment)
|
||||||
|
|
||||||
|
dataTableBuilder.AppendTransaction(dataRowMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) parseImportedData(user *models.User, dataTable ImportedDataTable, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) ([]*models.Transaction, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||||
|
if dataTable.DataRowCount() < 1 {
|
||||||
|
return nil, nil, nil, nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
headerLineItems := dataTable.HeaderLineColumnNames()
|
||||||
|
headerItemMap := make(map[string]int)
|
||||||
|
|
||||||
|
for i := 0; i < len(headerLineItems); i++ {
|
||||||
|
headerItemMap[headerLineItems[i]] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
timeColumnIdx, timeColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TRANSACTION_TIME]]
|
||||||
|
timezoneColumnIdx, timezoneColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TRANSACTION_TIMEZONE]]
|
||||||
|
typeColumnIdx, typeColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TRANSACTION_TYPE]]
|
||||||
|
subCategoryColumnIdx, subCategoryColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_SUB_CATEGORY]]
|
||||||
|
accountColumnIdx, accountColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_ACCOUNT_NAME]]
|
||||||
|
accountCurrencyColumnIdx, accountCurrencyColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_ACCOUNT_CURRENCY]]
|
||||||
|
amountColumnIdx, amountColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_AMOUNT]]
|
||||||
|
account2ColumnIdx, account2ColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_RELATED_ACCOUNT_NAME]]
|
||||||
|
account2CurrencyColumnIdx, account2CurrencyColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_RELATED_ACCOUNT_CURRENCY]]
|
||||||
|
amount2ColumnIdx, amount2ColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_RELATED_AMOUNT]]
|
||||||
|
geoLocationIdx, geoLocationExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_GEOGRAPHIC_LOCATION]]
|
||||||
|
tagsColumnIdx, tagsColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_TAGS]]
|
||||||
|
descriptionColumnIdx, descriptionColumnExists := headerItemMap[c.dataColumnMapping[DATA_TABLE_DESCRIPTION]]
|
||||||
|
|
||||||
|
if !timeColumnExists || !typeColumnExists || !subCategoryColumnExists ||
|
||||||
|
!accountColumnExists || !amountColumnExists || !account2ColumnExists || !amount2ColumnExists {
|
||||||
|
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountMap == nil {
|
||||||
|
accountMap = make(map[string]*models.Account)
|
||||||
|
}
|
||||||
|
|
||||||
|
if categoryMap == nil {
|
||||||
|
categoryMap = make(map[string]*models.TransactionCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagMap == nil {
|
||||||
|
tagMap = make(map[string]*models.TransactionTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
allNewTransactions := make(ImportedTransactionSlice, 0, dataTable.DataRowCount())
|
||||||
|
allNewAccounts := make([]*models.Account, 0)
|
||||||
|
allNewSubCategories := make([]*models.TransactionCategory, 0)
|
||||||
|
allNewTags := make([]*models.TransactionTag, 0)
|
||||||
|
|
||||||
|
dataRowIterator := dataTable.DataRowIterator()
|
||||||
|
|
||||||
|
for dataRowIterator.HasNext() {
|
||||||
|
dataRow := dataRowIterator.Next()
|
||||||
|
columnCount := dataRow.ColumnCount()
|
||||||
|
|
||||||
|
if columnCount < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if columnCount < len(headerLineItems) {
|
||||||
|
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
timezoneOffset := defaultTimezoneOffset
|
||||||
|
|
||||||
|
if timezoneColumnExists {
|
||||||
|
transactionTimezone, err := utils.ParseFromTimezoneOffset(dataRow.GetData(timezoneColumnIdx))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timezoneOffset = utils.GetTimezoneOffsetMinutes(transactionTimezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionTime, err := utils.ParseFromLongDateTime(dataRow.GetData(timeColumnIdx), timezoneOffset)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionDbType, err := c.getTransactionDbType(dataRow.GetData(typeColumnIdx))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryId := int64(0)
|
||||||
|
|
||||||
|
if transactionDbType != models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||||
|
transactionCategoryType, err := c.getTransactionCategoryType(transactionDbType)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subCategoryName := dataRow.GetData(subCategoryColumnIdx)
|
||||||
|
|
||||||
|
if subCategoryName == "" {
|
||||||
|
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
subCategory, exists := categoryMap[subCategoryName]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
subCategory = c.createNewTransactionCategoryModel(user.Uid, subCategoryName, transactionCategoryType)
|
||||||
|
allNewSubCategories = append(allNewSubCategories, subCategory)
|
||||||
|
categoryMap[subCategoryName] = subCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryId = subCategory.CategoryId
|
||||||
|
}
|
||||||
|
|
||||||
|
accountName := dataRow.GetData(accountColumnIdx)
|
||||||
|
|
||||||
|
if accountName == "" {
|
||||||
|
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
account, exists := accountMap[accountName]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
currency := user.DefaultCurrency
|
||||||
|
|
||||||
|
if accountCurrencyColumnExists {
|
||||||
|
currency = dataRow.GetData(accountCurrencyColumnIdx)
|
||||||
|
|
||||||
|
if _, ok := validators.AllCurrencyNames[currency]; !ok {
|
||||||
|
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
account = c.createNewAccountModel(user.Uid, accountName, currency)
|
||||||
|
allNewAccounts = append(allNewAccounts, account)
|
||||||
|
accountMap[accountName] = account
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountCurrencyColumnExists {
|
||||||
|
if account.Currency != dataRow.GetData(accountCurrencyColumnIdx) {
|
||||||
|
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, err := utils.ParseAmount(dataRow.GetData(amountColumnIdx))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
relatedAccountId := int64(0)
|
||||||
|
relatedAccountAmount := int64(0)
|
||||||
|
|
||||||
|
if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
|
account2Name := dataRow.GetData(account2ColumnIdx)
|
||||||
|
|
||||||
|
if account2Name == "" {
|
||||||
|
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
account2, exists := accountMap[account2Name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
currency := user.DefaultCurrency
|
||||||
|
|
||||||
|
if accountCurrencyColumnExists {
|
||||||
|
currency = dataRow.GetData(account2CurrencyColumnIdx)
|
||||||
|
|
||||||
|
if _, ok := validators.AllCurrencyNames[currency]; !ok {
|
||||||
|
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
account2 = c.createNewAccountModel(user.Uid, account2Name, currency)
|
||||||
|
allNewAccounts = append(allNewAccounts, account2)
|
||||||
|
accountMap[account2Name] = account2
|
||||||
|
}
|
||||||
|
|
||||||
|
if account2CurrencyColumnExists {
|
||||||
|
if account2.Currency != dataRow.GetData(account2CurrencyColumnIdx) {
|
||||||
|
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relatedAccountId = account2.AccountId
|
||||||
|
relatedAccountAmount, err = utils.ParseAmount(dataRow.GetData(amount2ColumnIdx))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
geoLongitude := float64(0)
|
||||||
|
geoLatitude := float64(0)
|
||||||
|
|
||||||
|
if geoLocationExists {
|
||||||
|
geoLocationItems := strings.Split(dataRow.GetData(geoLocationIdx), c.geoLocationSeparator)
|
||||||
|
|
||||||
|
if len(geoLocationItems) == 2 {
|
||||||
|
geoLongitude, err = utils.StringToFloat64(geoLocationItems[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
geoLatitude, err = utils.StringToFloat64(geoLocationItems[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagsColumnExists {
|
||||||
|
tagNames := strings.Split(dataRow.GetData(tagsColumnIdx), c.transactionTagSeparator)
|
||||||
|
|
||||||
|
for i := 0; i < len(tagNames); i++ {
|
||||||
|
tagName := tagNames[i]
|
||||||
|
|
||||||
|
if tagName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tag, exists := tagMap[tagName]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
tag = c.createNewTransactionTagModel(user.Uid, tagName)
|
||||||
|
allNewTags = append(allNewTags, tag)
|
||||||
|
tagMap[tagName] = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
description := ""
|
||||||
|
|
||||||
|
if descriptionColumnExists {
|
||||||
|
description = dataRow.GetData(descriptionColumnIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction := &models.Transaction{
|
||||||
|
Uid: user.Uid,
|
||||||
|
Type: transactionDbType,
|
||||||
|
CategoryId: categoryId,
|
||||||
|
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
|
||||||
|
TimezoneUtcOffset: timezoneOffset,
|
||||||
|
AccountId: account.AccountId,
|
||||||
|
Amount: amount,
|
||||||
|
HideAmount: false,
|
||||||
|
RelatedAccountId: relatedAccountId,
|
||||||
|
RelatedAccountAmount: relatedAccountAmount,
|
||||||
|
Comment: description,
|
||||||
|
GeoLongitude: geoLongitude,
|
||||||
|
GeoLatitude: geoLatitude,
|
||||||
|
CreatedIp: "127.0.0.1",
|
||||||
|
}
|
||||||
|
|
||||||
|
allNewTransactions = append(allNewTransactions, transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(allNewTransactions)
|
||||||
|
|
||||||
|
return allNewTransactions, allNewAccounts, allNewSubCategories, allNewTags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getTransactionDbType(transactionTypeName string) (models.TransactionDbType, error) {
|
||||||
|
transactionType, exists := c.transactionTypeNameMapping[transactionTypeName]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return 0, errs.ErrTransactionTypeInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getTransactionCategoryType(transactionType models.TransactionDbType) (models.TransactionCategoryType, error) {
|
||||||
|
if transactionType == models.TRANSACTION_DB_TYPE_INCOME {
|
||||||
|
return models.CATEGORY_TYPE_INCOME, nil
|
||||||
|
} else if transactionType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
||||||
|
return models.CATEGORY_TYPE_EXPENSE, nil
|
||||||
|
} else if transactionType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
||||||
|
return models.CATEGORY_TYPE_TRANSFER, nil
|
||||||
|
} else {
|
||||||
|
return 0, errs.ErrTransactionTypeInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getDisplayTransactionTypeName(transactionDbType models.TransactionDbType) string {
|
||||||
|
transactionTypeName, exists := c.transactionTypeMapping[transactionDbType]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionTypeName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getExportedTransactionCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||||
|
category, exists := categoryMap[categoryId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if category.ParentCategoryId == 0 {
|
||||||
|
return c.replaceDelimiters(category.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
parentCategory, exists := categoryMap[category.ParentCategoryId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.replaceDelimiters(parentCategory.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getExportedTransactionSubCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
||||||
|
category, exists := categoryMap[categoryId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return c.replaceDelimiters(category.Name)
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getExportedAccountName(accountId int64, accountMap map[int64]*models.Account) string {
|
||||||
|
account, exists := accountMap[accountId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return c.replaceDelimiters(account.Name)
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getAccountCurrency(accountId int64, accountMap map[int64]*models.Account) string {
|
||||||
|
account, exists := accountMap[accountId]
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return c.replaceDelimiters(account.Currency)
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getExportedGeographicLocation(transaction *models.Transaction) string {
|
||||||
|
if transaction.GeoLongitude != 0 || transaction.GeoLatitude != 0 {
|
||||||
|
return fmt.Sprintf("%f%s%f", transaction.GeoLongitude, c.geoLocationSeparator, transaction.GeoLatitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) getExportedTags(transactionId int64, allTagIndexes map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
||||||
|
tagIndexes, exists := allTagIndexes[transactionId]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < len(tagIndexes); i++ {
|
||||||
|
tagIndex := tagIndexes[i]
|
||||||
|
tag, exists := tagMap[tagIndex]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.Len() > 0 {
|
||||||
|
ret.WriteString(c.transactionTagSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.WriteString(strings.Replace(tag.Name, c.transactionTagSeparator, " ", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.replaceDelimiters(ret.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) replaceDelimiters(text string) string {
|
||||||
|
text = strings.Replace(text, "\r\n", " ", -1)
|
||||||
|
text = strings.Replace(text, "\r", " ", -1)
|
||||||
|
text = strings.Replace(text, "\n", " ", -1)
|
||||||
|
text = strings.Replace(text, c.columnSeparator, " ", -1)
|
||||||
|
text = strings.Replace(text, c.lineSeparator, " ", -1)
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) createNewAccountModel(uid int64, accountName string, currency string) *models.Account {
|
||||||
|
return &models.Account{
|
||||||
|
Uid: uid,
|
||||||
|
Name: accountName,
|
||||||
|
Currency: currency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) createNewTransactionCategoryModel(uid int64, categoryName string, transactionCategoryType models.TransactionCategoryType) *models.TransactionCategory {
|
||||||
|
return &models.TransactionCategory{
|
||||||
|
Uid: uid,
|
||||||
|
Name: categoryName,
|
||||||
|
Type: transactionCategoryType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DataTableTransactionDataConverter) createNewTransactionTagModel(uid int64, tagName string) *models.TransactionTag {
|
||||||
|
return &models.TransactionTag{
|
||||||
|
Uid: uid,
|
||||||
|
Name: tagName,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package converters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EzBookKeepingCSVFileConverter defines the structure of CSV file converter
|
|
||||||
type EzBookKeepingCSVFileConverter struct {
|
|
||||||
EzBookKeepingPlainFileConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
const csvSeparator = ","
|
|
||||||
|
|
||||||
// ToExportedContent returns the exported CSV data
|
|
||||||
func (e *EzBookKeepingCSVFileConverter) ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
|
||||||
return e.toExportedContent(uid, csvSeparator, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseImportedData parses transactions of ezbookkeeping CSV data
|
|
||||||
func (e *EzBookKeepingCSVFileConverter) ParseImportedData(user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) ([]*models.Transaction, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
|
||||||
return e.parseImportedData(user, csvSeparator, data, defaultTimezoneOffset, accountMap, categoryMap, tagMap)
|
|
||||||
}
|
|
||||||
@@ -1,491 +0,0 @@
|
|||||||
package converters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EzBookKeepingPlainFileConverter defines the structure of plain file converter
|
|
||||||
type EzBookKeepingPlainFileConverter struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineSeparator = "\n"
|
|
||||||
const geoLocationSeparator = " "
|
|
||||||
const transactionTagSeparator = ";"
|
|
||||||
const headerLine = "Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description" + lineSeparator
|
|
||||||
const dataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" + lineSeparator
|
|
||||||
|
|
||||||
// toExportedContent returns the exported plain data
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) toExportedContent(uid int64, separator string, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
|
||||||
var ret strings.Builder
|
|
||||||
|
|
||||||
ret.Grow(len(transactions) * 100)
|
|
||||||
|
|
||||||
actualHeaderLine := headerLine
|
|
||||||
actualDataLineFormat := dataLineFormat
|
|
||||||
|
|
||||||
if separator != "," {
|
|
||||||
actualHeaderLine = strings.Replace(headerLine, ",", separator, -1)
|
|
||||||
actualDataLineFormat = strings.Replace(dataLineFormat, ",", separator, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.WriteString(actualHeaderLine)
|
|
||||||
|
|
||||||
for i := 0; i < len(transactions); i++ {
|
|
||||||
transaction := transactions[i]
|
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
|
|
||||||
transactionTime := utils.FormatUnixTimeToLongDateTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone)
|
|
||||||
transactionTimezone := utils.FormatTimezoneOffset(transactionTimeZone)
|
|
||||||
transactionType := e.getTransactionTypeName(transaction.Type)
|
|
||||||
category := e.replaceDelimiters(e.getTransactionCategoryName(transaction.CategoryId, categoryMap), separator)
|
|
||||||
subCategory := e.replaceDelimiters(e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap), separator)
|
|
||||||
account := e.replaceDelimiters(e.getAccountName(transaction.AccountId, accountMap), separator)
|
|
||||||
accountCurrency := e.getAccountCurrency(transaction.AccountId, accountMap)
|
|
||||||
amount := utils.FormatAmount(transaction.Amount)
|
|
||||||
account2 := ""
|
|
||||||
account2Currency := ""
|
|
||||||
account2Amount := ""
|
|
||||||
geoLocation := ""
|
|
||||||
|
|
||||||
if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
|
||||||
account2 = e.replaceDelimiters(e.getAccountName(transaction.RelatedAccountId, accountMap), separator)
|
|
||||||
account2Currency = e.getAccountCurrency(transaction.RelatedAccountId, accountMap)
|
|
||||||
account2Amount = utils.FormatAmount(transaction.RelatedAccountAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
if transaction.GeoLongitude != 0 || transaction.GeoLatitude != 0 {
|
|
||||||
geoLocation = fmt.Sprintf("%f%s%f", transaction.GeoLongitude, geoLocationSeparator, transaction.GeoLatitude)
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := e.replaceDelimiters(e.getTags(transaction.TransactionId, allTagIndexes, tagMap), separator)
|
|
||||||
comment := e.replaceDelimiters(transaction.Comment, separator)
|
|
||||||
|
|
||||||
ret.WriteString(fmt.Sprintf(actualDataLineFormat, transactionTime, transactionTimezone, transactionType, category, subCategory, account, accountCurrency, amount, account2, account2Currency, account2Amount, geoLocation, tags, comment))
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(ret.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) parseImportedData(user *models.User, separator string, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) ([]*models.Transaction, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
|
||||||
lines := strings.Split(string(data), lineSeparator)
|
|
||||||
|
|
||||||
if len(lines) < 2 {
|
|
||||||
return nil, nil, nil, nil, errs.ErrOperationFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
headerLineItems := strings.Split(lines[0], separator)
|
|
||||||
headerItemMap := make(map[string]int)
|
|
||||||
|
|
||||||
for i := 0; i < len(headerLineItems); i++ {
|
|
||||||
headerItemMap[headerLineItems[i]] = i
|
|
||||||
}
|
|
||||||
|
|
||||||
timeColumnIdx, timeColumnExists := headerItemMap["Time"]
|
|
||||||
timezoneColumnIdx, timezoneColumnExists := headerItemMap["Timezone"]
|
|
||||||
typeColumnIdx, typeColumnExists := headerItemMap["Type"]
|
|
||||||
subCategoryColumnIdx, subCategoryColumnExists := headerItemMap["Sub Category"]
|
|
||||||
accountColumnIdx, accountColumnExists := headerItemMap["Account"]
|
|
||||||
accountCurrencyColumnIdx, accountCurrencyColumnExists := headerItemMap["Account Currency"]
|
|
||||||
amountColumnIdx, amountColumnExists := headerItemMap["Amount"]
|
|
||||||
account2ColumnIdx, account2ColumnExists := headerItemMap["Account2"]
|
|
||||||
account2CurrencyColumnIdx, account2CurrencyColumnExists := headerItemMap["Account2 Currency"]
|
|
||||||
amount2ColumnIdx, amount2ColumnExists := headerItemMap["Account2 Amount"]
|
|
||||||
geoLocationIdx, geoLocationExists := headerItemMap["Geographic Location"]
|
|
||||||
tagsColumnIdx, tagsColumnExists := headerItemMap["Tags"]
|
|
||||||
descriptionColumnIdx, descriptionColumnExists := headerItemMap["Description"]
|
|
||||||
|
|
||||||
if !timeColumnExists || !typeColumnExists || !subCategoryColumnExists ||
|
|
||||||
!accountColumnExists || !amountColumnExists || !account2ColumnExists || !amount2ColumnExists {
|
|
||||||
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
if accountMap == nil {
|
|
||||||
accountMap = make(map[string]*models.Account)
|
|
||||||
}
|
|
||||||
|
|
||||||
if categoryMap == nil {
|
|
||||||
categoryMap = make(map[string]*models.TransactionCategory)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagMap == nil {
|
|
||||||
tagMap = make(map[string]*models.TransactionTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
allNewTransactions := make(ImportTransactionSlice, 0, len(lines))
|
|
||||||
allNewAccounts := make([]*models.Account, 0)
|
|
||||||
allNewSubCategories := make([]*models.TransactionCategory, 0)
|
|
||||||
allNewTags := make([]*models.TransactionTag, 0)
|
|
||||||
|
|
||||||
for i := 1; i < len(lines); i++ {
|
|
||||||
line := lines[i]
|
|
||||||
|
|
||||||
if len(line) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lineItems := strings.Split(line, separator)
|
|
||||||
|
|
||||||
if len(lineItems) < len(headerLineItems) {
|
|
||||||
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
timezoneOffset := defaultTimezoneOffset
|
|
||||||
|
|
||||||
if timezoneColumnExists {
|
|
||||||
transactionTimezone, err := utils.ParseFromTimezoneOffset(lineItems[timezoneColumnIdx])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
timezoneOffset = utils.GetTimezoneOffsetMinutes(transactionTimezone)
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionTime, err := utils.ParseFromLongDateTime(lineItems[timeColumnIdx], timezoneOffset)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionDbType, err := e.getTransactionDbType(lineItems[typeColumnIdx])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryId := int64(0)
|
|
||||||
|
|
||||||
if transactionDbType != models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
|
||||||
transactionCategoryType, err := e.getTransactionCategoryType(transactionDbType)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
subCategoryName := lineItems[subCategoryColumnIdx]
|
|
||||||
|
|
||||||
if subCategoryName == "" {
|
|
||||||
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
subCategory, exists := categoryMap[subCategoryName]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
subCategory = e.createNewTransactionCategoryModel(user.Uid, subCategoryName, transactionCategoryType)
|
|
||||||
allNewSubCategories = append(allNewSubCategories, subCategory)
|
|
||||||
categoryMap[subCategoryName] = subCategory
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryId = subCategory.CategoryId
|
|
||||||
}
|
|
||||||
|
|
||||||
accountName := lineItems[accountColumnIdx]
|
|
||||||
|
|
||||||
if accountName == "" {
|
|
||||||
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
account, exists := accountMap[accountName]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
currency := user.DefaultCurrency
|
|
||||||
|
|
||||||
if accountCurrencyColumnExists {
|
|
||||||
currency = lineItems[accountCurrencyColumnIdx]
|
|
||||||
|
|
||||||
if _, ok := validators.AllCurrencyNames[currency]; !ok {
|
|
||||||
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
account = e.createNewAccountModel(user.Uid, accountName, currency)
|
|
||||||
allNewAccounts = append(allNewAccounts, account)
|
|
||||||
accountMap[accountName] = account
|
|
||||||
}
|
|
||||||
|
|
||||||
if accountCurrencyColumnExists {
|
|
||||||
if account.Currency != lineItems[accountCurrencyColumnIdx] {
|
|
||||||
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
amount, err := utils.ParseAmount(lineItems[amountColumnIdx])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
relatedAccountId := int64(0)
|
|
||||||
relatedAccountAmount := int64(0)
|
|
||||||
|
|
||||||
if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
|
||||||
account2Name := lineItems[account2ColumnIdx]
|
|
||||||
|
|
||||||
if account2Name == "" {
|
|
||||||
return nil, nil, nil, nil, errs.ErrFormatInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
account2, exists := accountMap[account2Name]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
currency := user.DefaultCurrency
|
|
||||||
|
|
||||||
if accountCurrencyColumnExists {
|
|
||||||
currency = lineItems[account2CurrencyColumnIdx]
|
|
||||||
|
|
||||||
if _, ok := validators.AllCurrencyNames[currency]; !ok {
|
|
||||||
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
account2 = e.createNewAccountModel(user.Uid, account2Name, currency)
|
|
||||||
allNewAccounts = append(allNewAccounts, account2)
|
|
||||||
accountMap[account2Name] = account2
|
|
||||||
}
|
|
||||||
|
|
||||||
if account2CurrencyColumnExists {
|
|
||||||
if account2.Currency != lineItems[account2CurrencyColumnIdx] {
|
|
||||||
return nil, nil, nil, nil, errs.ErrAccountCurrencyInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
relatedAccountId = account2.AccountId
|
|
||||||
relatedAccountAmount, err = utils.ParseAmount(lineItems[amount2ColumnIdx])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
geoLongitude := float64(0)
|
|
||||||
geoLatitude := float64(0)
|
|
||||||
|
|
||||||
if geoLocationExists {
|
|
||||||
geoLocationItems := strings.Split(lineItems[geoLocationIdx], geoLocationSeparator)
|
|
||||||
|
|
||||||
if len(geoLocationItems) == 2 {
|
|
||||||
geoLongitude, err = utils.StringToFloat64(geoLocationItems[0])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
geoLatitude, err = utils.StringToFloat64(geoLocationItems[1])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagsColumnExists {
|
|
||||||
tagNames := strings.Split(lineItems[tagsColumnIdx], transactionTagSeparator)
|
|
||||||
|
|
||||||
for i := 0; i < len(tagNames); i++ {
|
|
||||||
tagName := tagNames[i]
|
|
||||||
|
|
||||||
if tagName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tag, exists := tagMap[tagName]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
tag = e.createNewTransactionTagModel(user.Uid, tagName)
|
|
||||||
allNewTags = append(allNewTags, tag)
|
|
||||||
tagMap[tagName] = tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
description := ""
|
|
||||||
|
|
||||||
if descriptionColumnExists {
|
|
||||||
description = lineItems[descriptionColumnIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction := &models.Transaction{
|
|
||||||
Uid: user.Uid,
|
|
||||||
Type: transactionDbType,
|
|
||||||
CategoryId: categoryId,
|
|
||||||
TransactionTime: utils.GetMinTransactionTimeFromUnixTime(transactionTime.Unix()),
|
|
||||||
TimezoneUtcOffset: timezoneOffset,
|
|
||||||
AccountId: account.AccountId,
|
|
||||||
Amount: amount,
|
|
||||||
HideAmount: false,
|
|
||||||
RelatedAccountId: relatedAccountId,
|
|
||||||
RelatedAccountAmount: relatedAccountAmount,
|
|
||||||
Comment: description,
|
|
||||||
GeoLongitude: geoLongitude,
|
|
||||||
GeoLatitude: geoLatitude,
|
|
||||||
CreatedIp: "127.0.0.1",
|
|
||||||
}
|
|
||||||
|
|
||||||
allNewTransactions = append(allNewTransactions, transaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(allNewTransactions)
|
|
||||||
|
|
||||||
return allNewTransactions, allNewAccounts, allNewSubCategories, allNewTags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getTransactionTypeName(transactionDbType models.TransactionDbType) string {
|
|
||||||
if transactionDbType == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
|
||||||
return "Balance Modification"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_INCOME {
|
|
||||||
return "Income"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
|
||||||
return "Expense"
|
|
||||||
} else if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT || transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_IN {
|
|
||||||
return "Transfer"
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getTransactionDbType(transactionTypeName string) (models.TransactionDbType, error) {
|
|
||||||
if transactionTypeName == "Balance Modification" {
|
|
||||||
return models.TRANSACTION_DB_TYPE_MODIFY_BALANCE, nil
|
|
||||||
} else if transactionTypeName == "Income" {
|
|
||||||
return models.TRANSACTION_DB_TYPE_INCOME, nil
|
|
||||||
} else if transactionTypeName == "Expense" {
|
|
||||||
return models.TRANSACTION_DB_TYPE_EXPENSE, nil
|
|
||||||
} else if transactionTypeName == "Transfer" {
|
|
||||||
return models.TRANSACTION_DB_TYPE_TRANSFER_OUT, nil
|
|
||||||
} else {
|
|
||||||
return 0, errs.ErrTransactionTypeInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getTransactionCategoryType(transactionType models.TransactionDbType) (models.TransactionCategoryType, error) {
|
|
||||||
if transactionType == models.TRANSACTION_DB_TYPE_INCOME {
|
|
||||||
return models.CATEGORY_TYPE_INCOME, nil
|
|
||||||
} else if transactionType == models.TRANSACTION_DB_TYPE_EXPENSE {
|
|
||||||
return models.CATEGORY_TYPE_EXPENSE, nil
|
|
||||||
} else if transactionType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT {
|
|
||||||
return models.CATEGORY_TYPE_TRANSFER, nil
|
|
||||||
} else {
|
|
||||||
return 0, errs.ErrTransactionTypeInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getTransactionCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
|
||||||
category, exists := categoryMap[categoryId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if category.ParentCategoryId == 0 {
|
|
||||||
return category.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
parentCategory, exists := categoryMap[category.ParentCategoryId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return parentCategory.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getTransactionSubCategoryName(categoryId int64, categoryMap map[int64]*models.TransactionCategory) string {
|
|
||||||
category, exists := categoryMap[categoryId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return category.Name
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getAccountName(accountId int64, accountMap map[int64]*models.Account) string {
|
|
||||||
account, exists := accountMap[accountId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return account.Name
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getAccountCurrency(accountId int64, accountMap map[int64]*models.Account) string {
|
|
||||||
account, exists := accountMap[accountId]
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
return account.Currency
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) getTags(transactionId int64, allTagIndexes map[int64][]int64, tagMap map[int64]*models.TransactionTag) string {
|
|
||||||
tagIndexes, exists := allTagIndexes[transactionId]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret strings.Builder
|
|
||||||
|
|
||||||
for i := 0; i < len(tagIndexes); i++ {
|
|
||||||
tagIndex := tagIndexes[i]
|
|
||||||
tag, exists := tagMap[tagIndex]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ret.Len() > 0 {
|
|
||||||
ret.WriteString(transactionTagSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.WriteString(strings.Replace(tag.Name, transactionTagSeparator, " ", -1))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) replaceDelimiters(text string, separator string) string {
|
|
||||||
text = strings.Replace(text, separator, " ", -1)
|
|
||||||
text = strings.Replace(text, "\r\n", " ", -1)
|
|
||||||
text = strings.Replace(text, "\r", " ", -1)
|
|
||||||
text = strings.Replace(text, "\n", " ", -1)
|
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) createNewAccountModel(uid int64, accountName string, currency string) *models.Account {
|
|
||||||
return &models.Account{
|
|
||||||
Uid: uid,
|
|
||||||
Name: accountName,
|
|
||||||
Currency: currency,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) createNewTransactionCategoryModel(uid int64, categoryName string, transactionCategoryType models.TransactionCategoryType) *models.TransactionCategory {
|
|
||||||
return &models.TransactionCategory{
|
|
||||||
Uid: uid,
|
|
||||||
Name: categoryName,
|
|
||||||
Type: transactionCategoryType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EzBookKeepingPlainFileConverter) createNewTransactionTagModel(uid int64, tagName string) *models.TransactionTag {
|
|
||||||
return &models.TransactionTag{
|
|
||||||
Uid: uid,
|
|
||||||
Name: tagName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
// ezBookKeepingTransactionDataCSVFileConverter defines the structure of CSV file converter
|
||||||
|
type ezBookKeepingTransactionDataCSVFileConverter struct {
|
||||||
|
ezBookKeepingTransactionDataPlainTextConverter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize an ezbookkeeping transaction data csv file converter singleton instance
|
||||||
|
var (
|
||||||
|
EzBookKeepingTransactionDataCSVFileConverter = &ezBookKeepingTransactionDataCSVFileConverter{
|
||||||
|
ezBookKeepingTransactionDataPlainTextConverter{
|
||||||
|
DataTableTransactionDataConverter: DataTableTransactionDataConverter{
|
||||||
|
dataColumnMapping: ezbookkeepingDataColumnNameMapping,
|
||||||
|
transactionTypeMapping: ezbookkeepingTransactionTypeNameMapping,
|
||||||
|
transactionTypeNameMapping: ezbookkeepingNameTransactionTypeMapping,
|
||||||
|
columnSeparator: ",",
|
||||||
|
lineSeparator: "\n",
|
||||||
|
geoLocationSeparator: " ",
|
||||||
|
transactionTagSeparator: ";",
|
||||||
|
},
|
||||||
|
columns: ezbookkeepingDataColumns,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ezBookKeepingTransactionDataPlainTextConverter defines the structure of plain file converter for transaction data
|
||||||
|
type ezBookKeepingTransactionDataPlainTextConverter struct {
|
||||||
|
DataTableTransactionDataConverter
|
||||||
|
columns []DataTableColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
var ezbookkeepingDataColumnNameMapping = map[DataTableColumn]string{
|
||||||
|
DATA_TABLE_TRANSACTION_TIME: "Time",
|
||||||
|
DATA_TABLE_TRANSACTION_TIMEZONE: "Timezone",
|
||||||
|
DATA_TABLE_TRANSACTION_TYPE: "Type",
|
||||||
|
DATA_TABLE_CATEGORY: "Category",
|
||||||
|
DATA_TABLE_SUB_CATEGORY: "Sub Category",
|
||||||
|
DATA_TABLE_ACCOUNT_NAME: "Account",
|
||||||
|
DATA_TABLE_ACCOUNT_CURRENCY: "Account Currency",
|
||||||
|
DATA_TABLE_AMOUNT: "Amount",
|
||||||
|
DATA_TABLE_RELATED_ACCOUNT_NAME: "Account2",
|
||||||
|
DATA_TABLE_RELATED_ACCOUNT_CURRENCY: "Account2 Currency",
|
||||||
|
DATA_TABLE_RELATED_AMOUNT: "Account2 Amount",
|
||||||
|
DATA_TABLE_GEOGRAPHIC_LOCATION: "Geographic Location",
|
||||||
|
DATA_TABLE_TAGS: "Tags",
|
||||||
|
DATA_TABLE_DESCRIPTION: "Description",
|
||||||
|
}
|
||||||
|
|
||||||
|
var ezbookkeepingTransactionTypeNameMapping = map[models.TransactionDbType]string{
|
||||||
|
models.TRANSACTION_DB_TYPE_MODIFY_BALANCE: "Balance Modification",
|
||||||
|
models.TRANSACTION_DB_TYPE_INCOME: "Income",
|
||||||
|
models.TRANSACTION_DB_TYPE_EXPENSE: "Expense",
|
||||||
|
models.TRANSACTION_DB_TYPE_TRANSFER_OUT: "Transfer",
|
||||||
|
models.TRANSACTION_DB_TYPE_TRANSFER_IN: "Transfer",
|
||||||
|
}
|
||||||
|
|
||||||
|
var ezbookkeepingNameTransactionTypeMapping = map[string]models.TransactionDbType{
|
||||||
|
"Balance Modification": models.TRANSACTION_DB_TYPE_MODIFY_BALANCE,
|
||||||
|
"Income": models.TRANSACTION_DB_TYPE_INCOME,
|
||||||
|
"Expense": models.TRANSACTION_DB_TYPE_EXPENSE,
|
||||||
|
"Transfer": models.TRANSACTION_DB_TYPE_TRANSFER_OUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
var ezbookkeepingDataColumns = []DataTableColumn{
|
||||||
|
DATA_TABLE_TRANSACTION_TIME,
|
||||||
|
DATA_TABLE_TRANSACTION_TIMEZONE,
|
||||||
|
DATA_TABLE_TRANSACTION_TYPE,
|
||||||
|
DATA_TABLE_CATEGORY,
|
||||||
|
DATA_TABLE_SUB_CATEGORY,
|
||||||
|
DATA_TABLE_ACCOUNT_NAME,
|
||||||
|
DATA_TABLE_ACCOUNT_CURRENCY,
|
||||||
|
DATA_TABLE_AMOUNT,
|
||||||
|
DATA_TABLE_RELATED_ACCOUNT_NAME,
|
||||||
|
DATA_TABLE_RELATED_ACCOUNT_CURRENCY,
|
||||||
|
DATA_TABLE_RELATED_AMOUNT,
|
||||||
|
DATA_TABLE_GEOGRAPHIC_LOCATION,
|
||||||
|
DATA_TABLE_TAGS,
|
||||||
|
DATA_TABLE_DESCRIPTION,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToExportedContent returns the exported plain text transaction data
|
||||||
|
func (c *ezBookKeepingTransactionDataPlainTextConverter) ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
||||||
|
dataTableBuilder := createNewezbookkeepingTransactionPlainTextDataTableBuilder(len(transactions), c.columns, c.dataColumnMapping, c.columnSeparator, c.lineSeparator)
|
||||||
|
err := c.buildExportedContent(dataTableBuilder, uid, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(dataTableBuilder.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ezBookKeepingTransactionDataPlainTextConverter) ParseImportedData(user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) ([]*models.Transaction, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||||
|
dataTable, err := createNewezbookkeepingTransactionPlainTextDataTable(string(data), c.columnSeparator, c.lineSeparator)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.parseImportedData(user, dataTable, defaultTimezoneOffset, accountMap, categoryMap, tagMap)
|
||||||
|
}
|
||||||
+49
-49
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterToExportedContent(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterToExportedContent(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
transactions := make([]*models.Transaction, 3)
|
transactions := make([]*models.Transaction, 3)
|
||||||
transactions[0] = &models.Transaction{
|
transactions[0] = &models.Transaction{
|
||||||
@@ -116,21 +116,21 @@ func TestEzBookKeepingPlainFileConverterToExportedContent(t *testing.T) {
|
|||||||
"2024-09-01 12:34:56,+08:00,Income,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,123.450000 45.670000,Test Tag;Test Tag2,Hello World\n" +
|
"2024-09-01 12:34:56,+08:00,Income,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,123.450000 45.670000,Test Tag;Test Tag2,Hello World\n" +
|
||||||
"2024-09-01 12:34:56,+00:00,Expense,Test Category2,Test Sub Category2,Test Account,CNY,-0.10,,,,,Test Tag,Foo#Bar\n" +
|
"2024-09-01 12:34:56,+00:00,Expense,Test Category2,Test Sub Category2,Test Account,CNY,-0.10,,,,,Test Tag,Foo#Bar\n" +
|
||||||
"2024-09-01 12:34:56,-05:00,Transfer,Test Category3,Test Sub Category3,Test Account,CNY,123.45,Test Account2,USD,17.35,,Test Tag2,T\te s t test\n"
|
"2024-09-01 12:34:56,-05:00,Transfer,Test Category3,Test Sub Category3,Test Account,CNY,123.45,Test Account2,USD,17.35,,Test Tag2,T\te s t test\n"
|
||||||
actualContent, err := converter.toExportedContent(123, ",", transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
actualContent, err := converter.ToExportedContent(123, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, expectedContent, string(actualContent))
|
assert.Equal(t, expectedContent, string(actualContent))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_MinimumValidData(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, allNewAccounts, allNewSubCategories, allNewTags, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
allNewTransactions, allNewAccounts, allNewSubCategories, allNewTags, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,\n"+
|
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,\n"+
|
||||||
"2024-09-01 01:23:45,Income,Test Category,Test Account,0.12,,\n"+
|
"2024-09-01 01:23:45,Income,Test Category,Test Account,0.12,,\n"+
|
||||||
"2024-09-01 12:34:56,Expense,Test Category2,Test Account,1.00,,\n"+
|
"2024-09-01 12:34:56,Expense,Test Category2,Test Account,1.00,,\n"+
|
||||||
@@ -182,44 +182,44 @@ func TestEzBookKeepingPlainFileConverterParseImportedData_MinimumValidData(t *te
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidTime(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"2024-09-01T12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
"2024-09-01T12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"09/01/2024 12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
"09/01/2024 12:34:56,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidType(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"2024-09-01 12:34:56,Type,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Type,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidTimezone(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
allNewTransactions, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"2024-09-01 12:34:56,+08:00,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,+08:00,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
@@ -227,57 +227,57 @@ func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidTimezone(t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidTimezone(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidTimezone(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Timezone,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"2024-09-01 12:34:56,Asia/Shanghai,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Asia/Shanghai,Expense,Test Category,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidSubCategoryName(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidSubCategoryName(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"2024-09-01 12:34:56,Expense,,Test Account,123.45,,"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Expense,,Test Account,123.45,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAccountName(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAccountName(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(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)
|
"2024-09-01 12:34:56,Expense,Test Category,,123.45,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err = converter.ParseImportedData(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)
|
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,,123.45"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidAccountCurrency(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, allNewAccounts, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
allNewTransactions, allNewAccounts, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||||
"2024-09-01 01:23:45,Balance Modification,Test Category,Test Account,USD,123.45,,,\n"+
|
"2024-09-01 01:23:45,Balance Modification,Test Category,Test Account,USD,123.45,,,\n"+
|
||||||
"2024-09-01 12:34:56,Transfer,Test Category2,Test Account,USD,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Transfer,Test Category2,Test Account,USD,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil)
|
||||||
|
|
||||||
@@ -296,67 +296,67 @@ func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidAccountCurre
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAccountCurrency(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account,CNY,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account,CNY,1.23,Test Account2,EUR,1.10"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||||
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
"2024-09-01 01:23:45,Balance Modification,,Test Account,USD,123.45,,,\n"+
|
||||||
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account2,CNY,1.23,Test Account,EUR,1.10"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Transfer,Test Category3,Test Account2,CNY,1.23,Test Account,EUR,1.10"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseNotSupportedCurrency(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||||
"2024-09-01 01:23:45,Balance Modification,Test Category,Test Account,XXX,123.45,,,"), 0, nil, nil, nil)
|
"2024-09-01 01:23:45,Balance Modification,Test Category,Test Account,XXX,123.45,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount\n"+
|
||||||
"2024-09-01 01:23:45,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, nil, nil, nil)
|
"2024-09-01 01:23:45,Transfer,Test Category,Test Account,USD,123.45,Test Account2,XXX,123.45"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidAmount(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
||||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123 45,,"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123 45,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount\n"+
|
_, _, _, _, err = converter.ParseImportedData(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,Test Account2,123 45"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Transfer,Test Category,Test Account,123.45,Test Account2,123 45"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidGeographicLocation(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidGeographicLocation(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
allNewTransactions, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,123.45 45.56"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,123.45 45.56"), 0, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -366,38 +366,38 @@ func TestEzBookKeepingPlainFileConverterParseImportedData_ParseValidGeographicLo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidGeographicLocation(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseInvalidGeographicLocation(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
allNewTransactions, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1"), 0, nil, nil, nil)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, len(allNewTransactions))
|
assert.Equal(t, 1, len(allNewTransactions))
|
||||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
assert.Equal(t, float64(0), allNewTransactions[0].GeoLongitude)
|
||||||
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
assert.Equal(t, float64(0), allNewTransactions[0].GeoLatitude)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,a b"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,a b"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Geographic Location\n"+
|
||||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1 "), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,1 "), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseTag(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseTag(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, allNewTags, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+
|
_, _, _, allNewTags, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Tags\n"+
|
||||||
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,Balance Modification,,Test Account,123.45,,,foo;;bar.;#test;hello\tworld;;"), 0, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -418,14 +418,14 @@ func TestEzBookKeepingPlainFileConverterParseImportedData_ParseTag(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_ParseDescription(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1234567890,
|
Uid: 1234567890,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
allNewTransactions, _, _, _, err := converter.parseImportedData(user, ",", []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+
|
allNewTransactions, _, _, _, err := converter.ParseImportedData(user, []byte("Time,Type,Sub Category,Account,Amount,Account2,Account2 Amount,Description\n"+
|
||||||
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,foo bar\t#test"), 0, nil, nil, nil)
|
"2024-09-01 12:34:56,Expense,Test Category,Test Account,123.45,,,foo bar\t#test"), 0, nil, nil, nil)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -434,45 +434,45 @@ func TestEzBookKeepingPlainFileConverterParseImportedData_ParseDescription(t *te
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEzBookKeepingPlainFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
func TestEzBookKeepingPlainFileConverterParseImportedData_MissingRequiredColumn(t *testing.T) {
|
||||||
converter := &EzBookKeepingPlainFileConverter{}
|
converter := EzBookKeepingTransactionDataCSVFileConverter
|
||||||
|
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
Uid: 1,
|
Uid: 1,
|
||||||
DefaultCurrency: "CNY",
|
DefaultCurrency: "CNY",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _, _, err := converter.parseImportedData(user, ",", []byte(""), 0, nil, nil, nil)
|
_, _, _, _, err := converter.ParseImportedData(user, []byte(""), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||||
"+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
"+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||||
"2024-09-01 00:00:00,+08:00,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,+08:00,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Type,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Type,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Account,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Timezone,Type,Category,Sub Category,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Timezone,Type,Category,Sub Category,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,CNY,123.45,,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,,,,,,"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Geographic Location,Tags,Description\n"+
|
||||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
_, _, _, _, err = converter.parseImportedData(user, ",", []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
_, _, _, _, err = converter.ParseImportedData(user, []byte("Time,Timezone,Type,Category,Sub Category,Account,Account Currency,Amount,Account2,Account2 Currency,Account2 Amount,Geographic Location,Tags,Description\n"+
|
||||||
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil)
|
"2024-09-01 00:00:00,+08:00,Balance Modification,Test Category,Test Sub Category,Test Account,CNY,123.45,,,,,"), 0, nil, nil, nil)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ezBookKeepingTransactionPlainTextDataTable defines the structure of ezbookkeeping transaction plain text data table
|
||||||
|
type ezBookKeepingTransactionPlainTextDataTable struct {
|
||||||
|
columnSeparator string
|
||||||
|
lineSeparator string
|
||||||
|
allLines []string
|
||||||
|
headerLineColumnNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ezBookKeepingTransactionPlainTextDataRow defines the structure of ezbookkeeping transaction plain text data row
|
||||||
|
type ezBookKeepingTransactionPlainTextDataRow struct {
|
||||||
|
allItems []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ezBookKeepingTransactionPlainTextDataRowIterator defines the structure of ezbookkeeping transaction plain text data row iterator
|
||||||
|
type ezBookKeepingTransactionPlainTextDataRowIterator struct {
|
||||||
|
dataTable *ezBookKeepingTransactionPlainTextDataTable
|
||||||
|
currentIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ezBookKeepingTransactionPlainTextDataTableBuilder defines the structure of ezbookkeeping transaction plain text data table builder
|
||||||
|
type ezBookKeepingTransactionPlainTextDataTableBuilder struct {
|
||||||
|
columnSeparator string
|
||||||
|
lineSeparator string
|
||||||
|
columns []DataTableColumn
|
||||||
|
dataColumnNameMapping map[DataTableColumn]string
|
||||||
|
dataLineFormat string
|
||||||
|
builder *strings.Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataRowCount returns the total count of data row
|
||||||
|
func (t *ezBookKeepingTransactionPlainTextDataTable) DataRowCount() int {
|
||||||
|
if len(t.allLines) < 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(t.allLines) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderLineColumnNames returns the header column name list
|
||||||
|
func (t *ezBookKeepingTransactionPlainTextDataTable) HeaderLineColumnNames() []string {
|
||||||
|
return t.headerLineColumnNames
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataRowIterator returns the iterator of data row
|
||||||
|
func (t *ezBookKeepingTransactionPlainTextDataTable) DataRowIterator() ImportedDataRowIterator {
|
||||||
|
return &ezBookKeepingTransactionPlainTextDataRowIterator{
|
||||||
|
dataTable: t,
|
||||||
|
currentIndex: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnCount returns the total count of column in this data row
|
||||||
|
func (r *ezBookKeepingTransactionPlainTextDataRow) ColumnCount() int {
|
||||||
|
return len(r.allItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetData returns the data in the specified column index
|
||||||
|
func (r *ezBookKeepingTransactionPlainTextDataRow) GetData(columnIndex int) string {
|
||||||
|
return r.allItems[columnIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNext returns whether the iterator does not reach the end
|
||||||
|
func (t *ezBookKeepingTransactionPlainTextDataRowIterator) HasNext() bool {
|
||||||
|
return t.currentIndex+1 < len(t.dataTable.allLines)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next imported data row
|
||||||
|
func (t *ezBookKeepingTransactionPlainTextDataRowIterator) Next() ImportedDataRow {
|
||||||
|
if t.currentIndex+1 >= len(t.dataTable.allLines) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.currentIndex++
|
||||||
|
|
||||||
|
rowContent := t.dataTable.allLines[t.currentIndex]
|
||||||
|
rowItems := strings.Split(rowContent, t.dataTable.columnSeparator)
|
||||||
|
|
||||||
|
return &ezBookKeepingTransactionPlainTextDataRow{
|
||||||
|
allItems: rowItems,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendTransaction appends the specified transaction to data builder
|
||||||
|
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) AppendTransaction(data map[DataTableColumn]string) {
|
||||||
|
dataRowParams := make([]any, len(b.columns))
|
||||||
|
|
||||||
|
for i := 0; i < len(b.columns); i++ {
|
||||||
|
dataRowParams[i] = data[b.columns[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
b.builder.WriteString(fmt.Sprintf(b.dataLineFormat, dataRowParams...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the textual representation of this data
|
||||||
|
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) String() string {
|
||||||
|
return b.builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) generateHeaderLine() string {
|
||||||
|
var ret strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < len(b.columns); i++ {
|
||||||
|
if ret.Len() > 0 {
|
||||||
|
ret.WriteString(b.columnSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
dataColumn := b.columns[i]
|
||||||
|
columnName := b.dataColumnNameMapping[dataColumn]
|
||||||
|
|
||||||
|
ret.WriteString(columnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.WriteString(b.lineSeparator)
|
||||||
|
|
||||||
|
return ret.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ezBookKeepingTransactionPlainTextDataTableBuilder) generateDataLineFormat() string {
|
||||||
|
var ret strings.Builder
|
||||||
|
|
||||||
|
for i := 0; i < len(b.columns); i++ {
|
||||||
|
if ret.Len() > 0 {
|
||||||
|
ret.WriteString(b.columnSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.WriteString("%s")
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.WriteString(b.lineSeparator)
|
||||||
|
|
||||||
|
return ret.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNewezbookkeepingTransactionPlainTextDataTable(content string, columnSeparator string, lineSeparator string) (*ezBookKeepingTransactionPlainTextDataTable, error) {
|
||||||
|
allLines := strings.Split(content, lineSeparator)
|
||||||
|
|
||||||
|
if len(allLines) < 2 {
|
||||||
|
return nil, errs.ErrOperationFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
headerLineItems := strings.Split(allLines[0], columnSeparator)
|
||||||
|
|
||||||
|
return &ezBookKeepingTransactionPlainTextDataTable{
|
||||||
|
columnSeparator: columnSeparator,
|
||||||
|
lineSeparator: lineSeparator,
|
||||||
|
allLines: allLines,
|
||||||
|
headerLineColumnNames: headerLineItems,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNewezbookkeepingTransactionPlainTextDataTableBuilder(transactionCount int, columns []DataTableColumn, dataColumnNameMapping map[DataTableColumn]string, columnSeparator string, lineSeparator string) *ezBookKeepingTransactionPlainTextDataTableBuilder {
|
||||||
|
var builder strings.Builder
|
||||||
|
builder.Grow(transactionCount * 100)
|
||||||
|
|
||||||
|
dataTableBuilder := &ezBookKeepingTransactionPlainTextDataTableBuilder{
|
||||||
|
columnSeparator: columnSeparator,
|
||||||
|
lineSeparator: lineSeparator,
|
||||||
|
columns: columns,
|
||||||
|
dataColumnNameMapping: dataColumnNameMapping,
|
||||||
|
builder: &builder,
|
||||||
|
}
|
||||||
|
|
||||||
|
headerLine := dataTableBuilder.generateHeaderLine()
|
||||||
|
dataLineFormat := dataTableBuilder.generateDataLineFormat()
|
||||||
|
|
||||||
|
dataTableBuilder.builder.WriteString(headerLine)
|
||||||
|
dataTableBuilder.dataLineFormat = dataLineFormat
|
||||||
|
|
||||||
|
return dataTableBuilder
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package converters
|
||||||
|
|
||||||
|
// ezBookKeepingTransactionDataTSVFileConverter defines the structure of TSV file converter
|
||||||
|
type ezBookKeepingTransactionDataTSVFileConverter struct {
|
||||||
|
ezBookKeepingTransactionDataPlainTextConverter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize an ezbookkeeping transaction data tsv file converter singleton instance
|
||||||
|
var (
|
||||||
|
EzBookKeepingTransactionDataTSVFileConverter = &ezBookKeepingTransactionDataTSVFileConverter{
|
||||||
|
ezBookKeepingTransactionDataPlainTextConverter{
|
||||||
|
DataTableTransactionDataConverter: DataTableTransactionDataConverter{
|
||||||
|
dataColumnMapping: ezbookkeepingDataColumnNameMapping,
|
||||||
|
transactionTypeMapping: ezbookkeepingTransactionTypeNameMapping,
|
||||||
|
transactionTypeNameMapping: ezbookkeepingNameTransactionTypeMapping,
|
||||||
|
columnSeparator: "\t",
|
||||||
|
lineSeparator: "\n",
|
||||||
|
geoLocationSeparator: " ",
|
||||||
|
transactionTagSeparator: ";",
|
||||||
|
},
|
||||||
|
columns: ezbookkeepingDataColumns,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package converters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EzBookKeepingTSVFileConverter defines the structure of TSV file converter
|
|
||||||
type EzBookKeepingTSVFileConverter struct {
|
|
||||||
EzBookKeepingPlainFileConverter
|
|
||||||
}
|
|
||||||
|
|
||||||
const tsvSeparator = "\t"
|
|
||||||
|
|
||||||
// ToExportedContent returns the exported TSV data
|
|
||||||
func (e *EzBookKeepingTSVFileConverter) ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error) {
|
|
||||||
return e.toExportedContent(uid, tsvSeparator, transactions, accountMap, categoryMap, tagMap, allTagIndexes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseImportedData parses transactions of ezbookkeeping TSV data
|
|
||||||
func (e *EzBookKeepingTSVFileConverter) ParseImportedData(user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) ([]*models.Transaction, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
|
||||||
return e.parseImportedData(user, tsvSeparator, data, defaultTimezoneOffset, accountMap, categoryMap, tagMap)
|
|
||||||
}
|
|
||||||
@@ -2,21 +2,21 @@ package converters
|
|||||||
|
|
||||||
import "github.com/mayswind/ezbookkeeping/pkg/models"
|
import "github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
|
||||||
// ImportTransactionSlice represents the slice data structure of import transaction data
|
// ImportedTransactionSlice represents the slice data structure of import transaction data
|
||||||
type ImportTransactionSlice []*models.Transaction
|
type ImportedTransactionSlice []*models.Transaction
|
||||||
|
|
||||||
// Len returns the count of items
|
// Len returns the count of items
|
||||||
func (s ImportTransactionSlice) Len() int {
|
func (s ImportedTransactionSlice) Len() int {
|
||||||
return len(s)
|
return len(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swap swaps two items
|
// Swap swaps two items
|
||||||
func (s ImportTransactionSlice) Swap(i, j int) {
|
func (s ImportedTransactionSlice) Swap(i, j int) {
|
||||||
s[i], s[j] = s[j], s[i]
|
s[i], s[j] = s[j], s[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less reports whether the first item is less than the second one
|
// Less reports whether the first item is less than the second one
|
||||||
func (s ImportTransactionSlice) Less(i, j int) bool {
|
func (s ImportedTransactionSlice) Less(i, j int) bool {
|
||||||
if s[i].Type != s[j].Type && (s[i].Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE || s[j].Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE) {
|
if s[i].Type != s[j].Type && (s[i].Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE || s[j].Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE) {
|
||||||
if s[i].Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
if s[i].Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE {
|
||||||
return true
|
return true
|
||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestImportTransactionSliceLess(t *testing.T) {
|
func TestImportTransactionSliceLess(t *testing.T) {
|
||||||
var transactionSlice ImportTransactionSlice
|
var transactionSlice ImportedTransactionSlice
|
||||||
transactionSlice = append(transactionSlice, &models.Transaction{
|
transactionSlice = append(transactionSlice, &models.Transaction{
|
||||||
TransactionId: 1,
|
TransactionId: 1,
|
||||||
Type: models.TRANSACTION_DB_TYPE_EXPENSE,
|
Type: models.TRANSACTION_DB_TYPE_EXPENSE,
|
||||||
@@ -4,11 +4,20 @@ import (
|
|||||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DataConverter defines the structure of data converter
|
// TransactionDataExporter defines the structure of transaction data exporter
|
||||||
type DataConverter interface {
|
type TransactionDataExporter interface {
|
||||||
// ToExportedContent returns the exported data
|
// ToExportedContent returns the exported data
|
||||||
ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error)
|
ToExportedContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexes map[int64][]int64) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionDataImporter defines the structure of transaction data importer
|
||||||
|
type TransactionDataImporter interface {
|
||||||
// ParseImportedData returns the imported data
|
// ParseImportedData returns the imported data
|
||||||
ParseImportedData(user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) ([]*models.Transaction, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error)
|
ParseImportedData(user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, categoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) ([]*models.Transaction, []*models.Account, []*models.TransactionCategory, []*models.TransactionTag, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionDataConverter defines the structure of transaction data converter
|
||||||
|
type TransactionDataConverter interface {
|
||||||
|
TransactionDataExporter
|
||||||
|
TransactionDataImporter
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user