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

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

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

14 KiB

Discount pe linie (DISCOUNT_UNITAR) in afara formularului de facturare — rapoarte .frx, eFactura, SAF-T

Cercetare read-only pentru S4c (plan_13). Nu s-a modificat niciun fisier, nu s-a rulat git_sync.

Verdict (5-10 randuri)

Niciun raport tiparit de factura (factura.fr2, facturatip*.fr2, factura_val*.fr2, invoice*.fr2, proforma*.fr2) nu afiseaza discountul pe linie ca coloana separata — nici ca valoare, nici ca procent. Rapoartele tiparesc pretftva (pret unitar) si valftva (valoare linie) care sunt deja nete de discount (calculate ca pretftva-discountftva / Sum(valdiminuatftva) in cursorul intermediar crsfacttemp, construit de prelucreaza_factura/creeaza_crsfacttemp din ofacturare_comun.prg). eFactura (xmlefactura.prg) foloseste exact acelasi cursor — comentariul din cod chiar spune asta explicit — deci LineExtensionAmount/PriceAmount trimise la ANAF sunt aceleasi valori nete, cu optiunea (dezactivata implicit, din flag-uri globale) de a adauga si un cac:AllowanceCharge informativ pe linie. SAF-T (D406): nu exista niciun generator D406/SAF-T in ROAFACTURARE — exista doar tabele de nomenclator cu prefix saft_ (coduri TVA/plata) folosite pe partea de achizitii, fara legatura cu DISCOUNT_UNITAR.

Raspuns la intrebarea care conteaza: daca discountul pe linie devine editabil direct in grid, nu se strica nimic in codul rapoartelor/eFacturii — ele citesc oricum campurile finale (valdiminuatftva/discountftva/discount_unitar) din cursor, indiferent cum au fost populate (dialog separat vs. editare in grid). Riscul real e in alta parte: exista deja azi o cale prin care discountul e editabil direct in grid (vdiscountftva, pe factura in valuta) care nu declanseaza recalcularea lui valdiminuatftva/valdiminuatctva — vezi docs\cercetare\discount_verificare2.md punctul 3, ultimul paragraf. Daca S4c extinde editarea in grid la toate coloanele de discount fara sa cablaje si recalculul, rapoartele si eFactura vor tipari/trimite valori vechi (stale), pentru ca ambele citesc valdiminuatftva, nu discount_unitar direct.

1. Rapoartele .frx/.fr2 tiparite

Cautare: Grep 'DISCOUNT' si apoi Grep 'disc' (case-insensitive) in toate .fr2 din COMUN\Rapoarte cu glob *factur*.fr2, *proforma*.fr2, *invoice*.fr2 — zero potriviri in toate cele (factura.fr2, facturatip.fr2, facturatip_a5.fr2, facturatip_cuchit.fr2, factura_a5.fr2, factura_chit.fr2, factura_val.fr2, factura_val_a5.fr2, invoice.fr2, invoice_a5.fr2, proforma.fr2, proforma_orig.fr2, proforma_val.fr2, proforma_val_orig.fr2). Singurele 2 fisiere .fr2 din toata COMUN\Rapoarte care contin "DISCOUNT" sunt rap_nir_materiale.fr2 si rap_nir_marfuri.fr2 — rapoarte de NIR (receptie), nu facturi emise.

Ce se tipareste per linie (confirmat in factura.fr2):

1002: <expr><![CDATA[formateaza(cantitate,30,gnPCant)]]>
1014: <expr><![CDATA[formateaza(pretftva,14,gnPPretV)]]>
1026: <expr><![CDATA[formateaza(valftva,14,gnPc)]]>
1062: <expr><![CDATA[PADL(ALLTRIM(STR((proc_tva-1)*100)),2,[ ])+'%']]>   -- procent TVA, nu discount

Deci: cantitate, pret unitar, valoare linie, procent TVA. Nici discount valoric, nici procent discount.

De unde vin pretftva/valftva — si de ce sunt deja nete: cursorul de tiparire (crsfacttemp) e construit de Procedure prelucreaza_factura (COMUN\programe\ofacturare_comun.prg:1055-1059), apelata din COMUN\programe\ofacturare.prg:1887:

prelucreaza_factura([crsfactura], [crsfacturaset], [crsfacturafinala], poDate.discount_evidentiat, poDate.in_valuta, lnPretListAviz, poDate.nListareDetaliata)

In interior, Do Case pe tnDiscountEvidentiat/lnPretListAviz (ofacturare_comun.prg:1156-1248). Cazul comun, tnDiscountEvidentiat = 0 (checkbox-ul "Se pune in evidenta discount-ul..." NEBIFAT — comportamentul implicit):

1182: Sum(cantitate) As cantitate,pretftva-discountftva As pretftva,;
...
1186: Sum(valdiminuatftva) As valftva,Sum(valdiminuattva) As valtva,;

adica pretul tiparit = pret brut minus discountul pe unitate, iar valoarea tiparita = suma valdiminuatftva (deja calculata la adaugarea articolului ca pretftva - discount_unitar, cf. ofacturare.vc2:13126, deja documentat in discount_pe_articol.md sectiunea 3). Simetric pentru varianta cu TVA la :1226-1230.

Cazul tnDiscountEvidentiat = 1 (checkbox bifat — discountul "se pune in evidenta"):

1165: pretftva,Nvl(codbare,...   -- pretftva RAMANE brut, nu se scade discountftva
1166-1167: Sum(valftva) As valftvai,... Sum(valftva) As valftva,Sum(valtva) As valtva,
1168: discountftva,Sum(valdiscountftva) As valdiscountftva,Sum(valdiscounttva) As valdiscounttva,

Aici pretftva/valftva tiparite raman brute (neta de discount), iar discountftva/ valdiscountftva/valdiscounttva sunt calculate in cursor dar nu sunt tiparite de niciun .frx (confirmat, zero hit pe "disc" in .fr2). Practic, cand discountul e "pus in evidenta", pretul si valoarea linie tiparite pe factura NU reflecta discountul per linie — discountul apare doar ca linie separata de document (randul-sentinela denumire = Replicate('Z',20), populat din Thisform.nbazafdiscount/nbazafdiscountval la ofacturare.vc2:14534,14591,18532,18602 si ofacturare_comun.prg:1891) — asta e insa discountul de document (VANZARI.DISCOUNT), nu DISCOUNT_UNITAR pe linie. Nu am gasit nicio dovada ca acest mod (discount_evidentiat=1) ar fi folosit uzual — e o optiune existenta, documentata deja in discount_pe_articol.md punctul 3 ca schimband "doar mecanismul aritmetic", dar aici se vede consecinta suplimentara: si ce se tipareste.

Fara impartiri la pret zero pe randul de discount: unde raportul calculeaza procent (proc_disc), sursa e protejata explicit:

1184-1185 (ofacturare_comun.prg): Iif(cu_tva=0,Iif(pretftva<>0,Round(discountftva/pretftva*100,2),0),;
                                   Iif(pretctva<>0,Round(discountctva/pretctva*100,2),0))

deci nu exista risc de impartire la zero — dar, ca observat mai sus, proc_disc nu e tiparit nicaieri in .frx-urile de factura verificate (e calculat doar pentru consum intern/eFactura).

2. eFactura (ANAF/UBL) — xmlefactura.prg

Concluzie: eFactura foloseste acelasi cursor de linii ca cel de la tiparire, explicit documentat in cod:

oexport.prg:1590: * tcCursorLiniiFactura = numele cursorului cu liniile facturii, asa cum arata la listare

Lantul: ofacturare.prg:2126 -> goExport.export2xml_efactura(poDate, m.lcCursoreFactura, ...) cu lcCursoreFactura = lcCursorFacturaTemp (= 'crsFacturaFinala', ofacturare.prg:1892, sau 'crsfacturafinalaval' pentru valuta) -> xmlefactura.prg:231:

Select a.*, ... From (m.tcCursorLiniiFactura) a Left Join cJTVAVanzariTemp b ... Into Cursor C_IES_FORM Readwrite

Deci C_IES_FORM (sursa liniilor XML) e construit direct din cursorul de tiparire — aceleasi pretftva/pretftvai/valftva/valftvai/proc_disc descrise la punctul 1, cu aceeasi dependenta de poDate.discount_evidentiat.

Ce se trimite pe linie:

  • cbc:LineExtensionAmount (BT-131) = C_IES_FORM.valftva — xmlefactura.prg:935-937.
  • cbc:PriceAmount = C_IES_FORM.pretftva — xmlefactura.prg:1043-1046.
  • Optional (doar daca flag global gnEFACTURA_XML_DISC_PLISTA_LINIE = 1 SI pretftvai > pretftva): un cac:AllowanceCharge la nivel de linie cu Amount = valftvai-valftva, BaseAmount = valftvai, MultiplierFactorNumeric = proc_disc — xmlefactura.prg:952-971.
  • Optional (flag separat gnEFACTURA_XML_DISC_PLISTA_ART = 1): un cac:AllowanceCharge la nivel de cac:Price cu discountul unitar (ABS(pretftvai-pretftva)) — xmlefactura.prg:1054-1067.

Ambele flag-uri sunt verificate cu TYPE(...) = 'N' AND ... = 1 — daca variabila globala nu exista sau e 0 (implicit), nu se trimite niciun AllowanceCharge pe linie; discountul e vizibil pentru ANAF doar implicit, prin faptul ca PriceAmount * InvoicedQuantity = LineExtensionAmount (adica pretul e deja net). Nu am gasit unde sunt setate global aceste doua variabile (gnEFACTURA_XML_DISC_PLISTA_LINIE/_ART) — posibil optiuni de firma necautate in acest fisier; nu afecteaza concluzia principala.

Discount de document, separat: exista o eticheta euristica in C_IES_FORM (Iif('DISCOUNT' $ Upper(a.denumire) And a.pretftva < 0, 1, 0) As discount, xmlefactura.prg:230) care detecteaza randuri-pseudo-articol de discount/taxa (denumire contine "DISCOUNT", pret negativ) si le exclude din liniile normale (Scan For discount = 0, :901), agregandu-le separat intr-un cac:AllowanceCharge la nivel de document (:756-793, motiv "Discount", cod 95). Acesta e discountul de document, nu DISCOUNT_UNITAR pe linie — mecanism diferit de randul-sentinela Replicate('Z',20) folosit la tiparire (punctul 1).

Nicio validare care ar respinge discount > pret: n-am gasit nicio verificare explicita in xmlefactura.prg care sa blocheze o linie cu discount_unitar mai mare ca pretul (ar rezulta pretftva negativ pe linie individuala; codul are doar o corectie generala pentru pretftva < 0 la nivel de linie completa, xmlefactura.prg:236-239, care inverseaza semnul cantitate/pret — nu specifica pentru discount).

3. SAF-T (D406)

Concluzie: nu exista implementare SAF-T/D406 in ROAFACTURARE. Cautat explicit:

  • Grep 'SAF-T|SAFT|D406|declaratie406|GenereazaSAFT|GenereazaD406' in tot COMUN\programe si COMUN — nicio potrivire pe un generator de fisier D406/SAF-T XML.
  • Glob '**/*saft*' pe tot proiectul si pe COMUN — zero fisiere cu "saft" in nume.
  • Singurele aparitii reale (nu fals-pozitive de tip "safe") sunt:
    • oinit_optiuni.prg:523-524: gl406 = (TYPE('gnD406') = 'N' and m.gnD406 = 1) — un flag "firma are activata SAFT 406", comentat "Firma are activata SAFT 406".
    • oproceduri_comune.prg:889-896,6083,6151, ointroduceri.prg:1597: saft_taxtable si saft_mecanisme_plati — tabele de nomenclator (coduri de TVA/mecanism de plata compatibile SAF-T) folosite pe partea de achizitii (limitare deducere TVA la introducerea facturilor de achizitie), nu pe vanzari/facturare.
    • oproceduri_comune.prg:6066-6067,6137-6138,6195: comentarii care mentioneaza "Taxa SAFT tip" ca denumire pentru codurile de TVA, tot pe achizitii.
  • Niciuna din aceste aparitii nu are legatura cu DISCOUNT/DISCOUNT_UNITAR — cautarea DISCOUNT in fisierele care contin "saft" (oproceduri_comune.prg, ointroduceri.prg, pmenu.prg, oinit_optiuni.prg) nu a gasit nicio intersectie intre cele doua seturi de rezultate.

Interpretare: gl406 pare sa activeze doar validari/coduri suplimentare pentru conformitate SAF-T pe partea de achizitii (poate pentru ca alt produs din suita, ROACONT, genereaza efectiv D406 din datele contabile) — ROAFACTURARE nu produce singur un fisier D406.

4. Alte consumatori care ar presupune discount uniform pe document

Nu am gasit vreun raport/export care sa presupuna un discount unic pe tot documentul in sensul de "aceeasi valoare/procent pe toate liniile" — mecanismul de discount de document (VANZARI.DISCOUNT, randul Replicate('Z',20)) e tratat ca valoare agregata separata, adaugata ca linie proprie (in tiparire) sau ca AllowanceCharge de document (in eFactura), nu redistribuita implicit pe liniile existente — cu o exceptie: xmlefactura.prg are logica de distribuire explicita a discountului/taxelor de document pe articole cand bifa chkDistribuieDiscount/ llDistribuieDiscountTaxe e activa (xmlefactura.prg:12189-12320, :12534-12790) — asta insa opereaza pe discountul de document, nu presupune ca discountul de linie (DISCOUNT_UNITAR) e uniform; dimpotriva, distribuie proportional cu valoarea fiecarei linii, deci suporta discount diferit per linie din start.

Ce ar strica S4c, concret

Nu se strica codul rapoartelor sau al eFacturii — ambele citesc valori finale din cursor (valdiminuatftva, discountftva, pretftva deja net), indiferent de sursa UI a discountului.

Se poate strica invariantul pe care se bazeaza, daca editarea in grid nu recalculeaza:

  • Dovada ca gaura exista deja azi: pe factura in valuta, coloana vdiscountftva e editabila direct in grid (ofacturare.vc2:12340-12345, fara ReadOnly) dar fara niciun handler Valid/InteractiveChange care sa recalculeze vvaldiminuatftva/totalurile — confirmat cautat explicit in docs\cercetare\discount_verificare2.md punctul 3, ultimul paragraf ("editarea directa in grid NU declanseaza recalcularea automata a vvaldiminuatftva/totaluri, doar modifica valoarea bruta in crsfactura").
  • De ce conteaza pentru rapoarte/eFactura: valdiminuatftva/valdiminuatctva (nu discount_unitar brut) sunt campurile citite de prelucreaza_factura/creeaza_crsfacttemp pentru valftva tiparit si trimis la ANAF (punctele 1 si 2 de mai sus). Daca operatorul modifica discountul direct in grid si acel camp nu se recalculeaza, factura tiparita si XML-ul ANAF vor arata valoarea veche (dinainte de editare) — o discrepanta reala intre ce vede operatorul in grid si ce se tipareste/trimite.
  • Concluzie pentru plan: daca S4c extinde editarea de discount la toate coloanele din grid (inclusiv discountctva, azi read-only pe lei), trebuie cablat un recalcul echivalent cu frm_articol_factura.do_calculeaza_discount (ofacturare.vc2:1874-1976) pe evenimentul de editare din grid — altfel riscul de mai sus (deja prezent azi doar pe valuta) se extinde la toate facturile in lei.

Ramas de verificat

  • Valorile implicite ale flag-urilor globale gnEFACTURA_XML_DISC_PLISTA_LINIE si gnEFACTURA_XML_DISC_PLISTA_ART (unde sunt setate ca optiune de firma) — nu am cautat sursa lor, doar am confirmat ca cu TYPE(...) <> 'N' (nedefinite) comportamentul e "fara AllowanceCharge pe linie".
  • Nu am verificat pe date reale (Oracle/XML generat) un caz cu discount_evidentiat=1 si DISCOUNT_UNITAR populat simultan, ca sa confirm empiric (nu doar din citirea codului) ca factura tiparita arata pretul brut.
  • Nu am urmarit crsfacturafinalaval (varianta valuta a cursorului final) linie cu linie — am presupus ca urmeaza acelasi patron ca crsfacturafinala/crsfacttemp (cursorul in lei), pe baza simetriei campurilor vpretftva/vvalftva/vdiscountftva gasite deja documentate in discount_verificare2.md si discount_pe_articol.md; n-am reverificat separat sursa SQL pentru varianta valuta.
  • Nu am identificat un al doilea produs (ROACONT?) care sa genereze efectiv D406 din datele scrise de ROAFACTURARE — presupunerea din sectiunea 3 e speculativa, nu confirmata cu cod.