feat(core): Add support for CI workflows and update gitignore

This commit is contained in:
2024-07-05 11:03:59 +02:00
commit 7fc8f04be9
35 changed files with 7285 additions and 0 deletions

8
ts/00_commitinfo_data.ts Normal file
View File

@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@fin.cx/csvparser',
version: '1.1.0',
description: 'A TypeScript-based parser for CSV files from various financial service providers.'
}

View File

@ -0,0 +1,98 @@
import * as plugins from '../csvparser.plugins.js';
import * as interfaces from './interfaces/index.js';
export class CsvCommerzbank extends plugins.portablefinance
.AcCsvParser<interfaces.ICommerzbankTransaction> {
// INSTANCE
public paymentProviderName = 'Commerzbank';
public description: string = `a csv parser for parsing downloaded csv transaction files from Commerzbank`;
private csvDescriptorArray: plugins.portablefinance.ICsvDescriptor[] = [];
constructor() {
super();
}
addCsvDecriptor(csvDescriptorArg: plugins.portablefinance.ICsvDescriptor): void {
this.csvDescriptorArray.push(csvDescriptorArg);
}
public async getTransactions(): Promise<plugins.portablefinance.IMonetaryTransaction[]> {
const payments: interfaces.ICommerzbankOriginalTransaction[] = [];
for (const csvDescriptor of this.csvDescriptorArray) {
const csvInstance = await plugins.smartcsv.Csv.createCsvFromString(csvDescriptor.contentString, {
headers: true,
unquote: true,
});
payments.push(
...(await csvInstance.exportAsObject())
);
}
const finalTransactionArray: interfaces.ICommerzbankTransaction[] = [];
for (const transaction of payments) {
// transaction.Buchungstag = transaction.Wertstellung;
console.log(transaction);
const finalTransaction: interfaces.ICommerzbankTransaction = {
simpleTransaction: null,
transactionHash: null,
original: transaction,
amount: plugins.smartmoney.parseEuropeanNumberString(transaction.Betrag),
currency: transaction.Währung,
description: transaction.Buchungstext,
transactionDate: plugins.smarttime.ExtendedDate.fromEuropeanDate(transaction.Buchungstag),
valuationDate: plugins.smarttime.ExtendedDate.fromEuropeanDate(transaction.Wertstellung),
transactionType: ((): interfaces.TTransactionType => {
switch (transaction.Umsatzart) {
case 'Gutschrift':
return 'Credit';
case 'Lastschrift':
return 'Debit';
case 'Zinsen/Entgelte':
return 'BankFees';
case 'Überweisung':
return 'ActiveTransfer';
default:
throw new Error(`unknown transactiontype ${transaction.Umsatzart}`);
}
})(),
};
// lets assign the transactionHash
finalTransaction.transactionHash = await plugins.smarthash.sha265FromObject({
description: finalTransaction.description,
amount: finalTransaction.amount,
date: finalTransaction.valuationDate,
});
finalTransaction.simpleTransaction = {
id: finalTransaction.transactionHash,
accountId: null,
name: finalTransaction.description,
amount: finalTransaction.amount,
description: finalTransaction.description,
date: finalTransaction.transactionDate,
};
finalTransactionArray.push(finalTransaction);
}
return finalTransactionArray.map((commerzbankTransaction) => {
const fin2021Transaction: plugins.portablefinance.IMonetaryTransaction = {
id: null,
data: {
additionalIds: [],
amount: commerzbankTransaction.amount,
date: commerzbankTransaction.transactionDate.getTime(),
description: commerzbankTransaction.description,
name: commerzbankTransaction.description,
originAccountId: null,
originTransactionId: commerzbankTransaction.transactionHash,
paymentAccountId: null,
justForLooks: null,
},
};
return fin2021Transaction;
});
}
}

1
ts/commerzbank/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './csv-commerzbank.classes.csvcommerzbank.js';

View File

@ -0,0 +1 @@
export * from './interfaces.commerzbanktransaction.js';

View File

@ -0,0 +1,30 @@
import * as plugins from '../../csvparser.plugins.js';
export interface ICommerzbankOriginalTransaction {
Buchungstag: string;
Wertstellung: string;
Umsatzart: 'Überweisung' | 'Gutschrift' | 'Lastschrift' | 'Zinsen/Entgelte';
Buchungstext: string;
Betrag: string;
Währung: string;
Auftraggeberkonto: string;
'Bankleitzahl Auftraggeberkonto': string;
'IBAN Auftraggeberkonto': string;
Kategorie: string;
}
export type TTransactionType = 'Credit' | 'Debit' | 'ActiveTransfer' | 'BankFees';
export interface ICommerzbankTransaction {
simpleTransaction: plugins.tsclass.finance.ITransaction;
transactionHash: string;
original: ICommerzbankOriginalTransaction;
// translated to English
transactionDate: plugins.smarttime.ExtendedDate;
valuationDate: plugins.smarttime.ExtendedDate;
transactionType: TTransactionType;
description: string;
amount: number;
currency: string;
}

26
ts/csvparser.plugins.ts Normal file
View File

@ -0,0 +1,26 @@
// node native scope
import * as path from 'path';
export { path };
// fin.cx scope
import * as portablefinance from '@fin.cx/portablefinance';
export {
portablefinance
}
// pushrocks scope
import * as smartcsv from '@push.rocks/smartcsv';
import * as smartfile from '@push.rocks/smartfile';
import * as smarthash from '@push.rocks/smarthash';
import * as smartmoney from '@push.rocks/smartmoney';
import * as smartstring from '@push.rocks/smartstring';
import * as smarttime from '@push.rocks/smarttime';
export { smartcsv, smartfile, smarthash, smartmoney, smartstring, smarttime };
// tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };

View File

@ -0,0 +1,55 @@
import * as plugins from '../csvparser.plugins.js';
import { ExtendedDate } from '@push.rocks/smarttime';
import * as interfaces from './interfaces/index.js';
export class CsvFidor extends plugins.portablefinance.AcCsvParser<plugins.portablefinance.IMonetaryTransaction> {
// INSTANCE
public paymentProviderName: string = 'Fidor Bank AG';
public description: string = 'a csv parser optimized for csv files from Fidor.';
public csvDescriptorArray: plugins.portablefinance.ICsvDescriptor[] = [];
constructor() {
super();
}
addCsvDecriptor (csvDescriptorArg: plugins.portablefinance.ICsvDescriptor): void {
this.csvDescriptorArray.push(csvDescriptorArg);
}
/**
* returns the transactions
*/
public async getTransactions(): Promise<plugins.portablefinance.IMonetaryTransaction[]> {
const payments: plugins.portablefinance.IMonetaryTransaction[] = [];
for (const csvDescriptor of this.csvDescriptorArray) {
const csvInstance = new plugins.smartcsv.Csv(csvDescriptor.contentString, {
headers: true
});
const fidorTransactionArray: interfaces.IFidorOriginalTransaction[] = await csvInstance.exportAsObject();
for (const transaction of fidorTransactionArray) {
payments.push({
id: await plugins.smarthash.sha265FromObject(transaction),
data: {
additionalIds: [],
amount: parseFloat(transaction.Wert.replace('.', '').replace(',', '.')),
date: plugins.smarttime.ExtendedDate.fromEuropeanDate(transaction.Datum).getTime(),
name: transaction.Beschreibung,
description: transaction.Beschreibung2,
originAccountId: null,
originTransactionId: null,
paymentAccountId: null,
justForLooks: null,
voucherData: null,
}
})
}
}
return payments;
}
}

1
ts/fidor/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './csv-fidor.classes.csvfidor.js';

View File

@ -0,0 +1 @@
export * from './interfaces.fidortransaction.js';

View File

@ -0,0 +1,6 @@
export interface IFidorOriginalTransaction {
Datum: string;
Beschreibung: string;
Beschreibung2: string;
Wert: string;
}

4
ts/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from './commerzbank/index.js';
export * from './fidor/index.js';
export * from './paypal/index.js';
export * from './spendesk/index.js';

View File

@ -0,0 +1,147 @@
import * as plugins from '../csvparser.plugins.js';
import * as interfaces from './interfaces/index.js';
export class CsvPayPal extends plugins.portablefinance
.AcCsvParser<plugins.portablefinance.IMonetaryTransaction> {
// INSTANCE
public paymentProviderName = 'PayPal';
public description: string = 'a csv parser optimized for PayPal obtained csv files.';
public csvDescriptorArray: plugins.portablefinance.ICsvDescriptor[] = [];
constructor() {
super();
}
/**
* gets transactions
*/
public async getTransactions() {
const payments: plugins.portablefinance.IMonetaryTransaction[] = [];
for (const csvDescriptor of this.csvDescriptorArray) {
let stringToParse = csvDescriptor.contentString;
stringToParse = stringToParse.replace(/"(.*?)"/gi, (match, p1, offset, originalString) => {
return plugins.smartstring.base64.encodeUri(match);
});
const smartCsvInstance = new plugins.smartcsv.Csv(stringToParse, {
headers: true,
});
const payPalTransactions: interfaces.IPayPalCsvOriginalTransaction[] = (
await smartCsvInstance.exportAsObject()
).map((originalTransaction) => {
// tslint:disable-next-line: no-object-literal-type-assertion
const decodedTransaction = {} as interfaces.IPayPalCsvOriginalTransaction;
for (const key in originalTransaction) {
if (originalTransaction[key]) {
let finalKey = plugins.smartstring.base64.decode(key);
finalKey = finalKey.replace(/['"]+/g, '');
let finalValue = plugins.smartstring.base64.decode(originalTransaction[key]);
finalValue = finalValue.replace(/['"]+/g, '');
decodedTransaction[finalKey] = finalValue;
}
}
// pushing the ready transaction
return decodedTransaction;
});
// adjust numberFormat
const anf = (numberString: string): number => {
return parseFloat(numberString.replace(/\,/g, '.'));
};
const monetaryTransactions: interfaces.IPayPalTransaction[] = [];
for (const originalTransaction of payPalTransactions) {
const paypalTransaction: interfaces.IPayPalTransaction = {
// assigned later
transactionHash: null,
simpleTransaction: null,
// assigned now
originalTransaction,
transactionDate: plugins.smarttime.ExtendedDate.fromEuropeanDateAndTime(
originalTransaction.Datum,
originalTransaction.Uhrzeit,
'Europe/Berlin'
),
transactionCode: originalTransaction.Transaktionscode,
linkedTransactionCode: originalTransaction['Zugehöriger Transaktionscode'],
timezone: originalTransaction.Zeitzone,
bankAccount: originalTransaction.Bankkonto,
bankName: originalTransaction['Name der Bank'],
brutto: anf(originalTransaction.Brutto),
netto: anf(originalTransaction.Netto),
credit: anf(originalTransaction.Guthaben),
fee: anf(originalTransaction.Gebühr),
processingAndShippingFee: anf(originalTransaction['Versand- und Bearbeitungsgebühr']),
currency: originalTransaction.Währung,
description: originalTransaction.Beschreibung,
invoiceNumber: originalTransaction.Rechnungsnummer,
name: originalTransaction.Name,
payeeEmail: originalTransaction['Absender E-Mail-Adresse'],
transactionTime: originalTransaction.Uhrzeit,
vatAmount: anf(originalTransaction.Umsatzsteuer),
};
monetaryTransactions.push(paypalTransaction);
}
const foreignTransactions: interfaces.IPayPalTransaction[] = [];
const eurTransactions: interfaces.IPayPalTransaction[] = monetaryTransactions.filter(
(payPalTransaction: interfaces.IPayPalTransaction) => {
const isEur = payPalTransaction.currency === 'EUR';
if (isEur) {
return true;
} else {
foreignTransactions.push(payPalTransaction);
return false;
}
}
);
let finalReturnTransactions = eurTransactions.map((transaction) => {
if (transaction.brutto > 0) {
return transaction; // lets don't bother with payments from the bank
}
const eurTime = transaction.transactionDate.getTime();
const foreignCandidates: interfaces.IPayPalTransaction[] = [];
for (const foreignTransaction of foreignTransactions) {
const foreignTime = foreignTransaction.transactionDate.getTime();
if (eurTime === foreignTime) {
foreignCandidates.push(foreignTransaction);
}
}
if (foreignCandidates.length !== 2 && foreignCandidates.length !== 0) {
console.log('error! Found a weird amoun of corresponding foreign transactions');
}
if (foreignCandidates.length === 2) {
const wantedForeignTransaction = foreignCandidates.find((foreignTransaction) => {
return foreignTransaction.brutto < 0;
});
transaction.description = wantedForeignTransaction.description;
transaction.payeeEmail = wantedForeignTransaction.payeeEmail;
transaction.name = wantedForeignTransaction.name;
}
return transaction;
});
// lets assign simple transactions at last
finalReturnTransactions = finalReturnTransactions.map((transaction) => {
transaction.simpleTransaction = {
accountId: null,
id: transaction.transactionCode,
amount: transaction.brutto,
date: transaction.transactionDate,
description: transaction.description,
name: transaction.name,
};
return transaction;
});
const csvPayPalInstance = new CsvPayPal(finalReturnTransactions);
return csvPayPalInstance;
}
return payments;
}
}

1
ts/paypal/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './csv-paypal.classes.csvpaypal';

View File

@ -0,0 +1 @@
export * from './interfaces.paypaltransaction.js';

View File

@ -0,0 +1,49 @@
import * as plugins from '../../csvparser.plugins.js';
export interface IPayPalCsvOriginalTransaction {
Datum: string;
Uhrzeit: string;
Zeitzone: string;
Beschreibung: string;
Währung: string;
Brutto: string;
Gebühr: string;
Netto: string;
Guthaben: string;
Transaktionscode: string;
'Absender E-Mail-Adresse': string;
Name: string;
'Name der Bank': string;
Bankkonto: string;
'Versand- und Bearbeitungsgebühr': string;
Umsatzsteuer: string;
Rechnungsnummer: string;
'Zugehöriger Transaktionscode': string;
}
export interface IPayPalTransaction {
// standardised
simpleTransaction: plugins.tsclass.finance.ITransaction;
originalTransaction;
transactionHash: string;
// specific
transactionCode: string;
transactionDate: Date;
transactionTime: string;
timezone: string;
description: string;
currency: string;
brutto: number;
fee: number;
netto: number;
credit: number;
payeeEmail: string;
name: string;
bankName: string;
bankAccount: string;
processingAndShippingFee: number;
vatAmount: number;
invoiceNumber: string;
linkedTransactionCode: string;
}

View File

@ -0,0 +1,205 @@
import * as plugins from '../csvparser.plugins.js';
import * as interfaces from './interfaces/index.js';
export class CsvSpendesk extends plugins.portablefinance.AcCsvParser<any> {
// ========= STATIC ================
/**
* get the SpendeskData from an extracted direcotory
* @param dirPath
*/
public static async fromFile(filePath: string): Promise<CsvSpendesk> {
const reresolvedPath = plugins.path.resolve(filePath);
const fileString = plugins.smartfile.fs.toStringSync(reresolvedPath);
const csvSpendesk = await CsvSpendesk.fromCsvString(fileString);
return csvSpendesk;
}
/**
* get the SpendeskData from an extracted direcotory
* @param dirPath
*/
public static async fromDir(dirPath: string): Promise<CsvSpendesk> {
const foundFiles: string[] = await plugins.smartfile.fs.listFileTree(
dirPath,
'**/Spendesk*',
true
);
if (foundFiles.length === 0) {
throw new Error('no files found!');
}
const csvSpendesks: CsvSpendesk[] = [];
for (const foundFile of foundFiles) {
const fileString = plugins.smartfile.fs.toStringSync(plugins.path.resolve(foundFile));
plugins.path.join(dirPath, foundFile);
csvSpendesks.push(await this.fromFile(foundFile));
}
let returnCsvSpendesk: CsvSpendesk;
for (const csvSpendeskInstance of csvSpendesks) {
if (!returnCsvSpendesk) {
returnCsvSpendesk = csvSpendeskInstance;
} else {
await returnCsvSpendesk.concat(csvSpendeskInstance);
}
}
return returnCsvSpendesk;
}
public static async fromCsvString(csvStringArg: string): Promise<CsvSpendesk> {
// lets parse the data from the directory
const csvInstance = await plugins.smartcsv.Csv.createCsvFromString(csvStringArg, {
headers: true
});
// lets differentiate between payments and credits
const originalTransactionArray: interfaces.ISpendeskOriginalTransaction[] = (await csvInstance.exportAsObject()) as interfaces.ISpendeskOriginalTransaction[];
const paymentsArray: interfaces.ISpendeskTransaction[] = [];
for (const originalTransaction of originalTransactionArray) {
const finalTransaction: interfaces.ISpendeskTransaction = {
// the original transaction
original: originalTransaction,
// assigned later
paymentType: null,
amount: null,
simpleTransaction: null,
transactionHash: null,
// assigned now
currency: originalTransaction.Currency as interfaces.TAvailableCurrencies,
description: originalTransaction.Description,
expenseAccount: originalTransaction['Expense account'],
month: originalTransaction.Month,
payer: originalTransaction.Payer,
paymentDate: new Date(originalTransaction['Payment date']),
paymentMethod: originalTransaction['Payment method'],
paymentState: originalTransaction.State as interfaces.TPaymentState,
settlementDate: new Date(originalTransaction['Settlement date']),
receiptAvailable: (() => {
if ((originalTransaction['Receipt?'] as any) === 'Yes') {
return true;
} else {
return false;
}
})(),
receiptNames: [],
supplier: originalTransaction.Supplier,
team: originalTransaction.Team,
vatAmount: parseFloat(originalTransaction.VAT),
vatPercentage: ((): number => {
if (!originalTransaction.VAT || originalTransaction.VAT === '0') {
return 0;
} else {
const vatAmount = parseFloat(originalTransaction.VAT);
const debitAmount = parseFloat(originalTransaction.Debit);
return Math.round((vatAmount / (debitAmount - vatAmount)) * 100);
}
})()
};
// type
finalTransaction.paymentType = (() => {
let paymentType: interfaces.TPaymentType;
if (parseFloat(finalTransaction.original.Credit) !== 0) {
paymentType = 'Load';
} else if (parseFloat(originalTransaction.Debit) !== 0) {
paymentType = 'Payment';
}
if (originalTransaction.Description.startsWith('FX fee')) {
paymentType = 'FXfee';
}
return paymentType;
})();
// amount
finalTransaction.amount = (() => {
switch (parseFloat(originalTransaction.Credit)) {
case 0:
return -parseFloat(originalTransaction.Debit);
default:
return parseFloat(originalTransaction.Credit);
}
})();
// transaction hash
finalTransaction.transactionHash = await plugins.smarthash.sha265FromObject({
amount: finalTransaction.amount,
transactionDate: finalTransaction.paymentDate,
settlementDate: finalTransaction.settlementDate,
supplier: finalTransaction.supplier
});
// simple transaction
finalTransaction.simpleTransaction = {
accountId: null,
id: finalTransaction.transactionHash,
amount: finalTransaction.amount,
date: finalTransaction.settlementDate,
description: finalTransaction.description,
name: finalTransaction.supplier
};
paymentsArray.push(finalTransaction);
}
const csvSpendeskInstance = new CsvSpendesk(paymentsArray);
return csvSpendeskInstance;
}
/**
* get the SpendeskData from Spendesk.com
* @param dirPath
*/
public static async fromSpendeskCom(dirPath: string) {
// TODO: implement spendesk API
throw new Error(`method is not yet implemented`);
}
// ========= INSTANCE ================
public paymentProviderName: string = 'Spendesk';
public origin: 'api' | 'file' | 'dir';
public updateFunction: (
fromTimeStamp: plugins.smarttime.TimeStamp,
untilTimeStamp: plugins.smarttime.TimeStamp
) => interfaces.ISpendeskTransaction[];
public transactionArray: interfaces.ISpendeskTransaction[];
constructor(transactionArrayArg: interfaces.ISpendeskTransaction[]) {
super();
this.transactionArray = transactionArrayArg;
}
/**
* gets all transactions
*/
public async getTransactions() {
return this.transactionArray;
}
/**
* gets all loads
*/
public async getLoads() {
return this.transactionArray.filter(payment => {
return payment.paymentType === 'Load';
});
}
public async getDebits() {
return this.transactionArray.filter(payment => {
return payment.paymentType === 'Payment';
});
}
/**
* concat this instance's transactions with those of another one
*/
public async concat(csvSpendeskInstance: CsvSpendesk): Promise<CsvSpendesk> {
this.transactionArray = this.transactionArray.concat(csvSpendeskInstance.transactionArray);
return this;
}
}

1
ts/spendesk/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './csv-spendesk.classes.csvspendesk.js';

View File

@ -0,0 +1 @@
export * from './interface.spendesktransaction';

View File

@ -0,0 +1,55 @@
import * as plugins from '../csv-spendesk.plugins';
export type TAvailableCurrencies = 'EUR';
export type TPaymentState = 'Settled';
export type TPaymentType = 'Load' | 'Credit' | 'Payment' | 'FXfee';
export interface ISpendeskOriginalTransaction {
simpleTransaction: plugins.tsclass.ITransaction;
original: any;
'Payment date': string;
'Settlement date': string;
Month: string;
Payer: string;
Team: string;
Description: string;
Supplier: string;
'Expense account': string;
'Payment method': string;
Type: string;
// 'Local amount': number;
// 'Local currency': 'EUR';
Debit: string;
Credit: string;
Currency: string;
VAT: string;
vatPercentage?: string;
State: string;
'Receipt?': string;
'Receipt name(s)': '';
}
export interface ISpendeskTransaction {
simpleTransaction?: plugins.tsclass.ITransaction;
original: ISpendeskOriginalTransaction;
transactionHash: string;
paymentDate: Date;
settlementDate: Date;
month: string;
payer: string;
team: string;
description: string;
supplier: string;
expenseAccount: string;
paymentMethod: string;
paymentType: TPaymentType;
// 'Local amount': number;
// 'Local currency': 'EUR';
amount: number;
currency: TAvailableCurrencies;
vatAmount: number;
vatPercentage?: number;
paymentState: TPaymentState;
receiptAvailable: boolean;
receiptNames: string[];
}