Files
roafacturare/docs/cercetare/discount_document_cota_tva_b.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git,
nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de
cercetare pe care se sprijina modificarile din cod. O stergere acolo era
definitiva.

Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au
fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele
fisierelor de test la care se refereau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:17:17 +03:00

11 KiB

Supliment: gruparea pseudo-liniei de discount in C_TVA_FACTURA

Completare la discount_document_cota_tva.md, scrisa de al doilea agent pe aceeasi intrebare. Nu repeta raportul principal — acopera exact punctul pe care acela il lasa deschis la finalul sectiunii 3 („ipoteza gruparii separate — nu am atins-o deloc, o verifica alt agent"), plus doua verificari prin rulare pe care raportul principal nu le are.

Cercetare read-only: niciun fisier de cod atins, zero write-back, pe Oracle numai SELECT, niciun apel catre ANAF (validarea s-a facut offline, cu DUKIntegrator din D:\ROA\COMUNROA\dist_efactura).

Verdict in 5 randuri

Ipoteza pe care o ridicasem — ca pseudo-linia de discount de document, avand id_jtva_coloana = 0, iese din LEFT JOIN cu coloana_jv NULL si formeaza propriul grup in C_TVA_FACTURA, cu TaxableAmount negativ — este infirmata pentru factura obisnuita si confirmata pentru factura scutita / cu taxare inversa / intracomunitara. In al doilea caz XML-ul chiar iese cu doua TaxSubtotal-uri pe cota 0, unul cu baza negativa si categoria Z. Dar validatorul nu-l respinge — l-am rulat. Deci: comportament gresit ca modelare, nu defect care blocheaza factura.


1. Ce am masurat, nu dedus: IIF() peste NULL in VFP

Toata ipoteza atarna de o singura intrebare: ce intoarce Iif(Left(.Null.,2) = 'CE', 1, 0). Daca ar intoarce .NULL., cheia de grupare a pseudo-liniei ar diferi de a liniilor reale si gruparea s-ar rupe pe orice factura.

Am rulat o sonda headless (vfp9.exe -A -T) care reproduce exact LEFT JOIN-ul si GROUP BY-ul din xmlefactura.prg:226-248, cu o pseudo-linie de discount pe id_jtva_coloana = 0:

1) IIF(.NULL.="CE",1,0)          -> tip=N isnull=.F.
2) IIF(LEFT(.NULL.,2)="CE",1,0)  -> tip=N isnull=.F.
3) IIF(INLIST(.NULL.,...),1,0)   -> tip=N isnull=.F.
   linie=ARTICOL A                 | coloana_jv nul=.F. | intracom=0 | scutit=0
   linie=ARTICOL B                 | coloana_jv nul=.F. | intracom=0 | scutit=0
   linie=Discount 10.00 % Factura  | coloana_jv nul=.T. | intracom=0 | scutit=0
4) grupuri in C_TVA_FACTURA = 1
   grup: cota=21 baza=1350 tva=283.50      (1500 - 150, corect)

IIF() intoarce ramura falsa, nu .NULL., chiar daca conditia e nula. Deci pe o factura interna obisnuita pseudo-linia primeste intracomunitar=0, taxare_inversa=0, scutit=0 — exact ca liniile reale — si se contopeste in grupul cotei maxime. Sectiunile 3 si 4 din raportul principal raman valabile.

Sonda: ...\scratchpad\probe_null.prg. Nu depinde de baza de date si se poate reface oricand.

2. Unde ipoteza se confirma totusi: facturile scutite / taxare inversa / intracomunitare

Egalitatea de mai sus tine doar cat timp si liniile reale au scutit=0 si expltva gol. Pe o factura scutita nu e asa:

camp din cheia Group By (xmlefactura.prg:246) liniile reale (scutite) pseudo-linia de discount de document
id_jtva_coloana id real (ex. 11) 0 — prelucreaza_facturacrs nu-l seteaza niciodata pe randul ZZZZ (ofacturare_comun.prg:1891-1896)
coloana_jv (din LEFT JOIN) 'WRSCDD' .NULL. — cJTVAVanzariTemp e incarcat cu where afisat > 0 and id_jtva_coloana > 0 (updateserver.prg:578), deci nu exista rand cu id 0
scutit 1 0
expltva 'Scutit cu drept de deducere' gol

Ultimul rand merita explicat, pentru ca e contraintuitiv: completeaza_explicatie_tva (ofacturare_comun.prg:2077-2137) chiar include pseudo-linia in lista de id-uri cautate — scanul de la :2094 e For proc_tva = 1 And Nvl(id_jtva_coloana,-1) <> -1, iar pseudo-linia are proc_tva = 1 (cota 0) si id_jtva_coloana = 0, deci trece filtrul. Dar interogarea de la :2108 e where id_jtva_coloana in (...) pe jtva_coloane, unde id-ul 0 nu exista, asa ca Replace expltva ... For NVL(id_jtva_coloana,0) = m.lnIdJtva (:2129) nu o atinge niciodata. Ramane goala.

Doua campuri diferite din cinci => doua grupuri. XML-ul iese cu:

<cac:AllowanceCharge>
  ... <cbc:AllowanceChargeReason>Discount</cbc:AllowanceChargeReason>
  <cbc:Amount currencyID="RON">539.82</cbc:Amount>
  <cac:TaxCategory><cbc:ID>Z</cbc:ID><cbc:Percent>0</cbc:Percent>...
</cac:AllowanceCharge>
<cac:TaxTotal><cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
  <cac:TaxSubtotal><cbc:TaxableAmount currencyID="RON">-539.82</cbc:TaxableAmount>   <!-- grupul orfan -->
    <cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
    <cac:TaxCategory><cbc:ID>Z</cbc:ID><cbc:Percent>0</cbc:Percent>...
  <cac:TaxSubtotal><cbc:TaxableAmount currencyID="RON">4410.00</cbc:TaxableAmount>   <!-- liniile reale, NEnete -->
    <cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
    <cac:TaxCategory><cbc:ID>E</cbc:ID><cbc:Percent>0</cbc:Percent>
      <cbc:TaxExemptionReason>Scutit cu drept de deducere</cbc:TaxExemptionReason>...
</cac:TaxTotal>

Categoria Z a grupului orfan vine din GetTipTaxa ramura cu ramura (xmlefactura.prg:1116-1140): intracomunitar=0 sare peste 'K', taxare_inversa=0 sare peste 'AE', scutit=0 sare peste 'E', si cade pe Case m.lcTipFactura = 'F' And m.lnProcTva = 0 -> 'Z' (:1128-1129).

In plus, cautarea categoriei pentru AllowanceCharge (xmlefactura.prg:782-786) devine ambigua: Select tip From C_TVA_FACTURA Where proc_tva = 0 Into Array agettipcota gaseste acum doua randuri (E si Z), iar codul ia neconditionat agettipcota(1) — primul in ordinea cursorului, nu cel corect prin vreun criteriu.

Nu am reprodus scenariul end-to-end prin program (ar fi cerut o factura scutita cu discount de document, si in baza de dev nu exista — vezi punctul 4). Il stabilesc din citirea codului plus semantica IIF/NULL masurata la punctul 1.

3. Validare offline: XML-ul gresit nu e respins

Am construit XML-urile si le-am trecut prin DUKIntegrator (java -jar DUKIntegrator.jar -c <config> -v FACT1 <in.xml> <out.txt>, exact apelul din xmlefactura.prg:1192), pornind de la o factura reala acceptata de ANAF ca schelet:

scenariu fisier rezultat
scutit + discount de document, cu grupul orfan (ce produce codul azi) scutit_bug.xml ok
scutit + discount, un singur TaxSubtotal net (varianta corecta) scutit_corect.xml ok
cote mixte 21%+11%, tot discountul pe 21% (ce produce codul azi) mixt_azi.xml ok
cote mixte 21%+11%, discount repartizat proportional mixt_proportional.xml ok
discount mai mare decat baza cotei maxime -> TaxableAmount = -400.00 si TaxAmount = -84.00 pe grupul 21% mixt_baza_negativa.xml ok
control negativ: TaxExclusiveAmount stricat intentionat control_stricat.xml eroare BR-CO-13 + BR-CO-15

Controlul negativ conteaza: fara el, cele cinci „ok" n-ar dovedi nimic. Validatorul chiar valideaza.

De ce trec: regulile EN16931 pe categorii (BR-S-08, BR-E-08, BR-Z-08) cer ca baza declarata pe o categorie sa fie egala cu suma liniilor din acea categorie minus alocarile din aceeasi categorie. Grupul orfan respecta asta trivial (0 - 539.82 = -539.82), iar grupul E la fel (4410.00 - 0). Aritmetica inchide; modelarea fiscala e cea gresita, si asta niciun schematron nu prinde.

Avertisment onest: validatorul local e din 2022 (ro16931-ubl-1.0.8, jar-ul din ianuarie 2022). Validatorul online curent al ANAF poate fi mai strict. Nu l-am apelat — interdictie explicita.

4. De ce nu am putut proba pe date reale

  • In baza accesibila (MARIUSM_AUTO@ROA_CENTRAL) exista 3 facturi cu discount de document, toate cu o singura cota si toate anterioare eFacturii (2008, 2014 x2), niciuna cu efactura = 1. Separat exista 34 de facturi cu cote mixte, dar niciuna cu discount de document. Deci intersectia care ne intereseaza e goala. Asta nu inseamna ca nu apare in productie — baza de dev are 712 facturi in total.
  • Cele 4 XML-uri de productie cu AllowanceCharge de document pe care le-am examinat sunt, dupa toate semnele, discounturi pe linie cu discount_evidentiat = 1, nu discounturi de document: cel de la 2024_07\...CIA_TRADE_POSSIBLE are cote 9% si 19%, iar alocarea unica e pe 9% — adica pe cota minima, ceea ce regula Max(proc_tvav) nu poate produce; iar cel de la 2024_01\...BEST_POWER_DANCE are doua alocari (9% si 19%), ceea ce discountul de document, fiind o singura pseudo-linie, nu poate produce nici el. Nu am gasit niciun XML de productie care sa fie sigur discount de document.
  • Firmele din D:\ROA\Efactura (VENDING_MASTER, CLEVER_MOTORS, ...) nu au scheme in baza accesibila (select ... from all_users — zero potriviri), deci nu am putut confrunta XML-ul cu antetul lui.

5. Un lucru dovedit pe un fisier real, nu dedus

Pe D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml:

BT-5 DocumentCurrencyCode = EUR
<cac:AllowanceCharge> ... <cbc:Amount currencyID="RON">539.82</cbc:Amount>
<cbc:AllowanceTotalAmount currencyID="EUR">539.82</cbc:AllowanceTotalAmount>

Aceeasi suma apare o data ca RON si o data ca EUR — currencyID e hardcodat "RON" la xmlefactura.prg:778, in timp ce restul documentului foloseste mmoneda. Doua precizari care schimba interpretarea:

  • fisierul de pe disc este identic pe SHA256 cu cel din ...\TRIMISE\4172939206_3250292036.zip, deci e exact ce a plecat la ANAF;
  • si a fost acceptat. Respingerea din ...\ERORI\4172675286_3249814520.zip e a unei incarcari anterioare si e pentru alte doua reguli (BR-CO-15 si BR-CL-04 — cod de moneda invalid, probabil EURO in loc de EUR, ceea ce explica linia de corectie xmlefactura.prg:267).

Deci currencyID="RON" pe factura in valuta e o neconformitate reala si activa in cod azi, dar nu am dovada ca ar cauza o respingere.

6. Ce inseamna pentru #13

Repartizarea proportionala pe cote — recomandarea din raportul principal — rezolva si problema de aici, fara nicio garda in plus: daca discountul se sparge pe cotele care exista efectiv pe factura, fiecare bucata mosteneste id_jtva_coloana si expltva ale grupului ei, grupul orfan dispare, iar agettipcota(1) nu mai poate fi ambiguu. Subscriu, cu doua completari:

  1. Pseudo-liniile de discount trebuie sa primeasca id_jtva_coloana al grupului pe care il reduc, nu doar cota. Cota singura nu ajunge — cheia de grupare are cinci campuri, iar expltva se completeaza tot prin id_jtva_coloana. O implementare care seteaza doar proc_tva ar lasa grupul orfan exact acolo unde e azi.
  2. Regula traieste in doua locuri — VFP (Calculate Max(proc_tvav)) si PL/SQL (MAX(ROUND(discount * (proc_tvav - 1), ...)) in recalculeaza_totaluri_vanzari). Daca se schimba doar una, VANZARI.TOTAL_TVA si TVA-ul din XML vor diferi pe facturile cu cote mixte.

7. Ce ramane deschis

  • Scenariul de la punctul 2 nu e reprodus prin program, ci dedus din cod + semantica IIF masurata. O confirmare ar cere o factura scutita cu discount de document, introdusa manual.
  • Ordinea randurilor in C_TVA_FACTURA dupa GROUP BY (deci ce alege agettipcota(1) intre E si Z) nu e garantata de VFP si nu am fixat-o experimental. Am presupus scutit=0 inaintea lui scutit=1; daca e invers, categoria alocarii iese E in loc de Z — tot gresita, dar altfel.
  • Validatorul ANAF online curent nu a fost testat (interdictie). Tot ce spun despre „nu e respins" se refera la DUKIntegrator 2022 de pe disc.
  • Nu am verificat calea in valuta (prelucreaza_factura_valuta) linie cu linie.