parse information to account owner data in mt940 file

This commit is contained in:
MaysWind
2025-06-21 00:52:08 +08:00
parent 4a6f7eb43c
commit b1349f57cd
5 changed files with 171 additions and 42 deletions
+31 -1
View File
@@ -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
}
+19 -19
View File
@@ -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)
}
+21 -21
View File
@@ -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) {
+91
View File
@@ -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)
}
@@ -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
}