From 47892f8262bccce20f9cdaf96b088de92bd28548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Mon, 5 May 2025 13:41:39 +0200 Subject: [PATCH] =?UTF-8?q?Add=20simple=20support=20for=20SGB=20O=C5=82awa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/base.ts | 8 +++ src/parser/index.ts | 2 + src/parser/pl/ing/index.ts | 10 ++-- src/parser/pl/sgbolawa/index.ts | 103 ++++++++++++++++++++++++++++++++ src/runner/index.ts | 4 +- src/types/config.ts | 1 + src/util/parser.ts | 4 ++ 7 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 src/parser/pl/sgbolawa/index.ts diff --git a/src/parser/base.ts b/src/parser/base.ts index 2f58b3f..6cf1cd0 100644 --- a/src/parser/base.ts +++ b/src/parser/base.ts @@ -33,4 +33,12 @@ export abstract class BaseTransactionParser { abstract pushTransaction(data: string[]): Promise; abstract reconcile(): Promise; + + get csvConfig(): Record|undefined { + return undefined; + } + + get csvEncoding(): string { + return "utf8"; + } } \ No newline at end of file diff --git a/src/parser/index.ts b/src/parser/index.ts index 018c532..c7df639 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -3,12 +3,14 @@ import { ProfileConfig, ParserConfig, ServerConfig } from "@/types/config"; import { BaseTransactionParser } from "./base"; import { default as PlIng } from "./pl/ing"; import { default as PlAmex } from "./pl/amex"; +import { default as PlSgbOlawa } from "./pl/sgbolawa"; type Constructor> = new (name: string) => T; const PARSERS: Record>> = { "pl.ing": PlIng, "pl.amex": PlAmex, + "pl.sgbolawa": PlSgbOlawa, }; export function createParser(config: ProfileConfig, serverConfig: ServerConfig): BaseTransactionParser { diff --git a/src/parser/pl/ing/index.ts b/src/parser/pl/ing/index.ts index 1b4e450..be963b3 100644 --- a/src/parser/pl/ing/index.ts +++ b/src/parser/pl/ing/index.ts @@ -152,20 +152,14 @@ export default class extends BaseTransactionParser { const bAmount = b.transactionAmount ?? b.lockAmount; if (a.date !== b.date) { - console.log(a); - console.log(b); throw new Error(`Transfer transaction dates mismatch`); } if (a.title !== b.title) { - console.log(a); - console.log(b); throw new Error(`Transfer transaction titles mismatch`); } if (aAmount === undefined || bAmount === undefined) { - console.log(a); - console.log(b); throw new Error(`Undefined amounts for transactions`); } @@ -196,4 +190,8 @@ export default class extends BaseTransactionParser { return transaction as Transaction; } + + get csvEncoding(): string { + return "win1250"; + } } \ No newline at end of file diff --git a/src/parser/pl/sgbolawa/index.ts b/src/parser/pl/sgbolawa/index.ts new file mode 100644 index 0000000..fb5db92 --- /dev/null +++ b/src/parser/pl/sgbolawa/index.ts @@ -0,0 +1,103 @@ +import { BaseTransactionParser } from "@/parser/base"; +import { ParserConfig } from "@/types/config"; +import { Transaction } from "@/types/transaction"; +import { deduplicateSpaces, parseAmount } from "@/util/parser"; + +const headers = [ + 'Lp', + 'Tytuł_1', + 'Tytuł_2', + 'Tytuł_3', + 'Tytuł_4', + 'Rachunek Nadawcy', + 'Odbiorca_1', + 'Odbiorca_2', + 'Odbiorca_3', + 'Odbiorca_4', + 'Rachunek Odbiorcy', + 'Nadawca_1', + 'Nadawca_2', + 'Nadawca_3', + 'Nadawca_4', + 'Data_operacji', + 'Kwota', + 'Saldo', + 'Nr_dokumentu', + 'Data_waluty', + 'Data_nadania', +]; + +type SgbTransaction = { + [K in typeof headers[number]]: string; +}; + +type ValidatedSgbTransaction = Omit & { + Kwota: number; +}; + +export default class extends BaseTransactionParser { + protected requiredFields = []; + #header = true; + #transactions: ValidatedSgbTransaction[] = []; + + + async pushTransaction(data: string[]): Promise { + if (data.length !== headers.length) { + return false; + } + + if (this.#header) { + this.#header = false; + return false; + } + + const transaction: SgbTransaction = {}; + headers.forEach((key, index) => { + transaction[key] = data[index].trim(); + }); + + const Kwota = parseAmount(transaction.Kwota); + + if (Kwota === undefined) { + return false; + } + + this.#transactions.push({ + ...transaction, + Kwota + } as ValidatedSgbTransaction); + + return true; + } + + async reconcile(): Promise { + return this.#transactions.map(this.#mapTransaction.bind(this)); + } + + #mapTransaction(transaction: ValidatedSgbTransaction): Transaction { + const from = deduplicateSpaces(`${transaction.Nadawca_1} ${transaction.Nadawca_2} ${transaction.Nadawca_3}`.trim()); + const to = deduplicateSpaces(`${transaction.Odbiorca_1} ${transaction.Odbiorca_2} ${transaction.Odbiorca_3}`.trim()); + const title = deduplicateSpaces(`${transaction['Tytuł_1']} ${transaction['Tytuł_2']} ${transaction['Tytuł_3']}`.trim()); + + return { + kind: 'regular', + date: transaction.Data_nadania, + amount: transaction.Kwota, + from, + fromDetails: from, + to, + toDetails: to, + title + } + } + + get csvEncoding(): string { + return "win1250"; + } + + get csvConfig(): Record { + return { + delimiter: ";" + } + } +} \ No newline at end of file diff --git a/src/runner/index.ts b/src/runner/index.ts index 7451db4..43a5780 100644 --- a/src/runner/index.ts +++ b/src/runner/index.ts @@ -55,8 +55,8 @@ const submitTransactions = (load: (server: Actual, t: Transaction[]) => Promise< }; stream - .pipe(iconv.decodeStream(profileConfig.encoding ?? "utf8")) - .pipe(Papa.parse(Papa.NODE_STREAM_INPUT)) + .pipe(iconv.decodeStream(profileConfig.encoding ?? parser.csvEncoding ?? "utf8")) + .pipe(Papa.parse(Papa.NODE_STREAM_INPUT, profileConfig.csv ?? parser.csvConfig)) .on('data', handleRow) .on('close', handleClose); }); diff --git a/src/types/config.ts b/src/types/config.ts index 04f3390..baf9184 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -10,6 +10,7 @@ export type ProfileConfig = { parser: string; encoding?: string; config?: ParserConfig; + csv?: Record; }; export type ParserConfig = { diff --git a/src/util/parser.ts b/src/util/parser.ts index 9f5b6af..49a1c21 100644 --- a/src/util/parser.ts +++ b/src/util/parser.ts @@ -64,3 +64,7 @@ export function parseAmount(input?: string): number|undefined { return v; } + +export function deduplicateSpaces(input: string): string { + return input.replaceAll(/\s+/, " "); +} \ No newline at end of file