187 lines
4.3 KiB
TypeScript
187 lines
4.3 KiB
TypeScript
import {
|
|
addLineNumbers,
|
|
chunks,
|
|
fillWithEmpty,
|
|
removeRepeatingSpace,
|
|
} from "./utils";
|
|
|
|
class Account {
|
|
constructor(
|
|
public number: string,
|
|
public initial_balance: number,
|
|
public closing_balance: number,
|
|
public currency: string
|
|
) {}
|
|
}
|
|
|
|
class Transaction {
|
|
constructor(
|
|
public acc_date: Date,
|
|
public op_date: Date,
|
|
public description: string,
|
|
public title: string,
|
|
public person: string,
|
|
public account_number: string,
|
|
public amount: number,
|
|
public balance_after: number
|
|
) {}
|
|
|
|
public static counter = (function* () {
|
|
let c = 1;
|
|
while (true) {
|
|
yield c.toString().padStart(11, "0");
|
|
c++;
|
|
}
|
|
})();
|
|
|
|
formatTitle() {
|
|
return addLineNumbers(
|
|
fillWithEmpty(chunks(this.title.trim(), 27), 9),
|
|
20
|
|
).join("\n");
|
|
}
|
|
|
|
formatPerson() {
|
|
return addLineNumbers(
|
|
fillWithEmpty(
|
|
chunks(removeRepeatingSpace(this.person).trim(), 27),
|
|
2
|
|
).slice(0, 2),
|
|
32
|
|
).join("\n");
|
|
}
|
|
|
|
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}
|
|
:86:${prefix}
|
|
:86:${prefix}~00B${prefix}${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${prefix}
|
|
~38PL${this.account_number}
|
|
~62
|
|
~63`;
|
|
}
|
|
}
|
|
|
|
class Range {
|
|
constructor(public date_start: Date, public date_end: Date) {}
|
|
}
|
|
|
|
abstract class CSVParser {
|
|
abstract read_csv(content_utf8: string): {
|
|
account: Account;
|
|
range: Range;
|
|
transactions: Transaction[];
|
|
};
|
|
}
|
|
|
|
class mBankParser extends CSVParser {
|
|
read_csv(content_utf8: string): {
|
|
account: Account;
|
|
range: Range;
|
|
transactions: Transaction[];
|
|
} {
|
|
const lines = content_utf8.split("\r\n").map((line) => line.split(";"));
|
|
let last = 0;
|
|
while (!lines[last][0].startsWith("Niniejszy dokument sporządzono")) {
|
|
last++;
|
|
}
|
|
const account = new Account(
|
|
lines[20][0],
|
|
this.parseAmount(lines[35][1]),
|
|
this.parseAmount(lines[last - 2][7]),
|
|
lines[18][0]
|
|
);
|
|
const range = new Range(
|
|
this.parseDate(lines[14][0]),
|
|
this.parseDate(lines[14][1])
|
|
);
|
|
const transactions = [];
|
|
for (let i = 38; i <= last - 5; i++) {
|
|
const line = lines[i];
|
|
if (line.length != 9) {
|
|
throw new Error(
|
|
"Wrong amount of columns! maybe a semicolon got stuck in a transaction description?"
|
|
);
|
|
}
|
|
const date_acc = new Date(line[0]);
|
|
const date_op = new Date(line[1]);
|
|
const [description, title, person, account_number] = line
|
|
.slice(2)
|
|
.map(this.trimString);
|
|
const [amount, balance_after] = line
|
|
.slice(6)
|
|
.map((s) => this.parseAmount(s));
|
|
transactions.push(
|
|
new Transaction(
|
|
date_acc,
|
|
date_op,
|
|
description,
|
|
title,
|
|
person,
|
|
account_number,
|
|
amount,
|
|
balance_after
|
|
)
|
|
);
|
|
}
|
|
return { account, range, transactions };
|
|
}
|
|
|
|
parseAmount(s: string): number {
|
|
return parseFloat(s.replace(/[^0-9,-]/g, "").replace(",", "."));
|
|
}
|
|
|
|
parseDate(s: string): Date {
|
|
const [day, month, year] = s.split(".").map((s) => parseInt(s));
|
|
const d = new Date();
|
|
d.setHours(0);
|
|
d.setMinutes(0);
|
|
d.setSeconds(0);
|
|
d.setMilliseconds(0);
|
|
d.setDate(day);
|
|
d.setMonth(month - 1);
|
|
d.setFullYear(year);
|
|
return d;
|
|
}
|
|
|
|
trimString(s: string): string {
|
|
//removes redundant quotes at the beginning or end of the transaction
|
|
return s.replaceAll(/(^["']|["']$)/g, "");
|
|
}
|
|
}
|
|
|
|
function mtDate(d: Date) {
|
|
return `${d.getFullYear() - 2000}${(d.getMonth() + 1)
|
|
.toString()
|
|
.padStart(2, "0")}${d.getDate().toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
function mtAmount(n: number) {
|
|
return Math.abs(n).toString().replace(".", ",");
|
|
}
|
|
|
|
export function convert(csv_utf8: string): { output: string; range: Range } {
|
|
const result = new mBankParser().read_csv(csv_utf8);
|
|
const { account, transactions, range } = result;
|
|
const string = `:20:MT940
|
|
:25:/PL${account.number.replaceAll(/[^0-9]/g, "")}
|
|
:28C:${mtDate(range.date_start)}
|
|
:60F:D${mtDate(range.date_start)}${account.currency}${mtAmount(
|
|
account.initial_balance
|
|
)}
|
|
${transactions.map((t) => t.toMT940()).join("\n")}
|
|
:62F:D${mtDate(range.date_end)}${account.currency}${mtAmount(
|
|
account.closing_balance
|
|
)}`;
|
|
return { output: string, range };
|
|
}
|