1
0
forked from icd/mt940-mbank-ts

Compare commits

...

10 Commits

12 changed files with 222 additions and 158 deletions

View File

@ -1,12 +1,7 @@
{ {
"phabricator.uri": "https://hub.sealcode.org/", "phabricator.uri": "https://hub.sealcode.org/",
"arc.land.onto.default": "hotwire", "arc.land.onto.default": "hotwire",
"load": [ "load": ["arcanist-linters", "arc-unit-mocha/src"],
"arcanist-linters",
"arc-unit-mocha/src"
],
"unit.engine": "MochaEngine", "unit.engine": "MochaEngine",
"unit.mocha.include": [ "unit.mocha.include": [".test/**/*.ts"]
".test/**/*.ts"
]
} }

7
.gitignore vendored
View File

@ -6,5 +6,8 @@ lib
coverage coverage
@types @types
public/*.js public/bundle.*
/public/bundle.js.map public/*.woff
public/*.woff2
.DS_Store

View File

@ -2,21 +2,6 @@
"useTabs": true, "useTabs": true,
"tabWidth": 4, "tabWidth": 4,
"trailingComma": "es5", "trailingComma": "es5",
"printWidth": 100,
"overrides": [
{
"files": "*.yml",
"options": {
"tabWidth": 2,
"useTabs": false
}
},
{
"files": "*.html",
"options": {
"printWidth": 120, "printWidth": 120,
"htmlWhitespaceSensitivity": "ignore" "htmlWhitespaceSensitivity": "ignore"
}
}
]
} }

93
package-lock.json generated
View File

@ -10,6 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@fontsource/atkinson-hyperlegible": "^5.2.6",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@types/yargs": "^17.0.32", "@types/yargs": "^17.0.32",
@ -24,6 +25,7 @@
"@types/diff": "^5.2.1", "@types/diff": "^5.2.1",
"@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0", "@typescript-eslint/parser": "^5.58.0",
"concurrently": "^9.2.0",
"diff": "^5.2.0", "diff": "^5.2.0",
"eslint": "^8.38.0", "eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
@ -900,6 +902,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@fontsource/atkinson-hyperlegible": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@fontsource/atkinson-hyperlegible/-/atkinson-hyperlegible-5.2.6.tgz",
"integrity": "sha512-Kfh6/UlHhotKuv4Oi9PXQIsmzwbtJIR442sSJnEHsO7TDZaDczK8cY0AlTNOB0XMDZj1j35nAlgbi2HZCdNg/Q==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.8", "version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@ -2105,6 +2116,48 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true "dev": true
}, },
"node_modules/concurrently": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz",
"integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"lodash": "^4.17.21",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"supports-color": "^8.1.1",
"tree-kill": "^1.2.2",
"yargs": "^17.7.2"
},
"bin": {
"conc": "dist/bin/concurrently.js",
"concurrently": "dist/bin/concurrently.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
"node_modules/concurrently/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
@ -4472,6 +4525,23 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/rxjs/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -4585,6 +4655,19 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel": { "node_modules/side-channel": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@ -4752,6 +4835,16 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"license": "MIT",
"bin": {
"tree-kill": "cli.js"
}
},
"node_modules/ts-node": { "node_modules/ts-node": {
"version": "10.9.1", "version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",

View File

@ -7,13 +7,16 @@
"test": "mocha", "test": "mocha",
"build": "npm run build-node && npm run build-browser", "build": "npm run build-node && npm run build-browser",
"build-node": "tsc", "build-node": "tsc",
"build-browser": "esbuild src/browser.ts --bundle --minify --sourcemap --target=firefox120 --outfile=public/bundle.js", "build-browser": "esbuild src/browser.ts --bundle --minify --sourcemap --target=firefox120 --outfile=public/bundle.js --loader:.woff=file --loader:.woff2=file",
"watch-node": "tsc --watch",
"watch-browser": "esbuild src/browser.ts --bundle --outfile=public/bundle.js --watch --loader:.woff=file --loader:.woff2=file",
"postinstall": "npm run build", "postinstall": "npm run build",
"clean-coverage": "rm -rf coverage .nyc_output .xunit", "clean-coverage": "rm -rf coverage .nyc_output .xunit",
"coverage": "npm run clean-coverage && nyc mocha", "coverage": "npm run clean-coverage && nyc mocha",
"test-reports": "npm run clean-coverage && nyc --reporter clover mocha --reporter xunit --reporter-option output=.xunit", "test-reports": "npm run clean-coverage && nyc --reporter clover mocha --reporter xunit --reporter-option output=.xunit",
"coverage-html": "npm run test-reports && nyc report --reporter lcov && xdg-open coverage/lcov-report/index.html", "coverage-html": "npm run test-reports && nyc report --reporter lcov && xdg-open coverage/lcov-report/index.html",
"start": "http-server public -p 8080", "start": "http-server public -p 8080",
"dev": "concurrently --names \"NODE,BROWSER,SERVER\" --prefix-colors \"blue,magenta,green\" \"npm run watch-node\" \"npm run watch-browser\" \"npm run start\"",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check ." "format:check": "prettier --check ."
}, },
@ -24,6 +27,7 @@
"@types/diff": "^5.2.1", "@types/diff": "^5.2.1",
"@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0", "@typescript-eslint/parser": "^5.58.0",
"concurrently": "^9.2.0",
"diff": "^5.2.0", "diff": "^5.2.0",
"eslint": "^8.38.0", "eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
@ -38,6 +42,7 @@
}, },
"types": "./@types/index.d.ts", "types": "./@types/index.d.ts",
"dependencies": { "dependencies": {
"@fontsource/atkinson-hyperlegible": "^5.2.6",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node": "^20.10.4", "@types/node": "^20.10.4",
"@types/yargs": "^17.0.32", "@types/yargs": "^17.0.32",

View File

@ -4,22 +4,23 @@
<title>mBank mt940 konwerter</title> <title>mBank mt940 konwerter</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.fuchsia.min.css" />
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="styles.css" />
</head> </head>
<body> <body>
<main>
<header> <header>
<section>
<h1>mBank mt940 konwerter</h1> <h1>mBank mt940 konwerter</h1>
<button id="theme-toggle" type="button"></button> <button id="theme-toggle" type="button"></button>
</section>
</header> </header>
<main>
<section> <section>
<p> <p>
Za pomocą tego narzędzia za darmo przekonwertujesz plik CSV wygenerowany przez mbank do formatu Za pomocą tego narzędzia za darmo przekonwertujesz plik CSV wygenerowany przez mbank do&nbsp;formatu
mt940. Konwersja odbywa się w przeglądarce, Twoje dane nie są nigdzie wysyłane. Dla pewności możesz mt940. Konwersja odbywa się w&nbsp;przeglądarce, Twoje dane nie są nigdzie wysyłane. Dla pewności
na czas konwersji wyłączyć dostęp do Internetu na Twoim komputerze, zamknąć tę stronę, i włączyć możesz na czas konwersji wyłączyć dostęp do&nbsp;Internetu na&nbsp;Twoim komputerze, zamknąć
dostęp do Internetu ponownie. &nbsp;stronę, i&nbsp;włączyć dostęp do&nbsp;Internetu ponownie.
</p> </p>
<form> <form>
@ -33,7 +34,7 @@
<div class="success hidden" role="alert"> <div class="success hidden" role="alert">
<p> <p>
<strong>Sukces!</strong> <strong>Sukces!</strong>
Rozpoczęto pobieranie wygenerowanego raportu mt940. Cieszymy się, że mogliśmy Ci oszczędzić Rozpoczęto pobieranie wygenerowanego raportu mt940. Cieszymy się, że mogliśmy Ci&nbsp;oszczędzić
trochę wydatków. Jeżeli chcesz się nam odwdzięczyć, zachęcamy do trochę wydatków. Jeżeli chcesz się nam odwdzięczyć, zachęcamy do
<a target="_blank" href="https://www.internet-czas-dzialac.pl/contact/#wesprzyj-nas"> <a target="_blank" href="https://www.internet-czas-dzialac.pl/contact/#wesprzyj-nas">
wspierania naszej fundacji wspierania naszej fundacji
@ -41,21 +42,34 @@
</p> </p>
</div> </div>
</section> </section>
</main>
<footer> <footer>
<section>
<a target="_blank" href="https://www.internet-czas-dzialac.pl/">
<figure id="icd-logo-container">
<div id="icd-logo"></div>
</figure>
</a>
<p> <p>
Aplikacja została wykonana przez Aplikacja została wykonana przez
<a target="_blank" href="https://www.internet-czas-dzialac.pl/"> <a target="_blank" href="https://www.internet-czas-dzialac.pl/">
Fundację „Internet. Czas działać!” Fundację „Internet. Czas działać!”
</a> </a>
i jest utrzymywana przez i&nbsp;jest utrzymywana przez
<a target="_blank" href="https://www.sealcode.it/">Sealcode</a> <a target="_blank" href="https://www.sealcode.it/">Sealcode</a>
</p> .
<p>
<a target="_blank" href="https://git.internet-czas-dzialac.pl/icd/mt940-mbank-ts">Kod źródłowy</a> <a target="_blank" href="https://git.internet-czas-dzialac.pl/icd/mt940-mbank-ts">Kod źródłowy</a>
</p> </p>
<small>
Wykorzystuje czcionkę
<a target="_blank" href="https://brailleinstitute.org/freefont">Atkinson Hyperlegible</a>
na&nbsp;licencji SIL Open Font License
</small>
</section>
</footer> </footer>
</main>
<script src="bundle.js"></script> <script src="bundle.js"></script>
</body> </body>
</html> </html>

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,6 +1,17 @@
body { :root {
max-width: 600px; --pico-font-family: "Atkinson Hyperlegible", sans-serif;
}
/* use to enforce styles for both themes */
[data-theme="dark"],
:not([data-theme="dark"]) {
--noop: none;
}
section {
max-width: 800px;
margin: 0 auto; margin: 0 auto;
padding: 0 1rem;
} }
button { button {
@ -19,7 +30,13 @@ button {
color: hsl(120, 100%, 7.1%); color: hsl(120, 100%, 7.1%);
} }
header { header,
footer {
max-width: unset !important;
background-color: var(--pico-modal-overlay-background-color);
}
header section {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -38,3 +55,25 @@ header {
#theme-toggle[dark]::before { #theme-toggle[dark]::before {
content: "🌙"; content: "🌙";
} }
#icd-logo {
height: 50px;
aspect-ratio: 1200/319;
background-color: white;
mask-image: url("logo.png");
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
-webkit-mask-image: url("logo.png");
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: contain;
-webkit-mask-position: center;
}
figure#icd-logo-container {
background-color: black;
padding: 1rem;
width: fit-content;
margin: 0 auto 1em auto;
border-radius: var(--pico-border-radius);
}

View File

@ -1,4 +1,5 @@
import { convert } from "."; import { convert } from ".";
import "@fontsource/atkinson-hyperlegible";
function download(data: string, filename: string, type: string) { function download(data: string, filename: string, type: string) {
var file = new Blob([data], { type: type }); var file = new Blob([data], { type: type });
@ -30,9 +31,7 @@ async function handle() {
const result = convert(string); const result = convert(string);
download( download(
result.output, result.output,
`raport-${result.range.date_start.getFullYear()}-${( `raport-${result.range.date_start.getFullYear()}-${(result.range.date_start.getMonth() + 1)
result.range.date_start.getMonth() + 1
)
.toString() .toString()
.padStart(2, "0")}.mt940`, .padStart(2, "0")}.mt940`,
"text" "text"

View File

@ -7,47 +7,31 @@ import * as Diff from "diff";
describe("mt940 converter", () => { describe("mt940 converter", () => {
it("converts properly", async () => { it("converts properly", async () => {
const content = await fs.readFile( const content = await fs.readFile(path.resolve(__dirname, "../tests/real_csv.csv"), {
path.resolve(__dirname, "../tests/real_csv.csv"),
{
encoding: null, encoding: null,
} });
);
const result = convert(iconv.decode(content, "cp1250")); const result = convert(iconv.decode(content, "cp1250"));
const expected_result = await fs.readFile( const expected_result = await fs.readFile(path.resolve(__dirname, "../tests/real_mt940.txt"), "utf-8");
path.resolve(__dirname, "../tests/real_mt940.txt"),
"utf-8"
);
try { try {
assert.strictEqual(result.output, expected_result); assert.strictEqual(result.output, expected_result);
} catch (e) { } catch (e) {
console.error("There was a difference. Fixes to apply:"); console.error("There was a difference. Fixes to apply:");
console.log( console.log(Diff.createPatch("mt940", result.output, expected_result));
Diff.createPatch("mt940", result.output, expected_result)
);
throw new Error("Texts differ"); throw new Error("Texts differ");
} }
}); });
it.only("converts properly", async () => { it.only("converts properly", async () => {
const content = await fs.readFile( const content = await fs.readFile(path.resolve(__dirname, "../tests/real_csv_2.csv"), {
path.resolve(__dirname, "../tests/real_csv_2.csv"),
{
encoding: null, encoding: null,
} });
);
const result = convert(iconv.decode(content, "cp1250")); const result = convert(iconv.decode(content, "cp1250"));
const expected_result = await fs.readFile( const expected_result = await fs.readFile(path.resolve(__dirname, "../tests/real_mt940_2.txt"), "utf-8");
path.resolve(__dirname, "../tests/real_mt940_2.txt"),
"utf-8"
);
try { try {
assert.strictEqual(result.output, expected_result); assert.strictEqual(result.output, expected_result);
} catch (e) { } catch (e) {
console.error("There was a difference. Fixes to apply:"); console.error("There was a difference. Fixes to apply:");
console.log( console.log(Diff.createPatch("mt940", result.output, expected_result));
Diff.createPatch("mt940", result.output, expected_result)
);
throw new Error("Texts differ"); throw new Error("Texts differ");
} }
}); });

View File

@ -1,9 +1,4 @@
import { import { addLineNumbers, chunks, fillWithEmpty, removeRepeatingSpace } from "./utils";
addLineNumbers,
chunks,
fillWithEmpty,
removeRepeatingSpace,
} from "./utils";
class Account { class Account {
constructor( constructor(
@ -35,18 +30,12 @@ class Transaction {
})(); })();
formatTitle() { formatTitle() {
return addLineNumbers( return addLineNumbers(fillWithEmpty(chunks(this.title.trim(), 27), 9), 20).join("\n");
fillWithEmpty(chunks(this.title.trim(), 27), 9),
20
).join("\n");
} }
formatPerson() { formatPerson() {
const ret = addLineNumbers( const ret = addLineNumbers(
fillWithEmpty( fillWithEmpty(chunks(removeRepeatingSpace(this.person).trim(), 27), 2).slice(0, 2),
chunks(removeRepeatingSpace(this.person).trim(), 27),
2
).slice(0, 2),
32 32
).join("\n"); ).join("\n");
return ret; return ret;
@ -55,26 +44,11 @@ class Transaction {
toMT940() { toMT940() {
// just a bunch of heuristics // just a bunch of heuristics
const prefix_fns = [ const prefix_fns = [
() => () => (this.description.includes("OPŁATA-PRZELEW WEWN.") ? "169" : false),
this.description.includes("OPŁATA-PRZELEW WEWN.") () => (this.description.includes("PRZELEW WEWNĘTRZNY PRZY") ? "160" : false),
? "169" () => (this.description.includes("OPŁATA-PRZELEW WEWN. DO") ? "755" : false),
: false, () => (this.description.includes("PRZELEW ZEWNĘTRZNY WYCH") ? "152" : false),
() => () => (this.description.includes("OPŁATA PRZELEW ZEW.DOWO") ? "771" : 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), () => (this.amount > 0 ? "150" : false),
() => "169", () => "169",
]; ];
@ -86,11 +60,9 @@ class Transaction {
break; break;
} }
} }
const result = `:61:${mtDate(this.acc_date)}${mtDate( const result = `:61:${mtDate(this.acc_date)}${mtDate(this.op_date).slice(2)}${
this.op_date this.amount > 0 ? "C" : "D"
).slice(2)}${this.amount > 0 ? "C" : "D"}${mtAmount( }${mtAmount(this.amount)}S${prefix}${Transaction.counter.next().value}
this.amount
)}S${prefix}${Transaction.counter.next().value}
:86:${prefix} :86:${prefix}
:86:${prefix}~00B${prefix}${this.description.slice(0, 23)} :86:${prefix}~00B${prefix}${this.description.slice(0, 23)}
${this.formatTitle()} ${this.formatTitle()}
@ -135,10 +107,7 @@ class mBankParser extends CSVParser {
this.parseAmount(lines[last - 2][7]), this.parseAmount(lines[last - 2][7]),
lines[18][0] lines[18][0]
); );
const range = new Range( const range = new Range(this.parseDate(lines[14][0]), this.parseDate(lines[14][1]));
this.parseDate(lines[14][0]),
this.parseDate(lines[14][1])
);
const transactions = []; const transactions = [];
for (let i = 38; i <= last - 5; i++) { for (let i = 38; i <= last - 5; i++) {
const line = lines[i]; const line = lines[i];
@ -148,29 +117,14 @@ class mBankParser extends CSVParser {
} }
if (line.length != 9) { if (line.length != 9) {
console.log({ line }); console.log({ line });
throw new Error( throw new Error("Wrong amount of columns! maybe a semicolon got stuck in a transaction description?");
"Wrong amount of columns! maybe a semicolon got stuck in a transaction description?"
);
} }
const date_acc = new Date(line[0]); const date_acc = new Date(line[0]);
const date_op = new Date(line[1]); const date_op = new Date(line[1]);
const [description, title, person, account_number] = line const [description, title, person, account_number] = line.slice(2).map(this.trimString);
.slice(2) const [amount, balance_after] = line.slice(6).map((s) => this.parseAmount(s));
.map(this.trimString);
const [amount, balance_after] = line
.slice(6)
.map((s) => this.parseAmount(s));
transactions.push( transactions.push(
new Transaction( new Transaction(date_acc, date_op, description, title, person, account_number, amount, balance_after)
date_acc,
date_op,
description,
title,
person,
account_number,
amount,
balance_after
)
); );
} }
return { account, range, transactions }; return { account, range, transactions };
@ -200,9 +154,10 @@ class mBankParser extends CSVParser {
} }
function mtDate(d: Date) { function mtDate(d: Date) {
return `${d.getFullYear() - 2000}${(d.getMonth() + 1) return `${d.getFullYear() - 2000}${(d.getMonth() + 1).toString().padStart(2, "0")}${d
.getDate()
.toString() .toString()
.padStart(2, "0")}${d.getDate().toString().padStart(2, "0")}`; .padStart(2, "0")}`;
} }
function mtAmount(n: number) { function mtAmount(n: number) {
@ -215,12 +170,8 @@ export function convert(csv_utf8: string): { output: string; range: Range } {
const string = `:20:MT940 const string = `:20:MT940
:25:/PL${account.number.replaceAll(/[^0-9]/g, "")} :25:/PL${account.number.replaceAll(/[^0-9]/g, "")}
:28C:${mtDate(range.date_start)} :28C:${mtDate(range.date_start)}
:60F:D${mtDate(range.date_start)}${account.currency}${mtAmount( :60F:D${mtDate(range.date_start)}${account.currency}${mtAmount(account.initial_balance)}
account.initial_balance
)}
${transactions.map((t) => t.toMT940()).join("\n")} ${transactions.map((t) => t.toMT940()).join("\n")}
:62F:D${mtDate(range.date_end)}${account.currency}${mtAmount( :62F:D${mtDate(range.date_end)}${account.currency}${mtAmount(account.closing_balance)}`;
account.closing_balance
)}`;
return { output: string, range }; return { output: string, range };
} }

View File

@ -15,11 +15,7 @@ export function fillWithEmpty(array: string[], target_length: number) {
return a; return a;
} }
export function addLineNumbers( export function addLineNumbers(s: string[], start_num: number, prefix = "~"): string[] {
s: string[],
start_num: number,
prefix = "~"
): string[] {
return s.map((l, i) => `${prefix}${start_num + i}${l}`); return s.map((l, i) => `${prefix}${start_num + i}${l}`);
} }