code refactor

This commit is contained in:
MaysWind
2025-06-19 22:40:32 +08:00
parent 5dc0e925c1
commit e9c175d2af
12 changed files with 611 additions and 611 deletions
+25 -25
View File
@@ -41,49 +41,49 @@ const (
// beancountData defines the structure of beancount data // beancountData defines the structure of beancount data
type beancountData struct { type beancountData struct {
accounts map[string]*beancountAccount Accounts map[string]*beancountAccount
transactions []*beancountTransactionEntry Transactions []*beancountTransactionEntry
} }
// beancountAccount defines the structure of beancount account // beancountAccount defines the structure of beancount account
type beancountAccount struct { type beancountAccount struct {
name string Name string
accountType beancountAccountType AccountType beancountAccountType
openDate string OpenDate string
closeDate string CloseDate string
} }
// beancountTransactionEntry defines the structure of beancount transaction entry // beancountTransactionEntry defines the structure of beancount transaction entry
type beancountTransactionEntry struct { type beancountTransactionEntry struct {
date string Date string
directive beancountDirective Directive beancountDirective
payee string Payee string
narration string Narration string
postings []*beancountPosting Postings []*beancountPosting
tags []string Tags []string
links []string Links []string
metadata map[string]string Metadata map[string]string
} }
// beancountPosting defines the structure of beancount transaction posting // beancountPosting defines the structure of beancount transaction posting
type beancountPosting struct { type beancountPosting struct {
account string Account string
amount string Amount string
originalAmount string OriginalAmount string
commodity string Commodity string
totalCost string TotalCost string
totalCostCommodity string TotalCostCommodity string
price string Price string
priceCommodity string PriceCommodity string
metadata map[string]string Metadata map[string]string
} }
func (a *beancountAccount) isOpeningBalanceEquityAccount() bool { func (a *beancountAccount) isOpeningBalanceEquityAccount() bool {
if a.accountType != beancountEquityAccountType { if a.AccountType != beancountEquityAccountType {
return false return false
} }
nameItems := strings.Split(a.name, string(beancountMetadataKeySuffix)) nameItems := strings.Split(a.Name, string(beancountMetadataKeySuffix))
if len(nameItems) != 2 { if len(nameItems) != 2 {
return false return false
@@ -49,8 +49,8 @@ func (r *beancountDataReader) read(ctx core.Context) (*beancountData, error) {
} }
data := &beancountData{ data := &beancountData{
accounts: make(map[string]*beancountAccount), Accounts: make(map[string]*beancountAccount),
transactions: make([]*beancountTransactionEntry, 0), Transactions: make([]*beancountTransactionEntry, 0),
} }
var err error var err error
@@ -100,7 +100,7 @@ func (r *beancountDataReader) read(ctx core.Context) (*beancountData, error) {
if ('A' <= actualFirstItem[0] && actualFirstItem[0] <= 'Z') || actualFirstItem[0] == '!' { // transaction posting if ('A' <= actualFirstItem[0] && actualFirstItem[0] <= 'Z') || actualFirstItem[0] == '!' { // transaction posting
if currentTransactionEntry != nil && currentTransactionPosting != nil { if currentTransactionEntry != nil && currentTransactionPosting != nil {
currentTransactionEntry.postings = append(currentTransactionEntry.postings, currentTransactionPosting) currentTransactionEntry.Postings = append(currentTransactionEntry.Postings, currentTransactionPosting)
currentTransactionPosting = nil currentTransactionPosting = nil
} }
@@ -120,12 +120,12 @@ func (r *beancountDataReader) read(ctx core.Context) (*beancountData, error) {
metadataValue := metadata[1] metadataValue := metadata[1]
if currentTransactionPosting != nil { if currentTransactionPosting != nil {
if _, exists := currentTransactionPosting.metadata[metadataKey]; !exists { if _, exists := currentTransactionPosting.Metadata[metadataKey]; !exists {
currentTransactionPosting.metadata[metadataKey] = metadataValue currentTransactionPosting.Metadata[metadataKey] = metadataValue
} }
} else if currentTransactionEntry != nil { } else if currentTransactionEntry != nil {
if _, exists := currentTransactionEntry.metadata[metadataKey]; !exists { if _, exists := currentTransactionEntry.Metadata[metadataKey]; !exists {
currentTransactionEntry.metadata[metadataKey] = metadataValue currentTransactionEntry.Metadata[metadataKey] = metadataValue
} }
} }
} else { } else {
@@ -172,11 +172,11 @@ func (r *beancountDataReader) read(ctx core.Context) (*beancountData, error) {
if currentTransactionEntry != nil { if currentTransactionEntry != nil {
if currentTransactionPosting != nil { if currentTransactionPosting != nil {
currentTransactionEntry.postings = append(currentTransactionEntry.postings, currentTransactionPosting) currentTransactionEntry.Postings = append(currentTransactionEntry.Postings, currentTransactionPosting)
currentTransactionPosting = nil currentTransactionPosting = nil
} }
data.transactions = append(data.transactions, currentTransactionEntry) data.Transactions = append(data.Transactions, currentTransactionEntry)
currentTransactionEntry = nil currentTransactionEntry = nil
} }
@@ -186,11 +186,11 @@ func (r *beancountDataReader) read(ctx core.Context) (*beancountData, error) {
func (r *beancountDataReader) updateCurrentState(data *beancountData, currentTransactionEntry *beancountTransactionEntry, currentTransactionPosting *beancountPosting) (*beancountTransactionEntry, *beancountPosting) { func (r *beancountDataReader) updateCurrentState(data *beancountData, currentTransactionEntry *beancountTransactionEntry, currentTransactionPosting *beancountPosting) (*beancountTransactionEntry, *beancountPosting) {
if currentTransactionEntry != nil { if currentTransactionEntry != nil {
if currentTransactionPosting != nil { if currentTransactionPosting != nil {
currentTransactionEntry.postings = append(currentTransactionEntry.postings, currentTransactionPosting) currentTransactionEntry.Postings = append(currentTransactionEntry.Postings, currentTransactionPosting)
currentTransactionPosting = nil currentTransactionPosting = nil
} }
data.transactions = append(data.transactions, currentTransactionEntry) data.Transactions = append(data.Transactions, currentTransactionEntry)
currentTransactionEntry = nil currentTransactionEntry = nil
currentTransactionPosting = nil currentTransactionPosting = nil
} }
@@ -277,7 +277,7 @@ func (r *beancountDataReader) readAccountLine(ctx core.Context, lineIndex int, i
var err error var err error
accountName := r.getNotEmptyItemByIndex(items, 2) accountName := r.getNotEmptyItemByIndex(items, 2)
account, exists := data.accounts[accountName] account, exists := data.Accounts[accountName]
if !exists { if !exists {
account, err = r.createAccount(ctx, data, accountName) account, err = r.createAccount(ctx, data, accountName)
@@ -288,10 +288,10 @@ func (r *beancountDataReader) readAccountLine(ctx core.Context, lineIndex int, i
} }
if directive == beancountDirectiveOpen { if directive == beancountDirectiveOpen {
account.openDate = date account.OpenDate = date
return account, nil return account, nil
} else if directive == beancountDirectiveClose { } else if directive == beancountDirectiveClose {
account.closeDate = date account.CloseDate = date
return account, nil return account, nil
} else { } else {
log.Warnf(ctx, "[beancount_data_reader.parseAccount] cannot parse account line#%d \"%s\", because directive is invalid", lineIndex, strings.Join(items, " ")) log.Warnf(ctx, "[beancount_data_reader.parseAccount] cannot parse account line#%d \"%s\", because directive is invalid", lineIndex, strings.Join(items, " "))
@@ -301,8 +301,8 @@ func (r *beancountDataReader) readAccountLine(ctx core.Context, lineIndex int, i
func (r *beancountDataReader) createAccount(ctx core.Context, data *beancountData, accountName string) (*beancountAccount, error) { func (r *beancountDataReader) createAccount(ctx core.Context, data *beancountData, accountName string) (*beancountAccount, error) {
account := &beancountAccount{ account := &beancountAccount{
name: accountName, Name: accountName,
accountType: beancountUnknownAccountType, AccountType: beancountUnknownAccountType,
} }
accountNameItems := strings.Split(accountName, beancountAccountNameItemsSeparator) accountNameItems := strings.Split(accountName, beancountAccountNameItemsSeparator)
@@ -311,31 +311,31 @@ func (r *beancountDataReader) createAccount(ctx core.Context, data *beancountDat
accountType, exists := r.accountTypeNameMap[accountNameItems[0]] accountType, exists := r.accountTypeNameMap[accountNameItems[0]]
if exists { if exists {
account.accountType = accountType account.AccountType = accountType
} else { } else {
log.Warnf(ctx, "[beancount_data_reader.createAccount] cannot parse account \"%s\", because account type \"%s\" is invalid", accountName, accountNameItems[0]) log.Warnf(ctx, "[beancount_data_reader.createAccount] cannot parse account \"%s\", because account type \"%s\" is invalid", accountName, accountNameItems[0])
return nil, errs.ErrInvalidBeancountFile return nil, errs.ErrInvalidBeancountFile
} }
} }
data.accounts[accountName] = account data.Accounts[accountName] = account
return account, nil return account, nil
} }
func (r *beancountDataReader) readTransactionLine(ctx core.Context, lineIndex int, items []string, date string, directive beancountDirective, tags []string) *beancountTransactionEntry { func (r *beancountDataReader) readTransactionLine(ctx core.Context, lineIndex int, items []string, date string, directive beancountDirective, tags []string) *beancountTransactionEntry {
transactionEntry := &beancountTransactionEntry{ transactionEntry := &beancountTransactionEntry{
date: date, Date: date,
directive: directive, Directive: directive,
tags: make([]string, 0), Tags: make([]string, 0),
links: make([]string, 0), Links: make([]string, 0),
metadata: make(map[string]string), Metadata: make(map[string]string),
} }
transactionEntry.tags = append(transactionEntry.tags, tags...) transactionEntry.Tags = append(transactionEntry.Tags, tags...)
allTags := make(map[string]bool, len(transactionEntry.tags)) allTags := make(map[string]bool, len(transactionEntry.Tags))
for _, tag := range transactionEntry.tags { for _, tag := range transactionEntry.Tags {
allTags[tag] = true allTags[tag] = true
} }
@@ -363,7 +363,7 @@ func (r *beancountDataReader) readTransactionLine(ctx core.Context, lineIndex in
tagName := item[1:] tagName := item[1:]
if _, exists := allTags[tagName]; !exists { if _, exists := allTags[tagName]; !exists {
transactionEntry.tags = append(transactionEntry.tags, tagName) transactionEntry.Tags = append(transactionEntry.Tags, tagName)
allTags[tagName] = true allTags[tagName] = true
} }
@@ -371,7 +371,7 @@ func (r *beancountDataReader) readTransactionLine(ctx core.Context, lineIndex in
payeeNarrationLastIndex = i - 1 payeeNarrationLastIndex = i - 1
} }
} else if item[0] == beancountLinkPrefix { // [ˆlink] } else if item[0] == beancountLinkPrefix { // [ˆlink]
transactionEntry.links = append(transactionEntry.links, item[1:]) transactionEntry.Links = append(transactionEntry.Links, item[1:])
if i-1 < payeeNarrationLastIndex { if i-1 < payeeNarrationLastIndex {
payeeNarrationLastIndex = i - 1 payeeNarrationLastIndex = i - 1
@@ -380,10 +380,10 @@ func (r *beancountDataReader) readTransactionLine(ctx core.Context, lineIndex in
} }
if payeeNarrationLastIndex-payeeNarrationFirstIndex >= 1 { if payeeNarrationLastIndex-payeeNarrationFirstIndex >= 1 {
transactionEntry.payee = items[payeeNarrationFirstIndex] transactionEntry.Payee = items[payeeNarrationFirstIndex]
transactionEntry.narration = items[payeeNarrationFirstIndex+1] transactionEntry.Narration = items[payeeNarrationFirstIndex+1]
} else if payeeNarrationLastIndex-payeeNarrationFirstIndex >= 0 { } else if payeeNarrationLastIndex-payeeNarrationFirstIndex >= 0 {
transactionEntry.narration = items[payeeNarrationFirstIndex] transactionEntry.Narration = items[payeeNarrationFirstIndex]
} }
return transactionEntry return transactionEntry
@@ -410,36 +410,36 @@ func (r *beancountDataReader) readTransactionPostingLine(ctx core.Context, lineI
} }
transactionPositing := &beancountPosting{ transactionPositing := &beancountPosting{
account: accountName, Account: accountName,
metadata: make(map[string]string), Metadata: make(map[string]string),
} }
amountActualLastIndex := -1 amountActualLastIndex := -1
transactionPositing.originalAmount, amountActualLastIndex = r.getOriginalAmountAndLastIndexFromIndex(items, accountNameActualIndex+1) transactionPositing.OriginalAmount, amountActualLastIndex = r.getOriginalAmountAndLastIndexFromIndex(items, accountNameActualIndex+1)
if transactionPositing.originalAmount == "" || amountActualLastIndex < 0 { if transactionPositing.OriginalAmount == "" || amountActualLastIndex < 0 {
log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot parse transaction posting line#%d \"%s\", because missing amount", lineIndex, strings.Join(items, " ")) log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot parse transaction posting line#%d \"%s\", because missing amount", lineIndex, strings.Join(items, " "))
return nil, errs.ErrAmountInvalid return nil, errs.ErrAmountInvalid
} }
finalAmount, err := evaluateBeancountAmountExpression(ctx, transactionPositing.originalAmount) finalAmount, err := evaluateBeancountAmountExpression(ctx, transactionPositing.OriginalAmount)
if err != nil { if err != nil {
log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot evaluate amount expression in line#%d \"%s\", because %s", lineIndex, strings.Join(items, " "), err.Error()) log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot evaluate amount expression in line#%d \"%s\", because %s", lineIndex, strings.Join(items, " "), err.Error())
return nil, errs.ErrAmountInvalid return nil, errs.ErrAmountInvalid
} else { } else {
transactionPositing.amount = finalAmount transactionPositing.Amount = finalAmount
} }
commodityActualIndex := -1 commodityActualIndex := -1
transactionPositing.commodity, commodityActualIndex = r.getNotEmptyItemAndIndexFromIndex(items, amountActualLastIndex+1) transactionPositing.Commodity, commodityActualIndex = r.getNotEmptyItemAndIndexFromIndex(items, amountActualLastIndex+1)
if transactionPositing.commodity == "" || commodityActualIndex < 0 { if transactionPositing.Commodity == "" || commodityActualIndex < 0 {
log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot parse transaction posting line#%d \"%s\", because missing commodity", lineIndex, strings.Join(items, " ")) log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot parse transaction posting line#%d \"%s\", because missing commodity", lineIndex, strings.Join(items, " "))
return nil, errs.ErrInvalidBeancountFile return nil, errs.ErrInvalidBeancountFile
} }
if strings.ToUpper(transactionPositing.commodity) != transactionPositing.commodity { // The syntax for a currency is a word all in capital letters if strings.ToUpper(transactionPositing.Commodity) != transactionPositing.Commodity { // The syntax for a currency is a word all in capital letters
log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot parse transaction posting line#%d \"%s\", because commodity name is not capital letters", lineIndex, strings.Join(items, " ")) log.Warnf(ctx, "[beancount_data_reader.readTransactionPostingLine] cannot parse transaction posting line#%d \"%s\", because commodity name is not capital letters", lineIndex, strings.Join(items, " "))
return nil, errs.ErrInvalidBeancountFile return nil, errs.ErrInvalidBeancountFile
} }
@@ -461,13 +461,13 @@ func (r *beancountDataReader) readTransactionPostingLine(ctx core.Context, lineI
totalCost, totalCostActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, i+1) totalCost, totalCostActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, i+1)
if totalCostActualIndex > 0 { if totalCostActualIndex > 0 {
transactionPositing.totalCost = totalCost transactionPositing.TotalCost = totalCost
i = totalCostActualIndex i = totalCostActualIndex
totalCostCommodity, totalCostCommodityActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, totalCostActualIndex+1) totalCostCommodity, totalCostCommodityActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, totalCostActualIndex+1)
if totalCostCommodityActualIndex > 0 { if totalCostCommodityActualIndex > 0 {
transactionPositing.totalCostCommodity = totalCostCommodity transactionPositing.TotalCostCommodity = totalCostCommodity
i = totalCostCommodityActualIndex i = totalCostCommodityActualIndex
} }
} }
@@ -475,13 +475,13 @@ func (r *beancountDataReader) readTransactionPostingLine(ctx core.Context, lineI
price, priceActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, i+1) price, priceActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, i+1)
if priceActualIndex > 0 { if priceActualIndex > 0 {
transactionPositing.price = price transactionPositing.Price = price
i = priceActualIndex i = priceActualIndex
priceCommodity, priceCommodityActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, priceActualIndex+1) priceCommodity, priceCommodityActualIndex := r.getNotEmptyItemAndIndexFromIndex(items, priceActualIndex+1)
if priceCommodityActualIndex > 0 { if priceCommodityActualIndex > 0 {
transactionPositing.priceCommodity = priceCommodity transactionPositing.PriceCommodity = priceCommodity
i = priceCommodityActualIndex i = priceCommodityActualIndex
} }
} }
@@ -489,11 +489,11 @@ func (r *beancountDataReader) readTransactionPostingLine(ctx core.Context, lineI
} }
} }
if transactionPositing.account != "" { if transactionPositing.Account != "" {
_, exists := data.accounts[transactionPositing.account] _, exists := data.Accounts[transactionPositing.Account]
if !exists { if !exists {
_, err := r.createAccount(ctx, data, transactionPositing.account) _, err := r.createAccount(ctx, data, transactionPositing.Account)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -41,56 +41,56 @@ func TestBeancountDataReaderRead(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 5, len(actualData.accounts)) assert.Equal(t, 5, len(actualData.Accounts))
assert.Equal(t, "AssetsAccount:TestAccount", actualData.accounts["AssetsAccount:TestAccount"].name) assert.Equal(t, "AssetsAccount:TestAccount", actualData.Accounts["AssetsAccount:TestAccount"].Name)
assert.Equal(t, beancountAssetsAccountType, actualData.accounts["AssetsAccount:TestAccount"].accountType) assert.Equal(t, beancountAssetsAccountType, actualData.Accounts["AssetsAccount:TestAccount"].AccountType)
assert.Equal(t, "2024-01-01", actualData.accounts["AssetsAccount:TestAccount"].openDate) assert.Equal(t, "2024-01-01", actualData.Accounts["AssetsAccount:TestAccount"].OpenDate)
assert.Equal(t, "2024-01-07", actualData.accounts["AssetsAccount:TestAccount"].closeDate) assert.Equal(t, "2024-01-07", actualData.Accounts["AssetsAccount:TestAccount"].CloseDate)
assert.Equal(t, "LiabilitiesAccount:TestAccount2", actualData.accounts["LiabilitiesAccount:TestAccount2"].name) assert.Equal(t, "LiabilitiesAccount:TestAccount2", actualData.Accounts["LiabilitiesAccount:TestAccount2"].Name)
assert.Equal(t, beancountLiabilitiesAccountType, actualData.accounts["LiabilitiesAccount:TestAccount2"].accountType) assert.Equal(t, beancountLiabilitiesAccountType, actualData.Accounts["LiabilitiesAccount:TestAccount2"].AccountType)
assert.Equal(t, "2024-01-02", actualData.accounts["LiabilitiesAccount:TestAccount2"].openDate) assert.Equal(t, "2024-01-02", actualData.Accounts["LiabilitiesAccount:TestAccount2"].OpenDate)
assert.Equal(t, 2, len(actualData.transactions)) assert.Equal(t, 2, len(actualData.Transactions))
assert.Equal(t, "2024-01-05", actualData.transactions[0].date) assert.Equal(t, "2024-01-05", actualData.Transactions[0].Date)
assert.Equal(t, "Payee Name", actualData.transactions[0].payee) assert.Equal(t, "Payee Name", actualData.Transactions[0].Payee)
assert.Equal(t, "Foo Bar", actualData.transactions[0].narration) assert.Equal(t, "Foo Bar", actualData.Transactions[0].Narration)
assert.Equal(t, 2, len(actualData.transactions[0].postings)) assert.Equal(t, 2, len(actualData.Transactions[0].Postings))
assert.Equal(t, "IncomeAccount:TestCategory", actualData.transactions[0].postings[0].account) assert.Equal(t, "IncomeAccount:TestCategory", actualData.Transactions[0].Postings[0].Account)
assert.Equal(t, "-123.45", actualData.transactions[0].postings[0].amount) assert.Equal(t, "-123.45", actualData.Transactions[0].Postings[0].Amount)
assert.Equal(t, "CNY", actualData.transactions[0].postings[0].commodity) assert.Equal(t, "CNY", actualData.Transactions[0].Postings[0].Commodity)
assert.Equal(t, "AssetsAccount:TestAccount", actualData.transactions[0].postings[1].account) assert.Equal(t, "AssetsAccount:TestAccount", actualData.Transactions[0].Postings[1].Account)
assert.Equal(t, "123.45", actualData.transactions[0].postings[1].amount) assert.Equal(t, "123.45", actualData.Transactions[0].Postings[1].Amount)
assert.Equal(t, "CNY", actualData.transactions[0].postings[1].commodity) assert.Equal(t, "CNY", actualData.Transactions[0].Postings[1].Commodity)
assert.Equal(t, 4, len(actualData.transactions[0].tags)) assert.Equal(t, 4, len(actualData.Transactions[0].Tags))
assert.Equal(t, actualData.transactions[0].tags[0], "tag1") assert.Equal(t, actualData.Transactions[0].Tags[0], "tag1")
assert.Equal(t, actualData.transactions[0].tags[1], "tag2") assert.Equal(t, actualData.Transactions[0].Tags[1], "tag2")
assert.Equal(t, actualData.transactions[0].tags[2], "tag3") assert.Equal(t, actualData.Transactions[0].Tags[2], "tag3")
assert.Equal(t, actualData.transactions[0].tags[3], "tag4") assert.Equal(t, actualData.Transactions[0].Tags[3], "tag4")
assert.Equal(t, 1, len(actualData.transactions[0].links)) assert.Equal(t, 1, len(actualData.Transactions[0].Links))
assert.Equal(t, actualData.transactions[0].links[0], "test-link") assert.Equal(t, actualData.Transactions[0].Links[0], "test-link")
assert.Equal(t, "2024-01-06", actualData.transactions[1].date) assert.Equal(t, "2024-01-06", actualData.Transactions[1].Date)
assert.Equal(t, "", actualData.transactions[1].payee) assert.Equal(t, "", actualData.Transactions[1].Payee)
assert.Equal(t, "test\n#test2", actualData.transactions[1].narration) assert.Equal(t, "test\n#test2", actualData.Transactions[1].Narration)
assert.Equal(t, 2, len(actualData.transactions[1].postings)) assert.Equal(t, 2, len(actualData.Transactions[1].Postings))
assert.Equal(t, "LiabilitiesAccount:TestAccount2", actualData.transactions[1].postings[0].account) assert.Equal(t, "LiabilitiesAccount:TestAccount2", actualData.Transactions[1].Postings[0].Account)
assert.Equal(t, "-0.12", actualData.transactions[1].postings[0].amount) assert.Equal(t, "-0.12", actualData.Transactions[1].Postings[0].Amount)
assert.Equal(t, "USD", actualData.transactions[1].postings[0].commodity) assert.Equal(t, "USD", actualData.Transactions[1].Postings[0].Commodity)
assert.Equal(t, "ExpensesAccount:TestCategory2", actualData.transactions[1].postings[1].account) assert.Equal(t, "ExpensesAccount:TestCategory2", actualData.Transactions[1].Postings[1].Account)
assert.Equal(t, "0.12", actualData.transactions[1].postings[1].amount) assert.Equal(t, "0.12", actualData.Transactions[1].Postings[1].Amount)
assert.Equal(t, "USD", actualData.transactions[1].postings[1].commodity) assert.Equal(t, "USD", actualData.Transactions[1].Postings[1].Commodity)
assert.Equal(t, 3, len(actualData.transactions[1].tags)) assert.Equal(t, 3, len(actualData.Transactions[1].Tags))
assert.Equal(t, actualData.transactions[1].tags[0], "tag2") assert.Equal(t, actualData.Transactions[1].Tags[0], "tag2")
assert.Equal(t, actualData.transactions[1].tags[1], "tag5") assert.Equal(t, actualData.Transactions[1].Tags[1], "tag5")
assert.Equal(t, actualData.transactions[1].tags[2], "tag6") assert.Equal(t, actualData.Transactions[1].Tags[2], "tag6")
assert.Equal(t, 1, len(actualData.transactions[1].links)) assert.Equal(t, 1, len(actualData.Transactions[1].Links))
assert.Equal(t, actualData.transactions[1].links[0], "test-link2") assert.Equal(t, actualData.Transactions[1].Links[0], "test-link2")
} }
func TestBeancountDataReaderRead_EmptyContent(t *testing.T) { func TestBeancountDataReaderRead_EmptyContent(t *testing.T) {
@@ -147,17 +147,17 @@ func TestBeancountDataReaderReadAndSetOption_AccountTypeName(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3, len(actualData.accounts)) assert.Equal(t, 3, len(actualData.Accounts))
assert.Equal(t, "A:TestAccount", actualData.accounts["A:TestAccount"].name) assert.Equal(t, "A:TestAccount", actualData.Accounts["A:TestAccount"].Name)
assert.Equal(t, beancountAssetsAccountType, actualData.accounts["A:TestAccount"].accountType) assert.Equal(t, beancountAssetsAccountType, actualData.Accounts["A:TestAccount"].AccountType)
assert.Equal(t, "L:TestAccount2", actualData.accounts["L:TestAccount2"].name) assert.Equal(t, "L:TestAccount2", actualData.Accounts["L:TestAccount2"].Name)
assert.Equal(t, beancountLiabilitiesAccountType, actualData.accounts["L:TestAccount2"].accountType) assert.Equal(t, beancountLiabilitiesAccountType, actualData.Accounts["L:TestAccount2"].AccountType)
assert.Equal(t, "E:Opening-Balances", actualData.accounts["E:Opening-Balances"].name) assert.Equal(t, "E:Opening-Balances", actualData.Accounts["E:Opening-Balances"].Name)
assert.Equal(t, beancountEquityAccountType, actualData.accounts["E:Opening-Balances"].accountType) assert.Equal(t, beancountEquityAccountType, actualData.Accounts["E:Opening-Balances"].AccountType)
assert.True(t, actualData.accounts["E:Opening-Balances"].isOpeningBalanceEquityAccount()) assert.True(t, actualData.Accounts["E:Opening-Balances"].isOpeningBalanceEquityAccount())
} }
func TestBeancountDataReaderReadAndSetOption_InvalidLineOrUnsupportedOption(t *testing.T) { func TestBeancountDataReaderReadAndSetOption_InvalidLineOrUnsupportedOption(t *testing.T) {
@@ -203,31 +203,31 @@ func TestBeancountDataReaderReadAndSetTags(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 5, len(actualData.transactions)) assert.Equal(t, 5, len(actualData.Transactions))
assert.Equal(t, 4, len(actualData.transactions[0].tags)) assert.Equal(t, 4, len(actualData.Transactions[0].Tags))
assert.Equal(t, actualData.transactions[0].tags[0], "tag1") assert.Equal(t, actualData.Transactions[0].Tags[0], "tag1")
assert.Equal(t, actualData.transactions[0].tags[1], "tag2") assert.Equal(t, actualData.Transactions[0].Tags[1], "tag2")
assert.Equal(t, actualData.transactions[0].tags[2], "tag3") assert.Equal(t, actualData.Transactions[0].Tags[2], "tag3")
assert.Equal(t, actualData.transactions[0].tags[3], "tag4") assert.Equal(t, actualData.Transactions[0].Tags[3], "tag4")
assert.Equal(t, 2, len(actualData.transactions[1].tags)) assert.Equal(t, 2, len(actualData.Transactions[1].Tags))
assert.Equal(t, actualData.transactions[1].tags[0], "tag5") assert.Equal(t, actualData.Transactions[1].Tags[0], "tag5")
assert.Equal(t, actualData.transactions[1].tags[1], "tag6") assert.Equal(t, actualData.Transactions[1].Tags[1], "tag6")
assert.Equal(t, 2, len(actualData.transactions[2].tags)) assert.Equal(t, 2, len(actualData.Transactions[2].Tags))
assert.Equal(t, actualData.transactions[2].tags[0], "tag5") assert.Equal(t, actualData.Transactions[2].Tags[0], "tag5")
assert.Equal(t, actualData.transactions[2].tags[1], "tag6") assert.Equal(t, actualData.Transactions[2].Tags[1], "tag6")
assert.Equal(t, 3, len(actualData.transactions[3].tags)) assert.Equal(t, 3, len(actualData.Transactions[3].Tags))
assert.Equal(t, actualData.transactions[3].tags[0], "tag3") assert.Equal(t, actualData.Transactions[3].Tags[0], "tag3")
assert.Equal(t, actualData.transactions[3].tags[1], "tag6") assert.Equal(t, actualData.Transactions[3].Tags[1], "tag6")
assert.Equal(t, actualData.transactions[3].tags[2], "tag5") assert.Equal(t, actualData.Transactions[3].Tags[2], "tag5")
assert.Equal(t, 3, len(actualData.transactions[4].tags)) assert.Equal(t, 3, len(actualData.Transactions[4].Tags))
assert.Equal(t, actualData.transactions[4].tags[0], "tag3") assert.Equal(t, actualData.Transactions[4].Tags[0], "tag3")
assert.Equal(t, actualData.transactions[4].tags[1], "tag6") assert.Equal(t, actualData.Transactions[4].Tags[1], "tag6")
assert.Equal(t, actualData.transactions[4].tags[2], "tag5") assert.Equal(t, actualData.Transactions[4].Tags[2], "tag5")
} }
func TestBeancountDataReaderReadAccountLine_InvalidLine(t *testing.T) { func TestBeancountDataReaderReadAccountLine_InvalidLine(t *testing.T) {
@@ -238,7 +238,7 @@ func TestBeancountDataReaderReadAccountLine_InvalidLine(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 0, len(actualData.accounts)) assert.Equal(t, 0, len(actualData.Accounts))
} }
func TestBeancountDataReaderReadAccountLine_InvalidAccountType(t *testing.T) { func TestBeancountDataReaderReadAccountLine_InvalidAccountType(t *testing.T) {
@@ -274,44 +274,44 @@ func TestBeancountDataReaderReadTransactionLine(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 6, len(actualData.transactions)) assert.Equal(t, 6, len(actualData.Transactions))
assert.Equal(t, "2024-01-01", actualData.transactions[0].date) assert.Equal(t, "2024-01-01", actualData.Transactions[0].Date)
assert.Equal(t, beancountDirectiveCompletedTransaction, actualData.transactions[0].directive) assert.Equal(t, beancountDirectiveCompletedTransaction, actualData.Transactions[0].Directive)
assert.Equal(t, "", actualData.transactions[0].payee) assert.Equal(t, "", actualData.Transactions[0].Payee)
assert.Equal(t, "", actualData.transactions[0].narration) assert.Equal(t, "", actualData.Transactions[0].Narration)
assert.Equal(t, "2024-01-02", actualData.transactions[1].date) assert.Equal(t, "2024-01-02", actualData.Transactions[1].Date)
assert.Equal(t, beancountDirectiveCompletedTransaction, actualData.transactions[1].directive) assert.Equal(t, beancountDirectiveCompletedTransaction, actualData.Transactions[1].Directive)
assert.Equal(t, "", actualData.transactions[1].payee) assert.Equal(t, "", actualData.Transactions[1].Payee)
assert.Equal(t, "test\ttest2\ntest3", actualData.transactions[1].narration) assert.Equal(t, "test\ttest2\ntest3", actualData.Transactions[1].Narration)
assert.Equal(t, "2024-01-03", actualData.transactions[2].date) assert.Equal(t, "2024-01-03", actualData.Transactions[2].Date)
assert.Equal(t, beancountDirectiveInCompleteTransaction, actualData.transactions[2].directive) assert.Equal(t, beancountDirectiveInCompleteTransaction, actualData.Transactions[2].Directive)
assert.Equal(t, "test", actualData.transactions[2].payee) assert.Equal(t, "test", actualData.Transactions[2].Payee)
assert.Equal(t, "test2", actualData.transactions[2].narration) assert.Equal(t, "test2", actualData.Transactions[2].Narration)
assert.Equal(t, "2024-01-04", actualData.transactions[3].date) assert.Equal(t, "2024-01-04", actualData.Transactions[3].Date)
assert.Equal(t, beancountDirectivePaddingTransaction, actualData.transactions[3].directive) assert.Equal(t, beancountDirectivePaddingTransaction, actualData.Transactions[3].Directive)
assert.Equal(t, "", actualData.transactions[3].payee) assert.Equal(t, "", actualData.Transactions[3].Payee)
assert.Equal(t, "test", actualData.transactions[3].narration) assert.Equal(t, "test", actualData.Transactions[3].Narration)
assert.Equal(t, 2, len(actualData.transactions[3].tags)) assert.Equal(t, 2, len(actualData.Transactions[3].Tags))
assert.Equal(t, actualData.transactions[3].tags[0], "tag") assert.Equal(t, actualData.Transactions[3].Tags[0], "tag")
assert.Equal(t, actualData.transactions[3].tags[1], "tag2") assert.Equal(t, actualData.Transactions[3].Tags[1], "tag2")
assert.Equal(t, "2024-01-05", actualData.transactions[4].date) assert.Equal(t, "2024-01-05", actualData.Transactions[4].Date)
assert.Equal(t, beancountDirectiveTransaction, actualData.transactions[4].directive) assert.Equal(t, beancountDirectiveTransaction, actualData.Transactions[4].Directive)
assert.Equal(t, "", actualData.transactions[4].payee) assert.Equal(t, "", actualData.Transactions[4].Payee)
assert.Equal(t, "test", actualData.transactions[4].narration) assert.Equal(t, "test", actualData.Transactions[4].Narration)
assert.Equal(t, 1, len(actualData.transactions[4].links)) assert.Equal(t, 1, len(actualData.Transactions[4].Links))
assert.Equal(t, actualData.transactions[4].links[0], "scheme://path/to/test/link") assert.Equal(t, actualData.Transactions[4].Links[0], "scheme://path/to/test/link")
assert.Equal(t, "2024-01-06", actualData.transactions[5].date) assert.Equal(t, "2024-01-06", actualData.Transactions[5].Date)
assert.Equal(t, beancountDirectiveTransaction, actualData.transactions[5].directive) assert.Equal(t, beancountDirectiveTransaction, actualData.Transactions[5].Directive)
assert.Equal(t, "", actualData.transactions[5].payee) assert.Equal(t, "", actualData.Transactions[5].Payee)
assert.Equal(t, "", actualData.transactions[5].narration) assert.Equal(t, "", actualData.Transactions[5].Narration)
} }
func TestBeancountDataReaderReadTransactionPostingLine(t *testing.T) { func TestBeancountDataReaderReadTransactionPostingLine(t *testing.T) {
@@ -331,39 +331,39 @@ func TestBeancountDataReaderReadTransactionPostingLine(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(actualData.transactions)) assert.Equal(t, 2, len(actualData.Transactions))
assert.Equal(t, "2024-01-01", actualData.transactions[0].date) assert.Equal(t, "2024-01-01", actualData.Transactions[0].Date)
assert.Equal(t, 2, len(actualData.transactions[0].postings)) assert.Equal(t, 2, len(actualData.Transactions[0].Postings))
assert.Equal(t, "Income:TestCategory", actualData.transactions[0].postings[0].account) assert.Equal(t, "Income:TestCategory", actualData.Transactions[0].Postings[0].Account)
assert.Equal(t, "-123.45", actualData.transactions[0].postings[0].amount) assert.Equal(t, "-123.45", actualData.Transactions[0].Postings[0].Amount)
assert.Equal(t, "CNY", actualData.transactions[0].postings[0].commodity) assert.Equal(t, "CNY", actualData.Transactions[0].Postings[0].Commodity)
assert.Equal(t, "Assets:TestAccount", actualData.transactions[0].postings[1].account) assert.Equal(t, "Assets:TestAccount", actualData.Transactions[0].Postings[1].Account)
assert.Equal(t, "123.45", actualData.transactions[0].postings[1].amount) assert.Equal(t, "123.45", actualData.Transactions[0].Postings[1].Amount)
assert.Equal(t, "CNY", actualData.transactions[0].postings[1].commodity) assert.Equal(t, "CNY", actualData.Transactions[0].Postings[1].Commodity)
assert.Equal(t, "2024-01-02", actualData.transactions[1].date) assert.Equal(t, "2024-01-02", actualData.Transactions[1].Date)
assert.Equal(t, 4, len(actualData.transactions[1].postings)) assert.Equal(t, 4, len(actualData.Transactions[1].Postings))
assert.Equal(t, "Liabilities:TestAccount2", actualData.transactions[1].postings[0].account) assert.Equal(t, "Liabilities:TestAccount2", actualData.Transactions[1].Postings[0].Account)
assert.Equal(t, "-0.23", actualData.transactions[1].postings[0].amount) assert.Equal(t, "-0.23", actualData.Transactions[1].Postings[0].Amount)
assert.Equal(t, "USD", actualData.transactions[1].postings[0].commodity) assert.Equal(t, "USD", actualData.Transactions[1].Postings[0].Commodity)
assert.Equal(t, "Expenses:TestCategory2", actualData.transactions[1].postings[1].account) assert.Equal(t, "Expenses:TestCategory2", actualData.Transactions[1].Postings[1].Account)
assert.Equal(t, "0.12", actualData.transactions[1].postings[1].amount) assert.Equal(t, "0.12", actualData.Transactions[1].Postings[1].Amount)
assert.Equal(t, "USD", actualData.transactions[1].postings[1].commodity) assert.Equal(t, "USD", actualData.Transactions[1].Postings[1].Commodity)
assert.Equal(t, "0.84", actualData.transactions[1].postings[1].totalCost) assert.Equal(t, "0.84", actualData.Transactions[1].Postings[1].TotalCost)
assert.Equal(t, "CNY", actualData.transactions[1].postings[1].totalCostCommodity) assert.Equal(t, "CNY", actualData.Transactions[1].Postings[1].TotalCostCommodity)
assert.Equal(t, "Expenses:TestCategory3", actualData.transactions[1].postings[2].account) assert.Equal(t, "Expenses:TestCategory3", actualData.Transactions[1].Postings[2].Account)
assert.Equal(t, "0.11", actualData.transactions[1].postings[2].amount) assert.Equal(t, "0.11", actualData.Transactions[1].Postings[2].Amount)
assert.Equal(t, "USD", actualData.transactions[1].postings[2].commodity) assert.Equal(t, "USD", actualData.Transactions[1].Postings[2].Commodity)
assert.Equal(t, "7.12", actualData.transactions[1].postings[2].price) assert.Equal(t, "7.12", actualData.Transactions[1].Postings[2].Price)
assert.Equal(t, "CNY", actualData.transactions[1].postings[2].priceCommodity) assert.Equal(t, "CNY", actualData.Transactions[1].Postings[2].PriceCommodity)
assert.Equal(t, "0.00", actualData.transactions[1].postings[3].amount) assert.Equal(t, "0.00", actualData.Transactions[1].Postings[3].Amount)
assert.Equal(t, "USD", actualData.transactions[1].postings[3].commodity) assert.Equal(t, "USD", actualData.Transactions[1].Postings[3].Commodity)
} }
func TestBeancountDataReaderReadTransactionPostingLine_AmountExpression(t *testing.T) { func TestBeancountDataReaderReadTransactionPostingLine_AmountExpression(t *testing.T) {
@@ -377,19 +377,19 @@ func TestBeancountDataReaderReadTransactionPostingLine_AmountExpression(t *testi
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(actualData.transactions)) assert.Equal(t, 1, len(actualData.Transactions))
assert.Equal(t, "2024-01-01", actualData.transactions[0].date) assert.Equal(t, "2024-01-01", actualData.Transactions[0].Date)
assert.Equal(t, 2, len(actualData.transactions[0].postings)) assert.Equal(t, 2, len(actualData.Transactions[0].Postings))
assert.Equal(t, "Income:TestCategory", actualData.transactions[0].postings[0].account) assert.Equal(t, "Income:TestCategory", actualData.Transactions[0].Postings[0].Account)
assert.Equal(t, "(1.2-3.4) * 5.6 / 7.8", actualData.transactions[0].postings[0].originalAmount) assert.Equal(t, "(1.2-3.4) * 5.6 / 7.8", actualData.Transactions[0].Postings[0].OriginalAmount)
assert.Equal(t, "-1.58", actualData.transactions[0].postings[0].amount) assert.Equal(t, "-1.58", actualData.Transactions[0].Postings[0].Amount)
assert.Equal(t, "CNY", actualData.transactions[0].postings[0].commodity) assert.Equal(t, "CNY", actualData.Transactions[0].Postings[0].Commodity)
assert.Equal(t, "Assets:TestAccount", actualData.transactions[0].postings[1].account) assert.Equal(t, "Assets:TestAccount", actualData.Transactions[0].Postings[1].Account)
assert.Equal(t, "1.2 * 3.4/-5.6 - 7.8", actualData.transactions[0].postings[1].originalAmount) assert.Equal(t, "1.2 * 3.4/-5.6 - 7.8", actualData.Transactions[0].Postings[1].OriginalAmount)
assert.Equal(t, "-8.53", actualData.transactions[0].postings[1].amount) assert.Equal(t, "-8.53", actualData.Transactions[0].Postings[1].Amount)
assert.Equal(t, "CNY", actualData.transactions[0].postings[1].commodity) assert.Equal(t, "CNY", actualData.Transactions[0].Postings[1].Commodity)
} }
func TestBeancountDataReaderReadTransactionPostingLine_InvalidAmountExpression(t *testing.T) { func TestBeancountDataReaderReadTransactionPostingLine_InvalidAmountExpression(t *testing.T) {
@@ -444,8 +444,8 @@ func TestBeancountDataReaderReadTransactionPostingLine_MissingAmount(t *testing.
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(actualData.transactions)) assert.Equal(t, 1, len(actualData.Transactions))
assert.Equal(t, 0, len(actualData.transactions[0].postings)) assert.Equal(t, 0, len(actualData.Transactions[0].Postings))
reader, err = createNewBeancountDataReader(context, []byte(""+ reader, err = createNewBeancountDataReader(context, []byte(""+
"2024-01-01 *\n"+ "2024-01-01 *\n"+
@@ -454,8 +454,8 @@ func TestBeancountDataReaderReadTransactionPostingLine_MissingAmount(t *testing.
actualData, err = reader.read(context) actualData, err = reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(actualData.transactions)) assert.Equal(t, 1, len(actualData.Transactions))
assert.Equal(t, 0, len(actualData.transactions[0].postings)) assert.Equal(t, 0, len(actualData.Transactions[0].Postings))
} }
func TestBeancountDataReaderReadTransactionPostingLine_MissingCommodity(t *testing.T) { func TestBeancountDataReaderReadTransactionPostingLine_MissingCommodity(t *testing.T) {
@@ -503,18 +503,18 @@ func TestBeancountDataReaderReadTransactionMetadataLine(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(actualData.transactions)) assert.Equal(t, 2, len(actualData.Transactions))
assert.Equal(t, "2024-01-01", actualData.transactions[0].date) assert.Equal(t, "2024-01-01", actualData.Transactions[0].Date)
assert.Equal(t, 2, len(actualData.transactions[0].postings)) assert.Equal(t, 2, len(actualData.Transactions[0].Postings))
assert.Equal(t, 2, len(actualData.transactions[0].metadata)) assert.Equal(t, 2, len(actualData.Transactions[0].Metadata))
assert.Equal(t, "value", actualData.transactions[0].metadata["key"]) assert.Equal(t, "value", actualData.Transactions[0].Metadata["key"])
assert.Equal(t, "value 2", actualData.transactions[0].metadata["key2"]) assert.Equal(t, "value 2", actualData.Transactions[0].Metadata["key2"])
assert.Equal(t, "2024-01-02", actualData.transactions[1].date) assert.Equal(t, "2024-01-02", actualData.Transactions[1].Date)
assert.Equal(t, 2, len(actualData.transactions[1].postings)) assert.Equal(t, 2, len(actualData.Transactions[1].Postings))
assert.Equal(t, 2, len(actualData.transactions[1].postings[0].metadata)) assert.Equal(t, 2, len(actualData.Transactions[1].Postings[0].Metadata))
assert.Equal(t, "value6", actualData.transactions[1].postings[0].metadata["key6"]) assert.Equal(t, "value6", actualData.Transactions[1].Postings[0].Metadata["key6"])
assert.Equal(t, "value 7", actualData.transactions[1].postings[0].metadata["key7"]) assert.Equal(t, "value 7", actualData.Transactions[1].Postings[0].Metadata["key7"])
assert.Equal(t, 0, len(actualData.transactions[1].postings[1].metadata)) assert.Equal(t, 0, len(actualData.Transactions[1].Postings[1].Metadata))
} }
+10 -10
View File
@@ -8,34 +8,34 @@ import (
func TestBeancountAccount_IsOpeningBalanceEquityAccount_True(t *testing.T) { func TestBeancountAccount_IsOpeningBalanceEquityAccount_True(t *testing.T) {
account := beancountAccount{ account := beancountAccount{
accountType: beancountEquityAccountType, AccountType: beancountEquityAccountType,
name: "Equity:Opening-Balances", Name: "Equity:Opening-Balances",
} }
assert.True(t, account.isOpeningBalanceEquityAccount()) assert.True(t, account.isOpeningBalanceEquityAccount())
account = beancountAccount{ account = beancountAccount{
accountType: beancountEquityAccountType, AccountType: beancountEquityAccountType,
name: "E:Opening-Balances", Name: "E:Opening-Balances",
} }
assert.True(t, account.isOpeningBalanceEquityAccount()) assert.True(t, account.isOpeningBalanceEquityAccount())
} }
func TestBeancountAccount_IsOpeningBalanceEquityAccount_False(t *testing.T) { func TestBeancountAccount_IsOpeningBalanceEquityAccount_False(t *testing.T) {
account := beancountAccount{ account := beancountAccount{
accountType: beancountAssetsAccountType, AccountType: beancountAssetsAccountType,
name: "Equity:Opening-Balances", Name: "Equity:Opening-Balances",
} }
assert.False(t, account.isOpeningBalanceEquityAccount()) assert.False(t, account.isOpeningBalanceEquityAccount())
account = beancountAccount{ account = beancountAccount{
accountType: beancountEquityAccountType, AccountType: beancountEquityAccountType,
name: "Opening-Balances", Name: "Opening-Balances",
} }
assert.False(t, account.isOpeningBalanceEquityAccount()) assert.False(t, account.isOpeningBalanceEquityAccount())
account = beancountAccount{ account = beancountAccount{
accountType: beancountEquityAccountType, AccountType: beancountEquityAccountType,
name: "Equity:Other", Name: "Equity:Other",
} }
assert.False(t, account.isOpeningBalanceEquityAccount()) assert.False(t, account.isOpeningBalanceEquityAccount())
} }
@@ -110,49 +110,49 @@ func (t *beancountTransactionDataRowIterator) Next(ctx core.Context, user *model
func (t *beancountTransactionDataRowIterator) parseTransaction(ctx core.Context, user *models.User, beancountEntry *beancountTransactionEntry) (map[datatable.TransactionDataTableColumn]string, error) { func (t *beancountTransactionDataRowIterator) parseTransaction(ctx core.Context, user *models.User, beancountEntry *beancountTransactionEntry) (map[datatable.TransactionDataTableColumn]string, error) {
data := make(map[datatable.TransactionDataTableColumn]string, len(beancountTransactionSupportedColumns)) data := make(map[datatable.TransactionDataTableColumn]string, len(beancountTransactionSupportedColumns))
if beancountEntry.date == "" { if beancountEntry.Date == "" {
return nil, errs.ErrMissingTransactionTime return nil, errs.ErrMissingTransactionTime
} }
// Beancount supports the international ISO 8601 standard format for dates, with dashes or the same ordering with slashes // Beancount supports the international ISO 8601 standard format for dates, with dashes or the same ordering with slashes
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = strings.ReplaceAll(beancountEntry.date, "/", "-") + " 00:00:00" data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = strings.ReplaceAll(beancountEntry.Date, "/", "-") + " 00:00:00"
if len(beancountEntry.postings) == 2 { if len(beancountEntry.Postings) == 2 {
splitData1 := beancountEntry.postings[0] splitData1 := beancountEntry.Postings[0]
splitData2 := beancountEntry.postings[1] splitData2 := beancountEntry.Postings[1]
account1 := t.dataTable.accountMap[splitData1.account] account1 := t.dataTable.accountMap[splitData1.Account]
account2 := t.dataTable.accountMap[splitData2.account] account2 := t.dataTable.accountMap[splitData2.Account]
if account1 == nil || account2 == nil { if account1 == nil || account2 == nil {
return nil, errs.ErrMissingAccountData return nil, errs.ErrMissingAccountData
} }
amount1, err := utils.ParseAmount(splitData1.amount) amount1, err := utils.ParseAmount(splitData1.Amount)
if err != nil { if err != nil {
log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse amount \"%s\", because %s", splitData1.amount, err.Error()) log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse amount \"%s\", because %s", splitData1.Amount, err.Error())
return nil, errs.ErrAmountInvalid return nil, errs.ErrAmountInvalid
} }
amount2, err := utils.ParseAmount(splitData2.amount) amount2, err := utils.ParseAmount(splitData2.Amount)
if err != nil { if err != nil {
log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse amount \"%s\", because %s", splitData2.amount, err.Error()) log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse amount \"%s\", because %s", splitData2.Amount, err.Error())
return nil, errs.ErrAmountInvalid return nil, errs.ErrAmountInvalid
} }
if ((account1.accountType == beancountEquityAccountType || account1.accountType == beancountIncomeAccountType) && (account2.accountType == beancountAssetsAccountType || account2.accountType == beancountLiabilitiesAccountType)) || if ((account1.AccountType == beancountEquityAccountType || account1.AccountType == beancountIncomeAccountType) && (account2.AccountType == beancountAssetsAccountType || account2.AccountType == beancountLiabilitiesAccountType)) ||
((account2.accountType == beancountEquityAccountType || account2.accountType == beancountIncomeAccountType) && (account1.accountType == beancountAssetsAccountType || account1.accountType == beancountLiabilitiesAccountType)) { // income ((account2.AccountType == beancountEquityAccountType || account2.AccountType == beancountIncomeAccountType) && (account1.AccountType == beancountAssetsAccountType || account1.AccountType == beancountLiabilitiesAccountType)) { // income
fromAccount := account1 fromAccount := account1
toAccount := account2 toAccount := account2
toCurrency := splitData2.commodity toCurrency := splitData2.Commodity
toAmount := amount2 toAmount := amount2
if (account2.accountType == beancountEquityAccountType || account2.accountType == beancountIncomeAccountType) && (account1.accountType == beancountAssetsAccountType || account1.accountType == beancountLiabilitiesAccountType) { if (account2.AccountType == beancountEquityAccountType || account2.AccountType == beancountIncomeAccountType) && (account1.AccountType == beancountAssetsAccountType || account1.AccountType == beancountLiabilitiesAccountType) {
fromAccount = account2 fromAccount = account2
toAccount = account1 toAccount = account1
toCurrency = splitData1.commodity toCurrency = splitData1.Commodity
toAmount = amount1 toAmount = amount1
} }
@@ -162,48 +162,48 @@ func (t *beancountTransactionDataRowIterator) parseTransaction(ctx core.Context,
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = utils.IntToString(int(models.TRANSACTION_TYPE_INCOME)) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = utils.IntToString(int(models.TRANSACTION_TYPE_INCOME))
} }
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = fromAccount.name data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = fromAccount.Name
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = toAccount.name data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = toAccount.Name
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = toCurrency data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = toCurrency
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(toAmount) data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(toAmount)
} else if account1.accountType == beancountExpensesAccountType && (account2.accountType == beancountAssetsAccountType || account2.accountType == beancountLiabilitiesAccountType) || } else if account1.AccountType == beancountExpensesAccountType && (account2.AccountType == beancountAssetsAccountType || account2.AccountType == beancountLiabilitiesAccountType) ||
(account2.accountType == beancountExpensesAccountType && (account1.accountType == beancountAssetsAccountType || account1.accountType == beancountLiabilitiesAccountType)) { // expense (account2.AccountType == beancountExpensesAccountType && (account1.AccountType == beancountAssetsAccountType || account1.AccountType == beancountLiabilitiesAccountType)) { // expense
fromAccount := account1 fromAccount := account1
fromCurrency := splitData1.commodity fromCurrency := splitData1.Commodity
fromAmount := amount1 fromAmount := amount1
toAccount := account2 toAccount := account2
if account1.accountType == beancountExpensesAccountType && (account2.accountType == beancountAssetsAccountType || account2.accountType == beancountLiabilitiesAccountType) { if account1.AccountType == beancountExpensesAccountType && (account2.AccountType == beancountAssetsAccountType || account2.AccountType == beancountLiabilitiesAccountType) {
fromAccount = account2 fromAccount = account2
fromCurrency = splitData2.commodity fromCurrency = splitData2.Commodity
fromAmount = amount2 fromAmount = amount2
toAccount = account1 toAccount = account1
} }
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = utils.IntToString(int(models.TRANSACTION_TYPE_EXPENSE)) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = utils.IntToString(int(models.TRANSACTION_TYPE_EXPENSE))
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = toAccount.name data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = toAccount.Name
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = fromAccount.name data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = fromAccount.Name
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = fromCurrency data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = fromCurrency
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-fromAmount) data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-fromAmount)
} else if (account1.accountType == beancountAssetsAccountType || account1.accountType == beancountLiabilitiesAccountType) && } else if (account1.AccountType == beancountAssetsAccountType || account1.AccountType == beancountLiabilitiesAccountType) &&
(account2.accountType == beancountAssetsAccountType || account2.accountType == beancountLiabilitiesAccountType) { (account2.AccountType == beancountAssetsAccountType || account2.AccountType == beancountLiabilitiesAccountType) {
var fromAccount, toAccount *beancountAccount var fromAccount, toAccount *beancountAccount
var fromAmount, toAmount int64 var fromAmount, toAmount int64
var fromCurrency, toCurrency string var fromCurrency, toCurrency string
if amount1 < 0 { if amount1 < 0 {
fromAccount = account1 fromAccount = account1
fromCurrency = splitData1.commodity fromCurrency = splitData1.Commodity
fromAmount = -amount1 fromAmount = -amount1
toAccount = account2 toAccount = account2
toCurrency = splitData2.commodity toCurrency = splitData2.Commodity
toAmount = amount2 toAmount = amount2
} else if amount2 < 0 { } else if amount2 < 0 {
fromAccount = account2 fromAccount = account2
fromCurrency = splitData2.commodity fromCurrency = splitData2.Commodity
fromAmount = -amount2 fromAmount = -amount2
toAccount = account1 toAccount = account1
toCurrency = splitData1.commodity toCurrency = splitData1.Commodity
toAmount = amount1 toAmount = amount1
} else { } else {
log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse transfer transaction, because unexcepted account amounts \"%d\" and \"%d\"", amount1, amount2) log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse transfer transaction, because unexcepted account amounts \"%d\" and \"%d\"", amount1, amount2)
@@ -212,26 +212,26 @@ func (t *beancountTransactionDataRowIterator) parseTransaction(ctx core.Context,
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = utils.IntToString(int(models.TRANSACTION_TYPE_TRANSFER)) data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = utils.IntToString(int(models.TRANSACTION_TYPE_TRANSFER))
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = "" data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = ""
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = fromAccount.name data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = fromAccount.Name
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = fromCurrency data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_CURRENCY] = fromCurrency
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(fromAmount) data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(fromAmount)
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = toAccount.name data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = toAccount.Name
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = toCurrency data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_CURRENCY] = toCurrency
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(toAmount) data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(toAmount)
} else { } else {
log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse transaction, because unexcepted account types \"%d\" and \"%d\"", account1.accountType, account2.accountType) log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse transaction, because unexcepted account types \"%d\" and \"%d\"", account1.AccountType, account2.AccountType)
return nil, errs.ErrThereAreNotSupportedTransactionType return nil, errs.ErrThereAreNotSupportedTransactionType
} }
} else if len(beancountEntry.postings) <= 1 { } else if len(beancountEntry.Postings) <= 1 {
log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse transaction, because postings count is %d", len(beancountEntry.postings)) log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse transaction, because postings count is %d", len(beancountEntry.Postings))
return nil, errs.ErrInvalidBeancountFile return nil, errs.ErrInvalidBeancountFile
} else { } else {
log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse split transaction, because postings count is %d", len(beancountEntry.postings)) log.Errorf(ctx, "[beancount_transaction_data_table.parseTransaction] cannot parse split transaction, because postings count is %d", len(beancountEntry.Postings))
return nil, errs.ErrNotSupportedSplitTransactions return nil, errs.ErrNotSupportedSplitTransactions
} }
data[datatable.TRANSACTION_DATA_TABLE_TAGS] = strings.Join(beancountEntry.tags, BEANCOUNT_TRANSACTION_TAG_SEPARATOR) data[datatable.TRANSACTION_DATA_TABLE_TAGS] = strings.Join(beancountEntry.Tags, BEANCOUNT_TRANSACTION_TAG_SEPARATOR)
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = beancountEntry.narration data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = beancountEntry.Narration
return data, nil return data, nil
} }
@@ -242,7 +242,7 @@ func createNewBeancountTransactionDataTable(beancountData *beancountData) (*bean
} }
return &beancountTransactionDataTable{ return &beancountTransactionDataTable{
allData: beancountData.transactions, allData: beancountData.Transactions,
accountMap: beancountData.accounts, accountMap: beancountData.Accounts,
}, nil }, nil
} }
+12 -12
View File
@@ -13,20 +13,20 @@ type iifAccountData struct {
// iifTransactionDataset defines the structure of intuit interchange format (iif) transaction dataset // iifTransactionDataset defines the structure of intuit interchange format (iif) transaction dataset
type iifTransactionDataset struct { type iifTransactionDataset struct {
transactionDataColumnIndexes map[string]int TransactionDataColumnIndexes map[string]int
splitDataColumnIndexes map[string]int SplitDataColumnIndexes map[string]int
transactions []*iifTransactionData Transactions []*iifTransactionData
} }
// iifTransactionData defines the structure of intuit interchange format (iif) transaction data // iifTransactionData defines the structure of intuit interchange format (iif) transaction data
type iifTransactionData struct { type iifTransactionData struct {
dataItems []string DataItems []string
splitData []*iifTransactionSplitData SplitData []*iifTransactionSplitData
} }
// iifTransactionSplitData defines the structure of intuit interchange format (iif) transaction split data // iifTransactionSplitData defines the structure of intuit interchange format (iif) transaction split data
type iifTransactionSplitData struct { type iifTransactionSplitData struct {
dataItems []string DataItems []string
} }
func (s *iifTransactionDataset) getTransactionDataItemValue(transactionData *iifTransactionData, columnName string) (string, bool) { func (s *iifTransactionDataset) getTransactionDataItemValue(transactionData *iifTransactionData, columnName string) (string, bool) {
@@ -34,13 +34,13 @@ func (s *iifTransactionDataset) getTransactionDataItemValue(transactionData *iif
return "", false return "", false
} }
index, exists := s.transactionDataColumnIndexes[columnName] index, exists := s.TransactionDataColumnIndexes[columnName]
if !exists || index < 0 || index >= len(transactionData.dataItems) { if !exists || index < 0 || index >= len(transactionData.DataItems) {
return "", false return "", false
} }
return transactionData.dataItems[index], true return transactionData.DataItems[index], true
} }
func (s *iifTransactionDataset) getSplitDataItemValue(splitData *iifTransactionSplitData, columnName string) (string, bool) { func (s *iifTransactionDataset) getSplitDataItemValue(splitData *iifTransactionSplitData, columnName string) (string, bool) {
@@ -48,11 +48,11 @@ func (s *iifTransactionDataset) getSplitDataItemValue(splitData *iifTransactionS
return "", false return "", false
} }
index, exists := s.splitDataColumnIndexes[columnName] index, exists := s.SplitDataColumnIndexes[columnName]
if !exists || index < 0 || index >= len(splitData.dataItems) { if !exists || index < 0 || index >= len(splitData.DataItems) {
return "", false return "", false
} }
return splitData.dataItems[index], true return splitData.DataItems[index], true
} }
+9 -9
View File
@@ -119,8 +119,8 @@ func (r *iifDataReader) read(ctx core.Context) ([]*iifAccountDataset, []*iifTran
if lastLineSign == "" { if lastLineSign == "" {
if items[0] == iifTransactionLineSignColumnName { if items[0] == iifTransactionLineSignColumnName {
currentTransactionData = &iifTransactionData{ currentTransactionData = &iifTransactionData{
dataItems: items, DataItems: items,
splitData: make([]*iifTransactionSplitData, 0), SplitData: make([]*iifTransactionSplitData, 0),
} }
lastLineSign = items[0] lastLineSign = items[0]
} else { } else {
@@ -134,8 +134,8 @@ func (r *iifDataReader) read(ctx core.Context) ([]*iifAccountDataset, []*iifTran
return nil, nil, errs.ErrInvalidIIFFile return nil, nil, errs.ErrInvalidIIFFile
} }
currentTransactionData.splitData = append(currentTransactionData.splitData, &iifTransactionSplitData{ currentTransactionData.SplitData = append(currentTransactionData.SplitData, &iifTransactionSplitData{
dataItems: items, DataItems: items,
}) })
lastLineSign = items[0] lastLineSign = items[0]
} else if items[0] == iifTransactionEndLineSignColumnName { } else if items[0] == iifTransactionEndLineSignColumnName {
@@ -144,12 +144,12 @@ func (r *iifDataReader) read(ctx core.Context) ([]*iifAccountDataset, []*iifTran
return nil, nil, errs.ErrInvalidIIFFile return nil, nil, errs.ErrInvalidIIFFile
} }
if len(currentTransactionData.splitData) < 1 { if len(currentTransactionData.SplitData) < 1 {
log.Errorf(ctx, "[iif_data_reader.read] expected reading transaction split line, but read \"%s\"", items[0]) log.Errorf(ctx, "[iif_data_reader.read] expected reading transaction split line, but read \"%s\"", items[0])
return nil, nil, errs.ErrInvalidIIFFile return nil, nil, errs.ErrInvalidIIFFile
} }
currentTransactionDataset.transactions = append(currentTransactionDataset.transactions, currentTransactionData) currentTransactionDataset.Transactions = append(currentTransactionDataset.Transactions, currentTransactionData)
lastLineSign = "" lastLineSign = ""
} else { } else {
log.Errorf(ctx, "[iif_data_reader.read] iif line expected reading split sign or transaction end sign, but actual is \"%s\"", items[0]) log.Errorf(ctx, "[iif_data_reader.read] iif line expected reading split sign or transaction end sign, but actual is \"%s\"", items[0])
@@ -234,9 +234,9 @@ func (r *iifDataReader) readTransactionSampleLines(ctx core.Context, items []str
} }
return &iifTransactionDataset{ return &iifTransactionDataset{
transactionDataColumnIndexes: transactionDataColumnIndexes, TransactionDataColumnIndexes: transactionDataColumnIndexes,
splitDataColumnIndexes: splitDataColumnIndexes, SplitDataColumnIndexes: splitDataColumnIndexes,
transactions: make([]*iifTransactionData, 0), Transactions: make([]*iifTransactionData, 0),
}, nil }, nil
} }
@@ -74,11 +74,11 @@ func (t *iifTransactionDataTable) TransactionRowCount() int {
for i := 0; i < len(t.transactionDatasets); i++ { for i := 0; i < len(t.transactionDatasets); i++ {
datasets := t.transactionDatasets[i] datasets := t.transactionDatasets[i]
for j := 0; j < len(datasets.transactions); j++ { for j := 0; j < len(datasets.Transactions); j++ {
transaction := datasets.transactions[j] transaction := datasets.Transactions[j]
if transaction.splitData != nil { if transaction.SplitData != nil {
totalDataRowCount += len(transaction.splitData) totalDataRowCount += len(transaction.SplitData)
} }
} }
} }
@@ -122,17 +122,17 @@ func (t *iifTransactionDataRowIterator) HasNext() bool {
currentDataset := allDatasets[t.currentDatasetIndex] currentDataset := allDatasets[t.currentDatasetIndex]
if t.currentIndexInDataset+1 < len(currentDataset.transactions) { if t.currentIndexInDataset+1 < len(currentDataset.Transactions) {
return true return true
} else if t.currentIndexInDataset < len(currentDataset.transactions) && } else if t.currentIndexInDataset < len(currentDataset.Transactions) &&
t.currentSplitDataIndex+1 < len(currentDataset.transactions[t.currentIndexInDataset].splitData) { t.currentSplitDataIndex+1 < len(currentDataset.Transactions[t.currentIndexInDataset].SplitData) {
return true return true
} }
for i := t.currentDatasetIndex + 1; i < len(allDatasets); i++ { for i := t.currentDatasetIndex + 1; i < len(allDatasets); i++ {
dataset := allDatasets[i] dataset := allDatasets[i]
if len(dataset.transactions) < 1 { if len(dataset.Transactions) < 1 {
continue continue
} }
@@ -150,8 +150,8 @@ func (t *iifTransactionDataRowIterator) Next(ctx core.Context, user *models.User
foundNextRow := false foundNextRow := false
dataset := allDatasets[i] dataset := allDatasets[i]
for j := t.currentIndexInDataset; j < len(dataset.transactions); j++ { for j := t.currentIndexInDataset; j < len(dataset.Transactions); j++ {
if t.currentSplitDataIndex+1 < len(dataset.transactions[j].splitData) { if t.currentSplitDataIndex+1 < len(dataset.Transactions[j].SplitData) {
t.currentSplitDataIndex++ t.currentSplitDataIndex++
foundNextRow = true foundNextRow = true
break break
@@ -176,22 +176,22 @@ func (t *iifTransactionDataRowIterator) Next(ctx core.Context, user *models.User
currentDataset := allDatasets[t.currentDatasetIndex] currentDataset := allDatasets[t.currentDatasetIndex]
if t.currentIndexInDataset >= len(currentDataset.transactions) { if t.currentIndexInDataset >= len(currentDataset.Transactions) {
return nil, nil return nil, nil
} }
data := currentDataset.transactions[t.currentIndexInDataset] data := currentDataset.Transactions[t.currentIndexInDataset]
if len(data.splitData) < 1 { if len(data.SplitData) < 1 {
log.Errorf(ctx, "[iif_transaction_data_table.Next] cannot parsing transaction in row#%d (dataset#%d), because split data is empty", t.currentIndexInDataset, t.currentDatasetIndex) log.Errorf(ctx, "[iif_transaction_data_table.Next] cannot parsing transaction in row#%d (dataset#%d), because split data is empty", t.currentIndexInDataset, t.currentDatasetIndex)
return nil, errs.ErrInvalidIIFFile return nil, errs.ErrInvalidIIFFile
} }
if t.currentSplitDataIndex >= len(data.splitData) { if t.currentSplitDataIndex >= len(data.SplitData) {
return nil, nil return nil, nil
} }
if len(data.splitData) > 1 { if len(data.SplitData) > 1 {
_, err := t.isSplitTransactionSupported(ctx, currentDataset, data) _, err := t.isSplitTransactionSupported(ctx, currentDataset, data)
if err != nil { if err != nil {
@@ -222,11 +222,11 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
return nil, err return nil, err
} }
transactionType, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionTypeColumnName) transactionType, _ := dataset.getSplitDataItemValue(transactionData.SplitData[splitDataIndex], iifTransactionTypeColumnName)
mainAccountName, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAccountNameColumnName) mainAccountName, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAccountNameColumnName)
splitAccountName, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionAccountNameColumnName) splitAccountName, _ := dataset.getSplitDataItemValue(transactionData.SplitData[splitDataIndex], iifTransactionAccountNameColumnName)
mainAmount, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAmountColumnName) mainAmount, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAmountColumnName)
splitAmount, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionAmountColumnName) splitAmount, _ := dataset.getSplitDataItemValue(transactionData.SplitData[splitDataIndex], iifTransactionAmountColumnName)
mainAmountNum, err := parseAmount(mainAmount) mainAmountNum, err := parseAmount(mainAmount)
if err != nil { if err != nil {
@@ -254,7 +254,7 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
categoryName = mainAccountName categoryName = mainAccountName
accountName = splitAccountName accountName = splitAccountName
if len(transactionData.splitData) > 1 { if len(transactionData.SplitData) > 1 {
amountNum = splitAmountNum amountNum = splitAmountNum
} else { } else {
amountNum = -mainAmountNum amountNum = -mainAmountNum
@@ -263,7 +263,7 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
categoryName = splitAccountName categoryName = splitAccountName
accountName = mainAccountName accountName = mainAccountName
if len(transactionData.splitData) > 1 { if len(transactionData.SplitData) > 1 {
amountNum = -splitAmountNum amountNum = -splitAmountNum
} else { } else {
amountNum = mainAmountNum amountNum = mainAmountNum
@@ -295,7 +295,7 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
categoryName = mainAccountName categoryName = mainAccountName
accountName = splitAccountName accountName = splitAccountName
if len(transactionData.splitData) > 1 { if len(transactionData.SplitData) > 1 {
amountNum = -splitAmountNum amountNum = -splitAmountNum
} else { } else {
amountNum = mainAmountNum amountNum = mainAmountNum
@@ -304,7 +304,7 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
categoryName = splitAccountName categoryName = splitAccountName
accountName = mainAccountName accountName = mainAccountName
if len(transactionData.splitData) > 1 { if len(transactionData.SplitData) > 1 {
amountNum = splitAmountNum amountNum = splitAmountNum
} else { } else {
amountNum = -mainAmountNum amountNum = -mainAmountNum
@@ -332,7 +332,7 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
relatedAmountNum := int64(0) relatedAmountNum := int64(0)
mainAccountTransferToSplitAccount := false mainAccountTransferToSplitAccount := false
if len(transactionData.splitData) > 1 { if len(transactionData.SplitData) > 1 {
amountNum = splitAmountNum amountNum = splitAmountNum
relatedAmountNum = splitAmountNum relatedAmountNum = splitAmountNum
mainAccountTransferToSplitAccount = amountNum >= 0 mainAccountTransferToSplitAccount = amountNum >= 0
@@ -369,11 +369,11 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
} }
} }
if splitMemo, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionMemoColumnName); splitMemo != "" { if splitMemo, _ := dataset.getSplitDataItemValue(transactionData.SplitData[splitDataIndex], iifTransactionMemoColumnName); splitMemo != "" {
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = splitMemo data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = splitMemo
} else if memo, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionMemoColumnName); memo != "" { } else if memo, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionMemoColumnName); memo != "" {
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = memo data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = memo
} else if splitName, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionNameColumnName); splitName != "" { } else if splitName, _ := dataset.getSplitDataItemValue(transactionData.SplitData[splitDataIndex], iifTransactionNameColumnName); splitName != "" {
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = splitName data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = splitName
} else if name, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionNameColumnName); name != "" { } else if name, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionNameColumnName); name != "" {
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = name data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = name
@@ -402,8 +402,8 @@ func (t *iifTransactionDataRowIterator) isSplitTransactionSupported(ctx core.Con
splitTotalAmount := int64(0) splitTotalAmount := int64(0)
for i := 0; i < len(transactionData.splitData); i++ { for i := 0; i < len(transactionData.SplitData); i++ {
splitAmountStr, _ := dataset.getSplitDataItemValue(transactionData.splitData[i], iifTransactionAmountColumnName) splitAmountStr, _ := dataset.getSplitDataItemValue(transactionData.SplitData[i], iifTransactionAmountColumnName)
splitAmount, err := parseAmount(splitAmountStr) splitAmount, err := parseAmount(splitAmountStr)
if err != nil { if err != nil {
@@ -420,7 +420,7 @@ func (t *iifTransactionDataRowIterator) isSplitTransactionSupported(ctx core.Con
} }
} }
if len(transactionData.splitData) > 1 && !supportSplitTransactions { if len(transactionData.SplitData) > 1 && !supportSplitTransactions {
return false, errs.ErrNotSupportedSplitTransactions return false, errs.ErrNotSupportedSplitTransactions
} }
@@ -463,7 +463,7 @@ func createNewIIfTransactionDataTable(ctx core.Context, accountDatasets []*iifAc
iifTransactionAccountNameColumnName, iifTransactionAccountNameColumnName,
iifTransactionAmountColumnName, iifTransactionAmountColumnName,
} { } {
if _, exists := transactionDataset.transactionDataColumnIndexes[requiredColumnName]; !exists { if _, exists := transactionDataset.TransactionDataColumnIndexes[requiredColumnName]; !exists {
return nil, errs.ErrMissingRequiredFieldInHeaderRow return nil, errs.ErrMissingRequiredFieldInHeaderRow
} }
} }
+58 -58
View File
@@ -34,91 +34,91 @@ const (
// qifData defines the structure of quicken interchange format (qif) data // qifData defines the structure of quicken interchange format (qif) data
type qifData struct { type qifData struct {
bankAccountTransactions []*qifTransactionData BankAccountTransactions []*qifTransactionData
cashAccountTransactions []*qifTransactionData CashAccountTransactions []*qifTransactionData
creditCardAccountTransactions []*qifTransactionData CreditCardAccountTransactions []*qifTransactionData
assetAccountTransactions []*qifTransactionData AssetAccountTransactions []*qifTransactionData
liabilityAccountTransactions []*qifTransactionData LiabilityAccountTransactions []*qifTransactionData
memorizedTransactions []*qifMemorizedTransactionData MemorizedTransactions []*qifMemorizedTransactionData
investmentAccountTransactions []*qifInvestmentTransactionData InvestmentAccountTransactions []*qifInvestmentTransactionData
accounts []*qifAccountData Accounts []*qifAccountData
categories []*qifCategoryData Categories []*qifCategoryData
classes []*qifClassData Classes []*qifClassData
} }
// qifTransactionData defines the structure of quicken interchange format (qif) transaction data // qifTransactionData defines the structure of quicken interchange format (qif) transaction data
type qifTransactionData struct { type qifTransactionData struct {
date string Date string
amount string Amount string
clearedStatus qifTransactionClearedStatus ClearedStatus qifTransactionClearedStatus
num string Num string
payee string Payee string
memo string Memo string
addresses []string Addresses []string
category string Category string
subTransactionCategory []string SubTransactionCategory []string
subTransactionMemo []string SubTransactionMemo []string
subTransactionAmount []string SubTransactionAmount []string
account *qifAccountData Account *qifAccountData
} }
// qifInvestmentTransactionData defines the structure of quicken interchange format (qif) investment transaction data // qifInvestmentTransactionData defines the structure of quicken interchange format (qif) investment transaction data
type qifInvestmentTransactionData struct { type qifInvestmentTransactionData struct {
date string Date string
action string Action string
security string Security string
price string Price string
quantity string Quantity string
amount string Amount string
clearedStatus qifTransactionClearedStatus ClearedStatus qifTransactionClearedStatus
text string Text string
memo string Memo string
commission string Commission string
accountForTransfer string AccountForTransfer string
amountTransferred string AmountTransferred string
account *qifAccountData Account *qifAccountData
} }
// qifMemorizedTransactionData defines the structure of quicken interchange format (qif) memorized transaction data // qifMemorizedTransactionData defines the structure of quicken interchange format (qif) memorized transaction data
type qifMemorizedTransactionData struct { type qifMemorizedTransactionData struct {
qifTransactionData qifTransactionData
transactionType qifTransactionType TransactionType qifTransactionType
amortization qifMemorizedTransactionAmortizationData Amortization qifMemorizedTransactionAmortizationData
} }
// qifMemorizedTransactionAmortizationData defines the structure of quicken interchange format (qif) memorized transaction amortization data // qifMemorizedTransactionAmortizationData defines the structure of quicken interchange format (qif) memorized transaction amortization data
type qifMemorizedTransactionAmortizationData struct { type qifMemorizedTransactionAmortizationData struct {
firstPaymentDate string FirstPaymentDate string
totalYearsForLoan string TotalYearsForLoan string
numberOfPayments string NumberOfPayments string
numberOfPeriodsPerYear string NumberOfPeriodsPerYear string
interestRate string InterestRate string
currentLoanBalance string CurrentLoanBalance string
originalLoanAmount string OriginalLoanAmount string
} }
// qifAccountData defines the structure of quicken interchange format (qif) account data // qifAccountData defines the structure of quicken interchange format (qif) account data
type qifAccountData struct { type qifAccountData struct {
name string Name string
accountType string AccountType string
description string Description string
creditLimit string CreditLimit string
statementBalanceDate string StatementBalanceDate string
statementBalanceAmount string StatementBalanceAmount string
} }
// qifCategoryData defines the structure of quicken interchange format (qif) category data // qifCategoryData defines the structure of quicken interchange format (qif) category data
type qifCategoryData struct { type qifCategoryData struct {
name string Name string
description string Description string
taxRelated bool TaxRelated bool
categoryType qifCategoryType CategoryType qifCategoryType
budgetAmount string BudgetAmount string
taxScheduleInformation string TaxScheduleInformation string
} }
// qifClassData defines the structure of quicken interchange format (qif) class data // qifClassData defines the structure of quicken interchange format (qif) class data
type qifClassData struct { type qifClassData struct {
name string Name string
description string Description string
} }
+66 -66
View File
@@ -98,18 +98,18 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
continue continue
} }
transactionData.account = currentAccount transactionData.Account = currentAccount
if currentEntryHeader == qifBankTransactionHeader { if currentEntryHeader == qifBankTransactionHeader {
data.bankAccountTransactions = append(data.bankAccountTransactions, transactionData) data.BankAccountTransactions = append(data.BankAccountTransactions, transactionData)
} else if currentEntryHeader == qifCashTransactionHeader { } else if currentEntryHeader == qifCashTransactionHeader {
data.cashAccountTransactions = append(data.cashAccountTransactions, transactionData) data.CashAccountTransactions = append(data.CashAccountTransactions, transactionData)
} else if currentEntryHeader == qifCreditCardTransactionHeader { } else if currentEntryHeader == qifCreditCardTransactionHeader {
data.creditCardAccountTransactions = append(data.creditCardAccountTransactions, transactionData) data.CreditCardAccountTransactions = append(data.CreditCardAccountTransactions, transactionData)
} else if currentEntryHeader == qifAssetAccountTransactionHeader { } else if currentEntryHeader == qifAssetAccountTransactionHeader {
data.assetAccountTransactions = append(data.assetAccountTransactions, transactionData) data.AssetAccountTransactions = append(data.AssetAccountTransactions, transactionData)
} else if currentEntryHeader == qifLiabilityAccountTransactionHeader { } else if currentEntryHeader == qifLiabilityAccountTransactionHeader {
data.liabilityAccountTransactions = append(data.liabilityAccountTransactions, transactionData) data.LiabilityAccountTransactions = append(data.LiabilityAccountTransactions, transactionData)
} }
} else if currentEntryHeader == qifMemorizedTransactionHeader || currentEntryHeader == qifMemorisedTransactionHeader { } else if currentEntryHeader == qifMemorizedTransactionHeader || currentEntryHeader == qifMemorisedTransactionHeader {
transactionData, err := r.parseMemorizedTransaction(ctx, entryData) transactionData, err := r.parseMemorizedTransaction(ctx, entryData)
@@ -122,8 +122,8 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
continue continue
} }
transactionData.account = currentAccount transactionData.Account = currentAccount
data.memorizedTransactions = append(data.memorizedTransactions, transactionData) data.MemorizedTransactions = append(data.MemorizedTransactions, transactionData)
} else if currentEntryHeader == qifInvestmentTransactionHeader { } else if currentEntryHeader == qifInvestmentTransactionHeader {
transactionData, err := r.parseInvestmentTransaction(ctx, entryData) transactionData, err := r.parseInvestmentTransaction(ctx, entryData)
@@ -135,8 +135,8 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
continue continue
} }
transactionData.account = currentAccount transactionData.Account = currentAccount
data.investmentAccountTransactions = append(data.investmentAccountTransactions, transactionData) data.InvestmentAccountTransactions = append(data.InvestmentAccountTransactions, transactionData)
} else if currentEntryHeader == qifAccountHeader { } else if currentEntryHeader == qifAccountHeader {
accountData, err := r.parseAccount(ctx, entryData) accountData, err := r.parseAccount(ctx, entryData)
@@ -149,7 +149,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
} }
currentAccount = accountData currentAccount = accountData
data.accounts = append(data.accounts, accountData) data.Accounts = append(data.Accounts, accountData)
} else if currentEntryHeader == qifCategoryHeader { } else if currentEntryHeader == qifCategoryHeader {
categoryData, err := r.parseCategory(ctx, entryData) categoryData, err := r.parseCategory(ctx, entryData)
@@ -161,7 +161,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
continue continue
} }
data.categories = append(data.categories, categoryData) data.Categories = append(data.Categories, categoryData)
} else if currentEntryHeader == qifClassHeader { } else if currentEntryHeader == qifClassHeader {
classData, err := r.parseClass(ctx, entryData) classData, err := r.parseClass(ctx, entryData)
@@ -173,7 +173,7 @@ func (r *qifDataReader) read(ctx core.Context) (*qifData, error) {
continue continue
} }
data.classes = append(data.classes, classData) data.Classes = append(data.Classes, classData)
} else { } else {
log.Warnf(ctx, "[qif_data_reader.read] read unsupported entry header \"%s\" and skip this entry", currentEntryHeader) log.Warnf(ctx, "[qif_data_reader.read] read unsupported entry header \"%s\" and skip this entry", currentEntryHeader)
} }
@@ -202,27 +202,27 @@ func (r *qifDataReader) parseTransaction(ctx core.Context, data []string, ignore
} }
if line[0] == 'D' { if line[0] == 'D' {
transactionData.date = line[1:] transactionData.Date = line[1:]
} else if line[0] == 'T' { } else if line[0] == 'T' {
transactionData.amount = line[1:] transactionData.Amount = line[1:]
} else if line[0] == 'C' { } else if line[0] == 'C' {
transactionData.clearedStatus = r.parseClearedStatus(ctx, line[1:]) transactionData.ClearedStatus = r.parseClearedStatus(ctx, line[1:])
} else if line[0] == 'N' { } else if line[0] == 'N' {
transactionData.num = line[1:] transactionData.Num = line[1:]
} else if line[0] == 'P' { } else if line[0] == 'P' {
transactionData.payee = line[1:] transactionData.Payee = line[1:]
} else if line[0] == 'M' { } else if line[0] == 'M' {
transactionData.memo = line[1:] transactionData.Memo = line[1:]
} else if line[0] == 'A' { } else if line[0] == 'A' {
transactionData.addresses = append(transactionData.addresses, line[1:]) transactionData.Addresses = append(transactionData.Addresses, line[1:])
} else if line[0] == 'L' { } else if line[0] == 'L' {
transactionData.category = line[1:] transactionData.Category = line[1:]
} else if line[0] == 'S' { } else if line[0] == 'S' {
transactionData.subTransactionCategory = append(transactionData.subTransactionCategory, line[1:]) transactionData.SubTransactionCategory = append(transactionData.SubTransactionCategory, line[1:])
} else if line[0] == 'E' { } else if line[0] == 'E' {
transactionData.subTransactionMemo = append(transactionData.subTransactionMemo, line[1:]) transactionData.SubTransactionMemo = append(transactionData.SubTransactionMemo, line[1:])
} else if line[0] == '$' { } else if line[0] == '$' {
transactionData.subTransactionAmount = append(transactionData.subTransactionAmount, line[1:]) transactionData.SubTransactionAmount = append(transactionData.SubTransactionAmount, line[1:])
} else { } else {
if !ignoreUnknown { if !ignoreUnknown {
log.Warnf(ctx, "[qif_data_reader.parseTransaction] read unsupported line \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.parseTransaction] read unsupported line \"%s\" and skip this line", line)
@@ -247,7 +247,7 @@ func (r *qifDataReader) parseMemorizedTransaction(ctx core.Context, data []strin
transactionData := &qifMemorizedTransactionData{ transactionData := &qifMemorizedTransactionData{
qifTransactionData: *baseTransactionData, qifTransactionData: *baseTransactionData,
amortization: qifMemorizedTransactionAmortizationData{}, Amortization: qifMemorizedTransactionAmortizationData{},
} }
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
@@ -266,33 +266,33 @@ func (r *qifDataReader) parseMemorizedTransaction(ctx core.Context, data []strin
if line[0] == 'K' { if line[0] == 'K' {
if line == string(qifCheckTransactionType) { if line == string(qifCheckTransactionType) {
transactionData.transactionType = qifCheckTransactionType transactionData.TransactionType = qifCheckTransactionType
} else if line == string(qifDepositTransactionType) { } else if line == string(qifDepositTransactionType) {
transactionData.transactionType = qifDepositTransactionType transactionData.TransactionType = qifDepositTransactionType
} else if line == string(qifPaymentTransactionType) { } else if line == string(qifPaymentTransactionType) {
transactionData.transactionType = qifPaymentTransactionType transactionData.TransactionType = qifPaymentTransactionType
} else if line == string(qifInvestmentTransactionType) { } else if line == string(qifInvestmentTransactionType) {
transactionData.transactionType = qifInvestmentTransactionType transactionData.TransactionType = qifInvestmentTransactionType
} else if line == string(qifElectronicPayeeTransactionType) { } else if line == string(qifElectronicPayeeTransactionType) {
transactionData.transactionType = qifElectronicPayeeTransactionType transactionData.TransactionType = qifElectronicPayeeTransactionType
} else { } else {
log.Warnf(ctx, "[qif_data_reader.parseMemorizedTransaction] read unsupported transaction type \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.parseMemorizedTransaction] read unsupported transaction type \"%s\" and skip this line", line)
continue continue
} }
} else if line[0] == '1' { } else if line[0] == '1' {
transactionData.amortization.firstPaymentDate = line[1:] transactionData.Amortization.FirstPaymentDate = line[1:]
} else if line[0] == '2' { } else if line[0] == '2' {
transactionData.amortization.totalYearsForLoan = line[1:] transactionData.Amortization.TotalYearsForLoan = line[1:]
} else if line[0] == '3' { } else if line[0] == '3' {
transactionData.amortization.numberOfPayments = line[1:] transactionData.Amortization.NumberOfPayments = line[1:]
} else if line[0] == '4' { } else if line[0] == '4' {
transactionData.amortization.numberOfPeriodsPerYear = line[1:] transactionData.Amortization.NumberOfPeriodsPerYear = line[1:]
} else if line[0] == '5' { } else if line[0] == '5' {
transactionData.amortization.interestRate = line[1:] transactionData.Amortization.InterestRate = line[1:]
} else if line[0] == '6' { } else if line[0] == '6' {
transactionData.amortization.currentLoanBalance = line[1:] transactionData.Amortization.CurrentLoanBalance = line[1:]
} else if line[0] == '7' { } else if line[0] == '7' {
transactionData.amortization.originalLoanAmount = line[1:] transactionData.Amortization.OriginalLoanAmount = line[1:]
} else { } else {
log.Warnf(ctx, "[qif_data_reader.parseMemorizedTransaction] read unsupported line \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.parseMemorizedTransaction] read unsupported line \"%s\" and skip this line", line)
continue continue
@@ -317,29 +317,29 @@ func (r *qifDataReader) parseInvestmentTransaction(ctx core.Context, data []stri
} }
if line[0] == 'D' { if line[0] == 'D' {
transactionData.date = line[1:] transactionData.Date = line[1:]
} else if line[0] == 'N' { } else if line[0] == 'N' {
transactionData.action = line[1:] transactionData.Action = line[1:]
} else if line[0] == 'Y' { } else if line[0] == 'Y' {
transactionData.security = line[1:] transactionData.Security = line[1:]
} else if line[0] == 'I' { } else if line[0] == 'I' {
transactionData.price = line[1:] transactionData.Price = line[1:]
} else if line[0] == 'Q' { } else if line[0] == 'Q' {
transactionData.quantity = line[1:] transactionData.Quantity = line[1:]
} else if line[0] == 'T' { } else if line[0] == 'T' {
transactionData.amount = line[1:] transactionData.Amount = line[1:]
} else if line[0] == 'C' { } else if line[0] == 'C' {
transactionData.clearedStatus = r.parseClearedStatus(ctx, line[1:]) transactionData.ClearedStatus = r.parseClearedStatus(ctx, line[1:])
} else if line[0] == 'P' { } else if line[0] == 'P' {
transactionData.text = line[1:] transactionData.Text = line[1:]
} else if line[0] == 'M' { } else if line[0] == 'M' {
transactionData.memo = line[1:] transactionData.Memo = line[1:]
} else if line[0] == 'O' { } else if line[0] == 'O' {
transactionData.commission = line[1:] transactionData.Commission = line[1:]
} else if line[0] == 'L' { } else if line[0] == 'L' {
transactionData.accountForTransfer = line[1:] transactionData.AccountForTransfer = line[1:]
} else if line[0] == '$' { } else if line[0] == '$' {
transactionData.amountTransferred = line[1:] transactionData.AmountTransferred = line[1:]
} else { } else {
log.Warnf(ctx, "[qif_data_reader.parseInvestmentTransaction] read unsupported line \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.parseInvestmentTransaction] read unsupported line \"%s\" and skip this line", line)
continue continue
@@ -364,17 +364,17 @@ func (r *qifDataReader) parseAccount(ctx core.Context, data []string) (*qifAccou
} }
if line[0] == 'N' { if line[0] == 'N' {
accountData.name = line[1:] accountData.Name = line[1:]
} else if line[0] == 'T' { } else if line[0] == 'T' {
accountData.accountType = line[1:] accountData.AccountType = line[1:]
} else if line[0] == 'D' { } else if line[0] == 'D' {
accountData.description = line[1:] accountData.Description = line[1:]
} else if line[0] == 'L' { } else if line[0] == 'L' {
accountData.creditLimit = line[1:] accountData.CreditLimit = line[1:]
} else if line[0] == '/' { } else if line[0] == '/' {
accountData.statementBalanceDate = line[1:] accountData.StatementBalanceDate = line[1:]
} else if line[0] == '$' { } else if line[0] == '$' {
accountData.statementBalanceAmount = line[1:] accountData.StatementBalanceAmount = line[1:]
} else { } else {
log.Warnf(ctx, "[qif_data_reader.parseAccount] read unsupported line \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.parseAccount] read unsupported line \"%s\" and skip this line", line)
continue continue
@@ -399,27 +399,27 @@ func (r *qifDataReader) parseCategory(ctx core.Context, data []string) (*qifCate
} }
if line[0] == 'N' { if line[0] == 'N' {
categoryData.name = line[1:] categoryData.Name = line[1:]
} else if line[0] == 'D' { } else if line[0] == 'D' {
categoryData.description = line[1:] categoryData.Description = line[1:]
} else if line[0] == 'T' { } else if line[0] == 'T' {
categoryData.taxRelated = true categoryData.TaxRelated = true
} else if line[0] == 'I' { } else if line[0] == 'I' {
categoryData.categoryType = qifIncomeTransaction categoryData.CategoryType = qifIncomeTransaction
} else if line[0] == 'E' { } else if line[0] == 'E' {
categoryData.categoryType = qifExpenseTransaction categoryData.CategoryType = qifExpenseTransaction
} else if line[0] == 'B' { } else if line[0] == 'B' {
categoryData.budgetAmount = line[1:] categoryData.BudgetAmount = line[1:]
} else if line[0] == 'R' { } else if line[0] == 'R' {
categoryData.taxScheduleInformation = line[1:] categoryData.TaxScheduleInformation = line[1:]
} else { } else {
log.Warnf(ctx, "[qif_data_reader.parseCategory] read unsupported line \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.parseCategory] read unsupported line \"%s\" and skip this line", line)
continue continue
} }
} }
if categoryData.categoryType == "" { if categoryData.CategoryType == "" {
categoryData.categoryType = qifExpenseTransaction categoryData.CategoryType = qifExpenseTransaction
} }
return categoryData, nil return categoryData, nil
@@ -440,9 +440,9 @@ func (r *qifDataReader) parseClass(ctx core.Context, data []string) (*qifClassDa
} }
if line[0] == 'N' { if line[0] == 'N' {
classData.name = line[1:] classData.Name = line[1:]
} else if line[0] == 'D' { } else if line[0] == 'D' {
classData.description = line[1:] classData.Description = line[1:]
} else { } else {
log.Warnf(ctx, "[qif_data_reader.parseClass] read unsupported line \"%s\" and skip this line", line) log.Warnf(ctx, "[qif_data_reader.parseClass] read unsupported line \"%s\" and skip this line", line)
continue continue
+137 -137
View File
@@ -64,48 +64,48 @@ func TestQifDataReaderParse(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(actualData.bankAccountTransactions)) assert.Equal(t, 2, len(actualData.BankAccountTransactions))
assert.Equal(t, "2024/10/9", actualData.bankAccountTransactions[0].date) assert.Equal(t, "2024/10/9", actualData.BankAccountTransactions[0].Date)
assert.Equal(t, "-123.45", actualData.bankAccountTransactions[0].amount) assert.Equal(t, "-123.45", actualData.BankAccountTransactions[0].Amount)
assert.Equal(t, "2024/10/12", actualData.bankAccountTransactions[1].date) assert.Equal(t, "2024/10/12", actualData.BankAccountTransactions[1].Date)
assert.Equal(t, "+234.56", actualData.bankAccountTransactions[1].amount) assert.Equal(t, "+234.56", actualData.BankAccountTransactions[1].Amount)
assert.Equal(t, 1, len(actualData.cashAccountTransactions)) assert.Equal(t, 1, len(actualData.CashAccountTransactions))
assert.Equal(t, "2024/9/1", actualData.cashAccountTransactions[0].date) assert.Equal(t, "2024/9/1", actualData.CashAccountTransactions[0].Date)
assert.Equal(t, "100.00", actualData.cashAccountTransactions[0].amount) assert.Equal(t, "100.00", actualData.CashAccountTransactions[0].Amount)
assert.Equal(t, "Opening Balance", actualData.cashAccountTransactions[0].payee) assert.Equal(t, "Opening Balance", actualData.CashAccountTransactions[0].Payee)
assert.Equal(t, "[Wallet]", actualData.cashAccountTransactions[0].category) assert.Equal(t, "[Wallet]", actualData.CashAccountTransactions[0].Category)
assert.Equal(t, 1, len(actualData.memorizedTransactions)) assert.Equal(t, 1, len(actualData.MemorizedTransactions))
assert.Equal(t, qifCheckTransactionType, actualData.memorizedTransactions[0].transactionType) assert.Equal(t, qifCheckTransactionType, actualData.MemorizedTransactions[0].TransactionType)
assert.Equal(t, "-123.45", actualData.memorizedTransactions[0].amount) assert.Equal(t, "-123.45", actualData.MemorizedTransactions[0].Amount)
assert.Equal(t, "2024/10/13", actualData.memorizedTransactions[0].amortization.firstPaymentDate) assert.Equal(t, "2024/10/13", actualData.MemorizedTransactions[0].Amortization.FirstPaymentDate)
assert.Equal(t, "3", actualData.memorizedTransactions[0].amortization.totalYearsForLoan) assert.Equal(t, "3", actualData.MemorizedTransactions[0].Amortization.TotalYearsForLoan)
assert.Equal(t, "1", actualData.memorizedTransactions[0].amortization.numberOfPayments) assert.Equal(t, "1", actualData.MemorizedTransactions[0].Amortization.NumberOfPayments)
assert.Equal(t, "2", actualData.memorizedTransactions[0].amortization.numberOfPeriodsPerYear) assert.Equal(t, "2", actualData.MemorizedTransactions[0].Amortization.NumberOfPeriodsPerYear)
assert.Equal(t, "12.34", actualData.memorizedTransactions[0].amortization.interestRate) assert.Equal(t, "12.34", actualData.MemorizedTransactions[0].Amortization.InterestRate)
assert.Equal(t, "100.45", actualData.memorizedTransactions[0].amortization.currentLoanBalance) assert.Equal(t, "100.45", actualData.MemorizedTransactions[0].Amortization.CurrentLoanBalance)
assert.Equal(t, "234.56", actualData.memorizedTransactions[0].amortization.originalLoanAmount) assert.Equal(t, "234.56", actualData.MemorizedTransactions[0].Amortization.OriginalLoanAmount)
assert.Equal(t, 1, len(actualData.investmentAccountTransactions)) assert.Equal(t, 1, len(actualData.InvestmentAccountTransactions))
assert.Equal(t, "2024/10/14", actualData.investmentAccountTransactions[0].date) assert.Equal(t, "2024/10/14", actualData.InvestmentAccountTransactions[0].Date)
assert.Equal(t, "Buy", actualData.investmentAccountTransactions[0].action) assert.Equal(t, "Buy", actualData.InvestmentAccountTransactions[0].Action)
assert.Equal(t, "Test", actualData.investmentAccountTransactions[0].security) assert.Equal(t, "Test", actualData.InvestmentAccountTransactions[0].Security)
assert.Equal(t, "12.34", actualData.investmentAccountTransactions[0].price) assert.Equal(t, "12.34", actualData.InvestmentAccountTransactions[0].Price)
assert.Equal(t, "10", actualData.investmentAccountTransactions[0].quantity) assert.Equal(t, "10", actualData.InvestmentAccountTransactions[0].Quantity)
assert.Equal(t, "-123.4", actualData.investmentAccountTransactions[0].amount) assert.Equal(t, "-123.4", actualData.InvestmentAccountTransactions[0].Amount)
assert.Equal(t, 2, len(actualData.accounts)) assert.Equal(t, 2, len(actualData.Accounts))
assert.Equal(t, "Test Account", actualData.accounts[0].name) assert.Equal(t, "Test Account", actualData.Accounts[0].Name)
assert.Equal(t, "Wallet", actualData.accounts[1].name) assert.Equal(t, "Wallet", actualData.Accounts[1].Name)
assert.Equal(t, 1, len(actualData.categories)) assert.Equal(t, 1, len(actualData.Categories))
assert.Equal(t, "Test Category", actualData.categories[0].name) assert.Equal(t, "Test Category", actualData.Categories[0].Name)
assert.Equal(t, qifIncomeTransaction, actualData.categories[0].categoryType) assert.Equal(t, qifIncomeTransaction, actualData.Categories[0].CategoryType)
assert.Equal(t, 1, len(actualData.classes)) assert.Equal(t, 1, len(actualData.Classes))
assert.Equal(t, "Test Class", actualData.classes[0].name) assert.Equal(t, "Test Class", actualData.Classes[0].Name)
assert.Equal(t, "Foo Bar", actualData.classes[0].description) assert.Equal(t, "Foo Bar", actualData.Classes[0].Description)
} }
func TestQifDataReaderParse_AccountEntryBeforeTransaction(t *testing.T) { func TestQifDataReaderParse_AccountEntryBeforeTransaction(t *testing.T) {
@@ -137,21 +137,21 @@ func TestQifDataReaderParse_AccountEntryBeforeTransaction(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(actualData.bankAccountTransactions)) assert.Equal(t, 2, len(actualData.BankAccountTransactions))
assert.Equal(t, "2024/10/9", actualData.bankAccountTransactions[0].date) assert.Equal(t, "2024/10/9", actualData.BankAccountTransactions[0].Date)
assert.Equal(t, "-123.45", actualData.bankAccountTransactions[0].amount) assert.Equal(t, "-123.45", actualData.BankAccountTransactions[0].Amount)
assert.Equal(t, "2024/10/12", actualData.bankAccountTransactions[1].date) assert.Equal(t, "2024/10/12", actualData.BankAccountTransactions[1].Date)
assert.Equal(t, "+234.56", actualData.bankAccountTransactions[1].amount) assert.Equal(t, "+234.56", actualData.BankAccountTransactions[1].Amount)
assert.Equal(t, 1, len(actualData.cashAccountTransactions)) assert.Equal(t, 1, len(actualData.CashAccountTransactions))
assert.Equal(t, "2024/9/1", actualData.cashAccountTransactions[0].date) assert.Equal(t, "2024/9/1", actualData.CashAccountTransactions[0].Date)
assert.Equal(t, "100.00", actualData.cashAccountTransactions[0].amount) assert.Equal(t, "100.00", actualData.CashAccountTransactions[0].Amount)
assert.Equal(t, "Opening Balance", actualData.cashAccountTransactions[0].payee) assert.Equal(t, "Opening Balance", actualData.CashAccountTransactions[0].Payee)
assert.Equal(t, "[Wallet]", actualData.cashAccountTransactions[0].category) assert.Equal(t, "[Wallet]", actualData.CashAccountTransactions[0].Category)
assert.Equal(t, 2, len(actualData.accounts)) assert.Equal(t, 2, len(actualData.Accounts))
assert.Equal(t, "Test Account", actualData.accounts[0].name) assert.Equal(t, "Test Account", actualData.Accounts[0].Name)
assert.Equal(t, "Wallet", actualData.accounts[1].name) assert.Equal(t, "Wallet", actualData.Accounts[1].Name)
} }
func TestQifDataReaderParse_EmptyContent(t *testing.T) { func TestQifDataReaderParse_EmptyContent(t *testing.T) {
@@ -188,13 +188,13 @@ func TestQifDataReaderParse_EmptyEntry(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 0, len(actualData.bankAccountTransactions)) assert.Equal(t, 0, len(actualData.BankAccountTransactions))
assert.Equal(t, 0, len(actualData.cashAccountTransactions)) assert.Equal(t, 0, len(actualData.CashAccountTransactions))
assert.Equal(t, 0, len(actualData.memorizedTransactions)) assert.Equal(t, 0, len(actualData.MemorizedTransactions))
assert.Equal(t, 0, len(actualData.investmentAccountTransactions)) assert.Equal(t, 0, len(actualData.InvestmentAccountTransactions))
assert.Equal(t, 0, len(actualData.accounts)) assert.Equal(t, 0, len(actualData.Accounts))
assert.Equal(t, 0, len(actualData.categories)) assert.Equal(t, 0, len(actualData.Categories))
assert.Equal(t, 0, len(actualData.classes)) assert.Equal(t, 0, len(actualData.Classes))
} }
func TestQifDataReaderParse_UnsupportedEntryHeader(t *testing.T) { func TestQifDataReaderParse_UnsupportedEntryHeader(t *testing.T) {
@@ -215,9 +215,9 @@ func TestQifDataReaderParse_UnsupportedEntryHeader(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, len(actualData.bankAccountTransactions)) assert.Equal(t, 1, len(actualData.BankAccountTransactions))
assert.Equal(t, "2024/10/9", actualData.bankAccountTransactions[0].date) assert.Equal(t, "2024/10/9", actualData.BankAccountTransactions[0].Date)
assert.Equal(t, "-123.45", actualData.bankAccountTransactions[0].amount) assert.Equal(t, "-123.45", actualData.BankAccountTransactions[0].Amount)
} }
func TestQifDataReaderParse_UnsupportedLine(t *testing.T) { func TestQifDataReaderParse_UnsupportedLine(t *testing.T) {
@@ -238,11 +238,11 @@ func TestQifDataReaderParse_UnsupportedLine(t *testing.T) {
actualData, err := reader.read(context) actualData, err := reader.read(context)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, len(actualData.bankAccountTransactions)) assert.Equal(t, 2, len(actualData.BankAccountTransactions))
assert.Equal(t, "2024/10/9", actualData.bankAccountTransactions[0].date) assert.Equal(t, "2024/10/9", actualData.BankAccountTransactions[0].Date)
assert.Equal(t, "-123.45", actualData.bankAccountTransactions[0].amount) assert.Equal(t, "-123.45", actualData.BankAccountTransactions[0].Amount)
assert.Equal(t, "2024/10/11", actualData.bankAccountTransactions[1].date) assert.Equal(t, "2024/10/11", actualData.BankAccountTransactions[1].Date)
assert.Equal(t, "100.00", actualData.bankAccountTransactions[1].amount) assert.Equal(t, "100.00", actualData.BankAccountTransactions[1].Amount)
} }
func TestQifDataReaderParse_NewEntryHeaderAfterUnclosedEntry(t *testing.T) { func TestQifDataReaderParse_NewEntryHeaderAfterUnclosedEntry(t *testing.T) {
@@ -289,26 +289,26 @@ func TestQifDataReaderParseTransaction_SupportedFields(t *testing.T) {
}, false) }, false)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "2024/10/12", actualData.date) assert.Equal(t, "2024/10/12", actualData.Date)
assert.Equal(t, "-123.45", actualData.amount) assert.Equal(t, "-123.45", actualData.Amount)
assert.Equal(t, qifClearedStatusUnreconciled, actualData.clearedStatus) assert.Equal(t, qifClearedStatusUnreconciled, actualData.ClearedStatus)
assert.Equal(t, "100", actualData.num) assert.Equal(t, "100", actualData.Num)
assert.Equal(t, "Foo", actualData.payee) assert.Equal(t, "Foo", actualData.Payee)
assert.Equal(t, "Bar", actualData.memo) assert.Equal(t, "Bar", actualData.Memo)
assert.Equal(t, 3, len(actualData.addresses)) assert.Equal(t, 3, len(actualData.Addresses))
assert.Equal(t, "Address 1", actualData.addresses[0]) assert.Equal(t, "Address 1", actualData.Addresses[0])
assert.Equal(t, "Address 2", actualData.addresses[1]) assert.Equal(t, "Address 2", actualData.Addresses[1])
assert.Equal(t, "Address 3", actualData.addresses[2]) assert.Equal(t, "Address 3", actualData.Addresses[2])
assert.Equal(t, "Test Category", actualData.category) assert.Equal(t, "Test Category", actualData.Category)
assert.Equal(t, 2, len(actualData.subTransactionCategory)) assert.Equal(t, 2, len(actualData.SubTransactionCategory))
assert.Equal(t, "Part1 Category", actualData.subTransactionCategory[0]) assert.Equal(t, "Part1 Category", actualData.SubTransactionCategory[0])
assert.Equal(t, "Part2 Category", actualData.subTransactionCategory[1]) assert.Equal(t, "Part2 Category", actualData.SubTransactionCategory[1])
assert.Equal(t, 2, len(actualData.subTransactionMemo)) assert.Equal(t, 2, len(actualData.SubTransactionMemo))
assert.Equal(t, "Part1 Memo", actualData.subTransactionMemo[0]) assert.Equal(t, "Part1 Memo", actualData.SubTransactionMemo[0])
assert.Equal(t, "Part2 Memo", actualData.subTransactionMemo[1]) assert.Equal(t, "Part2 Memo", actualData.SubTransactionMemo[1])
assert.Equal(t, 2, len(actualData.subTransactionAmount)) assert.Equal(t, 2, len(actualData.SubTransactionAmount))
assert.Equal(t, "-100.00", actualData.subTransactionAmount[0]) assert.Equal(t, "-100.00", actualData.SubTransactionAmount[0])
assert.Equal(t, "-23.45", actualData.subTransactionAmount[1]) assert.Equal(t, "-23.45", actualData.SubTransactionAmount[1])
} }
func TestQifDataReaderParseMemorizedTransaction_SupportedFields(t *testing.T) { func TestQifDataReaderParseMemorizedTransaction_SupportedFields(t *testing.T) {
@@ -333,36 +333,36 @@ func TestQifDataReaderParseMemorizedTransaction_SupportedFields(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifCheckTransactionType, actualData.transactionType) assert.Equal(t, qifCheckTransactionType, actualData.TransactionType)
assert.Equal(t, "2024/10/12", actualData.date) assert.Equal(t, "2024/10/12", actualData.Date)
assert.Equal(t, "-123.45", actualData.amount) assert.Equal(t, "-123.45", actualData.Amount)
assert.Equal(t, qifClearedStatusCleared, actualData.clearedStatus) assert.Equal(t, qifClearedStatusCleared, actualData.ClearedStatus)
assert.Equal(t, "100", actualData.num) assert.Equal(t, "100", actualData.Num)
assert.Equal(t, "Foo", actualData.payee) assert.Equal(t, "Foo", actualData.Payee)
assert.Equal(t, "Bar", actualData.memo) assert.Equal(t, "Bar", actualData.Memo)
assert.Equal(t, "2024/10/13", actualData.amortization.firstPaymentDate) assert.Equal(t, "2024/10/13", actualData.Amortization.FirstPaymentDate)
assert.Equal(t, "3", actualData.amortization.totalYearsForLoan) assert.Equal(t, "3", actualData.Amortization.TotalYearsForLoan)
assert.Equal(t, "1", actualData.amortization.numberOfPayments) assert.Equal(t, "1", actualData.Amortization.NumberOfPayments)
assert.Equal(t, "2", actualData.amortization.numberOfPeriodsPerYear) assert.Equal(t, "2", actualData.Amortization.NumberOfPeriodsPerYear)
assert.Equal(t, "12.34", actualData.amortization.interestRate) assert.Equal(t, "12.34", actualData.Amortization.InterestRate)
assert.Equal(t, "100.45", actualData.amortization.currentLoanBalance) assert.Equal(t, "100.45", actualData.Amortization.CurrentLoanBalance)
assert.Equal(t, "234.56", actualData.amortization.originalLoanAmount) assert.Equal(t, "234.56", actualData.Amortization.OriginalLoanAmount)
actualData, err = reader.parseMemorizedTransaction(context, []string{"KD"}) actualData, err = reader.parseMemorizedTransaction(context, []string{"KD"})
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifDepositTransactionType, actualData.transactionType) assert.Equal(t, qifDepositTransactionType, actualData.TransactionType)
actualData, err = reader.parseMemorizedTransaction(context, []string{"KP"}) actualData, err = reader.parseMemorizedTransaction(context, []string{"KP"})
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifPaymentTransactionType, actualData.transactionType) assert.Equal(t, qifPaymentTransactionType, actualData.TransactionType)
actualData, err = reader.parseMemorizedTransaction(context, []string{"KI"}) actualData, err = reader.parseMemorizedTransaction(context, []string{"KI"})
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifInvestmentTransactionType, actualData.transactionType) assert.Equal(t, qifInvestmentTransactionType, actualData.TransactionType)
actualData, err = reader.parseMemorizedTransaction(context, []string{"KE"}) actualData, err = reader.parseMemorizedTransaction(context, []string{"KE"})
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifElectronicPayeeTransactionType, actualData.transactionType) assert.Equal(t, qifElectronicPayeeTransactionType, actualData.TransactionType)
} }
func TestQifDataReaderParseInvestmentTransaction_SupportedFields(t *testing.T) { func TestQifDataReaderParseInvestmentTransaction_SupportedFields(t *testing.T) {
@@ -385,18 +385,18 @@ func TestQifDataReaderParseInvestmentTransaction_SupportedFields(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "2024/10/12", actualData.date) assert.Equal(t, "2024/10/12", actualData.Date)
assert.Equal(t, "Buy", actualData.action) assert.Equal(t, "Buy", actualData.Action)
assert.Equal(t, "Test", actualData.security) assert.Equal(t, "Test", actualData.Security)
assert.Equal(t, "12.34", actualData.price) assert.Equal(t, "12.34", actualData.Price)
assert.Equal(t, "10", actualData.quantity) assert.Equal(t, "10", actualData.Quantity)
assert.Equal(t, "-123.4", actualData.amount) assert.Equal(t, "-123.4", actualData.Amount)
assert.Equal(t, qifClearedStatusReconciled, actualData.clearedStatus) assert.Equal(t, qifClearedStatusReconciled, actualData.ClearedStatus)
assert.Equal(t, "Foo", actualData.text) assert.Equal(t, "Foo", actualData.Text)
assert.Equal(t, "Bar", actualData.memo) assert.Equal(t, "Bar", actualData.Memo)
assert.Equal(t, "Test2", actualData.commission) assert.Equal(t, "Test2", actualData.Commission)
assert.Equal(t, "Account Name", actualData.accountForTransfer) assert.Equal(t, "Account Name", actualData.AccountForTransfer)
assert.Equal(t, "100", actualData.amountTransferred) assert.Equal(t, "100", actualData.AmountTransferred)
} }
func TestQifDataReaderParseAccount_SupportedFields(t *testing.T) { func TestQifDataReaderParseAccount_SupportedFields(t *testing.T) {
@@ -413,12 +413,12 @@ func TestQifDataReaderParseAccount_SupportedFields(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Account Name", actualData.name) assert.Equal(t, "Account Name", actualData.Name)
assert.Equal(t, "Account Type", actualData.accountType) assert.Equal(t, "Account Type", actualData.AccountType)
assert.Equal(t, "Some Text", actualData.description) assert.Equal(t, "Some Text", actualData.Description)
assert.Equal(t, "1234.56", actualData.creditLimit) assert.Equal(t, "1234.56", actualData.CreditLimit)
assert.Equal(t, "2024/10/12", actualData.statementBalanceDate) assert.Equal(t, "2024/10/12", actualData.StatementBalanceDate)
assert.Equal(t, "123.45", actualData.statementBalanceAmount) assert.Equal(t, "123.45", actualData.StatementBalanceAmount)
} }
func TestQifDataReaderParseCategory_SupportedFields(t *testing.T) { func TestQifDataReaderParseCategory_SupportedFields(t *testing.T) {
@@ -435,12 +435,12 @@ func TestQifDataReaderParseCategory_SupportedFields(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Category Name:Sub Category Name", actualData.name) assert.Equal(t, "Category Name:Sub Category Name", actualData.Name)
assert.Equal(t, "Some Text", actualData.description) assert.Equal(t, "Some Text", actualData.Description)
assert.Equal(t, true, actualData.taxRelated) assert.Equal(t, true, actualData.TaxRelated)
assert.Equal(t, qifIncomeTransaction, actualData.categoryType) assert.Equal(t, qifIncomeTransaction, actualData.CategoryType)
assert.Equal(t, "123.45", actualData.budgetAmount) assert.Equal(t, "123.45", actualData.BudgetAmount)
assert.Equal(t, "Test", actualData.taxScheduleInformation) assert.Equal(t, "Test", actualData.TaxScheduleInformation)
actualData2, err := reader.parseCategory(context, []string{ actualData2, err := reader.parseCategory(context, []string{
"NCategory Name:Sub Category Name", "NCategory Name:Sub Category Name",
@@ -449,10 +449,10 @@ func TestQifDataReaderParseCategory_SupportedFields(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Category Name:Sub Category Name", actualData2.name) assert.Equal(t, "Category Name:Sub Category Name", actualData2.Name)
assert.Equal(t, "Some Text", actualData2.description) assert.Equal(t, "Some Text", actualData2.Description)
assert.Equal(t, false, actualData2.taxRelated) assert.Equal(t, false, actualData2.TaxRelated)
assert.Equal(t, qifExpenseTransaction, actualData2.categoryType) assert.Equal(t, qifExpenseTransaction, actualData2.CategoryType)
actualData3, err := reader.parseCategory(context, []string{ actualData3, err := reader.parseCategory(context, []string{
"NCategory Name:Sub Category Name", "NCategory Name:Sub Category Name",
@@ -460,9 +460,9 @@ func TestQifDataReaderParseCategory_SupportedFields(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Category Name:Sub Category Name", actualData3.name) assert.Equal(t, "Category Name:Sub Category Name", actualData3.Name)
assert.Equal(t, "Some Text", actualData3.description) assert.Equal(t, "Some Text", actualData3.Description)
assert.Equal(t, qifExpenseTransaction, actualData3.categoryType) assert.Equal(t, qifExpenseTransaction, actualData3.CategoryType)
} }
func TestQifDataReaderParseClass_SupportedFields(t *testing.T) { func TestQifDataReaderParseClass_SupportedFields(t *testing.T) {
@@ -475,8 +475,8 @@ func TestQifDataReaderParseClass_SupportedFields(t *testing.T) {
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "Class Name", actualData.name) assert.Equal(t, "Class Name", actualData.Name)
assert.Equal(t, "Some Text", actualData.description) assert.Equal(t, "Some Text", actualData.Description)
} }
func TestQifDataReaderParse_UnsupportedFieldsOrValues(t *testing.T) { func TestQifDataReaderParse_UnsupportedFieldsOrValues(t *testing.T) {
@@ -489,7 +489,7 @@ func TestQifDataReaderParse_UnsupportedFieldsOrValues(t *testing.T) {
"", "",
}, false) }, false)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifClearedStatusUnreconciled, actualTransactionData.clearedStatus) assert.Equal(t, qifClearedStatusUnreconciled, actualTransactionData.ClearedStatus)
actualMemorizedTransactionData, err := reader.parseMemorizedTransaction(context, []string{ actualMemorizedTransactionData, err := reader.parseMemorizedTransaction(context, []string{
"ZTest", "ZTest",
@@ -497,7 +497,7 @@ func TestQifDataReaderParse_UnsupportedFieldsOrValues(t *testing.T) {
"", "",
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, qifInvalidTransactionType, actualMemorizedTransactionData.transactionType) assert.Equal(t, qifInvalidTransactionType, actualMemorizedTransactionData.TransactionType)
_, err = reader.parseInvestmentTransaction(context, []string{ _, err = reader.parseInvestmentTransaction(context, []string{
"ZTest", "ZTest",
@@ -119,11 +119,11 @@ func (t *qifTransactionDataRowIterator) Next(ctx core.Context, user *models.User
func (t *qifTransactionDataRowIterator) parseTransaction(ctx core.Context, user *models.User, qifTransaction *qifTransactionData) (map[datatable.TransactionDataTableColumn]string, error) { func (t *qifTransactionDataRowIterator) parseTransaction(ctx core.Context, user *models.User, qifTransaction *qifTransactionData) (map[datatable.TransactionDataTableColumn]string, error) {
data := make(map[datatable.TransactionDataTableColumn]string, len(qifTransactionSupportedColumns)) data := make(map[datatable.TransactionDataTableColumn]string, len(qifTransactionSupportedColumns))
if qifTransaction.date == "" { if qifTransaction.Date == "" {
return nil, errs.ErrMissingTransactionTime return nil, errs.ErrMissingTransactionTime
} }
transactionTime, err := t.parseTransactionTime(ctx, qifTransaction.date) transactionTime, err := t.parseTransactionTime(ctx, qifTransaction.Date)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -131,37 +131,37 @@ func (t *qifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transactionTime data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = transactionTime
if qifTransaction.amount == "" { if qifTransaction.Amount == "" {
return nil, errs.ErrAmountInvalid return nil, errs.ErrAmountInvalid
} }
amount, err := utils.ParseAmount(strings.ReplaceAll(qifTransaction.amount, ",", "")) // trim thousands separator amount, err := utils.ParseAmount(strings.ReplaceAll(qifTransaction.Amount, ",", "")) // trim thousands separator
if err != nil { if err != nil {
return nil, errs.ErrAmountInvalid return nil, errs.ErrAmountInvalid
} }
if qifTransaction.account != nil { if qifTransaction.Account != nil {
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = qifTransaction.account.name data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = qifTransaction.Account.Name
} else { } else {
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = "" data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = ""
} }
if len(qifTransaction.category) > 0 && qifTransaction.category[0] == '[' && qifTransaction.category[len(qifTransaction.category)-1] == ']' { if len(qifTransaction.Category) > 0 && qifTransaction.Category[0] == '[' && qifTransaction.Category[len(qifTransaction.Category)-1] == ']' {
if qifTransaction.payee == qifOpeningBalancePayeeText { // balance modification if qifTransaction.Payee == qifOpeningBalancePayeeText { // balance modification
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = qifTransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE] data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = qifTransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE]
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount) data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = qifTransaction.category[1 : len(qifTransaction.category)-1] data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = qifTransaction.Category[1 : len(qifTransaction.Category)-1]
} else { // transfer } else { // transfer
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = qifTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER] data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = qifTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
if amount >= 0 { // transfer from [account name] if amount >= 0 { // transfer from [account name]
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount) data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amount)
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME]
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = qifTransaction.category[1 : len(qifTransaction.category)-1] data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = qifTransaction.Category[1 : len(qifTransaction.Category)-1]
} else { // transfer to [account name] } else { // transfer to [account name]
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount) data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = qifTransaction.category[1 : len(qifTransaction.category)-1] data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = qifTransaction.Category[1 : len(qifTransaction.Category)-1]
} }
} }
} else { // income/expense } else { // income/expense
@@ -173,20 +173,20 @@ func (t *qifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount) data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amount)
} }
if strings.Index(qifTransaction.category, ":") > 0 { // category:subcategory if strings.Index(qifTransaction.Category, ":") > 0 { // category:subcategory
categories := strings.Split(qifTransaction.category, ":") categories := strings.Split(qifTransaction.Category, ":")
data[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = categories[0] data[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = categories[0]
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = categories[len(categories)-1] data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = categories[len(categories)-1]
} else { } else {
data[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = "" data[datatable.TRANSACTION_DATA_TABLE_CATEGORY] = ""
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = qifTransaction.category data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = qifTransaction.Category
} }
} }
if qifTransaction.memo != "" { if qifTransaction.Memo != "" {
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = qifTransaction.memo data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = qifTransaction.Memo
} else if qifTransaction.payee != "" && qifTransaction.payee != qifOpeningBalancePayeeText { } else if qifTransaction.Payee != "" && qifTransaction.Payee != qifOpeningBalancePayeeText {
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = qifTransaction.payee data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = qifTransaction.Payee
} }
return data, nil return data, nil
@@ -240,11 +240,11 @@ func createNewQifTransactionDataTable(dateFormatType qifDateFormatType, qifData
} }
allData := make([]*qifTransactionData, 0) allData := make([]*qifTransactionData, 0)
allData = append(allData, qifData.bankAccountTransactions...) allData = append(allData, qifData.BankAccountTransactions...)
allData = append(allData, qifData.cashAccountTransactions...) allData = append(allData, qifData.CashAccountTransactions...)
allData = append(allData, qifData.creditCardAccountTransactions...) allData = append(allData, qifData.CreditCardAccountTransactions...)
allData = append(allData, qifData.assetAccountTransactions...) allData = append(allData, qifData.AssetAccountTransactions...)
allData = append(allData, qifData.liabilityAccountTransactions...) allData = append(allData, qifData.LiabilityAccountTransactions...)
return &qifTransactionDataTable{ return &qifTransactionDataTable{
dateFormatType: dateFormatType, dateFormatType: dateFormatType,