Files
roacont/docs/plan-anaf-tva-efactura-4-lucrari.md
Marius Mutu 7cd7d57708 Transa 1 ANAF TVA: changelog 2.11.67, plan corectat pe date, handoff-uri de verificare
Plan corectat in patru puncte confirmate pe Oracle:
- lista de coloane de taxare inversa are 13 termeni, nu 10 (TI19T/TI09T nu exista pe jc2007;
  expresia din oproceduri_decont.prg:8670 e scrisa peste view-ul VJC2025, cu aliasuri calculate)
- tva_incasare nu exista pe jc2007/jv2007; interogarea transei 3 merge pe VJC2025/VJV2025
- kill-switch-ul nu se face in Optiuni_FIRMA/PROGRAM.dbf, ci pe modelul RC_ANAF_VERIF_SELECTIE
  (INI + optiuni Oracle + intrare de meniu)
- oExecute deschide dialog de reconectare cu QUIT pe conexiune cazuta; apelul din transa 3 trebuie
  sa dezactiveze explicit reconectarea

SVN r17910/r17911.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhJwsrFFe1ZNAAtS8Twuti
2026-07-28 00:50:26 +03:00

39 KiB

Plan: 4 lucrari ROACONT (ANAF perioada TVA, validare CNP, avertizare exigibilizare, borderou eFactura)

Data: 27.07.2026. Autor livrare: marius.mutu. Versiune consolidata dupa runda 2 de review si poarta de decizie. Aceasta e versiunea de executat — incorporeaza toate deciziile. Istoricul review-ului e in Anexa, la final, si nu contine nimic de implementat.

Plan de test insotitor: ~/.gstack/projects/ROACONT/mmari-main-eng-review-test-plan-20260727-194744.md

Livrare in TREI TRANSE separate

Cele 4 lucrari nu au nicio dependenta functionala intre ele, dar au clase de risc si povesti de retragere incompatibile. Impachetate impreuna, o retragere pentru L3 ar lua cu ea si view-ul deja aplicat in schema clientului.

Transa Continut Blast radius Retragere Blocata de
1 L1 + L2 2 fisiere .prg in COMUN un pas nimic — se poate incepe
2 L4 script DB + o expresie in client doi pasi coordonati 2 conditii de intrare
3 L3 drumul de salvare al notelor, toata suita ROA un pas, dar afecteaza tot 1 conditie de intrare

Fiecare transa se inchide in SVN inainte de a incepe urmatoarea.


TRANSA 1 — L1 + L2

Ambele in COMUN\programe\ocautare.prg + COMUN\programe\validare.prg. Lane secvential: L1 apoi L2. Fara DB, fara drum de salvare. Reversibil prin revenire la binarul anterior.

L1 — de cand este platitor / neplatitor de TVA

Problema

Verificarea ANAF de la alegerea partenerului spune doar starea la data documentului ("ANAF: platitor TVA la 27.07.2026"). Pentru un cod fiscal care si-a schimbat atributul, utilizatorul nu vede DE CAND s-a schimbat.

Ce exista deja (verificat, nu se construieste)

  • ParseJsonANAFv8 (validare.prg:2001-2114) pune DEJA in cursor toate campurile necesare: data_inceput_ScpTVA, data_sfarsit_ScpTVA, data_anul_imp_ScpTVA, mesaj_ScpTVA (citite din inregistrare_scop_tva.perioade_tva[1], liniile 2060-2072).
  • Cele trei coloane de data sunt DEJA de tip D in CREATE CURSOR (validare.prg:2012). Nu e nevoie de normalizare, doar de garda pe gol.
  • Informatia se pierde in ANAF_VerdictDinCursor (validare.prg:1657-1689), care ia din cursor doar cui, scpTVA, statusInactivi, denumire, mesaj (:1670-1675).
  • Cache-ul de sesiune goCacheANAF_Sesiune pastreaza obiectul intreg (ocautare.prg:127 .Add(m.loRezANAF, ...), :130 .Item(...)). Proprietatile noi supravietuiesc. Nu se modifica.
  • lDiscordanta exista deja: ocautare.prg:143, (loRezANAF.scpTVA <> loOut.lAreRO) Or loRezANAF.statusInactivi. Banda se ramifica deja pe ea la :651. Nu se scrie detectie noua.

Lucrarea e strict propagare + afisare. Nu se schimba apelul HTTP, nu se face al doilea apel.

Modificari

1. validare.prg, ANAF_VerdictDinCursor (~1657-1689)

Pe obiectul rezultat se adauga patru proprietati, toate citite din cursorul deja parsat:

  • dInceputScpTVA, dSfarsitScpTVA, dAnulareScpTVA — din coloanele de tip D, cu Nvl si garda pe gol.

  • cMesajScpTVA — din coloana mesaj_ScpTVA (C(244)), NU din mesaj.

    Motiv: validare.prg:2012 declara mesaj_ScpTVA C(244) dar mesaj V(100), iar :2109 face loDate.mesaj = loDate.mesaj_ScpTVA urmat de Insert Into la :2112 — VFP taie tacut la 100 din 244 de caractere. Proprietatea mesaj deja expusa contine mesajul ANAF trunchiat la 41%.

Toate patru se citesc aici: cursorul se inchide la :1683 si nu mai exista alta ocazie.

2. ocautare.prg, ANAF_StarePartener, blocul de verdict (136-145)

Propaga cele patru proprietati in loOut.

Citirea se face cu Pemstatus(loRezANAF, 'dInceputScpTVA', 5) aici, la :140-145 — nu in TextAnaf. ocautare.prg e incarcat si de produse ROA care pot avea un validare.prg mai vechi (garda existenta la :63-66); citirea neprotejata ar arunca eroare in acest bloc, inainte sa se ajunga vreodata la TextAnaf.

3. ocautare.prg, TextAnaf (683-695) — cand si unde apare data

Data de perioada apare in banda doar pe trei conditii. In rest banda ramane identica cu azi, iar perioada completa se vede in F4.

Conditie Data in banda
lDiscordanta = .T. (ROA difera de ANAF, sau partener inactiv) DA
dAnulareScpTVA nevid (anulare din oficiu) DA
dSfarsitScpTVA < Date() (perioada s-a incheiat deja) DA
rest (cazul normal, concordant) NU

Regula de pozitionare, obligatorie: data se lipeste imediat dupa starea de TVA, inaintea oricarui segment , Inactiv.

TextStare (ocautare.prg:680) produce Platitor TVA, Inactiv. Forma gresita ANAF: Platitor TVA, Inactiv din 01.03.2019 afirma ca partenerul e inactiv din 2019, cand de fapt aceea e data de inceput a perioadei de TVA. Cum lDiscordanta include statusInactivi, cazul inactiv e chiar unul dintre cele aduse in banda — deci regula nu e optionala.

Forme corecte:

ANAF: Platitor TVA din 01.03.2019, Inactiv (DENUMIRE... RO12345678)  (F4 = detalii)
ANAF: Platitor TVA din 01.03.2019 (DENUMIRE... RO12345678)  (F4 = detalii)
ANAF: Platitor TVA 01.03.2019 - 31.03.2026 (DENUMIRE... RO12345678)  (F4 = detalii)
ANAF: Neplatitor TVA din 12.03.2026 (DENUMIRE... 12345678)  (F4 = detalii)
ANAF: Neplatitor TVA, anulat din 12.03.2026 (DENUMIRE... 12345678)  (F4 = detalii)

Precedenta cand ambele date sunt nevide: pe dAnulareScpTVA — anularea din oficiu e informatia mai grava, iar anulat e cuvantul pe care contabilul il cauta. Cele doua date NU se contopesc: data_sfarsit_ScpTVA (incetarea inregistrarii) si data_anul_imp_ScpTVA (anulare din oficiu) au consecinte diferite asupra dreptului de deducere.

Cand perioada apare in banda, data documentului redundanta (' la ' + Dtoc(toStare.dData)) se scoate din text, ca sa nu rezulte doua date cu doua prepozitii alaturate.

Banda are voie 2-3 randuri: lb_anaf are WordWrap = .T., Height = 46, Width = toForm.Width - 20 (ocautare.prg:472-476). Riscul real nu e latimea, ci ca un sufix de lungime variabila muta punctul de wrap si (F4 = detalii) sare intre randuri la derularea cu sagetile. Regula de mai sus il reduce mult: ~95% din randuri pastreaza banda de azi.

4. ocautare.prg, Detalii (787-873)

Bloc nou in dialog, dupa starea curenta: perioada de inregistrare in scopuri de TVA (inceput / sfarsit / anulare, cele nevide, cu etichete distincte) si, daca cMesajScpTVA e nevid, mesajul textual primit de la ANAF, integral.

Perioada apare in F4 intotdeauna cand ANAF a trimis-o, inclusiv in cazul concordant in care banda nu o arata.

5. validare.prg (2064-2072) — logul pe parsare esuata

Se logheaza cazul in care perioade_tva[1] exista, sirul sursa e nevid, dar data parsata e goala.

Conditia se scrie explicit, NU in CATCH: CTOD nu arunca exceptie pe format necunoscut, ci intoarce data goala. CATCH-ul de la :2070 prinde doar erori de acces la perioade_tva[1], deci un log pus acolo nu s-ar declansa niciodata pe cazul pentru care a fost cerut.

(Parsarea e corecta cat timp ANAF trimite YYYY-MM-DD, sub Set Date To YMD de la :2023, restaurat in Finally la :2131.)

Limita de acceptat

ANAF v9 intoarce O SINGURA perioada — cea care acopera data interogata. Textul afisat descrie perioada relevanta pentru data documentului, nu tot istoricul.


L2 — validare CNP la codurile de 13 cifre

Problema (bug raportat)

ARGHIR MARIA (CUI: 2540324131210, ID: 149) ANAF nu a raspuns - verificarea a fost sarita.

Mesajul e fals. ANAF_StarePartener (ocautare.prg:82-84) intoarce .Null. pentru codurile de 13 cifre FARA a seta tcClasaEsec. CNP-ul din exemplu este VALID.

Localizarea exacta a defectului:

  • BANDA nu acuza ANAF: .Null. cu clasa goala cade pe Case Isnull(m.loStare) (:648-650) -> promptul neutru. Nu e gresit, dar nu spune nimic.
  • DIALOGUL F4 e singurul care minte: Detalii, :799-804, ultima ramura a Iif-ului imbricat (:803) -> 'ANAF nu a raspuns - verificarea a fost sarita.'
  • VerificaAlegere (:875+) intoarce .T. tacut pe oStare nul — acolo nu e nimic de reparat.

Defectul e mai larg decat cazul raportat: EvalueazaRand sterge explicit clasa de esec cand codul e invalid (:600, This.cClasaEsec = ''). Deci pentru ORICE cod fiscal invalid, banda spune corect "CNP invalid" / "CIF invalid", dar F4 afiseaza tot 'ANAF nu a raspuns'. Cazul raportat e una din doua instante ale aceluiasi defect.

Ce exista deja (verificat)

  • Validarea locala RULEAZA DEJA: EvalueazaRand apeleaza ANAF_ValidareCod la :594 si iese devreme pe orice rezultat nevid (595-604), INAINTE de apelul ANAF. Deci "CNP invalid (...)" si "CIF invalid (...)" se afiseaza deja azi. Nu se scrie validare noua.
  • Consecinta: un cod care pica validarea nu ajunge niciodata pe drumul ANAF. Ramurile "CNP invalid" sub clasa noua si "CUI valid/invalid" sub FARA_RASPUNS sunt INACCESIBILE prin constructie si NU se implementeaza.
  • ANAF_ValidareCod (:208-227) decide CNP vs CIF cu prioritate pe tip_persoana, altfel lungime 13.
  • VALIDARE_CNP (validare.prg:1296-1312), VALIDARE_CIF (validare.prg:1225-1294).
  • Clasa noua COD_INVALID urmeaza tiparul existent COD_NENUMERIC de la ocautare.prg:643-646.

Modificari

  1. ocautare.prg, EvalueazaRand (:600): This.cClasaEsec = 'COD_INVALID' in loc de ''.

  2. ocautare.prg, ANAF_StarePartener (82-84): seteaza tcClasaEsec = 'CNP' inainte de Return .Null.

  3. ocautare.prg, ANAF_StarePartener: primeste in plus tnTipPersoana, iar conditia de la :82 devine Len(m.lcCui) = 13 And m.lnTipPersoana <> 1.

    Motiv: :82 decide "persoana fizica" doar pe lungime, ignorand tip_persoana, in timp ce ANAF_ValidareCod (:222) decide invers, cu prioritate pe tip_persoana. Divergenta e mascata azi pentru ca banda cade pe promptul neutru si nu afirma nimic. L2 pune acolo "Persoana fizica - CNP corect", deci pentru un partener extern cu cod numeric de 13 cifre aplicatia ar afirma pe ecran ceva fals, unde azi tace. Cauza e preexistenta, consecinta vizibila ar fi introdusa de L2.

  4. ocautare.prg, EvalueazaRand (636-657): ramura noua inaintea lui Case Isnull(m.loStare), pe clasa 'CNP' — banda arata Persoana fizica - CNP corect, culoare NEUTRA (SeteazaLabel(..., .F., .T.)), nu verde.

    De ce nu verde: verdele si formularea scurta stau in acelasi loc si in aceeasi culoare cu confirmarea reala de la ANAF. O cifra de control nu este o verificare la ANAF.

  5. ocautare.prg, Detalii (799-804): doua ramuri noi in Iif-ul imbricat:

    • 'CNP' -> Persoana fizica - CNP corect.
    • 'COD_INVALID' -> Cod fiscal invalid - nu s-a interogat ANAF. Corectati codul in fisa partenerului.

Informativ, fara blocare si fara dialog de confirmare — cascada RC_ANAF_VERIF_SELECTIE ramane neatinsa.


TRANSA 2 — L4: borderou eFactura, taxare inversa

Nu se incepe inainte ca transa 1 sa fie in SVN.

Problema

In borderoul eFactura, facturile primite cu taxare inversa apar gri (diferenta fata de registrul de cumparari). In XML-ul eFactura taxarea inversa are TVA 0 (ClassifiedTaxCategory/ID = 'AE'), dar in contabilitate se inregistreaza 4426 = 4427, deci jc2007.totctva contine si acest TVA. Diferenta rezultata e exact TVA-ul de taxare inversa si nu e o eroare reala.

Importul face deja corectia inversa la generarea notelor (anaf_efactura.vc2:12096-12106), dar numai IF thisform.lPrimite.

Decizie

Corectia se face din datele contabile REALE, prin view-ul Oracle — nu prin recalcul cu cota standard (care ar da gresit pe facturi mai vechi la 19%). Scope: DOAR facturi primite.

Coloana vizibila in grid NU se face. Se livreaza doar corectia interna a expresiei diferenta.

Conditii de intrare (ambele obligatorii inainte de a scrie scriptul)

  1. Trasare cap-coada a unei facturi reale: linie XML cu ClassifiedTaxCategory/ID = 'AE' -> id_jtva_coloana -> coloana din jc2007. Se confirma pe date ca acolo sta suma.

  2. Definitia curenta a lui anaf_vefactura_trimis se ia din USER_VIEWS.TEXT din schema tinta, nu din arhiva CLAR.

    Motiv: scriptul-model 2025\06\ff_2025_06_16_02_COMUN_EFACTURA.sql redefineste NUMAI anaf_vefactura_primit (:3-53). Corpul lui anaf_vefactura_trimis e in alt fisier, mai vechi (2024\08\ff_2024_08_22_01_COMUN_EFACTURA.sql:63-112); cele doua au divergat cu ~10 luni si exista 10 scripturi in arhiva care il redefinesc. Cine il rescrie "dupa model" luand fisierul gresit revine tacut la o versiune veche a view-ului.

Modificari

1. Script de migrare nou, in D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\07\

create or replace view pentru AMBELE view-uri, cu o coloana noua jtva_ti:

  • anaf_vefactura_primit: jtva_ti = suma coloanelor de TVA de taxare inversa din jc2007, cu EXACT aceeasi potrivire ca cea folosita azi pentru jtotctva (an/luna/dataact/regexp pe numar act si cod fiscal emitent).

  • anaf_vefactura_trimis: jtva_ti = constanta 0. La facturi emise nu se inregistreaza nota contabila de TVA pentru taxare inversa, deci diferenta de acolo ramane identica cu azi.

    Coloana e obligatorie in AMBELE: import_efactura.prg:40 foloseste FROM <<m.lcTabel>>, un singur SELECT pentru ambele view-uri (lcTabel setat la :27). Fara ea, borderoul de facturi TRIMISE crapa cu ORA-00904.

Lista de coloane — CORECTATA 28.07.2026, verificata pe USER_TAB_COLUMNS.

Expresia din Programe\oproceduri_decont.prg:8670 NU se poate copia: e scrisa peste view-ul VJC2025 (FROM VJC2025 J, :8677), nu peste tabela. In acel view ti19t si ti09t sunt ALIASURI calculate (SUM(ti19bct+ti19bvt+ti19bft) as ti19t, SUM(ti09bvt+ti09bft) as ti09t). Pe jc2007 coloanele TI19T si TI09T nu exista. Un create or replace view care le foloseste direct pica cu ORA-00904 — si, cum acelasi script redefineste si anaf_vefactura_trimis, ar rupe borderoul pe AMBELE sensuri.

Lista corecta pe jc2007 are 13 termeni, nu 10 (TI09 nu are varianta BC, asimetric fata de TI19):

NVL(j.TI21T,0)+NVL(j.TI11T,0)+NVL(j.TI24T,0)+NVL(j.TI20T,0)
+ NVL(j.TI19BCT,0)+NVL(j.TI19BVT,0)+NVL(j.TI19BFT,0)
+ NVL(j.TI09BVT,0)+NVL(j.TI09BFT,0)
+ NVL(j.XX21TIT,0)+NVL(j.XX11TIT,0)+NVL(j.XX19TIT,0)+NVL(j.XX9TIT,0)

Familia NU este XX*TI* singura. Importul eFactura trece prin ProcentTva2IdJtva (definita in COMUN\programe\oproceduri_comune.prg:5836, doar apelata din anaf_efactura.vc2:12093), care pentru cumparari cu taxare inversa da id-urile 216/218/141/145; in jtva_coloane, 216 = 'TX. INV. 21%' -> TI21B (pereche 217 = TI21T), 218 = 'TX. INV. 11%' -> TI11B (219 = TI11T). Coloanele XX*TIT sunt achizitii NON-CE, pe care importul eFactura nu le scrie. Toate confirmate ca existente in jc2007: TI21T=217, TI11T=219 (2026\03\ff_2026_03_09_02_COMUN_PACK_CONTAFIN_10G_ROMCONSTRUCT.pck:4991-4993), XX19TIB=192, XX19TIT=193 (2026\01\PACK_CONTAFIN_ROMCONSTRUCT...pck:4891-4892).

Performanta: jtotctva e deja un subselect corelat peste jc2007 cu REGEXP_SUBSTR + REGEXP_REPLACE in WHERE — neindexabil, scanare per rand. Un al doilea subselect identic pentru jtva_ti ar dubla exact acest cost. Se scrie un singur subselect care intoarce ambele sume.

Se pastreaza EXEC pack_migrare.UpdateVersiune(...) + COMMIT. Fisierul se scrie cu CRLF. Se bumpeaza versiune_db.txt. Scriptul se incheie cu o verificare de obiecte invalide: view-urile anaf_vefactura_primit_detaliu si anaf_vefactura_trimis_detaliu (2024\08\ff_2024_08_28_02_COMUN_EFACTURA.sql:84, :122) sunt construite peste cele doua si se recompileaza automat, dar invaliditatea trebuie sa nu treaca neobservata.

2. COMUN\programe\import_efactura.prg — o singura expresie

Linia :36:

NVL(total_cu_tva,0.00) - (NVL(jtotctva,0.00) - NVL(jtva_ti,0.00)) as diferenta

lcSchema (:31-33) NU se modifica. jtva_ti apare doar ca termen intr-o expresie, nu ca coloana de iesire, deci numarul de coloane al cursorului ramane acelasi. lcSchema e o schema pozitionala imperecheata cu lcSelect in gencursor (:50); orice coloana noua de iesire ar cere modificarea ambelor, iar modificarea uneia singure ar strica intreg borderoul.

3. Degradare gratioasa la lipsa coloanei

Inainte de a construi lcSelect, se testeaza o data existenta coloanei (SELECT jtva_ti FROM ... WHERE 1=2 intr-un TRY, sau interogare pe user_tab_columns) si se construieste lcSelect cu sau fara termenul jtva_ti.

Motiv: exe-ul ajunge la client prin update automat, iar migrarea CLAR ruleaza pe alta cadenta. Fara sonda, prima combinatie gresita da ORA-00904 si borderoul nu se deschide deloc, nici primite nici trimise. Cu sonda, ordinea de livrare devine optimizare, nu conditie de functionare.

Ordinea recomandata ramane: scriptul se aplica INAINTE de build — de scris in nota de release.

4. Ce NU se atinge

Colorarea ramane pe diferenta <> 0 (anaf_efactura.vc2:6020-6041 Page2 si 12790-12817 frm_import_efactura) — se corecteaza doar sursa numarului. NU se inventeaza o a treia culoare: randurile cu taxare inversa nu sunt de investigat, sunt corecte prin constructie, iar falsurile pozitive omoara marcajul de exceptie. NU se ating Page1 / Page3 si filtrul chkDiferente (anaf_efactura.vc2:5347-5349) — sunt facturi emise. Nu se editeaza niciun binar .vcx.

Limita cunoscuta, de consemnat

O taxare inversa inregistrata la cota GRESITA face ca diferenta sa se anuleze reciproc si randul sa devina alb. Fara coloana in grid nu mai exista nicio parghie vizuala pentru acest caz. E pretul deciziei de a nu adauga coloana si trebuie sa fie scris, nu descoperit.


TRANSA 3 — L3: avertizare de exigibilizare TVA la salvarea notelor

Ultima, singura. Nu se incepe inainte ca transele 1 si 2 sa fie in SVN.

Scopul lucrarii — ca sa nu se citeasca gresit

Singura modificare de cod e in COMUN\clase\omodificari.vc2, in inainte_de_do_termin al celor doua clase de formular de note: frm_modific2007 (:5001) si frm_modific2024 (:13131).

NU se modifica oproceduri_inchidere.prg. NU se modifica do_adauga_tva_exigibil. NU se genereaza nimic automat in afara butonului din dialog.

oproceduri_inchidere.prg apare mai jos doar ca drum de test: exigibilizarea din meniu deschide chiar frm_modific2007 (:902, Createobject([frm_modific2007], ...), cu titlul 'Exigibilizare TVA Incasare'), deci avertizarea se declanseaza si acolo fara ca cineva sa fi atins acel fisier.

Conditie de intrare

Cotele 21% si 11% in pack_contab.defalca_tva_incasare si pack_contab.cauta_facturaTVAEx. Partial inchisa: exista suport 21%/11% in 2025\08\ff_2025_08_08_02_COMUN__PACK_CONTAB.sql (comentariu "creeaza_note_tva_incasare TVA 21%, 11%"). De confirmat pe date.

Ce exista deja (verificat)

  • Command5 -> Thisform.do_adauga_tva_exigibil() (omodificari.vc2:13776-13778). Captionul DIFERA intre clase: :2643 = "Adauga nota TVA devenit exigibil" (frm_modific2007), :6923 = "Adauga TVA exigibilizat" (frm_modific2024).
  • do_adauga_tva_exigibil (:12483+) lucreaza pe conditia ales and (scc = '4428' or scd = '4428' or id_factd > 0 or id_factc > 0) (:12509). Nu contine nicio lista de cote — lucreaza pe liniile notei si pe proc_tva, si deleaga defalcarea Oracle-ului. Deci e agnostic la cota si functioneaza la 21%.
  • cmdSelect.Click = "Selecteaza tot" (:13640-13658) — face doar Replace All ales With .T.
  • Precedent identic ca forma in acelasi hook: avertizarea 4426-4428 (:13165-13176), cu amessagebox(..., 4+32, ...) si Return .F.
  • tact poarta tipnota, id_fact, id_factd, id_factc, ales, id_act. Cursorul e construit de fiecare apelant, nu de omodificari.vc2. Codul insusi nu are incredere ca tipnota exista: :4752 si :12847 folosesc If TYPE('tact.tipnota') = 'N' AND ...
  • Return .F. din inainte_de_do_termin opreste salvarea: apelantul din _frm_base.vc2 (PROCEDURE do_termin) nu face Release/Hide si nu seteaza buton/gnButon, iar toti apelantii testeaza If buton = 1 inainte de a scrie. Nicio subclasa a celor doua clase nu exista in arbore, deci niciun override nu poate sari peste hook.

Modificari — algoritm

Pasul 0 — pozitionare in hook. Verificarea se aseaza sub acelasi IF m.llRet ca avertizarea existenta (:13165) si dupa SELECT tact / SET FILTER TO de la inceputul procedurii (:5001-5005, :13131-13135). Altfel apare peste dialogul de eroare de la verificare_note_contabile, sau filtrul gridului ii ascunde randuri.

Ordinea dialogurilor la o singura apasare de Salvare: verificare_note_contabile -> avertizarea 4426-4428 -> avertizarea noua. Prima semnaleaza o greseala facuta, a doua o omisiune.

Pasul 0.5 — kill-switch. Optiune implicit pornita. Daca e oprita: nicio avertizare, nicio interogare, in tot produsul. L1/L2 stau deja in spatele RC_ANAF_VERIF_SELECTIE; L3 intra pe drumul de salvare al intregii suite si nu are voie sa fie fara oprire.

Mecanismul — CORECTAT 28.07.2026. NU se folosesc Optiuni_FIRMA/Optiuni_PROGRAM.dbf din DATE\: acele tabele exista pe disc, dar nu sunt calea folosita si nu apar deloc in cod. Modelul de urmat este chiar RC_ANAF_VERIF_SELECTIE, care are trei straturi (ocautare.prg:255-285, :341-381):

  1. kill-switch INI: getini(gcGeneralIniFile, 'anaf', 'verificare_selectie'), doar '0' opreste;
  2. optiuni Oracle (optiuni / optiuni_util), citite cu citeste_optiune / citeste_optiune_utilizator din oinit_optiuni.prg, cu cache intr-o Collection;
  3. UI = intrare de meniu (Meniuri\CONT2000.MPR:210, ON SELECTION BAR ... DO ANAF_ComutaVerificare), nu ecran cu campuri DBF.

Pasul 0.6 — flag de lot. Apelantii care instantiaza formularul in bucla seteaza un flag public "rulez in lot", iar verificarea se sare. anaf_efactura.vc2:12733 instantiaza frm_modific2024 per factura importata: un import de 150 de facturi ar insemna 150 de AMESSAGEBOX succesive plus 150 de interogari Oracle sincrone. Acelasi lucru la :12940 si comun.vc2:2436.

Pasul 1 — colectare. Din tact, id-urile id_factd / id_factc > 0. Daca nu exista niciunul: nimic, cost zero, fara interogare Oracle.

Pasul 2 — suprimarea platilor deja acoperite.

Regula: exista in tact o linie cu tipnota = 1 SI id_fact = <id>.

Citirea lui tipnota se face prin TYPE('tact.tipnota') = 'N', ca la :4752 si :12847tact e construit de apelant, posibil din alt produs ROA.

Santinela -5 NU intra in regula. do_adauga_tva_exigibil scrie -5 numai cand id-ul facturii nu se poate rezolva (:12531, :12542, :12551, :12559), iar comentariul din cod spune ce inseamna: "pun -5 ... ca sa-i completez id_fact la scrie_in_act" — adica "de completat la scriere", nu "rezolvat". Pentru liniile colectate la pasul 1 (id_factd/id_factc > 0) codul scrie intotdeauna id-ul real, deci -5 e imposibil prin constructie pe multimea urmarita. In schimb, o disjunctie SAU id_fact = -5 nu ar fi conditionata de id: o singura linie cu -5 oriunde in tact ar stinge avertizarea pentru TOATE platile din lot, tacut.

Calificarea pe tipnota = 1 se pastreaza: fara ea, excluderea ar prinde si notele MANUALE 4426=4428 — exact cele despre care avertizarea existenta de la :13165 spune ca sunt probabil gresite.

Nota de reconciliere: o decizie intermediara a review-ului (D23) ceruse mutarea cheii de pe tipnota pe perechea de conturi, pe motiv ca notele generate de inchidere_tva_sold au tipnota = 0 (oproceduri_inchidere.prg:884). Motivul a cazut: acel drum nu ajunge niciodata la regula, pentru ca INSERT INTO actactan (:848-854) nu completeaza id_factd/id_factc, deci pasul 1 nu colecteaza nimic. Cheia ramane pe tipnota, fara -5.

Pasul 3 — interogarea, fara lista IN.

O singura interogare goExecutor, care intoarce id_fact-urile cu tva_incasare = 1 si sold neexigibil <> 0. Intersectia cu tact se face local, in VFP.

Sursa — CORECTATA 28.07.2026. Interogarea NU se poate scrie peste jc2007 + jv2007: coloana tva_incasare nu exista pe cele doua tabele (verificat pe USER_TAB_COLUMNS). Ea sta pe DOCUMENTE (id_doc = id_fact) si pe view-urile VJC2025 / VJV2025. Se interogheaza view-urile: au deja tot ce trebuie (cotele, tva_incasare, an/luna, id_fact) si sunt exact ce foloseste deja pack_contab.cauta_facturaTVAEx. Cele 14 coloane de cota de mai jos exista, in schimb, si pe jc2007/jv2007 — verificat.

Nu se construieste lista IN (...). Pragul Oracle de 1000 (ORA-01795) se atinge la utilizare normala, nu la 10x: acelasi formular deserveste, dupa propriul caption, "Import extrase bancare, deconturi curieri, procesatori plati" (anaf_efactura.vc2:12941), iar imperecherea se face linie cu linie (oproceduri_import.prg:729, 755, 782, 808). Un extras obisnuit are 300-2000 linii/luna; un decont de procesator, mult mai mult. Un singur SQL de lungime fixa elimina o clasa intreaga de esec de pe drumul de salvare al intregii suite.

Formula de sold neexigibil — NU se copiaza din registru. Filtrul existent la Clase\ovanzcump.vc2:38011 foloseste ro24nb+ro24nt+ro20nb+ro20nt+ro19nb+ro19nt+ro9nb+ro9nt+ro5nb+ro5nt, care OMITE cotele 21% si 11%, in vigoare din august 2025 (RO21NB/RO21NT/RO11NB/RO11NT, id-uri 214/215, 2025\07\ff_2025_07_21_01_COMUN_TVA21.sql:134-140). Cu formula copiata, avertizarea nu s-ar declansa NICIODATA pe o factura din 2025 incoace — exact pe facturile pentru care a fost ceruta.

Lista corecta:

ro24nb+ro24nt+ro20nb+ro20nt+ro21nb+ro21nt+ro11nb+ro11nt+ro19nb+ro19nt+ro9nb+ro9nt+ro5nb+ro5nt

Robustete. Verificarea ruleaza doar dupa ce verificare_note_contabile a trecut si doar cu conexiune valida. Orice esec (tabela inexistenta in alt produs ROA, conexiune cazuta, timeout) -> se sare, cu linie in goLog. Niciodata tacut: o verificare care moare invizibil e mai rea decat una absenta, pentru ca nimeni nu afla ca nu mai merge. Esecul nu blocheaza salvarea.

Cum se obtine asta — PRECIZAT 28.07.2026, dupa citirea clasei. oexecutor e in COMUN\programe\oproceduri_comune.prg:69-547. Comportamentul real:

  • oExecute() NU arunca eroare VFP pe SQL esuat: intoarce CT_INSUCCES (-1). Deci nu urca la ON ERROR global. goLog.Log se apeleaza pe toate drumurile de esec. Pana aici, cerinta e indeplinita din oficiu.
  • DAR pe eroare ODBC de conexiune cazuta (cod 1526 cu subcod 12152/3113/3114/12560/4068/28/12 — exact cazul numit mai sus) se deschide un amessagebox "Doriti reconectare?" (oproceduri_comune.prg:390) NECONDITIONAT de tlShowError, iar raspunsul "Nu" duce la QUITinchide aplicatia, in mijlocul salvarii. Exact opusul cerintei.
  • Deci apelul TREBUIE facut cu reconectarea dezactivata explicit. Nu exista precedent: din 150+ apeluri oExecute/oExecuta din codebase, niciunul nu trece de 2 parametri. Tiparul se scrie aici prima oara — de verificat pozitia exacta a parametrului in semnatura inainte de a-l folosi.
  • Se foloseste oExecute, NU oExecuta (aceasta din urma afiseaza mesaj propriu, neconditionat, pe orice esec). Nu se adauga niciun amessagebox propriu.
  • Nu exista niciun timeout Oracle configurat (SQLSETPROP QueryTimeout/ConnectTimeout) nicaieri: un query agatat blocheaza la nesfarsit, nu doar da eroare. De avut in vedere la testare.

Pasul 4 — dialogul.

Trei butoane:

Exista <N> plati/incasari pe facturi cu TVA la incasare pentru care nu s-a adaugat
nota de TVA exigibilizat.

[Adauga acum]  [Continua]  [Anuleaza]
  • Adauga acum — ruleaza do_adauga_tva_exigibil pe liniile deja identificate de interogare, apoi continua salvarea.
  • Continua — salveaza fara exigibilizare.
  • AnuleazaReturn .F., ramane in formular fara sa salveze.

De ce buton si nu instructiune: sistemul stie deja care plati au nevoie de exigibilizare — asta e chiar interogarea de la pasul 3. O modala care spune "du-te si apasa un buton" ar fi fost inexecutabila in doua feluri: captionul difera intre cele doua clase (:2643 vs :6923), iar Command5.Enabled se calculeaza dupa randul curent (:5661-5667, :13850) si cmdSelect.Click nu il recalculeaza — deci butonul poate fi gri exact dupa "Selecteaza tot".

<N> — de fixat inainte de implementare daca numara facturi distincte sau linii din tact.

Pasul 5 — flag anti-bucla.

Proprietate de formular lAvertizatExigibilizare, setata dupa prima avertizare; a doua oara in aceeasi sesiune de formular nu se mai avertizeaza.

Motiv: cand chkTVAEX e bifat si linia e plata/incasare, do_adauga_tva_exigibil cheama creeaza_note_tva_incasare (:12598-12600), unde id_fact vine din cursorul intors de pack_contab.defalca_tva_incasare (:12412, a.id_fact As id_fact). Daca procedura nu intoarce randuri, nu se insereaza nimic potrivit -> suprimarea nu prinde niciodata -> avertizare -> buton -> avertizare -> buton (dubland notele generate) -> avertizare, fara iesire in afara de "Continua". Ca "zero randuri" e un caz real: oproceduri_inchidere.prg:843-848 il trateaza explicit.

Acoperire — drumuri de instantiere

frm_modific2007 / frm_modific2024 sunt instantiate din: ocont2003.prg:416, 1230, 1679, 2141; oproceduri_incasari.prg:299; frm_import_extrase_banca.sc2:1652; anaf_efactura.vc2:12733, 12940; frm_initializare_facturi_balanta.sc2:1803; comun.vc2:2436; oproceduri_inchidere.prg:719, 902, 1007, 1438; frm_import_note_a4200.sc2:1651; frm_import_note_facturi_clienti.sc2:895; ooperatii_comune.prg:1137.

Premisa "prinde si casa, si banca, si notele manuale" TINE.

Nota de metoda: COMUN/ e in .gitignore, deci Grep/ripgrep il sare implicit. Cautarile de tip "cine apeleaza" se fac cu rg --no-ignore sau cu cale explicita, altfel intorc zero fals.


NU intra in scop (amanat, cu motiv)

Defectele din drumul de exigibilizare din meniu (inchidere_tva_sold). De adaugat in TODOS.md cu descrierea de mai jos, care inlocuieste formularea anterioara — aceea descria un defect inexistent si ar fi trimis pe cine preia lucrarea catre o harta gresita.

Ce este de fapt:

  1. oproceduri_inchidere.prg:833 testeaza Iif(m.lnCota = 21, m.lnSuma21, m.lnSuma11) intr-o bucla For lnCota = 1 To 7 (:829). lnCota nu ajunge niciodata 21, deci lnSuma21 e cod mort, iar la lnCota = 6 se ia soldul de 11% cu cota 1.21 (:834). Linia vecina :837 o face corect (Iif(m.lnCota = 6, m.lnSuma21T, ...)). Este o greseala de tipar 6 -> 21.
  2. :802-803 citesc soldn21 / soldn11 neconditionat din crsTVAIncasareTemp. Aliasurile sunt emise doar de formele 2025 (ovanzcump.vc2:44220-44225, :55069-55073). Pe formele 2010 (:39660, :51403) nu exista -> eroare VFP 12, modala, la apasarea butonului. Exigibilizarea din registru e rupta pe perioadele anterioare lui 2025.
  3. lnSuma21, lnSuma11, lnSuma20, lnSuma19 lipsesc din lista Local (:770-773) — devin PRIVATE si se scurg in stiva de apel.

Ce NU este: ovanzcump.vc2:39660 nu "pierde 24/20"; e forma 2010, folosita pe perioade < 2025, al carei cursor sursa vjc2010/vjc2013 nu are deloc coloanele ro21*/ro11* (oproceduri_decont.prg:18795-18801), deci acolo omisiunea e corecta.

Nu afecteaza L3: butonul catre care trimite avertizarea L3 este do_adauga_tva_exigibil, care nu foloseste nicio lista de cote. Sunt doua butoane diferite, pe doua drumuri diferite.

De scris in nota de release: pe o factura cu TVA la incasare 21%, L3 avertizeaza corect, dar utilizatorul care merge pe drumul din meniu (registru -> exigibilizare) gaseste lista goala. Butonul din formular functioneaza.

Alte lucruri amanate:

  • Istoricul complet al atributului de TVA (mai multe perioade). ANAF v9 intoarce o singura perioada.
  • Al doilea apel ANAF la data curenta ("era platitor la data documentului, dar nu mai este azi").
  • Persistarea totalului cu TVA de taxare inversa pe randul din anaf_efactura la import: nu ar acoperi facturile deja importate; view-ul repara si istoricul.
  • Derivarea listelor de cote din jtva_coloane in loc de enumerare in mai multe locuri. E cauza radacina a defectelor gasite la L3 si L4.

Ce exista deja (si se reutilizeaza)

Sub-problema Cod existent Se reutilizeaza?
Perioada TVA de la ANAF ParseJsonANAFv8, validare.prg:2001-2114 Da, integral. Se propaga, nu se parseaza din nou
Detectia discordantei ROA vs ANAF lDiscordanta, ocautare.prg:143 Da. Nu se scrie detectie noua
Cache de sesiune goCacheANAF_Sesiune, ocautare.prg:127, :130 Da, nemodificat
Validare CNP / CIF VALIDARE_CNP / VALIDARE_CIF, apelate prin ANAF_ValidareCod la :594 Da, integral
Generarea notelor de exigibilizare do_adauga_tva_exigibil, omodificari.vc2:12483+ Da (rulat din butonul dialogului)
Tiparul de avertizare in inainte_de_do_termin omodificari.vc2:13165-13176 Da, se copiaza forma
Lista canonica de coloane TVA taxare inversa oproceduri_decont.prg:8670 Da, se preia ca atare

Reguli de livrare

  • Fiecare transa livreaza docs\diff_runda<N>_<subiect>.patch; write-back in binar (txt2vcx.ps1) si commit DOAR dupa aprobare. Patch-urile nu se comit.
  • Comentarii: o singura intrare cumulativa in antetul fisierului, *!* DD.MM.YYYY / *!* marius.mutu / 1-2 fraze. Fara comentarii in corpul codului. La L2 (corectie de eroare) nu se adauga comentariu.
  • ocautare.prg, validare.prg si omodificari.vc2 contin octeti cp1252 (0xBA). Orice scriere cu Edit/Write ii corupe in EF BF BD. Se editeaza TOT ce e de editat, si abia DUPA ultima scriere se verifica byte-level si se repara o singura data, luand octetul corect din svn cat.
  • Scripturile .sql din SCRIPTURI_CLAR se scriu cu CRLF.
  • changelog_roacont.txt: cate o intrare scurta per transa. L1/L3/L4 = :nou: sau :modificare:; L2 = :eroare: (mesajul fals a ajuns la utilizatori).

Ce ramane neverificat

Stare la 28.07.2026, dupa verificarea pe date (schema dev MARIUSM_AUTO). Detalii in docs\handoff_transa2_conditii.md, handoff_transa3_conditii.md, handoff_transa3_deschise.md.

  • Comportamentul goExecutor la timeout / conexiune cazuta — INCHIS, vezi "Robustete" mai sus.
  • Daca pack_contab.defalca_tva_incasare poate intoarce id_fact null/0 — INCHIS. id_fact in cursor e mereu parametrul trimis, deci nu poate fi null. Zero randuri ESTE posibil (filtrul final poate exclude tot), si e chiar garantat pe drumul omodificari.vc2:12547-12561, unde santinela -5 ajunge ca parametru la defalca_tva_incasare(-5, ...). Flagul anti-bucla de la Pasul 5 e confirmat necesar, nu preventiv.
  • Cotele 21/11 in pack_contab — INCHIS. defalca_tva_incasare deleaga la creeaza_note_tva_incasare, care are DECODE explicit pe id_jtva_coloana 211/215 (JC) si 38/42 (JV) pentru RO21*/RO11*; cauta_facturaTVAEx filtreaza si el pe RO21NT/RO11NT. Conditia de intrare a transei 3 e indeplinita.
  • Numarul real de linii pe un decont de curier/procesator — RAMANE DESCHIS pe cifra. Schema dev nu are date reprezentative (DECONT: 2 randuri in toata baza; EXTRAS CON: p95 ~10 linii/lot, cu un ordin de marime sub estimarea din plan, dar pe loturi de test, nu activitate reala). Nu schimba decizia: o interogare unica de lungime fixa elimina ORA-01795 indiferent de volum, deci nu exista prag sub care lista IN(...) ar fi de preferat.
  • Trasarea cap-coada a unei facturi cu linie AE (transa 2) — RAMANE DESCHISA pe date reale. In schema dev NU exista nicio factura venita din import eFactura cu taxare inversa (anaf_efactura: 487 randuri primite, 8 cu id_fact, zero suprapunere cu randuri TI* nenule). Mecanismul a fost demonstrat numeric doar pe un caz construit din jc2007 (id_fact=8007136, ti19bft=19, totctva=119: diferenta falsa de -19 se anuleaza exact cu jtva_ti). De consemnat in nota de release: verificarea finala ramane de facut pe prima factura reala de taxare inversa importata dupa aplicarea scriptului.


ANEXA — istoricul review-ului

Nimic de implementat aici. Se pastreaza pentru trasabilitate.

Metoda

Doua runde: /autoplan (CEO -> Design -> Eng) pe 27.07, apoi runda 2 cu trei voci independente (subagenti fara context de review) confruntate cu verificare proprie pe cod. Codex indisponibil pe masina, deci consensul e "voce independenta + verificare proprie", nu Claude/Codex. Faza DX sarita: produsul nu e pentru dezvoltatori.

Afirmatii verificate pe cod

CONFIRMATE: ParseJsonANAFv8 pune datele in cursor (validare.prg:2012); ANAF_VerdictDinCursor expune doar 5 campuri (:1670-1675); cache-ul pastreaza obiectul intreg (ocautare.prg:127, :130); tact poarta campurile necesare (omodificari.vc2:4496, :5661, :13166); precedentul de avertizare (:13165-13172); un singur SELECT pentru ambele view-uri (import_efactura.prg:40); coloanele TI21T=217, TI11T=219, XX19TIB=192, XX19TIT=193; lb_anaf multi-rand (ocautare.prg:472-476); localizarea defectului L2 in Detalii (:648-650, :803); Return .F. opreste salvarea (_frm_base.vc2, do_termin).

INFIRMATE: tipul coloanelor de perioada era declarat incert — sunt deja D; mesaj NU e echivalent cu mesaj_ScpTVA (V(100) vs C(244)); santinela -5 inseamna "de completat la scriere", nu "rezolvat"; capcana inchidere_tva_sold e evitata prin id_factd/id_factc necompletate, nu prin tipnota; defectul de cote amanat era descris gresit.

Decizii

D1-D21 (runda 1, /autoplan): mod SELECTIVE EXPANSION; lista de coloane TI*+XX*TI*; coloana in ambele view-uri; conditie de intrare prin trasare cap-coada; lista de cote cu 21/11; defect preexistent semnalat nu reparat; texte distincte pentru sfarsit vs anulare; log pe parsare esuata; eliminarea ramurilor inaccesibile din L2; corectarea localizarii defectului L2; data lipita de stare; banda multi-rand; interval pentru perioada inchisa; Pemstatus; defectul L2 mai larg (:600); banda neutra pentru CNP; regula de suprimare pe tipnota; jtva_ti coloana vizibila; fara a treia culoare; ordinea DB-inainte-de-exe.

D22-D33 (runda 2): N1 mesaj_ScpTVA in loc de mesaj (rastoarna D7); N2/N3/N4/N5/N6/N7; 4+32+256; conditie de intrare pe pack_contab; rescrierea textului amanarii.

D34-D37 (poarta, marius.mutu): trei livrari separate, fixurile N7 raman amanate cu text rescris; L3 cu buton de actiune in dialog; L4 fara coloana vizibila; L1 cu data si la lDiscordanta.

Contradictie rezolvata la consolidare: D23 (cheia de suprimare pe perechea de conturi) cade in favoarea E.2 (tipnota = 1 AND id_fact, fara -5) — motivul lui D23 a disparut odata cu E.3.

Deciziile D36 si D37 au dizolvat trei constatari: N5, K-1 si N6 nu mai au obiect fara coloana vizibila; N3 si N4 nu mai au obiect cu butonul in dialog.

Teme transversale

Tema 1 — "corect pe hartie, inexecutabil de utilizator". Trei constatari independente (caption diferit, buton conditionat de randul curent, coloana invizibila din cauza preferintelor de grid) au avut aceeasi forma. Toate trei au fost eliminate prin deciziile de la poarta.

Tema 2 — planul verifica listele de cote in VFP si nu deschide niciun pachet Oracle. De aici conditia de intrare pe pack_contab pentru transa 3.