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
732 lines
44 KiB
Markdown
732 lines
44 KiB
Markdown
# 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.
|
||
|