From 1ac633bdd742e96ee653d9b055492d5155994784 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Fri, 18 Apr 2025 20:03:43 +0800 Subject: [PATCH] use the sub-category according to the primary category name if there are duplicated sub-category names when importing transactions (#119) --- pkg/api/transactions.go | 2 +- pkg/cli/user_data.go | 4 +- ...ipay_transaction_data_csv_file_importer.go | 2 +- ...eancount_transaction_data_file_importer.go | 2 +- .../data_table_transaction_data_importer.go | 69 ++++++++++++++++--- .../converter/transaction_data_converter.go | 2 +- ...t_transaction_data_plain_text_converter.go | 2 +- ...stom_transaction_data_dsv_file_importer.go | 2 +- ..._app_transaction_data_csv_file_importer.go | 2 +- ...oud_transaction_data_xlsx_file_importer.go | 2 +- ..._web_transaction_data_xls_file_importer.go | 2 +- ...yiii_transaction_data_csv_file_importer.go | 2 +- .../gnucash_transaction_data_file_importer.go | 2 +- .../iif/iif_transaction_data_file_importer.go | 2 +- .../ofx/ofx_transaction_data_file_importer.go | 2 +- .../qif/qif_transaction_data_file_importer.go | 2 +- ..._pay_transaction_data_csv_file_importer.go | 2 +- pkg/services/transaction_categories.go | 59 +++++++++++++--- 18 files changed, 124 insertions(+), 38 deletions(-) diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index 2edb3aa5..90a810fa 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -1296,7 +1296,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext) return nil, errs.Or(err, errs.ErrOperationFailed) } - expenseCategoryMap, incomeCategoryMap, transferCategoryMap := a.transactionCategories.GetCategoryNameMapByList(categories) + expenseCategoryMap, incomeCategoryMap, transferCategoryMap := a.transactionCategories.GetSubCategoryNameMapByList(categories) tags, err := a.transactionTags.GetAllTagsByUid(c, user.Uid) diff --git a/pkg/cli/user_data.go b/pkg/cli/user_data.go index 5e8adcdb..f2ec1a99 100644 --- a/pkg/cli/user_data.go +++ b/pkg/cli/user_data.go @@ -876,7 +876,7 @@ func (l *UserDataCli) getUserEssentialData(c *core.CliContext, uid int64, userna return accountMap, categoryMap, tagMap, tagIndexes, tagIndexesMap, nil } -func (l *UserDataCli) getUserEssentialDataForImport(c *core.CliContext, uid int64, username string) (accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag, err error) { +func (l *UserDataCli) getUserEssentialDataForImport(c *core.CliContext, uid int64, username string) (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, err error) { if uid <= 0 { log.CliErrorf(c, "[user_data.getUserEssentialDataForImport] user uid \"%d\" is invalid", uid) return nil, nil, nil, nil, nil, errs.ErrUserIdInvalid @@ -898,7 +898,7 @@ func (l *UserDataCli) getUserEssentialDataForImport(c *core.CliContext, uid int6 return nil, nil, nil, nil, nil, err } - expenseCategoryMap, incomeCategoryMap, transferCategoryMap = l.categories.GetCategoryNameMapByList(categories) + expenseCategoryMap, incomeCategoryMap, transferCategoryMap = l.categories.GetSubCategoryNameMapByList(categories) tags, err := l.tags.GetAllTagsByUid(c, uid) diff --git a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go b/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go index f91a21c5..92ecfe9e 100644 --- a/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go +++ b/pkg/converters/alipay/alipay_transaction_data_csv_file_importer.go @@ -57,7 +57,7 @@ type alipayTransactionDataCsvFileImporter struct { } // ParseImportedData returns the imported data by parsing the alipay transaction csv data -func (c *alipayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *alipayTransactionDataCsvFileImporter) 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) { enc := simplifiedchinese.GB18030 reader := transform.NewReader(bytes.NewReader(data), enc.NewDecoder()) diff --git a/pkg/converters/beancount/beancount_transaction_data_file_importer.go b/pkg/converters/beancount/beancount_transaction_data_file_importer.go index f09adc54..41287880 100644 --- a/pkg/converters/beancount/beancount_transaction_data_file_importer.go +++ b/pkg/converters/beancount/beancount_transaction_data_file_importer.go @@ -24,7 +24,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the Beancount transaction data -func (c *beancountTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *beancountTransactionDataImporter) 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) { beancountDataReader, err := createNewBeancountDataReader(ctx, data) if err != nil { diff --git a/pkg/converters/converter/data_table_transaction_data_importer.go b/pkg/converters/converter/data_table_transaction_data_importer.go index 387fe397..d66ede3e 100644 --- a/pkg/converters/converter/data_table_transaction_data_importer.go +++ b/pkg/converters/converter/data_table_transaction_data_importer.go @@ -21,7 +21,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]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +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) { if dataTable.TransactionRowCount() < 1 { log.Errorf(ctx, "[data_table_transaction_data_exporter.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 @@ -48,15 +48,15 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u } if expenseCategoryMap == nil { - expenseCategoryMap = make(map[string]*models.TransactionCategory) + expenseCategoryMap = make(map[string]map[string]*models.TransactionCategory) } if incomeCategoryMap == nil { - incomeCategoryMap = make(map[string]*models.TransactionCategory) + incomeCategoryMap = make(map[string]map[string]*models.TransactionCategory) } if transferCategoryMap == nil { - transferCategoryMap = make(map[string]*models.TransactionCategory) + transferCategoryMap = make(map[string]map[string]*models.TransactionCategory) } if tagMap == nil { @@ -114,6 +114,7 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u } categoryId := int64(0) + categoryName := "" subCategoryName := "" if transactionDbType != models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { @@ -124,35 +125,51 @@ func (c *DataTableTransactionDataImporter) ParseImportedData(ctx core.Context, u return nil, nil, nil, nil, nil, nil, errs.Or(err, errs.ErrTransactionTypeInvalid) } + categoryName = dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_CATEGORY) subCategoryName = dataRow.GetData(datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY) if transactionDbType == models.TRANSACTION_DB_TYPE_EXPENSE { - subCategory, exists := expenseCategoryMap[subCategoryName] + subCategory, exists := c.getTransactionCategory(expenseCategoryMap, categoryName, subCategoryName) if !exists { subCategory = c.createNewTransactionCategoryModel(user.Uid, subCategoryName, transactionCategoryType) allNewSubExpenseCategories = append(allNewSubExpenseCategories, subCategory) - expenseCategoryMap[subCategoryName] = subCategory + + if _, exists = expenseCategoryMap[subCategoryName]; !exists { + expenseCategoryMap[subCategoryName] = make(map[string]*models.TransactionCategory) + } + + expenseCategoryMap[subCategoryName][categoryName] = subCategory } categoryId = subCategory.CategoryId } else if transactionDbType == models.TRANSACTION_DB_TYPE_INCOME { - subCategory, exists := incomeCategoryMap[subCategoryName] + subCategory, exists := c.getTransactionCategory(incomeCategoryMap, categoryName, subCategoryName) if !exists { subCategory = c.createNewTransactionCategoryModel(user.Uid, subCategoryName, transactionCategoryType) allNewSubIncomeCategories = append(allNewSubIncomeCategories, subCategory) - incomeCategoryMap[subCategoryName] = subCategory + + if _, exists = incomeCategoryMap[subCategoryName]; !exists { + incomeCategoryMap[subCategoryName] = make(map[string]*models.TransactionCategory) + } + + incomeCategoryMap[subCategoryName][categoryName] = subCategory } categoryId = subCategory.CategoryId } else if transactionDbType == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { - subCategory, exists := transferCategoryMap[subCategoryName] + subCategory, exists := c.getTransactionCategory(transferCategoryMap, categoryName, subCategoryName) if !exists { subCategory = c.createNewTransactionCategoryModel(user.Uid, subCategoryName, transactionCategoryType) allNewSubTransferCategories = append(allNewSubTransferCategories, subCategory) - transferCategoryMap[subCategoryName] = subCategory + + if _, exists = transferCategoryMap[subCategoryName]; !exists { + transferCategoryMap[subCategoryName] = make(map[string]*models.TransactionCategory) + } + + transferCategoryMap[subCategoryName][categoryName] = subCategory } categoryId = subCategory.CategoryId @@ -393,6 +410,38 @@ func (c *DataTableTransactionDataImporter) getTransactionCategoryType(transactio } } +func (c *DataTableTransactionDataImporter) getTransactionCategory(categories map[string]map[string]*models.TransactionCategory, categoryName string, subCategoryName string) (*models.TransactionCategory, bool) { + if len(categories) < 1 { + return nil, false + } + + subCategories, exists := categories[subCategoryName] + + if !exists || len(subCategories) < 1 { + return nil, false + } + + if categoryName == "" { + for _, subCategory := range subCategories { + if subCategory != nil { + return subCategory, true + } + } + } + + subCategory, exists := subCategories[categoryName] + + if !exists { + for _, subCategory := range subCategories { + if subCategory != nil { + return subCategory, true + } + } + } + + return subCategory, exists +} + func (c *DataTableTransactionDataImporter) createNewAccountModel(uid int64, accountName string, currency string) *models.Account { return &models.Account{ Uid: uid, diff --git a/pkg/converters/converter/transaction_data_converter.go b/pkg/converters/converter/transaction_data_converter.go index 3e3f4ae1..867e0401 100644 --- a/pkg/converters/converter/transaction_data_converter.go +++ b/pkg/converters/converter/transaction_data_converter.go @@ -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]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) + 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) } // TransactionDataConverter defines the structure of transaction data converter diff --git a/pkg/converters/default/default_transaction_data_plain_text_converter.go b/pkg/converters/default/default_transaction_data_plain_text_converter.go index a2275eda..f655be09 100644 --- a/pkg/converters/default/default_transaction_data_plain_text_converter.go +++ b/pkg/converters/default/default_transaction_data_plain_text_converter.go @@ -83,7 +83,7 @@ func (c *defaultTransactionDataPlainTextConverter) ToExportedContent(ctx core.Co } // ParseImportedData returns the imported data by parsing the transaction plain text data -func (c *defaultTransactionDataPlainTextConverter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *defaultTransactionDataPlainTextConverter) 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) { dataTable, err := createNewDefaultPlainTextDataTable( string(data), c.columnSeparator, diff --git a/pkg/converters/dsv/custom_transaction_data_dsv_file_importer.go b/pkg/converters/dsv/custom_transaction_data_dsv_file_importer.go index d2484c5d..7cf73001 100644 --- a/pkg/converters/dsv/custom_transaction_data_dsv_file_importer.go +++ b/pkg/converters/dsv/custom_transaction_data_dsv_file_importer.go @@ -145,7 +145,7 @@ func (c *customTransactionDataDsvFileImporter) ParseDsvFileLines(ctx core.Contex } // ParseImportedData returns the imported data by parsing the custom transaction dsv data -func (c *customTransactionDataDsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *customTransactionDataDsvFileImporter) 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) { allLines, err := c.ParseDsvFileLines(ctx, data) if err != nil { diff --git a/pkg/converters/feidee/feidee_mymoney_app_transaction_data_csv_file_importer.go b/pkg/converters/feidee/feidee_mymoney_app_transaction_data_csv_file_importer.go index 489e4b32..fa02ca03 100644 --- a/pkg/converters/feidee/feidee_mymoney_app_transaction_data_csv_file_importer.go +++ b/pkg/converters/feidee/feidee_mymoney_app_transaction_data_csv_file_importer.go @@ -55,7 +55,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the feidee mymoney app transaction csv data -func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *feideeMymoneyAppTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { fallback := unicode.UTF8.NewDecoder() reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback)) diff --git a/pkg/converters/feidee/feidee_mymoney_elecloud_transaction_data_xlsx_file_importer.go b/pkg/converters/feidee/feidee_mymoney_elecloud_transaction_data_xlsx_file_importer.go index 0d9be1ec..b1af8255 100644 --- a/pkg/converters/feidee/feidee_mymoney_elecloud_transaction_data_xlsx_file_importer.go +++ b/pkg/converters/feidee/feidee_mymoney_elecloud_transaction_data_xlsx_file_importer.go @@ -31,7 +31,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the feidee mymoney (elecloud) transaction xlsx data -func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *feideeMymoneyElecloudTransactionDataXlsxFileImporter) 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) { dataTable, err := excel.CreateNewExcelOOXMLFileImportedDataTable(data) if err != nil { diff --git a/pkg/converters/feidee/feidee_mymoney_web_transaction_data_xls_file_importer.go b/pkg/converters/feidee/feidee_mymoney_web_transaction_data_xls_file_importer.go index 5d6db6aa..0833ba5b 100644 --- a/pkg/converters/feidee/feidee_mymoney_web_transaction_data_xls_file_importer.go +++ b/pkg/converters/feidee/feidee_mymoney_web_transaction_data_xls_file_importer.go @@ -30,7 +30,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the feidee mymoney (web) transaction xls data -func (c *feideeMymoneyWebTransactionDataXlsFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *feideeMymoneyWebTransactionDataXlsFileImporter) 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) { dataTable, err := excel.CreateNewExcelMSCFBFileImportedDataTable(data) if err != nil { diff --git a/pkg/converters/fireflyIII/fireflyiii_transaction_data_csv_file_importer.go b/pkg/converters/fireflyIII/fireflyiii_transaction_data_csv_file_importer.go index 3fd81a9d..808f4b45 100644 --- a/pkg/converters/fireflyIII/fireflyiii_transaction_data_csv_file_importer.go +++ b/pkg/converters/fireflyIII/fireflyiii_transaction_data_csv_file_importer.go @@ -40,7 +40,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the firefly III transaction csv data -func (c *fireflyIIITransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *fireflyIIITransactionDataCsvFileImporter) 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) { reader := bytes.NewReader(data) dataTable, err := csv.CreateNewCsvImportedDataTable(ctx, reader) diff --git a/pkg/converters/gnucash/gnucash_transaction_data_file_importer.go b/pkg/converters/gnucash/gnucash_transaction_data_file_importer.go index 007a3955..277465b1 100644 --- a/pkg/converters/gnucash/gnucash_transaction_data_file_importer.go +++ b/pkg/converters/gnucash/gnucash_transaction_data_file_importer.go @@ -24,7 +24,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the gnucash transaction data -func (c *gnucashTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *gnucashTransactionDataImporter) 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) { gnucashDataReader, err := createNewGnuCashDatabaseReader(data) if err != nil { diff --git a/pkg/converters/iif/iif_transaction_data_file_importer.go b/pkg/converters/iif/iif_transaction_data_file_importer.go index 73837623..b12d5cfc 100644 --- a/pkg/converters/iif/iif_transaction_data_file_importer.go +++ b/pkg/converters/iif/iif_transaction_data_file_importer.go @@ -23,7 +23,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the intuit interchange format (iif) data -func (c *iifTransactionDataFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *iifTransactionDataFileImporter) 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) { iifDataReader := createNewIifDataReader(data) accountDatasets, transactionDatasets, err := iifDataReader.read(ctx) diff --git a/pkg/converters/ofx/ofx_transaction_data_file_importer.go b/pkg/converters/ofx/ofx_transaction_data_file_importer.go index 149340b6..2d409c86 100644 --- a/pkg/converters/ofx/ofx_transaction_data_file_importer.go +++ b/pkg/converters/ofx/ofx_transaction_data_file_importer.go @@ -23,7 +23,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the open financial exchange (ofx) file transaction data -func (c *ofxTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *ofxTransactionDataImporter) 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) { ofxDataReader, err := createNewOFXFileReader(ctx, data) if err != nil { diff --git a/pkg/converters/qif/qif_transaction_data_file_importer.go b/pkg/converters/qif/qif_transaction_data_file_importer.go index ffdd1472..38799cc6 100644 --- a/pkg/converters/qif/qif_transaction_data_file_importer.go +++ b/pkg/converters/qif/qif_transaction_data_file_importer.go @@ -35,7 +35,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the quicken interchange format (qif) transaction data -func (c *qifTransactionDataImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *qifTransactionDataImporter) 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) { qifDataReader := createNewQifDataReader(data) qifData, err := qifDataReader.read(ctx) diff --git a/pkg/converters/wechat/wechat_pay_transaction_data_csv_file_importer.go b/pkg/converters/wechat/wechat_pay_transaction_data_csv_file_importer.go index 96d433a7..6757c68d 100644 --- a/pkg/converters/wechat/wechat_pay_transaction_data_csv_file_importer.go +++ b/pkg/converters/wechat/wechat_pay_transaction_data_csv_file_importer.go @@ -45,7 +45,7 @@ var ( ) // ParseImportedData returns the imported data by parsing the wechat pay transaction csv data -func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { +func (c *wechatPayTransactionDataCsvFileImporter) ParseImportedData(ctx core.Context, user *models.User, data []byte, defaultTimezoneOffset int16, accountMap map[string]*models.Account, expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory, tagMap map[string]*models.TransactionTag) (models.ImportedTransactionSlice, []*models.Account, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionCategory, []*models.TransactionTag, error) { fallback := unicode.UTF8.NewDecoder() reader := transform.NewReader(bytes.NewReader(data), unicode.BOMOverride(fallback)) diff --git a/pkg/services/transaction_categories.go b/pkg/services/transaction_categories.go index d52feaa9..ccb0eb0f 100644 --- a/pkg/services/transaction_categories.go +++ b/pkg/services/transaction_categories.go @@ -448,22 +448,59 @@ func (s *TransactionCategoryService) GetCategoryMapByList(categories []*models.T return categoryMap } -// GetCategoryNameMapByList returns a transaction category map by a list -func (s *TransactionCategoryService) GetCategoryNameMapByList(categories []*models.TransactionCategory) (expenseCategoryMap map[string]*models.TransactionCategory, incomeCategoryMap map[string]*models.TransactionCategory, transferCategoryMap map[string]*models.TransactionCategory) { - expenseCategoryMap = make(map[string]*models.TransactionCategory) - incomeCategoryMap = make(map[string]*models.TransactionCategory) - transferCategoryMap = make(map[string]*models.TransactionCategory) +// GetSubCategoryNameMapByList returns a sub transaction category map by a list +func (s *TransactionCategoryService) GetSubCategoryNameMapByList(categories []*models.TransactionCategory) (expenseCategoryMap map[string]map[string]*models.TransactionCategory, incomeCategoryMap map[string]map[string]*models.TransactionCategory, transferCategoryMap map[string]map[string]*models.TransactionCategory) { + categoryMap := make(map[int64]*models.TransactionCategory, len(categories)) + expenseCategoryMap = make(map[string]map[string]*models.TransactionCategory) + incomeCategoryMap = make(map[string]map[string]*models.TransactionCategory) + transferCategoryMap = make(map[string]map[string]*models.TransactionCategory) + + for i := 0; i < len(categories); i++ { + category := categories[i] + categoryMap[category.CategoryId] = category + } for i := 0; i < len(categories); i++ { category := categories[i] - if category.Type == models.CATEGORY_TYPE_INCOME { - incomeCategoryMap[category.Name] = category - } else if category.Type == models.CATEGORY_TYPE_EXPENSE { - expenseCategoryMap[category.Name] = category - } else if category.Type == models.CATEGORY_TYPE_TRANSFER { - transferCategoryMap[category.Name] = category + if category.ParentCategoryId == models.LevelOneTransactionParentId { + continue } + + parentCategory, exists := categoryMap[category.ParentCategoryId] + + if !exists { + continue + } + + var categories map[string]*models.TransactionCategory + + if category.Type == models.CATEGORY_TYPE_INCOME { + categories, exists = incomeCategoryMap[category.Name] + + if !exists { + categories = make(map[string]*models.TransactionCategory) + incomeCategoryMap[category.Name] = categories + } + } else if category.Type == models.CATEGORY_TYPE_EXPENSE { + categories, exists = expenseCategoryMap[category.Name] + + if !exists { + categories = make(map[string]*models.TransactionCategory) + expenseCategoryMap[category.Name] = categories + } + } else if category.Type == models.CATEGORY_TYPE_TRANSFER { + categories, exists = transferCategoryMap[category.Name] + + if !exists { + categories = make(map[string]*models.TransactionCategory) + transferCategoryMap[category.Name] = categories + } + } else { + continue + } + + categories[parentCategory.Name] = category } return expenseCategoryMap, incomeCategoryMap, transferCategoryMap