Implement scaffolding of parsers and ING Bank Śląski parser

This commit is contained in:
2025-04-01 13:53:58 +02:00
parent 484ef1f8bd
commit eb934d750b
3 changed files with 123 additions and 0 deletions

35
src/parser/base.ts Normal file
View File

@@ -0,0 +1,35 @@
import { Transaction } from "@/types/transaction";
import { ProfileConfig, ParserConfig } from "@/types/config";
export abstract class BaseTransactionParser<C extends ParserConfig> {
public readonly name: string;
protected abstract requiredFields: readonly (keyof C)[];
abstract parse(config: C, data: string[]): Promise<Transaction|undefined>;
constructor(name: string) {
this.name = name;
}
#validate(config: Partial<C>|undefined): C {
for (const field of this.requiredFields) {
if (config?.[field] === undefined) {
throw new Error(`The '${String(field)}' configuration field of '${this.name}' parser is required`)
}
}
return config as C;
}
public async parseTransaction(config: ProfileConfig, data: string[]): Promise<Transaction|undefined> {
const cfg = config?.config as Partial<C> | undefined;
return this.parse(this.#validate(cfg), data);
}
protected parseAmount(input?: string): number|undefined {
if (input === undefined) {
return undefined;
}
return Number.parseFloat(input.replaceAll(",", "."));
}
}

24
src/parser/index.ts Normal file
View File

@@ -0,0 +1,24 @@
export { BaseTransactionParser } from "./base";
import { ProfileConfig, ParserConfig } from "@/types/config";
import { BaseTransactionParser } from "./base";
import { default as PlIng } from "./pl/ing";
import { Transaction } from "@/types/transaction";
type Constructor<T extends BaseTransactionParser<ParserConfig>> = new (name: string) => T;
const PARSERS: Record<string, Constructor<BaseTransactionParser<ParserConfig>>> = {
"pl.ing": PlIng
};
export async function parseTransaction(parserName: string, config: ProfileConfig, data: string[]): Promise<Transaction|undefined> {
const Parser = PARSERS[parserName];
if (!Parser) {
throw new Error(`Unknown parser: ${parserName}`);
}
const parser = new Parser(parserName);
return parser.parseTransaction(config, data);
}

View File

@@ -0,0 +1,64 @@
import { Transaction } from "@/types/transaction";
import { ParserConfig } from "@/types/config";
import { BaseTransactionParser } from "../..";
import { Parser } from "papaparse";
const headers = [
'transactionDate',
'accountingDate',
'contrahentData',
'title',
'accountNumber',
'bankName',
'details',
'transactionNumber',
'transactionAmount',
'transactionCurrency',
'lockAmount',
'lockCurrency',
'foreignAmount',
'foreignCurrency',
'account',
'bilanceAfterTransaction',
'accountCurrency',
'unknown1',
'unknown2',
'unknown3',
'unknown4',
];
type IngTransaction = {
[K in typeof headers[number]]: string;
};
const readIngTransaction = (data: string[]): IngTransaction|undefined => {
if (data.length !== headers.length) {
return;
}
const transaction: IngTransaction = {};
headers.forEach((key, index) => {
transaction[key] = data[index];
});
return transaction;
};
export default class extends BaseTransactionParser<ParserConfig> {
protected requiredFields = [];
async parse(config: ParserConfig, data: string[]): Promise<Transaction | undefined> {
const ing = readIngTransaction(data);
if (!ing) {
return undefined;
}
return {
account: "TODO: unknown account (not supported yet)",
date: ing.transactionDate,
amount: this.parseAmount(ing.transactionAmount),
imported_payee: ing.contrahentData?.trim()?.replaceAll(/\s+/g, " ")
}
}
}