11import Papa from 'papaparse'
2- import _ from 'lodash'
3- import { ICsvReadedTransaction , IFieldMapping , TransactionAction , Currencies } from '@poveroh/types'
2+ import { ICsvReadedTransaction , IFieldMapping , TransactionAction , Currencies , ICSVValueReturned } from '@poveroh/types'
43
54class CSVParser {
65 private datePatterns = [
@@ -53,7 +52,92 @@ class CSVParser {
5352 'transaction' ,
5453 'details' ,
5554 'product' ,
56- 'legenda'
55+ 'legenda' ,
56+ 'remark' ,
57+ 'text' ,
58+ 'transaction type' ,
59+ 'item' ,
60+ 'purpose' ,
61+ 'label' ,
62+ 'activity' ,
63+ 'particulars' ,
64+ 'explanation' ,
65+ 'subject' ,
66+ 'category' ,
67+ 'origin' ,
68+ 'destination' ,
69+ 'info' ,
70+ 'transaktionstext' ,
71+ 'betreff' ,
72+ 'Verwendungszweck' ,
73+ 'libelle' ,
74+ 'intitulé' ,
75+ 'descripción' ,
76+ 'concepto' ,
77+ 'referencia' ,
78+ 'historia' ,
79+ 'articolo' ,
80+ 'causale' ,
81+ 'ragione sociale' ,
82+ 'beneficiary' ,
83+ 'payer' ,
84+ 'counterparty' ,
85+ 'transaction id' ,
86+ 'trans id' ,
87+ 'operation' ,
88+ 'reason' ,
89+ 'comment' ,
90+ 'type' ,
91+ 'channel' ,
92+ 'document' ,
93+ 'invoice' ,
94+ 'receipt' ,
95+ 'statement' ,
96+ 'intestatario' ,
97+ 'beneficiario' ,
98+ 'ordinante' ,
99+ 'movimento' ,
100+ 'operazione' ,
101+ 'causale ABI' ,
102+ 'causale SIA' ,
103+ 'tipo operazione' ,
104+ 'descrizione operazione' ,
105+ 'dettaglio' ,
106+ 'trans details' ,
107+ 'trans description' ,
108+ 'account name' ,
109+ 'bank name' ,
110+ 'branch' ,
111+ 'location' ,
112+ 'description 1' ,
113+ 'description 2' ,
114+ 'narrative' ,
115+ 'posting text' ,
116+ 'source' ,
117+ 'recipient' ,
118+ 'sender' ,
119+ 'payee name' ,
120+ 'description of goods' ,
121+ 'order id' ,
122+ 'billing ref' ,
123+ 'clearing text' ,
124+ 'conto' ,
125+ 'codice operazione' ,
126+ 'riferimento' ,
127+ 'concept' ,
128+ 'designation' ,
129+ 'item description' ,
130+ 'line item' ,
131+ 'transaction detail' ,
132+ 'payment details' ,
133+ 'transaction reference' ,
134+ 'payment reference' ,
135+ 'transaction info' ,
136+ 'trans info' ,
137+ 'bank reference' ,
138+ 'customer reference' ,
139+ 'segno' ,
140+ 'descrizione estesa'
57141 ]
58142
59143 private dateKeywords = [
@@ -66,7 +150,58 @@ class CSVParser {
66150 'started' ,
67151 'when' ,
68152 'day' ,
69- 'datetime'
153+ 'datetime' ,
154+ 'posting date' ,
155+ 'transaction date' ,
156+ 'effective date' ,
157+ 'value date' ,
158+ 'settlement date' ,
159+ 'processing date' ,
160+ 'activity date' ,
161+ 'booked date' ,
162+ 'entry date' ,
163+ 'execution date' ,
164+ 'journal date' ,
165+ 'ora' ,
166+ 'giorno' ,
167+ 'fecha' ,
168+ 'hora' ,
169+ 'datum' ,
170+ 'uhrzeit' ,
171+ 'date operation' ,
172+ 'date valeur' ,
173+ 'date comptable' ,
174+ 'jour' ,
175+ 'transactiontime' ,
176+ 'system date' ,
177+ 'record date' ,
178+ 'payment date' ,
179+ 'emission date' ,
180+ 'scadenza' ,
181+ 'data contabile' ,
182+ 'data valuta' ,
183+ 'data operazione' ,
184+ 'data esecuzione' ,
185+ 'transfer date' ,
186+ 'report date' ,
187+ 'received date' ,
188+ 'sent date' ,
189+ 'cut-off date' ,
190+ 'statement date' ,
191+ 'period start' ,
192+ 'period end' ,
193+ 'data di accredito' ,
194+ 'data di addebito' ,
195+ 'data registrazione' ,
196+ 'start date' ,
197+ 'end date' ,
198+ 'due date' ,
199+ 'occurrence date' ,
200+ 'transaction_date' ,
201+ 'posting_date' ,
202+ 'valuta' ,
203+ 'data movimento' ,
204+ 'data contabilizzazione'
70205 ]
71206
72207 private amountKeywords = [
@@ -81,7 +216,89 @@ class CSVParser {
81216 'charge' ,
82217 'balance' ,
83218 'debit' ,
84- 'credit'
219+ 'credit' ,
220+ 'deposit' ,
221+ 'withdrawal' ,
222+ 'outflow' ,
223+ 'inflow' ,
224+ 'paid' ,
225+ 'received' ,
226+ 'currency' ,
227+ 'valore' ,
228+ 'prezzo' ,
229+ 'totale' ,
230+ 'saldo' ,
231+ 'addebito' ,
232+ 'accredito' ,
233+ 'montante' ,
234+ 'somme' ,
235+ 'débit' ,
236+ 'crédit' ,
237+ 'solde' ,
238+ 'importe' ,
239+ 'saldo' ,
240+ 'débito' ,
241+ 'crédito' ,
242+ 'betrag' ,
243+ 'summe' ,
244+ 'solde' ,
245+ 'soll' ,
246+ 'haben' ,
247+ 'net amount' ,
248+ 'gross amount' ,
249+ 'principal' ,
250+ 'interest' ,
251+ 'tax' ,
252+ 'payment' ,
253+ 'return' ,
254+ 'refund' ,
255+ 'disbursement' ,
256+ 'remittance' ,
257+ 'exchange' ,
258+ 'commission' ,
259+ 'discount' ,
260+ 'vat' ,
261+ 'tva' ,
262+ 'imposta' ,
263+ 'spese' ,
264+ 'interessi' ,
265+ 'capitale' ,
266+ 'salario' ,
267+ 'stipendio' ,
268+ 'rata' ,
269+ 'ammontare' ,
270+ 'due amount' ,
271+ 'final amount' ,
272+ 'subtotal' ,
273+ 'grand total' ,
274+ 'transaction amount' ,
275+ 'transfer amount' ,
276+ 'loan amount' ,
277+ 'initial amount' ,
278+ 'remaining balance' ,
279+ 'current balance' ,
280+ 'previous balance' ,
281+ 'commissione' ,
282+ 'prelievo' ,
283+ 'versamento' ,
284+ 'rata capitale' ,
285+ 'rata interessi' ,
286+ 'oneri' ,
287+ 'lordo' ,
288+ 'totale lordo' ,
289+ 'totale netto' ,
290+ 'importo originale' ,
291+ 'amount_usd' ,
292+ 'amount_eur' ,
293+ 'total_amount' ,
294+ 'current_balance' ,
295+ 'available balance' ,
296+ 'book balance' ,
297+ 'posted amount' ,
298+ 'transaction_amount' ,
299+ 'entry amount' ,
300+ 'valore nominale' ,
301+ 'differenza'
85302 ]
86303
87304 private detectFields ( headers : string [ ] , sampleRows : Record < string , any > [ ] ) : IFieldMapping {
@@ -218,8 +435,8 @@ class CSVParser {
218435 return isNaN ( parsed ) ? 0 : parsed
219436 }
220437
221- private parseDate ( value : string ) : string {
222- if ( ! value ) return new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ]
438+ private parseDate ( value : string ) : Date {
439+ if ( ! value ) return new Date ( )
223440
224441 const attempts = [
225442 ( ) => new Date ( value ) ,
@@ -267,35 +484,40 @@ class CSVParser {
267484 try {
268485 const date = attempt ( )
269486 if ( date instanceof Date && ! isNaN ( date . getTime ( ) ) ) {
270- return date . toISOString ( ) . split ( 'T' ) [ 0 ]
487+ return date
271488 }
272489 } catch {
273490 continue
274491 }
275492 }
276493
277494 console . warn ( `Unable to parse date: ${ value } , using current date` )
278- return new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ]
495+ return new Date ( )
279496 }
280497
281- private extractCurrency ( row : Record < string , any > , currencyField ?: string ) : string {
498+ private extractCurrency ( row : Record < string , any > , currencyField ?: string ) : Currencies {
282499 if ( currencyField && row [ currencyField ] ) {
283- const currency = String ( row [ currencyField ] ) . trim ( )
500+ const currency = String ( row [ currencyField ] )
501+ . trim ( )
502+ . replace ( / [ ^ A - Z € $ £ ¥ ₹ ₽ ] / g, '' )
284503 if ( this . isLikelyCurrency ( currency ) ) {
285- return currency . replace ( / [ ^ A - Z € $ £ ¥ ₹ ₽ ] / g, '' )
504+ const match = Currencies [ currency as keyof typeof Currencies ]
505+ return match ?? Currencies . UNKNOWN
286506 }
287507 }
288508
289- for ( const [ key , value ] of Object . entries ( row ) ) {
509+ for ( const [ _ , value ] of Object . entries ( row ) ) {
290510 if ( value && this . isLikelyCurrency ( String ( value ) ) ) {
291511 const match = String ( value ) . match ( / E U R | U S D | G B P | J P Y | C H F | C A D | A U D | € | \$ | £ | ¥ | ₹ | ₽ / i)
292512 if ( match ) {
293- return match [ 0 ] . toUpperCase ( )
513+ const cleaned = match [ 0 ] . toUpperCase ( ) . replace ( / [ ^ A - Z € $ £ ¥ ₹ ₽ ] / g, '' )
514+ const matchCurrency = Currencies [ cleaned as keyof typeof Currencies ]
515+ return matchCurrency ?? Currencies . UNKNOWN
294516 }
295517 }
296518 }
297519
298- return ' UNKNOWN'
520+ return Currencies . UNKNOWN
299521 }
300522
301523 private findDataTableStart ( csvData : string ) : {
@@ -495,19 +717,7 @@ class CSVParser {
495717 } )
496718 }
497719
498- public async parseCSVFile ( fileContent : string ) : Promise < {
499- transactions : ICsvReadedTransaction [ ]
500- mapping : IFieldMapping
501- errors : string [ ]
502- detectedStartRow ?: number
503- summary : {
504- totalTransactions : number
505- totalIncome : number
506- totalExpenses : number
507- dateRange : { from : string ; to : string }
508- currencies : string [ ]
509- }
510- } > {
720+ public async parseCSVFile ( fileContent : string ) : Promise < ICSVValueReturned > {
511721 const result = await this . parseCSV ( fileContent )
512722
513723 const summary = {
@@ -517,12 +727,7 @@ class CSVParser {
517727 . reduce ( ( sum , t ) => sum + t . amount , 0 ) ,
518728 totalExpenses : result . transactions
519729 . filter ( t => t . type === TransactionAction . EXPENSES )
520- . reduce ( ( sum , t ) => sum + t . amount , 0 ) ,
521- dateRange : {
522- from : result . transactions . length > 0 ? _ . min ( result . transactions . map ( t => t . date ) ) || '' : '' ,
523- to : result . transactions . length > 0 ? _ . max ( result . transactions . map ( t => t . date ) ) || '' : ''
524- } ,
525- currencies : _ . uniq ( result . transactions . map ( t => t . currency ) )
730+ . reduce ( ( sum , t ) => sum + t . amount , 0 )
526731 }
527732
528733 return { ...result , summary }
0 commit comments