mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-20 17:54:30 +08:00
import payee field as tags when importing a QIF file (#356)
This commit is contained in:
@@ -29,7 +29,7 @@ type DataTableTransactionDataImporter struct {
|
||||
}
|
||||
|
||||
// ParseImportedData returns the imported transaction data
|
||||
func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable datatable.TransactionDataTable, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, dataTable datatable.TransactionDataTable, defaultTimezoneOffset int16, additionalOptions TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) {
|
||||
if dataTable.TransactionRowCount() < 1 {
|
||||
log.Errorf(ctx, "[data_table_transaction_data_importer.ParseImportedData] cannot parse import data for user \"uid:%d\", because data table row count is less 1", user.Uid)
|
||||
return nil, nil, nil, nil, nil, nil, errs.ErrNotFoundTransactionDataInFile
|
||||
@@ -303,6 +303,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
|
||||
var tagIds []string
|
||||
var tagNames []string
|
||||
tagNamesMap := make(map[string]bool)
|
||||
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_TAGS) {
|
||||
var tagNameItems []string
|
||||
@@ -316,7 +317,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
for i := 0; i < len(tagNameItems); i++ {
|
||||
tagName := tagNameItems[i]
|
||||
|
||||
if tagName == "" {
|
||||
if tagName == "" || tagNamesMap[tagName] {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -333,6 +334,28 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
}
|
||||
|
||||
tagNames = append(tagNames, tagName)
|
||||
tagNamesMap[tagName] = true
|
||||
}
|
||||
}
|
||||
|
||||
if dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_PAYEE) && additionalOptions.IsPayeeAsTag() {
|
||||
payee := dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_PAYEE)
|
||||
|
||||
if payee != "" && !tagNamesMap[payee] {
|
||||
tag, exists := tagMap[payee]
|
||||
|
||||
if !exists {
|
||||
tag = c.createNewTransactionTagModel(user.Uid, payee)
|
||||
allNewTags = append(allNewTags, tag)
|
||||
tagMap[payee] = tag
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
tagIds = append(tagIds, utils.Int64ToString(tag.TagId))
|
||||
}
|
||||
|
||||
tagNames = append(tagNames, payee)
|
||||
tagNamesMap[payee] = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +365,10 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u
|
||||
description = dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_DESCRIPTION)
|
||||
}
|
||||
|
||||
if description == "" && additionalOptions.IsPayeeAsDescription() && dataTable.HasColumn(datatable.TRANSACTION_DATA_TABLE_PAYEE) {
|
||||
description = dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_PAYEE)
|
||||
}
|
||||
|
||||
transaction := &models.ImportTransaction{
|
||||
Transaction: &models.Transaction{
|
||||
Uid: user.Uid,
|
||||
|
||||
@@ -14,7 +14,7 @@ type TransactionDataExporter interface {
|
||||
// TransactionDataImporter defines the structure of transaction data importer
|
||||
type TransactionDataImporter interface {
|
||||
// ParseImportedData returns the imported data
|
||||
ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error)
|
||||
ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, additionalOptions TransactionDataImporterOptions, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error)
|
||||
}
|
||||
|
||||
// TransactionDataConverter defines the structure of transaction data converter
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package converter
|
||||
|
||||
import "strings"
|
||||
|
||||
// TransactionDataImporterOptions defines the options for transaction data importer
|
||||
type TransactionDataImporterOptions struct {
|
||||
payeeAsTag bool
|
||||
payeeAsDescription bool
|
||||
memberAsTag bool
|
||||
projectAsTag bool
|
||||
merchantAsTag bool
|
||||
}
|
||||
|
||||
// DefaultImporterOptions provides the default options for transaction data importer
|
||||
var DefaultImporterOptions = TransactionDataImporterOptions{
|
||||
payeeAsTag: false,
|
||||
payeeAsDescription: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
|
||||
// IsPayeeAsTag returns whether to import payee as tag
|
||||
func (o TransactionDataImporterOptions) IsPayeeAsTag() bool {
|
||||
return o.payeeAsTag
|
||||
}
|
||||
|
||||
// IsPayeeAsDescription returns whether to import payee as description
|
||||
func (o TransactionDataImporterOptions) IsPayeeAsDescription() bool {
|
||||
return o.payeeAsDescription
|
||||
}
|
||||
|
||||
// IsMemberAsTag returns whether to import member as tag
|
||||
func (o TransactionDataImporterOptions) IsMemberAsTag() bool {
|
||||
return o.memberAsTag
|
||||
}
|
||||
|
||||
// IsProjectAsTag returns whether to import project as tag
|
||||
func (o TransactionDataImporterOptions) IsProjectAsTag() bool {
|
||||
return o.projectAsTag
|
||||
}
|
||||
|
||||
// IsMerchantAsTag returns whether to import merchant as tag
|
||||
func (o TransactionDataImporterOptions) IsMerchantAsTag() bool {
|
||||
return o.merchantAsTag
|
||||
}
|
||||
|
||||
// WithPayeeAsTag sets the option to import payee as tag
|
||||
func (o TransactionDataImporterOptions) WithPayeeAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.payeeAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithPayeeAsDescription sets the option to import payee as description
|
||||
func (o TransactionDataImporterOptions) WithPayeeAsDescription() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.payeeAsDescription = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithMemberAsTag sets the option to import member as tag
|
||||
func (o TransactionDataImporterOptions) WithMemberAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.memberAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithProjectAsTag sets the option to import project as tag
|
||||
func (o TransactionDataImporterOptions) WithProjectAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.projectAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// WithMerchantAsTag sets the option to import merchant as tag
|
||||
func (o TransactionDataImporterOptions) WithMerchantAsTag() TransactionDataImporterOptions {
|
||||
cloned := o.Clone()
|
||||
cloned.merchantAsTag = true
|
||||
return cloned
|
||||
}
|
||||
|
||||
// Clone creates a copy of the options instance
|
||||
func (o TransactionDataImporterOptions) Clone() TransactionDataImporterOptions {
|
||||
return TransactionDataImporterOptions{
|
||||
payeeAsTag: o.payeeAsTag,
|
||||
payeeAsDescription: o.payeeAsDescription,
|
||||
memberAsTag: o.memberAsTag,
|
||||
projectAsTag: o.projectAsTag,
|
||||
merchantAsTag: o.merchantAsTag,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseImporterOptions parses the textual options to the instance
|
||||
func ParseImporterOptions(s string) TransactionDataImporterOptions {
|
||||
options := TransactionDataImporterOptions{}
|
||||
|
||||
if s == "" {
|
||||
return options
|
||||
}
|
||||
|
||||
for _, option := range strings.Split(s, ",") {
|
||||
switch option {
|
||||
case "payeeAsTag":
|
||||
options.payeeAsTag = true
|
||||
case "payeeAsDescription":
|
||||
options.payeeAsDescription = true
|
||||
case "memberAsTag":
|
||||
options.memberAsTag = true
|
||||
case "projectAsTag":
|
||||
options.projectAsTag = true
|
||||
case "merchantAsTag":
|
||||
options.merchantAsTag = true
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseImporterOptions(t *testing.T) {
|
||||
actualValue := ParseImporterOptions("payeeAsTag,memberAsTag")
|
||||
expectedValue := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
memberAsTag: true,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, true, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
|
||||
actualValue = ParseImporterOptions("")
|
||||
expectedValue = TransactionDataImporterOptions{
|
||||
payeeAsTag: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, false, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
}
|
||||
|
||||
func TestParseImporterOptions_WithAllOptions(t *testing.T) {
|
||||
actualValue := ParseImporterOptions("payeeAsTag,payeeAsDescription,memberAsTag,projectAsTag,merchantAsTag")
|
||||
expectedValue := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
payeeAsDescription: true,
|
||||
memberAsTag: true,
|
||||
projectAsTag: true,
|
||||
merchantAsTag: true,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsDescription())
|
||||
assert.Equal(t, true, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, true, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, true, actualValue.IsMerchantAsTag())
|
||||
}
|
||||
|
||||
func TestParseImporterOptions_WithInvalidOptions(t *testing.T) {
|
||||
actualValue := ParseImporterOptions("invalidOption,payeeAsTag,memberAsTag")
|
||||
expectedValue := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
memberAsTag: true,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, true, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, true, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
|
||||
actualValue = ParseImporterOptions("invalidOption")
|
||||
expectedValue = TransactionDataImporterOptions{
|
||||
payeeAsTag: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: false,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
assert.Equal(t, expectedValue, actualValue)
|
||||
assert.Equal(t, false, actualValue.IsPayeeAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMemberAsTag())
|
||||
assert.Equal(t, false, actualValue.IsProjectAsTag())
|
||||
assert.Equal(t, false, actualValue.IsMerchantAsTag())
|
||||
}
|
||||
|
||||
func TestParseImporterOptions_Clone(t *testing.T) {
|
||||
original := TransactionDataImporterOptions{
|
||||
payeeAsTag: true,
|
||||
payeeAsDescription: false,
|
||||
memberAsTag: false,
|
||||
projectAsTag: true,
|
||||
merchantAsTag: false,
|
||||
}
|
||||
|
||||
cloned := original.Clone()
|
||||
assert.Equal(t, original, cloned)
|
||||
|
||||
// Modify cloned options and verify original options are not affected
|
||||
cloned.payeeAsTag = false
|
||||
cloned.payeeAsDescription = true
|
||||
cloned.memberAsTag = true
|
||||
|
||||
assert.Equal(t, true, original.payeeAsTag)
|
||||
assert.Equal(t, false, original.payeeAsDescription)
|
||||
assert.Equal(t, false, original.memberAsTag)
|
||||
assert.Equal(t, true, original.projectAsTag)
|
||||
assert.Equal(t, false, original.merchantAsTag)
|
||||
|
||||
assert.Equal(t, false, cloned.payeeAsTag)
|
||||
assert.Equal(t, true, cloned.payeeAsDescription)
|
||||
assert.Equal(t, true, cloned.memberAsTag)
|
||||
assert.Equal(t, true, cloned.projectAsTag)
|
||||
assert.Equal(t, false, cloned.merchantAsTag)
|
||||
}
|
||||
Reference in New Issue
Block a user