diff --git a/pkg/converters/mt/mt_data.go b/pkg/converters/mt/mt_data.go index 42ae69ac..6e09f0e1 100644 --- a/pkg/converters/mt/mt_data.go +++ b/pkg/converters/mt/mt_data.go @@ -1,5 +1,7 @@ package mt +import "strings" + type mtCreditDebitMark string const ( @@ -9,6 +11,10 @@ const ( MT_MARK_REVERSAL_DEBIT mtCreditDebitMark = "RD" ) +const ( + MT_INFORMATION_TO_ACCOUNT_OWNER_TAG_REMITTANCE string = "REMI" +) + // mt940Data defines the structure of mt940 data type mt940Data struct { StatementReferenceNumber string @@ -31,7 +37,7 @@ type mtStatement struct { TransactionTypeIdentificationCode string ReferenceForAccountOwner string ReferenceOfAccountServicingInstitution string - AdditionalInformation []string + InformationToAccountOwner []string } // mtBalance defines the structure of mt940 balance @@ -41,3 +47,27 @@ type mtBalance struct { Currency string Amount string } + +// GetInformationToAccountOwnerMap returns a map of additional information +func (s *mtStatement) GetInformationToAccountOwnerMap() map[string]string { + additionalInfoMap := make(map[string]string, len(s.InformationToAccountOwner)) + + for _, info := range s.InformationToAccountOwner { + items := strings.Split(info, "/") + + if len(items) < 3 { + continue + } + + for i := 2; i < len(items); i += 2 { + key := strings.TrimSpace(items[i-1]) + value := strings.TrimSpace(items[i]) + + if len(key) > 0 { + additionalInfoMap[key] = value + } + } + } + + return additionalInfoMap +} diff --git a/pkg/converters/mt/mt_data_reader.go b/pkg/converters/mt/mt_data_reader.go index 3d9d94a4..f74fb356 100644 --- a/pkg/converters/mt/mt_data_reader.go +++ b/pkg/converters/mt/mt_data_reader.go @@ -17,20 +17,20 @@ const mtBasicHeaderBlockPrefix = "{1:" const mtTextBlockStartPrefix = "{4:" const mtTextBlockEndPrefix = "-}" const mtTagPrefix = ':' -const mtStatementAdditionalInformationMaxLines = 6 +const mtInformationToAccountOwnerMaxLines = 6 const ( - mtTagStatementReferenceNumber = ":20:" - mtTagRelatedReference = ":21:" - mtTagAccountId = ":25:" - mtTagSequentialNumber = ":28C:" - mtTagOpeningBalanceF = ":60F:" - mtTagOpeningBalanceM = ":60M:" - mtTagClosingBalanceF = ":62F:" - mtTagClosingBalanceM = ":62M:" - mtTagClosingAvailableBalance = ":64:" - mtTagStatementLine = ":61:" - mtTagStatementAdditionalInformation = ":86:" + mtTagStatementReferenceNumber = ":20:" + mtTagRelatedReference = ":21:" + mtTagAccountId = ":25:" + mtTagSequentialNumber = ":28C:" + mtTagOpeningBalanceF = ":60F:" + mtTagOpeningBalanceM = ":60M:" + mtTagClosingBalanceF = ":62F:" + mtTagClosingBalanceM = ":62M:" + mtTagClosingAvailableBalance = ":64:" + mtTagStatementLine = ":61:" + mtTagInformationToAccountOwner = ":86:" ) const ( @@ -123,16 +123,16 @@ func (r *mt940DataReader) read(ctx core.Context) (*mt940Data, error) { currentStatement = statement lastTag = mtTagStatementLine - } else if strings.HasPrefix(line, mtTagStatementAdditionalInformation) && currentStatement != nil { - currentStatement.AdditionalInformation = make([]string, 1) - currentStatement.AdditionalInformation[0] = line[len(mtTagStatementAdditionalInformation):] - lastTag = mtTagStatementAdditionalInformation + } else if strings.HasPrefix(line, mtTagInformationToAccountOwner) && currentStatement != nil { + currentStatement.InformationToAccountOwner = make([]string, 1) + currentStatement.InformationToAccountOwner[0] = line[len(mtTagInformationToAccountOwner):] + lastTag = mtTagInformationToAccountOwner } else if line[0] != mtTagPrefix && lastTag == mtTagStatementLine && currentStatement != nil { currentStatement.ReferenceForAccountOwner += line lastTag = "" - } else if line[0] != mtTagPrefix && lastTag == mtTagStatementAdditionalInformation && currentStatement != nil && len(currentStatement.AdditionalInformation) < mtStatementAdditionalInformationMaxLines { - currentStatement.AdditionalInformation = append(currentStatement.AdditionalInformation, line) - lastTag = mtTagStatementAdditionalInformation + } else if line[0] != mtTagPrefix && lastTag == mtTagInformationToAccountOwner && currentStatement != nil && len(currentStatement.InformationToAccountOwner) < mtInformationToAccountOwnerMaxLines { + currentStatement.InformationToAccountOwner = append(currentStatement.InformationToAccountOwner, line) + lastTag = mtTagInformationToAccountOwner } else { log.Warnf(ctx, "[mt_data_reader.read] unsupported line \"%s\" and skip this line", line) } diff --git a/pkg/converters/mt/mt_data_reader_test.go b/pkg/converters/mt/mt_data_reader_test.go index 930e8c95..68bda017 100644 --- a/pkg/converters/mt/mt_data_reader_test.go +++ b/pkg/converters/mt/mt_data_reader_test.go @@ -54,8 +54,8 @@ func TestMT940DataReaderParse(t *testing.T) { assert.Equal(t, "NTRF", actualData.Statements[0].TransactionTypeIdentificationCode) assert.Equal(t, "TEST", actualData.Statements[0].ReferenceForAccountOwner) assert.Equal(t, "ABC123456", actualData.Statements[0].ReferenceOfAccountServicingInstitution) - assert.Equal(t, "First Transaction", actualData.Statements[0].AdditionalInformation[0]) - assert.Equal(t, "Additional Info", actualData.Statements[0].AdditionalInformation[1]) + assert.Equal(t, "First Transaction", actualData.Statements[0].InformationToAccountOwner[0]) + assert.Equal(t, "Additional Info", actualData.Statements[0].InformationToAccountOwner[1]) assert.Equal(t, "250602", actualData.Statements[1].ValueDate) assert.Equal(t, "0620", actualData.Statements[1].EntryDate) @@ -65,8 +65,8 @@ func TestMT940DataReaderParse(t *testing.T) { assert.Equal(t, "NSTF", actualData.Statements[1].TransactionTypeIdentificationCode) assert.Equal(t, "FOOBAR", actualData.Statements[1].ReferenceForAccountOwner) assert.Equal(t, "DEF789012", actualData.Statements[1].ReferenceOfAccountServicingInstitution) - assert.Equal(t, "Second Transaction", actualData.Statements[1].AdditionalInformation[0]) - assert.Equal(t, "More Info", actualData.Statements[1].AdditionalInformation[1]) + assert.Equal(t, "Second Transaction", actualData.Statements[1].InformationToAccountOwner[0]) + assert.Equal(t, "More Info", actualData.Statements[1].InformationToAccountOwner[1]) assert.Equal(t, MT_MARK_CREDIT, actualData.ClosingBalance.DebitCreditMark) assert.Equal(t, "250602", actualData.ClosingBalance.Date) @@ -114,7 +114,7 @@ func TestMT940DataReaderParse_NoBlockHeaderFooter(t *testing.T) { assert.Equal(t, "NTRF", actualData.Statements[0].TransactionTypeIdentificationCode) assert.Equal(t, "TEST", actualData.Statements[0].ReferenceForAccountOwner) assert.Equal(t, "ABC123456", actualData.Statements[0].ReferenceOfAccountServicingInstitution) - assert.Equal(t, "First Transaction", actualData.Statements[0].AdditionalInformation[0]) + assert.Equal(t, "First Transaction", actualData.Statements[0].InformationToAccountOwner[0]) } func TestMT940DataReaderParse_ReferenceForTheAccountOwnerTwoLine(t *testing.T) { @@ -138,7 +138,7 @@ func TestMT940DataReaderParse_ReferenceForTheAccountOwnerTwoLine(t *testing.T) { assert.Equal(t, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", actualData.Statements[0].ReferenceForAccountOwner) } -func TestMT940DataReaderParse_AdditionalInformationSixLine(t *testing.T) { +func TestMT940DataReaderParse_InformationToAccountOwnerSixLine(t *testing.T) { reader := &mt940DataReader{ allLines: []string{ ":61:250601D123,45NTRFTEST", @@ -162,16 +162,16 @@ func TestMT940DataReaderParse_AdditionalInformationSixLine(t *testing.T) { assert.Equal(t, "123,45", actualData.Statements[0].Amount) assert.Equal(t, "NTRF", actualData.Statements[0].TransactionTypeIdentificationCode) assert.Equal(t, "TEST", actualData.Statements[0].ReferenceForAccountOwner) - assert.Equal(t, 6, len(actualData.Statements[0].AdditionalInformation)) - assert.Equal(t, "Additional Info Line 1", actualData.Statements[0].AdditionalInformation[0]) - assert.Equal(t, "Additional Info Line 2", actualData.Statements[0].AdditionalInformation[1]) - assert.Equal(t, "Additional Info Line 3", actualData.Statements[0].AdditionalInformation[2]) - assert.Equal(t, "Additional Info Line 4", actualData.Statements[0].AdditionalInformation[3]) - assert.Equal(t, "Additional Info Line 5", actualData.Statements[0].AdditionalInformation[4]) - assert.Equal(t, "Additional Info Line 6", actualData.Statements[0].AdditionalInformation[5]) + assert.Equal(t, 6, len(actualData.Statements[0].InformationToAccountOwner)) + assert.Equal(t, "Additional Info Line 1", actualData.Statements[0].InformationToAccountOwner[0]) + assert.Equal(t, "Additional Info Line 2", actualData.Statements[0].InformationToAccountOwner[1]) + assert.Equal(t, "Additional Info Line 3", actualData.Statements[0].InformationToAccountOwner[2]) + assert.Equal(t, "Additional Info Line 4", actualData.Statements[0].InformationToAccountOwner[3]) + assert.Equal(t, "Additional Info Line 5", actualData.Statements[0].InformationToAccountOwner[4]) + assert.Equal(t, "Additional Info Line 6", actualData.Statements[0].InformationToAccountOwner[5]) } -func TestMT940DataReaderParse_AdditionalInformationMoreThanSixLine(t *testing.T) { +func TestMT940DataReaderParse_InformationToAccountOwnerMoreThanSixLine(t *testing.T) { reader := &mt940DataReader{ allLines: []string{ ":61:250601D123,45NTRFTEST", @@ -196,13 +196,13 @@ func TestMT940DataReaderParse_AdditionalInformationMoreThanSixLine(t *testing.T) assert.Equal(t, "123,45", actualData.Statements[0].Amount) assert.Equal(t, "NTRF", actualData.Statements[0].TransactionTypeIdentificationCode) assert.Equal(t, "TEST", actualData.Statements[0].ReferenceForAccountOwner) - assert.Equal(t, 6, len(actualData.Statements[0].AdditionalInformation)) - assert.Equal(t, "Additional Info Line 1", actualData.Statements[0].AdditionalInformation[0]) - assert.Equal(t, "Additional Info Line 2", actualData.Statements[0].AdditionalInformation[1]) - assert.Equal(t, "Additional Info Line 3", actualData.Statements[0].AdditionalInformation[2]) - assert.Equal(t, "Additional Info Line 4", actualData.Statements[0].AdditionalInformation[3]) - assert.Equal(t, "Additional Info Line 5", actualData.Statements[0].AdditionalInformation[4]) - assert.Equal(t, "Additional Info Line 6", actualData.Statements[0].AdditionalInformation[5]) + assert.Equal(t, 6, len(actualData.Statements[0].InformationToAccountOwner)) + assert.Equal(t, "Additional Info Line 1", actualData.Statements[0].InformationToAccountOwner[0]) + assert.Equal(t, "Additional Info Line 2", actualData.Statements[0].InformationToAccountOwner[1]) + assert.Equal(t, "Additional Info Line 3", actualData.Statements[0].InformationToAccountOwner[2]) + assert.Equal(t, "Additional Info Line 4", actualData.Statements[0].InformationToAccountOwner[3]) + assert.Equal(t, "Additional Info Line 5", actualData.Statements[0].InformationToAccountOwner[4]) + assert.Equal(t, "Additional Info Line 6", actualData.Statements[0].InformationToAccountOwner[5]) } func TestMT940DataReaderParse_DuplicateBlockHeader(t *testing.T) { diff --git a/pkg/converters/mt/mt_data_test.go b/pkg/converters/mt/mt_data_test.go new file mode 100644 index 00000000..c6185ed6 --- /dev/null +++ b/pkg/converters/mt/mt_data_test.go @@ -0,0 +1,91 @@ +package mt + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMtStatementGetInformationToAccountOwnerMap_OneLineMultiTags(t *testing.T) { + statement := &mtStatement{ + InformationToAccountOwner: []string{ + "/REMI/test value/ABC/123/FOO/Bar", + }, + } + + expectedMap := map[string]string{ + "REMI": "test value", + "ABC": "123", + "FOO": "Bar", + } + + actualMap := statement.GetInformationToAccountOwnerMap() + assert.Equal(t, expectedMap, actualMap) +} + +func TestMtStatementGetInformationToAccountOwnerMap_MultipleLines(t *testing.T) { + statement := &mtStatement{ + InformationToAccountOwner: []string{ + "/REMI/test/ABC/123", + "/FOO/Bar/HELLO/World", + }, + } + + expectedMap := map[string]string{ + "REMI": "test", + "ABC": "123", + "FOO": "Bar", + "HELLO": "World", + } + + actualMap := statement.GetInformationToAccountOwnerMap() + assert.Equal(t, expectedMap, actualMap) +} + +func TestMtStatementGetInformationToAccountOwnerMap_EmptyInformation(t *testing.T) { + statement := &mtStatement{ + InformationToAccountOwner: []string{}, + } + + expectedMap := map[string]string{} + + actualMap := statement.GetInformationToAccountOwnerMap() + assert.Equal(t, expectedMap, actualMap) +} + +func TestMtStatementGetInformationToAccountOwnerMap_InvalidFormat(t *testing.T) { + statement := &mtStatement{ + InformationToAccountOwner: []string{ + "/ABCD", + "EFGH/123", + "/REMI/123/ABC", + }, + } + + expectedMap := map[string]string{ + "REMI": "123", + } + + actualMap := statement.GetInformationToAccountOwnerMap() + assert.Equal(t, expectedMap, actualMap) +} + +func TestMtStatementGetInformationToAccountOwnerMap_EmptyKeyValue(t *testing.T) { + statement := &mtStatement{ + InformationToAccountOwner: []string{ + "/REMI//ABC/ /DEF/456", + "/GHI/123/JKL/def", + }, + } + + expectedMap := map[string]string{ + "REMI": "", + "ABC": "", + "DEF": "456", + "GHI": "123", + "JKL": "def", + } + + actualMap := statement.GetInformationToAccountOwnerMap() + assert.Equal(t, expectedMap, actualMap) +} diff --git a/pkg/converters/mt/mt_transaction_data_table.go b/pkg/converters/mt/mt_transaction_data_table.go index 60afeb59..7104bdfe 100644 --- a/pkg/converters/mt/mt_transaction_data_table.go +++ b/pkg/converters/mt/mt_transaction_data_table.go @@ -151,7 +151,15 @@ func (t *mt940TransactionDataRowIterator) parseTransaction(ctx core.Context, use return nil, errs.ErrTransactionTypeInvalid } - data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = strings.Join(statement.AdditionalInformation, "\n") + informationToAccountOwnerMap := statement.GetInformationToAccountOwnerMap() + + if len(informationToAccountOwnerMap) > 0 { + if value, exists := informationToAccountOwnerMap[MT_INFORMATION_TO_ACCOUNT_OWNER_TAG_REMITTANCE]; exists { + data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = value + } + } else { + data[datatable.TRANSACTION_DATA_TABLE_DESCRIPTION] = strings.Join(statement.InformationToAccountOwner, "\n") + } return data, nil }