UNPKG

mt940js

Version:

javascript mt940 bank statement parser

450 lines (415 loc) 16.5 kB
const assert = require('chai').assert; const Parser = require('../lib/parser'); const Tags = require('../lib/tags'); const helpers = require('../lib/helperModels'); const DUMMY_STATEMENT_LINES = [ ':20:B4E08MS9D00A0009', ':21:X', ':25:123456789', ':28C:123/1', ':60F:C140507EUR0,00', ':61:1405070507C500,00NTRFNONREF//AUXREF', ':86:LINE1', 'LINE2', ':62F:C140508EUR500,00', ]; const DUMMY_STATEMENT_LINES_WITH_STRUCTURE = [ ':20:B4E08MS9D00A0009', ':21:X', ':25:123456789', ':28C:123/1', ':60F:C140507EUR0,00', ':61:1405070507C500,00NTRFNONREF//AUXREF', ':86:?20some?21data', ':62F:C140508EUR500,00', ]; const DUMMY_STATEMENT_LINES_61_64_65 = [ ':20:B4E08MS9D00A0009', ':21:X', ':25:123456789', ':28C:123/1', ':60F:C140507EUR0,00', ':61:1405070507C500,00NTRFNONREF//AUXREF', 'SUPPLEMENTARY61', ':86:LINE1', 'LINE2', ':62F:C140508EUR500,00', ':64:C140509EUR600,00', ':65:C140510EUR700,00', ':86:statement', 'comment', ]; const DUMMY_STATEMENT_W_MESSAGE_BLOCKS = [ '{1:F01KNABNL2HAXXX0000000000}{2:I940KNABNL2HXXXXN3020}{4:', ':20:B4E08MS9D00A0009', ':21:X', ':25:123456789', ':28C:123/1', ':60F:C140507EUR0,00', ':61:1405070507C500,00NTRFNONREF//AUXREF', ':86:LINE1', 'LINE2', ':62F:C140508EUR500,00', '-}', '{1:F01KNABNL2HAXXX0000000000}{2:I940KNABNL2HXXXXN3020}{4:', ':20:B4E08MS9D00A0009', ':21:X', ':25:123456789', ':28C:123/2', ':60F:C140508EUR500,00', ':62F:C140508EUR500,00', '-}{5:{CAC:VALIDATION SUCCESS}}' ]; function expectedStatement() { return { transactionReference: 'B4E08MS9D00A0009', relatedReference: 'X', accountIdentification: '123456789', number: { statement: '123', sequence: '1', section: '' }, statementDate: helpers.Date.parse('14', '05', '08'), openingBalanceDate: helpers.Date.parse('14', '05', '07'), closingBalanceDate: helpers.Date.parse('14', '05', '08'), currency: 'EUR', openingBalance: 0.0, closingBalance: 500.0, closingAvailableBalanceDate: helpers.Date.parse('14', '05', '08'), forwardAvailableBalanceDate: helpers.Date.parse('14', '05', '08'), closingAvailableBalance: 500.0, forwardAvailableBalance: 500.0, transactions: [ { amount: 500.00, isReversal: false, currency: 'EUR', reference: 'NONREF', bankReference: 'AUXREF', transactionType: 'NTRF', date: helpers.Date.parse('14', '05', '07'), entryDate: helpers.Date.parse('14', '05', '07'), details: 'LINE1\nLINE2', extraDetails: '', fundsCode: '', } ]}; } const DUMMY_GROUP_SIMPLE = [ new Tags.TagTransactionReferenceNumber('B4E08MS9D00A0009'), new Tags.TagAccountIdentification('123456789'), new Tags.TagStatementNumber('123/1'), new Tags.TagOpeningBalance('C140507EUR0,00'), new Tags.TagStatementLine('1405070507C500,00NTRFNONREF//AUXREF'), new Tags.TagTransactionDetails('DETAILS'), new Tags.TagClosingBalance('C140508EUR500,00') ]; const DUMMY_GROUP_STRUCTURED = [ new Tags.TagTransactionReferenceNumber('B4E08MS9D00A0009'), new Tags.TagAccountIdentification('123456789'), new Tags.TagStatementNumber('123/1'), new Tags.TagOpeningBalance('C140507EUR0,00'), new Tags.TagStatementLine('1405070507C500,00NTRFNONREF//AUXREF'), new Tags.TagTransactionDetails('?20Hello?30World'), new Tags.TagClosingBalance('C140508EUR500,00') ]; const DUMMY_GROUP_COMPLEX = [ // 2 detail lines and 2 transactions new Tags.TagTransactionReferenceNumber('B4E08MS9D00A0009'), new Tags.TagRelatedReference('X'), new Tags.TagAccountIdentification('123456789'), new Tags.TagStatementNumber('123/1'), new Tags.TagOpeningBalance('C140507EUR0,00'), new Tags.TagStatementLine('1405070507C500,00NTRFNONREF//AUXREF'), new Tags.TagTransactionDetails('LINE1\nLINE2'), new Tags.TagStatementLine('1405070507C0,00NTRFNONREF2'), new Tags.TagTransactionDetails('LINE1'), new Tags.TagClosingBalance('C140508EUR500,00') ]; /////////////////////////////////////////////////////////////////////////////// // TESTS /////////////////////////////////////////////////////////////////////////////// describe('Parser', () => { describe('Parser methods', () => { it('_splitAndNormalize', () => { const parser = new Parser(); const result = parser._splitAndNormalize('abc \r\n\r\n-'); assert.deepEqual(result, ['abc ']); }); it('_parseLines', () => { const parser = new Parser(); const result = [...parser._parseLines(DUMMY_STATEMENT_LINES)]; assert.equal(8, result.length); assert.deepEqual(result[0], {id: '20', subId: '', data: ['B4E08MS9D00A0009']}); assert.deepEqual(result[1], {id: '21', subId: '', data: ['X']}); assert.deepEqual(result[2], {id: '25', subId: '', data: ['123456789']}); assert.deepEqual(result[3], {id: '28', subId: 'C', data: ['123/1']}); assert.deepEqual(result[4], {id: '60', subId: 'F', data: ['C140507EUR0,00']}); assert.deepEqual(result[5], {id: '61', subId: '', data: ['1405070507C500,00NTRFNONREF//AUXREF']}); assert.deepEqual(result[6], {id: '86', subId: '', data: ['LINE1', 'LINE2']}); assert.deepEqual(result[7], {id: '62', subId: 'F', data: ['C140508EUR500,00']}); }); it('_groupTags', () => { const parser = new Parser(); const groups = [DUMMY_GROUP_SIMPLE, DUMMY_GROUP_COMPLEX]; const result = parser._groupTags([...DUMMY_GROUP_SIMPLE, ...DUMMY_GROUP_COMPLEX]); assert.deepEqual(result, groups); }); it('_buildStatement', () => { const parser = new Parser(); const group = DUMMY_GROUP_COMPLEX; let result = parser._buildStatement(group); let exp = expectedStatement(); exp.transactions.push({ // patch amount: 0.00, currency: 'EUR', isReversal: false, reference: 'NONREF2', bankReference: '', transactionType: 'NTRF', date: helpers.Date.parse('14', '05', '07'), entryDate: helpers.Date.parse('14', '05', '07'), details: 'LINE1', extraDetails: '', fundsCode: '', }); assert.deepEqual(result, exp); assert.isUndefined(result.tags); assert.isUndefined(result.structuredDetails); // with Tags result = parser._buildStatement(group, true); assert.deepEqual(result.tags, group); }); it('_buildStatement structured', () => { const parser = new Parser(); const result = parser._buildStatement(DUMMY_GROUP_STRUCTURED); assert.deepEqual(result.transactions[0].structuredDetails, { '20': 'Hello', '30': 'World', }); }); it('_validateGroup throws', () => { const parser = new Parser(); assert.throws(parser._validateGroup.bind(parser, [ // missing tags new Tags.TagTransactionReferenceNumber('B4E08MS9D00A0009'), ]), /Mandatory tag/); assert.throws(parser._validateGroup.bind(parser, [ // missing tags new Tags.TagClosingBalance('C140508EUR500,00') ]), /Mandatory tag/); assert.throws(parser._validateGroup.bind(parser, [ // missing tags new Tags.TagTransactionReferenceNumber('B4E08MS9D00A0009'), new Tags.TagOpeningBalance('C140507EUR0,00'), new Tags.TagClosingBalance('C140508EUR500,00') ]), /Mandatory tag/); assert.throws(parser._validateGroup.bind(parser, [ // inconsistent currency new Tags.TagTransactionReferenceNumber('B4E08MS9D00A0009'), new Tags.TagAccountIdentification('123456789'), new Tags.TagStatementNumber('123/1'), new Tags.TagOpeningBalance('C140507EUR0,00'), new Tags.TagStatementLine('1405070507C500,00NTRFNONREF//AUXREF'), new Tags.TagTransactionDetails('DETAILS'), new Tags.TagClosingBalance('C140508USD500,00') ]), /Currency markers/); assert.throws(parser._validateGroup.bind(parser, [ // inconsistent balances new Tags.TagTransactionReferenceNumber('B4E08MS9D00A0009'), new Tags.TagAccountIdentification('123456789'), new Tags.TagStatementNumber('123/1'), new Tags.TagOpeningBalance('C140507EUR0,00'), new Tags.TagStatementLine('1405070507C400,00NTRFNONREF//AUXREF'), new Tags.TagTransactionDetails('DETAILS'), new Tags.TagClosingBalance('C140508EUR500,00') ]), /Sum of lines/); }); }); /* MIDDLEWARES */ describe('Middlewares', () => { it('post parse wrong fn throws', () => { const parser = new Parser(); assert.throws(() => parser.usePostParse(1), /middleware must be a function/); }); it('post parse middleware', () => { const parser = new Parser(); parser.usePostParse((s, next) => { s.dummyMarker = true; next(); }); parser.usePostParse((s, next) => { if (s.dummyMarker) s.dummyMarker2 = true; next(); }); const result = parser.parse(DUMMY_STATEMENT_LINES.join('\n')); assert.isDefined(result); assert.isTrue(result[0].dummyMarker); assert.isTrue(result[0].dummyMarker2); }); }); /* INTEGRATION TEST */ describe('Integration test', () => { it('typical statement', () => { const parser = new Parser(); const result = parser.parse(DUMMY_STATEMENT_LINES.join('\n')); assert.equal(result.length, 1); assert.deepEqual(result[0], expectedStatement()); }); it('statement with structured 86', () => { let parser = new Parser(); let result = parser.parse(DUMMY_STATEMENT_LINES_WITH_STRUCTURE.join('\n')); assert.equal(result.length, 1); const exp = expectedStatement(); exp.transactions[0].details = '?20some?21data'; exp.transactions[0].structuredDetails = { '20': 'some', '21': 'data', }; assert.deepEqual(result[0], exp); parser = new Parser({ no86Structure: true }); result = parser.parse(DUMMY_STATEMENT_LINES_WITH_STRUCTURE.join('\n')); delete exp.transactions[0].structuredDetails; assert.deepEqual(result[0], exp); }); it('statement with fields 64, 65, long 61 and statement comment', () => { const parser = new Parser(); const exp = expectedStatement(); // patch data exp.closingAvailableBalanceDate = helpers.Date.parse('14', '05', '09'); exp.forwardAvailableBalanceDate = helpers.Date.parse('14', '05', '10'); exp.closingAvailableBalance = 600.0; exp.forwardAvailableBalance = 700.0; exp.transactions[0].extraDetails = 'SUPPLEMENTARY61'; exp.informationToAccountOwner = 'statement\ncomment'; const result = parser.parse(DUMMY_STATEMENT_LINES_61_64_65.join('\n')); assert.equal(result.length, 1); assert.deepEqual(result[0], exp); }); it('multiple statements with message blocks', () => { const parser = new Parser(); const exp1 = expectedStatement(); const exp2 = expectedStatement(); // patch data exp1.messageBlocks = { '1': { value: 'F01KNABNL2HAXXX0000000000' }, '2': { value: 'I940KNABNL2HXXXXN3020' }, }; exp2.messageBlocks = { '1': { value: 'F01KNABNL2HAXXX0000000000' }, '2': { value: 'I940KNABNL2HXXXXN3020' }, '5': { value: '{CAC:VALIDATION SUCCESS}' }, }; exp2.openingBalance = exp2.closingBalance; exp2.openingBalanceDate = exp2.closingBalanceDate; exp2.number.sequence = '2'; exp2.transactions = []; const result = parser.parse(DUMMY_STATEMENT_W_MESSAGE_BLOCKS.join('\n')); assert.equal(result.length, 2); assert.deepEqual(result[0], exp1); assert.deepEqual(result[1], exp2); }); }); /* NS TESTS */ describe('NS tests', () => { const DUMMY_STATEMENT_LINES_WITH_NS = [ ':20:B4E08MS9D00A0009', ':21:X', ':25:123456789', ':28C:123/1', ':60F:C140507EUR0,00', ':61:1405070507C100,00NTRFNONREF//AUXREF', ':86:LINE1', ':61:1405070507C200,00NTRFNONREF//AUXREF', ':NS:Hello world', ':86:LINE2', ':61:1405070507C300,00NTRFNONREF//AUXREF', ':86:LINE3', ':NS:Hello', 'bank info', ':62F:C140508EUR600,00', ]; it('_parseLines with NS', () => { const parser = new Parser(); const result = [...parser._parseLines(DUMMY_STATEMENT_LINES_WITH_NS)]; assert.deepEqual(result, [ {id: '20', subId: '', data: ['B4E08MS9D00A0009']}, {id: '21', subId: '', data: ['X']}, {id: '25', subId: '', data: ['123456789']}, {id: '28', subId: 'C', data: ['123/1']}, {id: '60', subId: 'F', data: ['C140507EUR0,00']}, {id: '61', subId: '', data: ['1405070507C100,00NTRFNONREF//AUXREF']}, {id: '86', subId: '', data: ['LINE1']}, {id: '61', subId: '', data: ['1405070507C200,00NTRFNONREF//AUXREF']}, {id: 'NS', subId: '', data: ['Hello world']}, {id: '86', subId: '', data: ['LINE2']}, {id: '61', subId: '', data: ['1405070507C300,00NTRFNONREF//AUXREF']}, {id: '86', subId: '', data: ['LINE3']}, {id: 'NS', subId: '', data: ['Hello', 'bank info']}, {id: '62', subId: 'F', data: ['C140508EUR600,00']}, ]); }); it('statement with NS', () => { const parser = new Parser(); const result = parser.parse(DUMMY_STATEMENT_LINES_WITH_NS.join('\n')); assert.equal(result.length, 1); assert.deepEqual(result[0], { transactionReference: 'B4E08MS9D00A0009', relatedReference: 'X', accountIdentification: '123456789', number: { statement: '123', sequence: '1', section: '' }, statementDate: helpers.Date.parse('14', '05', '08'), openingBalanceDate: helpers.Date.parse('14', '05', '07'), closingBalanceDate: helpers.Date.parse('14', '05', '08'), currency: 'EUR', openingBalance: 0.0, closingBalance: 600.0, closingAvailableBalanceDate: helpers.Date.parse('14', '05', '08'), forwardAvailableBalanceDate: helpers.Date.parse('14', '05', '08'), closingAvailableBalance: 600.0, forwardAvailableBalance: 600.0, transactions: [ { amount: 100.00, isReversal: false, currency: 'EUR', reference: 'NONREF', bankReference: 'AUXREF', transactionType: 'NTRF', date: helpers.Date.parse('14', '05', '07'), entryDate: helpers.Date.parse('14', '05', '07'), details: 'LINE1', extraDetails: '', fundsCode: '', }, { amount: 200.00, isReversal: false, currency: 'EUR', reference: 'NONREF', bankReference: 'AUXREF', transactionType: 'NTRF', date: helpers.Date.parse('14', '05', '07'), entryDate: helpers.Date.parse('14', '05', '07'), details: 'LINE2', extraDetails: '', fundsCode: '', nonSwift: 'Hello world', }, { amount: 300.00, isReversal: false, currency: 'EUR', reference: 'NONREF', bankReference: 'AUXREF', transactionType: 'NTRF', date: helpers.Date.parse('14', '05', '07'), entryDate: helpers.Date.parse('14', '05', '07'), details: 'LINE3', extraDetails: '', fundsCode: '', nonSwift: 'Hello\nbank info', }, ]}); }); }); });