@ashray.mehta/statement-converter 1.4.4-alpha.1 → 1.4.4-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.github/dependabot.yml +7 -0
  2. package/.github/workflows/node.js.yml +24 -0
  3. package/.github/workflows/npm-publish.yml +35 -0
  4. package/.mocharc.json +5 -0
  5. package/package.json +3 -5
  6. package/src/StatementConverter.ts +32 -0
  7. package/src/adapters/ABNAdapter.ts +45 -0
  8. package/src/adapters/AxisAdapter.ts +42 -0
  9. package/src/adapters/ICICIAdapter.ts +50 -0
  10. package/src/adapters/ICICICreditCardAdapter.ts +50 -0
  11. package/src/adapters/MT940Adapter.ts +49 -0
  12. package/src/adapters/N26Adapter.ts +45 -0
  13. package/src/adapters/StandardCharteredAdapter.ts +46 -0
  14. package/src/adapters/TransactionAdapter.ts +8 -0
  15. package/src/adapters/TransactionsQifConverter.ts +28 -0
  16. package/src/helpers/NumberUtil.ts +27 -0
  17. package/src/helpers/XLSXUtil.ts +38 -0
  18. package/{build/out/index.d.ts → src/index.ts} +1 -1
  19. package/src/models/Bank.ts +9 -0
  20. package/{build/out/models/Transaction.d.ts → src/models/Transaction.ts} +1 -1
  21. package/test/AxisBankStatement.xls +0 -0
  22. package/test/ICICIBankStatement.xls +0 -0
  23. package/test/N26Statement.csv +29 -0
  24. package/test/NumberUtil.spec.js +32 -0
  25. package/test/StatementConverter.spec.js +34 -0
  26. package/tsconfig.json +17 -0
  27. package/build/out/StatementConverter.d.ts +0 -7
  28. package/build/out/StatementConverter.js +0 -45
  29. package/build/out/adapters/ABNAdapter.d.ts +0 -7
  30. package/build/out/adapters/ABNAdapter.js +0 -55
  31. package/build/out/adapters/AxisAdapter.d.ts +0 -7
  32. package/build/out/adapters/AxisAdapter.js +0 -53
  33. package/build/out/adapters/ICICIAdapter.d.ts +0 -8
  34. package/build/out/adapters/ICICIAdapter.js +0 -56
  35. package/build/out/adapters/ICICICreditCardAdapter.d.ts +0 -8
  36. package/build/out/adapters/ICICICreditCardAdapter.js +0 -56
  37. package/build/out/adapters/MT940Adapter.d.ts +0 -7
  38. package/build/out/adapters/MT940Adapter.js +0 -56
  39. package/build/out/adapters/N26Adapter.d.ts +0 -7
  40. package/build/out/adapters/N26Adapter.js +0 -56
  41. package/build/out/adapters/StandardCharteredAdapter.d.ts +0 -7
  42. package/build/out/adapters/StandardCharteredAdapter.js +0 -52
  43. package/build/out/adapters/TransactionAdapter.d.ts +0 -6
  44. package/build/out/adapters/TransactionAdapter.js +0 -6
  45. package/build/out/adapters/TransactionsQifConverter.d.ts +0 -5
  46. package/build/out/adapters/TransactionsQifConverter.js +0 -31
  47. package/build/out/helpers/NumberUtil.d.ts +0 -4
  48. package/build/out/helpers/NumberUtil.js +0 -28
  49. package/build/out/helpers/XLSXUtil.d.ts +0 -6
  50. package/build/out/helpers/XLSXUtil.js +0 -41
  51. package/build/out/index.js +0 -18
  52. package/build/out/models/Bank.d.ts +0 -9
  53. package/build/out/models/Bank.js +0 -13
  54. package/build/out/models/Transaction.js +0 -2
@@ -0,0 +1,7 @@
1
+ version: 1
2
+ update_configs:
3
+ # Keep package.json (& lockfiles) up to date as soon as
4
+ # new versions are published to the npm registry
5
+ - package_manager: "javascript"
6
+ directory: "/"
7
+ update_schedule: "monthly"
@@ -0,0 +1,24 @@
1
+ # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
+
4
+ name: Build
5
+
6
+ on:
7
+ push:
8
+ branches: [ master ]
9
+ pull_request:
10
+ branches: [ master ]
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Setup Node.js
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version-file: 'package.json'
23
+ - run: npm ci
24
+ - run: npm run build --if-present
@@ -0,0 +1,35 @@
1
+ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3
+
4
+ name: Node.js Package
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - name: Setup Node.js
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version-file: 'package.json'
19
+ - run: npm ci
20
+ - run: npm test
21
+
22
+ publish-npm:
23
+ needs: build
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-node@v4
28
+ with:
29
+ node-version-file: 'package.json'
30
+ registry-url: https://registry.npmjs.org/
31
+ - run: npm ci
32
+ - run: npm run build
33
+ - run: npm publish
34
+ env:
35
+ NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/.mocharc.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "exit": true,
3
+ "recursive": true,
4
+ "require": "ts-node/register"
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashray.mehta/statement-converter",
3
- "version": "1.4.4-alpha.1",
3
+ "version": "1.4.4-alpha.2",
4
4
  "description": "Library to convert statements from frequently used banks to QIF.",
5
5
  "main": "dist/out/index.js",
6
6
  "types": "dist/out/index.d.ts",
@@ -8,6 +8,7 @@
8
8
  "node": ">=20.0.0"
9
9
  },
10
10
  "scripts": {
11
+ "prepublishOnly": "cp package.json ./build/out/",
11
12
  "prebuild": "npm run clean",
12
13
  "build": "tsc",
13
14
  "clean": "rm -rf build"
@@ -38,8 +39,5 @@
38
39
  "mocha": "^10.4.0",
39
40
  "ts-node": "^10.9.1",
40
41
  "typescript": "^4.8.3"
41
- },
42
- "files": [
43
- "build/out/*"
44
- ]
42
+ }
45
43
  }
@@ -0,0 +1,32 @@
1
+ import { Bank } from "./models/Bank";
2
+ import { N26Adapter } from "./adapters/N26Adapter";
3
+ import { ABNAdapter } from "./adapters/ABNAdapter";
4
+ import { AxisAdapter } from "./adapters/AxisAdapter";
5
+ import { ICICIAdapter } from "./adapters/ICICIAdapter";
6
+ import { ICICICreditCardAdapter } from "./adapters/ICICICreditCardAdapter";
7
+ import { StandardCharteredAdapter } from "./adapters/StandardCharteredAdapter";
8
+ import { TransactionsQifConverter } from "./adapters/TransactionsQifConverter";
9
+ import { MT940Adapter } from "./adapters/MT940Adapter";
10
+
11
+ export class StatementConverter {
12
+ private readonly transactionsToQif = new TransactionsQifConverter();
13
+ private readonly allAdapters = [
14
+ new AxisAdapter(),
15
+ new ICICIAdapter(),
16
+ new ICICICreditCardAdapter(),
17
+ new StandardCharteredAdapter(),
18
+ new ABNAdapter(),
19
+ new N26Adapter(),
20
+ new MT940Adapter(),
21
+ ];
22
+
23
+ public async convert(bank: Bank, fileData: ArrayBuffer): Promise<Buffer> {
24
+ const adapter = this.allAdapters.find((adapter) => adapter.supports(bank));
25
+ if (!adapter) {
26
+ throw new Error(`No adapter found for bank [${bank}]`);
27
+ }
28
+
29
+ const transactions = await adapter.convert(fileData);
30
+ return this.transactionsToQif.convert(transactions);
31
+ }
32
+ }
@@ -0,0 +1,45 @@
1
+ import { Bank } from '..';
2
+ import { XLSXUtil } from '../helpers/XLSXUtil';
3
+ import { Transaction } from '../models/Transaction';
4
+ import { TransactionAdapter } from './TransactionAdapter';
5
+ import XLSX = require('xlsx');
6
+ import moment = require('moment');
7
+
8
+ export class ABNAdapter extends TransactionAdapter {
9
+ public async convert(xlsxData: ArrayBuffer): Promise<Transaction[]> {
10
+ const workBook = XLSX.read(xlsxData, { raw: true, type: 'array' });
11
+ const sheet = workBook.Sheets[workBook.SheetNames[0]];
12
+ const addressForTransactionDate = XLSXUtil.findText(sheet, "transactiondate");
13
+ const addressForDescription = XLSXUtil.findText(sheet, "description");
14
+ const addressForAmount = XLSXUtil.findText(sheet, "amount");
15
+ const startingRow = addressForTransactionDate.r + 1;
16
+ const range = XLSX.utils.decode_range(sheet['!ref']);
17
+ const rangeEnd = range.e;
18
+ const lastRow = rangeEnd.r;
19
+ const rows = [...Array(1 + lastRow - startingRow).keys()].map(v => startingRow + v);
20
+ return rows.map(row => {
21
+ const parsedDate = moment(XLSXUtil.getCellValue(sheet, row, addressForTransactionDate.c) as string, 'YYYYMMDD');
22
+ if (!parsedDate.isValid()) return undefined;
23
+ const date = parsedDate.toDate();
24
+
25
+ const amount = XLSXUtil.getNumberInCell(sheet, row, addressForAmount.c);
26
+ const outflow = amount < 0 ? Math.abs(amount) : 0;
27
+ const inflow = amount >= 0 ? amount : 0;
28
+
29
+ const memo = XLSXUtil.getCellValue(sheet, row, addressForDescription.c);
30
+
31
+ return <Transaction>{
32
+ Payee: memo,
33
+ Outflow: outflow,
34
+ Inflow: inflow,
35
+ Date: date,
36
+ Memo: memo,
37
+ Category: null
38
+ };
39
+ }).filter(row => !!row);
40
+ }
41
+
42
+ public supports(bank: Bank): boolean {
43
+ return bank === Bank.ABN;
44
+ }
45
+ }
@@ -0,0 +1,42 @@
1
+ import { Bank } from '..';
2
+ import { XLSXUtil } from '../helpers/XLSXUtil';
3
+ import { Transaction } from '../models/Transaction';
4
+ import { TransactionAdapter } from './TransactionAdapter';
5
+ import XLSX = require("xlsx");
6
+ import moment = require('moment');
7
+
8
+ export class AxisAdapter extends TransactionAdapter {
9
+ public async convert(xlsxData: ArrayBuffer): Promise<Transaction[]> {
10
+ const workBook = XLSX.read(xlsxData, { raw: true, type: 'array' });
11
+ const sheet = workBook.Sheets[workBook.SheetNames[0]];
12
+ const addressForTransactionDate = XLSXUtil.findText(sheet, "Tran Date");
13
+ const addressForDetails = XLSXUtil.findText(sheet, "PARTICULARS");
14
+ const addressForDepositAmount = XLSXUtil.findText(sheet, "CR");
15
+ const addressForWithdrawalAmount = XLSXUtil.findText(sheet, "DR");
16
+ const startingRow = addressForTransactionDate.r + 1;
17
+ const range = XLSX.utils.decode_range(sheet['!ref']);
18
+ const rangeEnd = range.e;
19
+ const lastRow = rangeEnd.r;
20
+ const rows = [...Array(1 + lastRow - startingRow).keys()].map(v => startingRow + v);
21
+ return rows.map(row => {
22
+ const parsedDate = moment(XLSXUtil.getCellValue(sheet, row, addressForTransactionDate.c) as string, 'DD-MM-YYYY');
23
+ if (!parsedDate.isValid()) return undefined;
24
+
25
+ const date = parsedDate.toDate();
26
+ const payee = XLSXUtil.getCellValue(sheet, row, addressForDetails.c);
27
+
28
+ return <Transaction>{
29
+ Payee: payee,
30
+ Outflow: XLSXUtil.getNumberInCell(sheet, row, addressForWithdrawalAmount.c),
31
+ Inflow: XLSXUtil.getNumberInCell(sheet, row, addressForDepositAmount.c),
32
+ Date: date,
33
+ Memo: payee,
34
+ Category: null
35
+ };
36
+ }).filter(row => !!row);
37
+ }
38
+
39
+ public supports(bank: Bank) {
40
+ return bank === Bank.Axis;
41
+ }
42
+ }
@@ -0,0 +1,50 @@
1
+ import XLSX = require('xlsx');
2
+ import { Bank } from '..';
3
+ import { WorkSheet } from 'xlsx';
4
+ import { XLSXUtil } from '../helpers/XLSXUtil';
5
+ import { Transaction } from '../models/Transaction';
6
+ import { TransactionAdapter } from './TransactionAdapter';
7
+ import moment = require('moment');
8
+
9
+ export class ICICIAdapter extends TransactionAdapter {
10
+ public async convert(xlsxData: ArrayBuffer): Promise<Transaction[]> {
11
+ const workBook = XLSX.read(xlsxData, { raw: true, type: 'array' });
12
+ const sheet = workBook.Sheets[workBook.SheetNames[0]];
13
+
14
+ const addressForTransactionDate = XLSXUtil.findText(sheet, "Transaction Date");
15
+ const addressForDetails = XLSXUtil.findText(sheet, "Transaction Remark");
16
+ const addressForAmount = XLSXUtil.findText(sheet, "Amount (INR)");
17
+ const addressForCreditOrDebit = XLSXUtil.findText(sheet, "CR/DR");
18
+
19
+ const addressForLegend = XLSXUtil.findText(sheet, "Legends Used in Account Statement");
20
+ const startingRow = addressForTransactionDate.r + 1;
21
+ const lastRow = addressForLegend.r - 1;
22
+
23
+ const rows = [...Array(1 + lastRow - startingRow).keys()].map(v => startingRow + v);
24
+
25
+ return rows.map(row => {
26
+ const isOutflow = this.isOutflow(sheet, row, addressForCreditOrDebit.c);
27
+ const amount = XLSXUtil.getNumberInCell(sheet, row, addressForAmount.c);
28
+ const date = moment(XLSXUtil.getCellValue(sheet, row, addressForTransactionDate.c) as string, 'DD/MM/YYYY').toDate();
29
+ const payee = XLSXUtil.getCellValue(sheet, row, addressForDetails.c);
30
+
31
+ return <Transaction>{
32
+ Payee: payee,
33
+ Outflow: isOutflow ? amount : 0,
34
+ Inflow: !isOutflow ? amount : 0,
35
+ Date: date,
36
+ Memo: payee,
37
+ Category: null
38
+ };
39
+ });
40
+ }
41
+
42
+ private isOutflow(sheet: WorkSheet, row: number, column: number): boolean {
43
+ const cellValue = XLSXUtil.getCellValue(sheet, row, column) as string;
44
+ return cellValue.includes("Dr.");
45
+ };
46
+
47
+ public supports(bank: Bank): boolean {
48
+ return bank === Bank.ICICI;
49
+ }
50
+ }
@@ -0,0 +1,50 @@
1
+ import XLSX = require('xlsx');
2
+ import { Bank } from '..';
3
+ import { WorkSheet } from 'xlsx';
4
+ import { XLSXUtil } from '../helpers/XLSXUtil';
5
+ import { Transaction } from '../models/Transaction';
6
+ import { TransactionAdapter } from './TransactionAdapter';
7
+ import moment = require('moment');
8
+
9
+ export class ICICICreditCardAdapter extends TransactionAdapter {
10
+ public async convert(xlsxData: ArrayBuffer): Promise<Transaction[]> {
11
+ const workBook = XLSX.read(xlsxData, { raw: true, type: 'array' });
12
+ const sheet = workBook.Sheets[workBook.SheetNames[0]];
13
+
14
+ const addressForTransactionDate = XLSXUtil.findText(sheet, "Transaction Date");
15
+ const addressForDetails = XLSXUtil.findText(sheet, "Details");
16
+ const addressForAmount = XLSXUtil.findText(sheet, "Amount (INR)");
17
+ const addressForReferenceNumber = XLSXUtil.findText(sheet, "Reference Number");
18
+
19
+ const startingRow = addressForTransactionDate.r + 1;
20
+ const range = XLSX.utils.decode_range(sheet['!ref']);
21
+
22
+ const rangeEnd = range.e;
23
+ const lastRow = rangeEnd.r;
24
+
25
+ const rows = [...Array(1 + lastRow - startingRow).keys()].map(v => startingRow + v);
26
+
27
+ return rows.map(row => {
28
+ const { isOutflow, amount } = this.determineDebitOrCredit(sheet, row, addressForAmount.c);
29
+ const date = moment(XLSXUtil.getCellValue(sheet, row, addressForTransactionDate.c) as string, 'DD/MM/YYYY').toDate();
30
+ return <Transaction>{
31
+ Payee: XLSXUtil.getCellValue(sheet, row, addressForDetails.c),
32
+ Outflow: isOutflow ? amount : 0,
33
+ Inflow: !isOutflow ? amount : 0,
34
+ Date: date,
35
+ Memo: XLSXUtil.getCellValue(sheet, row, addressForReferenceNumber.c),
36
+ Category: null
37
+ };
38
+ });
39
+ }
40
+
41
+ private determineDebitOrCredit(sheet: WorkSheet, row: number, column: number): { isOutflow: boolean, amount: number } {
42
+ const cellValue = XLSXUtil.getCellValue(sheet, row, column) as string;
43
+ // TODO - Replace parsing with Globalize
44
+ return { isOutflow: cellValue.includes("Dr."), amount: parseFloat(cellValue.replace(/,/g, '')) };
45
+ };
46
+
47
+ public supports(bank: Bank): boolean {
48
+ return bank === Bank.ICICICreditCard;
49
+ }
50
+ }
@@ -0,0 +1,49 @@
1
+ import { Bank } from '..';
2
+ import { Transaction } from '../models/Transaction';
3
+ import { TransactionAdapter } from './TransactionAdapter';
4
+ import parser from 'stitch-swiftmessageparser';
5
+ import {flatMap, filter, isEmpty, negate} from 'lodash';
6
+ import { Transaction as SwiftTransaction } from 'stitch-swiftmessageparser/dist/lib/transaction';
7
+ import moment = require('moment');
8
+
9
+ export class MT940Adapter extends TransactionAdapter {
10
+ public async convert(data: ArrayBuffer): Promise<Transaction[]> {
11
+ const rows = parser.parse({
12
+ data: new TextDecoder().decode(data),
13
+ type: "mt940",
14
+ });
15
+
16
+ return flatMap(rows, row => row.transactions)
17
+ .map((row: SwiftTransaction) => {
18
+ const parsedDate = moment(row.date);
19
+ const date = parsedDate.toDate();
20
+
21
+ const amount = row.amount;
22
+ const outflow = amount.isNegative() ? amount.abs().toNumber() : 0;
23
+ const inflow = amount.isPositive() ? amount.toNumber() : 0;
24
+
25
+ const memo = filter([
26
+ row.bankReference,
27
+ row.details,
28
+ row.extraDetails,
29
+ row.reference,
30
+ filter(row.detailSegments, negate(isEmpty)).join(" - ")
31
+ ], negate(isEmpty))
32
+ .join(" - ")
33
+ .trim();
34
+
35
+ return <Transaction>{
36
+ Payee: memo,
37
+ Outflow: outflow,
38
+ Inflow: inflow,
39
+ Date: date,
40
+ Memo: memo,
41
+ Category: null
42
+ };
43
+ }).filter(row => !!row);
44
+ }
45
+
46
+ public supports(bank: Bank): boolean {
47
+ return bank === Bank.GenericMT940;
48
+ }
49
+ }
@@ -0,0 +1,45 @@
1
+ import { Bank } from '..';
2
+ import { XLSXUtil } from '../helpers/XLSXUtil';
3
+ import { Transaction } from '../models/Transaction';
4
+ import { TransactionAdapter } from './TransactionAdapter';
5
+ import XLSX = require('xlsx');
6
+ import moment = require('moment');
7
+
8
+ export class N26Adapter extends TransactionAdapter {
9
+ public async convert(xlsxData: ArrayBuffer): Promise<Transaction[]> {
10
+ const workBook = XLSX.read(xlsxData, { raw: true, type: 'array' });
11
+ const sheet = workBook.Sheets[workBook.SheetNames[0]];
12
+ const addressForTransactionDate = XLSXUtil.findText(sheet, "Date");
13
+ const addressForDescription = XLSXUtil.findText(sheet, "Payment reference");
14
+ const addressForAmount = XLSXUtil.findText(sheet, "Amount (EUR)");
15
+ const addressForPayee = XLSXUtil.findText(sheet, "Payee");
16
+ const addressForCategory = XLSXUtil.findText(sheet, "Transaction type");
17
+ const startingRow = addressForTransactionDate.r + 1;
18
+ const range = XLSX.utils.decode_range(sheet['!ref']);
19
+ const rangeEnd = range.e;
20
+ const lastRow = rangeEnd.r;
21
+ const rows = [...Array(1 + lastRow - startingRow).keys()].map(v => startingRow + v);
22
+ return rows.map(row => {
23
+ const parsedDate = moment(XLSXUtil.getCellValue(sheet, row, addressForTransactionDate.c) as string, 'YYYYMMDD');
24
+ if (!parsedDate.isValid()) return undefined;
25
+ const date = parsedDate.toDate();
26
+
27
+ const amount = XLSXUtil.getNumberInCell(sheet, row, addressForAmount.c);
28
+ const outflow = amount < 0 ? Math.abs(amount) : 0;
29
+ const inflow = amount >= 0 ? amount : 0;
30
+
31
+ return <Transaction>{
32
+ Payee: XLSXUtil.getCellValue(sheet, row, addressForPayee.c),
33
+ Outflow: outflow,
34
+ Inflow: inflow,
35
+ Date: date,
36
+ Memo: XLSXUtil.getCellValue(sheet, row, addressForDescription.c),
37
+ Category: XLSXUtil.getCellValue(sheet, row, addressForCategory.c)
38
+ };
39
+ }).filter(row => !!row);
40
+ }
41
+
42
+ public supports(bank: Bank): boolean {
43
+ return bank === Bank.N26;
44
+ }
45
+ }
@@ -0,0 +1,46 @@
1
+ import { Bank } from '..';
2
+ import { XLSXUtil } from '../helpers/XLSXUtil';
3
+ import { Transaction } from '../models/Transaction';
4
+ import { TransactionAdapter } from './TransactionAdapter';
5
+ import XLSX = require("xlsx");
6
+ import moment = require('moment');
7
+
8
+ export class StandardCharteredAdapter extends TransactionAdapter {
9
+ public async convert(xlsxData: ArrayBuffer): Promise<Transaction[]> {
10
+ const workBook = XLSX.read(xlsxData, { raw: true, type: 'array' });
11
+ const sheet = workBook.Sheets[workBook.SheetNames[0]];
12
+
13
+ const addressForTransactionDate = XLSXUtil.findText(sheet, "Date");
14
+ const addressForDetails = XLSXUtil.findText(sheet, "Transaction");
15
+ const addressForDepositAmount = XLSXUtil.findText(sheet, "Deposit");
16
+ const addressForWithdrawalAmount = XLSXUtil.findText(sheet, "Withdrawal");
17
+
18
+ const startingRow = addressForTransactionDate.r + 1;
19
+ const range = XLSX.utils.decode_range(sheet['!ref']);
20
+
21
+ const rangeEnd = range.e;
22
+ const lastRow = rangeEnd.r;
23
+
24
+ const rows = [...Array(1 + lastRow - startingRow).keys()].map(v => startingRow + v);
25
+
26
+ return rows.map(row => {
27
+ const date = moment(XLSXUtil.getCellValue(sheet, row, addressForTransactionDate.c) as string, 'DD/MM/YYYY').toDate();
28
+ const outflow = XLSXUtil.getNumberInCell(sheet, row, addressForWithdrawalAmount.c);
29
+ const inflow = XLSXUtil.getNumberInCell(sheet, row, addressForDepositAmount.c);
30
+ const payee = XLSXUtil.getCellValue(sheet, row, addressForDetails.c);
31
+
32
+ return <Transaction>{
33
+ Payee: payee,
34
+ Outflow: outflow,
35
+ Inflow: inflow,
36
+ Date: date,
37
+ Memo: payee,
38
+ Category: null
39
+ };
40
+ }).filter(datum => moment(datum.Date).isValid());
41
+ }
42
+
43
+ public supports(bank: Bank): boolean {
44
+ return bank === Bank.StandardChartered;
45
+ }
46
+ }
@@ -0,0 +1,8 @@
1
+ import {Bank} from '../models/Bank';
2
+ import {Transaction} from '../models/Transaction';
3
+
4
+ export abstract class TransactionAdapter {
5
+ public abstract convert(fileData: ArrayBuffer): Promise<Transaction[]>;
6
+
7
+ public abstract supports(bank: Bank): boolean;
8
+ }
@@ -0,0 +1,28 @@
1
+ import qif = require('qif');
2
+ import moment = require('moment');
3
+ import {Transaction} from '../models/Transaction';
4
+
5
+ export class TransactionsQifConverter {
6
+ public async convert(transactions: Transaction[]): Promise<Buffer> {
7
+ const qifTransactions = transactions.map(t => {
8
+ return <QifTransaction>{
9
+ date: moment(t.Date).format('D/M/YYYY'),
10
+ amount: t.Inflow ? t.Inflow : -t.Outflow,
11
+ payee: t.Payee,
12
+ memo: t.Memo,
13
+ category: t.Category
14
+ };
15
+ });
16
+
17
+ return qif.write({cash: qifTransactions});
18
+ }
19
+ }
20
+
21
+ interface QifTransaction {
22
+ date: string;
23
+ amount: number;
24
+ payee: string;
25
+ memo: string;
26
+ category: string;
27
+ checknumber?: number;
28
+ }
@@ -0,0 +1,27 @@
1
+ import parse from "multi-number-parse";
2
+
3
+ export class NumberUtil {
4
+ private readonly commonSeparators = [",", "."];
5
+
6
+ public parseNumber(text: string): Number {
7
+ if (!text) {
8
+ return undefined;
9
+ }
10
+
11
+ const trimmedText = text.trim();
12
+ if (!trimmedText) {
13
+ return undefined;
14
+ }
15
+
16
+ // Remove additional separators as the library seems to fail if they're present. Eg: 2,00,000.00
17
+ var parsableText = trimmedText;
18
+ for(const separator of this.commonSeparators) {
19
+ if (trimmedText.split(separator).length > 2) {
20
+ parsableText = trimmedText.replace(separator, '');
21
+ break;
22
+ }
23
+ }
24
+
25
+ return parse(parsableText);
26
+ }
27
+ }
@@ -0,0 +1,38 @@
1
+ import { trim, isNumber } from 'lodash';
2
+ import { NumberUtil } from './NumberUtil';
3
+ import { CellAddress, utils, WorkSheet } from 'xlsx';
4
+
5
+ export class XLSXUtil {
6
+ public static findText(sheet: WorkSheet, text: string): CellAddress {
7
+ const range = utils.decode_range(sheet['!ref']);
8
+ const rangeEnd = range.e;
9
+ const rangeStart = range.s;
10
+ for (let R = rangeStart.r; R <= rangeEnd.r; ++R) {
11
+ for (let C = rangeStart.c; C <= rangeEnd.c; ++C) {
12
+ const cellAddress = utils.encode_cell({ c: C, r: R });
13
+ if (!sheet[cellAddress]) continue;
14
+ const cell = sheet[cellAddress];
15
+
16
+ if (!(cell.t == 's' || cell.t == 'str')) continue;
17
+ if (trim(cell.v) === trim(text)) return utils.decode_cell(cellAddress);
18
+ }
19
+ }
20
+ }
21
+
22
+ public static getCellValue(sheet: WorkSheet, row: number, column: number): string | number | boolean {
23
+ const cellAddress = utils.encode_cell({ c: column, r: row });
24
+ return !sheet[cellAddress] ? null : sheet[cellAddress].v;
25
+ }
26
+
27
+ public static getNumberInCell(sheet: WorkSheet, row: number, column: number): number {
28
+ const cellAddress = utils.encode_cell({ c: column, r: row });
29
+ if (!sheet[cellAddress]) return null;
30
+ const value = sheet[cellAddress].v;
31
+ if (isNumber(value)) {
32
+ return value;
33
+ }
34
+
35
+ const number = new NumberUtil().parseNumber(value)
36
+ return number?.valueOf();
37
+ }
38
+ }
@@ -1,2 +1,2 @@
1
1
  export * from './StatementConverter';
2
- export * from './models/Bank';
2
+ export * from './models/Bank';
@@ -0,0 +1,9 @@
1
+ export enum Bank {
2
+ Axis = 'axis',
3
+ ICICI = 'icici',
4
+ ICICICreditCard = 'icicicc',
5
+ StandardChartered = 'sc',
6
+ ABN = 'abn',
7
+ N26 = 'n26',
8
+ GenericMT940 = 'Others (MT940)',
9
+ }
@@ -5,4 +5,4 @@ export interface Transaction {
5
5
  Date: Date;
6
6
  Memo: string;
7
7
  Category: string;
8
- }
8
+ }
Binary file
Binary file
@@ -0,0 +1,29 @@
1
+ "Date","Payee","Account number","Transaction type","Payment reference","Amount (EUR)","Amount (Foreign Currency)","Type Foreign Currency","Exchange Rate"
2
+ "2021-10-01","DIGITALOCEAN.COM","","MasterCard Payment","","-5.39","-6.23","USD","0.865169"
3
+ "2021-10-02","SNCB-NMBS INTERNET NA","","MasterCard Payment","","-31.2","-31.2","EUR","1.0"
4
+ "2021-10-11","AMZN MKTP NL*S23TM24Q5","","MasterCard Payment","","-12.44","-12.44","EUR","1.0"
5
+ "2021-10-12","TODOIST","","MasterCard Payment","","-3.46","-4.0","USD","0.865"
6
+ "2021-10-19","PAYPAL *GOOGLE GOOGLE","","MasterCard Payment","","-928.0","-928.0","EUR","1.0"
7
+ "2021-10-26","PAYPAL *GOOGLE GOOGLE","","MasterCard Payment","","928.0","928.0","EUR","1.0"
8
+ "2021-11-01","DIGITALOCEAN.COM","","MasterCard Payment","","-6.43","-7.42","USD","0.866577"
9
+ "2021-11-01","A MEHTA","NL54ABNA0123456789","Income","N26 Account","500.0","","",""
10
+ "2021-11-01","PAYPAL *GOOGLE GOOGLE","","MasterCard Payment","","-928.0","-928.0","EUR","1.0"
11
+ "2021-11-02","SP * ANKER DE","","MasterCard Payment","","-69.98","-69.98","EUR","1.0"
12
+ "2021-11-04","A MEHTA","NL54ABNA0123456789","Income","N26 Account","500.0","","",""
13
+ "2021-11-04","Media Markt","","MasterCard Payment","","-899.0","-899.0","EUR","1.0"
14
+ "2021-11-04","A MEHTA","NL54ABNA0123456789","Income","N26 Account","1000.0","","",""
15
+ "2021-11-04","Media Markt","","MasterCard Payment","","-899.0","-899.0","EUR","1.0"
16
+ "2021-11-04","A MEHTA","NL54ABNA0123456789","Income","N26 Account","1000.0","","",""
17
+ "2021-11-04","PAYPAL *MEDIAMARKT","","MasterCard Payment","","-1.0","-1.0","EUR","1.0"
18
+ "2021-11-04","PAYPAL *MEDIAMARKT","","MasterCard Payment","","1.0","1.0","EUR","1.0"
19
+ "2021-11-04","PAYPAL *MEDIAMARKT","","MasterCard Payment","","-1.0","-1.0","EUR","1.0"
20
+ "2021-11-04","PAYPAL *MEDIAMARKT","","MasterCard Payment","","-899.0","-899.0","EUR","1.0"
21
+ "2021-11-04","PAYPAL *MEDIAMARKT","","MasterCard Payment","","1.0","1.0","EUR","1.0"
22
+ "2021-11-05","Media Markt","","MasterCard Payment","","899.0","899.0","EUR","1.0"
23
+ "2021-11-05","PAYPAL *MEDIAMARKT","","MasterCard Payment","","-1.0","-1.0","EUR","1.0"
24
+ "2021-11-05","PAYPAL *MEDIAMARKT","","MasterCard Payment","","-899.0","-899.0","EUR","1.0"
25
+ "2021-11-05","PAYPAL *MEDIAMARKT","","MasterCard Payment","","1.0","1.0","EUR","1.0"
26
+ "2021-11-05","A MEHTA","NL54ABNA0123456789","Income","N26 Account","1000.0","","",""
27
+ "2021-11-05","Saturn Online","","MasterCard Payment","","-899.0","-899.0","EUR","1.0"
28
+ "2021-11-06","Saturn Online","","MasterCard Payment","","899.0","899.0","EUR","1.0"
29
+ "2021-11-06","Media Markt","","MasterCard Payment","","899.0","899.0","EUR","1.0"