parse split transactions in IIF file into separate transactions
This commit is contained in:
@@ -129,11 +129,26 @@ func (r *iifDataReader) read(ctx core.Context) ([]*iifAccountDataset, []*iifTran
|
||||
}
|
||||
} else if lastLineSign == iifTransactionLineSignColumnName || lastLineSign == iifTransactionSplitLineSignColumnName {
|
||||
if items[0] == iifTransactionSplitLineSignColumnName {
|
||||
if currentTransactionData == nil {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] expected current transaction data is not nil, but read \"%s\"", items[0])
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
currentTransactionData.splitData = append(currentTransactionData.splitData, &iifTransactionSplitData{
|
||||
dataItems: items,
|
||||
})
|
||||
lastLineSign = items[0]
|
||||
} else if items[0] == iifTransactionEndLineSignColumnName {
|
||||
if currentTransactionData == nil {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] expected current transaction data is not nil, but read \"%s\"", items[0])
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if len(currentTransactionData.splitData) < 1 {
|
||||
log.Errorf(ctx, "[iif_data_reader.read] expected reading transaction split line, but read \"%s\"", items[0])
|
||||
return nil, nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
currentTransactionDataset.transactions = append(currentTransactionDataset.transactions, currentTransactionData)
|
||||
lastLineSign = ""
|
||||
} else {
|
||||
|
||||
@@ -517,7 +517,7 @@ func TestIIFTransactionDataFileParseImportedData_ParseDescription(t *testing.T)
|
||||
assert.Equal(t, "Test", allNewTransactions[0].Comment)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_NotSupportedToParseSplitTransaction(t *testing.T) {
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransaction(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
@@ -526,11 +526,218 @@ func TestIIFTransactionDataFileParseImportedData_NotSupportedToParseSplitTransac
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, allNewAccounts, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"!ACCNT\tNAME\tACCNTTYPE\n"+
|
||||
"ACCNT\tTest Category\tINC\n"+
|
||||
"ACCNT\tTest Category2\tEXP\n"+
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Category\t-23.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/02/2024\tTest Account\t-100.00\n"+
|
||||
"SPL\t09/02/2024\tTest Category2\t30.00\n"+
|
||||
"SPL\t09/02/2024\tTest Account3\t20.00\n"+
|
||||
"SPL\t09/02/2024\tTest Account4\t50.00\n"+
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/03/2024\tTest Account\t100.00\n"+
|
||||
"SPL\t09/03/2024\tTest Account2\t-100.00\n"+
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/04/2024\tTest Category\t-100.00\n"+
|
||||
"SPL\t09/04/2024\tTest Account\t40.00\n"+
|
||||
"SPL\t09/04/2024\tTest Account2\t60.00\n"+
|
||||
"ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/05/2024\tTest Category2\t100.00\n"+
|
||||
"SPL\t09/05/2024\tTest Account3\t-40.00\n"+
|
||||
"SPL\t09/05/2024\tTest Account4\t-60.00\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, 10, len(allNewTransactions))
|
||||
assert.Equal(t, 4, len(allNewAccounts))
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[0].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[0].Type)
|
||||
assert.Equal(t, int64(1725148800), utils.GetUnixTimeFromTransactionTime(allNewTransactions[0].TransactionTime))
|
||||
assert.Equal(t, int64(2345), allNewTransactions[0].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[0].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category", allNewTransactions[0].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[1].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[1].Type)
|
||||
assert.Equal(t, int64(1725148800), utils.GetUnixTimeFromTransactionTime(allNewTransactions[1].TransactionTime))
|
||||
assert.Equal(t, int64(10000), allNewTransactions[1].Amount)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[1].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[1].OriginalDestinationAccountName)
|
||||
assert.Equal(t, "", allNewTransactions[1].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[2].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[2].Type)
|
||||
assert.Equal(t, int64(1725235200), utils.GetUnixTimeFromTransactionTime(allNewTransactions[2].TransactionTime))
|
||||
assert.Equal(t, int64(3000), allNewTransactions[2].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[2].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category2", allNewTransactions[2].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[3].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[3].Type)
|
||||
assert.Equal(t, int64(1725235200), utils.GetUnixTimeFromTransactionTime(allNewTransactions[3].TransactionTime))
|
||||
assert.Equal(t, int64(2000), allNewTransactions[3].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[3].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account3", allNewTransactions[3].OriginalDestinationAccountName)
|
||||
assert.Equal(t, "", allNewTransactions[3].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[4].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[4].Type)
|
||||
assert.Equal(t, int64(1725235200), utils.GetUnixTimeFromTransactionTime(allNewTransactions[4].TransactionTime))
|
||||
assert.Equal(t, int64(5000), allNewTransactions[4].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[4].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account4", allNewTransactions[4].OriginalDestinationAccountName)
|
||||
assert.Equal(t, "", allNewTransactions[4].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[5].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_TRANSFER_OUT, allNewTransactions[5].Type)
|
||||
assert.Equal(t, int64(1725321600), utils.GetUnixTimeFromTransactionTime(allNewTransactions[5].TransactionTime))
|
||||
assert.Equal(t, int64(10000), allNewTransactions[5].Amount)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[5].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[5].OriginalDestinationAccountName)
|
||||
assert.Equal(t, "", allNewTransactions[5].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[6].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[6].Type)
|
||||
assert.Equal(t, int64(1725408000), utils.GetUnixTimeFromTransactionTime(allNewTransactions[6].TransactionTime))
|
||||
assert.Equal(t, int64(4000), allNewTransactions[6].Amount)
|
||||
assert.Equal(t, "Test Account", allNewTransactions[6].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category", allNewTransactions[6].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[7].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_INCOME, allNewTransactions[7].Type)
|
||||
assert.Equal(t, int64(1725408000), utils.GetUnixTimeFromTransactionTime(allNewTransactions[7].TransactionTime))
|
||||
assert.Equal(t, int64(6000), allNewTransactions[7].Amount)
|
||||
assert.Equal(t, "Test Account2", allNewTransactions[7].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category", allNewTransactions[7].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[8].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[8].Type)
|
||||
assert.Equal(t, int64(1725494400), utils.GetUnixTimeFromTransactionTime(allNewTransactions[8].TransactionTime))
|
||||
assert.Equal(t, int64(4000), allNewTransactions[8].Amount)
|
||||
assert.Equal(t, "Test Account3", allNewTransactions[8].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category2", allNewTransactions[8].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewTransactions[9].Uid)
|
||||
assert.Equal(t, models.TRANSACTION_DB_TYPE_EXPENSE, allNewTransactions[9].Type)
|
||||
assert.Equal(t, int64(1725494400), utils.GetUnixTimeFromTransactionTime(allNewTransactions[9].TransactionTime))
|
||||
assert.Equal(t, int64(6000), allNewTransactions[9].Amount)
|
||||
assert.Equal(t, "Test Account4", allNewTransactions[9].OriginalSourceAccountName)
|
||||
assert.Equal(t, "Test Category2", allNewTransactions[9].OriginalCategoryName)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[0].Uid)
|
||||
assert.Equal(t, "Test Account", allNewAccounts[0].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[0].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[1].Uid)
|
||||
assert.Equal(t, "Test Account2", allNewAccounts[1].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[1].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[2].Uid)
|
||||
assert.Equal(t, "Test Account3", allNewAccounts[2].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[2].Currency)
|
||||
|
||||
assert.Equal(t, int64(1234567890), allNewAccounts[3].Uid)
|
||||
assert.Equal(t, "Test Account4", allNewAccounts[3].Name)
|
||||
assert.Equal(t, "CNY", allNewAccounts[3].Currency)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_ParseSplitTransactionDescription(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!ENDTRNS\t\t\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t\"Test\"\t123.45\t\"foo bar\t#test\"\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"foo\ttest#bar\"\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t\t-23.45\t\n"+
|
||||
"ENDTRNS\t\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(allNewTransactions))
|
||||
assert.Equal(t, "foo\ttest#bar", allNewTransactions[0].Comment)
|
||||
assert.Equal(t, "foo bar\t#test", allNewTransactions[1].Comment)
|
||||
|
||||
allNewTransactions, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!SPL\tDATE\tACCNT\tNAME\tAMOUNT\tMEMO\n"+
|
||||
"!ENDTRNS\t\t\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\tTest\t123.45\t\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t\t-100.00\t\"test\"\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\tfoo\t-12.34\t\n"+
|
||||
"SPL\t09/01/2024\tTest Account4\t\t-11.11\t\n"+
|
||||
"ENDTRNS\t\t\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(allNewTransactions))
|
||||
assert.Equal(t, "test", allNewTransactions[0].Comment)
|
||||
assert.Equal(t, "foo", allNewTransactions[1].Comment)
|
||||
assert.Equal(t, "Test", allNewTransactions[2].Comment)
|
||||
}
|
||||
|
||||
func TestIIFTransactionDataFileParseImportedData_NotSupportedSplitTransaction(t *testing.T) {
|
||||
converter := IifTransactionDataFileImporter
|
||||
context := core.NewNullContext()
|
||||
|
||||
user := &models.User{
|
||||
Uid: 1234567890,
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
// Opening balance transaction
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tTRNSTYPE\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\t\n"+
|
||||
"TRNS\tBEGINBALCHECK\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\tBEGINBALCHECK\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||
"SPL\tBEGINBALCHECK\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrNotSupportedSplitTransactions.Message)
|
||||
|
||||
// Transaction with invalid amount
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123 45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
// Transaction split data with invalid amount
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.45\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-100 00\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrAmountInvalid.Message)
|
||||
|
||||
// Transaction amount not equal to sum of split data amount
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"TRNS\t09/01/2024\tTest Account\t123.00\n"+
|
||||
"SPL\t09/01/2024\tTest Account2\t-100.00\n"+
|
||||
"SPL\t09/01/2024\tTest Account3\t-23.45\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
@@ -546,7 +753,7 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
|
||||
DefaultCurrency: "CNY",
|
||||
}
|
||||
|
||||
// Missing Transaction Line
|
||||
//Missing Transaction Line
|
||||
_, _, _, _, _, _, err := converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
@@ -555,6 +762,14 @@ func TestIIFTransactionDataFileParseImportedData_InvalidDataLines(t *testing.T)
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Transaction And Split Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!SPL\tDATE\tACCNT\tAMOUNT\n"+
|
||||
"!ENDTRNS\t\t\t\n"+
|
||||
"ENDTRNS\t\t\t\n"), 0, nil, nil, nil, nil, nil)
|
||||
assert.EqualError(t, err, errs.ErrInvalidIIFFile.Message)
|
||||
|
||||
// Missing Split Line
|
||||
_, _, _, _, _, _, err = converter.ParseImportedData(context, user, []byte(
|
||||
"!TRNS\tDATE\tACCNT\tAMOUNT\n"+
|
||||
|
||||
@@ -60,6 +60,7 @@ type iifTransactionDataRowIterator struct {
|
||||
dataTable *iifTransactionDataTable
|
||||
currentDatasetIndex int
|
||||
currentIndexInDataset int
|
||||
currentSplitDataIndex int
|
||||
}
|
||||
|
||||
// HasColumn returns whether the transaction data table has specified column
|
||||
@@ -73,8 +74,15 @@ func (t *iifTransactionDataTable) TransactionRowCount() int {
|
||||
totalDataRowCount := 0
|
||||
|
||||
for i := 0; i < len(t.transactionDatasets); i++ {
|
||||
transactions := t.transactionDatasets[i]
|
||||
totalDataRowCount += len(transactions.transactions)
|
||||
datasets := t.transactionDatasets[i]
|
||||
|
||||
for j := 0; j < len(datasets.transactions); j++ {
|
||||
transaction := datasets.transactions[j]
|
||||
|
||||
if transaction.splitData != nil {
|
||||
totalDataRowCount += len(transaction.splitData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalDataRowCount
|
||||
@@ -85,7 +93,8 @@ func (t *iifTransactionDataTable) TransactionRowIterator() datatable.Transaction
|
||||
return &iifTransactionDataRowIterator{
|
||||
dataTable: t,
|
||||
currentDatasetIndex: 0,
|
||||
currentIndexInDataset: -1,
|
||||
currentIndexInDataset: 0,
|
||||
currentSplitDataIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +126,9 @@ func (t *iifTransactionDataRowIterator) HasNext() bool {
|
||||
|
||||
if t.currentIndexInDataset+1 < len(currentDataset.transactions) {
|
||||
return true
|
||||
} else if t.currentIndexInDataset < len(currentDataset.transactions) &&
|
||||
t.currentSplitDataIndex+1 < len(currentDataset.transactions[t.currentIndexInDataset].splitData) {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := t.currentDatasetIndex + 1; i < len(allDatasets); i++ {
|
||||
@@ -135,20 +147,29 @@ func (t *iifTransactionDataRowIterator) HasNext() bool {
|
||||
// Next returns the next imported data row
|
||||
func (t *iifTransactionDataRowIterator) Next(ctx core.Context, user *models.User) (daraRow datatable.TransactionDataRow, err error) {
|
||||
allDatasets := t.dataTable.transactionDatasets
|
||||
currentIndexInDataset := t.currentIndexInDataset
|
||||
|
||||
for i := t.currentDatasetIndex; i < len(allDatasets); i++ {
|
||||
foundNextRow := false
|
||||
dataset := allDatasets[i]
|
||||
|
||||
if currentIndexInDataset+1 < len(dataset.transactions) {
|
||||
for j := t.currentIndexInDataset; j < len(dataset.transactions); j++ {
|
||||
if t.currentSplitDataIndex+1 < len(dataset.transactions[j].splitData) {
|
||||
t.currentSplitDataIndex++
|
||||
foundNextRow = true
|
||||
break
|
||||
}
|
||||
|
||||
t.currentIndexInDataset++
|
||||
currentIndexInDataset = t.currentIndexInDataset
|
||||
t.currentSplitDataIndex = -1
|
||||
}
|
||||
|
||||
if foundNextRow {
|
||||
break
|
||||
}
|
||||
|
||||
t.currentDatasetIndex++
|
||||
t.currentIndexInDataset = -1
|
||||
currentIndexInDataset = -1
|
||||
t.currentIndexInDataset = 0
|
||||
t.currentSplitDataIndex = -1
|
||||
}
|
||||
|
||||
if t.currentDatasetIndex >= len(allDatasets) {
|
||||
@@ -162,9 +183,28 @@ func (t *iifTransactionDataRowIterator) Next(ctx core.Context, user *models.User
|
||||
}
|
||||
|
||||
data := currentDataset.transactions[t.currentIndexInDataset]
|
||||
rowItems, err := t.parseTransaction(ctx, user, currentDataset, data)
|
||||
|
||||
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)
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
if t.currentSplitDataIndex >= len(data.splitData) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(data.splitData) > 1 {
|
||||
_, err := t.isSplitTransactionSupported(ctx, currentDataset, data)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
rowItems, err := t.parseTransaction(ctx, user, currentDataset, data, t.currentSplitDataIndex)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.Next] cannot parsing transaction in row#%d-split#%d (dataset#%d), because %s", t.currentIndexInDataset, t.currentSplitDataIndex, t.currentDatasetIndex, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -174,13 +214,7 @@ func (t *iifTransactionDataRowIterator) Next(ctx core.Context, user *models.User
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user *models.User, dataset *iifTransactionDataset, transactionData *iifTransactionData) (map[datatable.TransactionDataTableColumn]string, error) {
|
||||
if len(transactionData.splitData) < 1 {
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
} else if len(transactionData.splitData) > 1 {
|
||||
return nil, errs.ErrNotSupportedSplitTransactions
|
||||
}
|
||||
|
||||
func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user *models.User, dataset *iifTransactionDataset, transactionData *iifTransactionData, splitDataIndex int) (map[datatable.TransactionDataTableColumn]string, error) {
|
||||
var err error
|
||||
|
||||
data := make(map[datatable.TransactionDataTableColumn]string, len(iifTransactionSupportedColumns))
|
||||
@@ -190,18 +224,18 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transactionType, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionTypeColumnName)
|
||||
accountName1, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAccountNameColumnName)
|
||||
accountName2, _ := dataset.getSplitDataItemValue(transactionData.splitData[0], iifTransactionAccountNameColumnName)
|
||||
amount1, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAmountColumnName)
|
||||
amount2, _ := dataset.getSplitDataItemValue(transactionData.splitData[0], iifTransactionAmountColumnName)
|
||||
amountNum1, err := utils.ParseAmount(strings.ReplaceAll(amount1, ",", ""))
|
||||
transactionType, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionTypeColumnName)
|
||||
mainAccountName, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAccountNameColumnName)
|
||||
splitAccountName, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionAccountNameColumnName)
|
||||
mainAmount, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAmountColumnName)
|
||||
splitAmount, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionAmountColumnName)
|
||||
mainAmountNum, err := parseAmount(mainAmount)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.ErrAmountInvalid
|
||||
}
|
||||
|
||||
amountNum2, err := utils.ParseAmount(strings.ReplaceAll(amount2, ",", ""))
|
||||
splitAmountNum, err := parseAmount(splitAmount)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.ErrAmountInvalid
|
||||
@@ -209,24 +243,35 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
|
||||
if transactionType == iifTransactionTypeBeginningBalance { // balance modification
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = iifTransactionTypeNameMapping[models.TRANSACTION_TYPE_MODIFY_BALANCE]
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = accountName1
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amountNum1)
|
||||
} else if t.dataTable.incomeAccountNames[accountName1] || t.dataTable.incomeAccountNames[accountName2] { // income
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = mainAccountName
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(mainAmountNum)
|
||||
} else if (t.dataTable.incomeAccountNames[mainAccountName] && !t.dataTable.incomeAccountNames[splitAccountName] && !t.dataTable.expenseAccountNames[splitAccountName]) ||
|
||||
(t.dataTable.incomeAccountNames[splitAccountName] && !t.dataTable.incomeAccountNames[mainAccountName] && !t.dataTable.expenseAccountNames[mainAccountName]) { // income
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = iifTransactionTypeNameMapping[models.TRANSACTION_TYPE_INCOME]
|
||||
categoryName := ""
|
||||
accountName := ""
|
||||
amountNum := int64(0)
|
||||
|
||||
if t.dataTable.incomeAccountNames[accountName1] && !t.dataTable.incomeAccountNames[accountName2] {
|
||||
categoryName = accountName1
|
||||
accountName = accountName2
|
||||
amountNum = amountNum2
|
||||
} else if t.dataTable.incomeAccountNames[accountName2] && !t.dataTable.incomeAccountNames[accountName1] {
|
||||
categoryName = accountName2
|
||||
accountName = accountName1
|
||||
amountNum = amountNum1
|
||||
if t.dataTable.incomeAccountNames[mainAccountName] && !t.dataTable.incomeAccountNames[splitAccountName] {
|
||||
categoryName = mainAccountName
|
||||
accountName = splitAccountName
|
||||
|
||||
if len(transactionData.splitData) > 1 {
|
||||
amountNum = splitAmountNum
|
||||
} else {
|
||||
amountNum = -mainAmountNum
|
||||
}
|
||||
} else if t.dataTable.incomeAccountNames[splitAccountName] && !t.dataTable.incomeAccountNames[mainAccountName] {
|
||||
categoryName = splitAccountName
|
||||
accountName = mainAccountName
|
||||
|
||||
if len(transactionData.splitData) > 1 {
|
||||
amountNum = -splitAmountNum
|
||||
} else {
|
||||
amountNum = mainAmountNum
|
||||
}
|
||||
} else {
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.parseTransaction] cannot parse transaction, because two accounts \"%s\" and \"%s\" are all income account", accountName1, accountName2)
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.parseTransaction] cannot parse transaction, because main account \"%s\" and split account \"%s\" are all income account", mainAccountName, splitAccountName)
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
@@ -241,22 +286,33 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = accountName
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amountNum)
|
||||
} else if t.dataTable.expenseAccountNames[accountName1] || t.dataTable.expenseAccountNames[accountName2] { // expense
|
||||
} else if (t.dataTable.expenseAccountNames[mainAccountName] && !t.dataTable.expenseAccountNames[splitAccountName] && !t.dataTable.incomeAccountNames[splitAccountName]) ||
|
||||
(t.dataTable.expenseAccountNames[splitAccountName] && !t.dataTable.expenseAccountNames[mainAccountName] && !t.dataTable.incomeAccountNames[mainAccountName]) { // expense
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = iifTransactionTypeNameMapping[models.TRANSACTION_TYPE_EXPENSE]
|
||||
categoryName := ""
|
||||
accountName := ""
|
||||
amountNum := int64(0)
|
||||
|
||||
if t.dataTable.expenseAccountNames[accountName1] && !t.dataTable.expenseAccountNames[accountName2] {
|
||||
categoryName = accountName1
|
||||
accountName = accountName2
|
||||
amountNum = amountNum2
|
||||
} else if t.dataTable.expenseAccountNames[accountName2] && !t.dataTable.expenseAccountNames[accountName1] {
|
||||
categoryName = accountName2
|
||||
accountName = accountName1
|
||||
amountNum = amountNum1
|
||||
if t.dataTable.expenseAccountNames[mainAccountName] && !t.dataTable.expenseAccountNames[splitAccountName] {
|
||||
categoryName = mainAccountName
|
||||
accountName = splitAccountName
|
||||
|
||||
if len(transactionData.splitData) > 1 {
|
||||
amountNum = -splitAmountNum
|
||||
} else {
|
||||
amountNum = mainAmountNum
|
||||
}
|
||||
} else if t.dataTable.expenseAccountNames[splitAccountName] && !t.dataTable.expenseAccountNames[mainAccountName] {
|
||||
categoryName = splitAccountName
|
||||
accountName = mainAccountName
|
||||
|
||||
if len(transactionData.splitData) > 1 {
|
||||
amountNum = splitAmountNum
|
||||
} else {
|
||||
amountNum = -mainAmountNum
|
||||
}
|
||||
} else {
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.parseTransaction] cannot parse transaction, because two accounts \"%s\" and \"%s\" are all expense account", accountName1, accountName2)
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.parseTransaction] cannot parse transaction, because main account \"%s\" and split account \"%s\" are all expense account", mainAccountName, splitAccountName)
|
||||
return nil, errs.ErrInvalidIIFFile
|
||||
}
|
||||
|
||||
@@ -270,26 +326,57 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
}
|
||||
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = accountName
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amountNum)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amountNum)
|
||||
} else {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TYPE] = iifTransactionTypeNameMapping[models.TRANSACTION_TYPE_TRANSFER]
|
||||
data[datatable.TRANSACTION_DATA_TABLE_SUB_CATEGORY] = ""
|
||||
amountNum := int64(0)
|
||||
relatedAmountNum := int64(0)
|
||||
mainAccountTransferToSplitAccount := false
|
||||
|
||||
if amountNum1 >= 0 {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = accountName2
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amountNum2)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = accountName1
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(amountNum1)
|
||||
} else if amountNum2 >= 0 {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = accountName1
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amountNum1)
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = accountName2
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(amountNum2)
|
||||
if len(transactionData.splitData) > 1 {
|
||||
amountNum = splitAmountNum
|
||||
relatedAmountNum = splitAmountNum
|
||||
mainAccountTransferToSplitAccount = amountNum >= 0
|
||||
} else {
|
||||
if mainAmountNum >= 0 {
|
||||
amountNum = splitAmountNum
|
||||
relatedAmountNum = mainAmountNum
|
||||
mainAccountTransferToSplitAccount = false
|
||||
} else if splitAmountNum >= 0 {
|
||||
amountNum = mainAmountNum
|
||||
relatedAmountNum = splitAmountNum
|
||||
mainAccountTransferToSplitAccount = true
|
||||
}
|
||||
}
|
||||
|
||||
if mainAccountTransferToSplitAccount {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = mainAccountName
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = splitAccountName
|
||||
} else {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_ACCOUNT_NAME] = splitAccountName
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_ACCOUNT_NAME] = mainAccountName
|
||||
}
|
||||
|
||||
if amountNum >= 0 {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(amountNum)
|
||||
} else {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_AMOUNT] = utils.FormatAmount(-amountNum)
|
||||
}
|
||||
|
||||
if relatedAmountNum >= 0 {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(relatedAmountNum)
|
||||
} else {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_RELATED_AMOUNT] = utils.FormatAmount(-relatedAmountNum)
|
||||
}
|
||||
}
|
||||
|
||||
if memo, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionMemoColumnName); memo != "" {
|
||||
if splitMemo, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionMemoColumnName); splitMemo != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = splitMemo
|
||||
} else if memo, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionMemoColumnName); memo != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = memo
|
||||
} else if splitName, _ := dataset.getSplitDataItemValue(transactionData.splitData[splitDataIndex], iifTransactionNameColumnName); splitName != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = splitName
|
||||
} else if name, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionNameColumnName); name != "" {
|
||||
data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = name
|
||||
} else {
|
||||
@@ -299,6 +386,49 @@ func (t *iifTransactionDataRowIterator) parseTransaction(ctx core.Context, user
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (t *iifTransactionDataRowIterator) isSplitTransactionSupported(ctx core.Context, dataset *iifTransactionDataset, transactionData *iifTransactionData) (bool, error) {
|
||||
supportSplitTransactions := true
|
||||
transactionType, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionTypeColumnName)
|
||||
|
||||
if transactionType == iifTransactionTypeBeginningBalance { // balance modification
|
||||
supportSplitTransactions = false
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.isSplitTransactionSupported] cannot parse split balance modification transaction#%d (dataset#%d)", t.currentIndexInDataset, t.currentDatasetIndex)
|
||||
} else {
|
||||
transactionAmountStr, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionAmountColumnName)
|
||||
transactionAmount, err := parseAmount(transactionAmountStr)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.isSplitTransactionSupported] cannot parsing transaction in row#%d (dataset#%d), because transaction amount \"%s\" is invalid", t.currentIndexInDataset, t.currentDatasetIndex, transactionAmountStr)
|
||||
return false, errs.ErrAmountInvalid
|
||||
}
|
||||
|
||||
splitTotalAmount := int64(0)
|
||||
|
||||
for i := 0; i < len(transactionData.splitData); i++ {
|
||||
splitAmountStr, _ := dataset.getSplitDataItemValue(transactionData.splitData[i], iifTransactionAmountColumnName)
|
||||
splitAmount, err := parseAmount(splitAmountStr)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.isSplitTransactionSupported] cannot parsing transaction in row#%d-split#%d (dataset#%d), because split amount \"%s\" is invalid", t.currentIndexInDataset, i, t.currentDatasetIndex, splitAmountStr)
|
||||
return false, errs.ErrAmountInvalid
|
||||
}
|
||||
|
||||
splitTotalAmount += splitAmount
|
||||
}
|
||||
|
||||
if splitTotalAmount != -transactionAmount {
|
||||
supportSplitTransactions = false
|
||||
log.Errorf(ctx, "[iif_transaction_data_table.isSplitTransactionSupported] cannot parse split transaction#%d (dataset#%d), because the sum amount of each split data \"%d\" not equal to the transaction amount \"%d\"", t.currentIndexInDataset, t.currentDatasetIndex, splitTotalAmount, -transactionAmount)
|
||||
}
|
||||
}
|
||||
|
||||
if len(transactionData.splitData) > 1 && !supportSplitTransactions {
|
||||
return false, errs.ErrNotSupportedSplitTransactions
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (t *iifTransactionDataRowIterator) parseTransactionTime(dataset *iifTransactionDataset, transactionData *iifTransactionData) (string, error) {
|
||||
date, _ := dataset.getTransactionDataItemValue(transactionData, iifTransactionDateColumnName)
|
||||
dateParts := strings.Split(date, "/")
|
||||
@@ -395,3 +525,7 @@ func getIncomeAndExpenseAccountNameMap(accountDatasets []*iifAccountDataset) (in
|
||||
|
||||
return incomeAccountNames, expenseAccountNames
|
||||
}
|
||||
|
||||
func parseAmount(amount string) (int64, error) {
|
||||
return utils.ParseAmount(strings.ReplaceAll(amount, ",", ""))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user