Files
roafacturare/docs/cercetare/discount_document_cota_tva.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

732 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Cota de TVA a discountului de DOCUMENT (VANZARI.DISCOUNT) — program + eFactura
Cercetare read-only. Nu s-a modificat niciun fisier, nu s-a rulat `git_sync.ps1`, pe Oracle doar
`SELECT`.
STATUS: complet.
Acest document e rezultatul imbinarii a doua rapoarte de cercetare pe aceeasi intrebare, facute de
doi agenti separati. Raportul suplimentar, `discount_document_cota_tva_b.md`, ramane pe disc ca
sursa a partii adaugate aici (sectiunile 3.1-3.3 si observatiile din 2.3 si 4.1).
## Raspunsul scurt
Discountul de document **NU se sparge pe cote**. El devine o **singura pseudo-linie negativa**
numita `"Discount <procent> % Factura"`, careia i se atribuie **cota maxima de TVA de pe factura**
(`Calculate Max(proc_tvav) To lnProcTvav`). eFactura chiar genereaza `cac:AllowanceCharge` la nivel
de document, cu `TaxCategory/ID` + `Percent` + `TaxScheme` corecte si `AllowanceChargeReason =
"Discount"`, `ReasonCode = 95` — dar **cota e cea maxima, aleasa prin `Max()`, nu de
utilizator si nici proportionala**, iar "explicatia" e literalul hardcodat `"Discount"`.
Regula "cota maxima" e scrisa **de doua ori**, independent: in VFP (`Calculate Max(proc_tvav)`) si in
PL/SQL (`PACK_FACTURARE.recalculeaza_totaluri_vanzari`, care din ea deriva `VANZARI.DISCOUNT_TVA`,
`TOTAL_TVA` si `TOTAL_CU_TVA`). Pentru #13 asta e informatia operationala: **se schimba in doua
locuri, nu in unul** (sectiunea 1.5).
Pe factura obisnuita (cote mixte, fara scutire), XML-ul rezultat e intern coerent (TaxSubtotal-urile
includ deja pseudo-linia de discount) si **trece validarea** — verificat cu validatorul local
DUKIntegrator, inclusiv pe cazul mixt si pe unul cu baza impozabila negativa. Deci defectul pe care
**l-am putut dovedi** nu se vede: e o eroare **tacuta de atribuire fiscala**. Pe o factura cu 1000
lei la 21% si 1000 lei la 11% cu discount de document 10%, se declara cu **10 lei mai putin TVA**
decat ar trebui (sectiunea 4.4), mereu in acelasi sens — TVA colectat subdeclarat. Pe factura
scutita XML-ul e si mai putin coerent decat atat — vezi paragraful urmator.
**Sectiunea 3 s-a inchis intre timp, cu un rezultat impartit.** Pe o factura obisnuita (toate
liniile `scutit=0`, `expltva` gol), pseudo-linia de discount se contopeste corect in grupul cotei
maxime — verificat prin masuratoare, nu doar dedus (sectiunea 3.1). Dar pe o factura **scutita,
cu taxare inversa sau intracomunitara**, gruparea chiar se rupe: pseudo-linia formeaza un
`TaxSubtotal` **orfan**, cu baza negativa si categorie gresita (sectiunea 3.2). Validatorul local nu
respinge nici aceasta forma (sectiunea 3.3) — deci ramane, ca si defectul de atribuire fiscala, o
eroare **tacuta**, nu una care blocheaza factura.
---
## 1. Cum se aplica azi discountul de document in program
**Cine il aplica: amandoua, fiecare pentru consumatorul lui.** Oracle stocheaza `VANZARI.DISCOUNT`
(valoare absoluta, in moneda documentului) si `VANZARI.DISCOUNT_EVIDENTIAT`, si **isi calculeaza
singur** TVA-ul discountului in `PACK_FACTURARE.recalculeaza_totaluri_vanzari` (sectiunea 1.5);
prelucrarea pentru **listare si eFactura** se face separat, in cursoare VFP (sectiunile 1.1-1.4).
Ambele folosesc aceeasi regula — cota maxima — dar o implementeaza independent.
### 1.1. Randul-sentinela `ZZZZ...` in `crsfactura`
`COMUN\programe\ofacturare_comun.prg:1886-1897` (`Procedure prelucreaza_facturacrs`):
```
1886 If tnDiscount<> 0
1888 Calculate Sum(valdiminuatftva),Sum(vvaldiminuatftva),Max(id_temp) To lnTotalBaza,lnTotalBazaVal,lnIdTemp
1890 Append Blank
1891 Replace denumire With Replicate('Z',20),cantitate With 1,;
1892 pretftva With lnTotalBaza,discountftva With tnDiscount,valdiscountftva With tnDiscount,;
1893 valdiscounttva With Round(tnDiscount * (tnProcTvav - 1),gnPc),;
1894 vpretftva With lnTotalBazaVal,vdiscountftva With tnDiscountVal,vvaldiscountftva With tnDiscountVal,;
1895 vvaldiscounttva With Round(tnDiscountVal * (tnProcTvav - 1),gnPVal),;
1896 proc_Tvav With tnProcTvav,id_temp With lnIdTemp+1
1897 Endif
```
Observatii portante:
- **baza** discountului = `Sum(valdiminuatftva)` peste **toate** liniile, indiferent de cota;
- **TVA-ul** discountului = `tnDiscount * (tnProcTvav - 1)` — o **singura** cota, `tnProcTvav`;
- randul e un **singur rand de document**, nu o repartizare pe linii.
### 1.2. De unde vine `tnProcTvav` — cota maxima de pe factura
Toate cele trei cai de apel calculeaza acelasi lucru:
| apelant | linia | cod |
|---|---|---|
| listare factura emisa (din lista de facturi) | `COMUN\clase\ofacturare_comun.vc2:4494-4498` (`frm_facturi.do_listeaza_formular`, 4188-4539) | `Calculate Max(proc_tvav) To lnProcTvav` -> `prelucreaza_facturacrs([crsdetalii],[crsfactura],lnProcTvav,lnDiscount,lnDiscountVal)` |
| listare proforma | `COMUN\clase\ofacturare_comun.vc2:7293-7298` (`frm_proforme.do_listare`, 7233-7315) | idem |
| listare din `oproceduri_facturare` | `COMUN\programe\oproceduri_facturare.prg:1386-1391` | `Select crsDetaliiListare` / `Calculate Max(proc_tvav) To lnProcTvav` |
| facturare din stoc | `COMUN\programe\ofacturare_stoc.prg:582, 728` | `Calculate Max(proc_tvav) To lnProcTvav` |
**Deci cota de TVA a discountului de document = MAX(cotele de pe liniile facturii).** Nu e nici
aleasa de utilizator, nici derivata din structura pe cote, nici proportionala. Nu exista niciun
control in formular pentru ea si nicio coloana in `VANZARI` care sa o retina.
### 1.3. Randul `ZZZZ...` devine pseudo-linia `"Discount NN.NN % Factura"`
`COMUN\programe\ofacturare_comun.prg:1273-1392` (in `prelucreaza_factura`, scan peste
`crsfacttemp`):
```
1279 If loArticol.denumire <> Replicate('Z',20)
... (linia normala de articol se copiaza in cursorul destinatie)
1361 Else
1362 loArticol.denumire = [FACTURA]
1363 Endif
1365 If lnPretListAviz = 2 && pret fara tva
1367 If loArticol.discountftva <> 0
1368 Append Blank
1369 lnProcentDiscount = loArticol.discountftva * 100 / loArticol.pretftva
1370 Replace denumire With [Discount ]+Alltrim(Str(lnProcentDiscount,10,2))+[ % ]+Alltrim(Proper(loArticol.denumire)),;
1374 pretftva With (-1) * loArticol.discountftva,valftva With (-1) * loArticol.valdiscountftva,...;
1375 ... valtva With (-1) * loArticol.valdiscounttva,;
1376 proc_tva With loArticol.proc_Tvav,id_jtva_coloana With loArticol.id_jtva_coloana,...
1378 Endif
```
Randul `ZZZZ...` nu ajunge niciodata in cursorul final ca atare — la `:1361-1363` doar i se schimba
denumirea in `FACTURA`, iar la `:1367-1377` se creeaza pseudo-linia
**`"Discount 10.00 % Factura"`** cu `pretftva` si `valftva` **negative** si `proc_tva = tnProcTvav`.
**Coloanele atinse in cursorul final** (`crsFacturaFinala` / `crsfacturafinalaval`): `denumire`,
`cantitate`, `pretftva` (negativ), `valftva` (negativ), `valtva` (negativ), `proc_tva`,
`id_jtva_coloana`, `id_jtva_coloana_ex`. Deci **discountul de document mosteneste si coloana de
jurnal TVA (`id_jtva_coloana`) a randului-sentinela** — care nu e setata la `:1891-1896`, deci
ramane 0/blank pe randul `ZZZZ`. (Consecinta in eFactura: sectiunea 2.3.)
### 1.4. `discount_evidentiat` schimba cate pseudo-linii "Discount" apar
- `discount_evidentiat = 0` (implicit): ramura `ofacturare_comun.prg:1177-1203`. Liniile de articol
primesc `0 As discountftva` (`:1188`), deci **nu** genereaza pseudo-linii; doar randul `ZZZZ`
(reinserat separat prin `Where denumire = Replicate('Z',20)`, `:1193-1203`) pastreaza
`discountftva` -> **o singura** pseudo-linie de discount, cea de document.
- `discount_evidentiat = 1`: ramura `:1157-1173`, un singur `Insert` fara `WHERE`, `discountftva`
pastrat per articol (coloana 20 din `group by 2,3,...,20,...`) -> **fiecare articol cu discount
unitar produce propria pseudo-linie** `"Discount X % <articol>"`, plus cea de document. Aici
cotele chiar sunt cele ale articolelor respective.
### 1.5. Regula "cota maxima" e implementata A DOUA OARA, in Oracle
Corectie la afirmatia de la inceputul sectiunii 1 ("Oracle stocheaza doar `VANZARI.DISCOUNT`"):
**este incompleta**. `PACK_FACTURARE.recalculeaza_totaluri_vanzari` reface acelasi rationament
independent, in PL/SQL —
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`, procedura de la
`:16024`:
```
16081 MAX(ROUND(decode(lnInValuta, 1,
16082 ROUND(a1.curs * NVL(lnDiscountFactura,0) / a1.multiplicator, lnPreciziePretV),
16085 NVL(lnDiscountFactura,0)) *
16088 (a1.proc_tvav - 1), lnPreciziePretV)) as DISC_TVA_RON,
16090 MAX(ROUND(NVL(lnDiscountFactura,0) * (a1.proc_tvav - 1), lnPreciziePretV)) as DISC_TVA_VAL,
```
**Precizare pe forma exacta**: nu e `discount * MAX(proc_tvav)`, ci `MAX(discount * (proc_tvav-1))`
— maximul se ia peste **produsele rotunjite**. Cat timp `lnDiscountFactura >= 0` cele doua coincid
(acelasi rand castiga), deci efectul e identic cu al VFP-ului. Ar diverge doar pe un discount
**negativ** (o majorare), unde `MAX` ar alege cota cea mai **mica** — n-am verificat daca un discount
negativ e posibil in UI.
**Ce se scrie cu asta** (`:16049-16062` -> `:16214-16227`): nu doar `VANZARI.DISCOUNT_TVA`, ci si
**totalurile salvate ale documentului**:
```
16052 a.suma_tva_ron - a.disc_tva_ron as TOTAL_TVA,
16053 a.suma_fara_tva_ron - a.disc_fara_tva_ron + a.suma_tva_ron - a.disc_tva_ron as TOTAL_CU_TVA,
...
16214 update vanzari set discount = lnDiscountFactura, discount_tva = lnDiscountTVA, ...
16218 total_tva = lnTotalTVA, total_cu_tva = lnTotalCuTVA, ... where id_vanzare = V_ID_VANZARE;
```
**Consecinta pentru #13**: regula de repartizare a discountului pe cote trebuie schimbata **in doua
locuri, nu unul** — `prelucreaza_facturacrs` (VFP, pentru listare/eFactura/nota contabila) **si**
`recalculeaza_totaluri_vanzari` (Oracle, pentru `VANZARI.DISCOUNT_TVA` / `TOTAL_TVA` /
`TOTAL_CU_TVA`, adica pentru ce se vede in liste, rapoarte de sinteza si sold). Daca se schimba doar
unul, `VANZARI.TOTAL_TVA` si TVA-ul din XML **vor diverge**. Nu am verificat care dintre cele doua
valori e considerata azi "cea buna" acolo unde ambele sunt disponibile.
### 1.6. Ramura "pret cu TVA" (`lnPretListAviz = 1`) — nu atinge facturile
`ofacturare_comun.prg:1380-1391` (ramurile `Otherwise` / `tnDiscountEvidentiat=1 And
lnPretListAviz=1`) seteaza pe pseudo-linia de discount **numai** `pretctva`/`valctva`/`valtva`,
**nu** si `pretftva`/`valftva` — care raman 0. Am verificat daca asta poate ajunge in eFactura:
**nu poate**. `lnPretListAviz` e initializat `= 2` (`COMUN\programe\ofacturare.prg:1112`) si e pus
pe `1` intr-un singur loc, in ramura de **aviz de transfer** (`lcRaport = [AVIZ]`,
`ofacturare.prg:1856-1861`, conditionat de `gnPretListAviz = 1`), iar avizele nu trec de filtrul
`tip_doc_394 IN ('F','S','M','U','H')` din `xmlefactura.prg:276-279`. Deci nu e un defect eFactura.
---
## 2. Ce ajunge in XML-ul eFactura
**Generatorul viu** e `COMUN\programe\xmlefactura.prg` (`getXmlEFactura`), apelat prin
`goExport.export2xml_efactura` (`COMUN\programe\oexport.prg:1586-1594`) din
`COMUN\programe\ofacturare.prg:2126`, cu cursorul `crsFacturaFinala` (sau `crsfacturafinalaval` in
valuta) — exact cursorul de listare (`ofacturare.prg:2108-2114`).
`COMUN\clase\anaf_efactura.vc2` este UI-ul/transportul ANAF (trimitere, validare, preview), nu
generatorul de XML.
### 2.1. Da — se genereaza `cac:AllowanceCharge` la nivel de document
Detectia e **euristica pe denumire + semn**, `xmlefactura.prg:230`:
```
230 Iif('DISCOUNT' $ Upper(a.denumire) And a.pretftva < 0, 1, 0) As discount ;
```
Pseudo-linia `"Discount 10.00 % Factura"` cu `pretftva < 0` (sectiunea 1.3) satisface ambele
conditii. Randurile marcate `discount = 1` sunt excluse din liniile normale (`Scan For discount =
0`, `:901`) si emise ca alocari de document, `xmlefactura.prg:756-795`:
```
757 If mliniireducere > 0
758 SELECT SUM(-1*valftva) as valftva, proc_tva ;
759 FROM C_IES_FORM ;
760 WHERE discount = 1 ;
761 GROUP BY proc_tva ;
762 ORDER BY proc_tva ;
763 INTO CURSOR cDiscounturiTemp
765 Select cDiscounturiTemp
766 SCAN
770 oallowancecharge = oinvoice.appendchild(oxml.createelement("cac:AllowanceCharge"))
771 ... ChargeIndicator = "false"
773 ... cbc:AllowanceChargeReasonCode = "95"
775 ... cbc:AllowanceChargeReason = "Discount"
777 odocallowancecharge = ... ("cbc:Amount") && BT-92
778 odocallowancecharge.setattribute("currencyID", "RON")
779 odocallowancecharge.Text = Alltrim(Str(m.lnValftva, 15, 2))
780 otaxcategoryallowance = ... ("cac:TaxCategory")
782 Select tip From C_TVA_FACTURA Where proc_tva = (m.lnProcTva - 1) * 100 Into Array agettipcota
786 otaxcategoryallowance.lastchild.Text = Alltrim(agettipcota(1))
787 ... ("cbc:Percent")
788 otaxcategoryallowance.lastchild.Text = Alltrim(Str((m.lnProcTva - 1) * 100, 2, 0))
789 otaxschemeallowance = ... ("cac:TaxScheme") / cbc:ID = "VAT"
792 ENDSCAN
```
Raspunsurile punctuale:
- **`cac:TaxCategory/cbc:ID`** (`S`, `AE`, `Z`, `E`, `K`, `O`): **nu e hardcodat** — se ia din
`C_TVA_FACTURA.tip`, adica din `This.GetTipTaxa(mtipfactura, intracomunitar, taxare_inversa,
scutit, proc_tva, @lcExplicatie, @lcMotiv, expltva)` (`xmlefactura.prg:298`), pe grupul cu
**aceeasi cota** ca pseudo-linia de discount.
- **`cbc:Percent`**: `(proc_tva - 1) * 100` al pseudo-liniei — adica **cota maxima de pe factura**
(sectiunea 1.2). *Aici e raspunsul la intrebarea lui Marius: cota exista in XML, dar sursa ei e
un `Max()`, nu o alegere.*
- **`cbc:AllowanceChargeReason`**: literalul `"Discount"` (`:776`), **hardcodat**;
**`cbc:AllowanceChargeReasonCode`**: `"95"` (`:774`), hardcodat. Textul real din program
(`"Discount 10.00 % Factura"`) **nu** ajunge in XML — ramane doar in cursorul de listare.
Deci "explicatia" pe care o cauta Marius nu exista: e o constanta.
### 2.2. Cote mixte: se sparge in mai multe `AllowanceCharge`?
**Mecanismul exista** (`GROUP BY proc_tva`, `:761` -> cate un `AllowanceCharge` per cota), dar
**nu se activeaza pentru discountul de document**, pentru ca acesta e prin constructie o **singura**
pseudo-linie cu o **singura** cota (`Max`). Gruparea foloseste efectiv doar cand
`discount_evidentiat = 1`, unde exista mai multe pseudo-linii de discount **pe articol**, fiecare cu
cota articolului ei.
Deci, pe o factura cu 21% si 11% si un discount de document: **un singur** `AllowanceCharge`, cu
`Percent = 21`.
### 2.3. Riscuri identificate in acest bloc (nedovedite pe rulare)
- **`currencyID` hardcodat `"RON"`** la `:778`, in timp ce tot restul documentului foloseste
`mmoneda` (`:820, 827, 873, 876, ...`, definit la `:266-267`). Pe o factura in valuta cu discount
de document, `AllowanceCharge/cbc:Amount` iese cu `currencyID="RON"` iar
`LegalMonetaryTotal/cbc:AllowanceTotalAmount` cu `currencyID="EUR"`. Acelasi tipar la acciza
(`:803`). **Testat cu validatorul local** (sectiunea 4.3): DUKIntegrator **nu** respinge
neconcordanta. Ramane o inconsecventa de cod, dar nu doar teoretica: sectiunea 4.1 arata un XML
real de productie cu exact aceasta neconcordanta, confirmat pe SHA256 ca a plecat la ANAF si a
fost **acceptat**.
- **`Select ... Into Array agettipcota` fara garda pe `_Tally`** (`:782-786`): daca gruparea nu
gaseste randul (egalitate pe numere in virgula mobila: campul `C_TVA_FACTURA.proc_tva` e stocat
rotunjit, comparatia se face cu `(lnProcTva-1)*100` calculat la rulare), `agettipcota(1)` da
eroare de variabila inexistenta. *Risc teoretic — nu l-am putut reproduce; il semnalez ca "de
verificat", nu ca defect.*
- **`Str((lnProcTva-1)*100, 2, 0)`** — latime 2. Pentru cotele romanesti (0, 5, 9, 11, 19, 21) e in
regula; o cota de trei cifre ar da `**`.
---
## 3. Coerenta cu `TaxTotal` / `TaxSubtotal`
**Da, baza impozabila din XML tine cont de discount** — si o face corect aritmetic.
`C_TVA_FACTURA` se construieste **peste toate randurile** din `C_IES_FORM`, **inclusiv**
pseudo-linia de discount (nu exista `WHERE discount = 0`), `xmlefactura.prg:242-248`:
```
242 Select(proc_tva - 1) * 100 As proc_tva, intracomunitar, taxare_inversa, scutit, expltva, ;
243 Sum(valtva) As tva, ;
244 Sum(valftva) As valoare, ... ;
245 From C_IES_FORM ;
246 Group By proc_tva, intracomunitar, taxare_inversa, scutit, expltva ;
247 Into Cursor C_TVA_FACTURA
```
`valftva`/`valtva` ale pseudo-liniei sunt negative -> grupul cotei maxime iese **deja net de
discount**. `cbc:TaxableAmount` = `C_TVA_FACTURA.valoare` (`:828`), `cbc:TaxAmount` =
`C_TVA_FACTURA.tva` (`:831`).
Totalurile inchid corect (`:863-898`):
```
864 Sum valftva To mtotalnet For discount = 0 && numai liniile reale
867 mtotalnetliniifactura = mtotalnet && BT-106 LineExtensionAmount
869 mtotalnet = mtotalnet + mtotalcharges - mtotalallowances && BT-109 TaxExclusiveAmount
870 mtotalbrut = mtotalnet + mtotaltva && BT-112 TaxInclusiveAmount
882 ... AllowanceTotalAmount = mtotalallowances && BT-107
```
cu `mtotalallowances = mdiscounturi = Sum(-1*valftva) For discount = 1` (`:282-287`).
Verificare: `Σ TaxSubtotal/TaxableAmount` = suma peste toate randurile = `mtotalnetliniifactura −
mdiscounturi` = `mtotalnet` = `TaxExclusiveAmount`. Deci **BR-CO-13** (`TaxExclusiveAmount =
LineExtensionAmount − AllowanceTotalAmount + ChargeTotalAmount`) si **BR-CO-15** se respecta.
**Concluzie**: pe **factura obisnuita**, suma liniilor minus discount **da** `TaxableAmount`-ul
declarat; discrepanta nu e aritmetica, ci **de atribuire pe cote** (sectiunea 4). Pe **factura
scutita / cu taxare inversa / intracomunitara**, concluzia nu tine — vezi 3.1-3.3.
Rationamentul de mai sus presupune ca pseudo-linia de discount **cade in acelasi grup** cu liniile
de la cota maxima. Asta e adevarat doar daca cele patru chei suplimentare din `GROUP BY`
(`intracomunitar`, `taxare_inversa`, `scutit`, `expltva`, `xmlefactura.prg:246`) ies **egale** pe
pseudo-linie si pe liniile reale. Subsectiunile care urmeaza verifica exact asta, prin masuratoare,
nu prin deductie.
### 3.1. Verificat prin masuratoare: `IIF()` peste NULL nu se propaga ca `.NULL.` in VFP
Pseudo-linia are `id_jtva_coloana = 0` (randul-sentinela e `Append Blank` la
`ofacturare_comun.prg:1890` si campul nu e setat la `:1891-1896`). Daca LEFT JOIN-ul de la
`xmlefactura.prg:226-232` nu gaseste rand in `cJTVAVanzariTemp` pentru `id = 0`, `b.coloana_jv` iese
`.NULL.`, si intrebarea e ce intoarce `Iif(Left(.Null.,2) = 'CE', 1, 0)` — daca s-ar propaga ca
`.NULL.`, cheia de grupare a pseudo-liniei ar diferi de a liniilor reale pe **orice** factura.
Am rulat o sonda headless (`vfp9.exe -A -T`, `probe_null.prg`) care reproduce exact acest `LEFT
JOIN` si `GROUP BY`, 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**. Sonda ramane pe disc in
`%TEMP%\claude\...\scratchpad\probe_null.prg`, nu depinde de baza de date si se poate reface
oricand.
### 3.2. Dar pe factura scutita / taxare inversa / intracomunitara, gruparea chiar se rupe
Contopirea de la 3.1 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:
```xml
<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. Ordinea nu e garantata de VFP si nu a fost fixata experimental;
categoria alocarii poate iesi `E` sau `Z` dupa caz — oricare din ele e gresita ca modelare, doar in
mod diferit.
**Nu s-a reprodus scenariul end-to-end prin program** (ar fi cerut o factura scutita cu discount de
document, iar in baza de dev nu exista — sectiunea 4.2). Ramane stabilit din citirea codului plus
semantica `IIF`/NULL masurata la 3.1, nu dintr-o factura rulata efectiv prin program.
### 3.3. Validare offline: XML-ul cu grup orfan nu e respins, dar controlul negativ functioneaza
XML-urile de mai sus au fost construite pornind de la o factura reala acceptata de ANAF ca schelet
si trecute prin validatorul local `DUKIntegrator.jar` (acelasi apel ca la 4.3):
| scenariu | ce contine | rezultat |
|---|---|---|
| `scutit_bug` | scutit + discount de document, **cu grupul orfan** (ce produce codul azi) | **ok** |
| `scutit_corect` | scutit + discount, un singur `TaxSubtotal` net (varianta corecta) | **ok** |
| `control_stricat` | `TaxExclusiveAmount` stricat intentionat, ca martor ca validatorul chiar valideaza | **eroare `BR-CO-13` + `BR-CO-15`** |
Controlul negativ conteaza: fara el, un „ok" pe `scutit_bug` n-ar dovedi nimic — ar putea insemna
ca validatorul nu verifica deloc coerenta totalurilor. Cu el, e clar ca validatorul **chiar
verifica**, si totusi trece grupul orfan.
**De ce trece**: 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.
Aceeasi rezerva ca in 4.3: validatorul local e din ianuarie 2022 (`ro16931-ubl-1.0.8`); validatorul
online curent al ANAF nu a fost apelat (interdictie explicita).
> **Verdict sectiunea 3**: contopirea corecta descrisa mai sus e confirmata prin masuratoare pentru
> factura obisnuita (3.1) si infirmata pentru factura scutita / taxare inversa / intracomunitara
> (3.2), unde discountul de document formeaza un `TaxSubtotal` orfan cu baza negativa si categorie
> ambigua (`E` sau `Z`). Pe niciuna din cele doua forme validatorul local nu respinge XML-ul (3.3,
> si 4.3 pentru cazul cu cote mixte) — deci ramane, ca si defectul din sectiunea 4, o eroare
> **tacuta**, nu una care blocheaza factura.
---
## 4. Defect real sau gol de proiectare
**Verdict: NU s-a putut dovedi un defect de VALIDARE — nici pe factura obisnuita, nici pe cea
scutita cu grupul orfan (sectiunea 3.2-3.3), validatorul local nu respinge XML-ul. S-a dovedit in
schimb un defect de ATRIBUIRE FISCALA: pe facturi cu cote mixte, tot TVA-ul discountului de
document se scade din cota maxima, si pe facturi scutite/taxare inversa/intracomunitare, o
modelare fiscala gresita (grup orfan) care trece neobservata.** Adica exact ce banuia Marius, dar
consecinta stabilita nu e "factura respinsa", ci "TVA colectat gresit sau baza modelata gresit,
tacut".
> **Nuanta ramasa, dupa inchiderea ipotezei din sectiunea 3**: ipoteza grupului orfan **s-a
> confirmat** pentru facturile scutite/taxare inversa/intracomunitare (3.2), dar validatorul local
> **nu** o respinge (3.3) — deci nu devine, cum s-ar fi putut anticipa, un defect de validare
> propriu-zis, ci ramane in aceeasi categorie cu restul sectiunii 4: o eroare de modelare care nu
> blocheaza factura. Testele din 4.3 (cote mixte) si cele din 3.3 (factura scutita) acopera acum
> ambele forme.
### 4.1. Dovada ca mecanismul chiar produce `AllowanceCharge` de document in productie
Am cautat in cele ~250 de XML-uri eFactura salvate pe disc (`D:\ROA\Efactura`, `D:\ROA\ROAEFACTURA`,
`D:\ROA\ROAFACTURARE\Utile\efactura`) blocurile `cac:AllowanceCharge` care contin `cac:TaxCategory`
(marca alocarii **de document**; cele de linie nu au `TaxCategory`, au `MultiplierFactorNumeric`).
Exemple reale:
| fisier | Amount | TaxCategory | Percent |
|---|---|---|---|
| `D:\ROA\Efactura\2024_01\VENDING_MASTER_SRL\efactura_20240125_1526_BEST_POWER_DANCE_S_R_L_.xml` | 13.84 / 25.02 (doua) | S / S | 9 / 19 |
| `D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml` | 539.82 | E | 0 |
| `D:\ROA\Efactura\2024_03\VENDING_MASTER_SRL\efactura_20240301_3736_BRIANA_ALIMENT_SRL.xml` | 411.01 | S | 9 |
| `D:\ROA\Efactura\2024_07\VENDING_MASTER_SRL\efactura_20240426_7446_CIA_TRADE_POSSIBLE_SRL.xml` | 176.15 | S | 9 |
Forma emisa (exemplu real, `...2885...`):
```xml
<cac:AllowanceCharge><cbc:ChargeIndicator>false</cbc:ChargeIndicator>
<cbc:AllowanceChargeReasonCode>95</cbc:AllowanceChargeReasonCode>
<cbc:AllowanceChargeReason>Discount</cbc:AllowanceChargeReason>
<cbc:Amount currencyID="RON">539.82</cbc:Amount>
<cac:TaxCategory><cbc:ID>E</cbc:ID><cbc:Percent>0</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:TaxCategory></cac:AllowanceCharge>
```
**Precizare de onestitate**: niciunul dintre aceste patru nu pot sa-l atribui sigur discountului de
**document**. Dimpotriva — la `...7446...` factura are linii pe 9% (2016.51) si pe 19% (1477.11),
iar unica alocare cade pe **9%**; daca ar fi fost discount de document, `Max(proc_tvav)` ar fi dat
**19%**. Deci acolo sunt discounturi **pe linie** cu `discount_evidentiat = 1` (sectiunea 1.4). La
fel `...1526...`, unde cele doua alocari sunt exact 2.00% din baza fiecarei cote. **Nu am gasit pe
disc un XML in care sa pot identifica pozitiv un discount de document.** Ce dovedesc aceste fisiere
e ca **traseul cod -> `AllowanceCharge` cu `TaxCategory` chiar functioneaza in productie** si ca
gruparea pe cote e reala cand exista mai multe pseudo-linii de discount.
**Al treilea indiciu, pe acelasi fisier `...2885...`, in sens invers.** Factura e integral **scutita
cu drept de deducere**: toate cele trei linii sunt `E` / `Percent 0` cu `TaxExemptionReason =
"Scutit cu drept de deducere"`, `DocumentCurrencyCode = EUR`, si are `AllowanceCharge` la nivel de
document de 539.82 cu `TaxCategory ID = E`. Are **un singur `TaxSubtotal`**: `TaxableAmount =
3870.18`, adica exact `4410.00 − 539.82` — **net de discount, fara grup orfan**.
Asta e semnificativ dupa sectiunea 3.2: `expltva` face parte din cheia de grupare, iar grupul unic
de aici poarta `TaxExemptionReason` completat. Daca pseudo-linia de discount ar fi avut `expltva`
gol si `id_jtva_coloana = 0` (cazul discountului de **document**, descris la 3.2), gruparea nu s-ar
fi putut contopi — ar fi iesit doua grupuri, ca in `scutit_bug` (3.3). Contopirea observata aici
dovedeste deci ca pseudo-linia purta un `id_jtva_coloana` real, adica era un discount **pe linie**
(`discount_evidentiat = 1`), nu de document — un al treilea indiciu, independent de cele doua de mai
sus (alocare pe cota minima la `...7446...`; doua alocari la `...1526...`), care coroboreaza aceeasi
concluzie: cele patru XML-uri de productie gasite sunt discounturi pe linie, nu de document.
Fisierul arata insa **pozitiv forma corecta** pentru o factura scutita cu discount de document: cand
pseudo-linia poarta `id_jtva_coloana` corect, grupul iese unul singur si net — exact forma
recomandata la sectiunea 6, punctul 1. Deci recomandarea are un precedent real in productie, nu doar
o constructie sintetica de test. **Rezerva onesta**: fisierul e din februarie 2024, iar inferenta
presupune ca traseul de cod e neschimbat de atunci; ramane adevarat ca **nu exista niciun XML de
productie identificat pozitiv ca discount de DOCUMENT**.
**Identitatea fisierului, verificata pe SHA256.**
`D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml` (5922
octeti) are acelasi SHA256 (`3303AFE34BC9E3E84DA9B3527C435DD9B8CB0BC7881D7D2F660A250237E23C64`) cu
`4172939206.xml` din `...\TRIMISE\4172939206_3250292036.zip` — deci e exact ce a plecat la ANAF, si
a fost **acceptat**. Continutul din `...\ERORI\4172675286_3249814520.zip` e un mesaj de eroare de
446 octeti pentru o incarcare **anterioara**, cu alt index de incarcare, si listeaza exact doua
reguli: `BR-CO-15` si `BR-CL-04` (cod de moneda invalid). Deci respingerea aceea **nu** e a
fisierului cu `currencyID="RON"` discutat la sectiunea 2.3 — acela a trecut.
### 4.2. Datele din baza de dev — putine, dar arata ca situatia e posibila
`MARIUSM_AUTO@ROA_CENTRAL`, doar `SELECT`:
- facturi nesterse cu `VANZARI.DISCOUNT <> 0`: **3** (id_vanzare 165 / 575 / 576;
`discount_evidentiat` = 1 / 1 / 0). Toate trei au **o singura cota** pe linii (1.19, 1.24, 1.24).
- facturi nesterse cu **cote mixte** pe linii: **34**.
Deci in dev nu exista intersectia "discount de document + cote mixte". **Asta nu dovedeste nimic** —
volumele de dev sunt mici, iar ambele ingrediente exista separat. In productie combinatia e banala
(orice comerciant cu alimente 9%/11% + nealimentare 21% care da un discount global).
### 4.3. Ce am testat efectiv cu validatorul ANAF, offline
Am folosit **validatorul local** `D:\ROA\COMUNROA\dist_efactura\DUKIntegrator.jar` (`-v FACT1`,
reguli `ro16931-ubl-1.0.8`), acelasi pe care il apeleaza `xmlefactura.prg:1166-1194`. **Nu s-a
trimis nimic catre ANAF; nu s-a apelat niciun serviciu web.** XML-urile de test sunt in
`%TEMP%\claude\...\scratchpad\` (base/scenA/scenB/scenC_*), construite plecand de la un XML real
valid.
| test | ce contine | rezultat |
|---|---|---|
| `base` | XML-ul real `...7446...`, nemodificat (martor) | **ok** |
| `scenA` | cote mixte 21% + 11%, discount 200 lei atribuit **integral** cotei 21% (exact ce genereaza programul) | **ok** |
| `scenB` | 100 lei @21% + 5000 lei scutit, discount 510 lei la 21% -> `TaxableAmount = -410.00`, `TaxAmount = -86.10` | **ok** |
| `scenC_corect` | acelasi ca scenA, in EUR, cu `AllowanceCharge/Amount currencyID="EUR"` | **ok** |
| `scenC_bug` | idem, dar cu `currencyID="RON"` pe alocare (ce face codul la `:778`) | **ok** |
**Concluzii, inclusiv cele care imi infirma ipotezele:**
1. Atribuirea intregului discount cotei maxime **trece validarea** — deci nu e o eroare pe care ANAF
sa o prinda. E o eroare **tacuta**.
2. Ipoteza mea ca o `TaxableAmount` **negativa** ar fi respinsa e **infirmata** de validatorul local.
3. Ipoteza ca neconcordanta de moneda (`currencyID="RON"` pe factura in EUR) ar fi respinsa e tot
**infirmata** de validatorul local.
4. **Rezerva**: validatorul instalat e din **ianuarie 2022** (`DUKIntegrator.jar`, 19.01.2022,
reguli 1.0.8). Regulile ANAF s-au inasprit de atunci. Punctele 2 si 3 raman "neinfirmate de
validatorul disponibil local", nu "garantat acceptate azi de ANAF".
### 4.4. Scenariul reproductibil al defectului REAL (fiscal)
**Factura**: client intern, in lei, `discount_evidentiat = 0`.
- Linia 1: marfa cota standard, 1 buc x 1000.00 lei, **21%**
- Linia 2: marfa cota redusa, 1 buc x 1000.00 lei, **11%**
- Discount de document: **10%** -> `VANZARI.DISCOUNT = 200.00`
**Ce face programul**:
- `Max(proc_tvav)` = 1.21 -> pseudo-linia de discount primeste **21%**
(`oproceduri_facturare.prg:1387` / `ofacturare_comun.vc2:4495`)
- `valdiscounttva = Round(200 * 0.21, 2) = 42.00` (`ofacturare_comun.prg:1893`)
**Ce iese in XML** (verificat ca valid — `scenA`):
```
AllowanceCharge: Amount 200.00, TaxCategory S, Percent 21
TaxSubtotal S/21: TaxableAmount 800.00 TaxAmount 168.00
TaxSubtotal S/11: TaxableAmount 1000.00 TaxAmount 110.00
TaxAmount total 278.00 ; LineExtension 2000.00 ; TaxExclusive 1800.00 ; TaxInclusive 2078.00
```
**Ce ar fi trebuit** (discount repartizat proportional cu baza: 100 lei pe fiecare cota):
```
AllowanceCharge #1: 100.00, S, 21 AllowanceCharge #2: 100.00, S, 11
TaxSubtotal S/21: 900.00 / 189.00
TaxSubtotal S/11: 900.00 / 99.00
TaxAmount total 288.00 ; TaxInclusive 2088.00
```
**Diferenta: 10.00 lei TVA colectat in minus**, adica 5% din TVA-ul facturii — si creste cu ecartul
dintre cote si cu marimea discountului. **Sensul e mereu acelasi**: cota maxima absoarbe tot
discountul, deci TVA-ul declarat e **prea mic**. Riscul e al emitentului (TVA colectat
subdeclarat), nu al ANAF-ului care respinge documentul. Aceeasi valoare gresita ajunge si in nota
contabila si in jurnalul de TVA, pentru ca `valdiscounttva` e acelasi camp.
**Caz-limita, tot valid dupa validator dar clar absurd** (`scenB`): factura cu 100 lei la 21% si
5000 lei scutit, discount de document 10% (510 lei). Tot discountul intra pe cota 21%, unde baza e
100 lei -> `TaxSubtotal S/21` iese cu `TaxableAmount = -410.00` si `TaxAmount = -86.10`. Factura
declara **TVA negativ** pe o livrare normala. Nu e nevoie de cote diferite de zero pe ambele parti:
ajunge o factura in care liniile de la cota maxima sunt o mica parte din total.
### 4.5. Gol de proiectare, distinct de defect
**Explicatia ("reason") nu exista ca notiune in program.** In XML `AllowanceChargeReason` e literalul
`"Discount"` si `ReasonCode` e `"95"`, ambele hardcodate (`xmlefactura.prg:774-776`). Textul construit
in VFP — `"Discount 10.00 % Factura"` (`ofacturare_comun.prg:1370`) — **nu ajunge in XML**. Nu exista
nicio coloana pe `VANZARI` pentru motivul discountului si niciun control in formular. Deci raspunsul
la a doua jumatate a intrebarii lui Marius: **nu, discountul de document nu are azi explicatie —
are o constanta.**
---
## 5. Recomandare pentru formularul unificat (#13)
**Sa NU ceara utilizatorului cota. Sa sparga discountul automat, proportional cu baza fiecarei cote,
si sa expuna doar un camp optional de motiv.** Argumentul e ca discountul de document nu *are* o cota
proprie de ales: prin definitie e un procent aplicat bazei intregii facturi (`lnTotalBaza =
Sum(valdiminuatftva)` peste toate liniile, `ofacturare_comun.prg:1888`), deci natura lui de TVA e
determinata de liniile pe care le reduce, nu de o optiune. A pune operatorul sa aleaga o cota inseamna
a-i cere sa ia o decizie fiscala pe care datele o dau deja, si a deschide o a doua cale de a gresi —
azi `Max()` greseste consecvent, un camp liber ar greseste imprevizibil. Repartizarea proportionala e
si singura care satisface modelul EN16931, unde `TaxableAmount`-ul fiecarei categorii se calculeaza ca
*net de linii al categoriei minus alocarile de document ale aceleiasi categorii*. **Costul de
implementare e mic, dar atinge doua locuri, nu unul** (sectiunea 1.5): `prelucreaza_facturacrs`
(`ofacturare_comun.prg:1886-1897`) trebuie sa insereze cate un rand-sentinela **per cota** in loc de
unul singur, cu `tnDiscount` repartizat pe baza fiecarei cote (ultima cota preia diferenta de
rotunjire, ca suma sa fie exact `VANZARI.DISCOUNT`), **si** `recalculeaza_totaluri_vanzari`
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16081-16092`) trebuie sa inlocuiasca `MAX(...)` cu suma
repartizarii, altfel `VANZARI.TOTAL_TVA` ramane pe regula veche si diverge de XML; **eFactura nu are
nevoie de nicio modificare structurala** —
`xmlefactura.prg:758-792` grupeaza deja `GROUP BY proc_tva` si emite cate un `AllowanceCharge` per
cota, iar `C_TVA_FACTURA` include deja pseudo-liniile in `TaxSubtotal`. Doua consecinte de decis
explicit cu Marius: **factura tiparita** va arata N randuri "Discount X % Factura" in loc de unul (pe
cote mixte), si **nota contabila** primeste TVA-ul discountului spart pe cote. Pentru "explicatie",
recomand un singur camp text optional pe document, folosit ca `AllowanceChargeReason` in locul
constantei `"Discount"` (`ReasonCode` ramane `95`) — util si pe hartie, si e singura bucata care chiar
cere UI nou.
**Precizare, dupa sectiunea 3.2**: "eFactura nu are nevoie de modificare" e adevarat doar daca
fiecare pseudo-linie noua, per cota, primeste si `id_jtva_coloana` **al grupului pe care il reduce**,
nu doar `proc_tva` al lui. Cota singura nu ajunge — cheia de grupare din `xmlefactura.prg:246` are
cinci campuri, iar `expltva` se completeaza tot prin `id_jtva_coloana` (sectiunea 3.2). O
implementare care seteaza doar `proc_tva` pe randul-sentinela ar lasa grupul orfan exact acolo unde
e azi, pe facturile scutite / cu taxare inversa / intracomunitare. Fisierul `...2885...` de la
sectiunea 4.1 e dovada pozitiva ca, atunci cand `id_jtva_coloana` e setat corect, forma iese unul
singur si net — deci reparatia are deja un precedent real in productie, nu doar o constructie de
test.
---
## Verificat direct vs. dedus
**Verificat direct (citit in cod, cu `fisier:linie`)**
- randul-sentinela `ZZZZ` si campurile lui — `ofacturare_comun.prg:1886-1897`
- transformarea in pseudo-linia `"Discount % Factura"` — `ofacturare_comun.prg:1361-1392`
- `Max(proc_tvav)` pe toate cele patru cai de apel — `oproceduri_facturare.prg:1387`,
`ofacturare_comun.vc2:4495` si `:7294`, `ofacturare_stoc.prg:582`
- a doua implementare a aceleiasi reguli, in PL/SQL, si campurile pe care le scrie —
`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16024` (procedura), `:16081-16092` (`MAX(...)`),
`:16049-16062` (`TOTAL_TVA`/`TOTAL_CU_TVA` derivate din el), `:16214-16227` (`update vanzari`)
- euristica de detectie in eFactura — `xmlefactura.prg:230`
- generarea `AllowanceCharge` de document, cu `GROUP BY proc_tva` — `xmlefactura.prg:756-795`
- sursa lui `TaxCategory/ID` (`GetTipTaxa`, `xmlefactura.prg:1103-1143`) si a lui `Percent`
- includerea pseudo-liniei in `C_TVA_FACTURA` -> `TaxSubtotal` — `xmlefactura.prg:242-248, 822-851`
- inchiderea totalurilor `LegalMonetaryTotal` — `xmlefactura.prg:863-898`
- ca generatorul viu e `xmlefactura.prg`, nu `anaf_efactura` — `oexport.prg:1586-1594`,
`ofacturare.prg:2107-2126`
- ca ramura "pret cu TVA" (unde pseudo-linia iese cu `valftva = 0`) nu poate ajunge in eFactura —
`ofacturare.prg:1112, 1856-1861` + filtrul `tip_doc_394` din `xmlefactura.prg:276-279`
- semantica `LEFT JOIN` + `IIF`/`INLIST` peste `.NULL.` in cheia de grupare (sectiunea 3.1/3.2) —
`xmlefactura.prg:226-248`
- de ce grupul orfan nu poate fi completat prin `completeaza_explicatie_tva` — filtrul de scan vs.
filtrul de update (`ofacturare_comun.prg:2094` vs. `:2108, 2129`, sectiunea 3.2)
- sursa categoriei `Z` a grupului orfan, ramura cu ramura in `GetTipTaxa` — `xmlefactura.prg:1116-1140`
(sectiunea 3.2)
- incarcarea `cJTVAVanzariTemp` doar cu `id_jtva_coloana > 0`, motivul pentru care randul 0 nu se
gaseste — `updateserver.prg:578` (sectiunea 3.2)
**Verificat prin rulare**
- 4 XML-uri reale de productie cu `AllowanceCharge` de document (sectiunea 4.1)
- 3 facturi cu discount in baza de dev, 34 cu cote mixte (`SELECT`, sectiunea 4.2)
- 5 validari DUKIntegrator offline pe cote mixte (sectiunea 4.3) — **au infirmat doua dintre
ipotezele initiale**
- semantica `IIF()` peste `.NULL.` masurata cu o sonda headless (`probe_null.prg`, sectiunea 3.1) —
intoarce ramura falsa, nu `.NULL.`
- 3 validari DUKIntegrator offline pe scenariul grupului orfan (`scutit_bug`, `scutit_corect`,
plus `control_stricat` ca martor negativ, sectiunea 3.3) — grupul orfan **nu** e respins
- identitatea SHA256 a XML-ului EUR de productie fata de fisierul chiar trimis la ANAF, si citirea
celor doua zip-uri (`TRIMISE`/`ERORI`) care arata ca respingerea gasita e a unei incarcari
anterioare, pentru alte reguli (sectiunea 4.1)
**Dedus, NEverificat prin rulare**
- calculul numeric din scenariul 4.4 (aritmetica pe formulele citite, nu o factura rulata efectiv
prin program)
- riscul `agettipcota(1)` fara garda pe `_Tally` (`xmlefactura.prg:782-786`) — n-am reusit sa-l
provoc si nu-l afirm ca defect
- ce se intampla in nota contabila / jurnalul de TVA cu `valdiscounttva` — am dedus din faptul ca e
acelasi camp, n-am urmarit consumatorii
- scenariul "factura scutita + discount de document" **end-to-end prin program** (sectiunea 3.2):
gruparea separata e stabilita din cod + semantica `IIF`/NULL masurata, nu dintr-o factura reala
rulata prin program — in baza de dev nu exista intersectia (sectiunea 4.2)
- ordinea randurilor in `C_TVA_FACTURA` dupa `GROUP BY` (deci daca `agettipcota(1)` alege `E` sau
`Z` in cazul grupului orfan) — nu e garantata de VFP si nu a fost fixata experimental
**Neacoperit — si stiut ca neacoperit**
- daca `VANZARI.TOTAL_TVA` (Oracle, sectiunea 1.5) si TVA-ul din XML sunt confruntate undeva si care
primeaza — am constatat doar ca ambele exista si folosesc aceeasi regula
- daca un discount de document **negativ** e posibil din UI (ar inversa `MAX`-ul din Oracle, 1.5)
- calea in **valuta** (`crsfacturafinalaval` / `prelucreaza_factura_valuta`,
`ofacturare_comun.prg:1540+`) — am presupus simetrie cu cea in lei pe baza campurilor `v*`
paralele de la `:1894-1895`, n-am parcurs-o linie cu linie
- daca `Max(proc_tvav)` e cota corecta si pentru **proforme** in vreun sens special
- comportamentul validatorului ANAF **curent** (cel local e din 2022, atat pentru cote mixte cat si
pentru grupul orfan)
---
## Intrebari ramase pentru Marius
1. **Repartizarea proportionala se aplica retroactiv la relistare?** O factura veche relistata sau
retrimisa in eFactura ar genera alt XML decat cel trimis initial. Se accepta, sau noul
comportament se leaga de o data / de un flag?
2. **Pe factura tiparita**: e in regula sa apara N randuri "Discount X % Factura" (unul pe cota), sau
vrei un singur rand vizibil si spargerea doar in XML / nota contabila?
3. **Rotunjirea**: pe cine cade diferenta de rotunjire cand discountul nu se imparte exact
(ex. 100 lei pe trei cote)? Propunerea mea: pe cota cu baza cea mai mare.
4. **Discountul care depaseste baza unei cote** (cazul din 4.4, factura majoritar scutita): dupa
repartizarea proportionala problema dispare de la sine — confirmi ca nu mai e nevoie de nicio
garda separata?
5. **Campul de motiv**: il vrei per document (un singur text), sau e suficient sa ramana constanta
`"Discount"` si sa nu adaugam UI?
6. **`discount_evidentiat`**: mai e folosit in productie? Schimba semnificativ ce se tipareste
(sectiunea 1.4) si e a doua sursa de pseudo-linii "Discount" in XML.
7. **Cele doua implementari ale regulii** (VFP + Oracle, sectiunea 1.5): se schimba amandoua in
aceeasi livrare, sau Oracle ramane pe regula veche o vreme? Daca raman desincronizate,
`VANZARI.TOTAL_TVA` si TVA-ul din eFactura vor diferi pe facturile cu cote mixte si discount.
8. **Discount de document negativ** (majorare): e posibil din UI? Daca da, `MAX`-ul din Oracle alege
cota cea mai **mica**, iar `Max(proc_tvav)` din VFP tot pe cea mai mare — adica cele doua
implementari ar diverge deja azi, inainte de orice modificare.