mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-30 02:29:58 -05:00
enhancement: add BPER Italy bank parser (BPER_RETAIL_BPMOIT22) (#5741)
* Refine BPER retail parser with anonymized fixtures * style: streamline bper parser comments * chore: add release note for BPER Italy parser
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Normalize BPER Retail BPMOIT22 transactions by extracting a friendly payee
|
||||
* while keeping the raw description in the notes field.
|
||||
*/
|
||||
|
||||
import Fallback from './integration-bank.js';
|
||||
|
||||
const CARD_PAYMENT_PREFIX = 'PAGAMENTO SU CIRCUITO INTERNAZIONALE';
|
||||
const CARD_PAYMENT_SUFFIX = 'Operazione carta';
|
||||
const BONIFICO_PREFIX = 'BONIFICO';
|
||||
const BONIFICO_ESTERI_PREFIX = 'BONIFICI ESTERI';
|
||||
const SDD_PREFIX = 'ADDEBITO SDD';
|
||||
const BOLLETTINO_MARKER = 'CREDITORE:';
|
||||
|
||||
const BONIFICO_ORIGINATOR_REGEX =
|
||||
/o\/c:\s*([A-Z0-9\s.'/&-]+?)(?:ABI|BIC|IBAN|a favore di|Num|EUR|$)/i;
|
||||
const SDD_PAYEE_REGEX = /ADDEBITO SDD\s+([A-Z0-9\s.'/&-]+?)(?:N:|ID:|$)/i;
|
||||
const BOLLETTINO_PAYEE_REGEX = /CREDITORE:\s*([A-Z0-9\s.'/&-]+)/i;
|
||||
|
||||
// Extract payee for card transactions
|
||||
function parseCardPayee(description) {
|
||||
const [beforeSuffix] = description.split(CARD_PAYMENT_SUFFIX);
|
||||
|
||||
return beforeSuffix.replace(CARD_PAYMENT_PREFIX, '').trim();
|
||||
}
|
||||
|
||||
// Extract originator for bonifico (domestic/foreign transfers)
|
||||
function parseBonificoOriginator(description) {
|
||||
const match = description.match(BONIFICO_ORIGINATOR_REGEX);
|
||||
|
||||
return match ? match[1].trim() : '';
|
||||
}
|
||||
|
||||
// Extract creditor for SDD direct debits
|
||||
function parseSddPayee(description) {
|
||||
const match = description.match(SDD_PAYEE_REGEX);
|
||||
|
||||
return match ? match[1].trim() : '';
|
||||
}
|
||||
|
||||
// Extract creditor for bollettini / utilities
|
||||
function parseBollettinoPayee(description) {
|
||||
const match = description.match(BOLLETTINO_PAYEE_REGEX);
|
||||
|
||||
return match ? match[1].trim() : '';
|
||||
}
|
||||
|
||||
function setPayee(editedTransaction, payee) {
|
||||
if (!payee) {
|
||||
return;
|
||||
}
|
||||
|
||||
editedTransaction.creditorName = payee;
|
||||
editedTransaction.debtorName = payee;
|
||||
}
|
||||
|
||||
/** @type {import('./bank.interface.js').IBank} */
|
||||
const BperRetailBank = {
|
||||
...Fallback,
|
||||
|
||||
institutionIds: ['BPER_RETAIL_BPMOIT22'],
|
||||
|
||||
normalizeTransaction(transaction, booked) {
|
||||
const editedTrans = { ...transaction };
|
||||
const description = (
|
||||
transaction.remittanceInformationUnstructured || ''
|
||||
).trim();
|
||||
|
||||
if (description) {
|
||||
editedTrans.remittanceInformationUnstructured = description;
|
||||
}
|
||||
|
||||
let payee = '';
|
||||
|
||||
if (description.startsWith(CARD_PAYMENT_PREFIX)) {
|
||||
payee = parseCardPayee(description);
|
||||
} else if (
|
||||
description.startsWith(BONIFICO_PREFIX) ||
|
||||
description.startsWith(BONIFICO_ESTERI_PREFIX)
|
||||
) {
|
||||
payee = parseBonificoOriginator(description);
|
||||
} else if (description.startsWith(SDD_PREFIX)) {
|
||||
payee = parseSddPayee(description);
|
||||
} else if (description.includes(BOLLETTINO_MARKER)) {
|
||||
payee = parseBollettinoPayee(description);
|
||||
}
|
||||
|
||||
setPayee(editedTrans, payee);
|
||||
|
||||
return Fallback.normalizeTransaction(transaction, booked, editedTrans);
|
||||
},
|
||||
};
|
||||
|
||||
export default BperRetailBank;
|
||||
@@ -0,0 +1,122 @@
|
||||
import BperRetail from '../bper_retail_bpmoit22.js';
|
||||
|
||||
const bookingDate = '2025-09-17';
|
||||
|
||||
describe('BPER Retail BPMOIT22', () => {
|
||||
describe('#normalizeTransaction', () => {
|
||||
it('extracts card merchant between the circuit prefix and operation suffix', () => {
|
||||
const transaction = {
|
||||
bookingDate,
|
||||
remittanceInformationUnstructured:
|
||||
'PAGAMENTO SU CIRCUITO INTERNAZIONALE HEALTHCARE DISTRICT ZX042 METROPOLIS ITA Operazione carta ****8005 del 15.09.2025',
|
||||
transactionAmount: { amount: '-17.80', currency: 'EUR' },
|
||||
};
|
||||
|
||||
const normalizedTransaction = BperRetail.normalizeTransaction(
|
||||
transaction,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(normalizedTransaction.payeeName).toEqual(
|
||||
'Healthcare District Zx042 Metropolis Ita',
|
||||
);
|
||||
expect(normalizedTransaction.notes).toEqual(
|
||||
'PAGAMENTO SU CIRCUITO INTERNAZIONALE HEALTHCARE DISTRICT ZX042 METROPOLIS ITA Operazione carta ****8005 del 15.09.2025',
|
||||
);
|
||||
});
|
||||
|
||||
it('extracts bonifico originator appearing after o/c:', () => {
|
||||
const transaction = {
|
||||
bookingDate,
|
||||
remittanceInformationUnstructured:
|
||||
'BONIFICO o/c: ACME CONSULTING SRL ABI-CAB: 03015-03200 a favore di Example Recipient Num. Bon.Sepa 252531000195141 Note di cortesia',
|
||||
transactionAmount: { amount: '1000.00', currency: 'EUR' },
|
||||
};
|
||||
|
||||
const normalizedTransaction = BperRetail.normalizeTransaction(
|
||||
transaction,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(normalizedTransaction.payeeName).toEqual('Acme Consulting Srl');
|
||||
expect(normalizedTransaction.notes).toEqual(
|
||||
'BONIFICO o/c: ACME CONSULTING SRL ABI-CAB: 03015-03200 a favore di Example Recipient Num. Bon.Sepa 252531000195141 Note di cortesia',
|
||||
);
|
||||
});
|
||||
|
||||
it('extracts foreign bonifico originator before the BIC marker', () => {
|
||||
const transaction = {
|
||||
bookingDate,
|
||||
remittanceInformationUnstructured:
|
||||
'BONIFICI ESTERI o/c: GLOBAL PARTNERS LTD BIC: EXAMPGB2L a favore di Example Recipient (BPER) Num. Bon.Sepa 252131000238275BE Memo casuale 1.000,00 EUR',
|
||||
transactionAmount: { amount: '1000.00', currency: 'EUR' },
|
||||
};
|
||||
|
||||
const normalizedTransaction = BperRetail.normalizeTransaction(
|
||||
transaction,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(normalizedTransaction.payeeName).toEqual('Global Partners Ltd');
|
||||
expect(normalizedTransaction.notes).toEqual(
|
||||
'BONIFICI ESTERI o/c: GLOBAL PARTNERS LTD BIC: EXAMPGB2L a favore di Example Recipient (BPER) Num. Bon.Sepa 252131000238275BE Memo casuale 1.000,00 EUR',
|
||||
);
|
||||
});
|
||||
|
||||
it('extracts creditor for SDD direct debits', () => {
|
||||
const transaction = {
|
||||
bookingDate,
|
||||
remittanceInformationUnstructured:
|
||||
'ADDEBITO SDD CLOUD HOSTING LTD N: 1057087621/48 ID:0210000049513 Cod.Cl. K309846700/ Fatt. 109993626070 Deb: Example Account Owner',
|
||||
transactionAmount: { amount: '-1.22', currency: 'EUR' },
|
||||
};
|
||||
|
||||
const normalizedTransaction = BperRetail.normalizeTransaction(
|
||||
transaction,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(normalizedTransaction.payeeName).toEqual('Cloud Hosting Ltd');
|
||||
expect(normalizedTransaction.notes).toEqual(
|
||||
'ADDEBITO SDD CLOUD HOSTING LTD N: 1057087621/48 ID:0210000049513 Cod.Cl. K309846700/ Fatt. 109993626070 Deb: Example Account Owner',
|
||||
);
|
||||
});
|
||||
|
||||
it('captures bollettino creditor after CREDITORE:', () => {
|
||||
const transaction = {
|
||||
bookingDate,
|
||||
remittanceInformationUnstructured:
|
||||
'PAGAMENTI DIVERSI DA INTERNET BANKING E CSA PAGAMENTO BOLLETTINO POSTALE 420251388002409360 DEL 22/06/2025 TRAMITE I.B. / CSA TIPO : 896 CCPOST : 000000000000 CREDITORE: UTILITY COMPANY S.P.A.',
|
||||
transactionAmount: { amount: '-171.34', currency: 'EUR' },
|
||||
};
|
||||
|
||||
const normalizedTransaction = BperRetail.normalizeTransaction(
|
||||
transaction,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(normalizedTransaction.payeeName).toEqual('Utility Company S.P.A.');
|
||||
expect(normalizedTransaction.notes).toEqual(
|
||||
'PAGAMENTI DIVERSI DA INTERNET BANKING E CSA PAGAMENTO BOLLETTINO POSTALE 420251388002409360 DEL 22/06/2025 TRAMITE I.B. / CSA TIPO : 896 CCPOST : 000000000000 CREDITORE: UTILITY COMPANY S.P.A.',
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to the original description when no pattern matches', () => {
|
||||
const transaction = {
|
||||
bookingDate,
|
||||
remittanceInformationUnstructured: 'COMPETENZE SPESE ED ONERI',
|
||||
transactionAmount: { amount: '-4.90', currency: 'EUR' },
|
||||
};
|
||||
|
||||
const normalizedTransaction = BperRetail.normalizeTransaction(
|
||||
transaction,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(normalizedTransaction.payeeName).toEqual(
|
||||
'Competenze Spese Ed Oneri',
|
||||
);
|
||||
expect(normalizedTransaction.notes).toEqual('COMPETENZE SPESE ED ONERI');
|
||||
});
|
||||
});
|
||||
});
|
||||
6
upcoming-release-notes/5741.md
Normal file
6
upcoming-release-notes/5741.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [dirk-apers]
|
||||
---
|
||||
|
||||
Add BPER Italy bank parser (BPER_RETAIL_BPMOIT22)
|
||||
Reference in New Issue
Block a user