diff --git a/.gitignore b/.gitignore index ba25923..b3e2e43 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage @types public/*.js +/public/bundle.js.map diff --git a/package-lock.json b/package-lock.json index f4019d5..3cdb7b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "mbank-mt940", "version": "0.1.0", + "hasInstallScript": true, "license": "ISC", "dependencies": { "@types/mocha": "^10.0.1", @@ -20,8 +21,10 @@ }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/diff": "^5.2.1", "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", + "diff": "^5.2.0", "eslint": "^8.38.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", @@ -1144,6 +1147,13 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "node_modules/@types/diff": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz", + "integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -2190,10 +2200,11 @@ } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -3696,6 +3707,16 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/mocha/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", diff --git a/package.json b/package.json index 5a3a558..893df30 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,10 @@ "license": "ISC", "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/diff": "^5.2.1", "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", + "diff": "^5.2.0", "eslint": "^8.38.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", @@ -40,7 +42,7 @@ "esbuild": "^0.19.9", "http-server": "^14.1.1", "iconv-lite": "^0.6.3", - "yargs": "^17.7.2", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "yargs": "^17.7.2" } } diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..e979186 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,54 @@ +import { promises as fs } from "fs"; +import path from "node:path"; +import iconv from "iconv-lite"; +import { convert } from "."; +import assert from "node:assert"; +import * as Diff from "diff"; + +describe("mt940 converter", () => { + it("converts properly", async () => { + const content = await fs.readFile( + path.resolve(__dirname, "../tests/real_csv.csv"), + { + encoding: null, + } + ); + const result = convert(iconv.decode(content, "cp1250")); + const expected_result = await fs.readFile( + path.resolve(__dirname, "../tests/real_mt940.txt"), + "utf-8" + ); + try { + assert.strictEqual(result.output, expected_result); + } catch (e) { + console.error("There was a difference. Fixes to apply:"); + console.log( + Diff.createPatch("mt940", result.output, expected_result) + ); + throw new Error("Texts differ"); + } + }); + + it.only("converts properly", async () => { + const content = await fs.readFile( + path.resolve(__dirname, "../tests/real_csv_2.csv"), + { + encoding: null, + } + ); + const result = convert(iconv.decode(content, "cp1250")); + const expected_result = await fs.readFile( + path.resolve(__dirname, "../tests/real_mt940_2.txt"), + "utf-8" + ); + try { + assert.strictEqual(result.output, expected_result); + } catch (e) { + console.error("There was a difference. Fixes to apply:"); + console.log( + Diff.createPatch("mt940", result.output, expected_result) + ); + throw new Error("Texts differ"); + } + }); +}); diff --git a/src/index.ts b/src/index.ts index 35b6d2a..5a69c4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,21 +42,55 @@ class Transaction { } formatPerson() { - return addLineNumbers( + const ret = addLineNumbers( fillWithEmpty( chunks(removeRepeatingSpace(this.person).trim(), 27), 2 ).slice(0, 2), 32 ).join("\n"); + return ret; } toMT940() { - const prefixes = { incoming: "150", outcoming: "169" }; - const prefix = this.amount > 0 ? prefixes.incoming : prefixes.outcoming; - return `:61:${mtDate(this.acc_date)}${mtDate(this.op_date).slice( - 2 - )}C${mtAmount(this.amount)}S${prefix}${Transaction.counter.next().value} + // just a bunch of heuristics + const prefix_fns = [ + () => + this.description.includes("OPŁATA-PRZELEW WEWN.") + ? "169" + : false, + () => + this.description.includes("PRZELEW WEWNĘTRZNY PRZY") + ? "160" + : false, + () => + this.description.includes("OPŁATA-PRZELEW WEWN. DO") + ? "755" + : false, + () => + this.description.includes("PRZELEW ZEWNĘTRZNY WYCH") + ? "152" + : false, + () => + this.description.includes("OPŁATA PRZELEW ZEW.DOWO") + ? "771" + : false, + () => (this.amount > 0 ? "150" : false), + () => "169", + ]; + let prefix = ""; + for (const fn of prefix_fns) { + const result = fn(); + if (result !== false) { + prefix = result; + break; + } + } + const result = `:61:${mtDate(this.acc_date)}${mtDate( + this.op_date + ).slice(2)}${this.amount > 0 ? "C" : "D"}${mtAmount( + this.amount + )}S${prefix}${Transaction.counter.next().value} :86:${prefix} :86:${prefix}~00B${prefix}${this.description.slice(0, 23)} ${this.formatTitle()} @@ -68,6 +102,7 @@ ${this.formatPerson()} ~38PL${this.account_number} ~62 ~63`; + return result; } } @@ -166,7 +201,7 @@ function mtDate(d: Date) { } function mtAmount(n: number) { - return Math.abs(n).toString().replace(".", ","); + return Math.abs(n).toFixed(2).replace(".", ","); } export function convert(csv_utf8: string): { output: string; range: Range } {