sync SVN r18077
This commit is contained in:
731
docs/cercetare/discount_document_cota_tva.md
Normal file
731
docs/cercetare/discount_document_cota_tva.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user