Compare commits
5 Commits
94f82ea82d
...
9fb888c59d
Author | SHA1 | Date | |
---|---|---|---|
9fb888c59d | |||
06c08ef93b | |||
43f7871267 | |||
f6e00d120a | |||
5efc239fff |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ coverage
|
||||
@types
|
||||
|
||||
public/*.js
|
||||
/public/bundle.js.map
|
||||
|
@ -1,4 +1,14 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"templateId": "node/19"
|
||||
}
|
||||
"schemaVersion": 2,
|
||||
"dockerfileLines": [
|
||||
"FROM node:20-alpine",
|
||||
"RUN mkdir -p /usr/src/app",
|
||||
"WORKDIR /usr/src/app",
|
||||
"COPY ./ /usr/src/app",
|
||||
"RUN npm install && npm cache clean --force && npm run build",
|
||||
"ENV NODE_ENV production",
|
||||
"ENV PORT 80",
|
||||
"EXPOSE 80",
|
||||
"CMD [ \"npm\", \"start\" ]"
|
||||
]
|
||||
}
|
||||
|
27
package-lock.json
generated
27
package-lock.json
generated
@ -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",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"build": "npm run build-node && npm run build-browser",
|
||||
"build-node": "tsc",
|
||||
"build-browser": "esbuild src/browser.ts --bundle --minify --sourcemap --target=firefox120 --outfile=public/bundle.js",
|
||||
"postinstall": "npm run build",
|
||||
"clean-coverage": "rm -rf coverage .nyc_output .xunit",
|
||||
"coverage": "npm run clean-coverage && nyc mocha",
|
||||
"test-reports": "npm run clean-coverage && nyc --reporter clover mocha --reporter xunit --reporter-option output=.xunit",
|
||||
@ -18,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",
|
||||
@ -39,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"
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
(()=>{function l(e,n){let t=[];for(let o=0;o<e.length;o+=n){let r=e.slice(o,o+n);t.push(r)}return t}function p(e,n){let t=[...e];for(;t.length<n;)t.push("");return t}function m(e,n,t="~"){return e.map((o,r)=>`${t}${n+r}${o}`)}function _(e){return e.replace(/ +/g," ")}var g=class{constructor(n,t,o,r){this.number=n;this.initial_balance=t;this.closing_balance=o;this.currency=r}},d=class e{constructor(n,t,o,r,s,i,c,a){this.acc_date=n;this.op_date=t;this.description=o;this.title=r;this.person=s;this.account_number=i;this.amount=c;this.balance_after=a}static{this.counter=function*(){let n=1;for(;;)yield n.toString().padStart(11,"0"),n++}()}formatTitle(){return m(p(l(this.title.trim(),27),9),20).join(`
|
||||
`)}formatPerson(){return m(p(l(_(this.person).trim(),27),2).slice(0,2),32).join(`
|
||||
`)}toMT940(){let n={incoming:"150",outcoming:"169"},t=this.amount>0?n.incoming:n.outcoming;return`:61:${u(this.acc_date)}${u(this.op_date).slice(2)}C${$(this.amount)}S${t}${e.counter.next().value}
|
||||
:86:${t}
|
||||
:86:${t}~00B${t}${this.description.slice(0,23)}
|
||||
${this.formatTitle()}
|
||||
~29${this.account_number}
|
||||
~30${this.account_number.slice(2,10)}
|
||||
~31${this.account_number.slice(10)}
|
||||
${this.formatPerson()}
|
||||
~34${t}
|
||||
~38PL${this.account_number}
|
||||
~62
|
||||
~63`}},b=class{constructor(n,t){this.date_start=n;this.date_end=t}},h=class{},f=class extends h{read_csv(n){let t=n.split(`\r
|
||||
`).map(c=>c.split(";")),o=0;for(;!t[o][0].startsWith("Niniejszy dokument sporz\u0105dzono");)o++;let r=new g(t[20][0],this.parseAmount(t[35][1]),this.parseAmount(t[o-2][7]),t[18][0]),s=new b(this.parseDate(t[14][0]),this.parseDate(t[14][1])),i=[];for(let c=38;c<=o-5;c++){let a=t[c];if(a.length!=9)throw new Error("Wrong amount of columns! maybe a semicolon got stuck in a transaction description?");let v=new Date(a[0]),y=new Date(a[1]),[D,S,x,L]=a.slice(2).map(this.trimString),[M,E]=a.slice(6).map(T=>this.parseAmount(T));i.push(new d(v,y,D,S,x,L,M,E))}return{account:r,range:s,transactions:i}}parseAmount(n){return parseFloat(n.replace(/[^0-9,-]/g,"").replace(",","."))}parseDate(n){let[t,o,r]=n.split(".").map(i=>parseInt(i)),s=new Date;return s.setHours(0),s.setMinutes(0),s.setSeconds(0),s.setMilliseconds(0),s.setDate(t),s.setMonth(o-1),s.setFullYear(r),s}trimString(n){return n.replaceAll(/(^["']|["']$)/g,"")}};function u(e){return`${e.getFullYear()-2e3}${(e.getMonth()+1).toString().padStart(2,"0")}${e.getDate().toString().padStart(2,"0")}`}function $(e){return Math.abs(e).toString().replace(".",",")}function w(e){let n=new f().read_csv(e),{account:t,transactions:o,range:r}=n;return{output:`:20:MT940
|
||||
:25:/PL${t.number.replaceAll(/[^0-9]/g,"")}
|
||||
:28C:${u(r.date_start)}
|
||||
:60F:D${u(r.date_start)}${t.currency}${$(t.initial_balance)}
|
||||
${o.map(i=>i.toMT940()).join(`
|
||||
`)}
|
||||
:62F:D${u(r.date_end)}${t.currency}${$(t.closing_balance)}`,range:r}}function A(e,n,t){var o=new Blob([e],{type:t}),r=document.createElement("a"),s=URL.createObjectURL(o);r.href=s,r.download=n,document.body.appendChild(r),r.click(),setTimeout(function(){document.body.removeChild(r),window.URL.revokeObjectURL(s)},0)}async function R(){console.log("browser.ts:2");let e=document.getElementById("csv");if(!e||!(e instanceof HTMLInputElement))throw console.log("browser.ts:5"),new Error("coudl not find the csv input");var n=e.value.replace("C:\\fakepath\\","");let t=e.files;if(!t)throw new Error("no files in the input");let o=await t[0].arrayBuffer(),r=new TextDecoder("windows-1250").decode(o),s=w(r);A(s.output,`raport-${s.range.date_start.getFullYear()}-${(s.range.date_start.getMonth()+1).toString().padStart(2,"0")}.mt940`,"text")}document.querySelector("#submit")?.addEventListener("click",R);console.log("added handler to",document.querySelector("#submit"));})();
|
||||
//# sourceMappingURL=bundle.js.map
|
File diff suppressed because one or more lines are too long
@ -15,10 +15,8 @@ function download(data: string, filename: string, type: string) {
|
||||
}
|
||||
|
||||
async function handle() {
|
||||
console.log("browser.ts:2");
|
||||
const csv_input = document.getElementById("csv");
|
||||
if (!csv_input || !(csv_input instanceof HTMLInputElement)) {
|
||||
console.log("browser.ts:5");
|
||||
throw new Error("coudl not find the csv input");
|
||||
}
|
||||
var fileName = csv_input.value.replace("C:\\fakepath\\", "");
|
||||
|
54
src/index.test.ts
Normal file
54
src/index.test.ts
Normal file
@ -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");
|
||||
}
|
||||
});
|
||||
});
|
49
src/index.ts
49
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 } {
|
||||
|
Loading…
Reference in New Issue
Block a user