Files
roacont/docs/cercetare/piata_ai_2026_09/COD_EXTRASE_BANCA.md
Marius Mutu f9af574877 Dovezile masurate pentru propunerile de directie (09.2026)
Backtest read-only pe productie: recunoasterea articolului la importul de
eFactura, contarea pe istoric de furnizor, identificarea partenerului la
importul de extrase, plafonul potrivirii facturii din textul bancii.
Include harta codului de import extrase, testul care confirma eroarea de
parsare BT si scripturile SQL reproductibile.

Propunerile care se sprijina pe ele:
COMUN/docs/cercetare/rec_directie_roacont_2026_09.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8jmGHs29e9eyLgoMiLBHW
2026-09-03 14:19:16 +03:00

17 KiB

Import extrase de banca si imperecherea partener/factura - cum functioneaza azi

Cercetare de cod, fara nicio modificare. Toate afirmatiile au citat fisier:linie din D:\roa\ROACONT. Sursa e VFP compilat in .prg/.vcx; codul citat mai jos e din .prg (text direct, nu are nevoie de .vc2).

A. Harta fluxului

  1. Pornire: Programe\ocont2003.prg:845 (lans_import_extras) deschide setul de note 90023 = "IMPORT EXTRAS MT940" (ocont2003.prg:858, ocont2003.prg:964-965).
  2. note_fara_predefinire (ocont2003.prg:948) pregateste cursorul tAct si apeleaza import_extras('tAct', ...) la ocont2003.prg:1226.
  3. import_extras (ocont2003.prg:866) instantiaza clasa fabrica ImportNote din Programe\oproceduri_import.prg:57 si cheama .Import(). In functie de fisierul ales, ImportNote (oproceduri_import.prg:120-227) alege subclasa potrivita: ExtrasBanca_MT940 (Raiffeisen etc.), ExtrasBanca_CREDITEUROPE_XML, ExtrasBanca_UNICREDIT_CSV, sau ExtrasBanca_General (oproceduri_import.prg:1415) - clasa "detectez singur formatul", folosita si pentru fisierele Banca Transilvania.
  4. ExtrasBanca_General::Parse (oproceduri_import.prg:1417) citeste prima linie nevida a fisierului si decide dupa continutul ei ce parser cheama. Pentru CSV sunt ~25 formate bancare/procesatori de plati recunoscute dupa antet (oproceduri_import.prg:1452-1531: Stripe, Booking, PayU, Intesa, FanCourier, Cargus, DPD, Garanti, OTP, Raiffeisen, Unicredit, ING, ING Business, BT (linia 1502-1507), PayPal, BRD, BCR, First Bank, CEC, MobilPay, Un-Doi, Paypoint, Idea, CreditEurope, Alpha).
  5. Pentru Banca Transilvania liniile 1502-1507 detecteaza doua variante de export (lista de tranzactii lunara vs. extras de cont) si cheama This.csvBT(fisier, 1) respectiv This.csvBT(fisier, 2). Parserul activ e csvBT la oproceduri_import.prg:2923-3025 (functia veche csvBT_original, linia 3027, nu mai e apelata de nicaieri).
  6. Rezultatul parsarii ajunge in cursorul local c_iex, apoi C_IMPORT_TEMP (oproceduri_import.prg:1544-1550), care alimenteaza motorul de imperechere ExtrasBanca::CreeazaNote (oproceduri_import.prg:312-926) - aici se cauta partenerul si factura pentru fiecare linie.
  7. Rezultatul (cActTemp -> tAct) e afisat operatorului intr-un formular de corectie manuala, frm_modific2024 (instantiat la ocont2003.prg:1229), unde utilizatorul poate modifica orice camp (partener, cont, factura) inainte de salvare.
  8. La apasarea butonului de salvare (buton = 1, ocont2003.prg:1245-1257), cursorul corectat e scris in tabela Oracle ACTACTAN (jurnalul contabil) prin oscrie_in_fisiere (ocont2003.prg:1256). Coloanele relevante confirmate din INSERT INTO actactan(...) real (Programe\oproceduri_incasari.prg:270-279, Programe\oproceduri_inchidere.prg:856-879) si din vederea vact_tot/xnote (COMUN\programe\ocont2003... / Programe\ocont2003.prg:1101-1183): nr_nota, an, luna, dataireg, nract, dataact, explicatia, scd, ascd, scc, ascc, suma, suma_val, id_valuta, curs, id_set, id_fact, id_partd, id_partc, id_factd, id_factc, pereched, perechec, id_fdoc. Liniile provenite din import extras au id_set = 90023 (ocont2003.prg:858), ceea ce le face identificabile ulterior in Oracle.
  9. Nomenclatorul de parteneri e tabela NOM_PARTENERI (cautat prin vederea vnom_parteneri, coloane cod_fiscal, denumire, cont_banca, sters, inactiv, COMUN\programe\oproceduri_comune.prg:6599-6667). Facturile/documentele de imperecheat sunt in tabela IREG_PARTENERI (fisa partener pe cont/an/luna, coloane id_fact, cont, acont, nract, id_part, an, luna, precdeb, debit, preccred, credit, id_lucrare, cautate in GetDocumentByContPartenerAct, COMUN\programe\oproceduri_comune.prg:6984-7057).

Pe scurt: (a) manual = operatorul introduce direct in frm_modific2024 fara pasii 3-6. (b) import fisier = pasii 1-9 de mai sus; pasul 7 (formularul) e exact punctul unde "operatorul intervine" mentionat de tine.

B. Cum se identifica azi PARTENERUL (client/furnizor)

Motorul e in ExtrasBanca::CreeazaNote, blocul de cautare parteneri (oproceduri_import.prg:433-547), rulat o singura data per (cod_fiscal, denumire, iban) distinct din import (oproceduri_import.prg:435), apoi aplicat pe toate liniile (oproceduri_import.prg:542-544). Ordinea de cautare, in cascada:

  1. Dupa cod fiscal (oproceduri_import.prg:453-488) - doar daca linia are cod fiscal completat. GetPartenerByCodFiscal (COMUN\programe\oproceduri_comune.prg:6599-6621) face potrivire exacta dupa cod fiscal normalizat (fara spatii, fara prefix "RO"), MAX(id_part) daca sunt mai multi identici. Daca acelasi CUI normalizat exista pe mai multi parteneri (grup de duplicate, GetParteneriByCuiNormalizat, oproceduri_import.prg:4515-4526), se alege partenerul din grup cu cele mai multe inregistrari (si cel mai mare sold) in ireg_parteneri pentru anul/luna curenta (oproceduri_import.prg:459-483, comentariu explicit la linia 52: "la CUI-uri duplicate aleg partenerul cu facturi in perioada curenta, nu ultimul creat").
  2. Dupa denumire (oproceduri_import.prg:490-513), doar daca pasul 1 nu a gasit nimic. GetPartenerByDenumire (COMUN\programe\oproceduri_comune.prg:6623-6645) face potrivire exacta TRIM(UPPER(denumire)) = ?pcDenumire - nu foloseste LIKE, nu elimina diacritice, nu normalizeaza forma juridica ("SRL" vs "S.R.L." vs fara sufix). Daca nu gaseste exact, incearca variante de ordine a cuvintelor pentru nume de persoane (GetNamePermutations, COMUN\programe\oproceduri_comune.prg:6789 si urm., apelat la oproceduri_import.prg:500-511) - util doar pentru "Popescu Ion" vs "Ion Popescu", nu pentru firme.
  3. Dupa IBAN (oproceduri_import.prg:516-523), doar daca pasii 1-2 au esuat. GetPartenerByContBanca (COMUN\programe\oproceduri_comune.prg:6647-6667) - potrivire exacta pe cont_banca.
  4. Creare automata de partener nou (oproceduri_import.prg:527-540) - doar daca are cod fiscal si optiunea This.lCreeazaParteneri e activa; fara cod fiscal, linia ramane fara partener (id_part = 0), vizibila operatorului in grid la pasul de corectie.

Cazul concret Banca Transilvania: parserul csvBT nu extrage niciodata codul fiscal al platitorului - mcf = "" e fixat necondiotionat la oproceduri_import.prg:3015. Asta inseamna ca pentru singurul format pe care il ai (BT), pasul 1 de mai sus (cod fiscal, cel mai sigur) nu se executa niciodata - se sare direct la potrivirea exacta dupa denumire (pasul 2), care e cea mai fragila. Denumirea partenerului extrasa din linia BT vine din pozitia 3 a campului "Descriere", despartit dupa ; (mtert = ALLTRIM(syGETWORDNUM(mexplicatie, 3, ';')), oproceduri_import.prg:3013), iar IBAN-ul partenerului din pozitia 4 (oproceduri_import.prg:3014).

C. Cum se identifica azi FACTURA

Tot in CreeazaNote, blocul "asociez document" (oproceduri_import.prg:701-817), care ruleaza doar daca optiunea This.lFactura e activa (cautare dupa numar de document, oproceduri_import.prg:710-764) sau This.lComanda (cautare dupa numar de comanda, oproceduri_import.prg:769-817, logica identica dar cauta si in nrord/explicatia prin LIKE).

Comentariul autorului chiar deasupra blocului descrie exact ce ar trebui sa faca si ce NU face azi (oproceduri_import.prg:701-706):

"Repet pana la epuizarea sumei de plata/incasare... Daca nu mai am documente, dar suma de plata nu este epuizata, imperechez facturile in ordine cronologica..." - acest TODO nu e implementat.

Ce se intampla efectiv, pas cu pas (oproceduri_import.prg:719-764):

  1. Se ia lista de numere gasite in text (documente, extrase de GetRegExpAllNumbers, oproceduri_import.prg:4480-4510 - ia toate numerele intregi din descriere, dupa ce elimina datele de forma zz.ll.aa(aa) si anul curent). Pentru BT, aceasta lista vine din pozitia 2 a campului "Descriere" (lcDescriereFacturi, oproceduri_import.prg:3017-3019).
  2. Pentru primul numar din lista, GetDocumentByContPartenerAct (COMUN\programe\oproceduri_comune.prg:6984-7057) cauta in ireg_parteneri documentul cu acel nract, pe acelasi partener si (daca exista) acelasi cont; daca gaseste mai mult de un rezultat, renunta si doar scrie in log ("Sunt prea multe rezultate care se potrivesc", linia 7038) - nu alege niciunul.
  3. Daca a gasit exact un document (oproceduri_import.prg:726), suma din extras e acceptata doar daca nu depaseste soldul ramas al facturii (lnSuma <= solddeb, liniile 728-734). Dupa acest test, bucla iese necondiotionat (EXIT, linia 736) - indiferent daca s-a facut sau nu vreo asociere.

Consecinte directe, cu explicatie:

  • Suma exacta pe o singura factura -> functioneaza (cazul comun, o plata = o factura).
  • O plata acopera mai multe facturi (suma > soldul primei facturi gasite) -> testul de la linia 728/731 esueaza, dar bucla oricum iese la EXIT (linia 736) fara sa incerce urmatorul numar de document din lista si fara sa distribuie suma pe facturi. Rezulta o linie fara factura pereche, desi numarul facturii a fost corect citit din text.
  • O plata partiala pe o factura (suma < sold) -> se asociaza, dar factura ramane cu sold neachitat corect in continuare (nu e un bug, e comportamentul dorit).
  • Avansuri (plata fara numar de factura in text) -> documente ramane gol, se forteaza totusi o cautare cu '0' (oproceduri_import.prg:715-717), care de regula nu gaseste nimic -> linia ramane fara factura, de corectat manual.
  • Compensari -> nu sunt tratate aici; sunt un modul separat (COMUN\programe\ocompensari.prg), nu fac parte din fluxul de import extras.
  • Comisioane bancare -> sunt recunoscute prin cuvinte cheie in descriere ("comision", "comision-atm", "pachet izi", etc., oproceduri_import.prg:2998-3012 pentru BT) si duse direct pe cont 627, fara sa treaca prin cautarea de partener/factura - acest caz e deja tratat rezonabil.

D. Ce e slab si ce s-ar schimba

Defecte reale si confirmate in cod. Ce s-a masurat efectiv pe date de productie e in BACKTEST_BANCA.md - acolo e si cifra care arata ca schimbarea de la punctul 1 de mai jos nu e cea mai buna: CONT_BANCA e completat la doar 4,5% dintre parteneri activi, deci cheia de IBAN nu are azi pe ce lucra la scara mare.

  1. Pentru BT (singurul format folosit), cautarea partenerului nu foloseste niciodata codul fiscal - oproceduri_import.prg:3015 (mcf = "" fix) + cascada din oproceduri_import.prg:453-523. Se ajunge direct la potrivire exacta de denumire (COMUN\programe\oproceduri_comune.prg:6623-6645, fara LIKE, fara normalizare diacritice/ forma juridica). Extrasul BT contine IBAN-ul platitorului la pozitia 4 din descriere (oproceduri_import.prg:3014) - IBAN e mai stabil ca si cheie decat denumirea si e deja extras, dar in cod e incercat abia al treilea, dupa cod fiscal (mereu gol pentru BT) si dupa denumire. Nomenclatorul insa nu are CONT_BANCA completat decat la 4,5% dintre parteneri (masurat, vezi BACKTEST_BANCA.md), deci o simpla schimbare de ordine nu recupereaza mult pe cont propriu - cheia invatata din istoric, masurata in acelasi document, e cea care aduce castig real.
  2. O plata care acopera mai multe facturi, sau al carei prim numar de factura gasit nu se potriveste cu soldul, ramane azi complet nepereche - oproceduri_import.prg:710-764, EXIT necondiotionat la linia 736 dupa primul document gasit, indiferent de rezultatul testului de suma; TODO-ul autorului chiar deasupra (oproceduri_import.prg:701-706) confirma ca aceasta e o lipsa cunoscuta, nu o alegere deliberata. Aceasta e cea mai probabila cauza pentru "plata acopera mai multe facturi" mentionata in cerinta ta.
  3. Cand acelasi numar de document se potriveste pe mai multe randuri in ireg_parteneri, sistemul renunta silentios - COMUN\programe\oproceduri_comune.prg:7035-7039 (Reccount('cRezultatTemp') > 1 -> doar log, fara alegere).
  4. Extragerea numarului de factura ia orice numar din text, nu doar numere plauzibile de factura - GetRegExpAllNumbers (oproceduri_import.prg:4480-4510) elimina doar datele calendaristice, nu si alte numere (referinte, telefoane). Combinat cu punctul 2 (se incearca doar primul numar gasit), un numar irelevant aflat inaintea numarului real de factura in text ar putea fi incercat primul si esua fara a mai incerca urmatorul.
  5. Pozitiile fixe din descrierea BT (cuvintele 3 si 4 despartite de ;) nu sunt validate - oproceduri_import.prg:3013-3014 - daca banca schimba formatul sau tranzactia are alt tip de descriere decat "Incasare/Plata OP", mtert/miban pot prelua bucati de text gresite, fara nicio verificare (ex: ca IBAN-ul extras chiar arata a IBAN).

E. Formatul fisierului Banca Transilvania (din cod)

Nu exista niciun fisier exemplu de la Banca Transilvania in Alte\ (doar Alte\Extras_de_cont_11634808MT 940.txt si .xls, care nu par BT dupa continut/format). Descrierea de mai jos vine strict din parserul csvBT (oproceduri_import.prg:2923-3025).

Detectie format (oproceduri_import.prg:1502-1507), pe baza primei linii nevide a fisierului CSV:

  • incepe cu "tranzactii" sau "transactions" -> csvBT(fisier, 1) = "lista de tranzactii" (export lunar din BT24/NeoBT), antet comentat in cod: Data tranzactie, Data valuta, Descriere, Referinta tranzactiei, Debit, Credit, Sold contabil (7 coloane).
  • incepe cu [lista de tranzactii] sau "lista de tranzactii" -> csvBT(fisier, 2) = "extras de cont", antet: Data tranzactie, Data valuta, Referinta, Tip tranzactie, Descriere, Debit, Credit (7 coloane, ordinea Referinta/Tip tranzactie difera de varianta 1).

Cont propriu (IBAN al extrasului): cautat linie cu linie dupa eticheta "numar cont:", primele 24 caractere dupa virgula (oproceduri_import.prg:2936-2940); daca nu se gaseste, This.cIBAN = 'BT' (oproceduri_import.prg:2942-2944).

Curatare text inainte de parsare CSV (oproceduri_import.prg:2954-2969):

  • elimina virgulele din interiorul ghilimelelor si ghilimelele insesi;
  • elimina LF + "~~" (artefact de export BT24 cand descrierea trece pe linia urmatoare);
  • reuneste liniile rupte, pastrand un rand nou doar daca urmeaza dupa el o data (mai multe formate acceptate: AAAA-LL-ZZ, ZZ/LL/AAAA, ZZ.LL.AAAA, ZZ-LL-AAAA, AAAA/LL/ZZ);
  • detecteaza varianta BT24 (are coloana "sold contabil" -> llBT24, linia 2966): la BT24 sumele de pe Debit sunt negative si se inverseaza semnul (oproceduri_import.prg:2980-2982).

Camp "Descriere" - text liber, dar cu conventie interna a bancii separata prin ;. Exemplele reale din codul sursa (comentarii, oproceduri_import.prg:2989,2992 - denumiri de firma anonimizate mai jos):

"30-05-2024","30-05-2024","Incasare OP - canal electronic;f 2024103;XXX SRL;ROXXBTRLXXXXXXXXXXXXXXXX;BTRLRO22;REF: 014ZEXA24151017X","014ZEXA24151017X","","1,184.24 ","2,184.24 "
2024-06-05,2024-06-05,"Incasare OP - canal electronic;F 2024131;XXX SA;ROXXBTRLXXXXXXXXXXXXXXXX;BTRLRO22;REF: 414ZEXA2415702DU",414ZEXA2415702DU,,"797.30","12,013.14"

Campurile din "Descriere" (despartite prin ;), asa cum le foloseste codul:

  1. tip tranzactie (text liber, ex. "Incasare OP - canal electronica");
  2. referinta/numar document - din acest camp GetRegExpAllNumbers extrage numerele candidate de factura (oproceduri_import.prg:3017-3019);
  3. denumirea partenerului (mtert, oproceduri_import.prg:3013);
  4. IBAN-ul partenerului (miban, oproceduri_import.prg:3014);
  5. codul SWIFT al bancii partenerului (necitit de cod);
  6. referinta platii (necitita de cod, ramane doar in text).

Clasificare tip tranzactie dupa cuvinte cheie in "Descriere" (oproceduri_import.prg:2998-3012): "comision" (la inceput), "comision-atm", "comision procesare ridicare numerar", "pachet izi" -> cont 627 (cheltuiala bancara); "retragere de numerar" -> transfer numerar (cont 581/5311); "transfer intern" -> transfer intre conturi proprii (cont 581); orice altceva -> tratat ca tert (client/furnizor), cont 4111/401.

Ce nu am lamurit

  • Nu am gasit un fisier CSV real exportat de Banca Transilvania in acest working copy, deci descrierea de la punctul E e dedusa din cod si din exemplele scrise ca si comentarii de dezvoltator - nu am validat-o pe un export proaspat (BT poate sa fi schimbat coloanele intre timp; codul insusi are comentarii separate pentru "BT24" si "NeoBT" ca doua variante usor diferite).
  • Nu exista (cel putin nu l-am gasit) un tabel de audit/log care sa retina propunerea automata a algoritmului separat de starea finala dupa corectia manuala din frm_modific2024 - masuratorile de productie (BACKTEST_BANCA.md) vad doar rezultatul final, nu cat de mult "salveaza" operatorul azi prin corectii manuale linie cu linie. Pentru a masura exact asta ar trebui adaugata o logare la pasul de import (inainte de afisarea formularului).
  • Nu am verificat daca IBAN-ul partenerului extras de csvBT (miban, oproceduri_import.prg:3014) ajunge sa fie salvat ca atare in ACTACTAN sau se pierde la constructia liniei finale (oproceduri_import.prg:898-909) - iban apare in C_IMPORT_TEMP/cActTemp, dar nu l-am gasit explicit in lista de coloane a INSERT INTO actactan verificata la punctul A.8; ar necesita o verificare suplimentara pe schema reala a tabelei ACTACTAN din Oracle (DESC actactan), pe care nu am rulat-o (doar SELECT-uri, nu aveam nici conexiune).