#13: formular de facturare unificat - etapa curenta

Squash al branch-ului de lucru plan13-s2.

Cod: clasa noua ofacturare_util (geometrie si utilitare de formular), inrolata in
roafacturare.prg; meniul "Facturare (nou)" plat, cu reparatia literalului peste
limita VFP de 255 de caractere care impiedica compilarea metodei.

Livrare: changelog 2.11.19, marcajul de versiune DB pentru scripturile finale din
SCRIPTURI_CLAR\2026\09, inventarul livrarii in docs/livrare_13.md si registrul de
erori deschise. Documentatia de lucru a etapei (cercetare, rapoarte, handoff-uri,
snapshot-uri de scripturi si backup-uri de rollback) a fost scoasa din arbore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkyjGyrV2S7932om4kfSiK
This commit is contained in:
2026-09-09 21:30:51 +03:00
parent 87063b06a8
commit 104c24ec20
80 changed files with 5585 additions and 27852 deletions

View File

@@ -119,6 +119,12 @@ Depanare detaliata: `COMUN\docs\depanare_testare_vfp.md`.
## Mod de lucru: delegare catre subagenti + review inainte de commit
> **REGULA OBLIGATORIE (ceruta explicit de Marius, 26.08.2026):** orice investigatie,
> modificare de cod, editare de fisiere, rulare de teste sau verificare se deleaga unui
> SUBAGENT (Task tool). Sesiunea principala DOAR orchestreaza si verifica rezultatele
> subagentilor; nu lucreaza direct pe fisiere, nu ruleaza suite, nu editeaza cod. Inca
> de la prima sarcina a unui bloc, nu dupa ce ai acumulat context.
- **Predarea contextului e OBLIGATORIE, si pentru sesiunea principala, si pentru subagenti.**
Nu e o optimizare, e o conditie de corectitudine. Declansatoare: **~50% din fereastra**
(sesiunea principala / orchestrator), **~200-250k tokens** (subagent), anuntul de compactare
@@ -140,3 +146,43 @@ Depanare detaliata: `COMUN\docs\depanare_testare_vfp.md`.
iar sesiunea principala doar orchestreaza si verifica rezultatele.
- **Fara commit fara review**: nu da commit (git sau svn) din proprie initiativa pe modificari de
cod — diff-ul se livreaza intai ca fisier in `docs/`, iar commit-ul vine dupa aprobare.
### Executie continua a unui plan (mandat permanent, Marius, 30.08.2026)
> „vreau sa continui toate stories, cu commit dupa fiecare, si handoff la limita de context”
Cand lucrezi la un plan cu mai multe stories, mandatul e valabil pana la revocare explicita:
1. **Toate stories**, in ordinea de dependente din fisierul de executie al planului — fara oprire
dupa fiecare ca sa intrebi „continuam?”.
2. **Commit dupa fiecare story**, pe branch-ul de lucru, in repo-ul potrivit (cod `COMUN` din
`COMUN\`, restul din radacina proiectului). Review inainte de commit, dar **fara a mai cere
aprobare** — aprobarea e data pentru intreg lantul. Fara push; SVN si merge-ul raman la Marius.
3. **Handoff la limita de context** — vezi Regula zero de mai sus. Fisierul de stare viu al
planului se actualizeaza dupa FIECARE story inchisa, nu la final.
Corolar cand Marius nu poate testa manual: pentru fiecare story al carui criteriu oficial de „gata”
e o proba manuala UI, se proiecteaza si o **proba automata de paritate** (headless sau prin
`vfp_ui_harness.ps1`), iar ce ramane obligatoriu manual se inregistreaza explicit ca **datorie
deschisa** — nu se declara „gata”.
## Ponytail (plugin la nivel de user)
Plugin-ul tert `ponytail` e activ implicit pe nivelul `full` la fiecare sesiune si reinjectat in
fiecare subagent — nu doar in sesiunea principala. Nu ruleaza retea si nu trimite telemetrie: scrie
doar un flag local (`~/.claude/.ponytail-active`) si citeste `~/.claude/settings.json`.
Se pastreaza scara YAGNI/reuse-first (nu scrii ce exista deja in `inventar-comun.md`,
`COMUN\programe`, `COMUN\clase`, sau ce face VFP/Oracle nativ) — se suprapune cu regula
proiectului de modificari minime, SCOPED.
Ce NU se aplica aici, cu regula proiectului care primeaza (detaliu complet:
`COMUN\docs\reguli_lucru.md`, punctul 3, „Scara ponytail”):
- comentarii `ponytail:` in cod -> interzise (regula 2); scurtatura si limita ei merg in
`docs\progres.md`, nu inline.
- proba „runnable test / assert self-check” -> script headless (regulile 4 si 8), nu `test_*.py`.
- „cod intai, maxim trei randuri” -> nu se aplica handoff-urilor, rapoartelor din `docs\` si
patch-urilor, care sunt output cerut explicit.
- `/ponytail-review` si `/ponytail-audit`: `delete:`/`yagni:` sunt ipoteze, nu constatari — in
`COMUN` cauti apelantii in tot `D:\ROA` inainte sa stergi; audit pe tot arborele nu se ruleaza aici.
- nivelul `ultra` nu se foloseste pe cod legacy partajat.

136
Clase/ofacturare_util.vc2 Normal file
View File

@@ -0,0 +1,136 @@
*--------------------------------------------------------------------------------------------------------------------------------------------------------
* (EN) AUTOGENERATED - ATTENTION!! - NOT INTENDED FOR EXECUTION!! USE ONLY FOR MERGING CHANGES AND STORING WITH SCM TOOLS!!
*--------------------------------------------------------------------------------------------------------------------------------------------------------
*< FOXBIN2PRG: Version="1.21" SourceFile="ofacturare_util.vcx" CPID="1252" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
*
*
DEFINE CLASS frm_cursuri_valutare AS _frmbase OF "_frm_base.vcx"
*< CLASSDATA: Baseclass="form" Timestamp="" Scale="Pixels" Uniqueid="" />
*-- OBJECTDATA items order determines ZOrder / El orden de los items OBJECTDATA determina el ZOrder
*< OBJECTDATA: ObjPath="grd_cursuri" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grd_cursuri.cCurs.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grd_cursuri.cCurs.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grd_cursuri.cNume_val.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grd_cursuri.cNume_val.Text1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grd_cursuri.cMultiplicator.Header1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="grd_cursuri.cMultiplicator.Text1" UniqueID="" Timestamp="" />
*<PropValue>
AutoCenter = .T.
Caption = "Cursuri valutare"
DoCreate = .T.
Height = 224
Name = "frm_cursuri_valutare"
Width = 420
WindowType = 1
_shape1.Name = "_shape1"
_shape1.Width = 422
_shape2.Left = 370
_shape2.Name = "_shape2"
Lb_titlu_alb_b121.Caption = "Curs valutar"
Lb_titlu_alb_b121.FontBold = .T.
Lb_titlu_alb_b121.Name = "Lb_titlu_alb_b121"
BUT_TERMIN1.Left = 390
BUT_TERMIN1.Name = "BUT_TERMIN1"
*</PropValue>
ADD OBJECT 'grd_cursuri' AS _grdrow WITH ;
ColumnCount = 3, ;
DeleteMark = .F., ;
FontName = "Arial Narrow", ;
Height = 180, ;
Left = 12, ;
Name = "grd_cursuri", ;
ScrollBars = 2, ;
TabIndex = 1, ;
Top = 32, ;
Width = 396, ;
Column1.ColumnOrder = 3, ;
Column1.ControlSource = "curs", ;
Column1.FontName = "Arial Narrow", ;
Column1.InputMask = (get_mask(14,gnPCurs)), ;
Column1.Name = "cCurs", ;
Column1.Width = 102, ;
Column2.ColumnOrder = 2, ;
Column2.ControlSource = "nume_val", ;
Column2.FontName = "Arial Narrow", ;
Column2.Name = "cNume_val", ;
Column2.Width = 130, ;
Column3.ColumnOrder = 1, ;
Column3.ControlSource = "multiplicator", ;
Column3.FontName = "Arial Narrow", ;
Column3.InputMask = (get_mask(10,0)), ;
Column3.Name = "cMultiplicator", ;
Column3.Width = 130
*< END OBJECT: ClassLib="_grd_base.vcx" BaseClass="grid" />
ADD OBJECT 'grd_cursuri.cCurs.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "Curs", ;
FontName = "Arial Narrow", ;
Name = "Header1"
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grd_cursuri.cCurs.Text1' AS textbox WITH ;
BackColor = 255,255,255, ;
BorderStyle = 0, ;
FontName = "Arial Narrow", ;
ForeColor = 0,0,0, ;
Margin = 0, ;
Name = "Text1", ;
SelectedBackColor = 0,64,128
*< END OBJECT: BaseClass="textbox" />
ADD OBJECT 'grd_cursuri.cMultiplicator.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "Multiplicator", ;
FontName = "Arial Narrow", ;
Name = "Header1"
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grd_cursuri.cMultiplicator.Text1' AS textbox WITH ;
BackColor = 255,255,255, ;
BorderStyle = 0, ;
FontName = "Arial Narrow", ;
ForeColor = 0,0,0, ;
Margin = 0, ;
Name = "Text1", ;
SelectedBackColor = 100,185,255
*< END OBJECT: BaseClass="textbox" />
ADD OBJECT 'grd_cursuri.cNume_val.Header1' AS header WITH ;
Alignment = 2, ;
Caption = "Valuta", ;
FontName = "Arial Narrow", ;
Name = "Header1"
*< END OBJECT: BaseClass="header" />
ADD OBJECT 'grd_cursuri.cNume_val.Text1' AS textbox WITH ;
BackColor = 255,255,255, ;
BorderStyle = 0, ;
FontName = "Arial Narrow", ;
ForeColor = 0,0,0, ;
Margin = 0, ;
Name = "Text1", ;
SelectedBackColor = 0,64,128
*< END OBJECT: BaseClass="textbox" />
PROCEDURE Init
Lparameters tcCursor, tdZiCurs, tnTip
Local lcCursor
DoDefault()
lcCursor = Iif(Empty(tcCursor), 'crscursuri', tcCursor)
If !Used(m.lcCursor) Or Reccount(m.lcCursor) = 0
This.Release()
Return .F.
Endif
This.grd_cursuri.RecordSource = m.lcCursor
If !Empty(Nvl(m.tdZiCurs, {}))
This.Lb_titlu_alb_b121.Caption = "Curs valutar (" + Dtoc(m.tdZiCurs) + ")"
Else
This.Lb_titlu_alb_b121.Caption = "Curs valutar"
Endif
ENDPROC
ENDDEFINE

View File

@@ -32,11 +32,14 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
*< OBJECTDATA: ObjPath="Page2.Cw8" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page2.Pict_liniuta2" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page2.Cw9" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page2.Cw10" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page2.Pict_liniuta1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page3.Pict_meniu1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page3.Cw1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page3.Cw4" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page3.Cw3" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page3.Cw2" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page3.Cw5" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page4.Pict_meniu1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page4.Cw1" UniqueID="" Timestamp="" />
*< OBJECTDATA: ObjPath="Page4.Cw4" UniqueID="" Timestamp="" />
@@ -244,6 +247,16 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
Label_item1.WordWrap = .T.
*< END OBJECT: ClassLib="..\comun\clase\ofundal.vcx" BaseClass="container" />
ADD OBJECT 'Page2.Cw10' AS cw WITH ;
Left = 5, ;
Name = "Cw10", ;
nid_cw = 10, ;
TabIndex = 10, ;
Top = 250, ;
Label_item1.Caption = "Facturare (nou)", ;
Label_item1.Name = "Label_item1"
*< END OBJECT: ClassLib="..\comun\clase\ofundal.vcx" BaseClass="container" />
ADD OBJECT 'Page2.Cw2' AS cw WITH ;
Left = 5, ;
Name = "Cw2", ;
@@ -348,6 +361,13 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
LABEL_ITEM1.WordWrap = .T.
*< END OBJECT: ClassLib="..\comun\clase\ofundal.vcx" BaseClass="container" />
ADD OBJECT 'Page2.Pict_liniuta1' AS pict_liniuta WITH ;
Left = 6, ;
Name = "Pict_liniuta1", ;
Picture = ..\comun\grafice\f3.jpg, ;
Top = 239
*< END OBJECT: ClassLib="..\comun\clase\ofundal.vcx" BaseClass="image" />
ADD OBJECT 'Page2.Pict_liniuta2' AS pict_liniuta WITH ;
Left = 6, ;
Name = "Pict_liniuta2", ;
@@ -411,6 +431,16 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
Label_item1.WordWrap = .T.
*< END OBJECT: ClassLib="..\comun\clase\ofundal.vcx" BaseClass="container" />
ADD OBJECT 'Page3.Cw5' AS cw WITH ;
Left = 5, ;
Name = "Cw5", ;
nid_cw = 5, ;
TabIndex = 5, ;
Top = 106, ;
Label_item1.Caption = "Avize (nou)", ;
Label_item1.Name = "Label_item1"
*< END OBJECT: ClassLib="..\comun\clase\ofundal.vcx" BaseClass="container" />
ADD OBJECT 'Page3.Pict_meniu1' AS pict_meniu WITH ;
Height = 473, ;
Left = 2, ;
@@ -619,6 +649,9 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
_grdrow1.cAgent.Header1.Name = "Header1", ;
_grdrow1.cAgent.Name = "cAgent", ;
_grdrow1.cAgent.Text1.Name = "Text1", ;
_grdrow1.cCodFiscal.Header1.Name = "Header1", ;
_grdrow1.cCodFiscal.Name = "cCodFiscal", ;
_grdrow1.cCodFiscal.Text1.Name = "Text1", ;
_grdrow1.cComanda_externa.Header1.Name = "Header1", ;
_grdrow1.cComanda_externa.Name = "cComanda_externa", ;
_grdrow1.cComanda_externa.Text1.Name = "Text1", ;
@@ -641,6 +674,9 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
_grdrow1.cFacturat.Name = "cFacturat", ;
_grdrow1.cFacturat._checkbox1.Alignment = 2, ;
_grdrow1.cFacturat._checkbox1.Name = "_checkbox1", ;
_grdrow1.cIdPart.Header1.Name = "Header1", ;
_grdrow1.cIdPart.Name = "cIdPart", ;
_grdrow1.cIdPart.Text1.Name = "Text1", ;
_grdrow1.cLucrare.Header1.Name = "Header1", ;
_grdrow1.cLucrare.Name = "cLucrare", ;
_grdrow1.cLucrare.Text1.Name = "Text1", ;
@@ -690,6 +726,9 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
_grdrow2.cNrCrt.Header1.Name = "Header1", ;
_grdrow2.cNrCrt.Name = "cNrCrt", ;
_grdrow2.cNrCrt.Text1.Name = "Text1", ;
_grdrow2.cNumeListaPreturi.Header1.Name = "Header1", ;
_grdrow2.cNumeListaPreturi.Name = "cNumeListaPreturi", ;
_grdrow2.cNumeListaPreturi.Text1.Name = "Text1", ;
_grdrow2.cPret.Header1.Name = "Header1", ;
_grdrow2.cPret.Name = "cPret", ;
_grdrow2.cPret.Text1.Name = "Text1", ;
@@ -697,6 +736,9 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
_grdrow2.cPret_cu_tva.Name = "cPret_cu_tva", ;
_grdrow2.cPret_cu_tva._checkbox1.Alignment = 2, ;
_grdrow2.cPret_cu_tva._checkbox1.Name = "_checkbox1", ;
_grdrow2.cPTVA.Header1.Name = "Header1", ;
_grdrow2.cPTVA.Name = "cPTVA", ;
_grdrow2.cPTVA.Text1.Name = "Text1", ;
_grdrow2.Height = 367, ;
_grdrow2.Left = 173, ;
_grdrow2.Name = "_grdrow2", ;
@@ -734,7 +776,8 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
Cmd_executa4.Top = 425, ;
But_factura1.Name = "But_factura1", ;
BUT_START_CRITERII1.Name = "BUT_START_CRITERII1", ;
BUT_RESET_CRITERII1.Name = "BUT_RESET_CRITERII1"
BUT_RESET_CRITERII1.Name = "BUT_RESET_CRITERII1", ;
But_attach1.Name = "But_attach1"
*< END OBJECT: ClassLib="..\comun\clase\ocomenzi.vcx" BaseClass="container" />
ADD OBJECT 'Page6.Ct_lucrari_detalii1' AS ct_lucrari_detalii WITH ;
@@ -883,6 +926,45 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
DO facturare_lista_de_preturi IN oproceduri_facturare.prg
ENDPROC
PROCEDURE Page2.Cw10.do_actiune
Private plFacturareNoua
plFacturareNoua = .T.
lcOptiuni = 'In lei;Retur factura in lei;Restaurant;Marfa in custodie - fara descarcare K;Marfa in custodie - cu descarcare K;\-;Invoice;Credit note;Factura fiscala in valuta;Retur factura in valuta'
lcOptiuni = m.lcOptiuni + ';\-;Contract: Factura lei;Contract: Invoice;Contract: Factura fiscala in valuta;\-;Pe baza de comanda;Din aviz'
lnOptiune = xmenu(m.lcOptiuni)
DO CASE
CASE m.lnOptiune = 1
factureaza(1)
CASE m.lnOptiune = 2
factureaza(8)
CASE m.lnOptiune = 3
factureaza(45)
CASE m.lnOptiune = 4
factureaza(49)
CASE m.lnOptiune = 5
factureaza(48)
CASE m.lnOptiune = 7
factureaza(5)
CASE m.lnOptiune = 8
factureaza(7)
CASE m.lnOptiune = 9
factureaza(10)
CASE m.lnOptiune = 10
factureaza(9)
CASE m.lnOptiune = 12
DO facturare_contracte WITH 'FACTURA LEI' IN oproceduri_facturare.prg
CASE m.lnOptiune = 13
DO facturare_contracte WITH 'INVOICE' IN oproceduri_facturare.prg
CASE m.lnOptiune = 14
DO facturare_contracte WITH 'FACTURA VALUTA' IN oproceduri_facturare.prg
CASE m.lnOptiune = 16
goComanda = ''
DO facturare_comenzi IN oproceduri_facturare.prg
CASE m.lnOptiune = 17
DO facturare_avize IN oproceduri_facturare.prg
ENDCASE
ENDPROC
PROCEDURE Page2.Cw2.do_actiune
lnOptiune = xmenu('Factura lei;Invoice;Factura fiscala in valuta')
DO CASE
@@ -941,6 +1023,22 @@ DEFINE CLASS pg_meniu_princ AS pg_meniu OF "..\comun\clase\ofundal.vcx"
DO aviz_subunitati.mpr
ENDPROC
PROCEDURE Page3.Cw5.do_actiune
Private plFacturareNoua
plFacturareNoua = .T.
lnOptiune = xmenu('C<>tre clien<65>i;C<>tre clien<65>i <20>n custodie;C<>tre clien<65>i debitori;Transfer <20>ntre subunit<69><74>i')
DO CASE
CASE m.lnOptiune = 1
This.Parent.Cw1.do_actiune()
CASE m.lnOptiune = 2
This.Parent.Cw2.do_actiune()
CASE m.lnOptiune = 3
This.Parent.Cw3.do_actiune()
CASE m.lnOptiune = 4
This.Parent.Cw4.do_actiune()
ENDCASE
ENDPROC
PROCEDURE Page4.Cw1.do_actiune
lnOptiune = xmenu('Vizualizare \<standard;Vizualizare \<experimentala (mai rapida)')
If Empty(m.lnOptiune)

View File

@@ -135,6 +135,7 @@ SET CLASSLIB TO ofacturare_comun.vcx additive
SET CLASSLIB TO omodificari.vcx additive
SET CLASSLIB TO accessibility.vcx additive
SET CLASSLIB TO ocriterii.vcx ADDITIVE
SET CLASSLIB TO ofacturare_util.vcx ADDITIVE
Set Classlib To ctl32_statusbar.vcx Additive
Set Classlib To ctl32_common.vcx Additive
Set Classlib To ctl32_structs.vcx Additive
@@ -180,6 +181,7 @@ SET CLASSLIB TO overificari ADDITIVE
*** COMENZI
SET CLASSLIB TO ocomenzi ADDITIVE
SET CLASSLIB TO _cb_base.vcx ADDITIVE
************************************************************************************************
&& PROCEDURI ORACLE
@@ -210,8 +212,10 @@ Set Procedure To oexport.prg Additive
Set Procedure To validare.prg Additive
***
Set Procedure To ofacturare_comun.prg Additive
Set Procedure To ofacturare_antet.prg Additive
Set Procedure To ofacturare_stoc.prg Additive
Set Procedure To ofacturare_editare.prg Additive
Set Procedure To ofacturare_rutare_scriere.prg Additive
SET PROCEDURE TO pmenu.prg additive
SET PROCEDURE TO ointroduceri.prg additive
SET PROCEDURE TO ovariabile_globale.prg additive

View File

@@ -1,3 +1,52 @@
<!--
09/09/2026
ROAFACTURARE - 2.11.19
:modificare:
Se lucreaza la un model nou de formular de facturare unificat.
-->
<!--
08/09/2026
ROAFACTURARE - 2.11.18
:nou:
In formularul nou de editare articole, cautarea in linie functioneaza acum si la documentele de retur.
:eroare:
In formularul nou de editare articole, adaugarea unui articol care nu are o politica de pret asociata bloca salvarea documentului cu o eroare. Corectata - articolul primeste automat contul de venit potrivit din nomenclator.
La documentele de retur cu doua cote de TVA diferite, salvarea putea esua cu o eroare. Corectata.
La facturarea avizelor mai vechi, lipsa pretului de achizitie de pe unele linii putea bloca salvarea documentului. Corectata.
Coloana de jurnal TVA a unui articol nou adaugat in formularul nou de editare articole ramanea uneori necompletata. Corectata.
-->
<!--
04/09/2026
ROAFACTURARE - 2.11.17
:nou:
Facturi, Avize. In lista de facturi apare optiunea noua "Editare unificata articole (nou)", care permite modificarea articolelor unui document deja emis - cantitati, preturi, discount, adaugare si stergere de linii - pentru majoritatea tipurilor de document (facturi, avize, transferuri, retururi, custodie). La salvare, documentul vechi se anuleaza si se reemite cu acelasi numar de factura sau aviz. La modificarea cantitatii unui articol deja adaugat, plafonul disponibil (stoc sau limita de contract/comanda) se verifica acum corect, la fel ca la adaugarea unei linii noi. Optiunea veche de modificare ramane neschimbata si e in continuare singura disponibila cand se selecteaza mai multe documente deodata.
:modificare:
In formularul nou de editare articole, discountul pe linie se introduce direct in grid, pe oricare din cele trei coloane (suma cu TVA, suma fara TVA sau procent) - celelalte doua se recalculeaza automat.
:eroare:
In formularul nou de editare articole, coloana de procent discount era vizibila si se putea completa, dar valoarea introdusa nu se salva niciodata si nu avea niciun efect asupra facturii. Corectata.
La reemiterea unui document modificat, data si utilizatorul crearii initiale se pierdeau, fiind inlocuite cu data si utilizatorul modificarii. Corectata - data si utilizatorul crearii raman cele originale, iar data si utilizatorul ultimei modificari se retin separat.
-->
<!--
21/08/2026
ROAFACTURARE - 2.11.16
:nou:
Facturare, Avize. Butoanele "Facturare (nou)" si "Avize (nou)" deschid formularul unificat de articole (prototip, in lucru), in paralel cu facturarea existenta.
-->
<!--
20/08/2026
ROAFACTURARE - 2.11.15

View File

@@ -1,222 +0,0 @@
# Cercetare: coloane de audit pe VANZARI / VANZARI_DETALII (creare/modificare/stergere)
Status: COMPLET (read-only)
## Intrebare
Marius cere pentru audit, pe documentele de facturare: data crearii, utilizatorul crearii, data
modificarii si utilizatorul modificarii (daca e cazul), data stergerii si utilizatorul stergerii
(daca e cazul).
## 1. Ce exista deja (structura DB)
Interogat direct `all_tab_columns` pe `ROA_CENTRAL` (schema live).
**VANZARI** — coloane relevante pentru audit:
| Coloana | Tip | Nullable | Rol |
|---|---|---|---|
| `ID_UTIL` | NUMBER | NOT NULL | utilizatorul crearii — completat la fiecare INSERT |
| `DATAORA` | DATE | NOT NULL | data/ora crearii — completat la fiecare INSERT |
| `STERS` | NUMBER | NOT NULL | flag sters (0/1) |
| `ID_UTILS` | NUMBER | NULL | utilizatorul care a sters — completat doar la stergere |
| `DATAORAS` | DATE | NULL | data/ora stergerii — completata doar la stergere |
| `DATA_ACT` | DATE | NULL | **data contabila/de inregistrare** (propaga in `ACT`, `DOCUMENTE`, `JV2007`, `RUL`), NU e "data modificarii" — vezi sectiunea 2 |
| `DATA_FACTURAT`, `ID_UTILFACT` | DATE / NUMBER | NULL | specifice actiunii "facturare din aviz", nu audit general pe document |
| `DATAORA_EXP` | DATE | NOT NULL | data expedierii/listarii, nu e audit de scriere |
| `DATAORA_DESCARCAT` | DATE | NULL | data descarcarii gestiunii, nu e audit de scriere |
| `DATA_SCAD` | DATE | NULL | data scadenta, nimic de audit |
**Nu exista nicio coloana dedicata "utilizator modificare" / "data modificare"** pe `VANZARI`
(gen `ID_UTIL_MODIF` / `DATA_MODIF`). `DATA_ACT` a fost verificata explicit in cod
(`EXPORT:14439-14500`, `modifica_date_factura`) si e o data contabila propagata in `ACT`/`DOCUMENTE`/
`JV2007`/`RUL`, nu un marcaj de audit "cine/cand a modificat".
**Conventia casei (adaugat, verificat de sesiunea principala):** `VANZARI` are deja tiparul **o
pereche utilizator+data per eveniment**: `ID_UTIL`/`DATAORA` (creare), `ID_UTILS`/`DATAORAS`
(stergere), `ID_UTILFACT`/`DATA_FACTURAT` (facturare din aviz — a treia pereche, omisa din
inventarul initial). Cu aceasta a treia pereche vizibila, tiparul e limpede: o pereche noua de
"modificare" (`ID_UTIL_MODIF`/`DATA_MODIF` sau echivalent) s-ar aseza natural langa celelalte trei,
ca nume si ca forma — nu ar fi o conventie noua, ci continuarea uneia deja existente.
**VANZARI_DETALII** — coloane relevante:
| Coloana | Tip | Nullable | Rol |
|---|---|---|---|
| `VALIDAT`, `ID_UTIL_VALID`, `DATAORA_VALID` | NUMBER/NUMBER/DATE | NULL pe ultimele doua | validare, alt concept decat creare |
| `STERS`, `ID_UTILS`, `DATAORAS` | NUMBER/NUMBER/DATE | NULL pe ultimele doua | stergere linie |
| `DATAORA_DESCARCAT` | DATE | NULL | descarcare gestiune |
**Pe `VANZARI_DETALII` nu exista nicio coloana de "creare"** (nici `ID_UTIL`, nici `DATAORA` proprii
liniei) — creatorul/data liniei se deduce indirect din antetul `VANZARI` al documentului parinte.
## 2. Cine scrie coloanele si cand
**Creare (`ID_UTIL`, `DATAORA` pe VANZARI):** scrise o singura data, la `INSERT INTO VANZARI`
din `PACK_FACTURARE.scrie_in_vanzari` (`EXPORT:13598-13640`), apelata pe drumul principal de emitere
(`scrie_factura2`, `EXPORT:6020` -> lantul de `finalizeaza_*`). Valorile vin din
`pack_facturare.nid_util` si `V_DATAORA` — utilizatorul si momentul sesiunii curente la INSERT.
Exista un al doilea `INSERT INTO VANZARI` (`EXPORT:14930`, in `finalizeaza_avize_lucrare`,
`EXPORT:14854-...`) — ruta specifica avizelor de lucrare, tot cu `ID_UTIL`/`DATAORA` completate la
INSERT. **Ambele rute de emitere completeaza consecvent aceste doua coloane** — nu s-a gasit niciun
INSERT in VANZARI care sa le lase NULL.
**Nu exista niciun `UPDATE VANZARI SET ID_UTIL = ...` sau `SET DATAORA = ...` in tot pachetul**
(cautare directa, zero rezultate) — deci, in afara de INSERT-ul initial, aceste doua coloane nu sunt
niciodata rescrise pe randul existent. Coerent cu design-ul de azi: singura cale de "modificare" a
antetului identitar e `modifica_date_factura` (care NU atinge `ID_UTIL`/`DATAORA`, doar serie/numar/
data/scadenta/delegat/etc., vezi `plan_13...md:796-801`), sau stergere+reemitere ca document nou.
**Stergere (`ID_UTILS`, `DATAORAS`, `STERS`):** scrise consecvent in ambele proceduri de stergere:
- `sterge_factura` (`EXPORT:5432-5607`): `UPDATE VANZARI SET STERS = V_STERS, ID_UTILS = V_ID_UTIL,
DATAORAS = V_DATAORA WHERE ID_VANZARE = ...` (`EXPORT:5496-5499`) si acelasi tipar pe
`VANZARI_DETALII` (`EXPORT:5560-5564`), pe `COMENZI_ELEMENTE` si `CTR_RATE_FACTURI` cand e cazul.
- `sterge_proforma` (`EXPORT:5610-...`): acelasi tipar (verificat header-ul procedurii; corpul
complet urmeaza acelasi model de `UPDATE ... SET STERS/ID_UTILS/DATAORAS`).
**Concluzie punct 2: coloanele existente sunt scrise consecvent** pe toate rutele identificate —
nu exista ruta de creare care sa lase `ID_UTIL`/`DATAORA` NULL, nici ruta de stergere care sa sara
peste `ID_UTILS`/`DATAORAS`.
**CORECTIE (verificata de sesiunea principala, nu de mine): afirmatia initiala "#6 nu atinge niciun
camp de audit" era gresita pentru `VANZARI_DETALII`.** Pe partea de nota contabila
(`pack_contafin.finalizeaza_modificare_nota`, apelat din `oscrie_in_fisiere`) ramane adevarat ca se
**realiniaza doar `VANZARI.COD`** prin `actualizeaza_vanzari`
(`COMUN\docs\flux-modificare-stergere-nota-jurnal.md:95`), iar `VANZARI.ID_FACT` "nu e atins de
niciun pas al secventei" (`:101-102`) — **dar** editarea liniilor de factura din #6
(`COMUN\programe\ofacturare_editare.prg`) **scrie** `id_utils`/`dataoras` pe `VANZARI_DETALII`, in
trei locuri:
- `ofacturare_editare.prg:492-494` — marcarea unei linii ca stearsa: `sters = 1` impreuna cu
`id_utils`/`dataoras`. Aici folosirea corespunde exact semanticii "stergere".
- `ofacturare_editare.prg:501-505` — `UPDATE vanzari_detalii SET sters = 0, cantitate = ...,
pret = ..., id_utils = ..., dataoras = sysdate` — perechea de "stergere" e scrisa **odata cu
`sters = 0`**, deci pe un rand **viu**, nesters.
- `ofacturare_editare.prg:522-525` — `INSERT INTO vanzari_detalii (..., id_utils, dataoras)
VALUES (...)` — perechea e populata pe un rand **proaspat inserat**, care nu a fost sters
niciodata.
**Concluzia corecta:** in codul livrat al lui #6, perechea `ID_UTILS`/`DATAORAS` pe
`VANZARI_DETALII` e folosita ca marcaj **"cine a umblat ultima data pe linia asta"**, nu strict ca
"sters de/la". **Consecinta pentru orice raport de audit:** `ID_UTILS`/`DATAORAS` NU pot fi citite
ca "sters de/la" fara sa se puna si `STERS` in conditie — altfel liniile adaugate sau modificate la
o editare (nesterse) apar gresit drept sterse. Pe `VANZARI` (antet), ramane adevarat ca #6 atinge
doar `COD` — nu s-a gasit nicio scriere pe `ID_UTIL`, `DATAORA`, `DATA_ACT`, `ID_UTILS` sau
`DATAORAS` la nivel de antet in acest flux.
## 3. Ce se intampla la regenerare (stergere + reemitere, #13 / S9)
**Important: acest mecanism NU e inca implementat.** Descrierea "stergere + reemitere" e planul
#13, sectiunea S9 (`docs\plan_13_unificare_formular_facturare.md:3484-3568`), marcata "PROIECTAT" /
"VERIFICAT ca e realizabila", nu cod livrat. Feature-ul aflat azi in lucru pe branch-ul curent (#6)
e alt mecanism (editare la nivel de linie de nota, sectiunea 2 mai sus), nu regenerare.
**Raspuns la intrebarea critica: DA, se pierde, exact cum ai suspectat.**
Mecanismul S9, asa cum e proiectat: documentul vechi primeste soft-delete (`sterge_factura`, ca
azi) -> `ID_UTILS`/`DATAORAS` ale randului **vechi** devin utilizatorul/momentul editarii (corect,
asta chiar e semantica lor). Documentul nou se scrie **pe acelasi drum de emitere ca la creare**
(`scrie_factura2` -> `scrie_in_vanzari` -> `INSERT INTO VANZARI`, sectiunea 2 de mai sus) — acelasi
`INSERT` care completeaza `ID_UTIL`/`DATAORA` din utilizatorul si momentul curente. **Niciun pas din
S9 nu citeste sau transporta `ID_UTIL`/`DATAORA` ale documentului vechi catre cel nou** — cautare
directa in tot planul (`ID_UTIL `, `DATAORA `) nu gaseste nicio mentiune a preservarii lor la
regenerare. Deci, cu proiectarea de azi a S9: **"data crearii" a documentului reemis devine data
regenerarii, iar "utilizatorul crearii" devine cel care a declansat editarea** — informatia despre
cine/cand a fost creat *initial* documentul se pierde tacut, exact temerea din brief.
**Atenuare (verificata de sesiunea principala):** pierderea nu e totala, ci **reconstruibila din
lant, nu direct pe document**. Randul vechi ramane in tabel cu `STERS = 1` si cu `ID_UTILS`/
`DATAORAS` completate — iar acea stergere **este** momentul modificarii. Cum `ID_FACT` se pastreaza
peste regenerare (sectiunea E/S9 mai jos), un raport de audit poate urca lantul `ID_FACT` -> gasi
randul vechi sters -> citi `DATAORA`/`ID_UTIL` de pe acela ca fiind "data/utilizator crearii
originale". Asta atenueaza, dar nu inlocuieste o pereche explicita: cere parcurgerea lantului de
randuri sterse in loc de o citire directa pe documentul curent, si se rupe daca vreodata `ID_FACT`
nu mai e pastrat identic (de exemplu la o a doua regenerare, daca lantul nu ramane liniar).
**Precedentul `ID_FACT` exista si e citat corect in brief, si arata ca problema e cunoscuta ca tipar
— dar rezolvata doar pentru `ID_FACT`, nu si generalizata la audit.** Planul dedica un mecanism
explicit ca sa evite pierderea lui `ID_FACT`:
- Sectiunea E (`:762-789`): cerinta explicita ("documentul reemis pastreaza `ID_FACT`"), verificarea
ca azi secventa l-ar regenera necondiționat, si decizia sa fie **citit din documentul vechi
inainte de stergere si impus** celui nou.
- S9 (`:3505-3538`): mecanismul concret — "`ID_FACT` se citeste inainte de stergere ... si se impune
documentului nou, printr-un comutator folosit numai de regenerare"; plus tot efortul de a ocoli
coliziunea `ORA-00001` pe `PK_DOCUMENTE` cand se refoloseste acelasi `ID_FACT`.
Acelasi tipar de mecanism ("citeste din vechi inainte de stergere, transporta explicit la INSERT-ul
nou") ar fi necesar si pentru `ID_UTIL`/`DATAORA` daca se vrea pastrata "data/utilizator creare
originala" — **dar acest pas nu exista nicaieri in plan azi**. Nu e o eroare de implementare, e un
gol de cerinta: planul #13 nu a fost scris cu "pastreaza si audit-ul de creare" ca obiectiv:
sectiunea 1 a acestui raport (`plan_13...md:1462-1467`) chiar **foloseste** `ID_UTILS`/`DATAORAS`
NULL ca dovada ca "nicio editare ulterioara nu s-a inregistrat" pe o factura din 2026 — ceea ce arata
ca autorii planului tratau deja `ID_UTILS`/`DATAORAS` (stergere) ca semnal indirect de "a fost
editat", dar fara sa discute explicit soarta lui `ID_UTIL`/`DATAORA` (creare) la regenerare.
## 4. Ce lipseste din cele sase cerute de Marius
| Cerut | Exista azi? | Observatie |
|---|---|---|
| Data crearii | DA — `VANZARI.DATAORA` | Scrisa consecvent la INSERT (sectiunea 2). Sub #13/S9 asa cum e proiectat azi, **s-ar suprascrie tacit la fiecare regenerare** (sectiunea 3) — nimic nu o transporta din documentul vechi. |
| Utilizatorul crearii | DA — `VANZARI.ID_UTIL` | Idem: scris consecvent la INSERT, dar **s-ar pierde la regenerare** sub #13/S9 asa cum e proiectat azi. |
| Data modificarii (daca e cazul) | **NU exista coloana dedicata pe antet (`VANZARI`)** | Nu exista `DATA_MODIF`/echivalent. `DATA_ACT` exista dar e data contabila, nu audit. Pe `VANZARI` (antet), #6 nu scrie nimic. **Pe `VANZARI_DETALII` (linie), #6 scrie `dataoras` chiar si pe randuri nesterse** (`ofacturare_editare.prg:501-505,522-525`) — semnal de "ultima atingere", dar suprapus peste semantica de stergere, nu o coloana proprie de modificare. Sub #13/S9, singurul semnal indirect pe antet ar fi `DATAORAS` a randului **vechi** (marcat sters) — reconstruibil prin lant (vezi atenuarea din sectiunea 3), nu direct pe documentul curent. |
| Utilizatorul modificarii (daca e cazul) | **NU exista coloana dedicata pe antet (`VANZARI`)** | Acelasi rationament — nu exista `ID_UTIL_MODIF`. Pe `VANZARI_DETALII`, #6 scrie `id_utils` si pe randuri nesterse (acelasi loc de mai sus), cu aceeasi suprapunere peste semantica de stergere. Pe antet, #13/S9 ar lasa doar `ID_UTILS` pe randul vechi (sters), reconstruibil prin lant, nu pe cel curent. |
| Data stergerii | DA — `VANZARI.DATAORAS` | Scrisa consecvent in `sterge_factura` si `sterge_proforma` (sectiunea 2). |
| Utilizatorul stergerii | DA — `VANZARI.ID_UTILS` | Idem, scris consecvent. |
**Rezumat:** 4 din 6 cerinte au deja coloana dedicata si scriere consecventa pe antet (creare x2,
stergere x2) — dar cele doua de "creare" sunt **fragile fata de regenerarea planificata in #13**,
riscand sa fie suprascrise silentios daca S9 nu adauga un pas explicit de transport (dupa modelul
deja folosit pentru `ID_FACT`), atenuat de faptul ca raman reconstruibile prin lant (sectiunea 3).
Cele doua de "modificare" **nu au coloana proprie pe antet**: pe `VANZARI` nici azi (#6 atinge doar
`COD`), nici in proiectarea #13 (regenerarea confunda "modificare" cu "creare noua" + "stergere
veche"); pe `VANZARI_DETALII`, #6 **reutilizeaza** `ID_UTILS`/`DATAORAS` ca semnal de "ultima
atingere" chiar pe linii nesterse — util ca indiciu, dar ambiguu fara `STERS` in conditie, si tot nu
e o pereche explicita de "modificare" pe care un raport sa o citeasca direct fara ambiguitate.
## Verificat direct vs dedus vs neacoperit
**Nota de provenienta:** sectiunile 1-2 si structura raportului sunt cercetarea mea initiala.
Corectia despre `ofacturare_editare.prg:492-494,501-505,522-525` (sectiunea 2, editarea #6 pe
`VANZARI_DETALII`), perechea `ID_UTILFACT`/`DATA_FACTURAT` (sectiunea 1) si atenuarea prin lant
`ID_FACT` (sectiunea 3) **au fost verificate si furnizate de sesiunea principala**, nu de mine — le-am
integrat ca atare, marcate explicit in text la locul lor.
**Verificat direct (citit in cod / rulat pe DB):**
- Structura `all_tab_columns` pentru `VANZARI` si `VANZARI_DETALII` (interogare live pe
`ROA_CENTRAL`, sectiunea 1) — a mea; reconfirmata independent de sesiunea principala, inclusiv
filtrarea explicita pe `%MODIF%` (zero rezultate).
- `INSERT INTO VANZARI` din `scrie_in_vanzari` (`EXPORT:13598-13640`) si al doilea din
`finalizeaza_avize_lucrare` (`EXPORT:14930`) — sursa lui `ID_UTIL`/`DATAORA` — a mea.
- Zero rezultate la cautarea `UPDATE VANZARI SET ... ID_UTIL/DATAORA` in tot pachetul — confirmat
prin grep direct pe fisierul export — a mea.
- `sterge_factura` complet (`EXPORT:5432-5607`) — scrierea `STERS`/`ID_UTILS`/`DATAORAS` pe
`VANZARI` (`:5496-5499`) si `VANZARI_DETALII` (`:5560-5564`) — a mea.
- `modifica_date_factura` (`EXPORT:14439-14500`) — confirmat ca `DATA_ACT` e propagata catre
`ACT`/`DOCUMENTE`/`JV2007`/`RUL`, deci e data contabila, nu audit — a mea.
- `COMUN\docs\flux-modificare-stergere-nota-jurnal.md:95,101-102` — editarea #6 (nota contabila)
atinge doar `VANZARI.COD`, nu `ID_FACT`, si nu s-a gasit nicio scriere pe coloanele de audit **de
antet** in acel flux — a mea, ramane corecta doar pentru `VANZARI`, nu pentru `VANZARI_DETALII`.
- `ofacturare_editare.prg:492-494, 501-505, 522-525` — scrierea `id_utils`/`dataoras` pe
`VANZARI_DETALII`, inclusiv pe randuri nesterse — **a sesiunii principale**, eu nu am citit acest
fisier (nu era in perimetrul cercetarii initiale, care s-a concentrat pe pachetul PL/SQL).
- Sectiunile E si S9 din `docs\plan_13_unificare_formular_facturare.md` (mecanismul de pastrare a
`ID_FACT`) si absenta oricarei mentiuni `ID_UTIL`/`DATAORA` in tot documentul (cautare directa,
singurele hit-uri sunt in alt context, sectiunea 1 din acest raport) — a mea.
**Dedus (nu verificat direct, dar sustinut de dovezile de mai sus):**
- Ca S9, DACA se implementeaza exact cum e proiectat azi in plan, ar suprascrie `ID_UTIL`/`DATAORA`
la regenerare — dedus din faptul ca reemiterea foloseste acelasi `INSERT INTO VANZARI` ca emiterea
normala, si niciun pas de transport nu e mentionat in plan. Nu exista inca implementare de rulat.
- `sterge_proforma` (`EXPORT:5610-...`) urmeaza acelasi tipar ca `sterge_factura` — verificat doar
header-ul si inceputul; nu am citit tot corpul procedurii linie cu linie (structura generala insa
se potriveste, fiind aceeasi familie de proceduri din acelasi pachet).
**Neacoperit:**
- Nu am verificat daca exista si alte cai de INSERT/UPDATE pe `VANZARI` in afara `PACK_FACTURARE`
(de exemplu `PACK_MIGRARE`, migrari istorice) care ar putea lasa `ID_UTIL`/`DATAORA` NULL sau
cu alta semantica pe date vechi — nu era in scopul intrebarii (audit pe fluxul curent).
`plan_13...md:844-847` mentioneaza ca a existat deja o cautare exhaustiva pe toate `UPDATE
VANZARI` (13 aparitii) in runda 7, dar cu alt scop (antet, nu audit) — nu am reluat-o eu.
- Nu am verificat daca exista rapoarte/ecrane in aplicatie care deja afiseaza vreuna din aceste
coloane catre utilizator (relevant pentru UX, nu pentru intrebarea de audit DB pusa aici).
- Nu am rulat interogari pe date reale (cate facturi au `ID_UTILS`/`DATAORAS` populate azi in
productie) — brief-ul cerea structura si rutele de scriere, nu statistici.

View File

@@ -1,146 +0,0 @@
# Cercetare: buton comutator creion/discheta (decizia 9, plan #13)
Verificat pe cod la 09.08.2026. Metoda: `vfp_symbols.ps1` (`-Class`, `-Find`, `-Where`, `-Grep -CodeOnly`)
peste indexul ROAFACTURARE, plus `Grep`/`Read` directe pe `.vc2`.
## 1. Clase de buton in `COMUN\clase\cmd_butoane.vc2` — salvare / modificare
Fisierul e o insiruire de `DEFINE CLASS ... AS buton OF "_cmd_base.vcx"` (butoane simple) si
`... AS cmd_buton OF "_cmd_base.vcx"` (variante). Doar doua clase au legatura directa:
- **`but_modifica`** — `cmd_butoane.vc2:184-198`
```
184: DEFINE CLASS but_modifica AS buton OF "_cmd_base.vcx"
188: caction = inainte_de_do_modifica
189: cpicturedown = modific_jos.bmp
190: cpictureup = modific_sus.bmp
193: Picture = ..\grafice\modific_sus.bmp
194: ToolTipText = "Modificare (CTRL+M)"
195: Visible = .F.
```
- **`but_salveaza`** — `cmd_butoane.vc2:340-352`
```
340: DEFINE CLASS but_salveaza AS buton OF "_cmd_base.vcx"
344: caction = do_salvare
345: cpicturedown = save_jos.bmp
346: cpictureup = save_sus.bmp
348: Picture = ..\grafice\save_sus.bmp
349: ToolTipText = "Salvare"
```
Nu exista o clasa `but_salvare` (fara "ea") — numele real e `but_salveaza`. Nicio alta clasa din
fisier (`but_reset`, `but_reface`, `but_retur`, `cmd_modifica` etc.) foloseste imagini de
discheta sau de creion.
## 2. Clasa efectiva a lui `but_modifica` in formularele de facturare
Cautat in `COMUN\clase\ofacturare_comun.vc2`, `COMUN\clase\ofacturare.vc2`,
`Clase\ofundal_facturare.vc2` (`ofacturare_comun.vc2` e cel corect; `Clase\ofacturare.vc2` din
prompt nu exista — clasa reala e in `COMUN\clase\ofacturare.vc2`).
- `ofacturare_comun.vc2:1424-1432` — `frm_facturi` (lista de facturi), `ADD OBJECT 'but_modifica1'
AS but_modifica WITH ... Picture = ..\grafice\modific_sus.bmp` (override explicit, egal cu
default-ul clasei). `caction` nu e suprascris pe instanta -> ramane `inainte_de_do_modifica` din
clasa. Metoda `inainte_de_do_modifica` (referita si in plan la J) e la
`ofacturare_comun.vc2:4925-4934` si azi deschide un `xmenu()`, nu comuta imaginea.
- `ofacturare_comun.vc2:1434-1443` — `But_modifica2` pe acelasi `frm_facturi`, tot `AS but_modifica`,
cu `caction = do_modifica_explicatie`; fara `Picture` propriu (mosteneste creionul).
- `COMUN\clase\ofacturare.vc2:4313, 11192, 19472, 22734` — patru instante `ADD OBJECT
'But_modifica1' AS but_modifica`, apartinand `frm_articole_compuse` (`:4212-5216`),
`frm_facturare_articole` (`:10968-15739` — formularul de compunere de azi), `frm_nomrute`
(`:19357-19551`) si `frm_rute` (`:22510-22821`). Niciuna nu are `Picture` pe instanta.
- **Nicio instanta `but_salveaza` / `but_salvare` gasita** in niciunul din cele trei fisiere
cautate. Formularele de facturare de azi nu au un buton dedicat de "salvare antet" — antetul se
salveaza azi prin `frm_modifica_factura` cu bife (`ofacturare_comun.vc2:5262-5774`, vezi si
planul, sectiunea I), nu printr-un buton `but_salveaza` separat.
Concluzie pe punctul 2: `but_modifica1`/`But_modifica2` de pe `frm_facturi` si toate cele patru
`But_modifica1` din `ofacturare.vc2` sunt instante ale clasei `but_modifica` din
`cmd_butoane.vc2`, fara suprascriere de comportament — ele raman butoane simple de deschidere, nu
comutatoare.
## 3. Precedent de schimbare a `Picture` la runtime (comutator modifica/salveaza)
**Exista precedent, si e exact tiparul cerut.** `COMUN\clase\rulaje.vc2`, metoda
`frm_rulaje.se_modifica_assign` (assign-method pe proprietatea `se_modifica` a formularului),
`rulaje.vc2:4716-4773`:
```
4745: If m.llEditMode && TREC IN MODUL EDITARE
4747: With Thisform.but_modifica1
4748: .cpicturedown = "save_jos.bmp"
4749: .cpictureup = "save_sus.bmp"
4750: .Picture = "save_sus.bmp"
4751: .ToolTipText = "Salvare"
4752: .Enabled = .T.
4753: .Refresh()
4754: Endwith
4755: Else
4760: With Thisform.but_modifica1
4761: .cpicturedown = "MODIFICA2.BMP"
4762: .cpictureup = "MODIFICA1.BMP"
4763: .Picture = "MODIFICA1.BMP"
4764: .ToolTipText = "Modificare"
4765: .Enabled = .T.
4766: ENDWITH
4769: Endif
```
`but_modifica1` pe `frm_rulaje` e instantiat `ADD OBJECT 'but_modifica1' AS but_modifica WITH ...`
(`rulaje.vc2:727-737`, clasa `but_modifica` din `cmd_butoane.vcx`, fara `Picture` propriu pe
instanta — pleaca de la creion, cf. clasa). Tiparul confirmat: la comutare se rescriu **impreuna**
`.cpicturedown`, `.cpictureup` **si** `.Picture` (nu doar `.Picture` — altfel hover-ul din
`buton.MouseEnter`/`MouseLeave`, `_cmd_base.vc2:88-97`, ar reveni la iconita veche la urmatorul
mouse-over), plus `.ToolTipText` si `.Refresh()`.
Nu exista alt precedent care sa comute intre creion si discheta pe acelasi buton; celelalte hit-uri
pe `.Picture =` gasite in suita (`baza.vc2`, `otouchscreen.vc2`, `ferestre_seturi_indicatori.vc2`
etc.) sunt fie hover MouseEnter/Leave standard (`this.Picture = this.cPictureDown/Up`), fie
schimbari de iconita fara legatura cu modificare/salvare (tab-uri, bife, animatii).
## 4. Imaginea de discheta pe disc si rezolvarea caii
Fisiere confirmate pe disc (`Get-ChildItem` recursiv in `D:\ROA\ROAFACTURARE`):
```
COMUN\grafice\save_sus.bmp
COMUN\grafice\save_jos.bmp
COMUN\grafice\modific_sus.bmp
COMUN\grafice\modific_jos.bmp
COMUN\grafice\modifica1.bmp
COMUN\grafice\modifica2.bmp
```
Nu exista niciun `save*.bmp`/`modific*.bmp` sub `D:\ROA\ROAFACTURARE\Grafice` (folderul propriu al
proiectului) — toate traiesc in `COMUN\grafice`.
Rezolvarea caii: clasa foloseste cale relativa la definirea proprietatii (`..\grafice\save_sus.bmp`,
rezolvata de VFP fata de `.vcx`-ul clasei la Init). Codul de runtime din `rulaje.vc2` seteaza insa
**nume de fisier fara cale** (`"save_sus.bmp"`, `"MODIFICA1.BMP"`), care se rezolva prin
`SET PATH` — verificat in `Programe\roafacturare.prg:85-108`: `lcPath` include atat
`gcAppPath + 'GRAFICE;'` cat si `gcAppPath + 'COMUN\GRAFICE;'`, `SET PATH TO &lcPath ADDITIVE`. Deci
o alocare `.Picture = "save_sus.bmp"` la runtime se rezolva corect, indiferent daca fisierul e in
`Grafice\` sau `COMUN\Grafice\`, atata timp cat numele fara cale e folosit (nu resursa inclusa in
EXE — nu s-a gasit nicaieri mecanism de resurse compilate pentru aceste bmp-uri).
## 5. Metoda de biblioteca `do_activeaza`/`do_dezactiveaza` pe clase de buton
**Neverificat -> verificat, raspuns: nu exista pe clasele de buton.** Cautat in
`COMUN\clase\_cmd_base.vc2` (clasele `_cmdbase`, `buton`, `cmd_buton`) — nicio metoda
`do_activeaza`/`do_dezactiveaza`. Mecanismul exista doar pe clase de **container/camp**, nu de
buton, cf. si planului insusi (`docs\plan_13_unificare_formular_facturare.md`, sectiunea I):
`ct_clb_cautare.do_activeaza`/`do_dezactiveaza` (`caut_ora.vc2:780-806`, dar neapelate azi in
`ofacturare_comun.vc2`) si `clb_tx_data.dezactiveaza()`/`reactiveaza()` (`lb_tx.vc2:521-538`,
singura reteta completa: `ReadOnly` + `TabStop` + butonul de calendar). Pe butoane, controlul se
face direct pe `.Enabled`/`.Visible` (asa cum face si `se_modifica_assign` din rulaje.vc2 mai sus).
## Rezumat pentru implementare
- Clasa de folosit pentru discheta: `but_salveaza` (`cmd_butoane.vc2:340-352`) — dar tiparul din
`rulaje.vc2` **nu instantiaza a doua clasa**; comuta o singura instanta `but_modifica` intre cele
doua seturi de imagini prin `.cpicturedown`/`.cpictureup`/`.Picture`. E reteta direct aplicabila
cerintei din decizia 9.
- Numele de fisier de folosit: `save_sus.bmp` / `save_jos.bmp` (discheta), `modific_sus.bmp` /
`modific_jos.bmp` sau `MODIFICA1.BMP` / `MODIFICA2.BMP` (creion — doua perechi echivalente
coexista pe disc; `rulaje.vc2` foloseste a doua pereche, clasa foloseste prima).
- Fara cale in fata numelui de fisier — se rezolva prin `SET PATH`.

View File

@@ -1,515 +0,0 @@
# Exista un canal fara politica de pret catre ACT_TEMP.SCC? (proiect #13)
Cercetare read-only. Continua `cont_venit_articol_fara_politica.md`, `nota_contabila_fara_politica.md`,
`coresp_cont_venchelt.md`, `verif_baza_vie_cont_venit.md`.
**Mandat schimbat pe parcurs — vezi sectiunea imediat de mai jos.** Sectiunile de la
"Verdict, in cinci randuri" incolo sunt cercetarea rundei cu mandatul vechi ("gaseste un canal
ocolitor, nu se atinge `pack_facturare`") si raman ca input de proiectare (harta intrarilor spre
`SCD`/`SCC`, DDL, ce face `V_CONT`) — nu mai sunt raspunsul principal.
**HANDOFF (predare de context, nu sarcina noua):** o a treia sarcina a fost primita de la team-lead
dupa livrarea proiectarii de mai jos — verificarea celor 37 de linii de comanda cu articol nemembru
al politicii lor (proiectul #13, decizia 32). **Nu a fost inceputa** — sesiunea a evaluat contextul
consumat pana la acel punct ca fiind aproape de pragul de predare (lectura extinsa de rapoarte mari +
export Oracle in aceasta sesiune) si a predat inainte de a deschide o interogare noua, conform Regula
zero. Livrabilul cerut pentru acea sarcina e alt fisier,
`docs\cercetare\linii_comanda_articol_nemembru.md` — **neinceput, nescris**. Nimic din
sesiunea curenta nu e intr-o stare periculoasa: doar `SELECT`-uri Oracle rulate (toate terminate),
niciun fisier de cod atins, niciun proces/tranzactie ramas deschis. Urmatorul agent poate porni
curat pe sarcina celor 37 de linii, cu contextul din mesajul team-lead-ului (re-confirma cifra,
extrage cele 37 de linii cu id_comanda/id_articol/id_pol/data/stare facturare, verifica daca vreuna
s-a facturat fara eroare, cauza daca se poate citi din audit, si validarea pe cod a conditiei
FACT-024 aplicata pe forma lor).
---
## Proiectarea parametrului de cont contabil (mandat nou, Marius 10.08.2026)
Marius a autorizat explicit modificarea punctuala a `pack_facturare` ("poți să adaugi parametrul
contul contabil la `contabilizeaza_articol`") — decizia 27-bis e relaxata. **Aceasta e o proiectare,
nu o implementare — niciun fisier de cod nu a fost atins.** Sursa Oracle:
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii,
verificat `wc -l`); toate liniile citate mai jos sunt din **acest fisier**, citite direct in aceasta
runda (nu preluate din rapoarte anterioare).
### Verdict proiectare, in sase randuri
**Fezabil, cu o precizare importanta fata de formularea lui Marius**: `contabilizeaza_articol`
primeste azi un **singur parametru**, `detalii_articol VANZARI_DETALII_TEMP%ROWTYPE` — un rand
intreg, nu o lista de scalari. Deci "parametrul de cont" **nu intra ca parametru nou pe
`contabilizeaza_articol` insasi** (asta ar fi cosmetic, fara efect, pentru ca cei 3 apelanti interni
ii dau deja randul intreg dintr-un `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`) — intra pe drumul real:
**o coloana noua pe `VANZARI_DETALII_TEMP`, populata printr-un parametru nou pe
`adauga_articol_factura`** (procedura chemata direct din VFP), pe care `contabilizeaza_articol` o
citeste automat prin `detalii_articol.<coloana>`, **fara nicio modificare la `scrie_factura2`,
`scrie_factura_avize_retur` sau `scrie_aviz_retur`**. Efectul practic e exact ce a cerut Marius — un
cont de venit calculat de VFP ajunge in `ACT_TEMP.SCC` fara politica — doar mecanica de livrare e
alta decat "parametru pe `contabilizeaza_articol`" literal. Restul (SCD, CU_TVA, IN_VALUTA,
EXPLICATIE, ID_VENCHELT, ID_SECTIE, `descarca_gestiune` o singura data, FACT-024 ocolit doar pe
ramura noua) au surse identificate mai jos, cu 2 hardcodari explicite care cer confirmarea lui
Marius (`SCD='4111'`, `CU_TVA=1`).
### 1. Lantul real de apel — de ce parametrul trebuie sa intre pe `adauga_articol_factura`, nu pe `contabilizeaza_articol`
```
ff_...:7173 FUNCTION contabilizeaza_articol(detalii_articol VANZARI_DETALII_TEMP%ROWTYPE) RETURN NUMBER IS
```
Singurul parametru. Apelata de exact **3 locuri**, toate interne pachetului, toate ii dau un element
dintr-un array populat integral din tabel:
```
ff_...:6039-6040 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
tab_detalii tab_detalii_type;
ff_...:6063 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
ff_...:6141 V_INCASAT_CALCUL := V_INCASAT_CALCUL + pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- scrie_factura2
ff_...:6858 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- scrie_factura_avize_retur
ff_...:7140 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- scrie_aviz_retur
```
`SELECT * BULK COLLECT` (nu o lista explicita de coloane) inseamna: **orice coloana noua adaugata pe
`VANZARI_DETALII_TEMP` ajunge automat in `tab_detalii(i)` si deci in `detalii_articol` in
`contabilizeaza_articol`, fara sa atingi aceste 3 proceduri.** Asta e drumul cu cea mai mica
suprafata de risc posibila — nu exista un drum mai mic care sa duca un cont calculat de VFP pana in
`contabilizeaza_articol`.
Intrarea reala din VFP e `adauga_articol_factura` (nu `contabilizeaza_articol`):
```
ff_...:4989-5015 PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER,
V_ID_ARTICOL IN NUMBER,
... (23 parametri) ...
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL,
V_LOT IN VARCHAR2 DEFAULT NULL) IS
```
**Precedent direct pentru "adauga parametru nou la coada, cu `DEFAULT NULL`"**: `V_TAXCODE` si
`V_LOT` sunt deja exact asta — doi parametri adaugati ulterior, la finalul listei, cu `DEFAULT NULL`,
fara sa oblige la rescrierea apelantilor existenti. Propunerea de mai jos repeta acelasi tipar, nu
inventeaza unul nou.
Apelantul VFP confirmat (singurul gasit in sursa, dublat identic in doua locuri din aceeasi clasa —
vezi "Ce nu s-a putut stabili"):
```
COMUN\clase\ofacturare.vc2:14069-14091 (si identic la :18089-18114)
lcSql = lcSql + [pack_facturare.adauga_articol_factura(] + Alltrim(Str(poArt.id_temp)) + [,] + ;
... (pozitional, 26 de argumente) ... + ;
NVL(ALLTRIM(STR(poArt.taxcode)),[NULL]) + ;
[,'] + OracleSpecialCharacters(Alltrim(Nvl(poArt.lot,[]))) + ['] + ;
[);]
```
Apel **pozitional**, se opreste la `V_LOT` (ultimul parametru existent azi). Oracle permite ca un
apel pozitional sa se opreasca inainte de parametri opționali de la coada — deci un parametru nou
**dupa** `V_LOT` nu afecteaza acest apel existent (ramane identic, echivalent cu "trimite `NULL`" pe
noul parametru).
### 2. Schema propusa
**Coloana noua**: `VANZARI_DETALII_TEMP.CONT_VENIT VARCHAR2(4) NULL` (fara `NOT NULL`, fara `CHECK`
— acelasi tipar ca `ACT_TEMP.SCC`, vezi DDL mai jos). Recomandat, dar nu strict necesar pentru
mecanismul de nota (doar pentru trasabilitate/raportare ulterioara): aceeasi coloana pe
`VANZARI_DETALII`, plus adaugarea ei in `INSERT /*+ APPEND */ INTO VANZARI_DETALII (...) SELECT ...
FROM VANZARI_DETALII_TEMP` din `scrie_in_vanzari` (citat in `nota_contabila_fara_politica.md`
sectiunea 4 ca avand lista explicita de coloane — **linia exacta nu a fost re-verificata in aceasta
runda**, vezi "Ce nu s-a putut stabili"). Fara acest pas secundar, nota contabila tot se scrie corect
— doar `VANZARI_DETALII` nu ar pastra, dupa fapt, cu ce cont s-a facturat linia.
**Parametru nou pe `adauga_articol_factura`**, la coada, dupa `V_LOT`:
```
V_CONT_VENIT IN VARCHAR2 DEFAULT NULL
```
Plumbing identic cu `V_CONT`/`V_CONT2` (`ff_...:5004,5035-5037,5238,5268` — acelasi tipar de
"copiaza daca nu e sentinela/gol, altfel NULL"), adaugat langa `INSERT INTO VANZARI_DETALII_TEMP`
(`ff_...:5222-5282`): o coloana in plus in lista, o valoare in plus in `VALUES`.
**Niciun rand de cod nou in `scrie_factura2` / `scrie_factura_avize_retur` / `scrie_aviz_retur`** —
confirmat la sectiunea 1, `SELECT *` + `%ROWTYPE` absoarbe coloana automat.
### 3. Ramura noua in `contabilizeaza_articol`
Corpul complet citit `ff_...:7173-7547`. Structura propusa: un `IF detalii_articol.cont_venit IS NOT
NULL THEN <ramura noua> ELSE <tot codul de azi, neschimbat> END IF;` care **infasoara inclusiv
blocul FACT-024**, plasat imediat dupa declaratiile locale (inainte de `ff_...:7278`). Cand
parametrul e `NULL` (toti apelantii de azi, nemodificati), executia cade in `ELSE` si comportamentul
e identic bit-cu-bit cu azi.
**Continutul ramurii noi** (doar pentru "articol simplu" — articolele compuse, `V_COMPUS=1`, raman
in afara scopului, ca azi):
| Camp | Sursa in ramura noua | Argumentatie |
|---|---|---|
| `SCC` | `detalii_articol.cont_venit` | Chiar parametrul primit — asta e intreg scopul modificarii. |
| `SCD` | **hardcodat `'4111'`** pentru cazul "factura" (ramurile aviz raman `'418'`/`'461'`, deja hardcodate azi la `ff_...:7415,7420`, independent de politica) | **Hardcodare explicita, de confirmat cu Marius.** Azi, pentru factura normala, `V_SCD := crs_rand_articol.scd` (`ff_...:7409`) — vine din `NOTE_CONTABILE.SCD`. Pe baza vie (`verif_baza_vie_cont_venit.md` tabel sectiunea 4), **toate cele 7 note de vanzari active au `SCD='4111'`** (contul de clienti, standard pentru vanzare pe credit) — zero exceptii masurate. Decizia 31 (nu generaliza de pe datele din Dev) se aplica la *numere*, nu la *structura contabila* — `4111` = clienti e conventie standard de plan de conturi, nu un artefact al datelor de test, dar tot ramane o presupunere care trebuie confirmata explicit, nu deja "descoperita". |
| `ASCD` | `PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD)` | Exact fallback-ul deja folosit necondiționat pentru ramurile de aviz azi (`ff_...:7416-7417,7421-7422`) — functioneaza fara cursor, deja verificat ca nu depinde de politica (`coresp_cont_venchelt.md` sectiunea 7). |
| `ASCC` | `PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCC)` | Acelasi tipar ca `V_ASCC` de azi (`ff_...:7426-7428`), care oricum e `NVL(crs_rand_articol.ascc, GetAnaliticByGrupUtilizatori(...))` — fallback-ul e calea normala, nu exceptia. |
| `EXPLICATIE` | `detalii_articol.explicatia` (coloana `EXPLICATIA` din `VANZARI_DETALII_TEMP`, populata de `V_EXPLICATIE` — parametru deja existent pe `adauga_articol_factura`, `ff_...:4992,5227,5257`) | **Mai buna decat sursa de azi**, nu doar un fallback — azi `crs_rand_articol.explicatie` vine dintr-un sablon static configurat pe nota; textul introdus de utilizator la adaugarea articolului e deja disponibil pe rand, fara politica. |
| `ID_VENCHELT` | `pack_facturare.nid_venchelt` | Acelasi fallback de sesiune pe care cursorul de azi il foloseste oricum ca prioritate (`NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))`, `ff_...:7244-7245`) — poate ramane `NULL` daca variabila de sesiune nu e setata, exact ca azi in acelasi caz. |
| `ID_SECTIE` | `pack_facturare.nid_sectie_stoc` | Acelasi tipar (`ff_...:7246`). |
| `IN_VALUTA` | `pack_facturare.nin_valuta` | **Nu e o presupunere noua** — e valoarea pe care cursorul de azi o **substituie deja** peste `D.IN_VALUTA` intr-un caz analog (`ff_...:7254-7260`, cand `A.ID_POL = pack_facturare.nid_politica_stoc AND pack_facturare.nin_valuta = 1`). Fiindca fara politica nu exista `D.IN_VALUTA` de citit, se foloseste direct flagul de document — acelasi flag pe care codul existent il trateaza deja ca sursa de incredere. |
| `CU_TVA` | **hardcodat `1`** | **Hardcodare explicita, de confirmat cu Marius.** `CU_TVA` (parametrul `V_CU_TVA` al `scrie_nota`, `ff_...:12347`) controleaza daca se scrie **separat** o linie de TVA (`ff_...:12537-12558`, `pack_facturare.scrie_tva(...)`) — nu trebuie confundat cu `V_PRET_ARE_TVA` (mapat pe `detalii_articol.pret_cu_tva`, deja disponibil independent de politica, controleaza doar interpretarea pretului). Pe baza vie, **toate cele 7 note de vanzari active au `CU_TVA=1`** (`verif_baza_vie_cont_venit.md` sectiunea 4) — nicio nota de vanzare reala cu `CU_TVA=0`. Semantic, `1` = comportamentul standard pentru o vanzare taxabila (TVA scrisa separat, necesar pentru raportarea de TVA); `0` n-are niciun exemplu real observat. Daca cota TVA a articolului e 0% (scutit), `scrie_tva` scrie oricum o linie cu suma 0 — inofensiv, nu gresit, dar de confirmat ca e acceptabil. |
**Apeluri, o singura data fiecare** (nu in bucla — nu exista cursor in aceasta ramura):
1. `pack_facturare.scrie_nota(...)` — aceiasi parametri ca la `ff_...:7443-7467`, cu valorile de mai
sus in locul celor din `crs_rand_articol`; restul (`cantitate`, `pret`, `discount_unitar`,
`pret_cu_tva`, `id_valuta`, `curs/multiplicator`, `id_ctr`, `proc_tvav`, `id_jtva_coloana`,
`taxcode`) vin din `detalii_articol`, exact ca azi — independente de politica.
2. `pack_facturare.descarca_gestiune(...)` — **aceeasi garda ca azi**
(`pack_facturare.nscadere_stoc=1 AND detalii_articol.id_gestiune<>-1000 AND
detalii_articol.in_stoc=1`, `ff_...:7473-7475`), cu `crs_rand_articol.id_sectie`/`.id_venchelt`
inlocuite de `pack_facturare.nid_sectie_stoc`/`.nid_venchelt` direct (fara cursor). **Ruleaza
exact o data, prin constructie** — nu exista `WHILE cursor%FOUND LOOP` in aceasta ramura, deci
defectul de "set multi-rand" (punctul urmator) nu poate aparea aici, fara sa fi fost nevoie sa se
repare nimic pe ramura veche.
3. Discount (`ff_...:7501-7518`), aceeasi garda (`discount_unitar<>0 AND ndiscount_evidentiat=1`),
cu `V_ASCD` si `V_CU_TVA` calculate mai sus in loc de cele din cursor.
4. `RETURN V_INCASAT_CALCUL;` — identic.
### 4. FACT-024 — ocolit doar pe ramura noua, garda neschimbata pe ramura veche
Blocul (`ff_...:7278-7302`) ramane **litera cu litera identic** in `ELSE`. Nu se slabeste nimic — o
linie fara politica **si** fara `cont_venit` continua sa primeasca FACT-024 exact ca azi. Bypass-ul
e strict conditionat de faptul ca VFP a trimis explicit un cont (`cont_venit IS NOT NULL`), nu de
absenta politicii in sine.
### 5. Bug-ul de set multi-rand — nemostenit, netratat
Documentat deja (`nota_contabila_fara_politica.md`, `verif_baza_vie_cont_venit.md` sectiunea 6.1):
`cursor_articol` face `LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET` fara `ROWNUM`/agregare, iar
bucla scrie venitul si descarca gestiunea o data per rand din set. **Ramura noua nu are cursor deloc**
— rezolva structural aceeasi clasa de bug, dar **nu e propusa ca reparatie a ramurii vechi**; ramane
un defect separat, de tratat separat, cum a cerut explicit team-lead-ul.
### 6. Suprafata de regresie
- **`contabilizeaza_articol`**: 3 apelanti, toti interni `pack_facturare` (`scrie_factura2`,
`scrie_factura_avize_retur`, `scrie_aviz_retur`) — **zero schimbari** la niciunul (sectiunea 1).
- **`adauga_articol_factura`**: **un singur apelant confirmat** in sursa citita,
`COMUN\clase\ofacturare.vc2:14069-14091`, duplicat identic la `:18089-18114` in aceeasi clasa
(posibil doua metode gemene, nu doua clase — neconfirmat, vezi mai jos). Apel pozitional, se
opreste la `V_LOT` — **neafectat** de un parametru nou dupa `V_LOT` cu `DEFAULT NULL`, pe acelasi
tipar deja folosit de `V_TAXCODE`/`V_LOT` insele.
- **Restul suitei** (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB): pe baza cercetarilor anterioare
(`nota_contabila_fara_politica.md` sectiunea 4, `coresp_cont_venchelt.md` sectiunea 9b), aceste
produse folosesc `adauga_articol_factura_deviz`/`_stoc` (proceduri **separate**, semnaturi diferite,
**neatinse** de aceasta propunere) sau `pack_acn.salveaza_regdoc` (alt pachet complet). O cautare
directa, in aceasta runda, pentru apeluri catre `adauga_articol_factura(` (fara `_deviz`/`_stoc`) in
`COMUN`-urile celorlalte produse **nu s-a terminat in timp util** (comanda de fond nu a raspuns) —
vezi "Ce nu s-a putut stabili". Pe baza dovezilor deja existente (nimeni altcineva nu are un flux
documentat prin `adauga_articol_factura` simplu), riscul asteptat e **zero**, dar nu e demonstrat
exhaustiv pentru toata suita in aceasta runda.
- **Teste minime recomandate**: (a) factura normala cu politica, parametru `NULL` — verifica ACT
identic cu azi (regresie); (b) articol fara politica, `cont_venit` populat — un singur rand `ACT`,
`SCC`=valoarea data, `SCD='4111'`, linie TVA scrisa separat; (c) articol negestionabil
(`id_gestiune=-1000`) cu `cont_venit` populat — `descarca_gestiune` NU ruleaza; (d) discount pe
linie cu `cont_venit` populat; (e) document in valuta (`nin_valuta=1`) cu `cont_venit` populat —
`IN_VALUTA=1` pe nota, `SUMA_VAL` completat; (f) linie fara politica **si** fara `cont_venit` —
FACT-024 tot apare (regresie negativa, garda nu s-a slabit).
### 7. Alternativa mai mica — nu exista una reala
Propunerea de mai sus **e** deja varianta minimala: un singur parametru `VARCHAR2(4) DEFAULT NULL`,
zero schimbare de comportament cand e `NULL`, zero atingere a celor 3 apelanti interni. O varianta
"doar un flag care opreste FACT-024, fara sa transporte contul" tot ar avea nevoie ca `SCC` sa vina
de undeva — nu reduce suprafata, doar muta numele. Nu recomand separarea in doi parametri
(flag + cont) — complexitate in plus fara beneficiu; un singur camp nenul e semnalul suficient.
### Ce nu s-a putut stabili in aceasta runda, si de ce
- **Daca `ofacturare.vc2:14069-14091` si `:18089-18114` sunt doua metode/clase distincte sau o
duplicare literala a aceleiasi metode** — nu am identificat clasele-container ale celor doua
blocuri (ar necesita indexul de simboluri `vfp_symbols.ps1`, nefolosit in aceasta runda din lipsa
de timp). Nu schimba verdictul (ambele se opresc la `V_LOT`), dar conteaza pentru a sti cate locuri
VFP trebuie atinse ca sa se **foloseasca** efectiv noul parametru dupa ce va exista in Oracle.
- **Daca alte produse (ROAGEST/ROAAUTO/ROAACNPRO/ROAIMOB) apeleaza `adauga_articol_factura` simplu**
undeva neexaminat — comanda de cautare pe `COMUN`-urile celorlalte produse a ramas fara raspuns in
timp util in aceasta sesiune; de re-rulat separat inainte de implementare.
- **Linia exacta a `INSERT ... INTO VANZARI_DETALII ... SELECT ... FROM VANZARI_DETALII_TEMP`** din
`scrie_in_vanzari` — citata doar din raportul anterior (`nota_contabila_fara_politica.md`), nu
re-verificata pe fisierul curent in aceasta runda; necesara doar daca se decide sa se persiste si
`CONT_VENIT` pe `VANZARI_DETALII` (pasul optional de la sectiunea 2).
- **Comportamentul `scrie_tva` cu cota 0%** (linie scutita) cand `CU_TVA` e hardcodat la `1` — nu am
citit corpul `scrie_tva` in aceasta runda pentru a confirma ca o suma 0 nu produce vreun efect
secundar (ex. impact pe `nproc_tva_max`/`nid_jtva_coloana` la `ff_...:12538-12541`, care se
actualizeaza necondiționat de valoarea sumei).
- **Confirmarea explicita a lui Marius pe cele doua hardcodari** (`SCD='4111'`, `CU_TVA=1`) — sunt
presupuneri argumentate din date reale si din tiparul de cod existent, nu descoperiri; raman
decizii de proiectare deschise.
---
## Verdict runda anterioara (canal ocolitor, mandat vechi — pastrat ca input de proiectare)
**DA, exista un canal real, deja cablat si deja folosit in productie pentru categoria de document
"facturi emise"** — complet independent de `pack_facturare` si de politica de pret. E cursorul VFP
generic `actactan`/`tact` -> `oscrie_in_fisiere.prg` (INSERT generic in `ACT_TEMP` din campurile
oricarui cursor VFP) -> `pack_contafin.SCRIE_IN_ACT`/`STERGE_DIN_ACT` (nu `pack_facturare`!). Cel mai
relevant: **fluxul de editare a facturii emise, `ofacturare_comun.vc2:3796-3821` (proiectul #6
insusi), foloseste exact acest canal** ca sa lase utilizatorul sa editeze `SCD`/`ASCD`/`SCC`/`ASCC`
direct pe randul notei prin `frm_modific2024` (`omodificari.vc2`, fisier **nerestrictionat**), apoi
rescrie `ACT_TEMP` cu valorile din cursorul VFP editat. **Limitarea majora**: canalul e cablat pe
**editarea unei note deja scrise** (sterge + rescrie), nu pe **emiterea** unei facturi noi — emiterea
trece azi exclusiv prin `pack_facturare.scrie_factura2 -> contabilizeaza_articol`, care nu are nicio
ramura alternativa (confirmat in rundele anterioare, FACT-024). Pistele 1, 2, 4, 5 din cerere sunt
**toate NU** — niciuna nu ofera un canal catre `SCC`. DDL-ul `ACT_TEMP` nu blocheaza nimic din asta:
`SCD`/`SCC` sunt `VARCHAR2(4) NULL`, fara `CHECK`.
Sursa principala pentru citatele Oracle: **`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`**
(17217 linii — confirmat `wc -l`). Fisierul din `docs\` nu mai exista, cum a semnalat cererea.
---
## Pistele cerute, in ordine
### 1. `V_CONT IN VARCHAR2` (parametru `adauga_articol_factura`) — **NU e canal pentru SCC**
Confirmat pe cod, nu doar preluat din raportul anterior (a carui concluzie pe acest punct era deja
corecta): `V_CONT` e contul de **gestiune/stoc** (clasa 3xx), nu de venit. E scris ca atare in
`VANZARI_DETALII_TEMP.CONT` (`adauga_articol_factura`, semnatura la
`ff_...:4989-5015` `V_CONT IN VARCHAR2`, folosire la `:5044-5046` `V_CONT2 := V_CONT`, `INSERT` la
`:5277`). **Niciodata folosit ca `SCD`/`SCC`** — `contabilizeaza_articol` isi ia `V_SCC` exclusiv din
`cursor_articol` (`D.SCC`, lantul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI
-> NOTE_CONTABILE`, `ff_...:7248-7253,7259-7260`), fara nicio ramura care sa citeasca `V_CONT`.
`V_CONT` alimenteaza doar `descarca_gestiune` (contul de iesire din gestiune, SCC pe nota de
**stoc**, nu de venit), nu nota de venit.
### 2. Variabile de sesiune `pack_facturare` — **NU exista setter pentru cont**
Am listat toate variabilele publice de pachet cu prefix `nid_` din specificatia `pack_facturare`
(`ff_...:72-211`, citat exhaustiv, cautare `grep -n "^ nid_"`):
```
nid_tipnir, nid_tipbon, nid_tipfactura, nid_tipaviz, nid_act, nid_serie, nid_fdoc, nid_part,
nid_part_rez, nid_lucrare, nid_sectie_stoc, nid_gestiune_sursa, nid_responsabil, nid_ordl, nid_set,
nid_util, nid_moneda_nationala, nid_fact, nid_factc, nid_partc, nid_jtva_coloana, nid_comanda,
nid_valuta, nid_politica_stoc, nid_sucursala, nid_venchelt, nid_vanzare, nid_beneficiar
```
Niciuna tipata pe un cont (`VARCHAR2(4)`/cod de cont) — toate sunt FK-uri numerice
(`%TYPE` pe alte tabele: `ID_SECTIE`, `ID_VENCHELT`, `ID_POL`, etc.). Cautare directa
`nid_scc`/`nid_scd`/`nid_cont` in tot fisierul: **zero rezultate**. `nid_venchelt` (folosit ca
fallback la `ID_VENCHELT` in `cursor_articol`, `ff_...:7244-7245`, deja documentat in
`coresp_cont_venchelt.md` sectiunea 7) e o clasificare dimensionala
(`NOM_VENIT_CHELTUIELI.ID_VENCHELT%TYPE`), nu un cont — **nu am gasit consumator care sa-l foloseasca
drept `SCC`**. Cautare separata `PROCEDURE SET_` in tot pachetul: zero rezultate — nu exista niciun
setter public de tip `SET_...` pentru cont. **Concluzie: NU.**
### 3. `ACT_TEMP` scris direct din VFP — **DA, canal real, dar pe fluxul de editare, nu de emitere**
Confirmat de `COMUN\docs\oracle_export.md:64-65`: *"Pachetul central de scriere a documentelor —
clientul VFP populeaza tabelele temporare `ACT_TEMP`/`RUL_TEMP` (prin `oscrie_in_fisiere`-ul din
COMUN), apoi pachetul distribuie"*. Mecanismul e generic si complet independent de `pack_facturare`:
**Mecanismul** (`COMUN\programe\oscrie_in_fisiere.prg`):
- `sql_temp_insert('actactan','ACT_TEMP')` (`oscrie_in_fisiere.prg:127`) face, pentru fiecare rand al
oricarui cursor VFP numit `actactan`, un `INSERT INTO ACT_TEMP (<coloanele care exista si in
cursor si in tabel>) VALUES (...)` construit dinamic din `user_tab_columns`
(`oscrie_in_fisiere.prg:190-280`) — **orice camp `SCD`/`ASCD`/`SCC`/`ASCC` prezent in cursorul VFP
ajunge direct in `ACT_TEMP`, indiferent de valoare, fara nicio validare de cont**.
- Scrierea efectiva in `ACT` se face prin `pack_contafin.init_scriere_act_rul_local` +
`final_scriere_act_rul_local` -> `SCRIE_IN_ACT`/`STERGE_DIN_ACT` (`oscrie_in_fisiere.prg:121-147`)
— **`pack_contafin`, nu `pack_facturare`**. Confirmat si in
`COMUN\docs\flux-modificare-stergere-nota-jurnal.md:20-28`.
**Cine populeaza `actactan` cu valori calculate/editate de VFP** — cele doua cazuri gasite, ambele
active in productie:
**(a) Registrul Jurnal — editare/stergere manuala de nota, orice document, generic**
(`COMUN\clase\comun.vc2`, clasa `afisjurcom`):
- `do_modifica` (`comun.vc2:2222-2562`): incarca nota existenta
(`Select * From v_act Into Cursor actactan`, `comun.vc2:2363`), instantiaza
`frm_modific2024` (`comun.vc2:2439`, `omodificari.vc2:6375`) — formular in care utilizatorul poate
edita direct `scd`/`ascd`/`scc`/`ascc` pe fiecare rand al notei (grid legat pe `tact`, verificare
prin `verific_analitic`/`do_verifica`, tipar prezent si in `omodificari.vc2:1714-1788` — comentat
azi, dar acelasi tipar activ e in `frm_modific2024`, vezi mai jos (b)). Dupa editare:
`oscrie_in_fisiere(2,...)` = sterge nota veche (`comun.vc2:2454`), apoi
`oscrie_in_fisiere(0,...)` = scrie nota noua **cu valorile din cursorul editat**
(`comun.vc2:2482`), apoi `pack_contafin.finalizeaza_modificare_nota(...)`
(`comun.vc2:2484-2486`). **Niciun apel catre `pack_facturare` in acest flux.**
- Documentat integral, cu aceleasi citate, in `COMUN\docs\flux-modificare-stergere-nota-jurnal.md`.
- E generic pe orice document din `ACT` (nu doar facturi) — daca o factura emisa are deja o nota,
poate fi editata de aici.
**(b) Editarea facturii emise — acelasi mecanism, scop specific "facturi emise" (proiectul #6
insusi)**, `COMUN\clase\ofacturare_comun.vc2:3769-3838` (functia `do_editare_factura`, citita
integral, **nu modificata**):
```
ofacturare_comun.vc2:3769 If IncarcaCursoareModificareNota(lnCod, pnAn, pnLuna, .F.) && ofacturare_editare.prg
...
ofacturare_comun.vc2:3787 Select a.*, Iif(Nvl(id_jtva_coloana,0)=0,0,1) As cu_Tva From tact a Into Cursor tact Readwrite
...
ofacturare_comun.vc2:3796 Omodif = Createobject([frm_modific2024], lnIdSet)
ofacturare_comun.vc2:3797 Omodif.Show()
ofacturare_comun.vc2:3799 If buton = 1
ofacturare_comun.vc2:3800 If Thisform.do_deschide_tranzactie()
ofacturare_comun.vc2:3801 Select actactan
ofacturare_comun.vc2:3802 lnSucces = OSCRIE_IN_FISIERE(2,.T.,.T.) && sterge nota veche
ofacturare_comun.vc2:3803 If lnSucces > 0
...
ofacturare_comun.vc2:3807 Select tact
ofacturare_comun.vc2:3808 Replace id_jtva_coloana With Null, proc_tva With 0 For cu_Tva = 0
ofacturare_comun.vc2:3809 Select * From tact Into Cursor actactan Readwrite && re-materializeaza tact (inclusiv orice SCD/SCC editat) in actactan
ofacturare_comun.vc2:3810 Replace All id_util With gnIdUtil, sters With 0
...
ofacturare_comun.vc2:3821 lnSucces = OSCRIE_IN_FISIERE(0,.T.,.T.) && scrie nota noua, direct din actactan, in ACT_TEMP
ofacturare_comun.vc2:3823 If lnSucces > 0
ofacturare_comun.vc2:3824 lcSql = [begin pack_contafin.finalizeaza_modificare_nota(...); end;]
ofacturare_comun.vc2:3828 If lnSucces > 0 And "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) And Used('tvanz')...
ofacturare_comun.vc2:3829 lnSucces = Iif(ScrieArticoleFacturaEditate(tvanz.id_vanzare), 1, -1) && scrie si VANZARI_DETALII
```
`IncarcaCursoareModificareNota` (`COMUN\programe\ofacturare_editare.prg:32-71`) incarca nota
existenta a facturii (`vact_tot -> v_act -> actactan -> tact`, toate **READWRITE**) inainte de a
arata `frm_modific2024`. Linia `:3809` **rescrie `actactan` direct din `tact`** — orice valoare
aflata pe `tact.scd`/`tact.scc` in acel moment (editata manual prin `frm_modific2024`, sau setata
programatic de orice cod VFP inainte de linia 3809) devine noua nota, scrisa in `ACT_TEMP` prin
`OSCRIE_IN_FISIERE(0,...)` la linia 3821 — **fara niciun apel `pack_facturare`, fara politica de
pret**. `pack_contafin.finalizeaza_modificare_nota` (linia 3824) doar re-leaga `cod`-ul nou de
`VANZARI`; `ScrieArticoleFacturaEditate` (linia 3829) scrie separat `VANZARI_DETALII`.
**Aceasta e dovada cea mai puternica posibila pentru intrebarea pusa**: canalul nu e teoretic — e
**exact mecanismul pe care proiectul #6 il foloseste azi** ca sa permita editarea contului unei
facturi deja emise, complet in afara `pack_facturare`.
**Limitarea reala**: mecanismul opereaza pe **o nota deja existenta** (sterge randul vechi din `ACT`,
scrie unul nou cu acelasi `id_fact`) — presupune ca factura a fost deja scrisa o data (prin
`pack_facturare`, cu politica). Nu e (azi) un canal pentru **emiterea** initiala a unei facturi noi
cu un articol fara politica — `scrie_factura2 -> contabilizeaza_articol` (calea de emitere) nu are
nicio ramura care sa ocoleasca politica si nu apeleaza acest mecanism.
**Alte doua confirmari ale aceluiasi tipar, tot pentru categoria "facturi"**:
- `COMUN\ferestre\frm_initializare_facturi_balanta.sc2:458` — `CREATE CURSOR actactan (id_set I, an
N(4), luna N(2), Dataact D, datascad D, dataireg D, serie_act C(20), nract N(20), proc_tva N(5,2),
id_jtva_coloana I NULL, scd C(4), ascd C(4), scc C(4), ascc C(4), ...)` — cursor creat de la zero
in VFP, cu `scd`/`scc` campuri simple, editabile liber, fara nicio derivare Oracle. Foloseste tot
`frm_modific2024` (`:1806`) pentru editare/validare inainte de scriere.
- `COMUN\ferestre\frm_import_note_facturi_clienti.sc2:667,815,898,911` — acelasi tipar
(`actactan`/`tact` editabil + `frm_modific2024`), pentru import manual de note pe facturi de
clienti.
Ambele sunt scenarii de **initializare/import** (sold de deschidere, import de note), nu de
facturare curenta — dar confirma ca `ACT_TEMP` pentru **categoria "facturi"** se scrie deja, in mod
curent, direct din cursoare VFP construite de la zero, fara `pack_facturare`.
### 4. `ID_VENCHELT` / `CRM_NOTE_VANZARI` — **NU e canal alternativ catre SCC**
Deja stabilit exhaustiv in `coresp_cont_venchelt.md` sectiunea 7: `ID_VENCHELT` e o dimensiune
(`NOM_VENIT_CHELTUIELI.ID_VENCHELT`), citita cu fallback pe `nid_venchelt`
(`ff_...:7244-7245`), dar **nu participa la calculul `SCC`** — `V_SCC` vine exclusiv din
`cursor_articol.D.SCC`. Nu exista camp de tip cont pe `VANZARI`/`VANZARI_DETALII` — reconfirmat aici
prin cautare `grep -inE "SCC|CONT_VENIT"` in blocurile `CREATE`/`ALTER TABLE VANZARI` din
`ff_...` (fara rezultate suplimentare fata de ce era deja stabilit).
### 5. `GetAnaliticByGrupUtilizatori` — **doar analitic, nu cont sintetic**
Confirmat deja in `coresp_cont_venchelt.md` sectiunea 7: functia da fallback pentru `ASCD`/`ASCC`
(analiticul), nu pentru `SCD`/`SCC` (contul sintetic). Nu exista o functie analoaga pentru cont —
cautare `GetSinteticBy`/`GetContBy` in tot `ff_...`: zero rezultate.
---
## Lista exhaustiva a intrarilor catre `SCD`/`SCC` in `ACT_TEMP`, pe/langa fluxul de facturare
Sursa: `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` + fisierele VFP citate.
1. **`pack_facturare.contabilizeaza_articol -> scrie_nota`** (`ff_...:7218-7556` / `12338-12570`,
`INSERT INTO ACT_TEMP` la `:12458-12544`) — calea principala de **emitere**. `SCD`/`SCC` vin din
`cursor_articol` (`D.SCD`, `D.SCC`, lantul politicii). Blocheaza cu FACT-024
(`:7275-7311`) daca articolul nu e in politica — fara fallback (deja stabilit in
`nota_contabila_fara_politica.md`).
2. **Ramurile speciale din `scrie_factura2`** (transfer subunitati `ntip IN (23,25,30,41)`, custodie
`ntip IN (42,47)`, rata `id_rata<>0`) — tot `contabilizeaza_articol`, acelasi punct 1.
3. **`scrie_factura_avize_retur`** (`ff_...:6273-6666`, apel la `:6867`) si **`scrie_aviz_retur`**
(`ff_...:7094-7180`, apel la `:7149`) — tot `contabilizeaza_articol`.
4. **`descarca_gestiune`** (`ff_...:7476-7498`, apelata din interiorul buclei `cursor_articol`) —
scrie o a doua nota, de **iesire din gestiune** (`SCD`/`SCC` = conturi de stoc, din `V_CONT`/
`poArticol.Cont`, nu din politica) — parte a fluxului de facturare, dar nu e nota de venit.
5. **Discount pe linie** (`ff_...:7501-7518`) — scrie o nota separata (vazuta pe date live ca
`id_nota=2`, `SCD=667`/`SCC=4111`, `verif_baza_vie_cont_venit.md` sectiunea 4) — tot in interiorul
`cursor_articol`, deci tot dependenta de politica pentru a ajunge acolo.
6. **`oscrie_vanzare_din_stoc`** (`COMUN\programe\ofacturare_stoc.prg:104-450`) — flux ROAGEST
"vanzare din stoc". Apeleaza `pack_facturare.adauga_articol_factura_stoc` per articol
(`:357-378`) apoi `oscrie_in_fisiere(0,.F.,.T.)` (`:392`). **Neverificat complet**: codul citit nu
arata explicit unde/cum se populeaza `actactan` cu `SCD`/`SCC` inainte de linia 392 in acest flux
particular (cursorul `ACTACTAN` e doar *citit* la `:253-289` pentru alte campuri, nu am gasit
punctul de scriere in fisierul citit) — posibil populat de o procedura anterioara neexaminata.
Marcat ca zona neinchisa, vezi "Ce nu s-a putut stabili".
7. **Registrul Jurnal — editare/stergere manuala** (`COMUN\clase\comun.vc2:2222-2724`, clasa
`afisjurcom`) — canalul confirmat la pista 3(a). Genereic, orice document din `ACT`.
8. **Editare factura emisa** (`COMUN\clase\ofacturare_comun.vc2:3769-3838`, proiectul #6) — canalul
confirmat la pista 3(b), specific "facturi emise".
9. **`frm_initializare_facturi_balanta.sc2`** si **`frm_import_note_facturi_clienti.sc2`** — canalul
confirmat la pista 3, scop initializare/import pe categoria "facturi".
10. **eFactura import** (`COMUN\clase\anaf_efactura.vc2:12241,13087-13102,13244-13325`) — acelasi
tipar generic (`actactan`/`tact` + `frm_modific2024` + `oscrie_in_fisiere`), dar pentru facturi
de **achizitie** importate din XML ANAF, nu facturi emise — mentionat pentru completitudine, nu
aplicabil direct la #13.
11. **`ointroduceri.vc2`/`ointroduceri.prg`** (NIR, BON, RETUR, intrare din gestiune valorica) si
**`oschimbare_pret.prg`**, **`oproceduri_rulaje.prg`**, **`reglari_denominare2005.prg`** —
acelasi canal generic `actactan`/`oscrie_in_fisiere`, dar pentru alte categorii de document
(gestiune, nu vanzare/factura). Mentionate pentru completitudinea listei de consumatori ai
canalului, nu aplicabile la #13.
**Niciuna dintre intrarile 1-6 (fluxul de emitere efectiv) nu are o ramura care sa ocoleasca
politica.** Intrarile 7-11 folosesc toate acelasi canal generic (punctul 3), dar niciuna nu e
cablata pe fluxul de **emitere** — toate opereaza pe note deja existente sau pe alte categorii de
document.
---
## DDL `ACT_TEMP` (interogat 10.08.2026, schema `MARIUSM_AUTO`, `ROA_CENTRAL`)
| Coloana | Tip | Null |
|---|---|---|
| SCD | VARCHAR2(4) | **Y** |
| ASCD | VARCHAR2(4) | Y |
| SCC | VARCHAR2(4) | **Y** |
| ASCC | VARCHAR2(4) | Y |
| COD, NRACT, SUMA, PERECHED, PERECHEC, SUMA_VAL, CURS, NEIMPOZAB, NNIR, ID_UTIL, ID_UTILS, ID_RESPONSABIL, ID_VENCHELT, ID_SECTIE, ID_SET, ID_FACT, ID_PARTD, ID_PARTC, ID_FDOC, ID_LUCRARE, ID_GESTIN, ID_GESTOUT, ID_VALUTA, PROC_TVA, STERS, ID_FACTD, ID_FACTC, VALIDAT, TVA_INCASARE | numeric | N (29 coloane NOT NULL, restul optionale) |
Restul coloanelor (52 in total) sunt fie `NUMBER` (majoritatea `NOT NULL`), fie `DATE`/`VARCHAR2`
opționale (`DATAIREG`, `EXPLICATIA`, `DATASCAD`, `EXPLICATIA4/5`, `ID_SUCURSALA`, `ID_ACT`, `ID_CTR`,
`ID_JTVA_COLOANA`, `SERIE_ACT`, `ID_UTILV`, `DATAORAV`, `TAXCODE`, `PAYMENTCODE`).
Constrangeri: **29 `CHECK` constraints** (`SYS_C0014966`...`SYS_C0014994`) — corespund exact
coloanelor `NOT NULL` de mai sus (Oracle genereaza `CHECK (coloana IS NOT NULL)` pentru `NOT NULL`
declarat fara nume). **Niciun constraint pe `SCD`/`SCC`** (nici `NOT NULL`, nici `CHECK` de format,
nici FK catre un plan de conturi). **Nicio cheie primara/unica** — normal pentru un tabel `_TEMP` de
staging. Concluzie: schema **nu blocheaza in niciun fel** o valoare `SCC` calculata de VFP, oricare
ar fi ea, inclusiv `NULL`.
---
## Ce nu s-a putut stabilit si de ce
- **Punctul exact unde `ACTACTAN` primeste `SCD`/`SCC` in fluxul `oscrie_vanzare_din_stoc`**
(`ofacturare_stoc.prg`, intrarea 6 de mai sus) — codul citit (`:104-450`) nu arata sursa; ar
necesita urmarirea completa a apelantului (`initializeaza_vanzare_din_stoc`) si a modulului care
populeaza `ACTACTAN` inainte de acest punct (posibil `pack_facturare.adauga_articol_factura_stoc`
intoarce un ref cursor legat automat de `goExecutor` pe numele `actactan`, dar nu am gasit apelul
explicit). Nu schimba verdictul (oricum ar fi tot `pack_facturare`, deci tot politica), dar ramane
o zona neinchisa complet.
- **Daca discountul pe linie (`SCD=667`/`SCC=4111`) are vreo cale alternativa in afara
`cursor_articol`** — nu verificat aici, presupus dependent de politica la fel ca restul buclei.
- **Comportamentul practic al `frm_modific2024` cu privire la validarea contului tastat manual**
(daca exista vreo verificare `verific_cont`/plan de conturi la salvare, sau accepta orice text de
4 caractere) — nu urmarit pana la capat in `omodificari.vc2`; relevant doar daca se propune
reutilizarea canalului 3(b)/3(a) pentru scriere programatica (fara interactiune UI), caz in care ar
trebui verificat daca `frm_modific2024` insusi impune vreo validare care ar bloca o scriere
automata.
- **Daca fluxul de emitere (`scrie_factura2`) ar putea, teoretic, sa scrie intai o factura "goala" de
nota (sau cu o nota tehnica minimala) si apoi sa foloseasca imediat, in aceeasi tranzactie logica,
mecanismul de la pista 3(b) pentru a corecta `SCC`-ul** — nu explorat aici (ar fi o propunere de
design, in afara scopului acestei cercetari read-only); mentionat doar ca directie posibila care
reiese direct din ce s-a gasit.
- Nu am cautat/citit `pack_contafin.SCRIE_IN_ACT`/`STERGE_DIN_ACT` in `PACK_CONTAFIN.pck` pentru a
confirma ca acestea, la randul lor, nu re-deriva `SCC` din altceva — presupunerea (bazata pe
`oracle_export.md` si pe `flux-modificare-stergere-nota-jurnal.md`, care descriu deja aceste
proceduri drept "distribuie ce a scris clientul", nu "recalculeaza") e ca fac un simplu
`INSERT INTO ACT SELECT * FROM ACT_TEMP`-tip transfer, nu o derivare noua — dar nu am deschis
personal codul lor in aceasta runda.

View File

@@ -1,240 +0,0 @@
# Corespondenta cont de stoc (3xx) -> cont de venit (7xx): ce exista in cod
**Context**: continuare a `cont_venit_articol_fara_politica.md`, care stabilise ca `VANZARI_DETALII.CONT`
e un cont de **gestiune/stoc** (371, 301-303, 357), fara sa gaseasca un cont de venit pe linia de
factura in `pack_facturare`, si lasase neverificat unde se genereaza efectiv contul de venit.
Cercetarea de fata raspunde la cele 4 intrebari, cu corectii fata de raportul anterior.
## Raspuns scurt
1. **Nu exista un tabel de corespondenta "3xx -> 7xx"** (nume de forma `CORESPONDENTA*`,
`NOM_CONTURI*`, `CONT_VENIT*`, `ARTICOLE_CONTABILE*`) nicaieri in `SCRIPTURI_CLAR`. In schimb
exista un **mecanism echivalent functional, dar configurat manual, nu derivat automat din contul
de stoc**: tabelul `NOTE_CONTABILE` (coloane `SCD`/`ASCD` = cont+analitic debitor,
`SCC`/`ASCC` = cont+analitic creditor), legat de politica de pret a articolului prin
`CRM_POLITICI_PRETURI.ID_NOTA -> CRM_NOTE_VANZARI.ID_SET -> NOTE_CONTABILE`. Un contabil
configureaza acest tabel dintr-un ecran dedicat (`frm_config_note_contabile[2007]`), nu se
calculeaza din `NOM_ARTICOLE.CONT`/`STOC.CONT`.
2. **`NOM_ARTICOLE.CONT` NU e restrans la conturi de stoc (clasa 3).** Validarea la editare
(`verific_cont`) verifica doar ca respectivul cod exista in planul de conturi al anului curent
(`vplcont_sintetic`), fara filtru pe clasa. UI-ul trimite explicit catre "Planul de conturi"
(buton de help general), nu catre un subset de conturi de stoc. Asta confirma afirmatia lui
Marius: campul accepta orice cont, inclusiv 6xx/7xx.
3. **Nota contabila a vanzarii SE genereaza in `pack_facturare`** — raportul anterior a cautat
literalii `707`/`704`/`706`/`708` (care nu apar hardcodati nicaieri, corect) si a conchis gresit
ca lipseste mecanismul. El exista, dar e **indirect**: `pack_facturare.contabilizeaza_articol`
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7182-7556`) ia `SCD`/`SCC` din `NOTE_CONTABILE` (via
politica de pret a articolului) si scrie nota cu `pack_facturare.scrie_nota(...)`. `SCC` (contul
creditor) e contul de venit efectiv al liniei — nu vine din `VANZARI_DETALII.CONT` (care ramane
contul de gestiune, folosit separat, doar pentru descarcarea de gestiune, in
`descarca_gestiune`).
4. **`ID_VENCHELT`/`NOM_VENIT_CHELTUIELI` nu are coloana de cont** (nicio `ALTER TABLE
NOM_VENIT_CHELTUIELI ADD ... CONT` in `SCRIPTURI_CLAR`). E o dimensiune analitica separata
(clasificare venituri/cheltuieli, posibil pentru raportare/buget), transmisa ca parametru propriu
catre `scrie_nota`, in paralel cu `SCD`/`SCC` — nu e sursa contului.
## 1. Nu exista tabel de corespondenta 3xx->7xx; exista `NOTE_CONTABILE` + politica de pret
### Cautare tabel dedicat — negativa
Cautari in `D:\ROA\DATABASE\SCRIPTURI_CLAR` (`CREATE TABLE`/`ALTER TABLE`, case-insensitive) pentru
`CORESPONDENT*`, `NOM_CONTURI*`, `CONT_VENIT*`, `PLAN_CONTURI*`, `ARTICOLE_CONTABILE*`: niciun
rezultat relevant — singurele hituri pe `CORESPONDENT` sunt cuvantul romanesc generic ("banca
corespondenta" etc.) in sute de fisiere fara legatura, iar `CONT_VENIT` nu apare deloc ca nume de
tabel/coloana.
### Mecanismul real: lant de 4 tabele, configurat pe politica de pret
`pack_facturare.contabilizeaza_articol` (`ff_...:7227-7280`) foloseste acest cursor pentru a afla
contul debitor/creditor al liniei de vanzare:
```sql
CURSOR cursor_articol IS
SELECT A.ID_POL_ART, A.ID_POL, A.ID_ARTICOL, A.PRET, ...
NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT)) AS ID_VENCHELT,
NVL(pack_facturare.nid_sectie_stoc, C.ID_SECTIE) as ID_SECTIE,
C.ID_SET, D.EXPLICATIE, D.SCD, D.ASCD, D.SCC, D.ASCC, D.CU_TVA, ...
FROM CRM_POLITICI_PRET_ART A
LEFT JOIN CRM_POLITICI_PRETURI B ON A.ID_POL = B.ID_POL
LEFT JOIN CRM_NOTE_VANZARI C ON B.ID_NOTA = C.ID_NOTA
LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET
WHERE A.ID_POL = detalii_articol.id_pol AND A.ID_ARTICOL = detalii_articol.id_articol;
```
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7227-7280`)
Lantul e: **articol + politica de pret** (`CRM_POLITICI_PRET_ART`, deja documentat in raportul
anterior ca avand `PRET`/`PROC_TVAV`/`ID_VALUTA`, fara `CONT`) `->` **antetul politicii de pret**
(`CRM_POLITICI_PRETURI.ID_POL`, care are `ID_NOTA`) `->` **nota de vanzare CRM**
(`CRM_NOTE_VANZARI.ID_NOTA`, care are `ID_SET`) `->` **sablonul de nota contabila**
(`NOTE_CONTABILE.ID_SET`, care are `SCD`/`ASCD`/`SCC`/`ASCC`/`EXPLICATIE`/`CU_TVA`/`IN_VALUTA`).
Apoi, la scriere (`ff_...:7405-7437`):
```sql
CASE
WHEN pack_facturare.ntip <= 20 or pack_facturare.ntip IN (...) THEN
-- factura, factura roahotel
V_SCD := crs_rand_articol.scd;
V_ASCD := NVL(crs_rand_articol.ascd, PACK_FACTURARE.GetAnaliticByGrupUtilizatori(...));
WHEN pack_facturare.ntip in (28, 29) THEN
-- aviz catre clienti debitori
V_SCD := '461'; ...
ELSE
-- aviz
V_SCD := '418'; ...
END CASE;
V_SCC := crs_rand_articol.scc;
V_ASCC := NVL(crs_rand_articol.ascc, PACK_FACTURARE.GetAnaliticByGrupUtilizatori(...));
```
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7407-7437`)
Pentru o factura normala (`ntip <= 20`), `V_SCD` e contul debitor din nota (tipic un cont de
creante, 411/etc.), iar `V_SCC` e contul creditor din nota — **acesta e efectiv contul de venit**
al liniei (707/704/706/708, in functie de cum a configurat contabilul nota respectiva), preluat
**direct din `NOTE_CONTABILE.SCC`**, fara nicio derivare din contul de stoc al articolului. Perechea
`V_SCD`/`V_SCC` (plus `ID_VENCHELT`, `ID_SECTIE`, `EXPLICATIE`, cota TVA) e trimisa la
`pack_facturare.scrie_nota(...)` (`ff_...:7452-7476`), care scrie randul in nota contabila
efectiva a vanzarii.
**Concluzie fata de ipoteza lui Marius**: mecanismul exista si rezolva exact problema — "de unde
stiu contul de venit al unei linii de vanzare, indiferent daca articolul e gestionabil sau nu" —
dar **nu e o corespondenta automata cont-stoc -> cont-venit**. E o **configurare manuala per
politica de pret**: fiecare politica de pret (`CRM_POLITICI_PRETURI`) e legata la o "nota de
vanzare" CRM (`CRM_NOTE_VANZARI`), care la randul ei e legata la un sablon de nota contabila
(`NOTE_CONTABILE`) cu perechea SCD/SCC fixata de un contabil. Politica de pret aplicata articolului
decide, indirect, contul de venit — nu clasa/valoarea contului de gestiune al articolului.
### Ecranul de configurare (unde se seteaza SCD/SCC)
`D:\ROA\ROAFACTURARE\COMUN\ferestre\frm_config_note_contabile.sc2` (+ varianta
`frm_config_note_contabile2007.sc2` pentru anii >= 2007, selectata in
`COMUN\programe\omeniu_initializari.prg:257-282`) e formularul din meniu "Configurare note
contabile". Clasa e `onote_contabile.vc2`, cu un grid `gridInregistrari` avand coloanele
`cScd`/`cAscd`/`cScc`/`cAscc` (`onote_contabile.vc2:2401-2435`) legate direct de
`cnote_contab.scd`/`.scc`/`.ascd`/`.ascc`, si salvare prin INSERT/UPDATE direct pe
`note_contabile(explicatie,scd,ascd,scc,ascc,ordine,cu_tva,ptva,id_set,in_valuta,...)`
(`onote_contabile.vc2:315-322`, `:486-493`). Deci **contabilul e cel care scrie manual perechea
SCD/SCC** (inclusiv contul de venit `SCC`) pentru fiecare `id_set`/nota, nu exista automatism care
sa deriveze `SCC` din contul de gestiune al articolului.
## 2. `NOM_ARTICOLE.CONT` — nu e restrans la clasa 3
- **Camp**: `Clb_tx_cont.Text_simplu1.ControlSource = "porec.cont"`, `MaxLength = 4`, eticheta
`"Cont*"` (obligatoriu), buton de help cu tooltip `"Help - Planul de conturi (CTRL+ H)"` —
`COMUN\clase\onom_articole.vc2:1058-1087`. Trimiterea catre "Planul de conturi" (nu catre un
subset "conturi de stoc") sugereaza ca formularul asteapta orice cont valid, nu doar clasa 3.
- **Validare la Valid() al campului** (in alt formular generic de conturi, `onomenclatoare.vc2`, nu
in cel de articole, dar foloseste aceeasi functie globala):
```
PROCEDURE clb_cont.Text_simplu1.Valid
IF !EMPTY(this.Value)
lnVerificat = verific_cont(thisform.orec.cont)
IF lnVerificat < 0
RETURN 0
ENDIF
ENDIF
ENDPROC
```
(`COMUN\clase\onomenclatoare.vc2:3251-3258`)
- **`verific_cont`** (`COMUN\programe\oproceduri_comune.prg:2389-2415`):
```
Procedure verific_cont
Parameters tcCont, tlNoMessage
lcSel = [SELECT cont from ] + GCS + [.vplcont_sintetic WHERE TRIM(cont) = ?pcCont and an = ?gnAn ]
...
If Reccount('crs_verific') = 0
lnSucces = -1
Endif
If lnSucces < 0 And !tlNoMessage
amessagebox('Acest cont nu este definit in planul de conturi!', 0 + 48, 'Atentie')
Endif
Endproc
```
Singura conditie e ca `cont` sa existe in `vplcont_sintetic` (planul de conturi sintetic al
anului curent, `gnAn`) — **fara `LIKE '3%'`, fara `SUBSTR(cont,1,1) = '3'`, fara nicio alta
restrictie de clasa**. O varianta identica exista si in
`COMUN\programe\ooperatii_comune.prg:1307` (nu am comparat corp cu corp, dar semnatura e aceeasi).
- Nu exista `CHECK CONSTRAINT` pe `NOM_ARTICOLE.CONT` in `SCRIPTURI_CLAR` (cautare
`CHECK.*CONT`/`constraint.*CONT.*check` — zero rezultate relevante), deci nici la nivel de
baza de date nu exista o restrictie de clasa.
- Nu am gasit nicaieri in `COMUN` sau `ROAFACTURARE` o ramificare pe `Left(cont,1)`/
`SUBSTR(cont,1,1)` aplicata specific campului `NOM_ARTICOLE.CONT` (cautare in
`D:\ROA\ROAFACTURARE` si `D:\ROA\ROAFACTURARE\COMUN`) — singurul loc cu `SUBSTR(cont,1,1)` gasit e
in `onomenclatoare.vc2:3647`, un filtru de grid pe planul de conturi general (tab-uri "1", "2",
"3"... pentru navigare in plan), fara legatura cu articolele.
**Concluzie**: campul e validat generic (orice cont din planul de conturi al anului), fara
restrictie de clasa la nivel de UI sau DB. Asta confirma ce a spus Marius: un articol negestionabil
poate avea legitim un cont 6xx/7xx in `NOM_ARTICOLE.CONT`.
## 3. Unde se genereaza efectiv nota contabila a vanzarii
Raportul anterior cauta gresit literalii `707`/`704`/`706`/`708` (niciunul nu apare hardcodat — corect)
si a conchis ca nu exista mecanism in `pack_facturare`. De fapt **exista, dar e in `pack_facturare`,
nu intr-un pachet separat de contabilitate**:
- **Functia**: `pack_facturare.contabilizeaza_articol(detalii_articol VANZARI_DETALII_TEMP%ROWTYPE)`
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7182-7556`), apelata pe fiecare rand din
`VANZARI_DETALII_TEMP` — vezi apelul `V_INCASAT_CALCUL := V_INCASAT_CALCUL +
pack_facturare.contabilizeaza_articol(tab_detalii(i));` in `scrie_aviz_retur`
(`ff_...:7148-7150`); acelasi tipar se repeta si in fluxul principal de facturare (nu doar in
aviz-retur — semnatura si structura functiei arata ca acopera si `ntip <= 20`, adica factura
propriu-zisa, prin `CASE ... WHEN pack_facturare.ntip <= 20 ...` la `ff_...:7409-7421`).
- **Sursa `SCD`/`SCC`**: vezi sectiunea 1 — cursorul `cursor_articol` (`ff_...:7227-7280`), pe baza
politicii de pret a articolului (`detalii_articol.id_pol`), nu pe `VANZARI_DETALII.CONT`.
- **`VANZARI_DETALII.CONT` ramane folosit separat, doar pentru gestiune**: in aceeasi functie,
`descarca_gestiune(...)` primeste explicit `detalii_articol.cont` ca parametru
(`ff_...:7485-7507`, apelat cand `nscadere_stoc = 1 AND id_gestiune <> -1000 AND in_stoc = 1`) —
acesta e contul de gestiune/stoc (371/301-303/357 etc., documentat in raportul anterior),
folosit pentru randul de descarcare de gestiune (marfa iesita din stoc), complet independent de
`V_SCD`/`V_SCC` calculate mai sus pentru randul de venit al vanzarii. **Cele doua conturi
coexista pe aceeasi linie de vanzare, cu surse complet diferite**: unul (gestiune) vine din
`NOM_ARTICOLE.CONT`/`STOC.CONT`/`NOM_GESTIUNI.CONT` (cf. raport anterior), celalalt (venit) vine
din `NOTE_CONTABILE.SCC` via politica de pret.
- **Nu exista fallback/hardcodare** pentru `SCC`: daca `NOTE_CONTABILE` nu are un rand pentru
`ID_SET`-ul politicii, `LEFT JOIN`-urile din `cursor_articol` produc `NULL` pe `SCC`/`SCD`
(fara eroare explicita in acest cursor — spre deosebire de cazul `V_COMPUS`/politica lipsa la
`ff_...:7293-7311`, care ridica `FACT-024`).
## 4. `ID_VENCHELT` / `NOM_VENIT_CHELTUIELI`
- **Nu are coloana de cont**: nicio `ALTER TABLE NOM_VENIT_CHELTUIELI ADD ... CONT` in
`SCRIPTURI_CLAR` (cautare directa, zero rezultate) si nicio `CREATE TABLE NOM_VENIT_CHELTUIELI`
in arhiva (tabelul predateaza 2009, inceputul arhivei `SCRIPTURI_CLAR` — la fel ca
`NOTE_CONTABILE`, `CRM_NOTE_VANZARI`, `CRM_POLITICI_PRETURI`, pentru care nu exista `CREATE TABLE`
in arhiva din acelasi motiv).
- **Structura vazuta din uz**: `NOM_VENIT_CHELTUIELI.ID_VENCHELT%TYPE` (tip declarat repetat in
`pack_facturare`, ex. `ff_...:191`) si `ACT.ID_VENCHELT%TYPE` (`ff_...:6725`) — deci
`ID_VENCHELT` e o coloana FK pe `ACT` (tabelul de note contabile efective) si pe `CRM_NOTE_VANZARI`
/ `CRM_POLITICI_PRET_ART` (vezi `NVL(B.ID_VENCHELT, D.ID_VENCHELT)` la `ff_...:7205`, unde `B` =
`CRM_POLITICI_PRET_ART`, `D` = `CRM_NOTE_VANZARI` in cursorul comentat din pachet).
- **Cine il consuma**: `pack_facturare.contabilizeaza_articol` il rezolva cu prioritate similara
contului: `NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))` (override global de
sesiune -> articol/politica -> nota de vanzare) si il trimite ca parametru propriu catre
`scrie_nota(...)` (`ff_...:7461`), **separat** de `SCD`/`SCC`. E deci o **a treia dimensiune**
(clasificare venituri/cheltuieli) atasata randului de nota contabila, nu sursa contului insusi —
raportul anterior avea dreptate sa il caracterizeze ca "o clasificare, nu un cont", desi nu
urmarise consumatorul pana la capat.
- Numele `NOM_VENIT_CHELTUIELI` si folosirea alaturi de `SCD`/`SCC`/`ID_SECTIE` sugereaza un
centru de venituri/cheltuieli pentru raportare (posibil situatii financiare pe sectii/centre de
cost), nu un mecanism alternativ de determinare a contului 7xx.
## Neverificat
- **Structura completa (toate coloanele) a `NOTE_CONTABILE`, `CRM_NOTE_VANZARI`,
`CRM_POLITICI_PRETURI` si `NOM_VENIT_CHELTUIELI`**: niciuna nu are `CREATE TABLE` in
`SCRIPTURI_CLAR` (arhiva incepe 2009, tabelele sunt mai vechi). Coloanele folosite in cod sunt
confirmate (`SCD`/`ASCD`/`SCC`/`ASCC`/`EXPLICATIE`/`CU_TVA`/`IN_VALUTA`/`ID_SET` pe
`NOTE_CONTABILE`; `ID_NOTA`/`ID_SET` pe `CRM_NOTE_VANZARI`; `ID_NOTA`/`ID_POL` pe
`CRM_POLITICI_PRETURI`; `ID_VENCHELT` pe `NOM_VENIT_CHELTUIELI`), dar lista completa ar necesita
interogarea schemei Oracle live (`DESC NOTE_CONTABILE` etc.) sau un export DDL mai vechi decat
2009, in afara bugetului acestei cercetari.
- **Continutul efectiv al `NOTE_CONTABILE.SCC`** pentru notele configurate curent (adica ce conturi
707/704/706/708 sunt de fapt setate, si daca fiecare politica de pret are o nota configurata) —
ar necesita interogarea datelor din schema Oracle live, nu doar codul static.
- **Daca `contabilizeaza_articol` e chiar apelata pe fluxul principal de facturare** (nu doar din
`scrie_aviz_retur`) — am dedus asta din `CASE ... ntip <= 20 ...` (factura normala tratata explicit
in functie), dar nu am urmarit *toti* apelantii ei in fisier (fisierul are 17000+ linii; cautarea
`pack_facturare.contabilizeaza_articol` ar trebui repetata exhaustiv daca se doreste certitudine
completa pe toate punctele de intrare — facturare avans, deviz, retail etc.).
- **Comportamentul cand `NOTE_CONTABILE`/`CRM_NOTE_VANZARI` lipsesc pentru o politica de pret**:
cursorul foloseste `LEFT JOIN`, deci `SCD`/`SCC` ar ajunge `NULL` fara eroare explicita in acest
punct — nu am verificat daca exista o validare ulterioara (la `scrie_nota` sau la commit-ul notei)
care sa blocheze o nota cu cont `NULL`.

View File

@@ -1,800 +0,0 @@
# CORESP_CONT_VENCHELT ca sursa de cont de venit pentru articole fara politica de pret (decizia 27)
Cercetare pe cod, read-only, fara modificari. Continua `cont_venit_articol_fara_politica.md` si
`cont_venit_corespondente.md` (care stabilisera ca SCC vine azi din `NOTE_CONTABILE` prin lantul
politicii de pret) si `nota_contabila_fara_politica.md` (care stabilise ca `contabilizeaza_articol`
ridica FACT-024 si opreste tranzactia cand nu exista politica).
## 9. Intrebarea care putea rasturna tot: "in ROAACNPRO si pe factura din comanda/contract se adauga articole fara politica de preturi — de ce nu si aici?"
**Raspuns scurt, verificat pe cod: impresia e explicabila, dar niciunul din cele doua exemple nu e de
fapt un articol fara politica trecut cu bine prin `contabilizeaza_articol`. FACT-024 ramane blocantul
real — nu exista azi, in productie, niciun flux in care un articol ajunge la `contabilizeaza_articol`
fara sa fie membru al unei politici si sa treaca fara eroare.**
### 9a) Ce verifica de fapt FACT-024 — apartenenta articolului la politica liniei, nu "id_pol e gol"
Blocul (`D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7278-7302`, citat integral
la sectiunea 6) face:
```sql
SELECT COMPUS, ID_POL_ART
INTO V_COMPUS, V_ID_POL_ART
FROM VCRM_POLITICI_PRET_ART
WHERE ID_ARTICOL = detalii_articol.id_articol
AND ID_POL = detalii_articol.id_pol;
```
Mesajul confirma exact asta: `'Articolul ' || ... || ' nu este definit in politica de preturi ' || ...`
— verifica **apartenenta articolului la politica `id_pol` care a ajuns pe linie**, nu doar "exista vreun
`id_pol`". Insa `id_pol` **nu e derivat de Oracle** din client/contract/tip document — e parametru de
intrare explicit (`V_ID_POL IN NUMBER`, `adauga_articol_factura`, `ff_...:4993`, citat integral la
sectiunea 8a), trimis de VFP pe fiecare linie. Deci sunt **doua cauze distincte care duc la aceeasi
eroare**:
1. `id_pol` e NULL/gol pe linie (articolul a fost ales dintr-o cautare care nu are deloc coloana
`id_pol` — cazul `caut_articol`, sectiunea 9d) — `NO_DATA_FOUND` trivial (`X = NULL` nu se potriveste
niciodata).
2. `id_pol` e o valoare reala, dar articolul **nu e** in lista acelei politici — la fel `NO_DATA_FOUND`,
cu acelasi mesaj.
Ambele cauze converg spre FACT-024; codul nu le distinge in eroare. Formularea din intrebarea lui
Marius ("nu se cauta politica articolului, ci apartenenta articolului la politica documentului") e
**partial corecta**: verificarea e de apartenenta, dar `id_pol` nu vine din "antetul documentului" ca
o singura valoare fixa — vine per-linie, de la formularul de adaugare a articolului (sectiunea 9d).
### 9b) ROAACNPRO — importul Roris/Contracte NU trece deloc prin `contabilizeaza_articol`
Verificat direct in `D:\ROA\ROAACNPRO\Programe\proceduri_acnpro.prg` (read-only). Cautare
`pack_facturare\.|pack_acn\.|pack_contafin\.` in tot fisierul — apelurile de la salvarea facturii
(`factura_salvare_db`, `:3331-3467`) sunt:
```
proceduri_acnpro.prg:3332 lcSql = [begin pack_facturare.initializeaza_scriere_actrul(NULL,1); end;]
proceduri_acnpro.prg:3343 lcSql = [begin pack_facturare.initializeaza_date_factura(...
proceduri_acnpro.prg:3382 lcSql = [begin pack_facturare.adauga_articol_factura_deviz(...
proceduri_acnpro.prg:3412 lcSql = [begin pack_facturare.scrie_in_vanzari(0,...
proceduri_acnpro.prg:3430 lcSql = [begin pack_acn.salveaza_regdoc(?.id, ?.id_vanzare, ?.tip, ...
```
**Exact tiparul deja documentat pentru ROAAUTO "Alte servicii" in `nota_contabila_fara_politica.md`**:
`adauga_articol_factura_deviz` (insereaza doar in `VANZARI_DETALII_TEMP`, fara `ID_POL` — semnatura
n-are acest parametru) urmat de `scrie_in_vanzari` (copiaza in `VANZARI_DETALII`, **fara sa scrie vreo
nota contabila de venit**) — **`contabilizeaza_articol` nu apare deloc** in acest fisier (cautare
directa `contabilizeaza_articol` in `proceduri_acnpro.prg`: zero rezultate). Nota contabila a
documentului ACN o scrie **`pack_acn.salveaza_regdoc`**, o procedura complet separata, specifica ACN,
apelata cu parametri proprii (`?.id_articol, ?.id_locatia, ?.id_valuta, ?.id_client, ?.id_contract,
?.pret, ?.curs, ?.cantitate, ?.valval, ?.tvaval, ?.totval, ...`) — **fara `id_pol`** in lista de
parametri. Confirmat si de raportul deja existent `COMUN\docs\cercetare\import_roris_roaacnpro.md`
(sectiunea 3): "**nicio procedura Oracle stocata (`pack_*`) nu e apelata in etapa de import** ...
acestea sunt apelate abia la salvarea finala", si articolele/preturile vin din dialoage de calcul tarife
proprii ACN (`frm_calcul_tranzit`, `vctr_articole2`), nu din `CRM_POLITICI_PRET_ART`.
**Concluzie 9b**: ROAACNPRO nu e un exemplu de "articol fara politica trecut cu bine prin
`contabilizeaza_articol`" — e un produs care **nu foloseste deloc** acest mecanism de contabilizare
pentru facturile lui. Impresia lui Marius e intemeiata pe experienta ("acolo merge fara sa aleg vreo
politica"), dar explicatia e alta decat "FACT-024 se poate evita" — e ca **acel flux nu ruleaza deloc
codul care ar putea da FACT-024**.
### 9c) Factura din comanda/contract in ROAFACTURARE — articolul "liber" e de fapt din lista de preturi
Deja cercetat, cu dovezi, in `docs\cercetare\retur_si_lista_preturi.md` sectiunea B (citit integral aici,
nu doar rezumat). Punctul central (`retur_si_lista_preturi.md` sectiunea B7):
- **Contract** (tip 2,6,26,52): `crsarticole` (grila principala de articole, folosita si pentru
adaugare "libera") e populat de `pack_facturare.cursor_contract(...)`, care da **lista de preturi
completa**, nerestrictionata la continutul contractului — "azi se poate deja adauga liber din lista de
preturi pe un document contract".
- **Comanda** (tip 3): `crsarticole` e populat de `cursor_comanda`, **doar** articolele comenzii — nu
exista adaugare libera in fluxul normal (doar prin copiere de document, care re-adauga tot din
`cursor_preturi`).
Verificat aici, suplimentar, ca `cursor_preturi` (folosit si de `cursor_contract`, structura identica)
**selecteaza explicit `A.ID_POL`** ca coloana de output:
```sql
-- ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2162-2167 (cursor_preturi, ramura V_TIP = 45, tipic pentru toate ramurile CASE)
OPEN V_CURSOR FOR
SELECT rownum as id_c,
B.ID_ARTICOL,
NULL AS LOT,
NULL as SERIE,
A.ID_POL,
...
```
si ca cursorul insusi porneste, la fiecare apel, prin `pack_facturare.completare_politica_stoc()`
(`ff_...:2151`), care garanteaza deja randuri in `CRM_POLITICI_PRET_ART` pentru toate articolele
`IN_STOC=1` (procedura citata integral la sectiunea 8c2). Deci **fiecare articol afisat in grila
"lista de preturi"/"contract" vine deja cu un `id_pol` valid, dintr-o politica reala**, exact pentru ca
e extras printr-un join pe `CRM_POLITICI_PRETURI`/`CRM_POLITICI_PRET_ART`. "Adaugare libera pe contract"
nu inseamna "articol fara politica" — inseamna "orice articol din lista de preturi, nu doar cele din
contract", dar tot cu politica ceruta, doar aleasa implicit prin cursor, nu prin dialogul manual
`do_cauta_politica`.
**Concluzie 9c**: nu e un contraexemplu la FACT-024 — e acelasi mecanism (articol + politica), livrat
printr-o alta grila de selectie (`crsarticole` populat de `cursor_contract` in loc de `caut_articol`),
care intampla sa nu ceara utilizatorului sa caute manual politica pentru ca cursorul o aduce deja
atasata pe fiecare rand.
### 9d) Ce diferentiaza de fapt "adaugare din nomenclator" (cazul #13) de "adaugare din lista de preturi/contract" (cazul care merge azi)
Diferenta e cursorul sursa al gridului de articole, nu tipul de document:
- **`caut_articol`** (`COMUN\programe\ocautare.prg:1636-1735`, folosit pentru cautare libera in
nomenclator) — SELECT-ul confirmat la `ocautare.prg:1671-1689` (`vnom_articole_crm`/`vnom_articole`):
**nu are coloana `id_pol`** in nicio ramura (`tlCRM` sau nu). Un articol ales prin acest dialog ajunge
in `poArticol` (scatter din `crsarticole`, `ofacturare.vc2:12850-12851`) **fara `id_pol`** — exact
cazul care da FACT-024 azi, si exact cazul cerut de povestea #13 ("adaugat direct din nomenclator").
- **`cursor_preturi`/`cursor_contract`** (Oracle, sectiunea 9c) — id_pol e parte din rezultat, pentru ca
interogarea porneste de la politica de pret, nu de la nomenclator.
Deci povestea #13 cere exact ce nu exista azi: un articol ales **prin cautarea de nomenclator**
(fara trecere prin vreo politica), caruia i se cere totusi sa treaca prin `contabilizeaza_articol` fara
eroare. Niciunul din exemplele lui Marius (ROAACNPRO, contract) nu demonstreaza ca asta functioneaza deja
undeva — primul nu foloseste deloc functia, al doilea nu e de fapt "fara politica".
### Verdict 9
**FACT-024 ramane blocantul real**, confirmand (nu contrazicand) analiza din sectiunile 6-8. Nu exista
azi, pe niciun flux de productie cercetat, un caz in care un articol trece prin
`pack_facturare.contabilizeaza_articol` fara sa fie membru al unei politici de pret si nu cade cu
eroare. Impresia lui Marius e intemeiata pe doua experiente reale, dar explicatia lor e alta:
- ROAACNPRO: alt produs, alta procedura de contabilizare (`pack_acn.salveaza_regdoc`), niciodata
`contabilizeaza_articol`;
- Contract in ROAFACTURARE: articolul PARE liber, dar e adus printr-un cursor care il livreaza deja cu
o politica reala atasata (`cursor_contract`/`cursor_preturi` + `completare_politica_stoc`).
Asta nu inseamna ca planul A (sectiunea 8, VFP alege singur un `id_pol`) sau planul B (sectiunea 6,
fallback in pachet) sunt de abandonat — arata insa ca **niciuna din cele doua nu se poate simplifica
la "nu faceti nimic, functioneaza deja"**: mecanismul de protectie e real si consecvent, nu un artefact
uitat.
## Constrangere noua (Marius, aparuta in timpul cercetarii): pack_facturare e plan B
Marius prefera sa **nu se scrie cod nou in `pack_facturare`** — e pachetul comun folosit de toata
suita, si orice ramura noua acolo e risc peste tot, nu doar in ROAFACTURARE. Sectiunea 6 (punctul de
injectie in pachet) ramane in raport ca informatie, dar devine **plan B**. Intrebarea reala, tratata in
sectiunea 8 de mai jos: **se poate obtine acelasi rezultat din VFP, alegand singur un `id_pol` potrivit,
fara sa se modifice `pack_facturare`?** Raspuns scurt: **da, ingredientele exista deja**, dar solutia nu
e "zero cod" — e cod VFP nou (nu Oracle), care se sprijina pe 3 mecanisme deja functionale in productie.
Detalii in sectiunea 8.
## Concluzie: fezabil cu conditii, si conditiile sunt mai mari decat pare din formularea deciziei 27
0. **Verificare prioritara (sectiunea 9, ceruta de Marius in timpul cercetarii): FACT-024 ramane
blocantul real.** Nu exista azi niciun flux de productie in care un articol trece prin
`contabilizeaza_articol` fara sa fie membru al unei politici de pret si nu cade cu eroare.
ROAACNPRO nu e un contraexemplu — nu foloseste deloc `contabilizeaza_articol` (alta procedura de
contabilizare, `pack_acn.salveaza_regdoc`). Contractul in ROAFACTURARE nu e un contraexemplu —
articolele "libere" vin din `cursor_contract`/`cursor_preturi`, care le ataseaza deja o politica
reala. Detalii complete in sectiunea 9.
1. **Tabelul exista, e populat cu date reale si e folosit activ in productie** — dar niciodata pentru
`CONT_VENIT`. Toti consumatorii gasiti (pack_vin, pack_devize, gestiune_pack_gest_import,
gestiune_rapoarte) citesc `CONT_CHELT` (sau `CONT_APROVIZIONARE`), niciunul `CONT_VENIT`. Coloana
`CONT_VENIT` e completata cu date reale (o migrare din 2023 acopera 20 de conturi de gestiune), dar
n-are niciun consumator — nici Oracle, nici VFP. Cititul ei pentru facturare ar fi un consumator nou,
nu o reteta deja rulata in productie.
2. **Cheia de join e stabila si testata**: mereu `CONT` (contul de gestiune al liniei), cu filtru
`STERS = 0`, prin `LEFT JOIN`. Pattern-ul se poate copia identic pentru `CONT_VENIT`.
3. **Punctul care schimba verdictul de la "fezabil simplu" la "fezabil cu conditii mari"**: in
`contabilizeaza_articol`, ramura "articol simplu" nu executa **nimic** (nici `scrie_nota`, nici
`descarca_gestiune`) daca `cursor_articol` (care citeste din `CRM_POLITICI_PRET_ART`) nu gaseste
niciun rand — si nu gaseste niciun rand exact in cazurile in care azi se ridica FACT-024. Un simplu
"prinde exceptia si continua" ar produce o linie facturata **fara nota contabila si fara descarcare
de gestiune, fara nicio eroare** — mai rau decat blocajul actual. Fallback-ul cere o **ramura noua,
paralela cursorului**, nu doar inlocuirea unei valori.
4. **Chiar cu ramura noua, `CU_TVA` si `IN_VALUTA` (cota TVA inclusa in pret / linie in valuta) nu au
nicio sursa alternativa** in afara `NOTE_CONTABILE`. Corespondentele si `NOM_ARTICOLE.CONT` dau doar
un cont, nu aceste doua semnale de interpretare a pretului — ele trebuie fie hardcodate (risc de
calcul gresit al bazei/TVA), fie cerute ca intrare noua de la utilizator/articol.
5. **704 ca literal de fallback are un precedent real in suita**, dar in alt subsistem: proprietatea
`cconte = 704` ("cont implicit articol client") din `frm_configurare_efactura`
(`COMUN\clase\anaf_efactura.vc2:8376`), folosita la import eFactura, nu in `pack_facturare`. Nu e o
reteta gata scrisa pentru facturare, dar arata ca alegerea lui Marius nu e arbitrara in suita.
## 1. `CORESP_CONT_VENCHELT` exista? Structura, DDL, consumatori
**Exista.** Nu are `CREATE TABLE` in arhiva `D:\ROA\DATABASE\SCRIPTURI_CLAR` (arhiva incepe 2009-2010,
tabelul e mai vechi — acelasi motiv pentru care `NOTE_CONTABILE`/`CRM_NOTE_VANZARI` nu au `CREATE TABLE`
in arhiva, documentat deja in `cont_venit_corespondente.md`).
**Coloane confirmate din `ALTER TABLE` + `CREATE OR REPLACE VIEW` (cronologic):**
- `CONT`, `CONT_CHELT`, `CONT_VENIT`, `STERS` — preexistente arhivei (folosite direct in view-ul din
2010 fara `ALTER TABLE` premergator vizibil).
- `CONT_APROVIZIONARE varchar2(4)` — adaugata 19.02.2010
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2010\02\ff_2010_02_19_03_GESTIUNI.sql:9`:
`alter table CORESP_CONT_VENCHELT add CONT_APROVIZIONARE varchar2(4);`).
- `CONT_DIFERENTE varchar2(4)` — adaugata 05.02.2015
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2015\02\ff_2015_02_05_01_GESTIUNE.sql:6-7`:
`alter table coresp_cont_venchelt add cont_diferente varchar2(4);` +
`comment on column CORESP_CONT_VENCHELT.cont_diferente is 'cont diferente de pret pe NIR fata de
pretul din factura de achizitie (ex: 6588 = 401 sau 308 = 401)';`).
- Nu am gasit `ID_CCV`/`DATAORAS` in DDL-ul cercetat (cautare directa, zero rezultate in
`SCRIPTURI_CLAR`) — interogarea din decizia 27 (`select id_ccv, cont, cont_chelt, cont_venit, sters,
dataoras, cont_aprovizionare, cont_diferente from coresp_cont_venchelt`) le presupune, dar tabelul
fiind pre-arhiva, DDL-ul complet nu e in cod static. **Ramane de verificat pe baza vie** (vezi sectiunea
finala) — probabil `ID_CCV` e cheia surogat (PK) si `DATAORAS` un timestamp tehnic, tipar comun in
suita ROA pentru tabelele de configurare, dar neconfirmat din DDL.
- Toate coloanele `CONT*` sunt `varchar2(4)` (confirmat din `ALTER TABLE` explicit pentru
`CONT_APROVIZIONARE`/`CONT_DIFERENTE`; consistent cu tratarea lor ca cod de cont in tot codul citit).
**View-ul consumat de VFP**, `VCORESP_CONT_VENCHELT`, filtreaza deja `STERS = 0` (deci VFP nu mai trebuie
sa filtreze separat):
```sql
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2015\02\ff_2015_02_05_01_GESTIUNE.sql:10-13
create or replace view vcoresp_cont_venchelt as
select cont, cont_chelt, cont_venit, cont_aprovizionare, cont_diferente
from coresp_cont_venchelt
where sters = 0;
```
**Populare cu date reale — `CONT_VENIT` e completat, nu doar declarat.** Migrare dedicata 23.02.2023
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2023\02\ff_2023_02_23_01_COMUN.sql:1-21`, titlul chiar din fisier:
`-- Completare cont venit pe corespondenta 3xx - 6xx/7xx`):
```sql
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '301';
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '302';
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '3021';
... (3022,3023,3024,3025,3026,3028, 303 -> tot '707')
update CORESP_CONT_VENCHELT set cont_venit = '711' where cont = '331';
update CORESP_CONT_VENCHELT set cont_venit = '711' where cont = '332';
update CORESP_CONT_VENCHELT set cont_venit = '702' where cont = '341';
update CORESP_CONT_VENCHELT set cont_venit = '7015' where cont = '345';
update CORESP_CONT_VENCHELT set cont_venit = '703' where cont = '346';
update CORESP_CONT_VENCHELT set cont_venit = '7015' where cont = '348';
update CORESP_CONT_VENCHELT set cont_venit = '7018' where cont = '361';
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '371';
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '381';
```
Deci pentru conturile de gestiune uzuale (marfuri 371/381, materii prime 301-303/3021-3028, produse
finite 345/348, semifabricate 341, animale 346, ambalaje 381) exista deja o valoare `CONT_VENIT`
configurata in Oracle, de 3 ani. **Datele exista** — intrebarea e doar cine le citeste.
**Ecran de configurare in VFP: NU exista.** Cautare exhaustiva `CORESP_CONT_VENCHELT` (case-insensitive)
in `D:\ROA\ROAFACTURARE\COMUN` -> exact 3 fisiere, niciunul un formular de editare:
- `COMUN\clase\ointroduceri.vc2:2` — un comentariu de istoric (19.02.2010, mentioneaza
`crsconfigcvc (coresp_cont_venchelt)`), nu cod.
- `COMUN\programe\ointroduceri.prg:733` (procedura `update_corespondente_cvc`, citata integral mai jos)
— un `SELECT` care **reincarca** un cursor local, nu scrie in tabel.
- `COMUN\programe\updateserver.prg:733` — acelasi `SELECT`, alt loc de apel.
```
-- COMUN\programe\ointroduceri.prg:727-743
Procedure update_corespondente_cvc
If Used('crsconfigcvc')
Use In crsconfigcvc
Endif
*!* 19.02.2010
lcSql = [select cont,cont_chelt,cont_venit, cont_aprovizionare, cont_diferente from vcoresp_cont_venchelt order by cont]
*!* 19.02.2010 ^
lnSucces = goExecutor.oExecute(lcSql,[crsconfigcvc])
If lnSucces < 0
amessagebox(goExecutor.cEroare,16,"Eroare")
Endif
goExecutor.oReset()
Return lnSucces
Endproc
```
Deci tabelul e intretinut exclusiv prin **scripturi SQL manuale** (cele din `SCRIPTURI_CLAR`, rulate de un
DBA/dezvoltator la nevoie), nu printr-un formular VFP — la fel ca alte tabele tehnice de configurare mai
vechi din suita. Nu exista niciun `INSERT INTO CORESP_CONT_VENCHELT` in arhiva (randurile de baza,
`CONT`/`CONT_CHELT`/`CONT_VENIT`, predateaza arhiva); doar `UPDATE`/`ALTER TABLE` pentru coloanele
adaugate ulterior (`CONT_APROVIZIONARE` in 2010, `CONT_DIFERENTE` in 2015, valorile `CONT_VENIT` in
2023).
## 2. Cheia de cautare
**`CONT` = contul de gestiune al liniei, mereu.** Confirmat prin 4 exemple reale de `JOIN`, in module
diferite:
- **pack_vin** (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2023\07\ff_2023_07_18_02_VIN_PACK_VIN.sql:1579-1589`,
procedura `inregistreaza_materiale`):
```sql
FROM (SELECT SUM(...) as suma, A.CONT as SCC, A.ACONT as ASCC, A.ID_GESTIUNE
FROM VIN_RULAJE_TEMP A WHERE A.TIP = V_TIP
GROUP BY A.ID_GESTIUNE, A.CONT, A.ACONT) A
LEFT JOIN CORESP_CONT_VENCHELT B
ON A.SCC = B.CONT
AND B.STERS = 0;
```
aici `A.SCC` e de fapt contul de gestiune (aliasul `SCC` vine din contextul rulajului de stoc, nu din
`NOTE_CONTABILE`) — deci join-ul e tot `cont_gestiune = B.CONT`.
- **pack_devize** (14+ aparitii identice intre 2013-2014, ex.
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2014\01\ff_2014_01_22_01_DEVIZE_PACK_DEVIZE.sql:3214-3217`):
```sql
from rul_temp a
left join coresp_cont_venchelt b
on a.cont = b.cont
and b.sters = 0
```
`rul_temp.cont` e contul de gestiune al articolului din rulajul de stoc.
- **gestiune_pack_gest_import** (5+ aparitii 2013-2017, ex.
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2017\05\ff_2017_05_31_01_GESTIUNE_PACK_GEST_IMPORT.sql:512`):
`left join coresp_cont_venchelt b` — pe `a.cont = b.cont` (acelasi tipar).
- **Raport recent, 2026** (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\07\ff_2026_07_23_01_GESTIUNE_RAPOARTE.sql:25-29`,
view `VRUL_ACT_CHELTUIELI`):
```sql
iesiri AS (
SELECT R.*, CC.CONT_CHELT AS CONT_CHELT_CORESP
FROM VRUL_TOT R
LEFT JOIN CORESP_CONT_VENCHELT CC ON CC.CONT = R.CONT AND CC.STERS = 0
WHERE R.STERS = 0 AND R.CANTE <> 0
)
```
cel mai recent consumator gasit (23.07.2026), deci tabelul e inca "viu" in productie — dar
`CONT_CHELT_CORESP` calculat aici nici nu apare in `SELECT`-ul final al view-ului (e calculat si
abandonat), semn ca tiparul de "join pe CONT, ia campul de care ai nevoie" e suficient de standard
incat sa fie reutilizat fara alta discutie de design.
- **In VFP**: `Select crsconfigcvc; Locate For Cont = Upper(Alltrim(...))` — vezi sectiunea 3, mereu
aceeasi cheie `CONT`.
**Nu am gasit nicio discriminare suplimentara pe gestiune/tip document.** Toate `JOIN`-urile/`LOCATE`
sunt strict pe `CONT` (+ `STERS = 0`), fara `ID_GESTIUNE`, fara `TIP_DOC`. `ID_CCV` nu apare folosit ca
FK in niciun cod cercetat — pare o simpla cheie surogata a tabelului, neexpusa in `VCORESP_CONT_VENCHELT`
(view-ul nu o include).
**Ambiguitate / mai multe randuri active pentru acelasi `CONT`**: nu exista `UNIQUE`/`PRIMARY KEY`
vizibil in cod (DDL-ul e pre-arhiva), dar **toate cele ~20 de utilizari reale presupun un singur rand
activ per `CONT`** — niciun cod cercetat foloseste `DISTINCT`, `ROW_NUMBER()`, `MAX(...)` sau alt
mecanism de dezambiguizare pe rezultatul join-ului. Daca ar exista doua randuri active cu acelasi `CONT`,
`LEFT JOIN`-ul ar multiplica randurile din interogarea principala (risc de dublare de suma in
`pack_vin`/`pack_devize`) — nu exista protectie in cod contra acestui caz. **Presupunerea de unicitate e
implicita in tot codul care exista deja**, nu doar in propunerea lui Marius.
## 3. Cine il foloseste azi — CONT_CHELT are 4 module consumatoare, CONT_VENIT are zero
| Consumator | Fisier | Coloana citita |
|---|---|---|
| `pack_vin.inregistreaza_materiale` | `ff_2023_07_18_02_VIN_PACK_VIN.sql:1567` (si variante 2010/2012/2014) | `CONT_CHELT` |
| `pack_devize` (procedura de calcul cost material, 14+ export-uri 2013-2014) | `ff_2014_01_22_01_DEVIZE_PACK_DEVIZE.sql:3222` (si altele) | `CONT_CHELT` (in `GROUP BY`, folosit ca `SCD`) |
| `pack_gest_import` (5+ export-uri 2013-2017) | `ff_2017_05_31_01_GESTIUNE_PACK_GEST_IMPORT.sql:512` | `CONT_CHELT` (deductie din context, tipar identic) |
| `VRUL_ACT_CHELTUIELI` (raport, 2026) | `ff_2026_07_23_01_GESTIUNE_RAPOARTE.sql:26` | `CONT_CHELT` (calculat, needatat in output final) |
| VFP `ointroduceri.vc2` (bon consum, finalizare aprovizionare, transfer) | `:5476-5490` (`oinventar.vc2`), `:14663-14681`, `:15327-15332` | `CONT_CHELT`, `CONT_APROVIZIONARE` |
| VFP `ointroduceri.vc2` (diferente NIR vs factura) | `:15395-15403` (bloc dezactivat `IF .F.`) | `CONT_DIFERENTE` (cod comentat/mort, nu ruleaza azi) |
**Niciun consumator pentru `CONT_VENIT`** — cautare directa `CONT_VENIT` (case-insensitive) in tot
`D:\ROA\DATABASE\SCRIPTURI_CLAR` (arhiva completa 2009-2026): doar 3 fisiere, toate deja citate (2 view-uri
care includ coloana in `SELECT` fara sa o consume, 1 script de populare cu date). In VFP, cautare
`cont_venit` (case-insensitive) in `COMUN` si `ROAGEST`: un singur hit, `SELECT ... cont_venit ...` in
`update_corespondente_cvc`/`updateserver.prg` (fetch in cursor), **zero utilizari ale campului dupa
fetch** (nicio referinta `.cont_venit`/`crsconfigcvc.cont_venit` gasita).
**Concluzie**: propunerea lui Marius ar fi **primul consumator real al `CONT_VENIT`** in toata suita,
desi coloana e populata din 2023. Reteta "citeste corespondenta pe cont de gestiune" e deja rulata in
productie de 10+ ani, dar mereu pentru latura `CONT_CHELT` (cheltuiala la consum de stoc), niciodata
pentru `CONT_VENIT` (venit la vanzare). Riscul tehnic al join-ului (cheie, filtru `STERS`, tipul
`LEFT JOIN`) e deja validat de utilizarea existenta; riscul de **date** (daca valorile `CONT_VENIT`
completate in 2023 sunt corecte/complete pentru toate conturile de gestiune folosite azi in facturare,
nu doar cele 20 acoperite explicit) e nou si neverificat pe baza vie.
## 4. Semantica coloanelor
Dedusa din utilizari reale, nu din nume:
- **`CONT`**: contul de gestiune/stoc al liniei (clasa 3xx: 301-303, 331/332, 341, 345/346/348, 361,
371, 381) — cheia de cautare in toate cazurile vazute (sectiunea 2).
- **`CONT_CHELT`**: contul de cheltuiala (6xx) care se debiteaza cand marfa/materialul iese din gestiune
(consum, bon de consum, productie) — confirmat de toti cei 4+ consumatori din sectiunea 3, unde e
folosit mereu ca `SCD` (debit) pe o nota de tip "iesire din gestiune".
- **`CONT_VENIT`**: prin simetrie de nume si migrarea din 2023 ("cont venit pe corespondenta 3xx-7xx"),
e menit sa fie contul de venit (7xx) corespunzator vanzarii aceluiasi cont de gestiune — dar **nu are
niciun consumator care sa confirme comportamentul in executie** (nicio ramura de cod care sa arate ce
se intampla la `NULL`, la vanzare partiala, la retur). Semantica e dedusa din nume + date, nu din
utilizare, contrar regulii "nu presupune semantica dupa nume" — de aceea marchez ca **ipoteza, nu
fapt verificat pe comportament**.
- **`CONT_APROVIZIONARE`**: contul folosit la operatia "finalizare aprovizionare" (`ID_SET` 264/265),
cand se transfera valoarea din contul de tranzit (401 sau alt cont de gestiune) intr-un cont de
gestiune specific de tranzit intern (32x) — confirmat de comentariul din DDL 2010 si de utilizarea in
`ointroduceri.vc2:15323-15332` (`Case Alltrim(Upper(oSet.explicatia)) = 'FINALIZARE APROVIZIONARE' ...
lcContC = Alltrim(cont_aprovizionare)`).
- **`CONT_DIFERENTE`**: contul folosit pentru diferentele de pret intre NIR si factura de achizitie —
confirmat de comentariul DDL 2015 ("cont diferente de pret pe NIR fata de pretul din factura de
achizitie, ex: 6588 = 401 sau 308 = 401") si de codul `ointroduceri.vc2:15394-15403`, dar acel bloc e
**dezactivat** (`IF .F. THEN ... ENDIF`), deci in executia curenta ramane mereu fallback-ul hardcodat
`'6588'` — dovada ca chiar si un camp cu semantica clara si comentariu explicit poate ajunge neutilizat
in productie, ceea ce intareste incertitudinea despre `CONT_VENIT`.
- **`STERS`**: flag boolean soft-delete, filtrat explicit `= 0` in **toate** utilizarile gasite (view-ul
`VCORESP_CONT_VENCHELT` il filtreaza deja la nivel de view; join-urile Oracle directe pe tabel il
filtreaza explicit `AND B.STERS = 0`/`AND CC.STERS = 0`). Niciun cod cercetat citeste randuri cu
`STERS = 1`.
- **`DATAORAS`**: nu apare in niciun cod cercetat (Oracle sau VFP) — nu am gasit nicio referinta directa
la aceasta coloana in afara interogarii propuse in decizia 27. Tipic in suita ROA e un timestamp
tehnic de audit (data ultimei modificari a randului), dar **neconfirmat aici** — ramane de verificat pe
schema vie.
- **Ambiguitate pe `CONT`**: vezi sectiunea 2 — niciun cod nu trateaza cazul "mai multe randuri active
pentru acelasi CONT"; presupunerea de unicitate e implicita, nu impusa de o constrangere vizibila.
## 5. `NOM_ARTICOLE.CONT` pentru articole negestionabile si fallback pe 704
- **`NOM_ARTICOLE.CONT` nu e restrans la clasa 3** — reconfirmat (vezi deja `cont_venit_corespondente.md`
sectiunea 2): validarea `verific_cont` (`COMUN\programe\oproceduri_comune.prg:2389-2415`) verifica doar
ca contul exista in planul de conturi al anului, fara filtru de clasa; niciun `CHECK CONSTRAINT` in
DDL. Deci campul poate contine legitim un cont 6xx/7xx pentru un articol negestionabil, exact cum
presupune decizia 27.
- **Niciun fallback pe `704` existent in `pack_facturare`** — cautare `'704'` (literal SQL) in
`D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17000+ linii, exportul curent al
pachetului): **zero rezultate**. Cautare in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026` (toate scripturile
din 2026): **zero rezultate**. Fallback-ul pe 704 propus de Marius ar fi cod nou, nu o reteta deja
scrisa in `pack_facturare`.
- **Exista totusi un precedent independent pentru 704 ca "cont implicit de venit"**, in alt subsistem:
```
COMUN\clase\anaf_efactura.vc2:8370-8376 (clasa "frm_configurare_efactura")
*p: cconte && cont implicit articol client
*p: ccontp && cont implicit articol furnizor
...
cconte = 704
ccontp = 628
```
E o proprietate cu valoare implicita `704` ("cont implicit articol client", deci un cont de venit
implicit pentru liniile generate la import eFactura) intr-un ecran de configurare a modulului ANAF
eFactura. **Nu am gasit cod care sa citeasca explicit `.cconte`** (cautare `\.cconte\b` in tot `COMUN`:
zero rezultate) — proprietatea e definita cu valoare implicita, dar consumatorul ei concret n-a fost
gasit in timpul alocat (posibil legat generic la un camp de configurare cu acelasi nume, tipar comun in
suita, dar neconfirmat aici). **Ce arata cu certitudine**: alegerea `704` ca implicit pentru "cont
articol client" nu e o inventie a acestei cereri — a mai fost aleasa o data, independent, de altcineva
din echipa (sau de Marius insusi, in alt moment), pentru un scop asemanator. Nu e insa o reteta de cod
reutilizabila direct in `pack_facturare` — e in alt pachet/clasa, alt flux (import, nu emitere).
## 6. Punctul de injectie in `contabilizeaza_articol`
Sursa: `D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (exportul curent al
pachetului, 17548+ linii). Numerotarea de mai jos e din **acest fisier**, verificata prin citire directa
(difera cu cateva linii fata de exportul mai vechi citat in rapoartele anterioare, care avea FACT-024 la
7301 in loc de 7311 — normal, fisierul a mai fost actualizat intre timp).
**Structura exacta a blocului care ridica FACT-024** (`:7275-7302`, inceputul functiei):
```
7275 BEGIN
7276
7277 -- 05.07.2011
7278 BEGIN
7279 SELECT COMPUS, ID_POL_ART
7280 INTO V_COMPUS, V_ID_POL_ART
7281 FROM VCRM_POLITICI_PRET_ART
7282 WHERE ID_ARTICOL = detalii_articol.id_articol
7283 AND ID_POL = detalii_articol.id_pol;
7284 EXCEPTION
7285 WHEN NO_DATA_FOUND THEN
7286 SELECT DENUMIRE
7287 INTO lcArticol
7288 FROM NOM_ARTICOLE
7289 where id_articol = detalii_articol.id_articol;
7290
7291 SELECT NUME_LISTA_PRETURI
7292 INTO lcPolitica
7293 FROM CRM_POLITICI_PRETURI
7294 where id_pol = detalii_articol.id_pol;
7295
7296 RAISE_APPLICATION_ERROR(-20000,
7297 'Articolul ' || detalii_articol.id_articol || '|' ||
7298 lcArticol ||
7299 ' nu este definit in politica de preturi ' ||
7300 detalii_articol.id_pol || '|' || lcPolitica ||
7301 '! (FACT-024)');
7302 END;
```
**Acesta e locul unde s-ar decide "articolul nu are politica" — dar NU e suficient sa se schimbe doar
acest bloc.** Motivul, cu citate exacte:
1. Dupa acest bloc, codul verifica `IF V_COMPUS = 1 THEN` (articol compus, `:7305`, ridica alta eroare,
FACT-016) `ELSE` (`:7391`) — ramura "articol simplu" care e cea relevanta pentru cazul cerut.
2. Ramura "articol simplu" **deschide un cursor nou**, `cursor_articol` (definit la `:7218-7271`),
filtrat exact pe aceeasi cheie care a dat `NO_DATA_FOUND` mai sus:
```
7261 FROM CRM_POLITICI_PRET_ART A
...
7268 WHERE
7269 -- A.STERS = 0 AND
7270 A.ID_POL = detalii_articol.id_pol
7271 AND A.ID_ARTICOL = detalii_articol.id_articol;
```
3. Tot corpul de lucru real (calculul `V_SCD`/`V_ASCD`/`V_SCC`/`V_ASCC`/`V_EXPLICATIE`, apelul
`pack_facturare.scrie_nota(...)` la `:7443-7467`, apelul `pack_facturare.descarca_gestiune(...)` la
`:7476-7498`, scrierea discountului la `:7501-7518`) e **in interiorul**
`WHILE cursor_articol%FOUND LOOP` (`:7396-7541`). **Daca articolul nu are politica de pret, acest
cursor nu returneaza niciun rand** (e exact aceeasi conditie care a dat `NO_DATA_FOUND` la pasul 1,
pe acelasi `(id_pol, id_articol)`), deci bucla ruleaza zero iteratii.
**Concluzia tehnica**: daca s-ar modifica *doar* blocul `EXCEPTION WHEN NO_DATA_FOUND` (pasul 1) ca sa nu
mai ridice `RAISE_APPLICATION_ERROR` si sa lase `V_COMPUS := 0` implicit, executia ar trece la ramura
"articol simplu", ar deschide `cursor_articol`, acesta ar gasi tot zero randuri (aceeasi cauza), bucla nu
ar rula deloc, si functia ar reveni `RETURN V_INCASAT_CALCUL` cu valoarea initiala `0`
(declarata la `:7178`), **fara sa scrie nicio nota contabila si fara sa descarce gestiunea** — silentios,
fara nicio eroare. Asta e o regresie mai grava decat FACT-024: azi tranzactia se opreste vizibil; cu un
fallback naiv, factura s-ar emite fara inregistrare contabila si fara descarcare de stoc, nedetectabil
fara audit manual.
**Ce ar cere de fapt fallback-ul, pentru a pastra comportamentul actual cand politica exista**:
- Blocul de la pasul 1 ar trebui sa **distinga** explicit cazul "politica lipseste" (continua cu
fallback) de cazul "articol compus fara politica" (probabil tot eroare, nu e in scopul cerut) —
posibil punand un flag local (`V_ARE_POLITICA := FALSE`) in loc de `RAISE_APPLICATION_ERROR`.
- Ar trebui adaugata o **ramura noua, in afara/inainte de bucla `cursor_articol`**, activa doar cand
`V_ARE_POLITICA = FALSE`, care sa:
- calculeze `V_SCD`/`V_ASCD` **reutilizand `CASE`-ul existent pe tip document** (`:7398-7423` —
acesta nu depinde de `cursor_articol`, poate fi extras/reutilizat identic);
- calculeze `V_SCC` din sursa noua (corespondente pentru articol gestionabil / `NOM_ARTICOLE.CONT` sau
`704` pentru negestionabil) — **doar acest pas e cel descris de decizia 27**;
- decida un `V_ASCC` (azi vine din `NVL(crs_rand_articol.ascc, GetAnaliticByGrupUtilizatori(...))` —
fara rand din cursor, ar trebui sa cada direct pe `GetAnaliticByGrupUtilizatori(...)`, posibil fara
modificare, pentru ca aceasta functie nu depinde de cursor);
- decida `V_EXPLICATIE`, `ID_VENCHELT`, `ID_SECTIE`, `CU_TVA`, `IN_VALUTA` — vezi sectiunea 7, aici e
gaura reala, nu doar cod de rescris.
- apeleze explicit `pack_facturare.scrie_nota(...)` si, conditionat, `descarca_gestiune(...)`, cu
parametrii calculati mai sus, **duplicand** (nu reutilizand) logica din interiorul buclei existente.
Asta confirma exact avertismentul din decizia 27 insasi ("modificare in COMUN, cu impact peste toata
suita") — nu e un `IF` adaugat pe o linie, e o ramura noua de ~40-60 linii care trebuie sa reproduca o
parte din logica ramurii existente, cu surse diferite pentru fiecare camp.
## 7. Ce se pierde fata de lantul complet `CRM_POLITICI_PRET_ART -> ... -> NOTE_CONTABILE`
Aceasta e partea centrala a raspunsului: **fallback-ul pe cont de venit (corespondente + nomenclator +
704) acopera doar `SCC`, nu si restul campurilor pe care lantul de politica de pret le aduce azi din
`NOTE_CONTABILE` fara efort.** Campurile aduse azi de `D.EXPLICATIE, D.SCD, D.ASCD, D.SCC, D.ASCC,
D.CU_TVA, D.IN_VALUTA` (`cursor_articol`, `:7248-7253, 7259-7260`) si ce se intampla cu fiecare fara
politica:
- **`SCD`/`ASCD` (contul debitor si analiticul lui)**: **nu se pierde** — `V_SCD` se calculeaza deja
independent de `NOTE_CONTABILE`, dintr-un `CASE` pe tipul de document (`:7398-7423`, ex. `461` pt aviz
catre debitori, `418` pt aviz simplu, `crs_rand_articol.scd` doar pt factura normala — dar acesta din
urma **tot vine din `NOTE_CONTABILE.SCD` prin cursor**, deci pt factura normala SCD s-ar pierde la fel
ca SCC). **Corectie necesara fata de formularea initiala a intrebarii**: nu doar SCC vine din
`NOTE_CONTABILE` pt factura normala — si SCD (contul de creante, tipic 411) vine de acolo. Decizia 27
vorbeste doar de "cont de venit", nu de contul debitor — inseamna ca **si sursa lui `V_SCD` pentru
factura normala ar trebui rezolvata** (probabil un cont fix, gen 411/`4111`, dar nu e in scopul
deciziei 27 asa cum e formulata azi).
- **`CU_TVA`**: **se pierde, fara alta sursa in cod.** Controleaza daca pretul de pe linie e interpretat
cu sau fara TVA la scrierea sumei (`pack_facturare.scrie_nota(...)`, parametru `crs_rand_articol.cu_tva`
la `:7463`). Nu exista nicio coloana echivalenta in `CORESP_CONT_VENCHELT` sau `NOM_ARTICOLE`. Fara ea,
fallback-ul trebuie sa aleaga o valoare hardcodata sau sa o deriveze din alt camp deja disponibil pe
`detalii_articol` (ex. `pret_cu_tva`, vazut ca parametru separat la `:7447` — posibil suficient, dar
necesita verificare separata, nu facuta aici).
- **`IN_VALUTA`**: **se pierde, fara alta sursa.** Controleaza daca linia e tratata ca fiind in valuta la
nivel de nota contabila (`V_IN_VALUTA := crs_rand_articol.in_valuta;` la `:7470`, folosit apoi la
discount `:7507`). La fel ca `CU_TVA`, nicio coloana echivalenta in corespondente/nomenclator.
- **`EXPLICATIE`**: se pierde ca sablon configurat, dar are deja fallback partial in cod — `V_EXPLICATIE`
e calculat cu un `CASE` pe tipul de operatie (`:7430-7439`, ex. concateneaza `pack_facturare.cdescriere`
pt facturi tip 7) folosind `crs_rand_articol.explicatie` ca baza; fara cursor, baza ar fi goala/NULL,
dar structura `CASE` insasi nu depinde de cursor — impact moderat, nu blocant.
- **`ID_VENCHELT`**: are deja un fallback la nivel de sesiune —
`NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))` (`:7244-7245`) — daca
`pack_facturare.nid_venchelt` e setat global (parametru de sesiune), fallback-ul functioneaza deja fara
politica; daca nu e setat, ramane `NULL` (fara eroare in cod, dar posibil relevant pentru rapoarte care
folosesc aceasta dimensiune, neverificat).
- **`ID_SECTIE`**: acelasi tipar — `NVL(pack_facturare.nid_sectie_stoc, C.ID_SECTIE)` (`:7246`), cu
fallback pe variabila de sesiune.
- **`ID_SET`** (identificatorul setului de nota folosit la scriere): **nu se pierde** — parametrul trimis
efectiv la `scrie_nota` e `pack_facturare.nid_set` (variabila de sesiune, `:7462`), nu
`crs_rand_articol.id_set` din cursor (acel camp e citit la `:7247` dar nu e folosit la apelul de
scriere in ramura articol simplu — verificat direct in cod). Deci acest camp special nu depinde de
politica de pret.
**Rezumat sectiunea 7**: din cele 7 campuri aduse de lantul politicii de pret, 2 (`ID_VENCHELT`,
`ID_SECTIE`) au deja fallback pe variabile de sesiune si ar functiona fara politica; `EXPLICATIE` are
impact moderat (structura de calcul ramane, doar baza se pierde); `ID_SET` nu depinde deloc de politica
in ramura articol simplu; dar **`CU_TVA` si `IN_VALUTA` nu au nicio sursa alternativa** — sunt gaura reala
pe care simpla adaugare a unui cont de venit din corespondente/nomenclator nu o umple. **Confirmarea
finala a intrebarii din cerere**: da, fallback-ul pe cont de venit e insuficient pentru o nota contabila
completa — nu pentru ca "SCC" ar fi gresit, ci pentru ca doua campuri de control (TVA, valuta) raman fara
sursa.
## 8. Varianta VFP, fara sa se atinga `pack_facturare` (plan A, cerut de Marius)
Ideea de verificat: daca `contabilizeaza_articol` deriva `SCC` din `id_pol`, VFP ar putea sa aleaga
singur, inainte de a trimite articolul, un `id_pol` a carui nota are deja `SCC` = contul de venit
calculat dupa regula lui Marius — pachetul ar rula neschimbat. Raspunsul, punct cu punct:
### a) `id_pol` vine din VFP? Da — e trimis explicit, pe fiecare linie
Semnatura efectiva a procedurii apelate pe fluxul principal (verificat direct in export, nu presupus):
```
D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:4989-5015
PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER,
V_ID_ARTICOL IN NUMBER,
V_SERIE IN VARCHAR2,
V_EXPLICATIE IN VARCHAR2,
V_ID_POL IN NUMBER,
V_ID_GESTIUNE IN NUMBER,
...
V_CONT IN VARCHAR2,
...
```
`V_ID_POL` e parametru de intrare explicit — Oracle nu il deriva din client/contract/tip document, il
primeste ca atare de la VFP. **Niciun alt parametru din aceasta lista nu ofera un canal pentru
SCC/id_set** (raspuns si la punctul d — vezi mai jos).
**De unde il ia azi VFP**: din controlul de cautare obligatoriu al formularului `frm_articol_factura`
(clasa `ofacturare.vc2`):
```
COMUN\clase\ofacturare.vc2:6822-6825
ADD OBJECT 'Ct_clb_politici_preturi' AS ct_clb_cautare WITH ;
cconditie = ISNULL(poDate.id_pol) or EMPTY(poDate.id_pol), ;
cprocedura = thisform.do_cauta_politica, ;
cvar_afisata = poDate.nume_politica, ...
```
```
COMUN\clase\ofacturare.vc2:7167-7182
PROCEDURE do_cauta_politica
Local loCauta
loCauta = caut_politici_curente_util()
If !Isnull(loCauta) And !Empty(Nvl(loCauta.id_pol,0))
poDate.id_pol = loCauta.id_pol
poDate.nume_politica = loCauta.nume
...
```
`cconditie = ISNULL(poDate.id_pol) or EMPTY(poDate.id_pol)` e exact starea "articol fara politica" din
cererea #13 — controlul UI cere explicit utilizatorului sa caute o politica cand campul e gol. VFP
controleaza integral valoarea: azi vine din alegerea utilizatorului, dar nimic tehnic nu impiedica sa fie
setata programatic, fara interactiune, la un `id_pol` calculat.
### b) Drumul invers e interogabil? Da — e simetricul exact al cursorului deja documentat la sectiunea 6
Cursorul din `contabilizeaza_articol` (`ff_...:7261-7267`) merge
`CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE` prin
`A.ID_POL = B.ID_POL`, `B.ID_NOTA = C.ID_NOTA`, `C.ID_SET = D.ID_SET`. Interogarea inversa, plecand de la
un `SCC` dorit catre `id_pol`-urile candidate, foloseste aceleasi coloane de legatura, doar fara filtrul
pe articol:
```sql
SELECT DISTINCT PP.ID_POL
FROM CRM_POLITICI_PRETURI PP
JOIN CRM_NOTE_VANZARI NV ON NV.ID_NOTA = PP.ID_NOTA
JOIN NOTE_CONTABILE NC ON NC.ID_SET = NV.ID_SET
WHERE NC.SCC = :cont_venit_calculat; -- '707'/'711'/'702'/'703'/'7015'/'7018' (gestionabil) sau valoarea din NOM_ARTICOLE.CONT/'704' (negestionabil)
```
E o interogare noua (nu am gasit-o deja scrisa nicaieri in cod), dar foloseste exclusiv coloane si
relatii deja confirmate ca existente si corecte (sectiunea 1 din `cont_venit_corespondente.md` + citirea
directa a `cursor_articol` la sectiunea 6 a acestui raport). **Neverificat pe date reale**: cate randuri
returneaza pentru fiecare `SCC` candidat — zero (nicio politica configurata cu acel SCC azi), unul
(cazul ideal) sau mai multe (ambiguitate: care `id_pol` se alege?). Interogarea de rulat pe baza vie e
chiar cea de mai sus, cu `:cont_venit_calculat` inlocuit pe rand cu `707`, `711`, `702`, `703`, `7015`,
`7018`, `704`.
### c) Se poate insera din VFP un rand in `CRM_POLITICI_PRET_ART`? Da — cod existent, functional, deja rulat in productie
Doua mecanisme distincte, ambele reale:
**c1. RPC direct, apelabil din VFP azi** — `pack_preturi.adauga_politica_pret_art`, apelat din
`ofacturare.vc2` in procedura de modificare a listei de preturi la NIR (`ID_SET = 231`):
```
COMUN\clase\ofacturare.vc2:15551-15587
*!* verific daca exista articolul in politica de preturi
lcSql = [Select id_pol_art, id_venchelt, proc_tvav from crm_politici_pret_art where id_pol = ] + Alltrim(Str(tnIdPol)) + [ and id_articol = ] + Alltrim(Str(tnIdArticol))
lnSucces = goExecutor.oExecute(m.lcSql, "cPoliticiPretArt")
...
If Nvl(loPoliticaPretArt.id_pol_art,0) = 0
lcSql = [begin pack_preturi.adauga_politica_pret_art(] + ;
ALLTRIM(Str(tnIdPol)) + [,] + ; && id_pol
Alltrim(Str(tnIdArticol)) + [,] + ; && id_articol
Alltrim(Str(tnPretLista,20,4)) + [,] + ; && pret
Alltrim(Str(tnPretftvaLista,20,4)) + [,] + ; && pretftva
Alltrim(Str(tnPretvtvaLista ,20,4)) + [,] + ; && pretctva
[NULL, ] + ; && id_valuta
Alltrim(Str(tnProcTvav,20,4)) + [,] + ; && proc_tvav
[NULL, ] + ; && procent
[NULL, ] + ; && id_venchelt
Alltrim(Str(gnIdUtil)) + ; && id_util
[); end;]
```
Verifica intai daca exista deja un rand (`id_pol`, `id_articol`), si daca nu, insereaza unul nou prin RPC
existent — **exact tiparul necesar pentru punctul (c)**: "adauga articolul X in politica de pret Y",
apelabil din VFP fara nicio modificare de pachet (`pack_preturi` nu e `pack_facturare`).
**c2. Auto-populare in masa, deja existenta in `pack_facturare` insusi, dar fara parametru nou** — o
procedura care nu ar necesita nicio modificare de cod pentru ca deja exista si e apelabila ca atare:
```
D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2093-2120
PROCEDURE completare_politica_stoc IS
BEGIN
IF pack_facturare.nid_politica_stoc IS NOT NULL THEN
MERGE INTO CRM_POLITICI_PRET_ART A
USING (SELECT ID_ARTICOL FROM NOM_ARTICOLE
WHERE STERS = 0 AND INACTIV = 0 AND IN_STOC = 1) B
ON (A.ID_POL = pack_facturare.nid_politica_stoc AND A.ID_ARTICOL = B.ID_ARTICOL)
WHEN NOT MATCHED THEN
INSERT (ID_POL, ID_ARTICOL, ID_VALUTA)
VALUES (pack_facturare.nid_politica_stoc, B.ID_ARTICOL, pack_facturare.nid_moneda_nationala);
...
END IF;
END completare_politica_stoc;
```
Aceasta e o functie **existenta, apelabila fara nicio modificare**, care garanteaza deja un rand in
`CRM_POLITICI_PRET_ART` pentru *orice* articol cu `IN_STOC = 1` sub o singura politica "de stoc"
(`pack_facturare.nid_politica_stoc`). Acest id_pol e la randul lui o **optiune de configurare controlata
din VFP**:
```
COMUN\clase\ofacturare.vc2:21733-21734 (Init-ul ecranului de optiuni facturare)
actualizeaza_politica_pret(23,@gnId_pol_pret_tr,@lcPolPretTr)
actualizeaza_politica_pret(1,@gnId_pol_pret_stoc,@lcPolPretStoc)
...
COMUN\clase\ofacturare.vc2:21753
This.nidpolpretstoc = Nvl(gnId_pol_pret_stoc,0)
```
`gnId_pol_pret_stoc` e o optiune globala (`ID_POL_PRET_STOC`, salvata/citita prin
`actualizeaza_politica_pret`), care alimenteaza `pack_facturare.nid_politica_stoc` la initializarea
sesiunii Oracle. **Avertisment**: aceasta politica pare deja folosita azi pentru un scop specific
(vanzare/evaluare la pret de stoc — `nin_valuta`, `nTipVanzareRetail` sunt tratate diferentiat pentru ea
la `ff_...:7223-7260`), cu o singura nota/SCC configurata pentru toate articolele `IN_STOC=1`. Nu e
direct reutilizabila pentru reteta pe 6 conturi diferite (707/711/702/703/7015/7018) ceruta de decizia
27 — dar **arata modelul exact** ("o politica tehnica, configurata o singura data, populata automat"),
model care se poate replica de cate ori e nevoie (o politica tehnica per `SCC` distinct), fara sa se
toate cod in `pack_facturare`.
### d) Alte cai de bypass la nivel de parametru — cautate explicit, nu gasite
Semnatura completa a `adauga_articol_factura` (sectiunea a) si a variantelor `_deviz`/`_stoc` (deja
citate in `cont_venit_articol_fara_politica.md`) **nu au niciun parametru pentru cont de venit sau
`id_set` direct** — singurul levier disponibil la nivelul acestui apel e `V_ID_POL`. Nu exista un
parametru `V_SCC`/`V_ID_SET`/`V_COD_VENIT` pe nicio varianta citita. Concluzie: **`id_pol` e singura
cale de influenta din VFP asupra contului de venit**, exact premisa din care a pornit intrebarea.
### e) Verdict onest
**Cale VFP posibila, dar nu "zero cod" si nu fara pasi de configurare o singura data**:
1. VFP calculeaza `SCC`-ul dorit exact ca in decizia 27 (interogare simpla, deja tipul de cod scris in
`update_corespondente_cvc` — sectiunea 1 a acestui raport): `CORESP_CONT_VENCHELT.CONT_VENIT` pe
contul de gestiune pentru articol gestionabil, `NOM_ARTICOLE.CONT` (daca e 6xx/7xx) sau `'704'` pentru
negestionabil.
2. VFP cauta (interogarea de la punctul b) o `id_pol` existenta a carei nota are deja acel `SCC`. **Daca
gaseste una** (plauzibil pentru conturile comune 707/711/702/703, care probabil se folosesc deja in
politici de pret reale ale firmei) — trece la pasul 3 direct, fara nicio configurare noua.
3. VFP se asigura (RPC `pack_preturi.adauga_politica_pret_art`, punctul c1, deja existent) ca exista un
rand `CRM_POLITICI_PRET_ART` pentru (acel `id_pol`, articolul curent) — insereaza unul cu pretul
tastat manual de utilizator (`tnPretLista` = pretul liniei) daca nu exista.
4. VFP trimite acel `id_pol` in `adauga_articol_factura`, exact ca pe fluxul actual (cu politica).
`contabilizeaza_articol` **ruleaza neschimbat** — si, bonus fata de varianta plan B (sectiunea 7),
**campurile `CU_TVA`/`IN_VALUTA`/`EXPLICATIE`/`ASCD`/`ASCC` vin corect din nota reala configurata**,
nu raman goale ca in fallback-ul partial din pachet.
**Ce nu e gratuit**:
- **Pasul 2 poate rata** (nicio politica existenta cu acel `SCC`) — pentru un `SCC` fara precedent (de
ex. `704`, pentru care nu am gasit nicio nota configurata azi, sectiunea 5), ar trebui creat manual,
o singura data, un lant nou `CRM_POLITICI_PRETURI` + `CRM_NOTE_VANZARI` + `NOTE_CONTABILE` (contabilul
configureaza nota din `frm_config_note_contabile[2007]`, deja documentat in
`cont_venit_corespondente.md` sectiunea 1) — asta e configurare (ecran existent), nu cod nou, dar e un
pas manual, nu automat.
- **Efect lateral real**: o "politica tehnica" (creata special pentru acest mecanism) ar aparea in
ecranul de cautare politici al utilizatorului (`caut_politici_curente_util()`, aceeasi functie folosita
la cautarea normala) — trebuie fie exclusa explicit din acel cursor de cautare (filtru pe un flag/prefix
dedicat), fie acceptata ca vizibila, cu riscul ca un utilizator sa o aleaga din greseala pentru o
factura normala. Nu am verificat daca `caut_politici_curente_util()` are deja un filtru care ar exclude
automat politici fara preturi reale/marcate altfel.
- **Ambiguitatea de la pasul 2** (mai multe `id_pol` cu acelasi `SCC`) ar cere o regula de departajare
(cea mai recenta? prima gasita? o marcare explicita "politica implicita pentru SCC X"?) — neverificat
pe date reale, vezi sectiunea "Ramas de verificat".
- Aceasta varianta **schimba `id_pol`-ul scris pe linie** (azi `NULL`/gol pentru articol fara politica,
ar deveni id-ul politicii tehnice) — orice raport/ecran care azi presupune "`id_pol` gol = articol fara
politica" (de ex. eventuale filtre in rapoarte de vanzari) ar vedea un `id_pol` populat; nu am gasit
cod care sa faca aceasta presupunere explicit, dar nici n-am cautat-o exhaustiv (in afara bugetului).
**Concluzie e)**: nu e nevoie de "niciuna dintre cai" pentru un "nu" — exista o cale VFP verificabila si
construita din piese deja functionale in productie (RPC de inserare in politica de pret, plus o
interogare de cautare inversa noua dar directa). E **fezabila**, probabil **mai completa** decat plan B
(rezolva si `CU_TVA`/`IN_VALUTA`, nu doar `SCC`), dar cere: (i) o interogare noua de cautare `id_pol` pe
`SCC`, (ii) reutilizarea unui RPC existent pentru inserarea articolului in politica, (iii) potential
configurare manuala o singura data (nota noua) pentru `SCC`-urile fara precedent, (iv) o decizie explicita
despre cum se exclude/trateaza politica tehnica in ecranele de cautare vizibile utilizatorului.
## Ramas de verificat pe baza de date vie
- **Structura completa DDL a `CORESP_CONT_VENCHELT`** (`DESC coresp_cont_venchelt` sau echivalent):
confirmarea coloanelor `ID_CCV` (probabil PK surogat) si `DATAORAS` (probabil timestamp tehnic),
tipurile exacte, nullability, si daca exista o constrangere `UNIQUE`/index pe `CONT` (relevant pentru
ambiguitatea discutata in sectiunea 2 si 4) — DDL-ul nu e in arhiva `SCRIPTURI_CLAR` (tabel pre-2009).
Interogare de verificat: `SELECT column_name, data_type, nullable FROM user_tab_columns WHERE
table_name = 'CORESP_CONT_VENCHELT' ORDER BY column_id;` si
`SELECT index_name, uniqueness FROM user_indexes WHERE table_name = 'CORESP_CONT_VENCHELT';`.
- **Continutul complet al `CONT_VENIT`** — migrarea din 2023 acopera 20 de conturi de gestiune; ramane de
verificat daca acopera **toate** conturile de gestiune folosite azi in `NOM_ARTICOLE.CONT`/`STOC.CONT`
in productie (altfel fallback-ul ar produce `CONT_VENIT = NULL` pentru conturi de gestiune
neacoperite). Interogare: `SELECT DISTINCT cont FROM (SELECT cont FROM nom_articole UNION SELECT cont
FROM stoc) WHERE cont NOT IN (SELECT cont FROM coresp_cont_venchelt WHERE sters = 0);`.
- **Daca exista mai mult de un rand activ (`STERS = 0`) pentru acelasi `CONT`** — cod critic pentru
join-uri fara dezambiguizare. Interogare: `SELECT cont, COUNT(*) FROM coresp_cont_venchelt WHERE
sters = 0 GROUP BY cont HAVING COUNT(*) > 1;`.
- **Valorile reale din `NOM_ARTICOLE.CONT` pentru articole negestionabile** — cate au deja un cont
6xx/7xx explicit vs. cate au un cont 3xx "gresit" (articol marcat negestionabil dar cu cont de gestiune
ramas din import) vs. cate au `NULL`/gol, ceea ce ar decide cat de des s-ar folosi de fapt fallback-ul
pe `704`. Interogare: `SELECT SUBSTR(cont,1,1) AS clasa, COUNT(*) FROM nom_articole WHERE gestionabil =
0 GROUP BY SUBSTR(cont,1,1);` (numele exact al coloanei `gestionabil` de verificat pe schema).
Verificat si numele exact al coloanei `NOM_ARTICOLE.CONT` — vezi si `cont_venit_articol_fara_politica.md`.
- **Cine citeste efectiv proprietatea `cconte` din `frm_configurare_efactura`** — nu am gasit consumatorul
in codul static cercetat (`\.cconte\b`, zero rezultate in `COMUN`); posibil legata generic la o
configuratie pe nume de camp, tipar comun in suita, dar neconfirmat.
- **Comportamentul `scrie_nota`/`ACT_TEMP` la `SCD`/`SCC` NULL sau la `CU_TVA`/`IN_VALUTA` NULL** — codul
PL/SQL nu are validare (confirmat in `nota_contabila_fara_politica.md`, sectiunea 2), dar constrangerile
reale de tabel (`ACT`/`ACT_TEMP`, `NOT NULL`?) nu au fost verificate — ar decide daca o implementare pe
jumatate facuta a fallback-ului ar da eroare Oracle explicita (`ORA-01400`) sau ar trece silentios.

View File

@@ -1,217 +0,0 @@
# Custodie (tipuri 48/49): stergere + reemitere intoarce curat descarcarea din custodie?
Cercetare read-only. Raspunde la intrebarea daca editarea prin regenerare (STERS=1 pe documentul
vechi + document nou, in aceeasi tranzactie, mecanismul proiectat la S9 din
`docs\plan_13_unificare_formular_facturare.md:3576-3660`) pentru facturile de marfa in custodie
(tipurile 48/49) lasa stocul de custodie neschimbat.
Sursa principala citata: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(prescurtat `EXPORT` mai jos, `EXPORT:linie`). Verificat direct in aceasta runda: numerotarea din
export **coincide** cu cea folosita in materialele anterioare citate de team-lead (`scrie_fact_aviz_custodie`
la `:10089-10325` pentru corpul functiei si `:7521-7536` pentru locul de unde e apelata;
`sterge_factura` la `:5432-5607`) — nu s-a gasit niciun offset.
**Rezultat central, care rescrie premiza cererii**: `scrie_fact_aviz_custodie` **nu se apeleaza
niciodata pentru documente de tip 48/49**. Se apeleaza exclusiv cand `pack_facturare.ntip = 4`
("factura din avize" — alt tip de document, complet diferit). Tipurile 48/49 sunt o ramura separata,
care **nu atinge stocul deloc** la emitere (nu doar "il descarca prin alta cale"). Detalii mai jos.
## 1. Ce sunt exact tipurile 48 si 49
Confirmat pe cod, nu doar pe indiciul din materiale:
- **Denumirile** ("custodie cu descarcare K" / "custodie fara descarcare K") vin din
`COMUN\docs\tipuri_documente_facturare.md`, deja verificate pe cod de cercetarea anterioara
`docs\cercetare\s5_acoperire_tipuri.md` (tabelul de la liniile 102-103 de acolo).
- **Reachable din meniu**: `Meniuri\politica.mn2`, submeniul `Marfaincus` — tip 48 la linia 29,
tip 49 la linia 26 (citat deja corect in `s5_acoperire_tipuri.md:22,102-103,146-147`).
- **Amandoua sunt facturi de sine statatoare** (categoria "Facturi", nu "Avize" — spre deosebire de
42/47, care sunt avize catre custodie), sursa **VFP** de articole fiind aceeasi pentru ambele:
`Case Inlist(tnTip, 48, 49) -> pack_facturare.cursor_articole_k(...)`
(`COMUN\programe\ofacturare.prg:271-272` pe formularul standard, identic la `:750-752` pe prototip).
- **`cursor_articole_k`** (`EXPORT:3595-3701`, definitia activa; exista si o declaratie in spec la
`:382`) e o interogare de **lista de preturi**, nu o interogare de stoc/aviz: alege articolele
dintr-o **politica de pret dedicata** (`A.ID_POL = to_number(pack_sesiune.getoptiunefirma('IDPOLPRETFACTK'))`,
`EXPORT:3681-3682`) si **filtreaza explicit doar articole negestionabile**:
`WHERE C.IN_STOC = 0 AND C.IN_CRM = 1 AND C.STERS = 0 AND C.INACTIV = 0` (`EXPORT:3695-3698`).
Cu alte cuvinte: **48 si 49 sunt aceeasi sursa de articole** ("K" = politica de pret speciala
pentru custodie, optiunea de firma `IDPOLPRETFACTK`), restransa explicit la articole
**care nu sunt gestionate in stoc** (`IN_STOC=0` in `NOM_ARTICOLE`).
- **Nu s-a gasit in aceasta runda ce anume distinge 48 de 49** ("cu"/"fara descarcare K") — pe tot
codul examinat (`adauga_articol_factura`, `contabilizeaza_articol`, `cursor_articole_k`,
`sterge_factura`), cele doua tipuri sunt tratate **identic**, fara nicio ramura care sa le separe.
Diferenta pare sa fie doar de conventie de utilizare (business), nu de cod — semnalat la sectiunea
"Neacoperit".
## 2. Ce scrie emiterea pe un document 48/49
**Nu se atinge stocul deloc, prin design, nu prin accident.** Lant de dovezi:
1. `adauga_articol_factura` (`EXPORT:4989-5220`) nu are ramura proprie pentru `ntip IN (48,49)` —
cade in `ELSE` (`:5187-5203`), care preia `V_IN_STOC` direct din `V_IN_STOC_TEMP`, valoarea
trimisa de VFP din grid — la randul ei populata din `cursor_articole_k.GESTIONABIL`
(`C.IN_STOC AS GESTIONABIL`, `EXPORT:3634`), deci **intotdeauna 0** pentru articolele oferite pe
aceste doua tipuri.
2. `contabilizeaza_articol` (`EXPORT:7173-7547`): tip 48/49 intra in bucketul "factura normala"
pentru determinarea `SCD`/`ASCD` (`WHEN pack_facturare.ntip <= 20 or ntip IN (... 48, 49, 51, 52)`,
`EXPORT:7400-7412`) — scrie o nota contabila normala prin `scrie_nota` (`:7443-7467`), ca orice
factura obisnuita, folosind conturile din politica de pret K.
3. **Apelul catre `descarca_gestiune` e conditionat** de `detalii_articol.in_stoc = 1`
(`EXPORT:7472-7475`, ramura `IF pack_facturare.ntip <> 4`, care e adevarata pentru 48/49). Cum
`in_stoc` vine intotdeauna 0 pentru articolele acestor doua tipuri (pasul 1), **conditia nu se
indeplineste niciodata** — `descarca_gestiune` nu se executa.
4. **Confirmare independenta, in interiorul lui `descarca_gestiune` insusi**: chiar daca cineva ar
reusi sa forteze apelul, functia are propria garda de iesire timpurie:
```
EXPORT:7789-7797
-- NU SE DESCARCA GESTIUNEA PENTRU ARTICOLELE NEGESTIONABILE
-- ASTFEL INCAT SA SE POATA FACE OPERATII GEN AVIZ DIN CUSTODIE INCLUSIV CU ARTICOLE NEGESTIONABILE
SELECT MAX(IN_STOC) INTO lnInStoc FROM NOM_ARTICOLE WHERE ID_ARTICOL = V_ID_ARTICOL;
if lnInStoc = 0 then GOTO SFARSIT; end if;
```
Comentariul insusi confirma intentia de design: articolele negestionabile (exact categoria din
care se alimenteaza 48/49) sunt **explicit excluse** din descarcarea de gestiune, tocmai pentru
ca fluxul de custodie sa poata folosi articole care nu au stoc urmarit.
5. **`scrie_fact_aviz_custodie` nu se apeleaza pentru 48/49.** Singurul apel din tot pachetul
(`EXPORT:7520-7537`) e in interiorul aceleiasi functii `contabilizeaza_articol`, pe ramura
`ELSE` a testului `IF pack_facturare.ntip <> 4` (`:7472`) — adica **doar cand `ntip = 4`**
("factura din avize"). Pentru 48/49, `ntip` e 48 sau 49, niciodata 4, deci acest cod **nu se
executa niciodata** pe aceasta ramura. Functia `scrie_fact_aviz_custodie` insasi
(`EXPORT:10089-10325`) nu scrie in `STOC`/`RUL` — cauta un rand deja existent in `RUL` (cu
`ID_TIP_RULAJ = 0`, `EXPORT:10175`) pentru a calcula valori de cost, si scrie doar **note
contabile** (`scrie_nota`, conturi `371/357/607/378/4428` — conturi de marfuri in custodie /
cheltuieli, `EXPORT:10229-10324`) — e o functie de **conversie contabila**, nu de miscare de
stoc.
**Concluzie sectiune**: premiza din cerere ("pe ramura custodiei se cheama `scrie_fact_aviz_custodie`
in loc de `descarca_gestiune`") descrie corect fluxul pentru **tip 4** (factura emisa dintr-un aviz,
posibil un aviz catre custodie 42/47 emis anterior), **nu** pentru tipurile 48/49 cerute explicit.
Pentru 48/49, niciuna din cele doua proceduri nu scrie nimic in stoc — documentul e din start
"fara efect de stoc", pentru ca sursa lui de articole (`cursor_articole_k`) e restransa la articole
negestionabile.
## 3. Ce face stergerea (`sterge_factura`) pe un document 48/49
`sterge_factura` (`EXPORT:5432-5607`) nu are nicio ramura proprie pentru `V_TIP IN (48,49)`. Constantele
speciale verificate (`EXPORT:78-86`): `nTipVanzareRetail=43`, `nTipFacturaHotel=44`,
`nTipFacturaRestaurant=45`, `nTipNotaPlata=46`, `nTipFacturaACN=51` — 48 si 49 nu sunt printre ele.
`CASE`-ul principal (`EXPORT:5501-5551`) evalueaza `V_TIP` astfel pentru 48/49:
- `V_TIP = 24`? Nu.
- `V_TIP > 20 AND V_TIP NOT IN (44,45,46)`? **Da** — 48 si 49 cad in aceasta ramura generica
("alte tipuri de avize", `EXPORT:5512-5523`), care face:
```sql
UPDATE VANZARI_CANTITATI SET STERS = 1
WHERE ID_VANZARE_DET_AVIZ IN
(SELECT ID_VANZARE_DET FROM VANZARI_DETALII WHERE ID_VANZARE = V_ID_VANZARE AND STERS = 0);
```
`VANZARI_CANTITATI` e tabelul de bookkeeping "cantitate ramasa din comanda/aviz" (Rol A, documentat
in `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md`, citat si in `s5_acoperire_tipuri.md`
coloana "Rol"). **48/49 nu au niciodata acest rol** (`s5_acoperire_tipuri.md:103-104`: coloana Rol
= "—" pentru ambele) — articolele lor vin din `cursor_articole_k` (lista de preturi K), nu din
`cursor_comanda`. Deci acest `UPDATE` **actioneaza pe zero randuri** pentru documente 48/49 (nu
exista randuri `VANZARI_CANTITATI` legate de ele) — e un no-op inofensiv, nu o eroare, dar nici o
actiune custodie-specifica.
- Restul ramurilor CASE (`V_TIP=4`, `V_TIP=nTipFacturaRestaurant`) nu se aplica.
Restul procedurii e comun tuturor tipurilor (nimic specific 48/49):
- `UPDATE VANZARI_DETALII SET STERS=1 ... WHERE ID_VANZARE = V_ID_VANZARE` (`:5560-5564`) —
marcheaza liniile facturii ca sterse.
- `IF V_TIP IN (2,6,52)` pentru `CTR_RATE_FACTURI` (`:5566-5580`) — nu se aplica (48/49 nu sunt in
lista).
- `UPDATE VANZARI_CORESP SET STERS=1 ...` (`:5582-5585`) — sterge corespondentele
factura<->aviz/comanda, generic.
- CASE-ul final pe pachete externe (restaurant/hotel/ACN, `:5587-5605`) — nu se aplica pentru 48/49,
cad in `ELSE NULL`.
**Nu exista, nicaieri in `sterge_factura`, vreun apel care sa reverseze explicit efectul lui
`descarca_gestiune`** (nu s-a gasit `incarca_gestiune` sau echivalent, pentru niciun tip de document,
nu doar 48/49 — cautare `grep -n "UPDATE STOC"` in tot pachetul, zero rezultate; singurele scrieri
gasite in zona lui `descarca_gestiune` sunt in `RUL_TEMP`, `EXPORT:9464,10006`, tabel tranzitoriu care
se descarca in `RUL` mai departe in flux, nu direct in acest apel). Mecanismul prin care stocul
"revine" la stergerea unei facturi normale (`ntip<=20`) **nu a fost identificat in aceasta runda** —
posibil printr-un filtru `WHERE VANZARI_DETALII.STERS=0` in interogarile de stoc disponibil, posibil
prin alt pachet (`PACK_STOC`?) sau printr-un trigger, in afara `PACK_FACTURARE` si a perimetrului
citit. Marcat explicit la "Neacoperit" — **e o intrebare generala a sistemului, nu specifica
custodiei**, pentru ca `sterge_factura` trateaza identic (fara reversare explicita) orice tip de
document.
## 4. Garzi existente la stergere — prind si cazul custodiei?
Cele trei garzi de la inceputul lui `sterge_factura`, verificate pe liniile citate de team-lead
(coincid exact, fara offset):
- `EXPORT:5452-5462` — blocheaza daca exista **facturi de retur** (`VANZARI_CORESP.TIP=3`) pe
documentul curent.
- `EXPORT:5466-5476` — blocheaza daca exista **facturi/avize de retur** (`TIP IN (1,2)`) pe un
aviz curent.
- `EXPORT:5480-5494` — blocheaza daca exista **avize de retur** pe avizele care au generat o
factura din aviz curenta.
**Niciuna nu mentioneaza custodie sau `ntip IN (48,49)`** — toate trei filtreaza exclusiv pe
`VANZARI_CORESP.TIP IN (1,2,3)` (relatii factura<->retur / aviz<->retur), independent de tipul
documentului curent (`V_ID_VANZARE`). Pentru un document 48/49 fara retur emis pe el, **niciuna nu
se declanseaza** — stergerea trece liber, exact ca pentru o factura normala fara retur.
## 5. Verdict
**Regenerarea (stergere + reemitere) e SIGURA pentru documentele 48/49 in privinta stocului de
custodie — dar dintr-un motiv diferit de cel presupus in cerere.** Nu pentru ca stergerea "intoarce
curat" o descarcare de custodie — ci pentru ca **emiterea unui document 48/49 nu descarca nimic din
stoc/custodie in primul rand** (sectiunea 2: sursa de articole e restransa structural la articole
negestionabile, `IN_STOC=0`, iar `descarca_gestiune` are doua garzi independente care o opresc pentru
astfel de articole). Stergerea (sectiunea 3) nu are nimic custodie-specific de reversat, si nici nu
are nevoie sa aiba, pentru ca nimic custodie-specific n-a fost scris la emitere. **`ntip=4` (factura
din avize) e ramura care foloseste efectiv `scrie_fact_aviz_custodie`, si e un tip de document
diferit de 48/49** — premiza din cerere ("pe ramura custodiei se cheama `scrie_fact_aviz_custodie`
in loc de `descarca_gestiune`") descrie corect tip 4, nu 48/49.
**Conditie sub care verdictul s-ar putea rasturna, ne-exclusa 100% in aceasta runda**: siguranta de
mai sus depinde de invariantul "un document 48/49 contine numai articole cu `IN_STOC=0`". Daca
articolele adaugate pe un document 48/49 ar veni vreodata **printr-o alta cale** decat
`cursor_articole_k` (o cautare libera de articole, nerestransa la politica K), un articol gestionabil
(`IN_STOC=1`) ar trece garda de la `adauga_articol_factura`/`contabilizeaza_articol` (sectiunea 2,
pasul 1: `V_IN_STOC_TEMP` ar deveni 1) si **`descarca_gestiune` s-ar executa normal** — caz in care
`sterge_factura`, care nu reverseaza explicit stocul pentru niciun tip (sectiunea 3), ar lasa un
decalaj real. **Nu s-a gasit in aceasta runda** o cale VFP care sa permita asta pentru 48/49 (grid-ul
de articole al acestor doua tipuri e populat o singura data, la deschiderea formularului, direct din
`cursor_articole_k` — `COMUN\programe\ofacturare.prg:271-272`; nu exista o cautare separata de
articole vazuta in codul citit), dar nu s-a verificat exhaustiv toate punctele de intrare posibile in
`VANZARI_DETALII_TEMP` pentru aceste doua tipuri.
## Verificat direct / Dedus / Neacoperit
**Verificat direct pe cod (fisier:linie citat in sectiunile 1-4):**
- Tip 48/49 = facturi (nu avize), sursa unica de articole `cursor_articole_k`, restransa la
`IN_STOC=0`.
- `scrie_fact_aviz_custodie` se apeleaza exclusiv pentru `ntip=4`, niciodata pentru 48/49.
- `descarca_gestiune` are doua garzi independente (`in_stoc=1` la apelant, plus garda proprie pe
`NOM_ARTICOLE.IN_STOC`) care o opresc pentru articolele din 48/49.
- `sterge_factura` nu are ramura proprie pentru 48/49 (cade in bucketul generic ">20, exclus
hotel/restaurant/notaplata"), iar actiunea acelui bucket (`VANZARI_CANTITATI`) nu are ce sa
actioneze pentru aceste doua tipuri (fara rol in acel tabel).
- Cele trei garzi de refuz al stergerii nu disting custodia, si nu se declanseaza pentru un document
48/49 fara retur emis pe el.
**Dedus, nu verificat exhaustiv:**
- Ca operatorul nu poate adauga pe un document 48/49 un articol gestionabil pe alta cale decat
`cursor_articole_k` — bazat pe faptul ca grid-ul se populeaza o singura data la deschidere, dar
n-am urmarit tot codul de interactiune al grid-ului (`adauga la lista`/editare inline) pentru cele
doua tipuri.
- Ca diferenta reala dintre tip 48 si tip 49 e doar de conventie/business, nu de cod — n-am gasit
nicio ramura care sa le separe, dar nici n-am cautat in afara `PACK_FACTURARE`/`ofacturare.prg`
(de exemplu in rapoarte sau in alte proceduri VFP care ar putea trata diferit "cu descarcare K" vs
"fara").
**Neacoperit in aceasta runda:**
- Mecanismul general prin care stocul "revine" la stergerea unei facturi **normale** (`ntip<=20`) —
nu s-a gasit niciun `UPDATE STOC`/reversare explicita in `PACK_FACTURARE`; posibil calculat prin
interogari care filtreaza `VANZARI_DETALII.STERS=0`, posibil in alt pachet Oracle sau prin trigger,
in afara perimetrului citit in aceasta runda. Nu e o intrebare specifica custodiei — se aplica
identic la orice tip de document — dar ramane deschisa.
- Semnificatia exacta si diferenta functionala/de raportare intre "custodie cu descarcare K" (48) si
"custodie fara descarcare K" (49) — n-am gasit in cod ce anume descarca "K" daca nu e stoc fizic
(posibil un calcul contabil de adaos comercial specific comertului cu amanuntul, coeficientul K,
dar nu confirmat pe cod in aceasta runda).
- Testare pe date reale (baza de dev) a unui ciclu emitere->stergere->reemitere pe un document 48/49
— cercetarea a fost exclusiv pe cod si `SELECT`-uri simple, fara sa ruleze fluxul.

View File

@@ -1,731 +0,0 @@
# 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.

View File

@@ -1,189 +0,0 @@
# Supliment: gruparea pseudo-liniei de discount in `C_TVA_FACTURA`
Completare la `discount_document_cota_tva.md`, scrisa de al doilea agent pe aceeasi intrebare.
**Nu repeta** raportul principal — acopera exact punctul pe care acela il lasa deschis la finalul
sectiunii 3 („ipoteza gruparii separate — nu am atins-o deloc, o verifica alt agent"), plus doua
verificari prin rulare pe care raportul principal nu le are.
Cercetare read-only: niciun fisier de cod atins, zero write-back, pe Oracle numai `SELECT`, niciun
apel catre ANAF (validarea s-a facut **offline**, cu DUKIntegrator din `D:\ROA\COMUNROA\dist_efactura`).
## Verdict in 5 randuri
Ipoteza pe care o ridicasem — ca pseudo-linia de discount de document, avand `id_jtva_coloana = 0`,
iese din `LEFT JOIN` cu `coloana_jv` NULL si formeaza propriul grup in `C_TVA_FACTURA`, cu
`TaxableAmount` negativ — este **infirmata pentru factura obisnuita** si **confirmata pentru factura
scutita / cu taxare inversa / intracomunitara**. In al doilea caz XML-ul chiar iese cu doua
`TaxSubtotal`-uri pe cota 0, unul cu baza negativa si categoria `Z`. **Dar validatorul nu-l
respinge** — l-am rulat. Deci: comportament gresit ca modelare, nu defect care blocheaza factura.
---
## 1. Ce am masurat, nu dedus: `IIF()` peste NULL in VFP
Toata ipoteza atarna de o singura intrebare: ce intoarce `Iif(Left(.Null.,2) = 'CE', 1, 0)`. Daca ar
intoarce `.NULL.`, cheia de grupare a pseudo-liniei ar diferi de a liniilor reale si gruparea s-ar
rupe pe **orice** factura.
Am rulat o sonda headless (`vfp9.exe -A -T`) care reproduce exact `LEFT JOIN`-ul si `GROUP BY`-ul din
`xmlefactura.prg:226-248`, cu o pseudo-linie de discount pe `id_jtva_coloana = 0`:
```
1) IIF(.NULL.="CE",1,0) -> tip=N isnull=.F.
2) IIF(LEFT(.NULL.,2)="CE",1,0) -> tip=N isnull=.F.
3) IIF(INLIST(.NULL.,...),1,0) -> tip=N isnull=.F.
linie=ARTICOL A | coloana_jv nul=.F. | intracom=0 | scutit=0
linie=ARTICOL B | coloana_jv nul=.F. | intracom=0 | scutit=0
linie=Discount 10.00 % Factura | coloana_jv nul=.T. | intracom=0 | scutit=0
4) grupuri in C_TVA_FACTURA = 1
grup: cota=21 baza=1350 tva=283.50 (1500 - 150, corect)
```
**`IIF()` intoarce ramura falsa, nu `.NULL.`**, chiar daca conditia e nula. Deci pe o factura
interna obisnuita pseudo-linia primeste `intracomunitar=0, taxare_inversa=0, scutit=0` — exact ca
liniile reale — si **se contopeste in grupul cotei maxime**. Sectiunile 3 si 4 din raportul principal
raman valabile.
Sonda: `...\scratchpad\probe_null.prg`. Nu depinde de baza de date si se poate reface oricand.
## 2. Unde ipoteza se confirma totusi: facturile scutite / taxare inversa / intracomunitare
Egalitatea de mai sus tine doar cat timp **si liniile reale** au `scutit=0` si `expltva` gol. Pe o
factura scutita nu e asa:
| camp din cheia `Group By` (`xmlefactura.prg:246`) | liniile reale (scutite) | pseudo-linia de discount de document |
|---|---|---|
| `id_jtva_coloana` | id real (ex. 11) | **0** — `prelucreaza_facturacrs` nu-l seteaza niciodata pe randul `ZZZZ` (`ofacturare_comun.prg:1891-1896`) |
| `coloana_jv` (din `LEFT JOIN`) | `'WRSCDD'` | `.NULL.` — `cJTVAVanzariTemp` e incarcat cu `where afisat > 0 and id_jtva_coloana > 0` (`updateserver.prg:578`), deci **nu exista rand cu id 0** |
| `scutit` | **1** | **0** |
| `expltva` | `'Scutit cu drept de deducere'` | **gol** |
Ultimul rand merita explicat, pentru ca e contraintuitiv: `completeaza_explicatie_tva`
(`ofacturare_comun.prg:2077-2137`) chiar **include** pseudo-linia in lista de id-uri cautate — scanul
de la `:2094` e `For proc_tva = 1 And Nvl(id_jtva_coloana,-1) <> -1`, iar pseudo-linia are
`proc_tva = 1` (cota 0) si `id_jtva_coloana = 0`, deci trece filtrul. Dar interogarea de la `:2108`
e `where id_jtva_coloana in (...)` pe `jtva_coloane`, unde id-ul 0 nu exista, asa ca `Replace expltva
... For NVL(id_jtva_coloana,0) = m.lnIdJtva` (`:2129`) nu o atinge niciodata. Ramane goala.
Doua campuri diferite din cinci => **doua grupuri**. XML-ul iese cu:
```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.
**Nu am reprodus scenariul end-to-end prin program** (ar fi cerut o factura scutita cu discount de
document, si in baza de dev nu exista — vezi punctul 4). Il stabilesc din citirea codului plus
semantica `IIF`/NULL masurata la punctul 1.
## 3. Validare offline: XML-ul gresit **nu** e respins
Am construit XML-urile si le-am trecut prin DUKIntegrator
(`java -jar DUKIntegrator.jar -c <config> -v FACT1 <in.xml> <out.txt>`, exact apelul din
`xmlefactura.prg:1192`), pornind de la o factura reala acceptata de ANAF ca schelet:
| scenariu | fisier | rezultat |
|---|---|---|
| scutit + discount de document, **cu grupul orfan** (ce produce codul azi) | `scutit_bug.xml` | **ok** |
| scutit + discount, un singur `TaxSubtotal` net (varianta corecta) | `scutit_corect.xml` | **ok** |
| cote mixte 21%+11%, tot discountul pe 21% (ce produce codul azi) | `mixt_azi.xml` | **ok** |
| cote mixte 21%+11%, discount repartizat proportional | `mixt_proportional.xml` | **ok** |
| discount mai mare decat baza cotei maxime -> `TaxableAmount = -400.00` si `TaxAmount = -84.00` pe grupul 21% | `mixt_baza_negativa.xml` | **ok** |
| **control negativ**: `TaxExclusiveAmount` stricat intentionat | `control_stricat.xml` | **eroare BR-CO-13 + BR-CO-15** |
Controlul negativ conteaza: fara el, cele cinci „ok" n-ar dovedi nimic. Validatorul chiar valideaza.
**De ce trec**: regulile EN16931 pe categorii (BR-S-08, BR-E-08, BR-Z-08) cer ca baza declarata pe o
categorie sa fie egala cu *suma liniilor din acea categorie minus alocarile din aceeasi categorie*.
Grupul orfan respecta asta trivial (`0 - 539.82 = -539.82`), iar grupul `E` la fel (`4410.00 - 0`).
Aritmetica inchide; **modelarea fiscala e cea gresita**, si asta niciun schematron nu prinde.
Avertisment onest: validatorul local e din 2022 (`ro16931-ubl-1.0.8`, jar-ul din ianuarie 2022).
Validatorul online curent al ANAF poate fi mai strict. **Nu l-am apelat** — interdictie explicita.
## 4. De ce nu am putut proba pe date reale
- In baza accesibila (`MARIUSM_AUTO@ROA_CENTRAL`) exista **3** facturi cu discount de document, toate
cu **o singura cota** si toate anterioare eFacturii (2008, 2014 x2), niciuna cu `efactura = 1`.
Separat exista 34 de facturi cu cote mixte, dar **niciuna** cu discount de document. Deci
intersectia care ne intereseaza e goala.
**Asta nu inseamna ca nu apare in productie** — baza de dev are 712 facturi in total.
- Cele 4 XML-uri de productie cu `AllowanceCharge` de document pe care le-am examinat sunt, dupa
toate semnele, **discounturi pe linie** cu `discount_evidentiat = 1`, nu discounturi de document:
cel de la `2024_07\...CIA_TRADE_POSSIBLE` are cote 9% si 19%, iar alocarea unica e pe **9%** —
adica pe cota **minima**, ceea ce regula `Max(proc_tvav)` nu poate produce; iar
cel de la `2024_01\...BEST_POWER_DANCE` are **doua** alocari (9% si 19%), ceea ce discountul de
document, fiind o singura pseudo-linie, nu poate produce nici el.
Nu am gasit niciun XML de productie care sa fie sigur discount de **document**.
- Firmele din `D:\ROA\Efactura` (VENDING_MASTER, CLEVER_MOTORS, ...) nu au scheme in baza accesibila
(`select ... from all_users` — zero potriviri), deci nu am putut confrunta XML-ul cu antetul lui.
## 5. Un lucru dovedit pe un fisier real, nu dedus
Pe `D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml`:
```
BT-5 DocumentCurrencyCode = EUR
<cac:AllowanceCharge> ... <cbc:Amount currencyID="RON">539.82</cbc:Amount>
<cbc:AllowanceTotalAmount currencyID="EUR">539.82</cbc:AllowanceTotalAmount>
```
Aceeasi suma apare o data ca `RON` si o data ca `EUR` — `currencyID` e hardcodat `"RON"` la
`xmlefactura.prg:778`, in timp ce restul documentului foloseste `mmoneda`. Doua precizari care
schimba interpretarea:
- fisierul de pe disc este **identic pe SHA256** cu cel din
`...\TRIMISE\4172939206_3250292036.zip`, deci e exact ce a plecat la ANAF;
- si a fost **acceptat**. Respingerea din `...\ERORI\4172675286_3249814520.zip` e a unei incarcari
*anterioare* si e pentru alte doua reguli (`BR-CO-15` si `BR-CL-04` — cod de moneda invalid,
probabil `EURO` in loc de `EUR`, ceea ce explica linia de corectie `xmlefactura.prg:267`).
Deci `currencyID="RON"` pe factura in valuta e o **neconformitate reala si activa in cod azi**, dar
nu am dovada ca ar cauza o respingere.
## 6. Ce inseamna pentru #13
Repartizarea proportionala pe cote — recomandarea din raportul principal — **rezolva si problema de
aici**, fara nicio garda in plus: daca discountul se sparge pe cotele care exista efectiv pe factura,
fiecare bucata mosteneste `id_jtva_coloana` si `expltva` ale grupului ei, grupul orfan dispare, iar
`agettipcota(1)` nu mai poate fi ambiguu. Subscriu, cu doua completari:
1. **Pseudo-liniile de discount trebuie sa primeasca `id_jtva_coloana` al grupului pe care il reduc**,
nu doar cota. Cota singura nu ajunge — cheia de grupare are cinci campuri, iar `expltva` se
completeaza tot prin `id_jtva_coloana`. O implementare care seteaza doar `proc_tva` ar lasa
grupul orfan exact acolo unde e azi.
2. **Regula traieste in doua locuri** — VFP (`Calculate Max(proc_tvav)`) si PL/SQL
(`MAX(ROUND(discount * (proc_tvav - 1), ...))` in `recalculeaza_totaluri_vanzari`). Daca se
schimba doar una, `VANZARI.TOTAL_TVA` si TVA-ul din XML vor diferi pe facturile cu cote mixte.
## 7. Ce ramane deschis
- Scenariul de la punctul 2 **nu e reprodus prin program**, ci dedus din cod + semantica `IIF`
masurata. O confirmare ar cere o factura scutita cu discount de document, introdusa manual.
- Ordinea randurilor in `C_TVA_FACTURA` dupa `GROUP BY` (deci ce alege `agettipcota(1)` intre `E` si
`Z`) nu e garantata de VFP si nu am fixat-o experimental. Am presupus `scutit=0` inaintea lui
`scutit=1`; daca e invers, categoria alocarii iese `E` in loc de `Z` — tot gresita, dar altfel.
- Validatorul ANAF **online** curent nu a fost testat (interdictie). Tot ce spun despre „nu e
respins" se refera la DUKIntegrator 2022 de pe disc.
- Nu am verificat calea in **valuta** (`prelucreaza_factura_valuta`) linie cu linie.

View File

@@ -1,216 +0,0 @@
# 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.

View File

@@ -1,185 +0,0 @@
# Cercetare: factura de retur ca document de sine statator (tip 8/9) + aviz retur (24)
Corectie fata de `retur_si_lista_preturi.md`: acea cercetare a documentat corect `But_retur`
(retur de articole *in interiorul* unei facturi normale, tip 1/5/7/10 — selectie **per articol**).
Aici e documentat mecanismul **separat**: factura de retur ca document propriu (tip 8 = retur lei,
tip 9 = retur valuta), unde utilizatorul alege **facturile sursa la nivel de document**, iar linia
de articole se populeaza integral din acele facturi.
## 1. Punctul de intrare si cursorul Oracle
Tile-ul de pe ecranul principal de facturare, `Page2.Cw1` (`COMUN\clase\ofundal_facturare.vc2:882-884`):
```
PROCEDURE Page2.Cw1.do_actiune
DO facturare_lista_de_preturi IN oproceduri_facturare.prg
ENDPROC
```
`facturare_lista_de_preturi` (`COMUN\programe\oproceduri_facturare.prg:114-116`) = `Do politica.mpr`,
care ruleaza meniul shortcut generat din `Meniuri\politica.mn2:14-15,45-46`:
```
DEFINE BAR 2 OF Shortcut PROMPT "\<Retur factura in lei"
ON SELECTION BAR 2 OF Shortcut factureaza(8)
...
DEFINE BAR 9 OF Shortcut PROMPT "Re\<tur factura in valuta"
ON SELECTION BAR 9 OF Shortcut factureaza(9)
```
Deci `factureaza(8)` / `factureaza(9)` sunt apelate direct, fara `toFactura` (nu e copiere).
Comentariul din `factureaza` (`COMUN\programe\ofacturare.prg:134-135`) confirma explicit numerotarea:
```
** (25007,8) - retur factura in lei ( 25017 )
** (25008,9) - retur factura in valuta ( 25018 )
```
In `Do Case` pe `tnTip` din `factureaza` (`ofacturare.prg:306-307`):
```
Case Inlist(tnTip, 8, 9, 24) && 8,9 = facturi de retur, 24 = aviz retur
lcSqlCursor = [{call ] + gcS + [.pack_facturare.cursor_retur(?poDate.in_valuta,?poDate.listaid,?gnIdUtil)}]
```
executat prin `goExecutor.oExecute(lcSqlCursor, [crsarticole])` (`ofacturare.prg:310-311`) — acelasi
cursor `crsarticole` folosit si de `cursor_preturi`/`cursor_comanda`/`cursor_contract` pentru
celelalte tipuri. Nota: `Do Case` are inaintea acestei ramuri o ramura separata `Case m.llCopiere`
(`ofacturare.prg:268`) care foloseste `pack_facturare.cursor_retur_document(...)` — dar `llCopiere`
e `.T.` doar cand `factureaza()` primeste un al doilea parametru `toFactura` (obiect), adica la
copiere de document, nu la intrarea normala prin meniu pentru tip 8/9 (`llCopiere = (Type('toFactura')='O')`,
`ofacturare.prg:111`). Vezi si punctul 6.
`nIdTipDoc` = 5 (FACTURA, `ofacturare.prg:193`, `tnTip<21`), formularul de date antet este
`frm_date_factura` (`ofacturare.prg:222-223`, acelasi caz `tnTip<21`).
## 2. Alegerea facturilor sursa
Formularul `frm_date_factura` (`COMUN\clase\ofacturare.vc2:8482`) are metoda dedicata
`do_cauta_facturi` (`ofacturare.vc2:9173-9212`):
```
Case Empty(Nvl(poDate.id_client,0))
amessagebox("Nu ati ales clientul!",...)
Case Empty(Nvl(poDate.id_valuta,0)) And poDate.tip = 9
amessagebox("Nu ati ales valuta!",...)
OTHERWISE
lcXMLFacturi = caut_facturi_multiple_client(poDate.id_client,poDate.in_valuta,poDate.id_valuta,.T.)
If !Empty(lcXMLFacturi) and gnButon = 1
Xmltocursor(lcXMLFacturi, "crsFacturiTemp")
...
poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")
poDate.descriere = cursor2lista("crsFacturiTemp", "numar_act", ",")
...
poDate.text_aditional = Iif(poDate.tip=8,[RETUR FACTURA ],[REFUND INVOICE FOR ]) + poDate.descriere
```
Dialogul e `caut_facturi_multiple_client` (`COMUN\programe\oproceduri_facturare.prg:2091-2121`),
un browse generic `cauta_alfa` cu titlu **"Alegeti facturile (mouse-click pe numar sau apasati SPACE)"**
(`:2104`, selectie multipla — `lnTipReturn = Iif(tlFacturiMultiple,1,0)`, apelat cu `tlFacturiMultiple=.T.`).
Criteriile SQL (`:2108-2111`):
```
lcSelect = [select serie_act,numar_act,data_act,dataora,id_vanzare from ] + gcS + [.fact_vfacturi ]
lcFiltruOriginal = [sters=0 and tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) ] + gcCondSucursala +
lcFiltruPart + lcFiltruValuta
```
adica **client** (`id_part`, obligatoriu ales inainte) si **valuta** (`in_valuta`/`id_valuta`, doar
daca `tnInValuta` e setat) — nu exista filtru SQL pe perioada sau serie/numar in interogare (coloanele
`Serie act, Numar act, Data, Data inreg.` sunt doar afisate/sortabile in browse-ul generic, filtrarea
pe ele e comportament generic al `cauta_alfa`, neverificat mecanismul intern). Sursa exclude explicit
tipurile de retur (`tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)`) — nu se poate face retur dintr-un retur.
**Selectie multipla**: se aduna prin `cursor2lista("crsFacturiTemp","id_vanzare",",")` intr-un
singur string CSV in `poDate.listaid` (id-urile facturilor alese), respectiv
`cursor2lista(...,"numar_act",",")` in `poDate.descriere` (afisat apoi pe antetul liniilor si in
titlul formularului de articole, `ofacturare.vc2:15022-15023`: `[ * Retur pentru facturile : ] + poDate.descriere`).
Validare obligatorie inainte de a continua (`frm_date_factura.inainte_de_do_termin`, `ofacturare.vc2:9523-9526`):
```
Case Empty(Nvl(poDate.descriere,[])) And Inlist(poDate.tip,8,9)
amessagebox("Nu ati ales factura/facturile pentru care se face returul!",48,"Atentie")
```
## 3. Popularea liniilor: gestiune si pret
`pack_facturare.cursor_retur` e un wrapper subtire peste implementarea reala
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:3943-3956`):
```
PROCEDURE cursor_retur(V_IN_VALUTA, V_LISTAID, V_ID_UTIL, V_CURSOR) IS
V_COPIERE NUMBER := 0;
V_PROFORMA NUMBER := 0;
BEGIN
pack_facturare.cursor_retur_document(V_IN_VALUTA, V_LISTAID, V_COPIERE, V_PROFORMA, V_ID_UTIL, V_CURSOR);
END;
```
`cursor_retur_document` (`:3958-4071`) selecteaza direct din `VANZARI_DETALII` (liniile facturilor
originale), filtrat pe `A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)` unde `CRS` e lista de id-uri
din `V_LISTAID` (= `poDate.listaid`, adica exact facturile alese la pasul 2, `:4059-4064`):
```sql
FROM VANZARI_DETALII A1 LEFT JOIN VANZARI_CURSURI A2 ON A1.ID_VANZARE = A2.ID_VANZARE ...
WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)
```
Coloane relevante, confirmate ca provin direct din linia facturii originale:
- **gestiune**: `nvl(A.ID_GESTIUNE, 0) as ID_GESTIUNE` (`:4034`), din `A1.ID_GESTIUNE` (`VANZARI_DETALII.ID_GESTIUNE`
al liniei originale, `:4055`) — confirma afirmatia lui Marius: gestiunea vine din factura sursa.
- **pret de achizitie**: `A.PRET_ACHIZITIE` (`:4035`), din `A1.PRET_ACHIZITIE` (`:4056`) —
preluat neschimbat din linia originala, fara recalcul de curs.
- **pret**: coloana `PRET` (`:4016-4024`) e pretul de vanzare al liniei originale, recalculat pe
cursul valutar daca moneda nu e nationala (`ROUND(A.CURS * ROUND(A.PRET,...) / A.MULTIPLICATOR, ...)`,
altfel `ROUND(A.PRET,...)`; plus `PRET_VAL` (`:4025-4030`) — valoarea in valuta straina, cand e cazul.
- `GESTIONABIL` (`:4002-4009`) pentru cazul `V_COPIERE=0` (retur, ramura efectiv folosita de
`cursor_retur`): `A.GESTIONABIL` = `NVL2(A1.ID_GESTIUNE,1,0)` (subselect intern, `:4048`) — gestionabil
doar daca linia originala avea gestiune.
Concluzie Q3: **ambele preturi** trec prin, atat cel de vanzare (`PRET`/`PRET_VAL`, ajustat pe curs)
cat si cel de achizitie (`PRET_ACHIZITIE`, neschimbat) — plus gestiunea originala (`ID_GESTIUNE`).
Numele coloanelor Oracle -> cate un camp cu acelasi nume in cursorul VFP `crsarticole` (maparea VFP
exacta camp-cu-camp nu a fost trasata pana in `crsfactura`; nu era necesara pentru raspuns).
## 4. Ce se poate face manual (`frm_facturare_articole`, `COMUN\clase\ofacturare.vc2:10968`)
- **Stergere linie**: `do_sterge` (`:14608-14693`) nu e restrictionat pe tip; pentru retur
(`Case Inlist(poDate.tip,8,9,24)`, `:14658-14659`) cantitatea stearsa se reintoarce in cursorul
sursa (`Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c`) — deci
**da, se pot sterge linii aduse**, iar cantitatea redevine disponibila pentru re-adaugare.
- **Retur partial (modificare cantitate)**: coloana de cantitate din grila sursa isi schimba titlul
in "Cant. max. de returnat" pentru tip 8/9 (`:15236-15239`); validarea in `do_verifica_articol`
(`:14743-14754`, `llRetur = Inlist(poDate.tip,8,9,24)`) respinge doar cazul in care cantitatea
ceruta ar depasi maximul returnabil — utilizatorul poate introduce orice cantitate <= maxim,
deci **da, retur partial e posibil**. `do_modifica` (`:13746-13914`) trateaza explicit
`Inlist(poDate.tip,8,9,24)` la `:13788,13895` fara blocaj suplimentar.
- **Adaugare linie care NU e in facturile sursa**: `do_adauga_articol` (`:12813-13086`) preia
articolul mereu din cursorul sursa al gridului (`lcCursor = [crsarticole]`, apoi
`Select (lcCursor) / Scatter Name poArticol`, `:12843-12851`) — pentru tip 8/9 acest `crsarticole`
e chiar rezultatul `cursor_retur` de la punctul 3, deci contine **doar** liniile facturilor alese.
Nu exista pe acest formular o cale de a alege un articol din lista de preturi completa cand
`poDate.tip` e 8/9 (spre deosebire de contract/comanda unde `crsarticole` ramane lista de preturi
libera) — **nu se poate adauga o linie in afara facturilor sursa**, prin design-ul continutului
cursorului, nu printr-o validare explicita de interzicere.
## 5. Aviz de retur (tip 24)
Acelasi mecanism de baza: `Inlist(tnTip,8,9,24)` la `ofacturare.prg:306-307` (acelasi
`pack_facturare.cursor_retur`) si aceleasi ramuri `Inlist(poDate.tip,8,9,24)` in
`frm_facturare_articole` (`do_adauga_articol:12863`, `do_alege_stoc:13230`, `do_modifica:13788,13895`,
`do_verifica_articol:14743`, `do_sterge:14658`). Diferenta: la antet, `nIdTipDoc` = 6 (AVIZ, ramura
`Otherwise` la `ofacturare.prg:194-195`, pentru ca 24 nu e `<21` si nu e in `Inlist(45,48,49,51,52)`),
iar formularul de date e `frm_date_aviz` (`ofacturare.prg:225`, ramura `Otherwise`), nu
`frm_date_factura`. Punctul de intrare pentru tip 24: `emitere_aviz_clienti` cu `tnTip=7`
(`COMUN\programe\oproceduri_facturare.prg:221-222`, `factureaza(24) && Retur aviz`), apelat din
tile-ul `Page3.Cw1` -> `aviz_clienti.mpr` (neverificat detaliat, nu s-a insistat conform cerintei).
## 6. Relatia cu `But_retur`
Mecanisme **complet separate**, confirmate pe cod:
- **Vizibilitate**: `but_retur` e vizibil doar pentru tip 1/5/7/10 (`ofacturare.vc2:15127`,
`This.but_retur.Visible = .T.` in ramura `Case Inlist(poDate.tip,1,5,7,10)`) — pe un document
tip 8/9 acest buton nu exista deloc in UI (ramura lui la `Init` e alta, `:15236`).
- **Formular de alegere a facturii sursa**: `But_retur` foloseste `caut_facturi_multiple_client_articol`
(`oproceduri_facturare.prg:2124-2159`, filtrat suplimentar pe `b.id_articol = ?pnIdArticol` —
cautare per-articol, cu titlu simplu "Alegeti factura"), tip 8/9 document foloseste
`caut_facturi_multiple_client` (`:2091-2121`, fara filtru pe articol, multi-select, titlu
"Alegeti facturile ..."). Doua functii diferite, in acelasi fisier, cu semnaturi aproape identice
dar interogari SQL diferite.
- **Procedura Oracle de populare a liniilor**: documentul tip 8/9 foloseste
`pack_facturare.cursor_retur` la nivel de document intreg (populeaza `crsarticole` cu toate
liniile facturilor alese, punctul 3 de mai sus). `But_retur` nu apeleaza `cursor_retur`/
`cursor_retur_document` deloc — foloseste `pack_facturare.cursor_gestiuni_articol_retur`
(`ofacturare.vc2:13230`, per articol individual, in `do_alege_stoc`) pentru a alege
gestiunea/seria/lotul de returnat pentru articolul deja selectat din lista de preturi a facturii
normale curente.
- **Punct comun**: niciunul direct. Singurul element comun e conventia de semn a cantitatii
(`Iif(Inlist(poDate.tip,8,9,24),(-1),1)`, folosita si in `do_alege_stoc` al `But_retur`,
`:13279-13309`, si pe formularul `frm_facturare_articole2`, `:17574-17585`) si textul UI
("cant. max. de returnat"). Concluzie: sunt doua fluxuri de cod independente care ating aceeasi
clasa de formular (`frm_facturare_articole`) dar prin metode si proceduri Oracle diferite.

View File

@@ -1,235 +0,0 @@
# Garda "aviz deja facturat" — verificare pe cod
Verificare pe cod, fara nicio modificare de fisier. Sursele citate:
- export pachet: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(mai jos: `EXPORT:<linie>`);
- corpul **desfasurat pe DB** (`MARIUSM_AUTO@ROA_CENTRAL`, `all_source`), citit ca sa se confirme ca
ce e in export e si ce ruleaza (mai jos: `DB PACK_FACTURARE:<linie>`). **Numerotarea difera**:
`sterge_factura` incepe la `EXPORT:5432`, dar la `DB PACK_FACTURARE:4192`. Nu exista offset
constant; textul insa e identic cuvant cu cuvant pe zona gardelor.
---
## 1. Blocheaza garda de azi si un AVIZ care are deja factura generata din el?
**DA.** Nu e nevoie de nicio garda noua pentru cazul asta — este exact ce testeaza a doua garda din
`sterge_factura`, prin `TIP IN (1, 2)`.
`EXPORT:5464-5476` = `DB PACK_FACTURARE:4224-4236`:
```
-- ptr. un aviz normal:
-- verific daca exista facturi sau avize de retur pe avizul resp.
SELECT COUNT(*)
INTO V_NR_AVIZE_FACT
FROM VANZARI_CORESP
WHERE STERS = 0
AND ID_VANZARE_AVIZ = V_ID_VANZARE
AND TIP IN (1, 2);
IF V_NR_AVIZE_FACT > 0 THEN
RAISE_APPLICATION_ERROR(-20000,
'Pe acest aviz s-au emis facturi / aviz de retur. Trebuie sa stergeti mai intai facturile / avizul de retur!');
END IF;
```
Cheia e semantica lui `TIP`, citita din **singurul scriitor** al tabelei, ramura cu ramura
(`finalizeaza_factura`, `EXPORT:14818-14839`):
| `VANZARI_CORESP.TIP` | scris cand | `ID_VANZARE_FACT` | `ID_VANZARE_AVIZ` | e retur? |
|---|---|---|---|---|
| **1** | `ntip = 4` — facturare din aviz (`EXPORT:14823-14826`) | factura | avizul-sursa | **NU** |
| 2 | `ntip = 24` — aviz de retur (`EXPORT:14828-14830`) | avizul de retur | avizul original | DA |
| 3 | `ntip in (8, 9)` — factura de retur (`EXPORT:14834-14836`) | factura de retur | factura originala | DA |
Deci `TIP = 1` este **corespondenta aviz -> factura normala**, nu retur. Garda de la `EXPORT:5473`
prinde ambele cazuri; textul mesajului chiar o spune ("s-au emis **facturi** / aviz de retur").
Formularea din brief ("garda de azi blocheaza documentul care are facturi/avize **de retur**") vine
dintr-o citire a comentariului `-- verific daca exista facturi sau avize de retur` in care "de retur"
se distribuie si peste "facturi"; codul zice altceva.
Deci: la ora asta, **exact regula ceruta de decizia 50 este deja implementata pentru avize**
(document cu urmasi = blocat; capatul lantului = liber).
### Ce NU citeste garda
- **`VANZARI.FACTURAT` nu e citit de nicio garda.** Pe calea de stergere e doar **scris**
(`EXPORT:5504-5510` pentru `V_TIP = 24`, `EXPORT:5527-5533` pentru `V_TIP = 4` — resetare la 0
cand dispare factura/avizul de retur), iar la facturare e **pus** de `marcheaza_facturat`
(`EXPORT:15381-15418`). Niciun `IF` / `RAISE` nu il interogheaza. Semnalul autoritar este
`VANZARI_CORESP`, `FACTURAT` e derivat redundant.
- **`ID_VANZARE_SURSA` / `ID_VANZARE_DEST` nu exista.** `VANZARI_CORESP` are exact 5 coloane
(interogare pe `all_tab_columns`): `ID_VANZARE_CORESP`, `ID_VANZARE_FACT`, `ID_VANZARE_AVIZ`,
`STERS`, `TIP`. Nici `VANZARI` nu are coloane `*_SURSA` / `*_DEST` (aceeasi interogare).
---
## 2. Exista alte garzi pe calea de stergere / modificare?
### 2a. Oracle — nu exista alta, dar garda e **atinsa pe ambele ramuri** de stergere
Interogare pe `all_source` (owner curent): singurele obiecte care contin `VANZARI_CORESP` sunt
**`PACK_FACTURARE` (package body)** si **`TRG_VANZARI_CORESP_BEFOINS`**. Deci nu exista o a doua
garda ascunsa in alt pachet.
**Triggerele nu contin garzi** (sursa citita integral din `all_source`):
| trigger | tabela | eveniment | ce face |
|---|---|---|---|
| `TRG_VANZARI_BEFOUPD` | `VANZARI` | UPDATE | 4 apeluri `pack_audit.verifica_val` (NR_ACT, SERIE_ACT, DATA_ACT, DATA_SCAD) — audit, nu blocare |
| `TRG_VANZARE_BEFOINS` | `VANZARI` | INSERT | — |
| `TRG_VANZARI_DET_BEFOINS` | `VANZARI_DETALII` | INSERT | — |
| `TRG_VANZARI_CORESP_BEFOINS` | `VANZARI_CORESP` | INSERT | doar `SEQ_VANZARI_CORESP.nextval` |
**Punct important de verificat, pentru ca la prima citire pare o portita:** in
`frm_facturi.do_sterge` apelul direct `pack_facturare.sterge_factura` e **comentat** pe ramura
documentelor cu note contabile — `ofacturare_comun.vc2:4807-4809` (`*!* modificare v 2.2.5`), iar
ramura activa (`:4810-4811`) cheama numai `pack_contafin.finalizeaza_stergere_nota`. **Nu e o
portita**: lantul se inchide in Oracle.
```
PACK_CONTAFIN:8322-8326 SELECT COUNT(*) INTO lnEInVanzari FROM vanzari WHERE cod = tnCod;
IF lnEInVanzari > 0 THEN
pack_facturare.sterge_din_vanzari(tnCod, tnAn, tnLuna, tnIdUtil);
PACK_FACTURARE:14996-14998 SELECT ID_VANZARE INTO V_ID_VANZARE FROM VANZARI WHERE COD = V_COD;
pack_facturare.sterge_factura(V_ID_VANZARE, V_LUNA, V_AN, V_ID_UTIL);
```
Apelantii lui `sterge_factura` in baza (cautare pe `all_source`) sunt exact doi:
`PACK_FACTURARE.sterge_din_vanzari` (`DB PACK_FACTURARE:14998`) si procedura standalone
`STERGE_DOCUMENT` (`DB STERGE_DOCUMENT:416`). Ambele cai trec prin garda.
### 2b. VFP — pe calea de stergere: nicio garda pe urmasi
`frm_facturi.do_sterge` (`COMUN\clase\ofacturare_comun.vc2:4661-4877`) verifica, inainte de apel:
luna inchisa (`:4676`), `sters = 1` (`:4689`), confirmare (`:4694`), luna curenta (`:4701`) si
referinte de incasari/plati (`ReferinteDocumenteNota`, `:4723-4727`). **Nimic despre `FACTURAT`,
`VANZARI_CORESP` sau urmasi** — verificarea lantului e delegata integral Oracle-ului.
### 2c. VFP + Oracle — pe calea de **modificare**: nicio garda, nici pe urmasi, nici pe lant
`frm_facturi.do_modifica` (`ofacturare_comun.vc2:4541-4640`) blocheaza doar doua lucruri
(`:4590`, `:4635`):
```
If poRec.sters = 0 AND NVL(m.pnEFactura,0) = 0
...
amessagebox("Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in eFactura!", 48, "Atentie")
```
`pack_facturare.modifica_date_factura` (`EXPORT:14439-14509`) e un lant de `UPDATE`-uri fara niciun
`IF` de validare si fara niciun `RAISE_APPLICATION_ERROR`. Ceea ce e coerent cu ce modifica azi
(ruta, delegat, agent, masina, dataora exp., text aditional, tip SAFT, eFactura, serie/numar/data act,
data scadenta) — campuri de antet care nu ating lantul aviz -> factura.
---
## 3. Concluzia operationala pentru decizia 50 / S7
**Regula ceruta de decizia 50 exista deja, si nu are gol pe aviz.** `sterge_factura` implementeaza
azi, in trei garzi consecutive, exact "documentul cu urmasi e blocat, capatul lantului e liber":
| garda | `EXPORT` | ce blocheaza |
|---|---|---|
| 1 | 5452-5462 | **factura** care are facturi de retur peste ea (`TIP = 3`) |
| 2 | 5466-5476 | **aviz** care are factura (`TIP = 1`) sau aviz de retur (`TIP = 2`) peste el |
| 3 | 5480-5494 | **factura din aviz** ale carei avize-sursa au primit intre timp aviz de retur |
A treia garda merita subliniata pentru decizia 50: factura din aviz e editabila **doar** cat timp
niciunul dintre avizele ei nu are aviz de retur; nu e "capat de lant" neconditionat.
**Deci S7 nu are nevoie de o garda noua ca regula — dar are nevoie de o citire noua a aceleiasi
conditii, ca moment.** Garda de azi e in interiorul lui `sterge_factura`, adica se manifesta ca
`ORA-20000` **in mijlocul** pasului de stergere din regenerare, dupa ce utilizatorul a completat
formularul si dupa ce s-a deschis tranzactia. Pentru un flux de editare asta e o esuare tarzie.
Ce lipseste e un **pre-flight read-only**, inainte de intrarea in formular, pe exact aceeasi
conditie (fara reimplementarea regulii):
```sql
select count(*) from vanzari_coresp
where sters = 0 and id_vanzare_aviz = :id_vanzare and tip in (1, 2, 3)
```
plus, pentru cazul "factura din aviz", subinterogarea din garda 3 (`EXPORT:5480-5488`). Garda din
`sterge_factura` ramane pe loc ca plasa de siguranta — nu se muta, nu se slabeste.
Interogarea se face pe `VANZARI_CORESP`, **nu** pe `VANZARI.FACTURAT`: `FACTURAT` e un marcaj
redundant, derivat, scris/resetat de `marcheaza_facturat` / `sterge_factura`, si necitit de nicio
garda; `VANZARI_CORESP` e semnalul autoritar.
---
## 4. Inventar: ce tipuri de documente-parinte produc urmasi prin `VANZARI_CORESP`?
**Doar trei, toate scrise din acelasi `CASE` din `finalizeaza_factura` (`EXPORT:14818-14839`), prin
singurul scriitor `scrie_corespondente_vanzari` (`EXPORT:15481-15516`, singurul `INSERT INTO
VANZARI_CORESP` din tot pachetul — si din toata baza, cf. `all_source`).**
| parinte | urmas | `TIP` | de unde vine lista parintilor |
|---|---|---|---|
| **aviz** (`VANZARI.TIP in 21, 22, 26, 42`) | factura din aviz (`ntip = 4`) | 1 | `clistaid_avize`, setat la `EXPORT:7037` in `scrie_factura_avize`; lista o construieste VFP prin `caut_avize`, filtrata `a.tip in (21,22,26,42) and a.facturat = 0` — `COMUN\programe\oproceduri_facturare.prg:2036-2038` |
| **aviz** | aviz de retur (`ntip = 24`) | 2 | `clistaid` |
| **factura** | factura de retur (`ntip in 8, 9`) | 3 | `clistaid` |
**Ce NU produce urmasi prin `VANZARI_CORESP`** (deci decizia 50 nu are acoperire acolo prin garda
existenta):
- **proforma -> factura.** Proforma sta in `VANZARI` cu `EPROFORMA = 1`; `finalizeaza_factura` nu are
ramura care sa scrie corespondenta pentru ea. `TIP = 4` este o **propunere** de la S5c, nu cod
existent. Mai mult, `pack_facturare.sterge_proforma` (`EXPORT:5610-5635`) **nu are nicio garda** —
corpul ei e doar doua `UPDATE ... SET STERS = 1` pe `VANZARI` si `VANZARI_DETALII`. O proforma din
care s-a emis factura se poate sterge azi fara niciun avertisment. (Calea VFP:
`ofacturare_comun.vc2:4707-4719`, care iese din `do_sterge` inainte de restul verificarilor.)
- **comanda -> factura.** Legatura e `VANZARI.ID_COMANDA` + `pack_facturare.inchide_comanda`
(`EXPORT:5769`), nu `VANZARI_CORESP`. `COMENZI` **nu are coloana `FACTURAT`** (verificat pe
`all_tab_columns`) — flagul `facturat` din grid vine din view-ul de incarcare. Garda exista, dar e
**in VFP si pe comanda**, nu pe factura: `COMUN\clase\ocomenzi.vc2:1806-1807` la modificare
("Aceasta comanda a fost facturata si nu mai poate fi modificata!") si `:2065-2066` la stergere.
- **contract -> factura.** Legatura e `VANZARI.ID_CTR` + `CTR_RATE_FACTURI`
(atinsa de `sterge_factura` la `EXPORT:5566-5580` pentru `V_TIP IN (2, 6, 52)`); nicio linie in
`VANZARI_CORESP`, nicio garda de tip "are urmasi".
---
## 5. Ce am verificat direct vs. ce am dedus
**Verificat direct pe cod / pe metadate:**
- textul celor trei garzi din `sterge_factura`, in export **si** desfasurat din `all_source` (identice);
- `CASE`-ul din `finalizeaza_factura` care da semantica lui `TIP`, ramura cu ramura;
- corpul lui `scrie_corespondente_vanzari` si `marcheaza_facturat`;
- ca `INSERT INTO VANZARI_CORESP` exista intr-un singur loc (grep pe export + `all_source` pe toata baza);
- sursa integrala a celor 4 triggere de pe `VANZARI` / `VANZARI_DETALII` / `VANZARI_CORESP`;
- lista completa a apelantilor lui `sterge_factura` (`all_source`), inclusiv lantul
`finalizeaza_stergere_nota -> sterge_din_vanzari -> sterge_factura`;
- coloanele reale ale `VANZARI_CORESP`, `PROFORME`, si absenta lui `FACTURAT` din `COMENZI`
(`all_tab_columns`);
- `frm_facturi.do_sterge` si `frm_facturi.do_modifica` integral, plus `modifica_date_factura`;
- filtrul `caut_avize` din `oproceduri_facturare.prg`.
**Dedus / cu limite declarate:**
- Ca `TIP = 1` inseamna "factura normala din aviz" e o deductie din singurul punct de scriere
(`ntip = 4` -> `scrie_corespondente_vanzari(1)`) — solida, dar e deductie, nu o eticheta declarata
intr-un nomenclator.
- Ca setul tipurilor-parinte pentru `TIP = 1` e `{21, 22, 26, 42}` vine din filtrul VFP `caut_avize`,
nu dintr-o restrictie in Oracle. Pachetul insereaza **orice** `ID_VANZARE` primit in
`clistaid_avize`, fara filtru pe tip (`EXPORT:15494-15504`). Alt apelant (alt produs ROA, un import)
ar putea introduce alte tipuri.
- Nu am verificat daca vreun **alt produs ROA** (ROACONT / ROAGEST / ...) are o cale proprie de
stergere care ocoleste `sterge_factura`. Am verificat doar ca in Oracle nu exista alt apelant si
ca in ROAFACTURARE ambele ramuri din `do_sterge` ajung acolo.
**Din date, deci nedovaditor:** interogarea pe `VANZARI_CORESP` din baza de dev arata `TIP=1` cu
parinti de tip 21 si 22, `TIP=2` cu parinte 22, `TIP=3` cu parinte 1 — consistent cu tabelul de mai
sus, dar volumul e de ordinul unitatilor (8 randuri in total). **Nu e dovada**; concluziile de mai
sus vin din cod.
## 6. Ce nu s-a putut stabili
Nimic din cele 4 intrebari nu a ramas nedeterminat. Singurul punct pe care l-as lasa explicit
deschis pentru S7 este cel de la §4: **golul real nu e pe aviz, e pe proforma** —
`sterge_proforma` nu are absolut nicio garda, iar daca S5c chiar introduce `TIP = 4`
(proforma -> factura), garda din `sterge_factura` **nu se aplica automat**, pentru ca `sterge_proforma`
e o procedura complet separata care nu o apeleaza.

View File

@@ -1,231 +0,0 @@
# Golul ntip=4 (facturare din avize) fata de proiectarea parametrului de cont contabil
Cercetare read-only, continua `canal_cont_venit_fara_politica.md` (sectiunea "Proiectarea
parametrului de cont contabil", liniile 13-227). Confirma si extinde golul deja semnalat, mai
succint, in `parametru_cont_contabilizeaza_articol.md:49-56,167-168`.
Sursa Oracle: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii). **Toate liniile citate mai jos sunt verificate direct pe acest fisier in aceasta
runda** — numerele din cererea initiala (7520-7537, 5096) s-au dovedit **identice**, fara offset,
fata de fisierul curent (nu +17 cum se anticipa).
## Verdict, in cinci randuri
**Ramura `ntip=4` e EXCLUSA STRUCTURAL, dar nu prin mecanismul presupus initial** (nu prin garda
`id_pol` din `cursor_articol`/FACT-024 a lui `contabilizeaza_articol`). Blocajul real e **cu o
functie mai devreme**: `adauga_articol_factura`, ramura `WHEN pack_facturare.ntip = 4`
(`:5080-5103`), cauta randul original din avizul sursa prin `A.ID_POL = V_ID_POL` **fara niciun
handler de exceptie** — daca `V_ID_POL` e `NULL` (exact cazul "articol fara politica"), `NULL =
NULL` nu se potriveste niciodata in SQL, deci `SELECT ... INTO` **cade mereu cu `NO_DATA_FOUND`
neprins**, la momentul **adaugarii articolului in factura** (`adauga_articol_factura`), cu mult
inainte ca linia sa ajunga vreodata la `contabilizeaza_articol`. Practic, un articol fara politica
**nu poate fi adaugat deloc** pe un document `ntip=4` — eroarea apare la primul pas, nu la scriere.
**Gol sora mai important, gasit in aceasta cercetare**: ramurile de **aviz** (`ntip` in afara
bucket-ului "factura") — `28`/`29` (SCD hardcodat `461`) si toate celelalte (SCD hardcodat `418`)
— **raman perfect accesibile** cu `cont_venit` populat, iar ramura noua propusa hardcodeaza
`SCD='4111'` necondiționat de `ntip`, ceea ce ar scrie contul gresit pe orice aviz cu articol fara
politica. Vezi sectiunea "Ramuri sora" mai jos — mai grav decat golul `ntip=4` insusi, pentru ca
e efectiv atins, nu doar teoretic.
## 1. Lantul de dovezi pentru "ntip=4 e exclus" — pas cu pas
### 1.1. `ntip` e o variabila de pachet, setata o singura data per document
```
ff_...:165 ntip NUMBER(2); -- declarata in spec, variabila publica de pachet
ff_...:1882 pack_facturare.ntip := V_TIP; -- in initializeaza_date_factura (una din cele 4 supraincarcari, ~1650-1918)
```
`V_TIP` vine din `poDate.Tip` la apelul VFP `pack_facturare.initializeaza_date_factura(...)`
(`COMUN\clase\ofacturare.vc2:13981-13999`, argumentul `Alltrim(Str(poDate.Tip))` pozitionat ca
`V_TIP`). O data setat, `pack_facturare.ntip` ramane valabil pentru toate apelurile ulterioare
din aceeasi tranzactie (`adauga_articol_factura`, apoi `scrie_factura2`/`scrie_factura_avize_retur`/
`scrie_aviz_retur` -> `contabilizeaza_articol`) — **e o singura valoare per document, nu per linie**.
### 1.2. Apelanti VFP care produc `poDate.Tip = 4`
Comentat consecvent `&& factura din aviz` / `&& aviz` in tot codul VFP:
```
COMUN\clase\ofacturare_comun.vc2:4347 If poDate.tip = 4 && factura din aviz
COMUN\programe\oproceduri_facturare.prg:1253 If poDate.tip = 4 && factura din aviz
COMUN\programe\ofacturare.prg:1512 Case poDate.tip = 4
COMUN\clase\ofacturare.vc2:9534,9636,9674,14301,15151,18299,19022 Case poDate.tip = 4 (afisare/validare)
```
`ofacturare.prg:1268,1488,1504` trateaza si combinatia `poDate.tip = 4 And Not
Empty(poDate.id_comanda_aviz)` alaturi de `ntip IN (3,21,28,42,47)` — confirma ca `ntip=4` e
categoria distincta "facturare pe baza de avize existente" (formularul dedicat de facturare din
avize), separata de facturarea directa sau din comenzi.
### 1.3. `adauga_articol_factura`, ramura `ntip=4` — blocajul real
```
ff_...:4989-5015 PROCEDURE adauga_articol_factura(..., V_ID_POL IN NUMBER, ...)
```
`V_ID_POL` e parametru direct (nu derivat), trimis de VFP la
`COMUN\clase\ofacturare.vc2:14072` (si identic `:18107`):
```
Nvl(Alltrim(Str(poArt.id_pol)),[NULL])
```
Daca `poArt.id_pol` e `.NULL.` in VFP (cazul "articol fara politica" — premiza intregului
proiect #13), `Str()` propaga `.NULL.`, iar `Nvl(...)` trimite literalul SQL `NULL` — deci
`V_ID_POL` ajunge `NULL` in Oracle exact cand articolul n-are politica.
In corpul `adauga_articol_factura`, CASE-ul care determina pretul/TVA/valuta ramurii (`:5052-5220`)
are, pentru `ntip=4`:
```
ff_...:5080-5103
WHEN pack_facturare.ntip = 4 THEN
-- facturare din avize
SELECT DISTINCT A.PRET, A.PROC_TVAV, A.ID_VALUTA, A.PRET_CU_TVA, B.IN_STOC
INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
FROM VANZARI_DETALII A
LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL
WHERE A.ID_ARTICOL = V_ID_ARTICOL
AND A.ID_POL = V_ID_POL
AND NVL(A.ID_GESTIUNE, -1000) = V_ID_GESTIUNE
AND A.DISCOUNT_UNITAR = V_DISCOUNT_UNITAR
AND NVL(A.CONT, 'XXXX') = V_CONT
AND A.ID_VANZARE IN (SELECT X FROM table(cast(CHARN2COLLECTION(pack_facturare.clistaid, ',') AS num_tab)));
```
**Fara niciun `EXCEPTION WHEN NO_DATA_FOUND`** — spre deosebire de ramurile vecine din acelasi
CASE: `ntip=45` are handler cu `FACT-018` (`:5140-5145`), `V_OPT_FACTURARE=3` are handler cu
fallback + `FACT-012` (`:5167-5185`), `ELSE` are handler cu `FACT-013` (`:5188-5198`). Doar ramurile
`ntip IN (3,21,28,42,47)` (`:5053-5078`, cauta in `COMENZI_ELEMENTE` dupa comanda, nu dupa politica)
si `ntip=4` sunt fara plasa de siguranta.
Cu `V_ID_POL = NULL`, `A.ID_POL = V_ID_POL` **nu se poate potrivi niciodata** (semantica SQL: orice
comparatie cu `NULL` da `UNKNOWN`, niciodata `TRUE`, indiferent ce e stocat in `A.ID_POL`) —
`SELECT ... INTO` (fara `DISTINCT`+agregat care sa tolereze zero randuri) **arunca mereu
`NO_DATA_FOUND`**. Neprinsa aici, exceptia se propaga necaptata pana la apelantul PL/SQL (blocul
anonim generat de VFP), care se intoarce la `goExecutor` ca eroare Oracle bruta (`ORA-01403: no
data found`), **nu ca `FACT-024`** — vezi punctul 3 mai jos pentru diferenta.
**Concluzie pas 1.3**: un articol fara politica (`V_ID_POL=NULL`, exact scenariul `cont_venit`)
**nu poate fi adaugat pe un document `ntip=4`** — apelul `adauga_articol_factura` insusi esueaza,
inainte ca `VANZARI_DETALII_TEMP` sa primeasca randul, deci **cu mult inainte** ca
`contabilizeaza_articol` (unde e ramura noua `cont_venit`) sa vada vreodata acea linie.
### 1.4. Blocajul secundar (daca 1.3 n-ar exista): `contabilizeaza_articol` insusi
Chiar daca ipotetic linia ar ajunge in `VANZARI_DETALII_TEMP` cu `id_pol=NULL`, funcia care scrie
efectiv factura are propria garda, **inaintea oricarui ntip**:
```
ff_...:7278-7302 BEGIN
SELECT COMPUS, ID_POL_ART INTO V_COMPUS, V_ID_POL_ART
FROM VCRM_POLITICI_PRET_ART
WHERE ID_ARTICOL = detalii_articol.id_articol
AND ID_POL = detalii_articol.id_pol;
EXCEPTION
WHEN NO_DATA_FOUND THEN
... RAISE_APPLICATION_ERROR(-20000, '... (FACT-024)');
END;
```
Aceasta ruleaza **la inceputul functiei, inainte de `OPEN cursor_articol`** (`:7393`) si deci
inainte de CASE-ul pe `ntip` (`:7398-7423`) si de IF-ul `ntip<>4` (`:7472`). Cu `id_pol=NULL`,
`FACT-024` cade aici, indiferent de `ntip` — **confirma independent ca ramura `ntip=4` de la
`:7520-7537` (`scrie_fact_aviz_custodie`) e neatinsa azi de o linie fara politica**, e a doua
plasa de siguranta, redundanta cu 1.3 dar pe alt strat.
**Ramura propusa `cont_venit IS NOT NULL`** (proiectare `canal_cont_venit_fara_politica.md`
sectiunea 3) se planteaza "inainte de `:7278`" — deci **inlocuieste ambele garda de mai sus** cand
`cont_venit` e populat. Asta e sigur (design-ul insusi), dar devine irelevant pentru `ntip=4`
pentru ca linia nu ajunge niciodata aici (blocata la 1.3).
## 2. Ce se intampla defensiv daca cineva ajunge totusi acolo
Doua scenarii distincte, cu raspunsuri diferite:
**(a) Scenariul normal — `cont_venit` populat, `id_pol` NULL (cum cere premiza proiectului)**:
esec **la adaugarea articolului**, nu la scrierea facturii. Eroare Oracle bruta
(`ORA-01403: no data found`), aparuta din `adauga_articol_factura` (`:5080-5103`), propagata prin
`goExecutor` catre VFP ca `oPrelucrareEroare()` — **nu `FACT-024`** (acela e alt cod, alta functie,
alt moment). Utilizatorul vede un mesaj Oracle generic, nu un mesaj de business explicativ. Nu
exista azi niciun cod care sa traduca acest caz intr-un mesaj prietenos — e un gol de UX, nu de
integritate a datelor (nu se scrie nimic gresit, doar esueaza fara explicatie buna).
**(b) Scenariul defensiv real — `cont_venit` SI `id_pol` populate simultan pe aceeasi linie**
(nimic in schema nu impune exclusivitate reciproca; `VANZARI_DETALII_TEMP.CONT_VENIT` e o coloana
noua, fara `CHECK` care sa lege de `ID_POL`). Daca cineva (bug in VFP, sau folosire deliberata
combinata pe viitor) ar trimite ambele populate pe un document `ntip=4`: pasul 1.3 ar reusi
(`V_ID_POL` nu mai e NULL, deci `SELECT` din `VANZARI_DETALII` isi gaseste randul), linia intra in
`VANZARI_DETALII_TEMP` cu `cont_venit` **si** `id_pol` completate. La `contabilizeaza_articol`,
ramura noua (`detalii_articol.cont_venit IS NOT NULL`) preia controlul complet — **fara sa
verifice `ntip`** — si executa necondiționat `scrie_nota` + `descarca_gestiune` + discount, exact
ca pentru o factura normala. **Nu cade cu nicio eroare. Scrie tacut date gresite**: sare complet
peste `scrie_fact_aviz_custodie` (`:7521-7536`), care exista tocmai pentru ca marfa a iesit deja
din gestiune cand s-a scris avizul sursa — rularea `descarca_gestiune` in loc de acea functie
**descarca stocul a doua oara** pentru aceeasi marfa, fara nicio garda care sa prinda dubla
scadere. Acesta e raspunsul relevant pentru textul de plan: nu un crash, un **defect tacut de
date** daca vreodata cele doua canale se intalnesc pe aceeasi linie.
## 3. Toate valorile `ntip` relevante in zona `contabilizeaza_articol` (`:7173-7547`) — verdict per valoare
Ramura noua (`cont_venit IS NOT NULL`) inlocuieste tot ce e listat mai jos, necondiționat de `ntip`
(design-ul, sectiunea 3, nu mentioneaza `ntip` deloc in continutul ramurii noi). Verdictul de mai
jos e "atinsa" = poate ajunge cu `cont_venit` populat prin `adauga_articol_factura` fara sa esueze
la pasul 1.3-echivalent; "neatinsa" = blocata structural inainte de a ajunge aici (ca `ntip=4`);
"ambigua" = depinde de alt cod neverificat complet in aceasta runda.
| `ntip` | Comportament azi in `contabilizeaza_articol` | Atinsa de linie `cont_venit`? | Verdict fata de ramura noua |
|---|---|---|---|
| `<=20` sau `IN (43,44,45,46,48,49,51,52)` (`:7400-7412`) | SCD/ASCD din politica (`crs_rand_articol.scd`) — bucket "factura" | **DA** — `adauga_articol_factura` pentru aceste `ntip` foloseste calea generica (`ELSE`, `:5187-5203`, fara cerinta de `id_pol`) sau ramuri proprii (`2,6,26,52`→contract, `45`→restaurant) care oricum accepta `V_PRET_TEMP` cand nu gasesc potrivire | **E cazul acoperit de design** — SCD='4111' hardcodat e gandit exact pentru acest bucket. Fara probleme noi, cu exceptia sub-cazurilor de mai jos. |
| `= 46` (`nTipNotaPlata`), sub-caz al bucketului "factura" | `scrie_nota` **NU se apeleaza** (`:7441`, `IF ntip <> nTipNotaPlata`) | DA (e in bucketul de mai sus) | **GOL SORA nou, neconsemnat in proiectare**: ramura noua apeleaza `scrie_nota` necondiționat — pentru un document `NotaPlata` cu linie `cont_venit`, s-ar scrie o nota care azi e sarita intentionat. De adaugat aceeasi garda in ramura noua. |
| `= 7`, sub-caz al bucketului "factura" | `EXPLICATIE` primeste sufix `' INVOICE:' + cdescriere` (`:7431-7433`) | DA | **Gol cosmetic**: ramura noua foloseste direct `detalii_articol.explicatia`, fara sufix. Probabil acceptabil (design-ul il numeste "mai bun"), dar e o schimbare de continut pe documente de export, de confirmat. |
| `IN (8,9)`, sub-caz al bucketului "factura" | `EXPLICATIE` primeste sufix `' RETUR FACTURA:' + cdescriere` (`:7434-7436`) | DA | Acelasi gol cosmetic ca mai sus. |
| `IN (28,29)` (`:7413-7417`) | SCD hardcodat `'461'` — aviz catre clienti debitori | **DA** — `adauga_articol_factura` pentru `28` intra pe ramura `ntip IN (3,21,28,42,47)` (`:5053-5078`), care cauta in `COMENZI_ELEMENTE` dupa comanda+pret, **nu dupa `id_pol`** — un articol fara politica poate gasi potrivire aici daca exista element de comanda corespunzator | **GOL SORA, mai grav decat `ntip=4`**: ramura noua ar scrie `SCD='4111'` in loc de `'461'` pe un aviz catre client debitor — cont contabil gresit, atins efectiv. |
| toate celelalte (`ELSE`, `:7418-7422`: `21,22,23,24,25,27,30,41,42,47,...`) | SCD hardcodat `'418'` — aviz generic | **DA, pentru aceleasi motive** (multe din aceste `ntip` — `3,21,42,47` — trec prin ramura "din comenzi" fara cerinta de `id_pol`; altele avansate direct din bucket implicit `ELSE` din `adauga_articol_factura`, `:5187-5203`, care nu cere `id_pol` deloc) | **Acelasi gol SCD hardcodat**, aplicabil la orice document de tip aviz (nu doar 28/29). |
| `= 4` (`:7472,7520-7537`) | `scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount | **NU** — exclus structural la pasul 1.3 (`adauga_articol_factura` esueaza inainte) | Neatins in scenariul normal (a); atins doar in scenariul defensiv (b) daca cineva forteaza `id_pol` si `cont_venit` simultan — vezi sectiunea 2. |
**Cel mai important rezultat al acestui tabel**: golul cerut explicit (`ntip=4`) e cel **mai putin
grav** dintre cele gasite, pentru ca e singurul exclus structural. Golurile reale, efectiv atinse,
sunt hardcodarea `SCD='4111'` pe orice document de tip **aviz** (`28`,`29` si bucket-ul `ELSE`) si
omisiunea gardei `ntip=46` pentru `scrie_nota`.
## 4. Text propus pentru plan (sectiunea deciziei 34)
```
**Ramura `ntip=4` (facturare din avize, `:7520-7537`) — exclusa structural, nu necesita cod
suplimentar.** `adauga_articol_factura`, ramura `WHEN ntip=4` (`:5080-5103`), cauta randul sursa
din `VANZARI_DETALII` prin `A.ID_POL = V_ID_POL`, fara handler de exceptie — cu `V_ID_POL=NULL`
(exact cazul liniei fara politica), comparatia nu se poate potrivi niciodata, deci apelul cade cu
`ORA-01403` la adaugarea articolului, inainte ca linia sa ajunga la `contabilizeaza_articol`. Un
articol fara politica nu poate fi, structural, adaugat pe un document `ntip=4`; ramura noua
`cont_venit` nu are nimic de tratat aici.
**Gol descoperit, de acoperit inainte de implementare**: ramurile de **aviz** (`ntip IN (28,29)` →
`SCD='461'`, celelalte → `SCD='418'`, `:7413-7422`) **raman accesibile** cu `cont_venit` populat —
`adauga_articol_factura` nu cere `id_pol` pentru aceste `ntip` (foloseste calea "din comenzi" sau
calea implicita). Ramura noua trebuie sa aleaga `SCD` in functie de `ntip` (pastrand `'461'`/`'418'`
pentru avize), nu sa hardcodeze `'4111'` necondiționat — altfel orice aviz cu articol fara politica
primeste contul de clienti in loc de contul de aviz. Similar, ramura noua trebuie sa pastreze garda
`IF ntip <> nTipNotaPlata` inainte de a apela `scrie_nota` (azi sarita pentru `ntip=46`).
**Defensiv (opțional)**: daca se doreste o garda explicita in loc de a te baza pe imposibilitatea
structurala de mai sus, se poate adauga `IF detalii_articol.cont_venit IS NOT NULL AND
pack_facturare.ntip = 4 THEN RAISE_APPLICATION_ERROR(...)` la inceputul ramurii noi — util doar
daca cineva ar reusi vreodata sa populeze simultan `id_pol` si `cont_venit` pe o linie `ntip=4`
(azi imposibil pe caile VFP cunoscute), caz in care ramura noua ar rula altfel tacut
`descarca_gestiune` in loc de `scrie_fact_aviz_custodie`, riscand dubla descarcare de gestiune.
```
## 5. Ce nu s-a putut stabili in aceasta runda
- **Daca exista vreo cale VFP care sa populeze simultan `poArt.id_pol` si viitorul `cont_venit`**
(scenariul defensiv (b)) — cercetarea de fata arata doar ca schema/codul Oracle nu o impiedica;
n-am gasit (si nici n-am cautat exhaustiv) un loc in VFP care ar genera aceasta combinatie azi.
Probabil imposibil pe fluxurile curente (proiectarea de baza presupune `cont_venit` populat
**doar** cand `id_pol` lipseste), dar nu demonstrat exhaustiv pe tot codul VFP.
- **Comportamentul exact `oExecute`/`oPrelucrareEroare` la o exceptie Oracle neprinsa** (scenariul
(a)) — presupus ca ajunge la utilizator ca mesaj Oracle brut prin mecanismul general de eroare al
`goExecutor`, dar nu am urmarit codul `oPrelucrareEroare()` in aceasta runda pentru a confirma
formatarea exacta.
- **Daca `ntip IN (23,25,30,41)` (transfer subunitati) si `IN (42,47)` (custodie) pot, in practica,
sa primeasca un articol fara politica** prin ramura "din comenzi" a `adauga_articol_factura`
(`:5053-5078`) — argumentat structural posibil (nu cere `id_pol`), dar nu testat pe date, nu
urmarit pana la capat daca `COMENZI_ELEMENTE` insusi ar putea contine un rand fara politica.
## Handoff
Cercetare incheiata, toate cele 5 livrabile cerute de team-lead sunt in sectiunile 1-4 de mai sus.
Niciun cod atins, nicio scriere Oracle, doar `SELECT`/`Read`/`Grep`. Context consumat moderat —
nu a fost necesara predarea de context.

View File

@@ -1,191 +0,0 @@
# Handoff — S4 runda 1 (PAGE3 doar afisare), predare pe prag de context
Sesiune intrerupta la prag de context (`monitorizare-context.md`). Stare **stabila si consistenta**
(text = binar), dar cu schele de diagnostic inca in cod — de scos inainte de livrare.
## 1. Ce am modificat, unde, si write-back-ul
Toate in `COMUN` (cross-proiect).
- **`COMUN\programe\ofacturare_editare.prg`** — **write-back N/A (e `.prg`, nu `.vcx`, se ruleaza direct)**.
Antet actualizat (linia cu `*!* helpere...`) + doua functii noi, adaugate la coada fisierului:
- `IncarcaVanzareNota(tnCod, tnNract, tcSerieAct, tdDataAct)` — gaseste randul din `VANZARI`
corespunzator notei curente, in cursorul `tvanz`.
- `IncarcaArticoleFactura(tnIdVanzare)` — incarca liniile active din `VANZARI_DETALII` (+ denumire
articol/gestiune/valuta) in cursorul `tvd`.
Fisier ASCII (fara diacritice), editat direct cu Edit tool — fara riscul de corupere cp1252.
- **`COMUN\clase\omodificari.vc2`** (clasa `frm_modific2024`) — **text si binar SINCRONIZATE**,
ambele includ inca **cod de diagnostic care trebuie scos**:
- PAGE3 nou pe `pgfArticole` (`PageCount=3`, `PAGE3.Caption="Articole factura"`) — linia cu
`PageCount = 3, ;` in blocul `ADD OBJECT 'pgfArticole'`.
- Grid nou `pgfArticole.PAGE3.grdArticoleFactura` (13 coloane, `RecordSource="tvd"`,
`ReadOnly=.T.` la nivel de grid si pe fiecare `Text1`), plus intrarile `OBJECTDATA`
corespunzatoare (cautabile dupa `grdArticoleFactura`).
- Proprietati noi pe clasa: `lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare` (in blocul
`*<PropValue>`, cautabile dupa `lareaarticolevanzari`/`nidvanzare`/`ntipvanzare`).
- `Load()`: placeholder `CREATE CURSOR tvd (...)` daca nu e deja deschis — **necesar**, altfel
grid-ul incearca `USE tvd` la constructie si pica pe dialog nativ "Open" (vezi sectiunea 4).
- `Show()`: bloc nou (cautabil dupa `This.lAreArticoleVanzari`) care apeleaza
`IncarcaVanzareNota`/`IncarcaArticoleFactura` si comuta `pgfArticole.PageCount` intre 2 si 3.
- **DE SCOS inainte de livrare**: 5 linii `STRTOFILE(...'diag_class.txt'...)` inserate ca
checkpoint-uri de depanare in `Init()` (3 linii: inceput, dupa `DoDefault()`, sfarsit) si
`Load()` (2 linii: inceput, sfarsit) — cautabile dupa `diag_class`. Nu au efect functional
(doar scriu intr-un fisier de log), dar nu trebuie sa ramana in cod livrat.
- **Fidelity check txt2vcx: OK** la ultimul write-back (cel CU liniile de diagnostic inca in el).
`.vcx`/`.vct` au mtime 12:10, exact dupa acel write-back — deci **binarul reflecta exact
`.vc2`-ul curent**, inclusiv diagnosticul.
### Backup-uri pe disc (`COMUN\clase\`)
| Fisier | Continut |
|---|---|
| `omodificari.vc2.pre_s4runda1.bak` | Originalul, dinaintea oricarei modificari S4 |
| `omodificari.vc2.pre_diag.bak` | Versiunea mea **curata** (fara cele 5 linii de diagnostic) — **asta e sursa de folosit pentru versiunea finala** |
| `omodificari.vc2.mine_s4_full.bak` | Identic cu `pre_diag.bak` (copie facuta in timpul unui test de izolare) |
| `omodificari.vcx.mine_s4.bak` / `omodificari.vct.mine_s4.bak` | **Binarul** corespunzator lui `pre_diag.bak`, copiat direct (fara compilare) — restaurare instantanee |
**Pasul urmator recomandat**: `Copy-Item omodificari.vc2.pre_diag.bak -> omodificari.vc2`,
`Copy-Item omodificari.vcx.mine_s4.bak -> omodificari.vcx` (+ `.vct`) — restaurare **instantanee**
(fara `txt2vcx`) la versiunea curata cu PAGE3 functional, fara schela de diagnostic. Verifica dupa
aceea cu un `diff` ca `omodificari.vc2` chiar nu mai contine `diag_class`.
## 2. Ce mai ramane din runda 1
Din sectiunea E / runda 1 a planului (A.2 PageCount + A.3 detectie + B.1 incarcare + B.2 marcaje):
- **A.2 (PageCount)** — FACUT, testat static (fidelity OK), **netestat live pe formular** (vezi
sectiunea 3 — blocaj de mediu, nu s-a ajuns sa vad `PageCount` pe formularul instantiat real).
- **A.3 (detectie)** — FACUT, testat **direct** (fara UI) cu rezultate PASS pe date reale (sectiunea
3). Deviaza de la recomandarea planului (cauta si de ce, sectiunea 4).
- **B.1 (incarcare cursoare)** — FACUT, testat direct, PASS.
- **B.2 (marcaje stare linii)** — **NU e in scope runda 1** (grid-ul e strict readonly, fara
editare — marcajele `_modificat`/`sters`/`id_vanzare_det=0` sunt pentru runda 2). Nu e inceput.
**Ce lipseste efectiv**: verificarea end-to-end ca, pe formularul REAL (`Createobject('frm_modific2024')`
+ `.Show()`), `PageCount` chiar ajunge 3 pe o factura si 2 pe o nota fara `VANZARI` — blocata de
problema de mediu din sectiunea 3. Codul e scris si compilat curat; ramane doar confirmarea live.
## 3. Ce am testat, cu ce rezultat
Suita: `COMUN\utile\Teste\editare_factura\test_page3_articole.prg` (headless, `vfp9.exe -A -T`,
conexiune reala `MARIUSM_AUTO`). Log: `test_page3_articole_log.txt` in acelasi folder.
**PASS, pe date reale, testat direct (apel functie, fara formular)**:
- `cod=1140888` → `IncarcaVanzareNota` gaseste `id_vanzare=1050`, `tip=1`; `IncarcaArticoleFactura`
incarca 4 linii — coincide exact cu baza de regresie din `progres.md` (`TOTAL_CU_TVA=1924.59`).
- `cod=1140885` → gaseste `id_vanzare=1047`, `tip=-12` (nu e factura, e alt tip de document) —
confirma ca detectia functioneaza pe **orice** tip din `VANZARI`, nu doar facturi (decizia 19).
- `cod=1125486` (an=2008, luna=2, notă contabila fara nicio legatura cu `VANZARI`) → 0 randuri
gasite — cazul negativ cerut de criteriul de "gata" al rundei.
- **Coliziune pe `cod=1139934`** (patru randuri active in `VANZARI`, acelasi cod): filtrul compus
(cod+nract+serie_act+data_act) gaseste EXACT randul cerut (`nract=375/SSS` → `id_vanzare=882`) si
NU gaseste nimic pentru un `nract` fara corespondent (`nract=13`) — vezi sectiunea 4, e important.
**NETESTAT live (formular real)**: `verifica_pagecount_form` (in acelasi fisier de test) instantiaza
`frm_modific2024` exact ca `do_editare_factura` (`Createobject`+`Show`) si verifica `PageCount` —
**se blocheaza intr-un dialog nativ VFP "View Parameter"** in timpul `Createobject`, inainte sa
apuce sa ruleze vreo linie din `Show()`. Vezi sectiunea 4 pentru diagnosticul facut si o pista noua,
neexplorata inca, primita de la team-lead.
**`test_baseline_isolation.prg`** (in acelasi folder) — **fisier temporar de diagnostic, se poate
sterge** dupa ce cineva confirma diagnosticul de mai jos independent; nu face parte din suita
finala (antetul lui o spune explicit). L-am folosit ca sa demonstrez ca **binarul ORIGINAL (dinainte
de S4) NU are acest blocaj** in acelasi mediu de test (`PageCount=2`, `done`, fara dialog) — deci
problema e din diff-ul meu, nu un gol preexistent de mediu.
## 4. Descoperiri care nu trebuie pierdute
### `VANZARI.COD` NU e unic — dovedit pe date, nu presupus
Contrar recomandarii A.3 din plan ("`SELECT ... FROM vanzari WHERE cod = tact.cod`"), am verificat
direct pe `MARIUSM_AUTO`:
- Indexul `IDX_VANZARI_04` e pe `(STERS, COD)` si e **NONUNIQUE** — schema insasi nu garanteaza
unicitate.
- `cod=1139934` are **4 randuri active** (`STERS=0`) in `VANZARI`, cu `TIP`/`NUMAR_ACT` diferite
(375/SSS, 24/PF, 25/PF, 26/PF). `cod=1139555` are 2 randuri cu `TIP` diferit (43 si 1).
- Filtrarea suplimentara pe `NUMAR_ACT`+`SERIE_ACT`+`DATA_ACT` (toate disponibile pe cursorul `tact`
ca `nract`/`serie_act`/`dataact`, din `vact_tot`) rezolva ambiguitatea: `(COD, NUMAR_ACT,
SERIE_ACT, DATA_ACT)` verificat **fara nicio dublura** pe toata tabela `VANZARI`.
- `ID_FACT` (alternativa mentionata in `progres.md` — "toate legaturile trec prin ID_FACT sau
ID_VANZARE, niciodata prin cod") **nu e utilizabil ca filtru unic aici**: e `NULL` pe o fractie
mare de randuri chiar si pentru facturi normale (`tip=1`: 58 din 183 `NULL`; `tip=51`: 58 din 74
`NULL`), deci n-ar gasi avizele/tipurile fara facturare clasica — ar incalca decizia 19.
**Concluzie aplicata in cod**: `IncarcaVanzareNota` filtreaza pe cele 4 coloane compuse, nu doar pe
`cod`. Aceasta e o **corectie de fond fata de A.3 din plan**, nu o improvizatie — sectiunea C.1 din
`plan_06_s4_proiectare.md` ar trebui sa mentioneze asta daca cineva o rescrie.
### Blocaj "View Parameter" la `Createobject('frm_modific2024')` — cauza NECONFIRMATA
Simptom: procesul `vfp9.exe` ramane blocat (CPU~0, Responding=True) intr-un dialog nativ **"View
Parameter"** exact in timpul liniei `Createobject([frm_modific2024], lnIdSet)`, inainte sa ajunga
la orice linie din `Show()` — confirmat cu checkpoint-uri `STRTOFILE` in `Init()`/`Load()` (niciunul
nu s-a scris, deci blocajul e chiar mai devreme decat `Load()`, sau checkpoint-urile nu s-au atins
din alt motiv neexplicat).
**Exclus cu dovezi** (nu pierde timp re-verificand):
- **Nu e cursorul `tvd` lipsa** — am incercat atat placeholder in `Load()`, cat si pre-creare in
scriptul de test inainte de `Createobject`; blocajul a persistat identic.
- **Nu e un gol de mediu preexistent** — binarul ORIGINAL (`omodificari.vcx.mine_s4.bak` restaurat
temporar) ruleaza curat in ACELASI mediu de test (`test_baseline_isolation.prg`, `PageCount=2`,
fara dialog). Deci e ceva din diff-ul meu.
- **Un blocaj anterior, diferit** (`File 'crsjtvatemp.dbf' does not exist`, dialog "Open") era
intr-adevar preexistent — cerut de `Column63` din `grdRulaje` (PAGE1, cod netusat de mine),
rezolvat in scriptul de test cu `update_jtva_coloane("", "crsJtvaTemp", 0)` inainte de
`Createobject` (nu in clasa — e o lipsa de mediu de test, nu de productie: in productie,
`crsJtvaTemp` e populat pe alte cai inainte sa ajunga un utilizator la acest formular).
- Am citit `_pageframe.Init()` (`_baza.vc2:514-523`, itereaza `For i = 1 To PageCount` si cheama
`tradu(.Caption)`) ca prim suspect — **`tradu()` (`oproceduri_comune.prg:1746`) e confirmat
inofensiv** (doar `STRTRAN` pe diacritice, fara Oracle/View). Nu e cauza, dar ramane singurul cod
DEPENDENT de `PageCount` gasit prin cautare in `_baza.vc2`/`_frm_base.vc2`/`_grd_base.vc2`.
- Am cautat "CREATE SQL VIEW" / `USE ... VIA` in toata ierarhia de clase (`omodificari`, `_baza`,
`_frm_base`, `_grd_base`, `gridextras`) — **zero rezultate**. Niciun view local gasit static.
**Pista noua, neexplorata** (primita de la team-lead, nu verificata de mine inca): dialogul "View
Parameter" apare tipic cand o variabila referita prin `?variabila` (SQL passthrough) sau intr-un
view parametrizat e declarata `LOCAL` in loc de `PRIVATE` intr-un harness de test — `LOCAL` nu e
vizibil in rutinele apelate. Exemplu citat: `do_editare_factura` declara
`Private pnAn, pnLuna, lnCod, lnIdFact, lnIdVanzare` (`ofacturare_comun.vc2:3742`). **Nu am apucat
sa verific** daca `test_page3_articole.prg` foloseste `LOCAL` unde codul original ar cere `PRIVATE`
undeva in lantul `IncarcaCursoareModificareNota`/`Createobject` — e primul lucru de incercat inainte
de a relua sapatul cu `EnumWindows`/`PrintWindow` (costisitor, ~15 cicluri de test in aceasta
sesiune doar pentru asta).
## 5. Capcane de mediu platite in aceasta sesiune
- **Sterge intotdeauna `.fxp`-ul vechi inainte de fiecare rulare de test** — m-a pacalit o data:
am editat `.prg`-ul dar am uitat sa sterg `.fxp`, iar VFP a rulat tacut codul VECHI compilat,
dand rezultate inconsistente intre rulari identice in aparenta. Simptom: log-ul arata alt
comportament decat sursa curenta ar trebui sa produca.
- **FoxBin2Prg NU pastreaza ordinea textuala la scriere-citire (roundtrip)** pentru ADD OBJECT
multiple si proprietati custom — le REGENEREAZA in ordine proprie (alfabetica pentru
coloane/obiecte fii, alta regula neclara pentru proprietati custom simple — `nidvanzare` a iesit
INAINTEA lui `nid_set`, desi `_` < `v` in ASCII). Fidelity-check-ul compara text-sursa cu
text-regenerat, deci **orice ordine "naturala" (alfabetica presupusa de mine) poate pica fidelity**.
Solutie aplicata: dupa primul fidelity FAIL, am adoptat ca sursa noua textul regenerat din
`<staging>\verify\*.vc2` (e deja in forma canonica), nu am incercat sa ghicesc ordinea corecta.
Confirma exact indicatia din `flux-editare-vfp-text.md`.
- **`InputMask` cu literal (nu `get_mask(...)`)** trebuie **ghilimeluit** (`"9.99"`), nu bar (`9.99`)
— altfel VFP il interpreteaza numeric, nu ca string de format. Prins abia la inspectia manuala a
textului regenerat, fidelity-check-ul NU l-a semnalat separat (a picat impreuna cu reordonarea).
- **Grid nou cu `RecordSource` pe un cursor care nu exista inca la constructia formularului** →
VFP incearca `USE <recordsource>` ca fisier fizic si arata dialogul nativ "Open" daca nu-l
gaseste pe disc — exact acelasi tipar deja documentat in clasa pentru `saft_taxtable`/
`saft_mecanisme_plati` (`Load()`, verificat `If !Used(...)` inainte de orice altceva). Solutia
e aceeasi: placeholder gol creat in `Load()`, INAINTE ca framework-ul sa construiasca grid-urile.
- **Query-uri Oracle multiple rulate manual (sqlplus) au fost esentiale** pentru validarea empirica
a filtrului compus pe `VANZARI` — nu s-ar fi descoperit coliziunea pe `cod` doar din cod static.
## 6. Ce recomand pentru urmatorul agent
1. Restaureaza versiunea curata (sectiunea 1, pasul recomandat) — 2 copieri de fisier, fara
compilare, verifica cu `grep diag_class` ca a disparut.
2. Incearca pista `LOCAL`/`PRIVATE` din sectiunea 4 pe `test_page3_articole.prg` inainte de orice
alta depanare GUI.
3. Daca se rezolva: reruleaza `test_page3_articole.prg` complet, confirma PASS pe toate cazurile
(inclusiv `verifica_pagecount_form`), sterge `test_baseline_isolation.prg` + `.fxp` + log-ul lui,
scrie diff-ul (diff aplicat (sters), `git diff --no-index <bak> <editat>`) si
raportul (`docs\cercetare\rec_s4_runda1.md`) cerute initial de team-lead.
4. Daca team-lead sau alt agent scrie capcana "View Parameter / LOCAL vs PRIVATE" in
`testare-ui-vfp.md`, nu o duplica — team-lead a intrebat explicit cine o scrie.

View File

@@ -1,337 +0,0 @@
Cercetare S9: reemiterea unui document cu acelasi ID_FACT (SET_IDFACT + DOCUMENTE)
====================================================================================
Verdict (rezumat)
------------------
**DA, dar cu conditii — nu e o schimbare izolata.** `SET_IDFACT` activ (`PACK_CONTAFIN.pck:3037-3040`)
ia neconditionat un `ID_FACT` nou din `SEQ_IdFact`; niciun apelant din toata suita (~25-30 copii
identice ale `oscrie_in_fisiere.prg`, cate una per produs) ii impune un `ID_FACT`. Varianta comentata
(`:3016-3035`, cauta pe `NRACT+SERIE_ACT+DATAACT+ID_CTR` cu `STERS=0`) **nu rezolva S9** ca atare:
dupa stergerea soft a documentului vechi, `STERS` e deja 1, cautarea nu-l gaseste, si cade tot pe
secventa — plus riscul de coliziune cu un document viitor neinrudit care are aceleasi 4 campuri.
Blocajul real nu e in `SET_IDFACT`, ci in scrierea din `DOCUMENTE`: acolo e un `INSERT` simplu
(`:796-817`) pe `ID_DOC`, care e **PRIMARY KEY** (`PK_DOCUMENTE`, unic, `fn_script.sql:5192-5198`).
Stergerea (`STERGE_DIN_ACT:1835-1855`) e soft-delete pur — `ACT.STERS=1`, apoi `DOCUMENTE.STERS=1`
pentru id_fact-urile gasite pe acel `COD` — randul vechi **nu se sterge fizic**. Deci reemiterea cu
acelasi `ID_FACT`, cu codul de azi neschimbat, ar arunca `ORA-00001` la insertul in `DOCUMENTE`,
pentru ca randul cu acel `ID_DOC` inca exista (doar marcat `STERS=1`). Varianta `MERGE` deja
comentata (`:818-847`) nu ajuta din prima: are doar `WHEN NOT MATCHED`, fara `WHEN MATCHED`, deci
daca randul exista deja l-ar ignora tacit — documentul reemis ar ramane cu `DOCUMENTE.STERS=1`.
Concluzie operationala: reutilizarea e posibila, dar cere trei schimbari simultane (detaliate la C.7),
nu doar reactivarea codului comentat din `SET_IDFACT`.
A. SET_IDFACT
--------------
### A.1 — Ramura activa vs. ramura comentata
Cod integral, `PACK_CONTAFIN.pck:3014-3040`:
```
------------------------------------------------------------------------------------
/* -- 25.02.2013 : am comentat pentru ca se face unirea id_fact pe listarea din JC/JV
PROCEDURE SET_IDFACT(tdDataAct ACT_TEMP.DATAACT%TYPE,
tcSerie_Act ACT_TEMP.SERIE_ACT%TYPE,
tnNrAct ACT_TEMP.NRACT%TYPE,
tnId_Ctr ACT_TEMP.ID_CTR%TYPE) IS
V_ID_FACT DOCUMENTE.ID_DOC%TYPE;
BEGIN
BEGIN
SELECT ID_DOC
into pack_contafin.nIdFact
FROM DOCUMENTE
WHERE NRACT = tnNrAct
AND NVL(SERIE_ACT, '+-') = NVL(tcSerie_Act, '+-')
AND DATAACT = tdDataAct
AND NVL(ID_CTR, 0) = NVL(tnId_Ctr, 0)
AND STERS = 0;
EXCEPTION
WHEN NO_DATA_FOUND THEN
SELECT SEQ_IdFact.NEXTVAL into pack_contafin.nIdFact FROM DUAL;
END;
END SET_IDFACT;*/
------------------------------------------------------------------------------------
PROCEDURE SET_IDFACT(V_GCS IN VARCHAR2) IS
BEGIN
SELECT SEQ_IdFact.NEXTVAL into pack_contafin.nIdFact FROM DUAL;
END SET_IDFACT;
```
Diferenta de comportament daca s-ar reactiva varianta comentata: in loc sa genereze mereu un
`ID_FACT` nou, ar cauta intai in `DOCUMENTE` un document **nesters** (`STERS=0`) cu aceeasi
combinatie `NRACT + SERIE_ACT + DATAACT + ID_CTR`, si doar daca nu gaseste nimic ar cere secventa.
Doua probleme pentru S9:
- **nu se aplica la reemitere**: documentul vechi e deja marcat `STERS=1` inainte de rescriere
(vezi B.6), asa ca filtrul `STERS=0` nu-l gaseste — cade tot pe `SEQ_IdFact.NEXTVAL`, exact ca azi;
- **risc de coliziune**: daca NRACT/SERIE_ACT/DATAACT/ID_CTR ar coincide intamplator cu alt document
nesters (nu neaparat cel pe care il reemitem), i-ar imprumuta ID_FACT-ul aceluia.
Semnatura veche are 4 parametri de identitate diferiti de semnatura activa (1 parametru `V_GCS`,
folosit doar ca "user"/context, nefolosit in corp) — deci reactivarea ar cere fie o supraincarcare,
fie schimbarea semnaturii peste tot unde e chemata (vezi A.2).
### A.2 — Apelanti
**Un singur punct de apel in PL/SQL**: `PACK_CONTAFIN.pck:721`, in bucla din `SCRIE_IN_ACT`:
```
pack_contafin.set_idfact(V_GCS);
/* 25.02.2013 : se face cumularea id_fact pe listarea din RC/RJ
PACK_CONTAFIN.SET_IDFACT(itemfact.dataact, itemfact.serie_act, itemfact.nract, itemfact.id_ctr);*/
lnIdFact := get_idFact();
```
buclat pe grupuri distincte `(NRACT, DATAACT, DATAIREG, SERIE_ACT, ID_CTR, ID_SET)` extrase din
`ACT_TEMP WHERE ID_FACT = -1` (`:713`) — deci `-1` e sentinela "acest rand cere un `ID_FACT` nou".
`SCRIE_IN_ACT` insusi e apelat **doar pe drumul de scriere**, niciodata pe cel de stergere —
in `finalizeaza_scriere_act_rul` (`:8449-8459`):
```
if tnScrieSterge <> 2 then
pack_contafin.SCRIE_IN_ACT(user);
...
else
pack_contafin.STERGE_DIN_ACT(user, lnAn, lnLuna, tnCod, tnIdUtil, tnModificareNota);
...
```
Deci `SET_IDFACT` nu se atinge deloc la stergere — confirma ca azi cele doua operatii (stergere +
reemitere) sunt independente unele de altele in privinta ID_FACT.
**Cine cheama `final_scriere_act_rul_local` / `SCRIE_IN_ACT` din VFP**: acelasi fisier
`COMUN\programe\oscrie_in_fisiere.prg`, replicat identic in ~25-30 produse ROA (verificat prin grep
pe `*.prg` in tot `D:\ROA`): `ROAFACTURARE`, `ROACONT` (output), `ROAGEST`, `ROAIMOB`, `ROAEFACTURA`,
`ROADEVIZE`, `ROAVIN`, `ROACASA`, `ROASAL`, `ROAPRETURI`, `ROAOBINV`, `ROARESTAURANT`,
`ROACOMENZI`, `ROAAPROV`, `ROASITOP`, `ROASITFIN`, `ROADECL`, `ROASALSPEC`, `ROABAVERT`,
`ROACONIMPORT`, `ROAPRINT`, s.a. — plus doua variante care apeleaza `SCRIE_IN_ACT` direct, fara
wrapper-ul local: `CONTAFIN2ORA\VFP2ORA\Programe\oscrie_in_fisiere.prg:367`,
`ROADEFSALARII\COMUN\programe\oscrie_in_fisiere.prg:141`,
`ROARESTAURANTCONFIG\COMUN_ROA\programe\oscrie_in_fisiere.prg:135`.
Un al doilea import vechi (`COMUN\datemenu\xold\import_xdbf\oscrie_in_fisiere.prg`) apare in multe
produse dar e **comentat integral** (`*!*`) — inactiv.
**Niciun apelant nu impune un `ID_FACT`.** Toti trimit `ID_FACT = -1` in `ACT_TEMP` (via
`sql_temp_insert` in `oscrie_in_fisiere.prg:126-137`, care copiaza direct campurile din cursorul VFP,
inclusiv `id_fact`) si lasa `SCRIE_IN_ACT` sa-l completeze din secventa. Confirmare suplimentara in
`COMUN\clase\omodificari.vc2:4131-4132`: `"daca se schimba partenerul, se pune id_fact = -1 in loc
de 0 pentru a se putea genera un nou id_fact"` — exact sentinela pe care se bazeaza bucla din
`SCRIE_IN_ACT`.
### A.3 — Variabila/parametru existent pentru un ID_FACT dorit
**Nu exista azi.** `PACK_CONTAFIN` are o variabila de pachet `nIdFact` (setata de `SET_IDFACT`,
citita de `GET_IDFACT`), dar e scrisa neconditionat de fiecare apel al lui `SET_IDFACT` — nu poate
fi folosita ca "intrare" fara sa se schimbe corpul procedurii. Nu exista niciun `nid_...` global, nici
alt parametru de sesiune care sa transmita un `ID_FACT` dorit lui `SET_IDFACT` sau lui `SCRIE_IN_ACT`.
Ar trebui adaugata o variabila noua de pachet (ex. un "ID_FACT fortat", implicit `NULL`), citita
**doar** in corpul activ al lui `SET_IDFACT`, consumata si resetata la prima folosire — vezi C.8.
Acelasi diagnostic e deja notat in planul de proiect, `plan_13_unificare_formular_facturare.md:1717-1719`:
`"ID_FACT. Se citeste inainte de stergere ... si se impune documentului nou, printr-un comutator
folosit numai de regenerare. SET_IDFACT nu se schimba neconditionat — e cod comun intregii suite."`
B. DOCUMENTE la reemitere cu acelasi ID_FACT
----------------------------------------------
### B.4 — INSERT sau UPDATE/MERGE azi
`PACK_CONTAFIN.pck:788-817`, in interiorul buclei din `SCRIE_IN_ACT`, cod activ (INSERT simplu):
```
-- SCRIE IN DOCUMENTE
lnTvaIncasare := case when itemfact.tva_incasare > 0 then 1 else 0 end;
-- 25.02.2013 : am repus insertul pentru ca se face cumularea id_fact pe listarea din RC/RJ
INSERT INTO DOCUMENTE
(ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT, DATAACT, DATAIREG, ID_CTR, ID_SET)
VALUES
(lnIdFact, lnID_Util, LD_DATAORA, lnTvaIncasare, itemfact.serie_act, itemfact.nract,
itemfact.dataact, itemfact.dataireg, decode(itemfact.id_ctr, 0, NULL, itemfact.id_ctr),
itemfact.id_set);
/*
-- modificare ROACONT v 2.4.0 (27.12.2012): am adaugat serie_act, nract, dataact
-- modificare 21.02.2013: am adaugat id_ctr
MERGE INTO DOCUMENTE A
USING (SELECT lnIdFact as ID_DOC, itemfact.serie_act as SERIE_ACT, itemfact.nract as NRACT,
itemfact.dataact as DATAACT,
decode(itemfact.id_ctr, 0, NULL, itemfact.id_ctr) AS ID_CTR
FROM DUAL) B
ON (A.ID_DOC = B.ID_DOC)
WHEN NOT MATCHED THEN
INSERT (ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT, DATAACT, ID_CTR)
VALUES (B.ID_DOC, lnID_Util, LD_DATAORA, lnTvaIncasare, B.SERIE_ACT, B.NRACT, B.DATAACT, B.ID_CTR);*/
```
Deci **azi e mereu un INSERT nou** — pe drumul normal (ID_FACT vine din secventa, mereu inexistent
in `DOCUMENTE`) asta e corect, dar pentru cazul S9 (ID_FACT reutilizat, deja existent — chiar si
soft-sters) INSERT-ul ar da eroare de unicitate (vezi B.5). Varianta `MERGE` comentata are doar
`WHEN NOT MATCHED` — daca s-ar reactiva neschimbata, un `ID_DOC` deja existent ar fi pur si simplu
ignorat (fara `WHEN MATCHED`), documentul reemis ramanand cu randul vechi din `DOCUMENTE`
(cu `TVA_INCASARE`/`ID_SET` nemodificate si, mai grav, cu `STERS` neresetat — vezi B.6).
### B.5 — Constrangere de unicitate pe DOCUMENTE
DDL gasit in `D:\ROA\DATABASE\ALTELE\Creare_server_scripturi\FirmaNoua\fn_script.sql:5162-5198`:
```
CREATE TABLE "DOCUMENTE" ("ID_DOC" NUMBER(20, 0) NOT NULL ENABLE, "DATAORA" DATE NOT NULL ENABLE,
"ID_UTIL" NUMBER(5, 0) NOT NULL ENABLE, "STERS" NUMBER(1, 0) NOT NULL ENABLE,
"DATAORAS" DATE, "ID_UTILS" NUMBER(5, 0) NOT NULL ENABLE) ...
...
CREATE UNIQUE INDEX "PK_DOCUMENTE" ON "DOCUMENTE" ("ID_DOC") ...
ALTER TABLE "DOCUMENTE" ADD CONSTRAINT "PK_DOCUMENTE" PRIMARY KEY ("ID_DOC") USING INDEX ... ENABLE
```
**Da: `ID_DOC` e PRIMARY KEY, unic.** (Coloanele `SERIE_ACT/NRACT/DATAACT/DATAIREG/ID_CTR/ID_SET/
TVA_INCASARE` din INSERT-ul de la B.4 nu apar in acest script — script vechi de firma noua,
probabil tabela a fost alterata ulterior cu `ALTER TABLE ADD COLUMN`; constrangerea de PK insa e
stabila si nu are motiv sa fi fost scoasa.) Un `INSERT INTO DOCUMENTE (ID_DOC=...)` cu o valoare de
`ID_DOC` care exista deja (chiar si `STERS=1`) arunca `ORA-00001: unique constraint (PK_DOCUMENTE)
violated`. Nu am gasit alta constrangere de unicitate pe `DOCUMENTE`; nici FK de la `ACT.ID_FACT`
catre `DOCUMENTE.ID_DOC` (`ACT.ID_FACT` e doar indexat, `IDX_ID_FACT`, `fn_script.sql:1637`, fara
constrangere de integritate referentiala gasita) — deci reinserarea de randuri in `ACT` cu
`ID_FACT` reutilizat **nu** e blocata de vreo constrangere pe `ACT` insusi; blocajul e exclusiv pe
`DOCUMENTE`.
### B.6 — Randurile vechi la stergere: fizic sterse, STERS=1, sau raman
**Soft-delete pur, pe toate cele patru tabele relevante — nimic nu se sterge fizic.**
`STERGE_DIN_ACT` (`PACK_CONTAFIN.pck:1815-1859`), apelata din `finalizeaza_scriere_act_rul` cand
`tnScrieSterge = 2`:
```
PROCEDURE STERGE_DIN_ACT(V_GCS VARCHAR2, tnAn in number, tnLuna in number, tnCod in number,
tnId_utils in number, tnTip IN NUMBER) is
-- tnTip : 0 = modificare ; 1 = stergere
...
BEGIN
...
UPDATE /*+ index(ACT IDX_COD) */ ACT
SET STERS = 1, DATAORAS = LD_DATAORA, ID_UTILS = lnId_util
WHERE COD = tnCod and an = tnAn and luna = tnLuna;
IF lnTip = 1 THEN
UPDATE DOCUMENTE
SET STERS = 1, ID_UTILS = LNID_UTIL, DATAORAS = LD_DATAORA
WHERE ID_DOC IN
(SELECT /*+ index(ACT IDX_COD) */ DISTINCT ID_FACT
FROM ACT
WHERE COD = tnCod and an = tnAn and luna = tnLuna
AND ID_FACT <> 0
and ID_SET not in (90501, 90021)
AND NOT (SCD = '4426' AND SCC = '4428')
AND NOT (SCD = '4428' AND SCC = '4427'));
END IF;
update act_temp set suma = -suma, suma_val = -suma_val;
END STERGE_DIN_ACT;
```
`ACT` -> `STERS=1` (randurile raman fizic, pe acelasi `COD`). Cand `tnTip=1` (stergere, nu simpla
modificare), **`DOCUMENTE` primeste si el `STERS=1`**, pentru toate `ID_FACT` distincte gasite pe
acel `COD` in `ACT` (cu exceptiile de mai sus pentru seturi/conturi speciale). Nicaieri nu am gasit
un `UPDATE DOCUMENTE ... SET STERS = 0` — cautare exhaustiva in `PACK_CONTAFIN.pck` (`grep -i
"UPDATE DOCUMENTE"`) a dat un singur rezultat, cel de mai sus. Deci nu exista azi niciun mecanism
care sa "reinvie" un rand din `DOCUMENTE`.
La fel, `STERGE_DIN_RUL` / `STERGE_DIN_RUL_OBINV` (`:1861-1893`) fac `UPDATE ... SET STERS = 1` pe
`RUL`/`RUL_OBINV`, fara stergere fizica.
Pentru `IREG_PARTENERI` si `JV2007` situatia e diferita in mod util pentru S9: acestea nu sunt copii
1:1 ale documentului, ci **agregate recalculate prin `MERGE`** pe chei de business (an, luna, cont /
`ID_FDOC`, `ID_FACT`, `NRACT`, `SERIE_ACT`, `DATAACT`, `DATAIREG`, `ID_PART`, ...) — vezi
`SCRIE_JV_2007` (`:3328-3373+`) si `SCRIE_IN_IREG_PARTENERI`/`EXECUTA_SCRIE_IN_IREG` (`:7030+`,
`:7607+`). Ambele sunt apelate **si pe drumul de scriere, si pe cel de stergere**
(`finalizeaza_scriere_act_rul:8495-8578`, fara conditie pe `tnScrieSterge` in afara sursei: `act_temp`
la scriere/stergere, `act` la refacere). La stergere, `sterge_document` (`:7699-8118`) reinsereaza in
`ACT_TEMP` copii ale randurilor vechi din `ACT`/`RUL`/`RUL_OBINV`, **cu acelasi `ID_FACT` ca inainte**
(coloana `id_fact` e copiata direct din sursa, nu resetata la `-1`), asa ca `MERGE`-ul din
`SCRIE_JV_2007`/`SCRIE_IN_IREG_PARTENERI` recalculeaza (compenseaza) exact randul cu acel `ID_FACT`
— nu creeaza duplicate. Deci reutilizarea `ID_FACT`-ului **nu produce dubluri in `JV2007`/
`IREG_PARTENERI`**; problema e strict izolata la `DOCUMENTE`.
Nota din plan, care confirma independent aceasta zona de risc:
`plan_13_unificare_formular_facturare.md:1737`: `"De verificat si daca DOCUMENTE primeste un al
doilea rand pe acelasi ID_DOC sau il refoloseste."` — raspunsul, dupa aceasta cercetare: **niciuna
din cele doua, in starea actuala a codului — ar arunca eroare de unicitate**, nu ar produce liniste
tacuta cu duplicat sau refolosire.
C. Verdictul care conteaza
----------------------------
### C.7 — Se poate reemite cu acelasi ID_FACT fara sa se strice nimic?
**DA, dar cu conditii** — niciuna dintre ele nu e implementata azi:
1. **Nu folosi varianta comentata a lui `SET_IDFACT` ca atare.** Cautarea `STERS=0` nu gaseste
documentul (deja sters soft inainte de rescriere) si e vulnerabila la coliziuni pe chei de
business partajate cu alt document. In loc de cautare, ID_FACT-ul trebuie **citit explicit
inainte de stergere** (VFP il are deja in cursorul documentului editat) si **transmis** pe drumul
de scriere — exact ce zice planul S9.
2. **`SET_IDFACT` are nevoie de o cale de intrare noua, inerta implicit.** O variabila de pachet
noua (ex. "ID_FACT fortat", `NULL` default) + o procedura noua de setat-o explicit inainte de
scriere; corpul activ al `SET_IDFACT` verifica intai variabila, o consuma si o reseteaza; daca e
`NULL` (cazul general — toate celelalte ~25-30 apeluri din suita), comportamentul e identic cu
azi (secventa). Nu se toarna in semnatura existenta `SET_IDFACT(V_GCS)`, ca sa nu se schimbe
apelul de la niciun alt caller.
3. **Scrierea in `DOCUMENTE` trebuie sa devina un upsert real, cu `WHEN MATCHED`.** Nici INSERT-ul
activ, nici MERGE-ul comentat (fara `WHEN MATCHED`) nu revigoreaza un rand soft-sters. E nevoie de
un `MERGE ... WHEN MATCHED THEN UPDATE SET STERS = 0, DATAORA = ..., ID_UTIL = ..., TVA_INCASARE
= ..., SERIE_ACT = ..., NRACT = ..., DATAACT = ..., DATAIREG = ..., ID_CTR = ..., ID_SET = ...`
pe langa `WHEN NOT MATCHED THEN INSERT` (cazul normal, ID_FACT nou din secventa).
4. **Succesiunea corecta**, in aceeasi tranzactie (deja planificata in S9 la nivel de VFP — vezi
`plan_13...md:1713-1719`, mutarea stergerii in tranzactia deschisa de scriere): sterge documentul
vechi (soft-delete, ca azi) -> seteaza variabila de la punctul 2 cu `ID_FACT`-ul citit inainte de
stergere -> scrie documentul nou prin drumul obisnuit (`ACT_TEMP` cu `ID_FACT = -1` ca de obicei,
dar acum grupul respectiv va primi ID_FACT-ul fortat in loc de unul nou) -> commit doar dupa ce
ambele operatii au avut succes; orice eroare -> rollback total, documentul vechi ramane intact.
- **NU** (fara aceste conditii): `INSERT INTO DOCUMENTE` cu `ID_DOC` reutilizat, cat timp randul vechi
e inca prezent cu `STERS=1`, arunca `ORA-00001` pe `PK_DOCUMENTE` — asta e dovada, nu presupunere
(B.5 + B.4).
### C.8 — Suprafata de risc pentru restul suitei
- **~25-30 cai de apel** trebuie sa ramana neafectate: toate copiile `COMUN\programe\
oscrie_in_fisiere.prg` din fiecare produs ROA (listate in A.2), plus cele 3 variante care apeleaza
`SCRIE_IN_ACT` direct. Toate trec prin acelasi punct final: `SET_IDFACT(V_GCS)` in
`PACK_CONTAFIN.pck:3037`.
- **Garantia structurala propusa**: variabila de pachet noua, default `NULL`/inert, citita **doar**
in interiorul corpului activ al lui `SET_IDFACT` (nu in semnatura, nu in vreun parametru propagat
de apelanti); se seteaza explicit **doar** de codul de regenerare din ROAFACTURARE, chiar inainte
de a incepe scrierea documentului reemis, si se consuma/reseteaza la prima citire (fie in
`SET_IDFACT`, fie la finalul tranzactiei, ca sa nu "scurgi" valoarea catre urmatoarea scriere
neinrudita din aceeasi sesiune Oracle — relevant daca `goExecutor`/conexiunea e reutilizata intre
operatii ale aceluiasi utilizator in acelasi produs). Cu default inert si citire localizata strict
in corpul lui `SET_IDFACT`, toate celelalte ~25-30 cai raman byte-for-byte identice cu azi — nu e
nevoie sa se verifice fiecare apelant individual, garantia e la sursa (variabila neinitializata =
comportament vechi).
- **Riscul real nu e in `SET_IDFACT`, ci in `DOCUMENTE`.** Schimbarea INSERT->MERGE cu `WHEN MATCHED`
la `PACK_CONTAFIN.pck:796-817` e riscanta pentru toata suita in alt sens: `WHEN MATCHED` s-ar
declansa doar cand `ID_DOC` (generat din secventa) coincide cu unul existent — ceea ce, pe drumul
normal, nu se intampla niciodata (secventa e monoton crescatoare, nu se repeta), deci practic
ramura noua e inerta pentru toti apelantii actuali si activa doar cand variabila de la C.8 a fost
setata explicit. Totusi orice modificare la acest INSERT/MERGE e in cod comun apelat de toata
suita si trebuie testata pe cel putin un ciclu normal de scriere (fara variabila fortata) in
fiecare produs care scrie facturi/note, nu doar in ROAFACTURARE.
Ramas de verificat pe baza vie
--------------------------------
- Structura curenta reala a tabelei `DOCUMENTE` (DDL-ul citat e dintr-un script vechi de "firma
noua"; coloanele `SERIE_ACT/NRACT/DATAACT/DATAIREG/ID_CTR/ID_SET/TVA_INCASARE` folosite de
INSERT-ul activ nu apar in acel DDL — probabil adaugate ulterior prin `ALTER TABLE`). De rulat pe
baza vie: `SELECT column_name, nullable FROM user_tab_columns WHERE table_name = 'DOCUMENTE'
ORDER BY column_id;` si `SELECT constraint_name, constraint_type FROM user_constraints WHERE
table_name = 'DOCUMENTE';` — sa se confirme ca `PK_DOCUMENTE` e inca activa si ca nu exista alta
constrangere aparuta intre timp.
- Daca exista deja documente reale in productie unde `DOCUMENTE.ID_DOC` a fost, intr-un fel sau
altul, reutilizat (ex. printr-o interventie manuala) — de verificat cu
`SELECT id_doc, count(*) FROM documente GROUP BY id_doc HAVING count(*) > 1;` (ar trebui sa fie
gol, dat fiind PK-ul, dar merita confirmat inaintea oricarei schimbari).
- Comportamentul exact al `finalizeaza_stergere_nota`/`finalizeaza_modificare_nota` (`:8601+`,
nu au fost citate integral aici) fata de `ID_FACT`/`ID_FACTD` — relevante daca reemiterea schimba
seria/numarul, nu doar continutul (cazul S9 "de baza" e cel mai simplu: acelasi NRACT/SERIE_ACT).
- Comportamentul lui `EXECUTA_SCRIE_TVA`/`SCRIE_JC_2007` (mentionate la `:8504-8540`, cazul an <
2007 sau JC) fata de reutilizarea ID_FACT — nu au fost verificate in detaliu, doar `SCRIE_JV_2007`.
- Nu am putut testa efectiv (fara acces la baza vie) daca `ORA-00001` e chiar eroarea ridicata de
Oracle in acest scenariu exact — e o deductie directa din DDL (PK unic + INSERT simplu pe coloana
respectiva), dar merita o rulare de proba pe o baza de test inainte de a proiecta solutia finala.

View File

@@ -1,553 +0,0 @@
# De unde vine SCC-ul pe facturile din comandă/contract — și dacă modelul se poate refolosi pentru #13
Cercetare read-only, pe cod (VFP text + export `PACK_FACTURARE` curent) și pe baza vie (`MARIUSM_AUTO`
pe `ROA_CENTRAL`, doar `SELECT`, 10.08.2026). Nu modifică nimic — nici `pack_facturare`, nici
`ofacturare_comun.vc2`, nici `ofacturare_editare.prg`.
Continuă `docs\cercetare\coresp_cont_venchelt.md` (secțiunea 9) și `docs\plan_13_unificare_formular_facturare.md`
(J-quater), care stabiliseră deja: FACT-024 verifică apartenența articolului la politica **de pe linie**,
`id_pol` e parametru per-linie trimis de VFP (`V_ID_POL`, `adauga_articol_factura`), și pe contract
articolul „liber" nu e de fapt fără politică — vine deja cu una atașată prin `cursor_contract`/`cursor_preturi`.
**Runda 2 (reformulare Marius):** prima trecere de mai jos (secțiunile D–C, păstrate ca dovadă) presupunea
că întrebarea era „de unde vine `id_pol`". Marius a corectat premisa: el chiar emite facturi pe bază de
document care **nu au** politică de preț și notă atașată **prin lanțul obișnuit** — ceea ce contrazicea
concluzia inițială („`id_pol` gol → `FACT-024`, mereu"). Secțiunea 0 de mai jos reconciliază contradicția,
pe cod și pe date reale, și e livrabilul principal al acestei runde. Sursa SQL folosită în ambele runde:
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (823.337 octeți — fișierul
cu același nume din `docs\` a fost șters în timpul sesiunii anterioare; numerele de linie de mai jos sunt
verificate direct pe fișierul din `SCRIPTURI_CLAR`, nu preluate din plan).
---
## 0. Reconcilierea contradicției — Marius are dreptate, dar mecanismul nu e cel presupus în runda 1
**Verdict, cu citat**: `id_pol` `NULL` **tot** blochează `contabilizeaza_articol` necondiționat — asta nu
s-a schimbat, e reconfirmat mai jos. Ce s-a stabilit greșit în runda 1 e presupunerea că **orice** linie de
document trece prin `contabilizeaza_articol`. **Nu e adevărat**: pachetul are cel puțin **două rute
paralele**, folosite azi în producție (confirmate pe date reale), care scriu o notă contabilă de vânzare
**fără `id_pol` și fără `CRM_POLITICI_PRETURI`** — pentru că nu apelează deloc `contabilizeaza_articol`.
### 0a. Reconfirmare: `contabilizeaza_articol` nu tolerează `id_pol NULL`, în nicio ramură
```sql
-- ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7275-7302 (inceputul functiei, necondiționat)
BEGIN
BEGIN
SELECT COMPUS, ID_POL_ART
INTO V_COMPUS, V_ID_POL_ART
FROM VCRM_POLITICI_PRET_ART
WHERE ID_ARTICOL = detalii_articol.id_articol
AND ID_POL = detalii_articol.id_pol; -- id_pol NULL => X=NULL => NO_DATA_FOUND, mereu
EXCEPTION
WHEN NO_DATA_FOUND THEN
... SELECT NUME_LISTA_PRETURI INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol;
RAISE_APPLICATION_ERROR(-20000, '... (FACT-024)');
```
Nu există niciun `IF detalii_articol.id_pol IS NULL THEN ...` înainte de acest bloc — e primul lucru pe
care funcția îl face, necondiționat, pentru orice apel. **Bonus, deja semnalat în runda 7** (`plan_13...
md:1451-1457`, reconfirmat aici pe fișierul curent): al doilea `SELECT` din `EXCEPTION` (cel care aduce
`lcPolitica` pentru mesaj) filtrează tot pe `id_pol = detalii_articol.id_pol`, deci și el dă `NO_DATA_FOUND`
când `id_pol` e `NULL` — utilizatorul nu vede mesajul formatat „FACT-024", ci un `ORA-01403` brut,
necaptat. În ambele cazuri: **eroare, tranzacție întreruptă, nimic scris**. Deci dacă o linie chiar ajunge
la `contabilizeaza_articol` cu `id_pol` gol, nu există azi nicio cale silențioasă de succes.
### 0b. Ce am găsit real, pe date vii — 384 din 1113 linii `VANZARI_DETALII` active (34%) au `ID_POL` `NULL`
```sql
select count(*), sum(case when id_pol is null then 1 else 0 end) from vanzari_detalii where sters=0;
-- 1113 384
```
Deci liniile facturate fără `id_pol` **există masiv** în producție — nu e un caz exotic. Împărțite pe
`VANZARI.TIP`:
| `TIP` | linii fără `id_pol` | din ele, rânduri `ACT` cu `SCC` populat | interpretare |
|---|---|---|---|
| `-12` | 97 | 588/604 | **ROAAUTO** (confirmat, `tip=-12` e explicit ROAAUTO — `plan_13...md:1710,1735`) |
| `-1..-13` (restul) | ~140 | majoritar populat | variante storno/aviz ale documentelor de mai jos, nu cercetate individual |
| `2` (**contract**) | 19 | populat, vezi 0c | facturare pe bază de contract, **rată/scadențar**, nu articol |
| `1` (listă prețuri) | 1 | — | un singur caz, neexplorat separat |
| `51` | 121 | 179/275 (cumulat) | tip necunoscut în codul VFP citit — posibil retail/POS, neidentificat |
| **`3` (comandă, ROAFACTURARE)** | **0 din 66** | — | **niciun caz** — vezi 0d |
`select count(*), sum(case when id_pol is null then 1 else 0 end) from vanzari_detalii d join vanzari v on v.id_vanzare=d.id_vanzare where d.sters=0 and v.sters=0 and v.id_comanda is not null` → **66 / 0**. Pe ruta
`COMENZI` a lui ROAFACTURARE (secțiunea A de mai jos), **`id_pol` nu e niciodată gol, pe datele disponibile.**
Contradicția lui Marius nu se reproduce pe acest obiect precis.
### 0c. Mecanismul găsit: `contabilizeaza_rata` — notă contabilă direct din contract, fără politică, fără `id_pol`
Contractele cu facturare pe **rate/scadențar** (`CONTRACTE.OPT_FACTURARE IN (1,2)`) nu trec liniile prin
`contabilizeaza_articol` — trec prin o funcție **separată**, `contabilizeaza_rata`:
```sql
-- ff_...:7549-7597
FUNCTION contabilizeaza_rata(detalii_rata VANZARI_DETALII_TEMP%ROWTYPE) RETURN NUMBER IS
BEGIN
BEGIN
SELECT NVL(B.ID_VENCHELT, pack_facturare.nid_venchelt), ..., C.SCD, C.ASCD, C.SCC, C.ASCC, C.CU_TVA, C.IN_VALUTA
INTO ...
FROM CONTRACTE A
LEFT JOIN CRM_NOTE_VANZARI B ON A.ID_NOTA = B.ID_NOTA -- <- nota vine din CONTRACTE.ID_NOTA, NU din id_pol
LEFT JOIN NOTE_CONTABILE C ON B.ID_SET = C.ID_SET
WHERE A.STERS = 0 AND A.ID_CTR = detalii_rata.id_ctr AND B.STERS = 0;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20000, 'Nu sunt configurate notele de vanzare pentru acest contract!');
END;
...
V_INCASAT_CALCUL := V_INCASAT_CALCUL + pack_facturare.scrie_nota(..., V_SCD, V_ASCD, V_SCC, V_ASCC, ...);
```
`CONTRACTE` are coloană proprie `ID_NOTA` (confirmat pe schema vie: `NUMBER`, nullable) — **contractul însuși
duce nota contabilă**, ales o singură dată la configurarea lui, independent de orice `id_pol`/politică de
preț per articol. Confirmă exact tiparul din `cursor_contract` (secțiunea B mai jos, ramura „rată" a
cursorului, `ff_...:2837-2893`): `NULL as id_articol, ..., NULL AS ID_POL` — liniile de rată **nu au
niciodată `id_pol`, prin construcție**, pentru că nu sunt articole din nomenclator, sunt rate de scadențar.
**Confirmat pe o factură reală** (`MARIUSM_AUTO`, 10.08.2026):
```sql
-- id_vanzare 360, tip 2 (contract), linia din VANZARI_DETALII: id_articol NULL, id_pol NULL
-- ACT pentru id_fact=5039903:
-- ID_ACT SCD ASCD SCC ASCC SUMA EXPLICATIA
-- 102204 4111 11 704 300 RATA 1
-- 102205 4111 11 4427 57 TVA RATA 1
```
O factură reală, cu o linie fără `id_articol` și fără `id_pol`, **cu notă contabilă scrisă corect** (`SCD
4111`, `SCC 704`) — exact dovada cerută. Mecanismul: **nota nu vine prin politică deloc**, vine direct din
`CONTRACTE.ID_NOTA`, o singură dată per contract, la configurare — un tipar „un document duce o notă fixă",
nu „fiecare linie duce o politică de preț".
### 0d. De ce nu se reproduce pe `COMENZI` (ROAFACTURARE): tabelul nu are echivalentul lui `ID_NOTA`
```sql
select column_name from user_tab_columns where table_name='COMENZI';
-- ID_COMANDA, NR_COMANDA, DATA_COMANDA, ID_PART, ID_GESTIUNE, ID_SECTIE, ID_SECTIE2, ID_CTR, ... (29 coloane)
-- nicio ID_NOTA, nicio ID_POL, nicio coloana de contabilizare
```
`COMENZI` (antetul) nu duce nicio informație contabilă — singura legătură posibilă e `ID_CTR` (dacă
comanda vine dintr-un contract). `COMENZI_ELEMENTE.ID_POL` rămâne singurul canal, și e `NOT NULL` (secțiunea
A). Deci pe **acest** obiect, mecanismul „notă fără politică" descris de Marius **nu există** — dacă
experiența lui vine de aici, ar însemna fie (a) o versiune de bază/schemă diferită de `MARIUSM_AUTO`, fie
(b) o factură pe care a facturat-o efectiv prin ruta **contract cu rate** (secțiunea 0c) sau prin **ROAAUTO**
(`tip -12`, deviz — tipar deja documentat în `coresp_cont_venchelt.md` §9b: `adauga_articol_factura_deviz`
→ `scrie_in_vanzari`, fără `contabilizeaza_articol`, cu notă scrisă separat de fiecare produs), și a numit-o
generic „factură pe bază de comandă". **Nu pot decide între aceste ipoteze fără să întreb** — dovada de cod
și de date arată clar CARE mecanisme există și niciunul nu e pe `COMENZI` propriu-zis.
### 0e. Variantele din brief, verdict pe fiecare
- *„liniile de comandă primesc totuși un `id_pol` de undeva"* — **confirmat, dar nu ascuns**: da, primesc,
documentat deja în secțiunea A/D veche, e vizibil în UI (`v_articole`), nu explică „fără politică".
- *„`contabilizeaza_articol` nu e apelată pe ruta comandă, ci altă procedură"* — **fals pentru `COMENZI`**
(`ofacturare.prg:292-293` → `cursor_comanda` → `crsarticole` → `do_scrie_articole` → `adauga_articol_factura`
→ `contabilizeaza_articol`, ruta normală); **adevărat pentru contract-cu-rate** (`contabilizeaza_rata`) și
pentru ROAAUTO/ROAACNPRO (proceduri proprii, deja documentate).
- *„există o ramură de fallback în `contabilizeaza_articol` care ajunge la notă fără politică"* — **fals**,
reconfirmat la 0a; funcția nu are asemenea ramură, blochează necondiționat.
- *„FACT-024 se ridică doar când `id_pol` e nenul dar articolul nu e membru, `id_pol` nul merge pe alt
drum"* — **fals ca „alt drum în aceeași funcție"**; **adevărat ca „alt drum = altă funcție"** (0c/0d).
---
## D. Răspunsul la întrebarea lui Marius, în cinci rânduri (actualizat după reconciliere)
**Depinde care „comandă".** Pe obiectul `COMENZI`/`COMENZI_ELEMENTE` al ROAFACTURARE (facturare „pe bază
de comandă", tip 3), SCC-ul vine tot din `NOTE_CONTABILE.SCC` prin `id_pol` — **`id_pol` nu lipsește
niciodată**, e coloană `NOT NULL` (confirmat pe schemă și pe date: 0 din 6868/66 linii fără el), populată la
adăugarea articolului dintr-o listă deja restrânsă la o singură politică (`v_articole`/`com_vpreturi_utilizator`),
politică aleasă o dată pe comandă. **Dacă experiența lui Marius vine din facturarea contractelor cu rate
(scadențar, `OPT_FACTURARE IN (1,2)`) sau din ROAAUTO**, atunci da, există o rută reală, azi în producție,
care scrie nota **fără nicio politică de preț**: pe contracte-cu-rate, nota vine direct din
`CONTRACTE.ID_NOTA` (o coloană pe contract, aleasă o singură dată la configurarea lui, independentă de orice
`id_pol` per linie) — `contabilizeaza_rata`, nu `contabilizeaza_articol`. Pe ROAAUTO/ROAACNPRO, nota o scrie
o procedură proprie a produsului (`pack_acn.salveaza_regdoc` etc.), tot în afara lanțului `CRM_POLITICI_PRETURI`.
**Aceste rute nu sunt „`id_pol` gol tratat cu grijă" — sunt căi care nu ating deloc `id_pol`/`contabilizeaza_articol`.**
Pe contract-cu-**articole** (nu rate), tiparul rămâne cel din runda 1: `CTR_ARTICOLE.ID_POL_ART` → membru
al unei politici reale, ca și pe comandă.
## E. Se poate refolosi „același model" pentru articolul ad-hoc din #13?
**Parțial. Mecanismul de jos (RPC + „apartenența e garantată prin construcția listei") e direct
transplantabil — dar e deja exact ce propune rețeta în 4 pași din J-quater, nu o alternativă mai simplă
la ea. Partea care ar simplifica cu adevărat lucrurile — „ia pur și simplu politica curentă și adaugă
articolul în ea" — e decizia 24, deja retrasă de Marius, din motive care rămân valabile.**
Detaliat:
1. **Ce arată comandă/contract, tehnic**: operatorul nu alege niciodată `id_pol` pentru un articol anume —
alege **o politică** (una dintre cele la care are drept, via `UTILIZATORI_ROL_INTERN` →
`POLITICI_GRUPURI` → `CRM_POLITICI_PRETURI`), iar din acel moment orice articol afișat spre alegere e
deja membru al ei (`com_vpreturi_utilizator`/`cPol_pret_art` filtrează `id_pol_art`/`id_pol IS NOT NULL`
la sursă). Verificarea FACT-024 „trece" pe comandă/contract **nu pentru că ar exista un fallback** — ci
pentru că nu există cale, în UI, de a ajunge la un articol care nu e deja membru. E o garanție de
construcție a listei, nu o validare separată.
2. **Acest tipar exact e deja documentat, ca fezabil, în `coresp_cont_venchelt.md` secțiunea „e)" / J-quater
punctul 3** — RPC `pack_preturi.adauga_politica_pret_art` (deja folosit în producție,
`ofacturare.vc2:15551-15587`) pentru „asigură apartenența", plus trimiterea lui `id_pol` neschimbat la
`adauga_articol_factura`. Nu e nimic nou de inventat aici — comanda/contractul confirmă că tiparul
„RPC + listă pre-filtrată" funcționează în producție de multă vreme, dar **planul A din J-quater deja îl
folosește**. Refolosirea nu elimină pasul, îl confirmă.
3. **Ce NU rezolvă modelul comandă/contract e alegerea SCC-ului corect per articol** — pe comandă/contract
politica e **una singură, fixă**, aleasă pentru tot documentul/sesiunea, cu un singur `SCC` (orice ar
fi el) pentru toate articolele adăugate așa. Aplicat identic pe `caut_articol` (restricționează
rezultatele căutării din nomenclator la politica curentă a operatorului), rezultatul ar fi
**exact ceea ce există deja azi ca „Cauta in lista de preturi…"** — nu rezolvă cazul pe care S4g/#13 îl
cere explicit: un articol care **nu e membru al niciunei politici** (`plan_13...md:1711-1716`: „din
nomenclator se oferă și articole gestionabile, și negestionabile"; „un articol fără `ID_POL` nu ajunge
la cont gol, ci la eroare").
4. **„Alege o politică fixă și pune articolul în ea" e literalmente decizia 24, deja retrasă**
(`plan_13...md:926-930`): *„Decizia 24: articolul ales din nomenclator primește `id_pol`-ul unei
politici de pret implicite. RETRASĂ în runda 8... prețul lui era o întrebare de configurare cu
contabilul (care politică, cu ce `SCC`, una sau mai multe) și un cont de venit uniform pentru orice
articol adăugat așa. Marius a ales în loc derivarea contului. Nu se reintroduce."* Motivul retragerii
nu s-a schimbat: o politică unică (fie ea „a operatorului", ca pe comandă) dă un **singur** cont de
venit oricărui articol, indiferent de natura lui (marfă/producție/serviciu) — exact opusul deciziei 27,
care cere `SCC` diferențiat după clasa contului de gestiune al articolului (`707`/`711`/`702`/`703`/
`7015`/`7018`/`704`).
5. **Deci ce rămâne de făcut e exact pasul central din rețeta în 4 pași**: nu „cum trimit un `id_pol`
valid la Oracle fără să ating `pack_facturare`" (asta e deja rezolvat, și demonstrat funcțional de
comandă/contract) — ci „**care** politică, cu **care** `SCC`, pentru **acest** articol anume" — acolo
comanda/contractul nu ajută, pentru că ele nu rezolvă niciodată această întrebare: politica le vine
gata aleasă, de operator, o singură dată, nu calculată per articol.
**Verdict la „mă gândeam la același model" (dacă modelul e comandă/contract-cu-articole)**: da, la nivel de
mecanism (RPC de inserare + membership garantat prin listă filtrată) — dar acel nivel era deja acoperit de
planul A/decizia 27-bis. Nu, la nivelul la care ar simplifica ceva („nu mai calculăm SCC per articol, luăm
politica gata aleasă") — pentru că acela e exact decizia 24, respinsă cu un motiv care rămâne valabil.
### E-bis. Dar există un al treilea model, mai simplu — cel din `contabilizeaza_rata` (secțiunea 0c)
Reconcilierea de la secțiunea 0 arată un tipar pe care raportul din runda 1 nu-l luase în calcul: **un
document poate duce el însuși o notă contabilă fixă, complet în afara `CRM_POLITICI_PRETURI`, iar
`pack_facturare` are deja o funcție care face exact asta** (`contabilizeaza_rata`, sursa `CONTRACTE.ID_NOTA`).
Aplicat la #13, ideea ar fi: nu mai deriva `SCC` per articol și nu mai caut/construi o politică tehnică — scrie
nota direct, cu conturile calculate în VFP (regula deciziei 27: `CORESP_CONT_VENCHELT`/`NOM_ARTICOLE.CONT`/
`704`), printr-o cale de scriere **paralelă**, ca și pentru rate.
**De ce nu se poate fără să ating `pack_facturare`, și asta contează, dat fiind decizia 27-bis**:
- `contabilizeaza_rata` există deja, dar e legată strict de `VANZARI_DETALII_TEMP` cu semantică de „rată de
contract" (`detalii_rata.id_ctr` obligatoriu în interogare) — nu e apelabilă pentru un rând de articol
obișnuit (`id_articol` populat, `id_pol` gol) fără o funcție **nouă**, analogă, în `pack_facturare`.
- Sursa notei la rată e `CONTRACTE.ID_NOTA` — o coloană pe un document care **există deja** și **se
configurează o dată** (la crearea contractului). Pentru articolul ad-hoc de pe formularul unificat nu
există azi un asemenea „document-sursă cu notă fixă" — ar trebui inventat (ex. o coloană similară pe
antetul facturii, sau parametru nou la `adauga_articol_factura`) — tot cod nou în `PACK_FACTURARE`.
- **Constrângerea „pack_facturare rămâne neatins" (decizia 27-bis) exclude explicit acest drum.** Dacă
Marius e dispus s-o relaxeze, tiparul `contabilizeaza_rata` e un precedent real, mai simplu decât rețeta
în 4 pași — pentru că elimină complet nevoia unei „politici tehnice" și a RPC-ului de apartenență
(`pack_preturi.adauga_politica_pret_art`): SCC-ul ar merge direct în `scrie_nota`, fără ocolul prin
`CRM_POLITICI_PRET_ART`. Dacă nu, rămâne exact ce spunea verdictul din runda 1: rețeta în 4 pași (planul
A, tot în VFP) rămâne singura variantă compatibilă cu constrângerea.
**Verdict final, actualizat**: modelul comandă/contract (runda 1) nu ajută, motivele rămân cele deja arătate.
Dar există un al doilea model real, comandă**-rate**/contract-rate, care **ar** ajuta — cu prețul de a
renunța la „`pack_facturare` neatins". E o decizie de produs pentru Marius, nu o concluzie tehnică unică —
raportul pune ambele opțiuni pe masă, cu costul lor exact.
**Corecție importantă, din verificarea independentă de mai jos („Completare de la sesiunea principală"):**
politica `7 DISCOUNT` (870 linii de comandă, `SCD=667`/`SCC=4111`, notă `2 DISCOUNT`) e deja, în producție,
o **politică tehnică folosită doar ca rutare contabilă**, nu ca listă comercială — exact tiparul „o politică
per scop contabil" pe care planul A/decizia 27-bis îl propune (nu decizia 24, care era o politică unică
pentru *orice* articol ad-hoc, indiferent de natură). Deci precedentul operațional pentru „politică tehnică,
una per destinație contabilă" **există deja pe ruta comandă** — mai puternic decât `gnId_pol_pret_stoc`
(care e un singur cont pentru tot stocul). **Dar** aceeași verificare arată că garanția „apartenență prin
construcția listei" (punctul 1 de mai sus) **nu e etanșă pe date**: 37 din 6868 linii active de comandă au
un articol care nu (mai) e membru al politicii înregistrate pe linie — verificare, cauză și comportament la
facturare, deschise, vezi secțiunea finală.
---
## A. Ruta COMANDĂ
### A.1 — `cursor_comanda` selectează `ID_POL`? Da, direct de pe linia comenzii
```sql
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2996-3054 (ramura factură)
OPEN V_CURSOR FOR
SELECT ROWNUM as id_c,
A.ID_ARTICOL,
NULL AS LOT,
NULL as SERIE,
A.ID_POL, -- <- direct din COMENZI_ELEMENTE, nu derivat
A.ID_VALUTA, ...
FROM COMENZI_ELEMENTE A
LEFT JOIN CRM_POLITICI_PRET_ART B
ON A.ID_POL = B.ID_POL AND A.ID_ARTICOL = B.ID_ARTICOL
...
WHERE A.STERS = 0 AND A.ID_COMANDA = V_ID_COMANDA ...
```
(identic la `:3084-3140` pentru ramura aviz, `V_TIP > 20`). `A.ID_POL` e coloană directă pe
`COMENZI_ELEMENTE` — `LEFT JOIN CRM_POLITICI_PRET_ART B` de mai jos **nu** derivă `id_pol`, doar aduce
`DISCOUNT_UNITAR`/`PROC_TVAV` pentru acea combinație `(id_pol, id_articol)`.
### A.2 — Unde e stocat: `COMENZI_ELEMENTE.ID_POL`, coloană `NOT NULL`
Confirmat pe schema vie (`MARIUSM_AUTO`, `ROA_CENTRAL`, 10.08.2026):
```sql
select column_name, data_type, nullable from user_tab_columns where table_name='COMENZI_ELEMENTE';
-- ID_POL NUMBER N <- NOT NULL
select count(*), sum(case when id_pol is null then 1 else 0 end) from comenzi_elemente where sters=0;
-- 6868 0
```
**`ID_POL` e obligatoriu la nivel de constrângere de tabel**, nu doar „de obicei populat" — și cele 6868
linii active din baza vie confirmă 0 excepții. E o coloană de **linie**, nu de antet (nu există `ID_POL`
pe `COMENZI`).
### A.3 — Cine îl pune acolo la crearea comenzii
Nu e o alegere liberă per articol — e moștenit dintr-o **politică aleasă o singură dată pe comandă**:
```
COMUN\clase\ocomenzi.vc2:1849-1870 (do_modifica, la deschiderea/crearea comenzii)
Do Case
Case Inlist(loRec.interna,2,5)
If lnTip = 0 And Reccount('crscomanda_curenta')>0
lnIdPol = id_pol && preia politica de pe comanda existenta
Else
loCauta = caut_politici_curente_utilizator() && cautare manuala, comanda noua
If !Empty(Nvl(loCauta.id_pol,0))
lnIdPol = loCauta.id_pol
Else
Return
Endif
Endif
update_articole_politica(lnIdPol)
Case loRec.interna = 3
update_articole_politica_comenzi([IDPOLITICAPRET]) && optiune implicita globala
Otherwise
update_articole_politica_comenzi([ID_LISTA_PRETURI_PV]) && optiune implicita globala
Endcase
```
Deci: pentru un tip de comandă (`interna` 2/5) operatorul **caută explicit** o politică
(`caut_politici_curente_utilizator`); pentru celelalte tipuri, politica vine dintr-o **opțiune globală**
(`gnIdPoliticaPret`/`gnId_lista_preturi_PV`, citite în `extrage_optiuni_firma`,
`update_comenzi.prg:16-29`). Nu se moștenește de la client.
Politica aleasă filtrează lista de articole disponibile de adăugat:
```
COMUN\programe\update_comenzi.prg:31-60 (update_articole_politica)
select p.id_articol,p.id_pol,p.pret,... from com_vpreturi_utilizator p ...
where p.id_util = <<gnIdUtil>> and p.id_pol = <<tnIdPol>>
```
```sql
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\01\ff_2026_01_21_01_FACTURARE.sql:3-30
create or replace view com_vpreturi_utilizator as
select a.id_util, b.id_politica as id_pol, ..., d.id_articol, d.pret, ...
from utilizatori_rol_intern a
left join politici_grupuri b on a.id_grup = b.id_grup
left join crm_politici_preturi c on b.id_politica = c.id_pol
left join crm_politici_pret_art d on c.id_pol = d.id_pol
left join nom_articole e on d.id_articol = e.id_articol
where a.sters = 0 and b.sters = 0 and ... and d.id_pol is not null and ...
```
`v_articole` (grid-ul de adăugare articole pe comandă, `ocomenzi.vc2:4005-4014`,
`RecordSource = "v_articole"`) e populat strict din acest view, **filtrat `d.id_pol IS NOT NULL`** — deci
orice articol afișat spre alegere e deja membru garantat. La alegere (`do_adauga`,
`ocomenzi.vc2:4645-4702`) și la salvare (`do_scrie_articole`, `:4864-4899`):
```
ocomenzi.vc2:4885-4893
Scatter Name poArticol
...
lcSql = [begin pack_comenzi.adauga_articol_comanda(] + Alltrim(Str(This.nId)) + [,] + ;
Alltrim(Str(poArticol.id_articol)) + [,] + Alltrim(Str(poArticol.id_pol)) + [,] + ...
```
`poArticol.id_pol` (scatter din `v_articole`) merge neschimbat în `COMENZI_ELEMENTE.ID_POL`. Nu există în
`ocomenzi.vc2` niciun al doilea punct de adăugare a articolelor pe comandă (nicio cale către `caut_articol`
de nomenclator liber) — `v_articole` e singura sursă.
### A.4 — Ce se întâmplă când linia de comandă are `ID_POL` gol la facturare
**Nu se poate întâmpla, prin construcție dublă**: (a) `COMENZI_ELEMENTE.ID_POL` e `NOT NULL` la nivel de
Oracle — un `INSERT` cu `id_pol` gol ar da `ORA-01400`, nu ajunge niciodată să fie facturat; (b) singurul
punct de inserare din VFP (`pack_comenzi.adauga_articol_comanda`, apelat cu `poArticol.id_pol` din
`v_articole`) nu poate produce `id_pol` gol, pentru că sursa (`com_vpreturi_utilizator`) filtrează deja
`id_pol IS NOT NULL`. Verificat pe date reale: 0 din 6868 linii active. Spre deosebire de nomenclator
(`caut_articol`, unde coloana `id_pol` **nu există deloc** în SELECT), pe comandă întrebarea „ce se
întâmplă la gol" nu are un răspuns de cod (fallback/eroare) pentru că premisa nu apare niciodată.
---
## B. Ruta CONTRACT
Produsul e separat, **`D:\ROA\ROACONTRACTE`** (există în arbore, alături de `ROAFACTURARE`).
### B.1 — Cursorul de facturare selectează `ID_POL`? Da, dar prin `ID_POL_ART`, nu direct
```sql
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2718-2836 (cursor_contract, V_CURSOR2 = grd_contracte)
SELECT ..., id_pol, ...
FROM (SELECT A.ID_CTR, C.ID_ARTICOL, NULL as id_rata, C.ID_POL, ...
FROM CONTRACTE A
LEFT JOIN CTR_ARTICOLE B ON A.ID_CTR = B.ID_CTR
LEFT JOIN CRM_POLITICI_PRET_ART C ON B.ID_POL_ART = C.ID_POL_ART -- <- cheia stocata e ID_POL_ART
LEFT JOIN NOM_ARTICOLE E ON C.ID_ARTICOL = E.ID_ARTICOL ...
```
Grid-ul principal de adăugare articole pe contract (`crsarticole`, folosit pentru „adăugare liberă") **nu**
vine din acest cursor — vine dintr-un apel separat, în interiorul aceleiași proceduri:
```sql
-- ff_...:2940-2948
pack_facturare.cursor_preturi(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_ID_GESTIUNE_INIT,
V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR);
```
adică **exact aceeași sursă ca „lista de prețuri" (secțiunea C)** — confirmă din nou concluzia din runda
anterioară (`retur_si_lista_preturi.md` B7): grid-ul „liber" de pe contract nu e liber de politică, e lista
de prețuri normală a operatorului.
### B.2 — Unde e stocat pe contract: `CTR_ARTICOLE.ID_POL_ART` (linie, nullable)
```sql
select column_name, nullable from user_tab_columns where table_name='CTR_ARTICOLE';
-- ID_POL_ART Y <- nullable, spre deosebire de COMENZI_ELEMENTE.ID_POL
select count(*), sum(case when id_pol_art is null then 1 else 0 end) from ctr_articole;
-- 27 6
```
Spre deosebire de comandă, contractul stochează **`ID_POL_ART`** — FK direct la rândul din
`CRM_POLITICI_PRET_ART` (articol+politică), nu la politică ca atare — și coloana **e nullable**, fără
constrângere de tabel. Pe datele reale (bază de dezvoltare, eșantion mic — 27 rânduri în total, deci
**nu extrapolez procentul la producție**), 6 din 27 rânduri nu au `id_pol_art`. Dacă un asemenea rând ar
ajunge selectat spre facturare prin `V_CURSOR2`/`grd_contracte` (nu prin `crsarticole`, care e mereu lista
de prețuri), `LEFT JOIN ... ON B.ID_POL_ART = C.ID_POL_ART` nu găsește nimic, `id_pol` iese `NULL` în
cursor — același drum spre FACT-024 ca la nomenclator. **Neverificat**: dacă UI-ul (`grd_contracte`) permite
efectiv selecția unei asemenea linii spre facturare sau o filtrează/dezactivează — nu am urmărit acel cod
de grid, în afara bugetului acestei runde.
### B.3 — Cine îl pune acolo la crearea contractului
`D:\ROA\ROACONTRACTE\Clase\ferestre_contracte.vc2`, `PROCEDURE do_adauga_obiectul` (`:9454+`):
```
:9464-9470
Case gnParametru_prog = 1 && clienti
lcSel = [{call pack_crm.get_politici_grup(] + Alltrim(Str(gnIdUtil)) + [,] + ;
Alltrim(Str(goContract.id_valuta)) + [,?gnIdSucursala)}]
... INTO Cursor crsPoliticiGrup1 ...
```
```
:9486-9500
fpp = Createobject("frm_politica_preturi") && operatorul alege O politica dintre cele la care are drept
fpp.Show(1)
...
Select *, PRETFTVA As pret_unitar, 1 As cant, ... FROM cPol_pret_art With (Buffering = .T.) ;
WHERE ales = 1 INTO Cursor cAles && articolele bifate, deja membre ale politicii alese
```
`pack_crm.get_politici_grup` e echivalentul ROACONTRACTE al lanțului
`UTILIZATORI_ROL_INTERN → POLITICI_GRUPURI → CRM_POLITICI_PRETURI` folosit și de comandă/factură normală —
operatorul primește lista politicilor lui, alege una, `cPol_pret_art` (bind pe `CRM_POLITICI_PRET_ART`
pentru politica aleasă) afișează articolele ei, iar rândurile bifate (`ales=1`) devin linii `CTR_ARTICOLE`
cu `id_pol_art` = `ID_POL_ART`-ul din acel rând. Din nou: **nu se alege un articol liber, se alege dintr-o
listă deja restrânsă la politică.**
### B.4 — Ce se întâmplă cu `ID_POL_ART` gol la facturare
Vezi B.2 — spre deosebire de comandă, **nu e imposibil prin constrângere de schemă** (coloana e nullable),
și există cazuri reale (6/27) în baza de dezvoltare. Comportamentul la facturare (dacă acea linie ajunge
selectată) urmează același `NULL → FACT-024` ca la nomenclator — **neconfirmat direct pe o factură reală**,
dedus din structura JOIN a cursorului (B.1).
---
## C. Ruta LISTĂ DE PREȚURI (tip 1/5/7/10/22/29/45), ca termen de comparație
**Corectare față de premisa din brief**: `id_pol` pe această rută **nu vine din alegerea operatorului pe
document** pentru majoritatea tipurilor — vine automat din rolul operatorului logat, exact ca pe comandă,
dar fără nicio interacțiune UI.
`ofacturare.prg:279-282` (apelul cursorului pentru tip 1,5,7,10,22,23,29):
```
lcSqlCursor = [{call pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta,] + ;
[?poDate.id_gestiune_init,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala)}]
```
`poDate.id_pol` **nu e printre parametri**. `cursor_preturi` (`ff_...:2138-2354`) derivă `A.ID_POL` intern:
```sql
-- ff_...:2222-2251 (V_TIP IN (1,2)) / 2330-2354 identic ca structura, tot fara parametru id_pol
FROM (select a1.id_util, a3.id_pol, ...
from utilizatori_rol_intern a1
left join politici_grupuri a2 on a1.id_grup = a2.id_grup
left join crm_politici_preturi a3 on a2.id_politica = a3.id_pol
...
where a1.id_util = V_ID_UTIL
and NVL(a1.ID_SUCURSALA,-99) = NVL(V_ID_SUCURSALA,-99)
and <data curentă între valabilitatea politicii>) A
LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL = B.ID_POL
```
Adică `id_pol` per linie vine din rolul de utilizator (`UTILIZATORI_ROL_INTERN.ID_UTIL = V_ID_UTIL`) și
sucursală — **niciodată dintr-o alegere pe documentul curent**, pentru tip 1/2/5/7/10/22/29/45.
Câmpul de căutare vizibil pe antet există, dar e **inert pentru aceste tipuri**:
```
COMUN\clase\ofacturare.vc2:6822-6825
ADD OBJECT 'Ct_clb_politici_preturi' AS ct_clb_cautare WITH ;
cconditie = ISNULL(poDate.id_pol) or EMPTY(poDate.id_pol), ;
cprocedura = thisform.do_cauta_politica, ...
```
`poDate.id_pol` e citit efectiv (trimis la Oracle) **doar** pentru `cursor_gestiune`, tip 41/23
(`ofacturare.prg:297,777`); e validat ca obligatoriu **doar** pentru aceleași două tipuri
(`ofacturare.vc2:7334-7337`: *„Nu ati ales politica de preturi!"*). Pe factura normală din listă de
prețuri, câmpul poate rămâne gol fără nicio consecință — nu alimentează nimic.
**Concluzie C**: „lista de preț pe document" nu există ca mecanism activ pentru rutele principale — e „lista
de preț a operatorului logat", determinată automat de rolul lui în `UTILIZATORI_ROL_INTERN`. Fiecare linie
din `crsarticole` vine deja cu propriul `A.ID_POL` din acest join; dacă operatorul are mai multe grupuri de
politici active simultan, teoretic același articol ar putea apărea de mai multe ori cu `id_pol` diferit —
**neverificat pe date reale**, în afara bugetului acestei runde.
---
## Ce nu s-a putut stabili și de ce
- **B.4, comportamentul exact la facturare a unei linii de contract cu `ID_POL_ART` gol** — dedus din
structura JOIN a `cursor_contract`, nu confirmat pe o factură reală emisă din contract cu o asemenea
linie (ar necesita un contract de test cu o linie fără politică, apoi o încercare de facturare — în
afara bugetului read-only al acestei runde).
- **Dacă `grd_contracte` (UI) permite selecția/afișarea unei linii cu `id_pol_art` gol** — nu am urmărit
codul de grid din `ROACONTRACTE`/`ofacturare.vc2` pentru acest caz specific.
- **Dacă un operator cu mai multe grupuri de politici active poate primi articole duplicate cu `id_pol`
diferit în `crsarticole`** (secțiunea C) — plauzibil din structura JOIN-ului (fără `DISTINCT` pe
`a1.id_util`), neverificat pe date reale.
- **Eșantionul `CTR_ARTICOLE` e mic (27 rânduri) în baza de dezvoltare** — raportul 6/27 fără
`id_pol_art` e o dovadă reală, dar nu extrapolabilă direct la volumul de producție; merită o verificare
separată pe o bază cu volum de producție dacă decizia depinde de frecvența cazului.
- **Cele 37 de linii de comandă active cu articol ne-membru al politicii de pe linie** (semnalate în
„Completare de la sesiunea principală") — cauza (import? editare ulterioară a politicii? ștergere din
`CRM_POLITICI_PRET_ART` după ce comanda a fost creată?) nu e stabilită, și nici comportamentul real la
facturare (`FACT-024`/`ORA-01403` așteptat pe cod, neconfirmat pe o încercare reală). E cea mai
importantă verificare rămasă înainte de S4g — dacă garanția „membership prin construcția listei" se sparge
și pe alte linii similare, tot raționamentul din secțiunea E (paragraful 1) trebuie recalibrat.
- **`VANZARI.TIP = 51`** — 121 de linii fără `id_pol`, cu 179/275 rânduri `ACT` cu `SCC` populat cumulat pe
documentele aferente — nu am identificat ce tip de document e (nu apare în `Do Case` din
`ofacturare.prg:266-308`); posibil vânzare retail/POS sau un tip specific altui produs ROA. Neexplorat.
- **Restul tipurilor negative (`-1`…`-13`, în afară de `-12`=ROAAUTO)** — nu au fost identificate individual;
presupun variante storno/aviz ale rutelor deja documentate, dar nu s-a verificat pe cod care produs le
generează.
- **Dacă experiența lui Marius vine efectiv din contract-cu-rate, din ROAAUTO, sau din altă rută
neidentificată (`51`, negative)** — nu s-a putut stabili fără să-l întrebăm direct; secțiunea 0d
enumeră ipotezele plauzibile, cu dovezi pentru fiecare, dar nu poate alege între ele.
---
## Completare de la sesiunea principala (confirmare pe schema, 10.08.2026)
Verdictul de mai sus a fost verificat independent, pentru ca **contrazice o afirmatie a lui Marius**
(„fac facturi pe baza de comanda care nu au politica de pret"). Confirmat:
- `COMENZI_ELEMENTE.ID_POL` — `nullable = N`, constrangerea `SYS_C0015376` (`"ID_POL" IS NOT NULL`) plus
`FK_COMENZI_ELEMENTE_002`. In date: **7108 randuri total, 0 cu `ID_POL` nul, 0 cu `ID_POL` = 0**, 9
politici distincte in uz. Pe liniile active (`STERS = 0`): 6868, tot 0 nule.
- `CTR_ARTICOLE.ID_POL_ART` — `nullable = Y`. Confirmata asimetria semnalata in raport.
- Toate cele 9 politici folosite pe linii de comanda au `ID_NOTA` si ajung la un `SCC`.
**Doua observatii noi, care nu erau in raport:**
1. **Politica `7 DISCOUNT` e o politica tehnica de rutare contabila, folosita in producție.** Apare pe
**870 de linii de comanda** si trimite la nota `2 DISCOUNT` (`SCD = 667`, `SCC = 4111`). Nu e o lista
comerciala de preturi — e o politica existenta doar ca sa duca linia la o nota contabila anume. **E
precedentul de care are nevoie decizia 27**, si e mai apropiat de ce cere #13 decat
`gnId_pol_pret_stoc` (care e o singura politica pentru tot stocul). Tiparul „politica tehnica per scop
contabil" nu trebuie inventat: exista.
2. **Garantia „apartenenta prin construcția listei" nu e etanșă in date.** Din 6868 de linii active de
comanda, **37 au un articol care nu e membru al politicii de pe linie**
(`NOT EXISTS (SELECT 1 FROM crm_politici_pret_art WHERE id_pol = e.id_pol AND id_articol = e.id_articol)`).
Adica 37 de linii care, la facturare, ar trebui sa cada cu `FACT-024`. Cauza nu e stabilita — import,
sau editarea / stergerea articolului din politica dupa introducerea comenzii. **Punctul 1 al secțiunii E
(„nu exista cale, in UI, de a ajunge la un articol care nu e deja membru") e corect despre UI, dar nu
despre starea datelor.** De verificat inainte de S4g: daca acele 37 de linii se factureaza fara eroare,
exista o cale nedescoperita si intrebarea deciziei 32 se redeschide.
Interogarile: schema `MARIUSM_AUTO` pe `ROA_CENTRAL`, doar `SELECT`.

View File

@@ -1,179 +0,0 @@
# Inventar controale — formulare facturare (ROAFACTURARE, `COMUN\clase`)
Investigatie read-only (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit). Toate liniile
sunt din fisierele text `.vc2` (FoxBin2Prg), verificate pe fisierul real (nu `.bak`). Scop: pregatirea
unui mockup pentru un formular de facturare unificat — inventarul reflecta controalele existente, nu
o propunere noua.
## 1. Butoane `frm_facturare_articole` (`ofacturare.vc2:10968-15739`)
Grid sursa (comanda/lista preturi) = `grd_articole` (`Left=9,Top=336,Width=326,Height=130`,
`RecordSource=crsarticole`, `ofacturare.vc2:11570`). Grid destinatie (linii factura) = `grd_factura`
(`Left=379,Height=337`, `RecordSource=crsfactura`, `ofacturare.vc2:12263`).
| Control | Clasa de baza | Caption/Picture | L/T/W/H | ToolTipText | Pozitie fata de grid | `fisier:linie` |
|---|---|---|---|---|---|---|
| But_modifica1 | but_modifica | (fara caption, `modific_sus.bmp`) | 773/61/30/27 | "Modificare (CTRL+M)" | dreapta-sus `grd_factura` (Anchor=9) | `ofacturare.vc2:11192` |
| But_sterge1 | but_sterge | `sterg_sus.bmp` | 811/61/30/27 | "Stergere (CTRL+D)" | dreapta-sus `grd_factura`, langa But_modifica1 | `ofacturare.vc2:11229` |
| But_renunt1 | but_renunt | `renunt_sus.bmp` | 792/1/30/27 | "Renuntare (ESC)" | coltul din dreapta-sus al formularului | `ofacturare.vc2:11202` |
| But_reset1 | but_reset | `reset_sus.bmp` | 303/295/30/27 | "Reseteaza" | deasupra `grd_articole` (Top grid=336) | `ofacturare.vc2:11213` |
| But_urmator1 | but_urmator, `caction=do_urmator` | `urmator1.bmp` | 343/348/30/27 | "Verificare" | lateral-dreapta `grd_articole` (Left grid+Width+8=343) | `ofacturare.vc2:11237` |
| But_urmator2 | but_urmator, `caction=do_urmator2` | `urmator1.bmp` | 343/143/30/27 | "Verificare" | deasupra `grd_articole`, langa `grd_contracte` | `ofacturare.vc2:11247` |
| **But_urmator_tot1** ("adauga tot din comanda") | but_urmator_tot, `caction=do_adauga_tot` | `urmator1_tot.bmp`, **fara ToolTipText** | 343/378/30/27, `Visible=.F.` implicit | — | lateral-dreapta `grd_articole`, sub But_urmator1 | `ofacturare.vc2:11257` |
| But_retur | but_retur | `retur1.bmp` | 343/407/30/27, `Visible=.F.` implicit | "Retur" | lateral-dreapta `grd_articole`, sub But_urmator_tot1 | `ofacturare.vc2:11221` |
**"Adauga tot din comanda" — But_urmator_tot1.Click -> `do_adauga_tot`**
(`ofacturare.vc2:13169-13198`): parcurge `crsarticole` (SCAN) si apeleaza
`Thisform.do_adauga_articol(.T.)` pentru fiecare linie; daca articolul e gestionabil si cantitatea
ramasa >0, cere confirmare "Nu ati selectat toata cantitatea... treceti la urmatorul?"
(`aMessageBox` cu butoane Da/Nu/Renunta, cod 7).
Vizibilitate pe tip document (`Init`, `ofacturare.vc2:14976-15344`, `Do Case poDate.tip`):
| Control | Vizibil cand | Dovada |
|---|---|---|
| But_urmator_tot1 | `poDate.eProforma=1`, `poDate.lCopiere`, `tip=3` (comanda), `tip=4` (din avize), `tip in(21,28,42,47)` (aviz din comanda), `tip=25`, `tip in(8,9)` (retur factura), `tip=24` (retur aviz) | `ofacturare.vc2:15113,15120,15150,15166,15177,15215,15240,15245` |
| But_retur | `tip in(1,5,7,10)` (facturare din lista de preturi) | `ofacturare.vc2:15127` — comentariu explicit: "pot sa fac retur de articole intr-o factura de vanzare" |
| But_urmator2 / `grd_contracte` | eliminate (`RemoveObject`) daca nu exista `crsarticole1` (fara contracte pe formular) | `ofacturare.vc2:15294-15324` |
**Conventia de clase de butoane** (suita ROA): clasa de baza `buton` (`_cmd_base.vc2:16`,
`AS _cmdbase OF "_cmd_base.vcx"`) — 30x27px implicit, `Caption=""` (buton doar cu imagine),
`BackColor=alb`, `SpecialEffect=1`. `Click` (`_cmd_base.vc2:41-70`) executa dinamic
`This.Parent.<caction>()` (macro pe `cAction`/`clistaparametri`) — de-asta majoritatea instantelor nu
au `Caption`/`Click` propriu, doar `caction=<nume_metoda>`. Subclasele concrete (`but_nou`,
`but_sterge`, `but_modifica`, `but_urmator_tot`, etc.) sunt in `cmd_butoane.vc2:7-441`, fiecare
hardcodand `caption`/`cpictureup`/`cpicturedown`/`Picture` (`..\grafice\*.bmp`, stare sus/jos) si
`ToolTipText` cu shortcut intre paranteze (ex. "Modificare (CTRL+M)"). `but_nou` (`do_adauga`,
`nou_sus.bmp`, "Adaugare (CTRL+N)") — `cmd_butoane.vc2:214`.
## 2. Butoane `frm_facturare_articole2` (prototip, `ofacturare.vc2:15741-19355`)
Diferenta arhitecturala fata de #1: **un singur grid** `grd_factura` (`Left=12,Top=204,Height=240`,
`ofacturare.vc2:16601`) cu editare inline prin comboboxuri in celule (`cCodMat.cboCodmat`,
`cDenumire.cCboDenumire`, `cGestiune.cCboGestiune`) — nu exista casete separate de cautare articol
(`ct_codmat`/`ct_articole`) si nici `crsarticole`/comanda sursa.
| Control | Clasa de baza | Caption/Picture | L/T/W/H | ToolTipText | Pozitie fata de grid | `fisier:linie` |
|---|---|---|---|---|---|---|
| **But_nou1** | but_nou (mostenit, `caption=do_adauga` suprascris in clasa) | `nou_sus.bmp` | 719/175/30/27 | "Adaugare (CTRL+N)" | deasupra-dreapta `grd_factura` (Top grid=204) | `ofacturare.vc2:15936` |
| But_sterge1 | but_sterge | `sterg_sus.bmp` | 747/175/30/27 | "Stergere (CTRL+D)" | deasupra-dreapta `grd_factura`, langa But_nou1 | `ofacturare.vc2:15955` |
| But_renunt1 | but_renunt | `renunt_sus.bmp` | 716/1/30/27 | "Renuntare (ESC)" | coltul din dreapta-sus | `ofacturare.vc2:15944` |
**But_nou1.Click -> `do_adauga`** (`ofacturare.vc2:17118-17122`, override propriu, nu
`but_nou`.caction implicit): `SELECT crsFactura / APPEND BLANK / this.grd_factura.SetFocus()` —
adauga direct un rand gol in grid si da focus, spre deosebire de `do_adauga_articol` complex din #1.
Nu exista `But_modifica` in aceasta clasa — editarea se face inline in celulele gridului, nu prin
dialog separat.
**Bonus relevant pentru mockup**: acest prototip **incorporeaza deja aceleasi controale de antet**
(`Ct_clb_venchelt`, `Ct_clb_sectie`, `Ct_clb_responsabil`, `Ct_clb_lucrare`, `Ct_clb_altele`,
`Ct_clb_valuta`, `clb_fdoc`, `Clb_serie_act1`, `Clb_nract`, `Clb_dataact`, `Clb_data_scadenta`,
`Clb_zi_curs`) direct pe formularul de articole, la `ofacturare.vc2:15741+182..749` — e cea mai
apropiata schita existenta de un "formular unificat".
## 3. Antet `frm_date_factura` (`ofacturare.vc2:8482-9869`) si `frm_date_aviz` (`ofacturare.vc2:6566-7618`)
| Control cerut | `frm_date_factura` — caption real | `frm_date_aviz` — caption real | Tip/container | Obligatoriu / vizibil conditionat |
|---|---|---|---|---|
| tip venit/cheltuiala | `Ct_clb_venchelt` = "Venit / cheltuiala" | `Ct_clb_venchelt` = "Venit / cheltuiala" | `ct_clb_cautare` (container cautare) | eliminat pe aviz pentru `tip=23,41,25` (transfer/retur) — `ofacturare.vc2:7438-7461` |
| sectie | `Ct_clb_sectie` = "Sectie" | `Ct_clb_sectie` = "Sectie" | `ct_clb_cautare` | intotdeauna prezent in ambele (nu s-a gasit `RemoveObject`) |
| responsabil | `Ct_clb_responsabil` = "Responsabil" | `Ct_clb_responsabil` = "Responsabil" | `ct_clb_cautare` | eliminat cand `gnScadereStoc=0` sau `tip in(4,7,8,9,48,49)` — factura: `9646-9707`; aviz: eliminat pe majoritatea tipurilor cu comanda/lista |
| lucrare | `Ct_clb_lucrare` = "Lucrare" | `Ct_clb_lucrare` = "Lucrare" | `ct_clb_cautare` | nu s-a gasit eliminare conditionata |
| altele | `Ct_clb_altele` — label dinamic ("Altele" implicit) | idem | `ct_clb_cautare`, `.do_schimba_explicatia(...)` | eticheta se schimba pe tip: "Nr. contract"/"Nr. comanda"/"Nr. factura"/"Nr. facturi"/"Locatie" (`9633-9643`); eliminat complet daca `gnScadereStoc=0 and tip in(1,5,10)` fara copiere |
| valuta | `Ct_clb_valuta` = "Valuta" | **nu exista pe aviz** | `ct_clb_cautare` | eliminat daca `poDate.in_valuta=0` (`ofacturare.vc2:9725-9732`) |
| fel document | `Ct_clb_fdoc` = combo `_combobox1` FACTURA/PROFORMA/BON FISCAL, label "Tip document" | `Ct_clb_fdoc` = camp cautare "Felul documentului" (`caut_ora.vcx`) | container diferit intre cele doua forme (combo la factura, cautare la aviz) | intotdeauna vizibil |
| serie | `Clb_serie_act` label "Serie document" | `Clb_serie_act` (fara label explicit override) | `clb_serie_act` (`serii_numere.vcx`) | eliminat daca `poDate.rezultat_serii` nu e in `(1,2,3)` — nicio serie configurata |
| numar | `Clb_nract` = "Numar document" | `Clb_nract` = "Nr. documentului" | `clb_tx_simplu`, `InputMask=get_mask(14,0)` | mereu prezent |
| data act | `Clb_dataact` = "Data document" | `Clb_dataact` = "Data documentului" | `clb_tx_data` | mereu prezent |
| data scadenta | `Clb_data_scadenta` = "Data scadenta" | **nu exista pe aviz** | `clb_tx_data` | **dezactivat** (nu eliminat) cand `gnScadentaAutomata=1` (`.dezactiveaza()`, `9713-9715`) |
| zi curs | `Clb_zi_curs` = "Data curs valutar" | `Clb_zi_curs` = "Data cursului valutar" | `clb_tx_data` | eliminat pe factura daca `tip in(8,9)` retur (`9718-9722`) |
| client | `Ct_clb_nume_client` = "Nume client" | `Ct_clb_nume_client` = "Nume client" | `ct_clb_cautare` | eticheta se schimba pe aviz ("Retur de la"/"Gestiune sursa") in functie de tip transfer |
Control specific doar in `frm_date_factura`: `Ct_clb_gestiune_init`="Gestiune sursa" (`ToolTipText`:
"Daca nu alegeti gestiunea, la scaderea din stoc a unui articol va vor fi aratate stocurile tuturor
gestiunilor pe care aveti drepturi.") — eliminat impreuna cu `Ct_clb_responsabil` in majoritatea
cazurilor `gnScadereStoc=0`; `txtCodFiscal`/`lblCodFiscal` (cod fiscal, `ReadOnly`, langa client) si
`txtSoldLei`/`lblSoldLei` (sold curent client, populat din `GetSoldClient()` daca
`poDate.id_client<>0`); `But_verifica1` (verificare ANAF). Control specific doar in
`frm_date_aviz`: `Ct_clb_politici_preturi`="Politica de preturi" — eliminat pe majoritatea
tipurilor cu comanda.
Toate conditiile de vizibilitate sunt in `Init` (nu in `do_schimba_tipdoc`, care doar realoca
seria/numarul): `frm_date_factura.Init` = `ofacturare.vc2:9563-9796`; `frm_date_aviz.Init` =
`ofacturare.vc2:7354-7600` (citit doar pana la ~`7533`; ultimele ~65 linii nu au fost verificate,
vezi "Necunoscute ramase").
## 4. `frm_alte_date` (`ferestre_cere_date.vc2:2219-3353`)
| Control | Caption | Grupare logica | `fisier:linie` |
|---|---|---|---|
| `Ct_clb_delegat` | "Delegat" | Delegat/transport | `2553` |
| `Ct_clb_masina` | "Masina" | Delegat/transport | `2569` |
| `Ct_clb_agent` | "Agent" | Delegat/transport | `2537` |
| `Clb_dataora_exp` | "Data si ora expedierii" | Delegat/transport | `2443` |
| `opt_incasat` (radiogrup 4 optiuni) | "Fara incasare" / "Chitanta" / "Bon fiscal" / "POS Card" | Incasare | `2638` |
| `Cb_casa` | "Casa" (-> "Banca POS" pe POS) | Incasare | `2362` |
| `Clb_serie_chit` | "Serie chitanta" | Incasare (doar Chitanta) | `2503` |
| `Clb_nrchit` | "Nr. chitanta" (-> "Nr. bon" pe Bon fiscal/POS) | Incasare | `2480` |
| `Clb_incasat` | "Incasat" | Incasare | `2461` |
| `cmdModificaBon` | (icon `but_modifica`) | Incasare (doar Bon fiscal), `caction=do_modifica_bon` | `2526` |
| `chkPOS` | "POS" | Incasare (doar Bon fiscal) | `2409` |
| `chkDetaliat` | "Detaliat" | Incasare (doar Bon fiscal) | `2396` |
| `cboTipFactura` | (combo, langa label "Tip factura") | Incasare | `2378` |
| `clb_adresa_facturare` | "Adresa facturare" | Adresa de facturare | `2422` |
| `Ed_tx_simplu1` | "Text aditional (max. 1000 caractere)" | Text aditional | `2585` |
| `But_modifica1` | (icon) `caction` implicit -> `do_modifica` | actiune pe delegat (deschide `nom_parteneri_modifica`) | `2343` |
**`actualizeaza_tipincasare`** (`2698-2856`) comuta vizibilitatea pe `This.opt_incasat.Value`:
| Valoare | Vizibile | Ascunse | Numar alocat |
|---|---|---|---|
| 1 = Fara incasare | — | `cb_casa,clb_serie_chit,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon,chkPOS,chkDetaliat` | dezaloca 16(chitanta)/3(bon)/26(POS) |
| 2 = Chitanta | `cb_casa,clb_serie_chit,clb_nrchit,clb_incasat,_shape3,lb_simplu1` | `cmdModificaBon,chkPOS,chkDetaliat` | `Thisform.clb_serie_chit.genereazaNumar()` (`2783`) |
| 3 = Bon fiscal | `cb_casa,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon,chkPOS,chkDetaliat` | `clb_serie_chit` | `Thisform.do_aloca_nr_bon([CLICK])` (`2807`) |
| 4 = POS/Card | `cb_casa,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon` | `clb_serie_chit,chkPOS,chkDetaliat` | `Thisform.do_aloca_nr_pos([CLICK])` (`2844`) |
`cmdModificaBon.Click -> do_modifica_bon` (`3001-3010`) deschide `viz_config_serii_complet WITH 3`
(`oserii_numere.prg`) si, la confirmare, dezaloca+realoca numarul de bon fiscal.
## 5. Butoane `frm_facturi` (lista facturi, `ofacturare_comun.vc2:1168-5126`, fisierul real —
verificat, nu `.pre_s4butoane.bak`)
| Control | Caption/Picture | Metoda apelata (`caction`/override) | `fisier:linie` |
|---|---|---|---|
| `but_modifica1` | `modific_sus.bmp` | `caction` implicit = `inainte_de_do_modifica` -> meniu `xmenu("Modificare date factura;Editare factura (articole, cantitati, preturi)")` -> optiune 1: `do_modifica()`, optiune 2: **`do_editare_factura()`** | ADD OBJECT `1424`; `inainte_de_do_modifica` `4925-4934`; `do_modifica` `4538-4637`; `do_editare_factura` `3715-3869` |
| `But_modifica2` | (fara caption, Top=341 — pe grid-ul de detalii, nu in bara de sus) | `caction=do_modifica_explicatie`, ToolTipText "Modificare explicatie articol" | `1434`; `do_modifica_explicatie` `4639-4656` |
| `But_copiaza1` | `copy_sus.bmp`, `Visible=.F.` implicit | `caction` mostenit = `do_copiaza` | `1404`; `do_copiaza` `3628-3713` |
| `But_sterge1` | `sterg_sus.bmp` | `caction` mostenit (`inainte_de_do_sterge` din clasa `but_sterge`) -> `do_sterge` | `1463`; `do_sterge` `4658-4874` |
| `But_listare1` | `listare_sus.bmp` | `do_listare` | `1414` |
| `But_verifica1` | (fara Picture explicit) | `do_verifica`, ToolTipText "Vericare coduri fiscale pe serverul ANAF" | `1473` |
| `But_attach1` | `attach_sus.bmp` | `caction=` gol, override `But_attach1.Click` | `1394` |
**Nu exista buton separat "editare 2024"**: `do_editare_factura` este optiunea 2 din meniul popup
deschis de `but_modifica1` (`inainte_de_do_modifica`), nu un buton propriu.
`Init` (`4936-4959`): daca `glLunaInchisa` (luna contabila inchisa),
`Thisform.but_sterge1.Visible = .F.` si se scoate dreptul de stergere din `gcAcces` — singura
conditionare de vizibilitate pe drept gasita in aceasta clasa.
## Necunoscute ramase
- `do_modifica` (frm_facturare_articole, `13746-13914`, 168 linii) si `do_sterge`/`do_editare_factura`
(frm_facturi) nu au fost citite in detaliu — doar identificate ca existenta/semnatura; daca
mockup-ul are nevoie de logica exacta de validare la modificare/stergere linie, trebuie citite
explicit.
- `frm_date_aviz.Init` (`7354-7600`) a fost citit doar pana la ~`7533`; ultimele ~65 linii (probabil
finalizare `laPozitii`/repozitionare, simetrice cu `frm_date_factura`) nu au fost verificate.
- Nu am verificat daca vizibilitatea controalelor din sectiunea 3 mai depinde si de **drept de
utilizator** (nu doar `poDate.tip`/`gnScadereStoc`/`gnScadentaAutomata`) — codul citit foloseste
doar variabile globale de setare firma si tipul documentului, nicio verificare explicita
`gcAcces`/drepturi pe aceste containere.
- Dimensiunile exacte (`Width`/`Height`) ale containerelor `ct_clb_cautare`/`clb_tx_*` nu sunt
listate explicit in multe instante (mostenite din clasa de baza `caut_ora.vcx`/`lb_tx.vcx`) — nu
am deschis acele biblioteci pentru dimensiuni implicite; doar `Left`/`Top`/`TabIndex` sunt
suprascrise per instanta.
- Nu am inspectat `caut_ora.vcx`/`lb_tx.vcx`/`serii_numere.vcx` (clasele de baza ale containerelor
`clb_*`/`ct_clb_*`) pentru a confirma dimensiunile standard sau comportamentul exact al
proprietatii `cconditie` (pare sa controleze cand campul de cautare cere obligatoriu o valoare,
nu vizibilitatea).

View File

@@ -1,228 +0,0 @@
# Cercetare: legatura "linie de retur -> linie/factura originala" se persista undeva?
## Verdict (10 randuri)
**NU se persista la nivel de linie. PARTIAL la nivel de document, si numai cand a fost aleasa o
singura factura sursa.** Cursorul Oracle care populeaza grila de retur pentru N.1 (documente tip
8/9/24, `pack_facturare.cursor_retur_document`, `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3949-4062`)
**nu selecteaza deloc** `A1.ID_VANZARE` sau `A1.ID_VANZARE_DET` din `VANZARI_DETALII` — legatura cu
factura/linia sursa se pierde chiar in query-ul care aduce datele in VFP, inainte sa existe vreo
sansa sa fie afisata. INSERT-ul final in `VANZARI_DETALII` (`scrie_in_vanzari`, documentat deja in
`rec_cale_vanzari_detalii.md:105-113`) nu are nicio coloana de tip sursa. Exista in schimb un link
**la nivel de document intreg**: `VANZARI_CORESP` (`TIP=3`, `ID_VANZARE_FACT`=documentul de retur,
`ID_VANZARE_AVIZ`=fiecare factura sursa aleasa) — dar cand utilizatorul alege mai multe facturi
sursa deodata (selectie multipla, suportata explicit de dialog), acest link iti da **multimea** de
facturi posibile, nu factura exacta a liniei. Pentru N.2 (`But_retur`, per articol) nu exista niciun
document nou scris separat — returul e o linie in factura normala curenta, iar "sursa" e verificata
doar la nivel de stoc (`RUL`/`STOC` filtrat pe `COD` legat de `VANZARI.ID_VANZARE`), nu persista ca
atribut al liniei noi. Concluzie pentru S4f: **se livreaza fara coloana de provenienta pe linie**;
cel mult se poate afisa, cand e un singur document sursa, "factura de retur X provine din factura Y"
la nivel de document (din `VANZARI_CORESP`), nu per linie.
## 1. Ce face Oracle cu `poDate.listaid`
Doua cai, in functie de mecanism:
**N.1 (document, tip 8/9/24)**: `poDate.listaid` = CSV de `id_vanzare` (facturile alese la pasul de
cautare multipla, `ofacturare.vc2:9200`: `poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")`).
Ajunge la Oracle ca parametru `V_LISTAID` in `pack_facturare.cursor_retur(V_IN_VALUTA, V_LISTAID,
V_ID_UTIL, V_CURSOR)` (`ff_...sql:3934-3947`), care e doar un wrapper peste
`cursor_retur_document(V_IN_VALUTA, V_LISTAID, V_COPIERE=0, V_PROFORMA, V_ID_UTIL, V_CURSOR)`
(`:3949-4062`). Acolo `V_LISTAID` devine CTE-ul `CRS` (`:3962-3964`):
```sql
WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR)))
```
folosit doar ca filtru: `WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)`
(`:4054-4055`). **Filtru, nu persistare** — dupa acest `WHERE`, `ID_VANZARE` nu mai apare deloc in
lista de coloane a `SELECT`-ului extern (`:3965-4028`: `ID_C` (=`ROWNUM`, sintetic), `ID_ARTICOL`,
`LOT`, `SERIE`, `ID_POL`, preturi, `GESTIONABIL`, `CANTITATE`, `ID_GESTIUNE`, `PRET_ACHIZITIE`... dar
nu `ID_VANZARE` si nu `ID_VANZARE_DET`). Cursorul primit de VFP in `crsarticole` nu are, deci, nicio
coloana care sa spuna din ce factura/linie vine randul.
Separat, la scrierea efectiva a documentului de retur, `scrie_corespondente_vanzari(3)`
(`:14834-14836`, apelata din `finalizeaza_factura`) foloseste tot `pack_facturare.clistaid`
(= acelasi `poDate.listaid`, tinut in variabila de sesiune a pachetului) ca sa scrie in
`VANZARI_CORESP` (`:15481-15516`):
```sql
INSERT INTO VANZARI_CORESP (ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)
SELECT pack_facturare.nid_vanzare, ID_VANZARE, 3
FROM VANZARI WHERE ID_VANZARE IN (SELECT X FROM table(charn2collection(V_LISTAID,',')));
```
Asta scrie **cate un rand per factura sursa aleasa** (nu per linie), cu noul document de retur ca
`ID_VANZARE_FACT` si fiecare sursa ca `ID_VANZARE_AVIZ` (numele coloanei e generic, reutilizat si
pentru perechi aviz-factura, `TIP=1/2`, vezi `sterge_factura:5452-5457,5582-5585`).
**N.2 (`But_retur`, per articol)**: `poDate.listaid` = `"ID_ARTICOL:ID_VANZARE"` (una sau mai multe
perechi CSV, `ofacturare.vc2:12888`: `thisform.cListaIdArticoleRetur = ... + STR(poArticol.id_articol) + ':' + poDate.listaid`,
trimis la Oracle la `:13978`). In `pack_facturare` (verificat in ramura `WHEN V_CANTE < 0 and
pack_facturare.clistaid is not null and instr(pack_facturare.clistaid, ':') > 0`,
`ff_...sql:8142-8212`) e folosit ca filtru pentru calculul cantitatii disponibile de retur din
rulaj (`RUL`), NU ca sa scrie o legatura:
```sql
AND A.COD IN (SELECT COD FROM VANZARI WHERE ID_VANZARE IN
(SELECT id_vanzare FROM (SELECT CAST(GETWORDNUM(id_articol_id_vanzare,1,':') AS NUMBER(20,0)) id_articol,
CAST(GETWORDNUM(id_articol_id_vanzare,2,':') AS NUMBER(20,0)) id_vanzare
FROM (SELECT x AS id_articol_id_vanzare FROM table(cast(CHARC2COLLECTION(pack_facturare.clistaid,',') AS char_tab))))
WHERE id_articol = V_ID_ARTICOL))
```
Foloseste `listaid` doar ca sa restranga `RUL` la miscarile de stoc (`A.COD`) legate de factura
aleasa, ca sa calculeze **cantitatea inca disponibila in gestiune din acea vanzare** pentru articolul
respectiv (`tab_stoc`, tip=1 in acel `SELECT`). Nu se scrie nicio linie noua de legatura in vreun
tabel — e folosit exclusiv ca filtru intr-un calcul de disponibil.
## 2. Coloana de provenienta pe `VANZARI_DETALII`
Nu exista. DDL-ul complet nu e disponibil in acest repo (nu exista `CREATE TABLE VANZARI_DETALII` in
`docs/`; confirmat deja de cercetari anterioare — `docs/cercetare/discount_verificare2.md:104-111`,
`COMUN\docs\cercetare\rec_tva_vanzari.md:19`), dar structura efectiva a fost verificata pe schema live in
`docs/cercetare/rec_cale_vanzari_detalii.md:157-169` (interogare `all_tab_columns`, 08.08.2026):
35 de coloane, PK `ID_VANZARE_DET` (generat prin trigger `TRG_VANZARI_DET_BEFOINS` din
`SEQ_VANZARI_DETALII`), FK logic `ID_VANZARE`. Lista coloanelor relevante citata acolo: `ID_ARTICOL`,
`PRET`, `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`,
`ID_VALUTA`, `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`, `TAXCODE`, `LOT`, `STERS`, `VALIDAT`,
`DATAORA_VALID`, `ID_UTIL_VALID`, `ID_UTILS`, `DATAORAS`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT`,
`PRETD`, `ID_VALUTAD`, `PRETV_ORIG`, `ID_CTR`, `ID_RATA`. **Nicio coloana** de tipul
`ID_VANZARE_SURSA`, `ID_DETALIU_SURSA`, `ID_FACT_SURSA`, `ID_VANZARE_RETUR`, `ID_ORIGINAL` sau orice
self-referinta catre o alta linie `VANZARI_DETALII`. Cautat explicit acele siruri in tot exportul
`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (~28000 linii) si in `COMUN\docs\` — zero potriviri
(vezi comanda de mai jos, sectiunea "Ramas de verificat").
Confirmarea independenta finala si cea mai directa: INSERT-ul care trece liniile din
`VANZARI_DETALII_TEMP` in `VANZARI_DETALII` la finalizarea oricarui document (inclusiv retur, pentru
ca `scrie_in_vanzari` e comun tuturor tipurilor) are lista de coloane explicita
(`rec_cale_vanzari_detalii.md:105-113`):
```sql
INSERT /*+ APPEND */ INTO VANZARI_DETALII
(ID_VANZARE, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, ID_JTVA_COLOANA)
SELECT pack_facturare.nid_vanzare, ID_ARTICOL, ... FROM VANZARI_DETALII_TEMP WHERE ...
```
Nicio coloana sursa in lista. Cum `VANZARI_DETALII_TEMP` insasi e populata pentru retur din
`cursor_retur_document` (care, cf. punctul 1, nu aduce `ID_VANZARE`/`ID_VANZARE_DET` sursa), legatura
e pierduta cu doi pasi inainte de a ajunge la acest INSERT — nu doar "nu se scrie", ci "nu mai exista
in date la momentul scrierii".
## 3. Tabel separat de legatura
**Da, exista, dar la nivel de document, nu de linie**: `VANZARI_CORESP(ID_VANZARE_FACT,
ID_VANZARE_AVIZ, TIP, STERS)`. Folosit pentru mai multe perechi de corespondenta, disambiguizate prin
`TIP`:
- `TIP=1`: factura scrisa dintr-un aviz (`scrie_corespondente_vanzari(1)`, la facturare din aviz,
`:14826`);
- `TIP=2`: aviz de retur (`scrie_corespondente_vanzari(2)`, `:14830`, la `ntip=24`);
- `TIP=3`: **factura de retur** (`scrie_corespondente_vanzari(3)`, `:14834-14836`, la `ntip in (8,9)`)
— exact cazul cerut de S4f.
Verificarile de stergere din `sterge_factura` (`:5450-5494`) confirma semantica: interogheaza
`VANZARI_CORESP WHERE ID_VANZARE_AVIZ = V_ID_VANZARE AND TIP = 3` pentru "exista facturi de retur pe
aceasta factura?" — deci pentru o factura normala, `ID_VANZARE_AVIZ` (nume generic, refolosit) e ea
insasi, iar `ID_VANZARE_FACT` gasit prin acea interogare e factura(le) de retur emise pe baza ei.
Invers, pentru un document de retur dat, `SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP WHERE
ID_VANZARE_FACT = :id_retur AND TIP = 3` da **toate** facturile sursa alese la emiterea acelui retur
(poate fi mai multe, cf. selectie multipla din `caut_facturi_multiple_client`,
`docs/cercetare/factura_retur_document.md:67-85`).
**Limita exacta**: cand pe un document de retur exista o singura factura sursa in `VANZARI_CORESP`,
"factura originala" e determinata fara ambiguitate pentru **toate** liniile documentului de retur
(pentru ca nu exista alt candidat). Cand exista mai multe (utilizatorul a bifat 2+ facturi la
cautare), `VANZARI_CORESP` da multimea, dar nu se poate spune care linie de retur vine din care
factura din multime — informatia care ar face diferenta (ID_VANZARE per linie in cursorul de
populare) a fost deja aruncata la pasul 1/2. Nu exista niciun tabel `VANZARI_RETUR`, `RETUR_DETALII`
sau `LEGATURI_DOCUMENTE`; cautate explicit in `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si in
`COMUN\docs\` — zero potriviri.
`DOCUMENTE.AVIZE` (coloana denormalizata pe `VANZARI`, nu tabel separat) e completata redundant tot
din `VANZARI_CORESP` (`scrie_corespondente_vanzari:15505-15514`, `UPDATE VANZARI SET AVIZE = ...`) —
tine text afisabil (serie+numar avize), nu un id structurat, si nu e populata pentru `TIP=3` (doar
pentru avize, vezi apelul unic la acel `UPDATE` in corpul procedurii, comun tuturor `V_TIP`-urilor
dar cu sens practic doar pentru avize-spre-factura).
## 4. Cum calculeaza serverul maximul returnabil
**Raspunsul e diferit pe cele doua mecanisme, si niciunul nu se sprijina pe o legatura persistata
linie-la-linie:**
**N.1 (document)**: NU exista un calcul de "cat s-a mai returnat deja". Coloana pe care utilizatorul
o vede ca "Cant. max. de returnat" (`ofacturare.vc2:15238,15243` — doar schimbare de caption pe capul
de coloana, nu logica noua) e pur si simplu `CANTITATE` a liniei originale, asa cum vine din
`cursor_retur_document` (`A1.CANTITATE`, `:4036`, filtrat doar pe `A1.STERS = 0`, fara nicio
agregare cu alte retururi anterioare pe aceeasi linie). Validarea din
`do_verifica_articol` (`:14743-14754`) nu face niciun calcul suplimentar de disponibil — blocheaza
doar cazul `tnCantitate >= 0` cand esti in mod retur (semnul gresit), nu o depasire de maxim istoric.
**Consecinta directa**: daca acelasi utilizator emite doua facturi de retur separate pe aceeasi
factura sursa, a doua interogare `cursor_retur_document` va aduce din nou linia originala cu
`CANTITATE` **intreaga**, neredusa de primul retur — nimic in cod nu scade sau marcheaza cat s-a
returnat deja la acest nivel. Acesta e cel mai clar indiciu ca legatura linie-la-linie *nu* e tinuta
nicaieri pentru N.1: daca ar fi fost tinuta, ar fi trebuit folosita exact aici, ca sa capeze
cantitatea, si nu e.
**N.2 (`But_retur`)**: exista un calcul real de disponibil, dar e un calcul de **stoc/rulaj**, nu de
"cat s-a returnat pe acea linie". Ramura din `pack_facturare` citata la punctul 1
(`ff_...sql:8142-8212`) calculeaza cantitatea inca prezenta in `RUL` (miscari de stoc) care a venit
din vanzarea aleasa (`RUL.COD` legat prin `VANZARI.COD`, filtrat pe `id_articol:id_vanzare` din
`listaid`), plus `STOC`/`RUL_TEMP` pentru cazul general. E un calcul valid de "poti scoate din
gestiune atat cat inca exista acolo cu provenienta asta", sprijinit pe legatura persistata
`RUL.COD -> VANZARI.COD` (asta e adevarata "legatura care exista" pentru N.2) — dar e o legatura la
nivelul miscarii de stoc, nu un contor "cantitate returnata" pe `VANZARI_DETALII`, si nu se
translateaza in nicio coloana de provenienta pe linia noua scrisa.
## 5. Ce se poate afisa efectiv
- **Numarul/seria facturii originale la nivel de document de retur, cand exista o singura factura
sursa**: SE POATE, din `VANZARI_CORESP` (`TIP=3`) join `VANZARI` pe `ID_VANZARE_AVIZ`. Interogare
de rulat pe baza vie (nu verificata aici, doar formulata):
```sql
SELECT v.serie_act, v.numar_act
FROM VANZARI_CORESP c JOIN VANZARI v ON v.ID_VANZARE = c.ID_VANZARE_AVIZ
WHERE c.ID_VANZARE_FACT = :id_document_retur AND c.TIP = 3 AND c.STERS = 0;
```
Daca randul e unic, se poate afisa "provine din factura X" **pe capul documentului de retur**
(nu pe linie — toate liniile ar arata aceeasi sursa, pentru ca e singura posibila).
- **Cand documentul de retur are 2+ facturi sursa** (selectie multipla permisa de dialog): se poate
afisa lista de facturi sursa posibile (tot din `VANZARI_CORESP`), dar NU care linie vine din care
factura din lista — informatia nu exista.
- **Numarul facturii originale pe fiecare linie individuala de retur**: NU SE POATE, in niciun caz —
nici cand exista un singur document sursa (pentru ca "linie cu linie" nu inseamna nimic diferit de
"documentul cu documentul" atunci, dar afirmatia stricta "aceasta linie de retur vine din linia Y a
facturii" nu poate fi demonstrata din date, doar presupusa cand exista un singur candidat), si sigur
nu cand exista mai multe facturi sursa sau cand linia e libera (permisa explicit de decizia 22).
- Pentru N.2 (retur in factura normala, tip 1/5/7/10): nu exista deloc "document de retur" separat de
arata provenienta — linia de retur e o linie normala (cu cantitate negativa) in factura curenta;
singura urma a sursei e `RUL.COD`/`VANZARI.COD` folosita tranzitoriu la calculul de disponibil, nu
persistata pe linia noua din `VANZARI_DETALII`.
## Verdict pentru plan (S4f)
**NU se persista legatura linie-la-linie.** Pentru documentul de retur (N.1), se poate afisa
"factura sursa" **la nivel de document**, din `VANZARI_CORESP` (TIP=3), **doar cand exista exact o
singura factura sursa aleasa** — cazul cu selectie multipla da doar multimea, fara atribuire per
linie. Pe linia individuala de retur nu se poate afisa nimic verificabil din date, in niciun caz.
Recomandare pentru S4f: se livreaza fara coloana de provenienta pe linie (cf. deciziei deja scrise in
plan, "nu se inventeaza"); daca se vrea un gest minim, singura afisare sustenabila cu date e un text
de tip "Retur pentru factura: X" pe **capul** documentului (deja exista, cf.
`factura_retur_document.md:85`: `poDate.text_aditional = ... + poDate.descriere`, unde
`poDate.descriere` e lista serie+numar a facturilor alese) — nu pe linie, si nu nou, e deja acolo.
## Ramas de verificat pe baza vie
- Nu s-a interogat live daca `VANZARI_CORESP.TIP=3` e scris consecvent pentru fiecare factura de
retur emisa istoric (posibil sa existe date vechi scrise inainte ca aceasta ramura sa existe, sau
prin alt cod neexaminat aici) — de rulat:
```sql
SELECT COUNT(*) FROM VANZARI v
WHERE v.TIP IN (8,9) AND v.STERS = 0
AND NOT EXISTS (SELECT 1 FROM VANZARI_CORESP c WHERE c.ID_VANZARE_FACT = v.ID_VANZARE AND c.TIP = 3);
```
Daca da >0, exista facturi de retur fara nicio legatura de document persistata, nici macar cea
partiala descrisa mai sus.
- Nu s-a verificat live cate din facturile de retur existente au >1 factura sursa in
`VANZARI_CORESP` (adica cate din documentele reale ar cadea in cazul "multime, nu atribuire") —
utila pentru a decide cat de des s-ar aplica de fapt afisarea propusa la punctul 5.
- Nu s-a gasit (si nici nu era in scop) un mecanism separat pentru avize de retur custodie (`tip=50`,
marcat "in lucru" in `tipuri_documente_facturare.md`) — daca S4f ajunge sa acopere si acel tip, de
recercetat separat.
- Subagentul de research auxiliar lansat pentru confirmarea independenta a definitiei
`caut_facturi_multiple_client`/`caut_facturi_multiple_client_articol` nu a returnat un raport
utilizabil (rezultat gol la finalizare); definitiile au fost gasite si citite direct in aceasta
sesiune, in `COMUN\programe\oproceduri_facturare.prg:2091-2160`, deci nu blocheaza verdictul, dar
nu a adaugat nimic peste ce e deja in acest raport.

View File

@@ -1,182 +0,0 @@
# Linii de comanda cu articol nemembru al politicii de pret — verificare facturare
Status: FINALIZAT.
## Verdict
**DA — s-a facturat cel putin o linie fara ca FACT-024 sa se declanseze.** Comanda `497`, linia
`4294507522` (VOUCHER DISCOUNT) / politica `7` (DISCOUNT), a fost facturata cu succes in factura
`ID_VANZARE=1028` (serie `SSS`, numar `535`, `FACTURAT=1`, `DATA_FACTURAT=2026-03-20`), desi
articolul **nu e membru** al politicii 7 in `CRM_POLITICI_PRET_ART` la data acestei cercetari
(10.08.2026). **Decizia 32 se REDESCHIDE partial**: nu pentru ca ar exista o cale de cod care ocoleste
verificarea (codul chiar cheama `contabilizeaza_articol` pentru acest tip de vanzare, cf. punctul 3),
ci pentru ca datele arata ca membru-ul politicii **s-a schimbat dupa facturare** — vezi punctul 4.
Pentru celelalte 34 de linii cu acelasi articol/politica (35 in total cu `CANTITATE<0`), **nu exista
nicio factura, nici macar stearsa** — la ele fisura ramane doar o stare inconsistenta nefacturata
(vezi punctul 1).
Observatie structurala separata de cea de mai sus: **inca 2 linii** (comenzile 114 si 115, alt articol,
alta politica) au `CANTITATE>0`, adica **nefacturate inca deloc** — pentru acestea afirmatia "ar trebui
sa cada la facturare" e neverificata pentru ca nu au fost niciodata trecute prin `contabilizeaza_articol`.
## 1. S-a facturat vreuna din ele fara eroare?
Interogare: pentru fiecare din cele 37 linii `COMENZI_ELEMENTE` cu `ID_POL NOT NULL` si articol
nemembru in `CRM_POLITICI_PRET_ART`, am cautat linii `VANZARI_DETALII` (prin `VANZARI.ID_COMANDA`)
cu acelasi `ID_ARTICOL`+`ID_POL`, **inclusiv cele sterse** (ca sa nu ratez o factura anulata):
```sql
select ce.id_comanda_element, ce.id_comanda, ce.id_articol, ce.id_pol, ce.cantitate,
(select count(*) from vanzari v join vanzari_detalii vd on vd.id_vanzare=v.id_vanzare
where v.id_comanda = ce.id_comanda and vd.id_articol=ce.id_articol and vd.id_pol=ce.id_pol) as nr_total_incl_sters
from comenzi_elemente ce
where ce.id_pol is not null and ce.cantitate < 0
and not exists (select 1 from crm_politici_pret_art cppa
where cppa.id_pol = ce.id_pol and cppa.id_articol = ce.id_articol);
```
Rezultat: **doar comanda 497** are potriviri (2 linii `VANZARI_DETALII`, id 1494 si 1495, ambele
`STERS=0`, `CANTITATE=-1` fiecare, apartinand facturii `ID_VANZARE=1028`). Toate celelalte 34 de
comenzi (349, 351, 352, 359, 360, 377, 388, 404, 412, 417, 429, 443, 447, 450, 451, 453, 455, 456,
457, 466, 471, 475, 486, 500, 501) au **0 potriviri**, inclusiv sters.
**Interpretare structurala a mecanismului** (cititul codului, nu presupunere): singurul loc care
insereaza randuri cu `CANTITATE<0` in `COMENZI_ELEMENTE` e procedura `inchide_comanda`
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:5769-5820`):
```sql
INSERT INTO COMENZI_ELEMENTE (... CANTITATE ...)
SELECT ..., NVL(C.CANTITATE, 0) + NVL(B.CANTITATE, 0) - A.CANTITATE AS CANTITATE, ...
FROM COMENZI_ELEMENTE A
LEFT JOIN (... FROM VANZARI_DETALII_TEMP ...) B ON ... -- lotul curent de facturare
LEFT JOIN (... FROM VANZARI JOIN VANZARI_DETALII ...) C ON ... -- deja facturat anterior
WHERE A.ID_COMANDA = :comanda
AND SIGN(A.CANTITATE) * A.CANTITATE >
SIGN(A.CANTITATE) * (NVL(C.CANTITATE, 0) + NVL(B.CANTITATE, 0));
```
Asta insemna ca randul negativ **nu dovedeste singur ca linia a fost facturata** — el reprezinta
*diferenta ramasa* fata de cantitatea comandata, la momentul **inchiderii** comenzii, chiar daca
`B` (lotul curent) si `C` (facturat anterior) sunt amandoua 0. O comanda inchisa fara sa i se
factureze o anumita linie tot primeste un rand `CANTITATE = -A.CANTITATE`. De-asta 34 din 35 de
linii "negative" nu au nicio factura in spate: comenzile respective au fost **inchise** (probabil
manual, ca "restanta anulata"), nu facturate pe acea linie. **Zero cazuri in date pentru acestea nu
demonstreaza ca nu se poate factura — demonstreaza doar ca nu s-a intamplat.**
Pentru comanda 497 insa, potrivirea cu 2 linii reale, active, `FACTURAT=1` in `VANZARI_DETALII`
e dovada directa (nu inferenta) ca acea combinatie articol/politica **a ajuns** intr-o factura emisa.
## 2. Reconfirmarea cifrei si lista liniilor
Cifra **37** se reconfirma exact (nu 6868/nemembru cum sugera masuratoarea anterioara — aceea era
alt numitor; numitorul corect pentru linii cu `ID_POL NOT NULL` e **7108**, nu 6868):
```sql
select count(*) from comenzi_elemente ce where ce.id_pol is not null; -- 7108
select count(*) from comenzi_elemente ce where ce.id_pol is not null
and not exists (select 1 from crm_politici_pret_art cppa
where cppa.id_pol = ce.id_pol and cppa.id_articol = ce.id_articol); -- 37
```
Compozitie (verificata, `select sign(cantitate), count(*) ... group by sign(cantitate)`):
- **35 linii** cu `CANTITATE=-1`: acelasi articol/politica in toate — `ID_ARTICOL=4294507522`
("VOUCHER DISCOUNT"), `ID_POL=7` ("DISCOUNT"). Comenzi: 349(x2), 351, 352(x2), 359, 360(x2), 377,
388, 404, 412(x2), 417, 429, 443, 447, 450, 451, 453, 455, 456, 457, 466, 471(x2), 475(x2),
486(x2), 497(x2), 500, 501(x2).
- **2 linii** cu `CANTITATE=+10` (nefacturate inca, nicio urma de consum):
- comanda 114, `ID_ARTICOL=4294507508` ("CAFEA TEST - PRODUS TEST PENTRU IMPORT WEB"), `ID_POL=2`
("LISTA 2"), data comanda 2025-09-10, `COMENZI.STERS` neverificat suplimentar dar randul e
prezent activ.
- comanda 115, `ID_ARTICOL=4171144217` ("CAFEA 1"), `ID_POL=2`. **Anomalie separata**: `COMENZI`
nu contine niciun rand cu `ID_COMANDA=115` — antetul comenzii lipseste, doar linia de element a
supravietuit. NEDETERMINABIL DIN DATE de ce (nu exista audit/istoric pe `COMENZI`); posibil
date de test/import, dat fiind ca articolul insusi e "CAFEA TEST ... PENTRU IMPORT WEB" pe
cealalta linie similara.
Toate cele 37 de linii au `COMENZI_ELEMENTE.STERS=0` (nicio linie de comanda marcata stearsa).
Stare de facturare per linie: singura cu urma reala de facturare e comanda 497 (vezi punctul 1);
restul de 36 sunt **nefacturate** pe aceasta combinatie articol/politica.
## 3. Verificare cod FACT-024 (`contabilizeaza_articol`)
Blocul citat in brief (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7278-7302`) e identic cu ce ruleaza
in productie — verificat direct in codul compilat, nu doar in scriptul istoric (fisierul `.sql` e un
istoric cumulativ cu **doua definitii** ale `contabilizeaza_articol` in text, la linia 746 si la
7173; doar a doua e cea vie):
```sql
select owner, line from all_source
where name='PACK_FACTURARE' and type='PACKAGE BODY'
and text like '%FACT-024%';
```
confirma acelasi text de eroare in schema `MARIUSM_AUTO` (Dev).
Verificarea foloseste view-ul `VCRM_POLITICI_PRET_ART`:
```sql
SELECT COMPUS, ID_POL_ART INTO ... FROM VCRM_POLITICI_PRET_ART
WHERE ID_ARTICOL = detalii_articol.id_articol AND ID_POL = detalii_articol.id_pol;
EXCEPTION WHEN NO_DATA_FOUND THEN ... RAISE_APPLICATION_ERROR(-20000, ... '(FACT-024)');
```
Am verificat textul view-ului (`user_views`): `VCRM_POLITICI_PRET_ART` are `CRM_POLITICI_PRET_ART PA`
ca tabel-sursa (driving table) al tuturor `LEFT JOIN`-urilor, fara niciun `WHERE` care sa filtreze
`PA` (nu exista coloana `STERS` pe `CRM_POLITICI_PRET_ART`). Deci view-ul are exact acelasi set de
perechi `(ID_POL, ID_ARTICOL)` ca tabelul de baza — verificarea mea prin `NOT EXISTS` pe
`CRM_POLITICI_PRET_ART` e echivalenta cu ce evalueaza codul. **Concluzie: pentru toate cele 37 de
linii, `SELECT ... INTO` ar da `NO_DATA_FOUND` -> `FACT-024`, daca ar trece prin
`contabilizeaza_articol` ACUM, cu starea curenta a `CRM_POLITICI_PRET_ART`.**
Apelul e neconditionat pentru facturi standard: am cautat toate apelurile
`pack_facturare.contabilizeaza_articol(tab_detalii(i))` in codul **compilat** (`all_source`, schema
`MARIUSM_AUTO`) — 6 aparitii, linii 4877, 4901, 5594, 5618, 5876, 5900. Linia 4901 e in procedura
`scrie_factura2` (facturarea principala), in ramura `ELSE` a unui `CASE pack_facturare.ntip` care
exclude doar transferurile intre subunitati (23,25,30,41), avizele de custodie (42,47) si facturile
cu rate (2,6,52 cu `id_rata<>0`) — **tip 3 (tipul facturii 1028) cade in ELSE, deci
`contabilizeaza_articol` chiar s-a executat pentru acea linie.**
Prin urmare, pentru comanda 497: fie (a) la data facturarii (20.03.2026) articolul 4294507522 CHIAR
era membru al politicii 7 si a fost scos ulterior din `CRM_POLITICI_PRET_ART`, fie (b) verificarea a
fost ocolita altfel. Nu am gasit dovada de tip (b) — vezi punctul 4.
## 4. Cauza
`CRM_POLITICI_PRET_ART` **nu are coloana `STERS`** si nu exista niciun tabel de istoric/audit pentru
ea (`select table_name from user_tables where table_name like '%POLITICI%' or ... '%AUDIT%'` — doar
tabelele de configurare curenta, niciunul de istoric). Stergerile din aceasta tabela sunt deci
**fizice, fara urma**. Nu pot reconstitui direct daca perechea (politica 7, articol 4294507522) a
existat la 20.03.2026.
Coloanele de audit disponibile pe randurile facturii/comenzii **nu arata nicio editare ulterioara**:
`VANZARI.ID_UTILS`/`DATAORAS` si `VANZARI_DETALII.ID_UTILS`/`DATAORAS` (1494, 1495) sunt toate NULL —
niciun `UPDATE` standard nu a atins aceste randuri dupa creare. Deci nu exista dovada ca linia de
factura ar fi fost modificata ulterior prin fluxul nou de "editare factura emisa" (adaugat recent in
proiect, cf. changelog 2.11.15) — desi asta nu exclude o editare care ar fi ocolit acele coloane de
audit, doar ca nu am gasit dovada ei.
Indiciu indirect, dar nu concludent: articolul 4294507522 e denumit **"VOUCHER DISCOUNT"**, iar
politica 7 e **"DISCOUNT"** — o pereche generica folosita ca linie de discount pe **35 de comenzi
diferite**, nu un caz izolat. Reutilizarea masiva a exact aceleiasi perechi sustine ipoteza unei
**modificari de catalog** (articolul-voucher scos ulterior din lista politicii DISCOUNT ca decizie de
business/curatare), mai degraba decat 35 de erori de introducere independente. **Aceasta ramane insa
o inferenta din tipar, nu o dovada directa — se marcheaza NEDETERMINABIL DIN DATE strict pe cauza si
data exacta a schimbarii.**
## Ce am incercat si ce a esuat
- Doua incercari de a delega cautarea codului sursa VFP/PL-SQL unui subagent `Explore` au esuat cu
raspunsuri goale/neinformative ("Nimic nou.", "No new input to act on.") — fara nicio dovada ca ar
fi rulat vreo cautare. Am renuntat la delegare si am facut cautarile direct cu Grep si Read.
- Interogarea `all_source` fara `owner=` a intors linii duplicate/interclasate (schema `ACN` are de
asemenea un `PACK_FACTURARE`) — a trebuit filtrat explicit pe `owner='MARIUSM_AUTO'` pentru
rezultate coerente.
- Un `prompt '---text---'` intre doua instructiuni SQL in acelasi fisier `.sql` a produs iesire
amestecata (headerul s-a lipit de urmatoarea instructiune) — am renuntat la `prompt` si am rulat
interogarile in fisiere separate.
## Date tehnice folosite
- Conexiune: `MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL` (Dev), via
`D:\ROA\instantclient_19_18\sqlplus.exe`, fisiere `.sql` ASCII in scratchpad, niciun `INSERT`/`UPDATE`/`DDL` rulat.
- Cod verificat: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(text istoric cumulativ) si pachetul compilat `MARIUSM_AUTO.PACK_FACTURARE` (`all_source`, sursa de
adevar pentru ce ruleaza efectiv).

View File

@@ -1,124 +0,0 @@
# Cercetare: `pack_facturare.modifica_date_factura` — parametri, mapare pe coloane si pe controale
Sursa pachetului: `D:\ROA\ROAACNPRO\PACK_FACTURARE.pck` (declaratie spec `:921-935`, corp `:14392-14462`).
Apel VFP: `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_modifica` (`:4538-4637`),
apelul efectiv la `:4599-4614`. Formular de editare: `frm_modifica_factura` (`:5262-5774`).
Nu exista alt fisier `.pck`/`.sql` cu semnatura diferita pentru `modifica_date_factura` in
`D:\ROA\ROAFACTURARE` sau `COMUN`; niciun alt loc din VFP (in afara de `ofacturare_comun.vc2` si
copia identica `.bak`) nu apeleaza procedura — cautat cu grep pe `*.vc2/*.sc2/*.prg` in tot arborele.
## 1-2. Tabel parametri (semnatura + destinatie in Oracle)
| # | Parametru | Tip (`.pck:921-935`) | Coloana / tabel scrise in corp (`.pck:14392-14462`) | Observatii |
|---|---|---|---|---|
| 1 | `V_ID_VANZARE` | `NUMBER` | Folosit doar in `WHERE ID_VANZARE = V_ID_VANZARE` (`:14424`) si ca sursa pentru `lnIdFact` (`:14426`) | Identitate randului, niciodata scris |
| 2 | `V_ID_RUTA` | `NUMBER` | `VANZARI.ID_RUTA` (`:14414`) | Scris neconditionat |
| 3 | `V_ID_DELEGAT` | `NUMBER` | `VANZARI.ID_DELEGAT` (`:14415`) | Scris neconditionat |
| 4 | `V_ID_AGENT` | `NUMBER` | `VANZARI.ID_AGENT` (`:14416`) | Scris neconditionat |
| 5 | `V_ID_MASINA` | `NUMBER` | `VANZARI.ID_MASINA` (`:14417`) | Scris neconditionat |
| 6 | `V_DATAORA_EXP` | `DATE` | `VANZARI.DATAORA_EXP` (`:14418`) | Scris neconditionat |
| 7 | `V_ID_FACTURARE` | `VANZARI.ID_FACTURARE%TYPE` | `VANZARI.ID_FACTURARE` (`:14419`) | Scris neconditionat |
| 8 | `V_LISTARE_DETALIATA` | `VANZARI.LISTARE_DETALIATA%TYPE` | `VANZARI.LISTARE_DETALIATA = NVL(V_LISTARE_DETALIATA, 0)` (`:14420`) | NVL la 0 |
| 9 | `V_TEXT_ADITIONAL` | `VANZARI.TEXT_ADITIONAL%TYPE` | `VANZARI.TEXT_ADITIONAL` (`:14421`) | Scris neconditionat, fara NVL |
| 10 | `V_TIP_SAFT` | `VANZARI.TIP_SAFT%TYPE DEFAULT NULL` | `VANZARI.TIP_SAFT` (`:14422`) | Scris neconditionat |
| 11 | `V_EFACTURA` | `VANZARI.EFACTURA%TYPE DEFAULT NULL` | `VANZARI.EFACTURA` (`:14423`) | Scris neconditionat |
| 12 | `V_DATA_ACT` | `VANZARI.DATA_ACT%TYPE DEFAULT NULL` | `VANZARI.DATA_ACT` (`:14448`) + `DOCUMENTE.DATAACT`, `ACT.DATAACT`, `IREG_PARTENERI.DATAACT`, `JV2007.DATAACT`, `RUL.DATAACT` si `RUL.DATAOUT` (`:14449-14453`), toate `WHERE ID_FACT = lnIdFact` | Doar daca `V_DATA_ACT IS NOT NULL AND V_DATA_ACT <> NVL(ldDataAct, SYSDATE)` (`:14447`) |
| 13 | `V_DATA_SCAD` | `VANZARI.DATA_SCAD%TYPE DEFAULT NULL` | `VANZARI.DATA_SCAD` (`:14457`) + `ACT.DATASCAD`, `IREG_PARTENERI.DATASCAD` (`:14458-14459`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_DATA_SCAD IS NOT NULL AND V_DATA_SCAD <> NVL(ldDataScad, sysdate)` (`:14456`) |
| 14 | `V_NUMAR_ACT` | `VANZARI.NUMAR_ACT%TYPE DEFAULT NULL` | `VANZARI.NUMAR_ACT` (`:14439`) + `DOCUMENTE.NRACT`, `ACT.NRACT`, `IREG_PARTENERI.NRACT`, `JV2007.NRACT`, `RUL.NRACT` (`:14440-14444`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_NUMAR_ACT IS NOT NULL AND V_NUMAR_ACT <> NVL(lnNrAct, 0)` (`:14438`) |
| 15 | `V_SERIE_ACT` | `VANZARI.SERIE_ACT%TYPE DEFAULT NULL` | `VANZARI.SERIE_ACT` (`:14429`) + `DOCUMENTE.SERIE_ACT`, `ACT.SERIE_ACT`, `IREG_PARTENERI.SERIE_ACT`, `JV2007.SERIE_ACT`, `RUL.SERIE_ACT` (`:14430-14434`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_SERIE_ACT IS NOT NULL AND V_SERIE_ACT <> NVL(lcSerieAct, '')` (`:14428`) |
Parametrii 2-11 se scriu neconditionat in `VANZARI` la fiecare apel (chiar cu `NULL`, daca asa vine
argumentul). Parametrii 12-15 (`data_act`, `data_scad`, `numar_act`, `serie_act`) au tratament
conditionat: se propaga si in `DOCUMENTE`/`ACT`/`IREG_PARTENERI`/`JV2007`/`RUL` (unde e cazul),
filtrate dupa `ID_FACT` (nu dupa `ID_VANZARE`) — deci ating toate randurile din acele tabele legate
de acelasi document, nu doar randul `VANZARI` curent. Se scriu doar daca valoarea trimisa e
`NOT NULL` si difera de valoarea curenta.
## 3. Apelul din VFP
`COMUN\clase\ofacturare_comun.vc2:4599-4614`, in interiorul unui `SCAN` peste `crsfacturi` (una sau
mai multe inregistrari alese):
```
begin pack_facturare.modifica_date_factura(<<Alltrim(Str(poRec.id_vanzare))>>,
<<Alltrim(Nvl(Str(poRec.id_ruta),[NULL]))>>,
<<Alltrim(Nvl(Str(poRec.id_delegat),[NULL]))>>,
<<Alltrim(Nvl(Str(poRec.id_agent),[NULL]))>>,
<<Alltrim(Nvl(Str(poRec.id_masina),[NULL]))>>,
to_date('<<TTOC(poRec.dataora_exp,1)>>','YYYYMMDDHH24:MI:SS'),
<<Alltrim(Nvl(Str(poRec.id_facturare),[NULL]))>>,
<<ALLTRIM(STR(NVL(poRec.listare_detaliata,0)))>>,
?poRec.text_aditional,
?poRec.tip_saft,
?poRec.efactura,
?poRec.data_act,
?poRec.data_scad,
?poRec.numar_act,
?poRec.serie_act);
end;
```
Toate argumentele vin din structura `poRec`, populata printr-un `Scatter Name poRec Memo` din
cursorul `crsfacturi` la intrarea in `do_modifica` (`:4538-4581`), apoi editata prin
`frm_modifica_factura` cat timp e afisat (`:4588-4590`), inainte de executia `SCAN`-ului.
Observatie de comportament: cand sunt alese mai multe inregistrari (`lnNrInreg > 1`), ramura
`Otherwise` (`:4565-4580`) face `Scatter Name poRec Memo Blank` si reseteaza explicit
`id_ruta`, `id_delegat`, `id_agent`, `id_masina`, `dataora_exp`, `id_facturare`,
`listare_detaliata`, `tip_saft`, `text_aditional`, `efactura` la valori goale/`NULL`/`0` —
campurile `data_act`/`data_scad`/`numar_act`/`serie_act` raman goale prin acelasi `NVL`. Daca
formularul nu le modifica explicit, `SCAN`-ul trimite aceste valori goale la **fiecare** inregistrare
aleasa (parametrii 2-11 se scriu neconditionat in corpul procedurii — vezi tabelul de mai sus).
## 4. Mapare parametru -> control -> fereastra
| Parametru | Control | Fereastra | Observatii |
|---|---|---|---|
| `V_ID_VANZARE` | — | — | Nu are control; e identitatea randului din `crsfacturi` (`poRec.id_vanzare`) |
| `V_ID_RUTA` | `Ct_clb_ruta` (`ControlSource` prin `cprocedura=thisform.do_cauta_ruta`, seteaza `poRec.id_ruta`) | `frm_modifica_factura` (`:5510-5526`, cautare `:5695-5721`) | |
| `V_ID_DELEGAT` | `Ct_clb_delegat` (`do_cauta_delegat` seteaza `poRec.id_delegat`) | `frm_modifica_factura` (`:5473-5489`, cautare `:5654-5678`) | |
| `V_ID_AGENT` | `Ct_clb_agent` (`do_cauta_agent` seteaza `poRec.id_agent`) | `frm_modifica_factura` (`:5455-5471`, cautare `:5639-5652`) | |
| `V_ID_MASINA` | `Ct_clb_masina` (`do_cauta_masina` seteaza `poRec.id_masina`) | `frm_modifica_factura` (`:5491-5508`, cautare `:5680-5693`) | |
| `V_DATAORA_EXP` | `Clb_dataora_exp.Text_simplu1` (`ControlSource="poRec.dataora_exp"`) | `frm_modifica_factura` (`:5437-5453`) | validat obligatoriu in `inainte_de_do_termin` (`:5723-5734`) |
| `V_ID_FACTURARE` | `clb_adresa_facturare` (`do_cauta_adresa` seteaza `poRec.id_facturare` + `poRec.adresa_facturare`) | `frm_modifica_factura` (`:5418-5435`, cautare `:5621-5637`) | |
| `V_LISTARE_DETALIATA` | `chkDetaliat` (`ControlSource="poRec.listare_detaliata"`) | `frm_modifica_factura` (`:5379-5390`) | |
| `V_TEXT_ADITIONAL` | `Ed_tx_simplu1._EDBASE1` (`ControlSource="poRec.text_aditional"`, `MaxLength=1000`) | `frm_modifica_factura` (`:5528-5551`) | comentariul VFP la `:4575` spune "MAXIM 250 CARACTERE", dar `MaxLength` control e 1000 — contradictie neverificata mai departe |
| `V_TIP_SAFT` | `cboTipFactura` (`ControlSource="poRec.tip_saft"`, `RowSource` din `saft_tip_facturi`) | `frm_modifica_factura` (`:5335-5351`) | `Visible = m.gl406` (`:5739-5741`), deci controlul e ascuns cand `gl406` e fals (`gl406` definit in `COMUN\programe\oinit_optiuni.prg:524`) |
| `V_EFACTURA` | **niciun control** | — | `poRec.efactura` vine doar din `Scatter` initial pe `crsfacturi` (cazurile `lnNrInreg=0/1`, `:4538-4564`) sau e fortat `0` la selectie multipla (`:4576`); formularul `frm_modifica_factura` nu are niciun obiect legat de `poRec.efactura` — nu poate fi schimbat de utilizator din acest formular azi |
| `V_DATA_ACT` | `txtDataAct` (`ControlSource="poRec.data_act"`) | `frm_modifica_factura` (`:5572-5581`) | vezi blocaj la punctul 5 |
| `V_DATA_SCAD` | `txtDataScad` (`ControlSource="poRec.data_scad"`) | `frm_modifica_factura` (`:5583-5592`) | vezi blocaj la punctul 5 |
| `V_NUMAR_ACT` | `txtNrAct` (`ControlSource="poRec.numar_act"`) | `frm_modifica_factura` (`:5594-5603`) | vezi blocaj la punctul 5 |
| `V_SERIE_ACT` | `txtSerieAct` (`ControlSource="poRec.serie_act"`) | `frm_modifica_factura` (`:5605-5614`) | vezi blocaj la punctul 5 |
Nu exista niciun parametru mapat pe `frm_alte_date` (`COMUN\clase\ferestre_cere_date.vc2:2219`) —
clasa aceea nu e implicata deloc in fluxul `do_modifica`/`modifica_date_factura`.
## 5. Parametri blocati/needitabili azi in `frm_modifica_factura`
Patru checkbox-uri controleaza accesul la seria/numarul/datele actului, si patru textbox-uri asociate:
- `chkSerieAct`, `chkNrAct`, `chkDataAct`, `chkDataScad`: definite cu `Enabled = .F.`
(`:5405-5416`, `:5392-5403`, `:5353-5364`, `:5366-5377`). Devin `Enabled` doar in `PROCEDURE Init`
(`:5743-5746`):
```
this.chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))
this.chkNrAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))
this.chkDataAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))
this.chkDataScad.Enabled = !EMPTY(NVL(poRec.numar_act,0))
```
adica doar daca factura are deja un numar de act completat (`poRec.numar_act` nenul) la
deschiderea formularului. Pe o factura fara act (proforma/nefinalizata), toate patru raman
needitabile.
- `txtSerieAct`, `txtNrAct`, `txtDataAct`, `txtDataScad`: definite cu `Enabled = .F.`
(`:5605-5614`, `:5594-5603`, `:5572-5581`, `:5583-5592`) si se activeaza doar din evenimentul
checkbox-ului corespunzator — `chkSerieAct.Valid` (`:5770-5772`), `chkNrAct.Valid`
(`:5766-5768`), `chkDataAct.Valid` (`:5758-5760`), `chkDataScad.Click` (`:5762-5764`) — deci
necesita ca checkbox-ul insusi sa fie deja `Enabled` (conditia de mai sus).
- `V_EFACTURA`: fara control — needitabil in acest formular indiferent de stare (punctul 4).
- `V_ID_VANZARE`: nu e un camp de editat, e identitatea randului.
Toti ceilalti parametri (`id_ruta`, `id_delegat`, `id_agent`, `id_masina`, `dataora_exp`,
`id_facturare`, `listare_detaliata`, `text_aditional`, `tip_saft`) au controale mereu `Enabled`
(fara restrictie in cod), `tip_saft` fiind conditionat doar de vizibilitate (`gl406`), nu de
`Enabled`.

View File

@@ -1,224 +0,0 @@
# Nota contabila pentru articol fara politica de pret (proiect #13)
Cercetare pe cod, fara modificari. Continua raportul anterior
`cont_venit_corespondente.md` (SCC vine prin lantul
`CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI.ID_NOTA -> CRM_NOTE_VANZARI.ID_SET -> NOTE_CONTABILE`,
citit de `cursor_articol` din `pack_facturare.contabilizeaza_articol`).
Sursa: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql`
(pachet `pack_facturare`, 17020 linii) si `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg` +
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql` (pachet `pack_auto`).
---
## 1. Ce face `contabilizeaza_articol` cand `id_pol` e NULL/0
Raspuns: **ridica exceptia FACT-024 si intreruce functia, inainte sa ajunga la cursorul care
citeste SCC**. Nu exista o ramura de default si nu se scrie nota cu SCC NULL pentru acest caz.
Corpul complet e la `ff_...:7182-7556`. Primul lucru pe care il face (inainte de `cursor_articol`,
inainte de orice `scrie_nota`) e:
```
ff_...:7286-7311
BEGIN
SELECT COMPUS, ID_POL_ART
INTO V_COMPUS, V_ID_POL_ART
FROM VCRM_POLITICI_PRET_ART
WHERE ID_ARTICOL = detalii_articol.id_articol
AND ID_POL = detalii_articol.id_pol;
EXCEPTION
WHEN NO_DATA_FOUND THEN
SELECT DENUMIRE INTO lcArticol FROM NOM_ARTICOLE WHERE id_articol = detalii_articol.id_articol;
SELECT NUME_LISTA_PRETURI INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol;
RAISE_APPLICATION_ERROR(-20000,
'Articolul ' || detalii_articol.id_articol || '|' || lcArticol ||
' nu este definit in politica de preturi ' ||
detalii_articol.id_pol || '|' || lcPolitica || '! (FACT-024)');
END;
```
`ID_POL = detalii_articol.id_pol` cu `id_pol` NULL nu poate potrivi niciun rand in SQL standard
(`X = NULL` e necunoscut, nu adevarat) — deci pentru `id_pol` NULL sau 0 (presupunand ca nu exista
o politica cu `ID_POL = 0`), `SELECT INTO` intra mereu pe `NO_DATA_FOUND`.
**Observatie suplimentara, neceruta explicit dar relevanta**: in ramura de exceptie, al doilea
`SELECT ... INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol` sufera
de aceeasi problema — cu `id_pol` NULL, si acest `SELECT INTO` da tot `NO_DATA_FOUND`, care **nu e
tratat** in acest bloc (nu exista un al doilea handler). Deci mesajul FACT-024 "curat" (cu numele
politicii) nu s-ar afisa niciodata pentru cazul `id_pol IS NULL` — s-ar propaga o exceptie
`NO_DATA_FOUND` neprinsa din interiorul handler-ului, cu alt mesaj Oracle generic, nu FACT-024 cu
text. Codul articolului (`lcArticol`) apuca sa fie rezolvat inaintea acestui eșec, deci
identificarea articolului nu s-ar pierde in log/eroare, dar textul final ar fi ORA-01403, nu
mesajul FACT-024 formatat. Pentru `id_pol = 0` (nu NULL), daca exista vreo politica cu id 0, acel
`SELECT` ar reusi si mesajul FACT-024 ar iesi corect.
Cursorul `cursor_articol` (care citeste SCD/ASCD/SCC/ASCC din `NOTE_CONTABILE` prin lant) **nu se
mai deschide** — funcția a ridicat deja exceptia mai sus. Deci intrebarea "cursorul intoarce zero
randuri, ce face codul mai departe" nu se pune in acest caz: nu ajunge acolo.
Concluzie Q1: **orice linie de vanzare cu `id_pol` NULL/0 trimisa la `contabilizeaza_articol` in
starea actuala a codului arunca o eroare si opreste tranzactia** (nu scrie nota cu cont NULL, nu
pune default). Cerinta proiectului #13 ("articol din nomenclator fara politica de pret, pret
tastat manual, pe orice tip de document") ar lovi FACT-024 (sau exceptia neprinsa descrisa mai sus)
daca linia respectiva e trecuta prin acest cod neschimbat.
---
## 2. Validare la `scrie_nota` pentru cont NULL
Raspuns: **nu exista**. Corpul complet e la `ff_...:12338-12570`.
- Singura verificare care ar fi atins asta e comentata:
```
ff_...:12441-12453
/* IF V_SUMA IS NULL THEN
RAISE_APPLICATION_ERROR(...) ...
END IF;*/
```
Si oricum verifica `V_SUMA` (suma calculata), nu `V_SCD`/`V_SCC`.
- `INSERT INTO ACT_TEMP (... SCD, ASCD, SCC, ASCC ...)` (`ff_...:12458-12544`) insereaza direct
`V_SCD`/`V_ASCD`/`V_SCC`/`V_ASCC` primite ca parametri, fara niciun `NVL`/`CASE`/verificare de
NULL pe ele.
- Nu exista `NOT NULL` verificat in cod pentru aceste coloane in acest fisier (constrangerea ar fi
la nivel de DDL al tabelei `ACT`/`ACT_TEMP`, care nu face parte din pachetul PL/SQL cercetat —
vezi sectiunea Neverificat).
Deci daca cineva ar ocoli blocul de la punctul 1 (de ex. printr-un `id_pol` care *exista* in
`CRM_POLITICI_PRET_ART` dar al carui lant `CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE`
e incomplet, asa incat `D.SCC` iese NULL din LEFT JOIN), `scrie_nota` ar scrie nota cu `SCC = NULL`
fara sa protesteze. Asta e insa un scenariu diferit de "fara politica de pret" (aici politica exista,
dar lantul de sub ea e incomplet) — nu e cazul cerut de proiectul #13 asa cum a fost formulat.
---
## 3. Toti apelantii lui `contabilizeaza_articol` in fisier
Cautare exhaustiva `Grep 'contabilizeaza_articol'` pe tot fisierul: 3 apeluri (in afara de propria
definitie si un comentariu):
| Linie | Procedura apelanta | Domeniu / flux |
|---|---|---|
| `ff_...:6150` | `scrie_factura2` (corp `ff_...:6029-6272`) | **Fluxul principal de scriere** — pentru orice document ale carui randuri nu cad in ramurile speciale ale `CASE`-ului de la `ff_...:6081-6151`: transfer intre subunitati (`ntip IN (23,25,30,41)`), factura/aviz custodie (`ntip IN (42,47)`), rata cu `id_rata<>0` (`ntip IN (2,6,52)`). Toate celelalte tipuri — **factura normala** (`ntip<=20`), hotel, restaurant, nota de plata, vanzare retail, tipurile 48/49/51/52, aviz simplu, aviz catre clienti debitori, si **aviz retur (tip 24)** cand e scris prin `scrie_factura2` — ajung la `contabilizeaza_articol` aici. Aceasta e apelarea "de baza" pe care se sprijina interpretarea corpului functiei facuta la punctul 1 (ramurile `CASE` din interiorul `contabilizeaza_articol`, `ff_...:7407-7432`, mapeaza direct pe tipurile de document tratate de acest apelant). |
| `ff_...:6867` | `scrie_factura_avize_retur` (corp `ff_...:6273-6666`) | Flux **factura scrisa pe baza de avize + retur simultan**. Apelul e in interiorul buclei pe `articole_aviz` (`ff_...:6841-6960` zona), pentru randurile cu `custodie = 1` (marfa in custodie ce se descarca din avize). |
| `ff_...:7149` | `scrie_aviz_retur` (corp `ff_...:7094-7180`) | Procedura dedicata **aviz retur** (`V_ID_DELEGAT, V_ID_MASINA, ...`). Apelul e in ramura `ELSE` a testului `pack_facturare.nfactavizcust = 1` (`ff_...:7124-7150`) — deci pentru randurile care **nu** sunt in custodie. |
Concluzie Q3: **da**, `contabilizeaza_articol` e apelata pe fluxul principal de facturare
(`scrie_factura2`, care acopera factura normala si majoritatea celorlalte tipuri de document), nu
doar din `scrie_aviz_retur`. Raportul anterior lasase asta neverificat; e confirmat aici cu cele 3
puncte de apel gasite si citate mai sus — nu exista un al 4-lea apel in fisier.
---
## 4. Precedentul ROAAUTO "Alte servicii" — **nu e de fapt un precedent pentru cazul cerut**
Asta contrazice premisa din cerere. Firul (`crsalteserv -> crsvanztemp -> adauga_articol_factura_deviz`)
duce spre un flux care **ocoleste complet `contabilizeaza_articol`**, deci nu demonstreaza ce se
intampla in `pack_facturare` pentru un articol fara politica — demonstreaza doar ca exista un *alt*
mecanism de contabilizare, in afara pachetului `pack_facturare`.
Pasii, cu citate:
1. **`crsalteserv -> crsvanztemp`** (`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1040-1041`):
```
Insert Into (lcCursorDeviz)(id_articol,denumire,explicatie,cantitate,um,id_jtva_coloana,proc_Tvav,taxcode,pret,discount_unitar) ;
Select id_articol,denumire,"",cantitate,um,CAST(lnIdJTvaColoana as N(6) null) as id_jtva_coloana,CAST(lnProcTvav as N(7,2) null) as proc_tvav,CAST(lnTaxCode as N(6) null) as taxcode,pretftva,0 From crsalteserv Where !Deleted()
```
Nu exista coloana `id_pol` in `crsalteserv`, in `lcCursorDeviz`, sau in cursorul `crsvanztemp`
creat la `oproceduri_devize.prg:1190-1192` (schema explicita a cursorului nu include `id_pol`).
2. **`crsvanztemp -> adauga_articol_factura_deviz`** (`oproceduri_devize.prg:1240-1257`): apelul RPC
trimite `id_articol, explicatie, serie, pret_achizitie, pretd, id_valuta_d, pret, id_valuta,
curs, multiplicator, proc_tvav, id_jtva_coloana, cantitate, discount_unitar, id_gestiune, cont,
pret_cu_tva, NULL, NULL, taxcode` — **fara `id_pol`**, pentru ca procedura insasi nu are un
parametru `id_pol`.
3. **Semnatura `adauga_articol_factura_deviz`** (spec `ff_...:468-488`, corp `ff_...:4675-4745`):
nu exista `V_ID_POL IN NUMBER` printre parametri. `INSERT INTO VANZARI_DETALII_TEMP` la
`ff_...:4697-4744` **nu include coloana `ID_POL`** in lista de coloane inserate — deci
`ID_POL` ramane pe valoarea implicita a coloanei (probabil NULL; DDL-ul tabelei nu face parte
din pachetul cercetat, vezi Neverificat). **Asta confirma partea (a) a intrebarii: da, liniile
"alte servicii" ajung cu `id_pol` gol** in `VANZARI_DETALII_TEMP`.
4. **Dar acele randuri nu trec niciodata prin `contabilizeaza_articol`.** Apelantul din
`oproceduri_devize.prg` nu cheama `scrie_factura2` / `scrie_factura_avize` / `scrie_aviz_retur`
(singurele proceduri care cheama `contabilizeaza_articol`, vezi punctul 3). In schimb cheama, in
ordine (`oproceduri_devize.prg:1211-1310`):
- `pack_facturare.initializeaza_date_factura(...)`
- bucla `pack_facturare.adauga_articol_factura_deviz(...)` (doar insert in `VANZARI_DETALII_TEMP`)
- opțional `pack_facturare.scrie_incasari(...)` (incasare, nu venit din vanzare)
- `pack_facturare.scrie_in_vanzari(0, ...)` (`ff_...:13497-13962`)
- `pack_auto.actualizeaza_deviz(...)` (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`)
**`scrie_in_vanzari` nu scrie nici o nota contabila de venit.** Corpul complet
(`ff_...:13497-13962`) face: `INSERT INTO VANZARI` (antetul documentului), `scrie_cursuri`,
`scrie_seturi`, apoi
```
ff_...:13714-13766
INSERT /*+ APPEND */ INTO VANZARI_DETALII (..., ID_POL, ...)
SELECT ..., ID_POL, ... FROM VANZARI_DETALII_TEMP;
```
(copiaza `ID_POL` — care e NULL pentru aceste randuri — direct in `VANZARI_DETALII`, fara nicio
validare), si la final actualizeaza totalurile cache pe `VANZARI`. **Nu apare niciun apel catre
`contabilizeaza_articol`, `scrie_nota`, `cumuleaza_note_act` sau `finalizeaza_factura` in acest
corp** (verificat citind procedura cap-coada).
`pack_auto.actualizeaza_deviz` (`ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`) face doar:
```
SELECT MAX(ID_FACT) INTO lnIdFact FROM ACT WHERE COD = pack_contafin.get_cod() AND SCD NOT LIKE '5%';
UPDATE DEV_ORDL SET PROC_TVAV = ... WHERE ID_ORDL IN (...);
UPDATE RUL SET ID_FACT = lnIdFact WHERE ...;
UPDATE NOM_LUCRARI SET ID_FACT = lnIdFact WHERE ... (doar pt anumite id_set);
```
Adica **presupune ca exista deja un rand in `ACT`** (nu `ACT_TEMP`) cu `SCD NOT LIKE '5%'` pentru
codul documentului curent, si doar propaga `ID_FACT` gasit acolo spre `DEV_ORDL`/`RUL`/`NOM_LUCRARI`.
Nu insereaza el insusi in `ACT` sau `NOTE_CONTABILE`, si nu contine nicio referinta la `SCC`,
`NOTE_CONTABILE` sau `CRM_POLITICI*` (cautat explicit, zero rezultate in acest fisier).
**Concluzie Q4, corectand premisa din cerere**: liniile "Alte servicii" chiar ajung cu `id_pol` gol
(confirmat), dar **nu demonstreaza ca `contabilizeaza_articol` tolereaza `id_pol` gol** — pentru ca
nu trec niciodata prin el. Ele demonstreaza doar ca exista deja, pentru fluxul deviz ROAAUTO, o cale
de facturare paralela (`scrie_in_vanzari` + `pack_auto.actualizeaza_deviz`) care **nu genereaza deloc
nota contabila de venit prin `pack_facturare`**. Daca acele randuri capata totusi un cont de venit in
`ACT`/`NOTE_CONTABILE` in productie, mecanismul nu e vizibil in codul cercetat (posibil un
trigger pe tabel, un job batch, sau o scriere manuala/alt modul neexaminat — vezi Neverificat).
**Nu se poate folosi acest precedent ca raspuns de fapt la intrebarea 1.**
---
## 5. Vreun mecanism care sa foloseasca `NOM_ARTICOLE.CONT` drept cont de venit
Raspuns: **NU**, in `pack_facturare`.
Cautat explicit `Grep -i 'NOM_ARTICOLE\.CONT'` pe intregul fisier
`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` (17020 linii) — **zero potriviri**. Singurele utilizari
ale `NOM_ARTICOLE` gasite in `contabilizeaza_articol` insusi sunt pentru `DENUMIRE` (mesajul de
eroare FACT-024, `ff_...:7295-7298`), nu pentru `CONT`.
`crs_rand_articol.scc` (folosit ca `V_SCC` la `ff_...:7434`) vine exclusiv din
`D.SCC` din join-ul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE`
(`ff_...:7261, 7275-7276`) — nu exista ramura alternativa care sa substituie `NOM_ARTICOLE.CONT`
cand politica lipseste. (Raportul anterior confirmase deja separat ca `NOM_ARTICOLE.CONT` e folosit
doar ca **cont de gestiune**, transmis catre `descarca_gestiune`, `ff_...:7499`.)
---
## Neverificat
- **Continutul efectiv al `NOTE_CONTABILE`/`CRM_POLITICI_PRET_ART`** (daca exista politici cu
`ID_POL = 0`, ce SCC au liniile din productie pentru documente "alte servicii" existente) —
necesita interogare pe baza de date, nu doar cod.
- **Constrangerile DDL** ale `VANZARI_DETALII_TEMP.ID_POL` si `ACT_TEMP.SCC`/`ACT.SCC` (NOT NULL?
default?) — scripturile DDL ale acestor tabele nu au fost cautate/citite in aceasta cercetare
(am cautat doar in pachetele PL/SQL `ff_...COMUN_PACK_FACTURARE.sql` si `..._AUTO_PACK_AUTO.sql`).
- **Cine scrie efectiv contul de venit (`ACT`/`NOTE_CONTABILE`) pentru documentele "alte servicii"
din ROAAUTO**, daca se scrie deloc. `pack_auto.actualizeaza_deviz` presupune ca exista deja un
rand `ACT` cu `SCD NOT LIKE '5%'` pentru codul documentului curent — sursa acelui rand nu a fost
gasita in `oproceduri_devize.prg` sau in corpul `scrie_in_vanzari`/`actualizeaza_deviz` cercetate.
Ar trebui cautat: alte proceduri `pack_auto` (fisierul `ff_2026_03_31_02_AUTO_PACK_AUTO.sql` are
si alte proceduri necercetate aici), triggere pe `VANZARI`/`VANZARI_DETALII`/`ACT`, sau apeluri
din alte forme VFP (`.scx`/`.vcx`) din ROAAUTO care preced acest apel RPC. Nu am cautat exhaustiv
triggere in afara folderului `SCRIPTURI_CLAR\2026` (cautare limitata, zero rezultate acolo).
- **Ce se intampla exact daca `id_pol = 0` si exista o politica reala cu `ID_POL = 0`** (caz
teoretic, neexclus explicit din schema) — ar schimba concluzia de la "eroare neprinsa" la
"FACT-024 cu mesaj complet", dar tot ramane o eroare, nu un default silentios.

View File

@@ -1,373 +0,0 @@
# Optiune de firma pentru contul de debit (`SCD`) pe ramura fara politica de pret (#13, decizia 36)
Cercetare read-only, terminata. Continua `canal_cont_venit_fara_politica.md` (proiectarea
parametrului de cont, sectiunea "Proiectarea parametrului de cont contabil", `:27-241`) si
verificarea ei, `parametru_cont_contabilizeaza_articol.md`. Acolo, randul `SCD` din tabelul de
sectiunea 3 propunea **`SCD` hardcodat `'4111'`**, marcat explicit "de confirmat cu Marius"
(`canal_cont_venit_fara_politica.md:146`). **Decizia 36 (Marius, 10.08.2026): `SCD` vine dintr-o
optiune de firma, cu `4111` ca implicit** — motivul fiind ca se poate schimba la client fara
recompilare. Aici e proiectarea acelei optiuni. Decizia 31 se aplica: datele din baza de dev arata
ce *exista*, nu ce e configurat la client — nu se generalizeaza pe numere, doar pe tipar/structura.
Oracle interogat doar `SELECT`, schema `MARIUSM_AUTO`/`ROA_CENTRAL`. Niciun fisier de cod atins.
---
## 0. Rezumat, in cinci randuri
Mecanismul de optiuni de firma **exista deja de 15+ ani** (`PACK_SESIUNE.getoptiunefirma`, tabelul
`OPTIUNI`, 458 randuri azi) si are **exact tiparul cerut deja in productie, in acelasi pachet**:
`pack_facturare.scrie_incasare2` seteaza `V_SCD` dintr-o optiune (`RF_CONT_INCASARE_BONFISCAL` etc.,
cate una per tip de incasare), cu fallback pe un cont hardcodat daca optiunea lipseste — **acelasi
camp (`SCD`), acelasi pachet, aceeasi sursa Oracle** (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`,
liniile 13191-13233, tratate integral in sectiunea 2). Ecranul de editare (`frm_optiuni`/
`frm_optiuni_nou`, `COMUN\clase\oOptiuni.vc2`) e **complet generic** peste tabelul `OPTIUNI` —
adaugarea cheii noi **nu cere niciun cod VFP nou** in ecranul de optiuni, doar randul in tabel.
Propunerea (sectiunea 3): cheia noua `FACT_SCD_ARTFPRET` (sau numele ales de Marius), instalata cu
valoare implicita `'4111'` chiar in randul din `OPTIUNI` (tiparul deja folosit de toate exemplele
gasite), **plus** fallback hardcodat `'4111'` in cod langa `getoptiunefirma`, in oglinda exacta a
tiparului `scrie_incasare2` — dubla plasa de siguranta (instalare veche fara randul din `OPTIUNI`
**si** rand editat gresit/gol raman amandoua acoperite).
---
## 1. Cum functioneaza mecanismul de optiuni de firma, azi
### 1.1 Stocare — tabelul `OPTIUNI`
Interogat direct (`ALL_TAB_COLUMNS`, schema curenta), 458 randuri azi:
| Coloana | Tip | Null | Rol |
|---|---|---|---|
| `ID_OPTIUNI` | `NUMBER` | N | cheie surogat, PK implicita |
| `VARNAME` | `VARCHAR2(30)` | Y | cheia optiunii — cautata `UPPER(TRIM(...))`, deci case-insensitive in practica |
| `VARTYPE` | `VARCHAR2(10)` | Y | eticheta de tip pentru randare/parsare in VFP: `CHARACTER`/`NUMERIC`/`CURRENCY`/`DATE`/`DATETIME`/`LOGICAL` — **descriptiva, nu impusa de Oracle**; distinct azi: `CHARACTER` 156, `NUMERIC` 288, `LOGICAL` 11, `DATE` 3 |
| `VARVALUE` | `VARCHAR2(1000)` | Y | valoarea, **mereu text**, indiferent de `VARTYPE` |
| `VARVALUE2` | `CLOB` | Y | valoare alternativa, folosita de un mecanism separat (`citeste_optiune(tcOptiune, tlValue2)`, `oinit_optiuni.prg:802-823`) — irelevant aici |
| `VARDESC` | `VARCHAR2(100)` | Y | descriere afisata in ecran |
| `PROGRAM` | `VARCHAR2(30)` | Y | produsul care a creat optiunea (metadata) |
| `PROGRAME` | `VARCHAR2(1000)` | Y | lista CSV de produse pentru care optiunea e vizibila/relevanta (folosita la filtrare in incarcarea globala VFP, sectiunea 1.4) |
| `ID_PRG_OWNER` | `NUMBER` | Y | neexaminat mai departe, neрелevant aici |
**Fara `ID_FIRMA`.** Motivul: fiecare firma are **schema Oracle proprie** — tabelul `OPTIUNI` din
schema curenta de conexiune **este** deja "optiunile firmei curente"; nu exista randare
multi-firma in acelasi tabel. Confirmat si de codul VFP: `frm_optiuni.cschema` e setat la
constanta de compilare `CONTAFIN_ORACLE` (`oOptiuni.vc2:1089`, acelasi simbol folosit peste tot in
COMUN pentru "schema firmei curente"), iar `getoptiunefirma(tcS, tcOptiune)` **primeste** un
parametru de schema (`tcS`, azi mereu `USER`) dar **nu il foloseste in interogare** — `select
varvalue from optiuni` fara calificare de schema (cod exact la sectiunea 1.2) — pentru ca sesiunea
Oracle e deja conectata direct in schema firmei. `tcS` ramane doar din compatibilitate istorica cu
supraincarcarea `getOptiuneFirma(tcOptiune)` (fara schema), care apeleaza pe cea cu doi parametri
cu `user` (sectiunea 1.2).
### 1.2 Semnatura si contract — `PACK_SESIUNE.getoptiunefirma`
Sursa curenta confirmata prin `ALL_SOURCE` pe pachetul live (`PACK_SESIUNE`, tip `PACKAGE BODY`) —
identica, verificat, cu ultima versiune gasita in `SCRIPTURI_CLAR`
(`2014/04/ff_2014_04_24_01_COMUN_PACK_SESIUNE.sql:328-350`; nicio modificare de atunci, cautare
`FUNCTION getoptiunefirma` in `SCRIPTURI_CLAR/2024`, `/2025`, `/2026` — zero rezultate):
```sql
FUNCTION getoptiunefirma(tcS IN VARCHAR2, tcOptiune IN VARCHAR2)
RETURN VARCHAR2 IS
lcValue Optiuni.Varvalue%Type := '';
BEGIN
BEGIN
select varvalue
INTO lcValue
from optiuni
where UPPER(TRIM(VARNAME)) = UPPER(TRIM(tcOptiune));
EXCEPTION
WHEN OTHERS THEN
lcValue := '';
END;
RETURN lcValue;
END;
FUNCTION getOptiuneFirma(tcOptiune IN VARCHAR2) RETURN VARCHAR2 IS
lcValue Optiuni.Varvalue%Type := '';
BEGIN
lcValue := getOptiuneFirma(user, tcOptiune);
return lcValue;
END;
```
Contract, verificat pe `RETURN`, nu dedus din nume:
- **Parametru**: numele optiunii (`tcOptiune`), cautat `UPPER(TRIM(...))` — insensibil la caz si la
spatii; a doua forma (`getOptiuneFirma(tcOptiune)`, un singur parametru) e cea folosita azi peste
tot in `pack_facturare` (vezi sectiunea 2) — deleaga la forma cu doi parametri cu `user`.
- **Cand optiunea NU exista** (`NO_DATA_FOUND`, prins de `WHEN OTHERS` generic): **`RETURN ''`**
(string gol), **niciodata `NULL` explicit si niciodata exceptie propagata**. Insa in Oracle un
`VARCHAR2` egal cu `''` **este** `NULL` la nivel de reprezentare (Oracle nu distinge stringul gol
de `NULL`) — deci orice apelant care testeaza `IF V_X IS NULL THEN` dupa un apel la
`getoptiunefirma` **prinde si cazul "optiune inexistenta"**, fara cod suplimentar. Acesta e
exact tiparul folosit de toti apelantii gasiti (sectiunea 2).
- **`WHEN OTHERS THEN lcValue := ''`** inghite **orice** eroare, nu doar `NO_DATA_FOUND` (de ex. si
`TOO_MANY_ROWS` daca ar exista vreodata doua randuri cu acelasi `VARNAME` — nu exista constrangere
`UNIQUE` pe `VARNAME`, doar convenția aplicatiei). Nu e un risc nou introdus de propunerea de
fata — e comportamentul mecanismului de 15+ ani, pastrat ca atare.
- **Variante**: **o singura functie**, intotdeauna `VARCHAR2`. **Nu exista** `getoptiunefirma`
numerica/booleana separata in Oracle — conversia se face **la apelant**, in PL/SQL cu
`TO_NUMBER(NVL(...))` (exemplu confirmat, acelasi fisier, `scrie_incasare2`,
`ff_...:13177`: `TO_NUMBER(NVL(pack_sesiune.getoptiunefirma('D406'), '0'))`) sau in VFP cu
`VAL()`/`CTOD()`/etc., pe baza coloanei `VARTYPE` (vezi `oinit_optiuni.prg:179-211`, functia
`actualizeaza_optiuni_program`, care materializeaza fiecare optiune intr-o variabila globala VFP
cu prefix dupa tip: `gc<nume>` pentru `CHARACTER`, `gn<nume>` pentru `NUMERIC`,
`gl<nume>` pentru `LOGICAL` etc.). **Pentru un cont contabil (`VARCHAR2(4)`), varianta corecta e
cea de baza, `VARTYPE='CHARACTER'`** — exact tipul folosit de toate exemplele cu cont din
sectiunea 2, fara nicio conversie necesara la citire in PL/SQL (`V_SCD` e deja `VARCHAR2`).
### 1.3 Ecranul de editare — complet generic peste tabel
`COMUN\clase\oOptiuni.vc2`, doua clase relevante:
- **`frm_optiuni`** (`:1064-1314`) — grid pe view-ul VFP local `v_optiuni` (populat dintr-un
`SELECT * FROM <schema>.optiuni`, vezi `oinit_optiuni.prg:337` in `viz_optiuni`), 4 coloane
(`varname`, `vartype`, `varvalue`, `vardesc`), cu cautare doar dupa `varname LIKE`
(`do_cauta`, `:1261-1278`) si butoane Adauga/Modifica/Sterge care deschid `frm_optiuni_nou`.
- **`frm_optiuni_nou`** (`:1316-1462`) — formular Adauga/Modifica cu 4 campuri: `varname` (text,
`Format="!K"` = uppercase), `vartype` (combobox cu lista fixa `CHARACTER,CURRENCY,NUMERIC,
DATETIME,DATE,LOGICAL`), `varvalue` (text), `vardesc` (text). Validare la salvare
(`inainte_de_do_termin`, `:1417-1450`): **doar "nu e gol"** pe `varname`, `vartype`, `varvalue` —
**nicio validare de format sau de continut** (nu verifica lungime, nu verifica daca `varvalue`
e un cont existent in planul de conturi, nimic specific per optiune).
- **`cus_odata_optiuni`** (`:139-206`) — clasa de persistenta: `INSERT`/`UPDATE`/`DELETE` generic pe
`optiuni (varname, vartype, varvalue, vardesc)` — confirma ca **orice cheie noua** functioneaza
fara nicio schimbare la acest cod; scrie exact cele 4 coloane editabile din ecran.
**Concluzie la intrebarea "adaugarea unei optiuni noi cere cod nou in ecran": NU.** Ecranul e
generic peste tabel dupa cele 4 coloane editabile. Randul nou (`FACT_SCD_ARTFPRET`, sectiunea 3)
apare automat in grid dupa ce e inserat, si e editabil din prima fara nicio modificare VFP.
*Nota conexa, nu necesara pentru mecanismul Oracle*: la fiecare pornire de sesiune, VFP
materializeaza **toate** randurile din `OPTIUNI` in variabile globale (`optiuni_firma`,
`oinit_optiuni.prg:234-310`), filtrate `Isnull(programe) Or gcNumeProgram $ programe` — deci coloana
`PROGRAME` conteaza doar pentru **vizibilitatea in acest mecanism global VFP**, nu pentru
`getoptiunefirma` (care nu se uita deloc la `PROGRAME`). Optiunea noua nu are nevoie de acest canal
VFP (Oracle o citeste direct), dar completarea corecta a `PROGRAME` evita zgomot/confuzie daca
cineva se uita in `frm_optiuni` din ROAFACTURARE si se asteapta sa vada optiunea listata acolo cu
`gcNumeProgram = 'ROAFACTURARE'` in `PROGRAME`.
---
## 2. Exemple reale de optiuni existente, cu lantul complet
### 2a. `RF_CONT_INCASARE_*` — exact tiparul cerut, in acelasi pachet, pe acelasi camp `SCD`
**Cel mai relevant exemplu posibil**: patru optiuni care alimenteaza direct `SCD` pe o nota
contabila, in `pack_facturare.scrie_incasare2` — **aceeasi functie Oracle pe care propunerea de la
`canal_cont_venit_fara_politica.md` o modifica** (acelasi fisier,
`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`).
**Randul din tabel** (interogat direct, 4 randuri, VARTYPE `CHARACTER` la toate):
| VARNAME | VARVALUE azi | VARDESC | PROGRAM | PROGRAME |
|---|---|---|---|---|
| `RF_CONT_INCASARE_BONFISCAL` | `5311` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111` | `ROAFACTURARE` | `ROAFACTURARE,ROAACNPRO,ROACOMENZI,ROACONTRACTE,ROAGEST,ROARESTAURANT` |
| `RF_CONT_INCASARE_CARDBANCAR` | `5125` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5113 = 4111` | idem | idem |
| `RF_CONT_INCASARE_TICHETE` | `5323` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5323 = 4111` | idem | idem |
| `RF_CONT_INCASARE_CHITANTA` | `5311` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111` | idem | idem |
(`VARDESC` mentioneaza si `= 4111` — nu e o eroare de copy-paste: e nota ca partea de credit a
notei de incasare e contul de clienti `4111`, deci descrierea documenteaza ambele parti ale notei,
nu doar valoarea implicita a optiunii.)
**Insertia originala** (`SCRIPTURI_CLAR/2010/08/ff_2010_08_11_01_OPTIUNI.sql:9-19`), tiparul de
migrare de urmat:
```sql
insert into optiuni (VARNAME, VARTYPE, PROGRAM, VARVALUE, VARDESC, ID_PRG_OWNER, PROGRAME)
values ('RF_CONT_INCASARE_BONFISCAL', 'CHARACTER', 'ROAFACTURARE', '5311',
'CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111', null, 'ROAFACTURARE');
```
**Apelul din PL/SQL** (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:13161-13234`,
`PROCEDURE scrie_incasare2`), citit integral — **exact tiparul "optiune cu fallback hardcodat" cerut
de decizia 36**:
```sql
V_SCD := V_PSCD; -- parametru explicit, daca a fost dat de apelant (ff_...:13185)
CASE
WHEN V_TIP = nTipIncasareBonFiscal THEN
...
IF V_SCD IS NULL THEN
V_SCD := PACK_SESIUNE.getoptiunefirma('RF_CONT_INCASARE_BONFISCAL');
END IF;
IF V_SCD IS NULL THEN
V_SCD := '5311'; -- fallback hardcodat, instalare veche
END IF;
...
END CASE;
```
Trei niveluri, in ordine: **(1) parametru explicit daca a fost dat, (2) optiunea de firma, (3)
constanta hardcodata** — folosita si daca randul din `OPTIUNI` lipseste complet (instalare veche,
migrare neaplicata) *si* daca a fost sters/golit manual din ecran. Acest cont ajunge apoi direct in
`INSERT INTO ACT_TEMP (..., SCD, ...)` (`ff_...:13241-13249` in continuare), **fara nicio validare
suplimentara de format sau de existenta in planul de conturi** — nici in Oracle, nici in ecranul de
optiuni (sectiunea 1.3), nici la `INSERT`.
**Ecranul de editare**: acelasi `frm_optiuni`/`frm_optiuni_nou` generic (sectiunea 1.3) — niciun
ecran dedicat pentru aceste 4 optiuni, se editeaza ca text simplu in grid.
### 2b. `D406` — optiune numerica/booleana folosita ca flag, cu conversie explicita la citire
`VARNAME='D406'`, `VARTYPE='NUMERIC'`, `VARVALUE='1'`, `PROGRAM='ROACONT'`,
`PROGRAME='ROACONT,ROAAUTO,ROACONTRACTE,ROADEVIZE,ROAFACTURARE,...'`. Citita in acelasi
`scrie_incasare2` (`ff_...:13177`): `lnD406 := TO_NUMBER(NVL(pack_sesiune.getoptiunefirma('D406'),
'0'))` — **tipar de citire pentru optiune numerica**: `getoptiunefirma` intoarce mereu text,
apelantul face `NVL(...,'0')` inainte de `TO_NUMBER` (aici fallback-ul `'0'` e in acelasi loc cu
apelul, nu separat ca la `RF_CONT_INCASARE_*` — variatie minora de stil intre autori, nu o regula
diferita).
### 2c. `CONT411` si `DEVCONTALTELE` — optiuni "cont" mai vechi, azi orfane in cod
`CONT411` (`VARTYPE='CHARACTER'`, `VARVALUE='4111'`, `VARDESC='411 SAU 4111'`, fara `PROGRAM`) si
`DEVCONTALTELE` (`VARTYPE='CHARACTER'`, `VARVALUE='704'`, `VARDESC='CONT ALTE SERVICII FACTURA'`,
`PROGRAM='ROAAUTO'`) — cautare `grep` pentru ambele in tot `SCRIPTURI_CLAR`: **niciun apelant activ
gasit** (doar insertiile originale din 2011). Probabil cod dezafectat sau folosit doar din alt
produs/raport neexaminat aici. Mentionate pentru completitudine si pentru ca `CONT411` confirma
**exact valoarea implicita ceruta de Marius** (`4111`) ca precedent de configurare tot pe un cont
de clienti — dar **nu** au un lant activ de apel, deci nu sunt un exemplu de "tipar in uz" la fel de
tare ca `RF_CONT_INCASARE_*`.
### 2d. `EFACTURA_CONT_ART_E`/`EFACTURA_CONT_ART_P` — optiune "cont implicit pe articol", azi goala
`VARTYPE='CHARACTER'`, `VARVALUE` **goala** azi (nesetata pe baza curenta), `VARDESC='CONT ARTICOLE
IMPLICIT FACTURI EMISE'`/`'...PRIMITE'`, `PROGRAM='ROACONT'`. Confirma ca tiparul "cont implicit
configurabil, gol pana e setat manual" exista deja ca optiune (nu s-a putut verifica in aceasta
runda cine o citeste — cautare in afara scopului, modulul `ROACONT`/eFactura nu a fost deschis).
Relevanta doar ca a patra confirmare a conventiei de nume (`EFACTURA_CONT_ART_<sufix>`), nu ca lant
complet verificat.
---
## 3. Propunerea concreta
### 3.1 Cheia optiunii
**`FACT_SCD_ARTFPRET`** — respecta conventia dominanta observata in tabel: prefix de modul
(`FACT` pentru optiunile specifice `pack_facturare`/ROAFACTURARE, ca `FACTSETURI`,
`FACTALGREPCOM`, `FACTURACUCHITANTA`, `FACTURAEMAIL`, `FACTURASOLD` — toate `PROGRAM='ROAFACTURARE'`)
plus sufix descriptiv scurt fara diacritice si fara spatii (`SCD` = campul contabil vizat,
`ARTFPRET` = "articol fara politica de pret" — in oglinda cu numele deja folosit in proiectarea
anterioara, "ramura fara politica"). Nicio cheie `SCD`/`SCC` existenta azi in tabel (cautare directa,
zero rezultate) — nu exista risc de coliziune de nume. **Alternativa mai scurta**, daca Marius
prefera: `SCD_FARA_POLITICA` (fara prefix de modul) — tiparul din tabel nu e strict, exista si chei
fara prefix (`D406`, `CONT411`); prefixul `FACT_` e recomandat, nu obligatoriu.
### 3.2 Valoarea implicita si unde traieste
**Tiparul deja stabilit si folosit de `RF_CONT_INCASARE_*` (sectiunea 2a) se copiaza identic**:
implicitul traieste **in doua locuri**, nu unul:
1. **In randul din `OPTIUNI`, la instalare** — valoarea `VARVALUE='4111'` scrisa direct de scriptul
de migrare (sectiunea 3.4). Asta e valoarea pe care o vede si o poate schimba Marius/clientul din
`frm_optiuni`, fara recompilare — exact cerinta deciziei 36.
2. **Hardcodat in PL/SQL, langa apelul `getoptiunefirma`**, ca a doua plasa de siguranta pentru
instalari vechi unde migrarea nu a rulat inca sau randul a fost sters/golit manual din ecran:
```sql
V_SCD := PACK_SESIUNE.getoptiunefirma('FACT_SCD_ARTFPRET');
IF V_SCD IS NULL THEN -- acopera si randul lipsa, si VARVALUE gol/sters din ecran (sectiunea 1.2)
V_SCD := '4111';
END IF;
```
Plasata **in ramura noua din `contabilizeaza_articol`** (`canal_cont_venit_fara_politica.md:146`,
randul `SCD` din tabelul design-ului), **in locul** liniei `SCD := '4111'` hardcodat propuse acolo —
inlocuire punctuala, restul ramurii (`ASCD` calculat din `GetAnaliticByGrupUtilizatori(...,
V_SCD)`, deja dependent de `V_SCD`) ramane neschimbat, primeste automat valoarea corecta indiferent
de sursa (optiune sau fallback).
### 3.3 Comportamentul cand optiunea lipseste sau e invalida
- **Lipseste randul din `OPTIUNI`** (instalare veche, migrare neaplicata): `getoptiunefirma` intoarce
`''` -> tratat `IS NULL` in Oracle -> fallback la `'4111'` hardcodat (sectiunea 3.2, pasul 2).
**Acoperit prin constructie**, acelasi tipar ca `RF_CONT_INCASARE_*`.
- **Randul exista dar `VARVALUE` e gol** (sters manual din ecran, fara sa fi fost stearsa cheia):
identic cazului anterior — `''` tot `IS NULL` in Oracle. **Acoperit prin constructie.**
- **`VARVALUE` contine un cont care nu exista in planul de conturi**, sau un string cu lungime/
format invalid: **nu exista nicio validare azi**, nici in `getoptiunefirma`, nici in ecranul de
optiuni (sectiunea 1.3, "nicio validare de format sau de continut"), nici la `INSERT INTO
ACT_TEMP` (coloana `SCD` e `VARCHAR2(4) NULL` fara `CHECK`, fara `FK`, confirmat in DDL-ul deja
citat in `canal_cont_venit_fara_politica.md`, sectiunea DDL). Un cont inexistent ar ajunge pe nota
neschimbat — **exact acelasi risc pe care il are deja `RF_CONT_INCASARE_*` de 15 ani**, nu unul
nou introdus de aceasta propunere. Daca se doreste o garda, ar trebui adaugata **la citire** (in
ramura noua din `contabilizeaza_articol`, dupa cele doua atribuiri de mai sus, cu un `SELECT
COUNT(*) FROM PLCONT WHERE COD_CONT = V_SCD` sau echivalent) — **nu exista azi un precedent de
validare pe niciuna dintre optiunile de tip cont examinate**, deci adaugarea uneia ar fi o
imbunatatire noua, nu o continuare a unui tipar existent; de decis explicit (sectiunea 4).
- **Lungime > 4 caractere** in `VARVALUE` (campul din tabel e `VARCHAR2(1000)`, mult mai lat decat
`ACT_TEMP.SCD VARCHAR2(4)`): ar da `ORA-12899` (value too large) la `INSERT INTO ACT_TEMP` daca
cineva scrie din greseala un text lung in ecran — eroare Oracle standard, nu un `NULL`/gol tacut;
comportament acceptabil ca gardă minimala "gratuita", fara cod suplimentar.
### 3.4 Scriptul de migrare — schita, neaplicata
Tiparul exact al insertiei `RF_CONT_INCASARE_BONFISCAL` (sectiunea 2a), idempotent prin verificarea
existentei prealabile (tipar comun in scripturile de migrare vazute in `SCRIPTURI_CLAR`, desi
insertia originala din 2010 nu avea garda — cea de mai jos o adauga, mai sigura pentru un script
care ar putea rula de doua ori):
```sql
-- ff_<data>_NN_COMUN_OPTIUNI.sql (schita, neaplicata)
DECLARE
V_CNT NUMBER;
BEGIN
SELECT COUNT(*) INTO V_CNT FROM OPTIUNI WHERE UPPER(TRIM(VARNAME)) = 'FACT_SCD_ARTFPRET';
IF V_CNT = 0 THEN
INSERT INTO OPTIUNI (VARNAME, VARTYPE, PROGRAM, VARVALUE, VARDESC, PROGRAME)
VALUES ('FACT_SCD_ARTFPRET', 'CHARACTER', 'ROAFACTURARE', '4111',
'CONTUL DEBITOR (SCD) PENTRU ARTICOL FACTURAT FARA POLITICA DE PRET',
'ROAFACTURARE');
END IF;
END;
/
exec pack_migrare.UpdateVersiune('ff_<data>_NN_COMUN_OPTIUNI');
commit;
```
`ID_PRG_OWNER` omis (rand `NULL` in toate exemplele citate, coloana pare neutilizata activ pentru
optiuni de firma simple). `PROGRAME='ROAFACTURARE'` — restrans, spre deosebire de
`RF_CONT_INCASARE_*` care listeaza 6 produse; de largit doar daca se confirma (in afara scopului
acestei cercetari, sectiunea "Ce ramane de decis") ca alte produse din suita ajung sa apeleze aceeasi
ramura din `contabilizeaza_articol` prin `adauga_articol_factura` simplu.
### 3.5 Validare la salvare in ecran
**Nu** — confirmat la sectiunea 1.3: `frm_optiuni_nou` valideaza doar "camp nevid" pe cele 3 campuri
principale, nicio validare specifica per-cheie. Optiunea noua se comporta identic cu restul tabelei:
utilizatorul poate scrie orice text de pana la 1000 caractere in `VARVALUE`, cu riscurile descrise
la sectiunea 3.3.
---
## 4. Ce ramane de decis
1. **Numele exact al cheii** — `FACT_SCD_ARTFPRET` propus (sectiunea 3.1); Marius poate prefera alt
nume sau formatul mai scurt `SCD_FARA_POLITICA`.
2. **Se adauga o validare a contului la citire** (cont trebuie sa existe in planul de conturi) sau
se pastreaza tiparul actual, fara validare, ca la `RF_CONT_INCASARE_*`? Niciun exemplu existent nu
valideaza — a introduce validare ar fi prima data cand se face asta pe o optiune de tip cont.
3. **`PROGRAME` restrans la `ROAFACTURARE` sau extins** ca la `RF_CONT_INCASARE_*` (6 produse)? Tine
de raspunsul, inca deschis in `parametru_cont_contabilizeaza_articol.md` sectiunea 2d, la
intrebarea daca alte produse din suita apeleaza `adauga_articol_factura` simplu.
4. **Textul exact din `VARDESC`** — schita de mai sus e o propunere, nu o cerinta.
---
## Ce nu s-a putut stabili
- **Cine citeste azi `EFACTURA_CONT_ART_E`/`EFACTURA_CONT_ART_P`** (sectiunea 2d) — cautarea s-a
oprit la insertia originala; modulul `ROACONT`/eFactura nu a fost deschis, in afara scopului
cerut (exemplul a servit doar ca a patra confirmare de conventie de nume, nu ca lant complet).
- **Definitia view-ului VFP local `v_optiuni`** (folosit ca `RecordSource` in `frm_optiuni`) — e un
cursor/view generat dinamic prin `gencursor()`/`goExecutor.oExecute()` din `oinit_optiuni.prg`, nu
un obiect Oracle persistent (interogare `DBMS_METADATA` pe `V_OPTIUNI` a esuat cu "object not
found", asteptat) — nu schimba nimic din propunere, doar noteaza ca numele nu desemneaza o vedere
Oracle.
- **Daca `CONT411`/`DEVCONTALTELE` (sectiunea 2c) mai au vreun apelant in alt produs din suita**
(ROACONT, ROAGEST etc.) — cautarea s-a limitat la `SCRIPTURI_CLAR` (tot codul PL/SQL istoric); nu
s-a cautat in `.vc2`/`.prg` din alte COMUN-uri de produs.

View File

@@ -1,210 +0,0 @@
# Cercetare: `pack_auto.actualizeaza_deviz` si riscul asupra devizului ROAAUTO la #13
Scop: `plan_13_unificare_formular_facturare.md` vrea sa permita adaugarea unei linii noi pe o
factura deja emisa, inclusiv pe facturile ROAAUTO (`tip = -12`). Intrebarea: ce se intampla cu
**devizul** din ROAAUTO cand factura primeste o linie pe care devizul nu o stie. Punctul de
plecare a fost `pack_auto.actualizeaza_deviz`, apelat din
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1310`, al carei corp nu fusese citit inainte de
aceasta cercetare.
Continua `roaauto_facturi.md` si `roaauto_articole_lista_preturi.md` (deja citite, nu reiau
constatarile lor: acelasi `PACK_FACTURARE`, `VANZARI_DETALII_TEMP` -> `VANZARI_DETALII`,
`tip=-12`, liniile sintetice `-100000..-100008` plus liniile reale "Alte servicii", editorul
`frm_modific2024` read-only, `oviz_devize.vc2:4536` blocheaza modificarea dupa `nrfact`).
## 1. Unde e definit `PACK_AUTO`
`D:\ROA\DATABASE\SCRIPTURI_CLAR\<an>\<luna>\ff_..._AUTO_PACK_AUTO.sql` — 14 versiuni istorice
gasite (2013-2026). Sursa de adevar folosita (cea mai recenta, dupa conventia deja aplicata in
`roaauto_facturi.md` pentru `PACK_FACTURARE`):
**`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql`** (1804 linii,
antet "17.03.2026 / robert / creare procedura setOptiuneInchidere").
Semnatura in spec: `:74-76`. Corp: `:692-733`.
## 2. Ce face `actualizeaza_deviz`, pas cu pas
Corpul complet (`ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`):
```sql
procedure actualizeaza_deviz(tnProcTvav IN NUMBER,
tcSirIdOrdl IN VARCHAR2,
tnIdSet IN NUMBER) is
lcSeparator VARCHAR2(1) := ',';
lnIdFact DOCUMENTE.ID_DOC%TYPE;
begin
-- nu iau pack_contafin.get_idfact pentru ca s-ar putea sa am id_fact de la incasare, nu de la factura
SELECT MAX(ID_FACT)
INTO lnIdFact
FROM ACT
WHERE COD = pack_contafin.get_cod()
AND SCD NOT LIKE '5%';
UPDATE DEV_ORDL
SET PROC_TVAV = tnProcTvav
WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator)));
UPDATE /*+ INDEX(RUL IDX_RUL_001) */ RUL
SET ID_FACT = lnIdFact
WHERE ID_SET <> 229
AND ID_LUCRARE IN (SELECT ID_LUCRARE FROM DEV_ORDL
WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator))));
IF tnIdSet IN (31003, 31004, 31005, 31006, 31007, 31011) THEN
UPDATE NOM_LUCRARI
SET ID_FACT = lnIdFact
WHERE ID_LUCRARE IN (SELECT ID_LUCRARE FROM DEV_ORDL
WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator))));
END IF;
end actualizeaza_deviz;
```
Trei pasi, toti indexati pe `ID_ORDL`/`ID_LUCRARE` (comanda/lucrarea din ROAAUTO), **niciunul pe
`VANZARI`/`VANZARI_DETALII`**:
1. Citeste `lnIdFact` = cel mai mare `ID_FACT` din `ACT` pentru "codul" curent de sesiune
(`pack_contafin.get_cod()`), excluzand notele de incasare (`SCD NOT LIKE '5%'`) — comentariul
din cod explica de ce: la momentul apelului pot exista in `ACT` atat o nota de incasare cat si
nota/factura propriu-zisa, si vrea explicit factura.
2. `UPDATE DEV_ORDL SET PROC_TVAV = tnProcTvav` — scrie cota de TVA pe comenzile din
`tcSirIdOrdl` (lista CSV de `ID_ORDL`, despartita cu `charn2collection`).
3. `UPDATE RUL SET ID_FACT = lnIdFact WHERE ID_SET <> 229 AND ID_LUCRARE IN (...)` — leaga
inapoi randurile din `RUL` (jurnalul de manopera/materiale al lucrarii, folosit si la
`frm_inchidere_productie.do_executa` pentru totalul pe sectii, vezi §4) de documentul creat.
4. Doar daca `tnIdSet` e unul din `{31003,31004,31005,31006,31007,31011}`, acelasi `ID_FACT` se
scrie si pe `NOM_LUCRARI` (nomenclatorul de lucrari).
**Nu exista niciun `SELECT`/`UPDATE` pe `VANZARI` sau `VANZARI_DETALII` in `actualizeaza_deviz`**
si, verificat suplimentar, **in tot pachetul `PACK_AUTO`** (grep pe `VANZARI` in cele 1804 linii
ale fisierului: 0 potriviri). Deci raspunsul direct la intrebarea din brief: procedura **nu
recalculeaza niciun total al devizului din liniile facturii** — nu citeste liniile facturii deloc.
Ce face e sa "stampileze" `ID_FACT` (documentul de vanzare/nota abia creat) pe randurile din `RUL`
si `NOM_LUCRARI` legate de comenzile din `tcSirIdOrdl`, plus sa actualizeze cota TVA pe
`DEV_ORDL`. E o legatura *deviz -> document*, nu un recalcul *document -> total deviz*.
## 3. Ce se intampla cu o linie de factura pe care devizul nu o are
Raspunsul e neted, pentru ca premisa intrebarii (un `SUM` peste `VANZARI_DETALII`) nu exista in
cod: **procedura nu vede deloc liniile facturii**, nici cele cunoscute (sintetice
`-100000..-100008`), nici cele reale (nomenclator, cazul "Alte servicii" din raportul anterior),
nici una noua adaugata din afara. Ea opereaza exclusiv pe `ID_ORDL`/`ID_LUCRARE` (identitatea
comenzii ROAAUTO), primite ca parametru `tcSirIdOrdl` — nu deriva niciodata acest identificator
din continutul `VANZARI_DETALII`.
Precedentul "Alte servicii" (articole reale, `id_articol` pozitiv, cf.
`roaauto_articole_lista_preturi.md` §2) confirma exact acest comportament pe date deja live: acele
linii coexista cu liniile sintetice in `VANZARI_DETALII` de multa vreme, fara ca
`actualizeaza_deviz` sa faca vreo distinctie — pentru ca nu se uita la `VANZARI_DETALII` deloc.
**Consecinta pentru #13**: adaugarea unei linii noi pe `VANZARI_DETALII` pentru o vanzare
`tip=-12` nu poate "dezechilibra" ce scrie `actualizeaza_deviz`, pentru simplul motiv ca aceasta
procedura nu depinde de continutul `VANZARI_DETALII`. Nu exista *eroare*, nu exista *ignorare
explicita* — e o absenta totala de citire.
Important insa (nuanta pe care intrebarea din brief o presupune implicit gresit): asta **nu**
inseamna ca "devizul" (asa cum e prezentat operatorului in ROAAUTO) ar reflecta automat noua
linie. Totalul devizului afisat in `oviz_devize.vc2` e calculat client-side din `RUL` (vezi
`crsTotaluri`, construit cu un `SELECT SUM(...) FROM RUL ... GROUP BY ID_SECTIE` in
`oviz_devize.vc2:7816-7823`, in `frm_inchidere_productie.do_executa`) si din cursoarele
`crsdeviz`/`crsalteserv` calculate in `oproceduri_devize.prg` la momentul facturarii — niciodata
din `VANZARI_DETALII`. Deci o linie adaugata ulterior pe factura **nu va aparea niciodata** in
totalul devizului asa cum il calculeaza ROAAUTO, indiferent daca `actualizeaza_deviz` mai ruleaza
sau nu — pur si simplu acel total nu are o sursa care sa citeasca `VANZARI_DETALII`.
## 4. Cand e apelata
Cautare in ROAAUTO (`Grep` pe `actualizeaza_deviz`, cele trei aparitii de cod, plus istoric
`.??2`) si in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR` (niciun alt pachet Oracle nu o apeleaza — grep
pe `actualizeaza_deviz` in tot arborele SCRIPTURI_CLAR gaseste doar fisierele care *definesc*
`PACK_AUTO`/vechiul `PACK_DEVIZE`, nu vreun apel extern):
Trei puncte de apel, toate in VFP, niciunul intr-un trigger/job Oracle:
1. **La emiterea facturii** — `oproceduri_devize.prg:1310`, in acelasi bloc `lcSql` cu
`pack_facturare.scrie_in_vanzari` (linia 1302), cu `tnIdSet` dinamic (`lnIdSet`, calculat mai
sus in `factureaza_deviz`). Comentariu la linia 1311: *"am scos apelul catre
`pack_devize.dev_completeaza_rul`; e inclus in `actualizeaza_deviz`"* — confirma ca functia a
inlocuit o procedura mai veche cu acelasi scop (legare RUL -> document).
2. **`frm_inchidere_productie.do_executa`** (`oviz_devize.vc2:7896`, clasa la `:7143`) — cu
`tnIdSet` **fix, 31007** — apelata dupa ce formularul de **inchidere productie** scrie o
*nota contabila* (`oscrie_in_fisiere()` peste cursorul `actactan`, `:7889`), **nu** o factura;
nu apeleaza `pack_facturare.scrie_in_vanzari`, deci nu atinge `VANZARI` deloc pe acest drum.
3. **`frm_inchidere_regie.do_executa`** (`oviz_devize.vc2:8907`, clasa la `:8093`) — identic,
`tnIdSet` fix **31006**, pentru inchiderea de **regie** (overhead), tot printr-o nota
contabila, nu o factura.
Deci `actualizeaza_deviz` nu e specifica facturarii — e o operatie generica *"leaga
comenzile/lucrarile astea de ultimul document contabil creat pentru codul curent"*, refolosita si
la doua fluxuri de inchidere interna care nu produc facturi de vanzare. In niciunul din cele trei
cazuri nu e re-declansata mai tarziu (nu exista un al patrulea apel, nici din UI, nici din
schedule/trigger Oracle) — deci editarea ulterioara a unei facturi deja emise (obiectivul #13) nu
ar re-invoca automat aceasta procedura, pentru ca nimic din codul citit o cheama in afara celor 3
puncte de mai sus.
## 5. Alte proceduri din `PACK_AUTO` care citesc `VANZARI`/`VANZARI_DETALII` pentru un deviz
**Niciuna.** Grep pe `VANZARI` in intregul fisier `ff_2026_03_31_02_AUTO_PACK_AUTO.sql` (1804 de
linii, tot pachetul, nu doar `actualizeaza_deviz`) — 0 potriviri. `PACK_AUTO` nu are nicio
dependenta de tabelele de vanzari/facturare; toata legatura cu partea financiara trece prin `ACT`
(note contabile) si `DEV_ORDL`/`RUL`/`NOM_LUCRARI` (structura proprie ROAAUTO).
Singurul loc care **citeste** liniile facturii pentru re-listarea unui deviz e in afara
`PACK_AUTO`, pe partea VFP: `relisteaza_factura_deviz`
(`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1516-1576`, mentionata in raportul anterior).
Verificat acum sursa: interogheaza direct view-urile COMUN `fact_vfacturi`
(`:1531`, filtrat `WHERE cod = ...`) si **`fact_vfacturi_detalii`** (`:1564`,
`select * from fact_vfacturi_detalii where id_vanzare = <poDate.nid_vanzare>` — fara alt filtru).
View-ul e definit in
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2024\06\ff_2024_06_04_01_COMUN_FACTURARE.sql:8-94`:
`create or replace view fact_vfacturi_detalii as select ... from vanzari_detalii a left join
nom_articole b ... ` — un simplu wrapper peste `VANZARI_DETALII` cu join-uri de denumire, **fara
nicio clauza `WHERE`** care sa restranga la id-uri de articol cunoscute sau la liniile
"originale" ale devizului.
**Consecinta**: o linie noua inserata in `VANZARI_DETALII` pentru acel `id_vanzare` **va aparea
automat** la o re-listare/re-tiparire ulterioara prin `relisteaza_factura_deviz` — comportament
asteptat de la un view neconditionat, nu o stricare. Nu exista risc de eroare sau de excludere pe
acest drum; riscul (daca exista) e doar de **asteptare a utilizatorului**: factura retiparita va
arata linia noua, dar ecranul de deviz din ROAAUTO (total calculat din `RUL`, §3) nu o va arata
niciodata, pentru ca nu deriva din `VANZARI_DETALII`.
## 6. Concluzie operationala pentru #13
**Da, se poate adauga o linie pe o vanzare `tip=-12` fara sa "strice" `pack_auto.actualizeaza_deviz`
sau vreo alta procedura din `PACK_AUTO`** — pentru ca niciuna din ele nu citeste `VANZARI`/
`VANZARI_DETALII`; nu exista mecanism Oracle-side care sa recalculeze/verifice totalul devizului
din liniile facturii, deci nu exista nimic de dezechilibrat la acel nivel. Nu e nevoie de niciun
apel Oracle suplimentar din partea ROAFACTURARE ca sa "anunte" ROAAUTO despre linia noua —
`actualizeaza_deviz` n-ar face nimic util cu acea informatie oricum (opereaza pe `ID_ORDL`, nu pe
linii de factura).
Ce **nu** rezolva aceasta concluzie, si ramane responsabilitatea planului #13, nu a acestei
proceduri:
- **Desincronizare de afisare, nu de date**: totalul devizului aratat in ROAAUTO
(`oviz_devize.vc2`, calculat din `RUL`/cursoarele de facturare) nu va reflecta niciodata o
linie adaugata ulterior direct pe `VANZARI_DETALII` — nici azi (cu liniile "Alte servicii"),
nici cu o linie noua din editorul unificat. Daca produsul vrea ca devizul sa "stie" de linia
noua vizual, ar trebui cod nou in ROAAUTO (nu exista azi), nu un apel catre `actualizeaza_deviz`.
- **Reprintarea** (`relisteaza_factura_deviz`) va include automat linia noua (§5) — de verificat
daca asta e comportamentul dorit de produs sau o suprindere pentru operatorul ROAAUTO.
- Confirmat si direct in cod ROAFACTURARE: `Grep` pe `pack_auto` in
`D:\ROA\ROAFACTURARE` (inclusiv `COMUN`) nu gaseste nicio referinta in cod, doar in `docs\`
(planul #13 si aceste rapoarte) — azi ROAFACTURARE nu apeleaza deloc `PACK_AUTO`, confirmand ca
nu exista deja o presupunere ascunsa de sincronizare intre cele doua.
## Neverificat
- Ce reprezinta exact tabelele `ACT`/`RUL`/`NOM_LUCRARI`/`DEV_ORDL` la nivel de schema completa
(DDL/comentarii) — inteles doar din felul in care sunt folosite in cod, nu dintr-un dictionar de
date citit explicit.
- Daca vreun raport fiscal/SAF-T (mentionat generic in `ff_2022_03_24_01_COMUN_SAFT.sql`, nedeschis)
agrega `VANZARI_DETALII` intr-un mod care ar fi afectat de o linie noua pe `tip=-12` — in afara
ariei `PACK_AUTO` cerute, nu a fost investigat.
- Daca `frm_incasare_finala.do_recalculeaza_total`/`calculeaza_total` (`oviz_devize.vc2:6494,6700`)
ar putea fi reconciliate manual de un operator ca sa "vada" o linie adaugata ulterior — nu am
citit corpul acestor metode, doar semnalul ca exista.
- Istoricul complet al celorlalte 13 versiuni `AUTO_PACK_AUTO.sql` nu a fost comparat linie cu
linie cu cea din 2026-03 — presupun (conform conventiei deja aplicate pentru `PACK_FACTURARE`)
ca cea mai recenta e sursa de adevar pentru comportamentul curent din productie.

View File

@@ -1,173 +0,0 @@
# Verificare adversariala — proiectarea parametrului de cont (`canal_cont_venit_fara_politica.md`, sectiunea "Proiectarea parametrului de cont contabil")
Mandat: **nu re-proiecta** — proiectarea exista deja in `canal_cont_venit_fara_politica.md:13-227`
(citita integral). Aici: incercare de a o sparge + inchiderea celor 4 goluri semnalate de autor.
Sursa PL/SQL: aceeasi, `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii). Oracle: doar `SELECT`, schema `MARIUSM_AUTO`/`ROA_CENTRAL`. VFP: doar `vfp_symbols.ps1`
(index, read-only). **Niciun fisier de cod atins.**
## Verdict, in patru randuri
**Proiectarea rezista la verificarea adversariala** — nu am gasit nicio eroare care sa-i invalideze
concluzia centrala. Am gasit **un rand din tabel mai slab decat descris** (`CU_TVA=1` hardcodat NU e
complet inofensiv — are un efect colateral masurabil, minor, prin `nproc_tva_max`), **un rand mai
tare decat descris** (`IN_VALUTA` din `nin_valuta` e o sursa solida, nu doar plauzibila — parametru
obligatoriu, fara `DEFAULT`), **o excludere confirmata corecta prin structura schemei** (articolul
"compus" nu poate exista pe o linie fara politica — nu o gaura), si **un gol raportat de autor acum
inchis cu fapt** (cele doua clase de la `:14069`/`:18089` sunt distincte, nu o duplicare a aceleiasi
metode — 2 locuri VFP de atins, nu 1). Pe decizia 35 (regenerare = acelasi cod): **DA, calea Oracle
e identica**, dar exista un obstacol real, deja proiectat separat (`idfact_refolosire_si_documente.md`),
**in alt pachet** (`PACK_CONTAFIN`), nu in `pack_facturare` — nu invalideaza design-ul curent, dar
regenerarea nu e completa fara acea a doua bucata de lucru.
---
## 1. Decizia 35 — regenerarea foloseste identic `pack_facturare`?
**DA pe calea Oracle relevanta pentru parametrul de cont, cu o exceptie deja cunoscuta si separata.**
Verificat direct: `scrie_factura2` reseteaza starea de sesiune la fiecare apel prin
`initializeaza_date_factura` (`PACK_FACTURARE:1808-1846`), care face
`DELETE FROM VANZARI_DETALII_TEMP;` (`:1835`) si `pack_facturare.nid_act := 0;` (`:1836`) —
**contorul folosit de `scrie_nota`/`scrie_discount`/`scrie_tva` la fiecare `INSERT INTO ACT_TEMP`
porneste curat la fiecare emitere, inclusiv la o a doua** (regenerare). Nimic in
`contabilizeaza_articol`, ramura noua inclusa, citeste vreo stare care ar presupune "documentul e
nou" — toate variabilele folosite (`nid_venchelt`, `nid_sectie_stoc`, `nin_valuta`, `nid_set`,
`nid_util`) sunt **citite**, nu verificate contra unei stari anterioare.
**Obstacolul real e in `PACK_CONTAFIN`, nu in `pack_facturare`**: reemiterea cu acelasi `ID_FACT` (ca
sa nu se dubleze documentul contabil la regenerare) ar da azi `ORA-00001` pe `PK_DOCUMENTE`, pentru ca
`SET_IDFACT` ia mereu `SEQ_IdFact.NEXTVAL` si documentul vechi ramane in tabel doar soft-sters
(`STERS=1`), nu disparut — analiza completa, cu solutia (variabila de pachet noua in `PACK_CONTAFIN`,
`MERGE ... WHEN MATCHED` pe `DOCUMENTE`), e deja facuta integral in
`idfact_refolosire_si_documente.md` (sectiunile A-C). **E exact "ceva care ar cere cod separat" — dar
separat inseamna alt pachet/alta procedura (`SET_IDFACT`, scrierea in `DOCUMENTE`), nu o ramura in
plus in `contabilizeaza_articol` sau `adauga_articol_factura`.** Ramane o bucata de lucru distincta,
necesara pentru ca regenerarea sa fie completa, dar nu intersecteaza si nu invalideaza design-ul
parametrului de cont.
**Un gol neadresat de design, gasit acum**: ramura `pack_facturare.ntip = 4` ("facturare din avize",
`:7520-7537`) apeleaza `scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount — design-ul
nu spune ce se intampla pe aceasta ramura in cazul fallback. In practica, **probabil nu conteaza**:
"facturare din aviz" cere azi ca linia sursa sa aiba deja `id_pol` (`adauga_articol_factura:5096`,
`AND A.ID_POL = V_ID_POL` pe `VANZARI_DETALII` a avizului sursa) — un aviz fara politica n-ar fi
ajuns niciodata pana la factura pe acest drum, deci scenariul "fallback pe `ntip=4`" e putin probabil
sa apara. **Dar design-ul nu spune asta explicit** — de adaugat o linie care sa acopere/exclude
explicit acest caz inainte de implementare, nu de presupus tacit.
## 2. Cele 4 goluri semnalate de autor
### 2a. `ofacturare.vc2:14069`/`:18089` — doua metode sau o duplicare?
`vfp_symbols.ps1 -Where 'ofacturare.vc2:14069'` -> **`frm_facturare_articole.do_scrie_articole`**
(`ofacturare.vc2:13967-14195`). `-Where 'ofacturare.vc2:18089'` -> **`frm_facturare_articole2.do_scrie_articole`**
(`ofacturare.vc2:18003-18221`). **Confirmat: doua clase distincte, `frm_facturare_articole` si
`frm_facturare_articole2`, fiecare cu propria metoda `do_scrie_articole`** (acelasi nume de metoda,
nu aceeasi clasa) — nu o duplicare literala a unui singur cod. **Consecinta pentru implementare**:
parametrul nou trebuie cablat **in doua locuri VFP**, nu unul — ambele clase construiesc separat
apelul RPC catre `adauga_articol_factura`. Nu schimba verdictul de fezabilitate, dar schimba
suprafata de lucru VFP fata de impresia "un singur loc de atins".
### 2b. `scrie_tva` la cota 0% cu `CU_TVA` hardcodat `1` — efect real, nu inofensiv
Verificat corpul `scrie_nota` (`:12537-12558`): actualizarea `nproc_tva_max`/`nid_jtva_coloana`/
`nTaxCode` **nu e neconditionata** — e in interiorul `IF V_CU_TVA = 1 THEN`, deci exact conditionata
de flagul pe care design-ul propune sa-l hardcodeze. In interior insa, comparatia
`IF pack_facturare.nproc_tva_max < V_PTVA THEN` **nu se uita la suma**, doar la rata — deci daca
articolul fallback are `proc_tvav` real 0% (scutit) si `CU_TVA` e fortat la `1`, rata 0% **intra in
comparatia de maxim** si, daca e prima/singura linie a documentului (`nproc_tva_max` initial `-1`,
`:1885`), **castiga** — seteaza `nid_jtva_coloana`/`nTaxCode` la valorile acelei linii scutite.
Aceste doua variabile sunt consumate mai departe **in aceeasi procedura `scrie_factura2`**, la
discountul global pe factura (`:6164-6184`, `V_DISCOUNT_FACTURA <> 0`): rata/coloana/taxcode ale
liniei scutite ar ajunge sa descrie linia de discount a **intregii facturi**, chiar daca alte linii
au TVA real. **Verdict: `CU_TVA=1` hardcodat nu e "inofensiv" in toate cazurile cum spune design-ul —
are un efect colateral real, dar restrans la o combinatie specifica (linie fallback cu TVA 0% +
discount global pe factura + acea linie e cea cu rata "maxima" vazuta pana atunci).** Nu invalideaza
alegerea `CU_TVA=1` ca implicit (majoritatea liniilor reale au TVA nenul), dar intareste, nu slabeste,
cererea deja facuta de autor ("de confirmat cu Marius") — motivul de confirmat e mai concret decat
"linie de TVA cu suma 0, inofensiv".
### 2c. `INSERT INTO VANZARI_DETALII ... FROM VANZARI_DETALII_TEMP` — linie exacta, confirmata
`PACK_FACTURARE:13705-13757`, in `scrie_in_vanzari` (spec `:13488`, apelata din `finalizeaza_factura`
`:14787`, care e apelata la finalul fluxului de verificare — nu din `scrie_factura2`, alta faza a
aceluiasi pipeline). **Confirmat: lista de coloane e explicita**, 24 coloane
(`ID_VANZARE, ID_ARTICOL, LOT, SERIE, ID_RATA, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
PRET, PROC_TVAV, ID_JTVA_COLOANA, ID_JTVA_COLOANA_EX, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT,
EXPLICATIE, PRET_CU_TVA, DIFERENTA, CUSTODIE, ID_VANZARE_SET, ID_CTR, TAXCODE`) — **nu** `SELECT *`.
Exact ce presupunea design-ul (sectiunea 2, citand `nota_contabila_fara_politica.md` fara
re-verificare): daca se doreste ca `CONT_VENIT` sa ajunga si in `VANZARI_DETALII` (trasabilitate),
**aceasta lista trebuie extinsa explicit** — fara acest pas, coloana noua ramane doar pe
`VANZARI_DETALII_TEMP` si `ACT_TEMP`, invizibila in `VANZARI_DETALII` dupa fapt. Confirmarea nu
schimba verdictul design-ului (deja marcase pasul ca "recomandat, nu strict necesar"), doar il
transforma din presupunere in fapt verificat.
### 2d. Apelanti `adauga_articol_factura` din restul suitei
Sarit, cum a cerut team-lead-ul — alt agent lucreaza pe suprafata de regresie.
---
## 3. Verificare adversariala pe tabelul din design (sectiunea 3 a raportului sursa)
| Rand din tabel | Incercare de infirmare | Rezultat |
|---|---|---|
| `IN_VALUTA` <- `pack_facturare.nin_valuta` | E setat pe toate fluxurile relevante, sau ramane nul si azi nu conteaza? | **Mai solid decat descris.** `nin_valuta := V_IN_VALUTA` (`:1902`) e in `initializeaza_date_factura`, apelata **o data la inceputul fiecarei emiteri**, cu `V_IN_VALUTA IN NUMBER` **fara `DEFAULT`** in semnatura (`:1827`) — VFP e obligat sa trimita o valoare, nu poate omite parametrul. Nu e un fallback de sesiune care "poate ramane nesetat" (ca `nid_venchelt`), e un flag de document obligatoriu, mereu curent. |
| `ID_VENCHELT`/`ID_SECTIE` <- variabile de sesiune | Cine le seteaza si cand? | **Confirmat, cu nuanta.** `nid_venchelt := V_ID_VENCHELT` si `nid_sectie_stoc := V_ID_SECTIE` (`:1876-1877`), tot in `initializeaza_date_factura`, din parametri **fara `DEFAULT`** insa **VFP poate trimite `NULL`** pe ei (sunt opționale ca *valoare*, nu ca prezenta in apel) — deci pot ramane `NULL` pe tot documentul. **Nu e o regresie**: pe ramura veche, `NVL(nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))` ar da tot `NULL` daca nici politica nu are aceste campuri — acelasi rezultat posibil, aceeasi cauza (sesiune nesetata), nu unul nou introdus de fallback. |
| Garda `descarca_gestiune` copiata identic | Acopera `id_gestiune=-1000` si `in_stoc`? | **Da, fara diferenta.** Garda foloseste `detalii_articol.id_gestiune`/`.in_stoc` — campuri populate identic de `adauga_articol_factura` indiferent de ramura (nu depind de politica azi, nici in design). Copierea literala a conditiei (`:7473-7475`) e corecta prin constructie. |
| `ASCD`/`ASCC` <- `GetAnaliticByGrupUtilizatori` | Ce intoarce pe `NO_DATA_FOUND`, e acceptabil? | **Confirmat `NULL`** (corp citit `:16704-16723`: `lcAcont` declarat fara valoare implicita, `EXCEPTION WHEN NO_DATA_FOUND THEN NULL;`, `RETURN lcAcont`). Acceptabil — e exact fallback-ul deja folosit azi, necondiționat, pe ramurile de aviz (`:7416-7417,7421-7422`) si `ACT_TEMP.ASCD/ASCC` sunt nullable (confirmat DDL, `canal_cont_venit_fara_politica.md` DDL section). Nu e un risc nou. |
| Articol "compus" (`V_COMPUS=1`) lasat in afara scopului | Poate un articol ales ad-hoc din nomenclator fi compus? | **NU — exclus prin structura schemei, nu prin presupunere.** Interogat direct `ALL_VIEWS.VCRM_POLITICI_PRET_ART`: coloana `COMPUS` citita de `contabilizeaza_articol` (`SELECT COMPUS, ID_POL_ART ... FROM VCRM_POLITICI_PRET_ART`, `:7279-7283`) e definita in view ca `CASE WHEN PA.ID_POL_ART IN (SELECT DISTINCT ID_PACHET FROM CRM_PACHETE_ARTICOLE WHERE STERS=0) THEN 1 ELSE 0 END` — o proprietate a **perechii (articol, politica)**, identificata prin `ID_POL_ART` (cheia surogat a randului din `CRM_POLITICI_PRET_ART`). O linie fara politica **nu are** un `ID_POL_ART` — deci intrebarea "e articolul compus" nu se poate pune structural pe aceasta ramura. (`NOM_ARTICOLE.COMPUS` exista ca alta coloana, aliasata `art_compus` in acelasi view, dar **nu e citita de `contabilizeaza_articol`** — irelevanta aici.) Excluderea din design e corecta, nu o gaura. |
| `RETURN V_INCASAT_CALCUL` | Se calculeaza corect pe ramura noua sau intoarce 0? | **Corect, prin constructie.** Design-ul specifica explicit aceeasi acumulare ca azi (`V_INCASAT_CALCUL := V_INCASAT_CALCUL + scrie_nota(...)`, apoi `- scrie_discount(...)`), cu `V_INCASAT_CALCUL` initializat `0` la declaratie (`:7178`), inainte de `IF`-ul care selecteaza ramura — identic cu azi. Niciun risc de `RETURN 0` gasit. |
---
## 4. Hardcodarile `SCD='4111'` / `CU_TVA=1` — exista o sursa mai buna?
Cautare directa in tot `PACK_FACTURARE` pentru orice sursa alternativa care nu trece prin
`NOTE_CONTABILE`: config de firma (`getoptiunefirma('CONT...')`), cont implicit pe partener/client
(`PARTENERI.CONT*`), flag de scutire TVA pe articol/client (`SCUTIT`, `EXCEPTAT`) — **zero rezultate
pentru toate cele trei cautari**. Singurul camp inrudit gasit, `ACT_TEMP.NEIMPOZAB`, e folosit in alt
scop (raportare sume neimpozabile), nu ca sursa pentru `CU_TVA`. **Concluzie: nu exista o sursa mai
buna in cod — hardcodarea (sau parametrul explicit trimis de VFP) ramane singura optiune.** Asta
intareste recomandarea deja facuta de autor: **de decis explicit cu Marius, nu de dedus din date**,
mai ales pentru `CU_TVA` dupa gasirea de la punctul 2b (efectul via `nproc_tva_max` nu mai e
"pur cosmetic").
---
## 5. Completare: `goExecutor.oExecuta` si cursorul de verificare — fals pozitiv, nu bug
Verdict: **fals pozitiv, confirmat cu argumente, nu doar presupus.** `oExecuta` (funcție-wrapper,
`COMUN\programe\oproceduri_comune.prg:121-159`) deleagă la `oExecute` (`:173-504`), care la rândul ei
face `SQLExec(lnHandle, lcSql, lcCursor)` (`:330`) — **`lcSql` e folosit exact cum a fost construit de
apelant, fără nicio rescriere care să adauge un bind lipsă.** Textul construit în
`COMUN\clase\ofacturare.vc2:14345-14359` (și identic la `:14373-14387`, `:18343-18371`) e sintaxă
ODBC escape `{call pack_facturare.scrie_factura2(...)}`, cu **16** argumente poziționale (15 `IN` +
`?@poDate.nid_vanzare` pentru `V_ID_VANZARE OUT NUMBER`, al 16-lea parametru declarat), paranteza se
închide imediat după — **al 17-lea parametru, `V_CURSOR_VERIFICARE OUT pack_facturare.cursor_facturare`
(spec `PACK_FACTURARE:656`), nu are niciun placeholder în text, nici legat, nici nelegat.** Asta e
**exact tiparul cunoscut al driverului ODBC Oracle pe care VFP îl folosește prin `SQLExec`**: cand
ultimul parametru declarat al unei proceduri e un `REF CURSOR OUT`, driverul îl detectează din
catalogul Oracle (nu din textul apelului) și întoarce automat rândurile lui ca *result set* al
apelului — **al treilea argument al `SQLExec`/`oExecute` (numele de cursor VFP, aici `lcCursorVerificare`)
e exact mecanismul de captare a acelui result set**, fără sa fie nevoie de bind explicit. Codul imediat
după apel (`ofacturare.vc2:14394-14397`, `llReturn = goExecutor.oExecuta(lcSql,lcCursorVerificare)`
urmat de `If Reccount(lcCursorVerificare)>0`) tratează cursorul ca fiind deja populat cu rânduri —
comportament incompatibil cu un apel eșuat sau cu un parametru nelegat (care ar da eroare Oracle,
nu un cursor gol interpretabil). **Nu pot confirma mecanismul din interiorul driverului însuși** (e
extern codebase-ului, verificabil doar prin comportament) — dar dovada indirectă e puternică: același
tipar apare identic în toate cele 4 locuri de apel găsite, neschimbat de-a lungul mai multor versiuni
(`v 2.0.13` -> `v 2.0.93`, comentarii de istoric vizibile în cod), iar ecranul de verificare (funcție
activă, folosită la fiecare emitere) depinde de acest cursor populat — dacă apelul ar eșua silențios,
ecranul de verificare n-ar arăta niciodată note propuse, ceea ce ar fi fost observat imediat. **Concluzie
pentru plan: anomalia nu există — nu trebuie tratată ca risc pe cele 7 produse.**
## Ce ramane neverificat
- Comportamentul exact al ramurii `ntip=4`/`scrie_fact_aviz_custodie` in scenariul fallback (punctul
1) — argumentat ca improbabil, nu testat/exclus explicit in design.
- Daca vreun raport Oracle activ presupune `ACT_TEMP.ID_VENCHELT`/`ID_SECTIE` populate pe liniile de
venit (relevant doar daca sesiunea nu seteaza `nid_venchelt`/`nid_sectie_stoc`) — in afara scopului
acestei verificari (cod PL/SQL only).
- Suprafata de regresie la nivelul apelantilor `adauga_articol_factura` din restul suitei — explicit
lasata altui agent.

View File

@@ -1,272 +0,0 @@
# Cercetare: calea VFP->Oracle la emitere si calea propusa pentru editare (#6, punctul E.4)
Sursa: interogari SELECT proaspete pe `MARIUSM_AUTO@ROA_CENTRAL` (08.08.2026), acelasi mediu si
aceeasi versiune de baza ca in `rec_s5_oracle_vanzari.md` (nu s-a schimbat nimic intre cele doua
cercetari din aceeasi zi). Numerele de linie PL/SQL de mai jos sunt dintr-un export propriu, facut
in aceasta sesiune, din `all_source` pentru `PACK_FACTURARE` (schema/pachet identic cu cel din
raportul anterior). Partea VFP e din cache-ul text `.vc2` din proiect (nu binarul).
Aceasta cercetare inchide golul lasat de `rec_s5_oracle_vanzari.md`, sectiunea E.4: cum ajung
liniile facturii in Oracle la emitere si care e calea corecta pentru editare.
## 1. Calea de azi, capat la capat
### 1.1 VFP: cine populeaza `VANZARI_DETALII_TEMP`
Raspuns: **Oracle**, prin apeluri per-linie facute din VFP, nu VFP direct prin INSERT.
- `frm_facturare_articole.do_scrie_articole` (`COMUN\clase\ofacturare.vc2:13967-14195`):
- `:13981-13999` cheama `pack_facturare.initializeaza_date_factura(...)` (antetul documentului,
tine minte parametrii in stare de sesiune pe pachet: `pack_facturare.ntip`, `nid_sucursala`,
`nid_util` etc. — folosite mai jos de `adauga_articol_factura`).
- `:14036-14115`: `SELECT crsfactura` -> `SCAN`, cate un `Scatter Name poArt MEMO` per linie, apoi
pentru fiecare linie fara rata de contract (`ELSE` la `:14051`) construieste text PL/SQL literal
(nu `{call}` cu parametri legati) si apeleaza
`pack_facturare.adauga_articol_factura(id_temp, id_articol, serie, explicatie, id_pol,
id_gestiune, pret_achizitie, pretd, id_valutad, pret, id_valuta, cu_tva, gestionabil,
cantitate, discount, cont, curs, multiplicator, id_jtva_coloana, id_part_rez, id_lucrare_rez,
pretv_orig, id_set_fact, id_ctr, gnIdUtil, taxcode, lot)` (`:14069-14091`), executat imediat
prin `goExecutor.oExecute(lcSql)` (`:14105`) — **un apel Oracle per linie de factura**, in
bucla, nu un singur apel cu tot cursorul.
- Pentru liniile din seturi: `pack_facturare.initializeaza_seturi_temp` + un apel
`pack_facturare.adauga_articol_set(...)` per linie de set (`:14117-14154`), scrie in
`VANZARI_SETURI_TEMP`.
- `frm_facturare_articole.do_scrie_factura` (`:14197-14554`): dupa ce toate liniile au fost urcate
in TEMP prin apelurile de mai sus, apeleaza finalizarea documentului —
`pack_facturare.scrie_factura2(...)` (facturi, `:14345`/`:14373`) sau
`pack_facturare.scrie_proforma(...)` (`:14286`) sau `scrie_factura_avize(...)`/
`scrie_factura_avize_retur(...)` dupa tipul documentului — **un singur apel**, fara sa mai
transmita liniile (ele sunt deja in `VANZARI_DETALII_TEMP`, populata de bucla anterioara, in
ACEEASI sesiune/tranzactie Oracle).
### 1.2 Oracle: `adauga_articol_factura` — nu doar INSERT, RECALCULEAZA din sursa originala
Verificat sursa completa a procedurii publice `pack_facturare.adauga_articol_factura` (semnatura cu
`V_ID_TEMP` ca prim parametru, cea apelata de VFP la `:14069`) — **nu** e un simplu INSERT cu
valorile primite de la VFP. Ramifica pe `pack_facturare.ntip` (starea de sesiune setata la
`initializeaza_date_factura`) si **re-deriva** `V_PRET`, `V_PROC_TVAV`, `V_ID_VALUTA`,
`V_PRETURI_CU_TVA`, `V_IN_STOC` direct din documentul-sursa:
- `ntip IN (3,21,28,42,47)` (facturare din comenzi): `SELECT ... FROM COMENZI_ELEMENTE ...`
- `ntip = 4` (facturare din avize): `SELECT ... FROM VANZARI_DETALII ...` (avizul deja scris)
- `ntip = 45` (restaurant): calcul din `JTVA_COLOANE` + `CRM_POLITICI_PRET_ART`
- `V_OPT_FACTURARE = 3` (contract): `SELECT ... FROM CTR_ARTICOLE ...`
- fallback (`ELSE`): doar cota TVA din `JTVA_COLOANE`, restul (`V_PRET`, `V_ID_VALUTA`,
`V_PRETURI_CU_TVA`, `V_IN_STOC`) preluate ca atare din parametrii transmisi de VFP.
Abia dupa acest bloc `CASE` urmeaza INSERT-ul propriu-zis:
```sql
INSERT INTO VANZARI_DETALII_TEMP
(ID_TEMP, ID_ARTICOL, SERIE, LOT, EXPLICATIA, ID_POL, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
PRET, PRET_CU_TVA, PROC_TVAV, CANTITATE, DISCOUNT_UNITAR, ID_GESTIUNE, CONT, ID_VALUTA,
CURS, MULTIPLICATOR, ID_JTVA_COLOANA, IN_STOC, ID_VANZARE_SET, ID_PART_REZ, ID_LUCRARE_REZ,
PRETV_ORIG, CUSTODIE, ID_CTR, ID_UTIL, TAXCODE)
VALUES (...)
```
(am confirmat integral corpul, INSERT-ul e ultimul bloc din procedura, imediat dupa `END CASE`).
**Consecinta directa pentru #6**: `adauga_articol_factura` NU poate fi reutilizata ca atare pentru
editare — depinde de starea de sesiune `pack_facturare.ntip`/`clistaid`/`id_ctr` care descrie
DOCUMENTUL SURSA de la emitere (comanda/aviz/contract), stare care nu exista si nu are sens la o
editare ulterioara a facturii deja emise. Orice procedura noua pentru editare trebuie sa scrie
direct in `VANZARI_DETALII` (tabela reala), nu prin acest drum.
Exista si `sterge_articol_factura(V_ID_TEMP, V_ID_UTIL)` — `DELETE FROM VANZARI_DETALII_TEMP WHERE
ID_TEMP = V_ID_TEMP` — folosita doar cat timp factura e in curs de compunere (inainte de emitere),
nu dupa.
### 1.3 Oracle: `scrie_factura2` -> `finalizeaza_factura` -> `scrie_in_vanzari` — trecerea TEMP -> real
`scrie_factura2` (PACK_FACTURARE, corpul activ, nu varianta comentata din cod):
- `:70` `pack_contafin.sterge_temp_actrul()`, seteaza `pack_facturare.ntotftva/ntottva` din
parametri (valori calculate in VFP).
- `:84` `SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP` — citeste TOATA tabela
temp intr-un array PL/SQL, fara filtru pe sesiune (GTT-ul e oricum izolat per sesiune/tranzactie).
- Bucla pe `tab_detalii`, ramificata tot pe `ntip` (transfer intre subunitati, restaurant etc.) —
pentru cazul standard de factura apeleaza mai departe spre `pack_facturare.finalizeaza_factura`.
`finalizeaza_factura` (nu comentata):
```sql
pack_facturare.initializeaza_scriere_actrul(V_DATAORA);
pack_facturare.scrie_in_vanzari(V_DISCOUNT_FACTURA, V_ID_DELEGAT, V_ID_MASINA, V_ID_FACTURARE,
V_LISTARE_DETALIATA, V_DATAORA_EXP, V_ID_AGENT, V_TEXT_ADITIONAL, pack_facturare.nid_vanzare);
pack_facturare.finalizeaza_scriere_actrul();
-- update vanzari set id_fact = ... (completare id_fact dupa scrierea in contabilitate)
```
`scrie_in_vanzari` — gasit exact mecanismul care lipsea din raportul anterior:
- `INSERT INTO VANZARI (...) VALUES (...) RETURNING ID_VANZARE INTO pack_facturare.nid_vanzare`
(randul parinte, un rand per document din bucla pe `tab_vanz`, tabela interna cu documentele de
scris — cazul normal e un singur document).
- `pack_facturare.scrie_cursuri(pack_facturare.nid_vanzare)`.
- **Trecerea efectiva TEMP -> real**:
```sql
INSERT /*+ APPEND */ INTO VANZARI_DETALII
(ID_VANZARE, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, ID_JTVA_COLOANA)
SELECT pack_facturare.nid_vanzare, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD,
ID_VALUTAD, PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA,
ID_JTVA_COLOANA
FROM VANZARI_DETALII_TEMP
WHERE ID_COMANDA = tab_vanz(i).id_comanda AND NUMAR_ACT = tab_vanz(i).numar_act
ORDER BY ID_TEMP;
```
Cheia de potrivire intre randul nou din `VANZARI` si liniile lui din TEMP e
`(ID_COMANDA, NUMAR_ACT)` — nu `ID_TEMP` direct — pentru ca un singur apel poate scrie mai multe
documente `VANZARI` deodata (facturare pe mai multe comenzi/numere de act simultan); fiecare
document isi ia doar liniile care se potrivesc.
- Coloanele COPIATE in `VANZARI_DETALII` sunt un subset din `VANZARI_DETALII_TEMP` (16 coloane) —
`ID_TEMP`, `ID_CTR`, `ID_UTIL`, `TAXCODE`, `LOT`, `ID_VANZARE_SET`, `ID_PART_REZ`,
`ID_LUCRARE_REZ`, `PRETV_ORIG`, `CUSTODIE`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST`
raman DOAR in TEMP, nu se copiaza in tabela reala (`VANZARI_DETALII` nu are coloanele `ID_TEMP`,
`NUMAR_ACT`, `ID_COMANDA` etc. — confirmat din `all_tab_columns`, vezi 2.2).
- Urmeaza blocul de agregare (`SELECT INTO` din `VANZARI_DETALII_TEMP`, deja documentat in
`rec_s5_oracle_vanzari.md` A.3-A.4) si `UPDATE VANZARI SET total_fara_tva=..., ...` cu totalurile
denormalizate.
**PK-ul `VANZARI_DETALII.ID_VANZARE_DET`** se genereaza automat: trigger `TRG_VANZARI_DET_BEFOINS`
(`BEFORE INSERT ... FOR EACH ROW`, verificat prin `dbms_metadata.get_ddl`) face exclusiv
`SELECT SEQ_VANZARI_DETALII.NEXTVAL INTO :NEW.ID_VANZARE_DET FROM DUAL` — nu seteaza alte coloane.
Deci **orice INSERT nou in `VANZARI_DETALII`** (inclusiv unul scris pentru #6, la adaugare de linie
noua la editare) primeste automat PK-ul corect fara sa fie nevoie de secventa apelata manual din
codul de editare.
## 2. Natura tabelei temp si structura
### 2.1 `VANZARI_DETALII_TEMP` — Global Temporary Table, `ON COMMIT DELETE ROWS`
Confirmat din `all_tables`:
| TABLE_NAME | TEMPORARY | DURATION |
|---|---|---|
| VANZARI | N | — |
| VANZARI_DETALII | N | — |
| VANZARI_DETALII_TEMP | **Y** | **SYS$TRANSACTION** |
| VANZARI_SETURI_TEMP | **Y** | **SYS$TRANSACTION** |
`DURATION = SYS$TRANSACTION` inseamna GTT cu `ON COMMIT DELETE ROWS` — randurile dispar la commit
(sau rollback), nu doar la sfarsit de sesiune. **Consecinta directa**: orice solutie care ar
reutiliza `VANZARI_DETALII_TEMP` pentru editare (#6) trebuie sa umple tabela SI sa consume
rezultatul in ACEEASI tranzactie — nu poate fi umpluta intr-un pas si citita in altul, ca in fluxul
de emitere unde umplere (bucla `adauga_articol_factura`) si consum (`scrie_factura2`) se intampla
deja in aceeasi conexiune/tranzactie, inainte de commit.
### 2.2 Coloane: `VANZARI_DETALII` vs `VANZARI_DETALII_TEMP`
`VANZARI_DETALII` (35 coloane, din `all_tab_columns`) — cheie primara `ID_VANZARE_DET` (NOT NULL,
generata din secventa prin trigger), FK logic `ID_VANZARE` (NOT NULL). Coloane relevante pentru
editare: `PRET` (NOT NULL), `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `STERS`
(NOT NULL), `VALIDAT`/`DATAORA_VALID`/`ID_UTIL_VALID`, `ID_UTILS`/`DATAORAS` (audit),
`DIFERENTA` (NOT NULL), `CUSTODIE`/`DESCARCAT` (NOT NULL) — ultimele patru nu au valoare implicita
vizibila in trigger, deci probabil `DEFAULT` la nivel de coloana (nu s-a verificat separat, nu era
in scope).
`VANZARI_DETALII_TEMP` (28 coloane) — **fara PK real** (`ID_TEMP` e generat in VFP, folosit doar ca
identificator temporar de linie in cadrul sesiunii curente de compunere a facturii), **fara**
`ID_VANZARE`/`ID_VANZARE_DET`/`STERS`/`VALIDAT` (nu se aplica inainte de a exista documentul
parinte). Are in schimb coloane specifice etapei de compunere, care NU exista in tabela reala:
`ID_TEMP`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST`, `ID_UTIL` (vs `ID_UTILS` in cea reala).
Coloanele comune folosite de editare (S4): `ID_ARTICOL`, `PRET`, `CANTITATE`, `PRET_CU_TVA`,
`DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`, `ID_VALUTA`, `CURS` (doar in TEMP; in
`VANZARI_DETALII` nu exista `CURS` per linie — cursul e doar pe document, in `VANZARI_CURSURI`/
`VANZARI.CURS`), `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`/`EXPLICATIA` (nume diferit!), `TAXCODE`,
`LOT`.
## 3. Stergerea si adaugarea de linii
### 3.1 Stergere — mecanism EXISTENT azi doar la nivel de document intreg, NU per linie
Cautat explicit orice `UPDATE VANZARI_DETALII SET STERS` in `PACK_FACTURARE` — gasite doar doua
locuri, ambele sterg TOATE liniile unui document, niciodata o singura linie:
- `sterge_factura(V_ID_VANZARE, ...)`:
`UPDATE VANZARI_DETALII SET STERS = V_STERS, ID_UTILS = V_ID_UTIL, DATAORAS = V_DATAORA WHERE
ID_VANZARE = V_ID_VANZARE AND STERS = V_NESTERS` (V_STERS variaza 0/1 dupa tipul documentului,
cazul normal fiind 1).
- `sterge_proforma(V_ID_VANZARE, ...)`: acelasi tipar, exclusiv pe proforme.
**Nu exista azi un mecanism Oracle sau VFP de stergere per-linie in `VANZARI_DETALII`** — orice
"stergere de linie la editare" ceruta de S4/S4b e functionalitate NOUA, de adaugat la #6, nu o
reutilizare a ceva existent.
Exista insa un precedent apropiat de UPDATE punctual per linie, util ca sablon:
`modifica_explicatie_articol(V_ID_VANZARE_DET, V_EXPLICATIE, V_ID_UTIL, V_TAXCODE)` —
`UPDATE VANZARI_DETALII SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE WHERE ID_VANZARE_DET =
V_ID_VANZARE_DET` — exact modelul de "UPDATE tintit prin `goExecutor`" mentionat ca varianta in
planul S5/S6, deja folosit azi din `frm_modifica_articol_factura` (mostenire de la #7, vezi
`plan_06_editare_factura.md`).
### 3.2 Adaugare de linie noua — nu cere nimic special in plus fata de un INSERT direct
`ID_VANZARE_DET` vine automat din `SEQ_VANZARI_DETALII` prin trigger la orice `INSERT INTO
VANZARI_DETALII`, indiferent de cine face insert-ul (nu doar fluxul de emitere) — vezi 1.3. Deci
adaugarea unei linii noi la editare **nu** cere obtinerea manuala a unui ID din secventa; e suficient
un `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE, PRET_CU_TVA, ...) VALUES
(...)` (fara `ID_VANZARE_DET` in lista de coloane) intr-o procedura noua, apelata din
`finalizeaza_modificare_nota` alaturi de recalculul de totaluri propus in `rec_s5_oracle_vanzari.md`
punctul B.
## 4. Propunerea pentru S4/S5/S6 — cum trimite editarea liniile modificate in Oracle
### Optiuni comparate
**A. Refolosirea `VANZARI_DETALII_TEMP` + o "mini scrie_in_vanzari" pentru editare** — ar insemna ca
formularul de editare (S4) sa populeze TEMP la fel ca la emitere (apeluri per linie), apoi o
procedura noua sa faca INSERT-urile/UPDATE-urile in `VANZARI_DETALII` din TEMP, in aceeasi
tranzactie (impusa de `ON COMMIT DELETE ROWS`, vezi 2.1). Risc: reproduce complexitatea lui
`adauga_articol_factura` (ramificare pe `ntip`/sursa document) fara sa aiba sens la editare — acolo
nu exista comanda/aviz/contract "curent" din care sa se re-deriva pretul; ar trebui un mod nou,
simplificat, de populare a TEMP doar pentru editare, ceea ce complica inutil un cod deja incarcat.
Risc mediu-mare de regresie pe calea de emitere daca vreo modificare atinge accidental
`adauga_articol_factura`/`scrie_in_vanzari` partajate.
**B. UPDATE/INSERT/soft-DELETE punctual direct in `VANZARI_DETALII`, prin `goExecutor`, dintr-o
procedura noua dedicata editarii** — fara sa treaca deloc prin `VANZARI_DETALII_TEMP`. Model deja
existent si folosit: `modifica_explicatie_articol` (UPDATE tintit pe `ID_VANZARE_DET`). Pentru cele
trei operatii cerute de S4/S4b:
- **Editare cantitate/pret/`pret_cu_tva`** pe o linie existenta: `UPDATE VANZARI_DETALII SET
CANTITATE=..., PRET=..., PRET_CU_TVA=..., DISCOUNT_UNITAR=... WHERE ID_VANZARE_DET = :id`.
- **Stergere linie**: `UPDATE VANZARI_DETALII SET STERS=1, ID_UTILS=:util, DATAORAS=SYSDATE WHERE
ID_VANZARE_DET = :id` — acelasi tipar ca `sterge_factura`, dar tintit pe o singura linie (nou,
nu exista azi, vezi 3.1).
- **Adaugare linie noua**: `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE,
PRET_CU_TVA, DISCOUNT_UNITAR, PROC_TVAV, ID_GESTIUNE, CONT, ID_VALUTA, ID_JTVA_COLOANA, SERIE,
EXPLICATIE, TAXCODE, LOT) VALUES (...)` — PK automat din trigger (vezi 3.2).
Apoi, in ACEEASI tranzactie (deschisa deja de `do_deschide_tranzactie` in fluxul de editare a
notei, cf. `rec_s5_oracle_vanzari.md` B "Idempotenta si tranzactionalitate"), se cheama procedura
de recalcul a totalurilor propusa in `rec_s5_oracle_vanzari.md` (`recalculeaza_totaluri_vanzari`),
care citeste direct din `VANZARI_DETALII WHERE ID_VANZARE=:id AND STERS=0` — **fara nicio
dependenta de `VANZARI_DETALII_TEMP`**.
### Recomandare: **Varianta B**
Argumentul principal e riscul de regresie asupra emiterii: Varianta A ar obliga fie la extinderea
lui `adauga_articol_factura` cu o ramura noua "editare" (cod partajat cu emiterea, risc direct pe
calea critica), fie la duplicarea partiala a logicii lui in altă procedura (risc de divergenta,
exact problema pe care #8 a corectat-o deja pentru calculul de totaluri). Varianta B nu atinge deloc
`adauga_articol_factura`/`scrie_in_vanzari`/`VANZARI_DETALII_TEMP` — cod nou, izolat, apelat doar
din calea de editare (`finalizeaza_modificare_nota`), cu acelasi profil de risc "mic" motivat deja
pentru `recalculeaza_totaluri_vanzari` in `rec_s5_oracle_vanzari.md`. In plus, B se potriveste
natural cu S4b (verificare/sincronizare explicita, nu silentioasa): fiecare rand modificat/sters/
adaugat poate fi tratat ca o comanda separata, usor de enumerat utilizatorului inainte de aplicare
("linia X: cantitate 5 -> 8, pret 10 -> 12"), pe cand Varianta A ar produce un singur bloc opac de
recalcul din care nu se pot extrage usor liniile individuale schimbate.
**Observatie tehnica pentru implementare**: la editarea preturilor, NU se recalculeaza `V_PROC_TVAV`
din `JTVA_COLOANE`/comanda/contract ca la emitere (asta ar reintroduce dependenta de sursa
documentului, respinsa mai sus) — cota de TVA ramane cea deja persistata pe linie
(`VANZARI_DETALII.PROC_TVAV`), coerent cu felul in care `pret_cu_tva`/`proc_tvav` sunt deja tratate
ca date proprii ale liniei si nu recalculate la fiecare atingere (cf. #7).
## 5. Ce ramane neconfirmat / in afara scopului acestei cercetari
- Coloanele `STERS`, `VALIDAT`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT` din `VANZARI_DETALII` sunt
`NOT NULL` fara sa aiba valoare din trigger — probabil au `DEFAULT` la nivel de coloana
(`all_tab_columns.data_default` nu a fost interogat, nu era necesar pentru concluziile de mai
sus); de verificat explicit inainte de a scrie INSERT-ul de linie noua din Varianta B, ca sa nu
fie nevoie sa le populeze manual.
- Valorile implicite exacte pentru `DEFAULT` (daca exista) pe coloanele NOT NULL de mai sus.
- Comportamentul `VANZARI_SETURI_TEMP`/liniile din seturi la editare (#6 nu pare sa acopere editarea
seturilor, doar articolele individuale — de clarificat cu Marius daca seturile sunt in scope).

View File

@@ -1,245 +0,0 @@
# Implementarea deciziei 42 — articole needitabile pe factura trimisa in eFactura
Scris 10.08.2026. Implementare + testare + write-back verificat. **ZERO commit** (git/svn),
conform interdictiei primite.
> ## COMPLETARE, 10.08.2026 ora ~12:55 — discountul de antet intra sub garda
>
> Agentul care a scris acest raport a apucat sa faca **modificarea de cod si write-back-ul**, apoi a
> cazut pe limita de sesiune **inainte de verificare**. Blocul de mai jos e scris de orchestrator,
> care a preluat si a dus verificarea la capat.
>
> **Decizia lui Marius**: `txtDiscountArt` (discountul de antet) **se blocheaza si el** cand documentul
> e trimis in eFactura — schimba totalurile unui document deja depus la ANAF, chiar daca nu atinge
> nicio linie.
>
> **Codul**: `omodificari.vc2:14813` — `This.pgfArticole.PAGE3.txtDiscountArt.ReadOnly =
> This.lArticoleReadOnly`, prin acelasi flag, fara mecanism nou.
>
> **A doua cale de scriere — cautata si exclusa**: `txtDiscountArt.Valid` (`:16592`) doar cheama
> `Thisform.ActualizeazaBaraTotaluri()`, deci **recalculeaza afisajul, nu scrie discountul nicaieri**.
> Nu exista buton sau apel programatic care sa-l seteze ocolind caseta, deci garda dubla care a fost
> necesara la butoanele de articole (`Click`) **nu isi are rostul aici**. Salvarea propriu-zisa trimite
> discountul ca parametru catre `recalculeaza_totaluri_vanzari` (decizia 41), luand valoarea din caseta.
>
> **Verificat de orchestrator pe disc, dupa caderea agentului**:
> - **Write-back complet**, dovedit prin reconversie binar -> text in cache temporar si comparatie
> **octet cu octet**: identic, 540712 octeti. Cens de octeti `2 aa . 2 e3 . 2 fe`, zero `EF BF BD`.
> - **Regresia rerulata integral pe starea de pe disc**, dupa write-back (binar 12:33:09):
> `test_page3_articole` **14/2** (artefactul headless cunoscut), `test_adauga_linie_articol` **20/0**,
> `test_adauga_linie_valuta` **16/0**, `test_ui_sterge_linie` **8/0**, `test_verdict_act_rul` **26/0**,
> `test_incarca_vanzare_din_nota` **5/0**, `test_s5_validari_articole` **35/0**. Nicio regresie.
> - **Acoperire noua**: `test_efactura_readonly.prg` extins cu doua asertii si rerulat —
> **23 PASS / 0 FAIL** (de la 21). Cele doua noi: `caz A: txtDiscountArt.ReadOnly = .T.` pe documentul
> real trimis in eFactura, si `caz B: txtDiscountArt.ReadOnly = .F. (neregresat)` pe cel netrimis.
> Garda e verificata deci **in ambele sensuri**, nu doar pe cazul pozitiv.
> - Zero procese `vfp9.exe` ramase, zero commituri.
>
> **Ce NU e acoperit**: suita UI vizibila (`test_ui_efactura_readonly.prg`, 14/0) **nu a fost rerulata**
> dupa adaugarea discountului — ea verifica `.When`-urile de celula, neatinse de aceasta completare,
> deci riscul e mic, dar golul e declarat, nu ascuns.
>
> Diff-ul consolidat (COMUN + ROAGEST) e regenerat in diff aplicat (sters).
## Ce s-a schimbat, si unde
### `COMUN\clase\omodificari.vc2` (`frm_modific2024`)
- **Proprietate noua** `lArticoleReadOnly` (Boolean, implicit `.F.`): `*p:` la `:6827`, valoare
implicita la `:6866`.
- **`Show`** (`:14788-14827`): calculeaza flagul in acelasi bloc unde se determina
`lAreArticoleVanzari`/`nIdVanzare`/`nTipVanzare` (adica doar cand `ofacturare_editare.prg` e
incarcat si documentul are rand in `VANZARI`):
`This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0))` (`:14798`). In blocul care
pregateste `PAGE3`, aplica flagul: `cmdAdaugaArticol.Enabled`/`cmdStergeArticol.Enabled` =
`!lArticoleReadOnly` (`:14811-14812`), `lblArticoleReadOnly.Visible = lArticoleReadOnly`
(`:14813`). **`PAGE3` continua sa se pregateasca si sa se afiseze normal** —
`PregatesteArticoleFacturaEditare`/`PageCount=3`/`grdArticoleFactura.Refresh()` raman neatinse,
conform corectiei explicite a lui Marius (articolele raman vizibile).
- **Control nou** `pgfArticole.PAGE3.lblArticoleReadOnly` (`ADD OBJECT` la `:12776`): eticheta
discreta, `Visible=.F.` implicit, pozitionata pe randul butoanelor (`Top=4`, `Left=360`,
`Width=390`) — la dreapta lui `cmdAdaugaArticol` (care se termina la `Left+Width=345`), deci
**nu ia spatiu din grid** (gridul ramane la `Top=26`). Text: „Articole needitabile - factura
trimisa in eFactura", `ForeColor RGB(180,120,0)` (aceeasi nuanta de atentionare folosita deja
la verdictul ACT/RUL divergent).
- **Garda pe butoane** (`cmdAdaugaArticol.Click` `:16488`, `cmdStergeArticol.Click` `:16531`):
`OR Thisform.lArticoleReadOnly` adaugat la conditia de `RETURN` timpuriu — belt-and-suspenders
fata de `Enabled=.F.`, pentru ca `Enabled` nu blocheaza un apel programatic al metodei `.Click()`.
- **Garda pe celule** — cele patru `.When` care controleaza editabilitatea pe rand (mecanismul
din S5): `cCantitateArt.Text1.When` (`:16549`), `cPretAchizitieArt.Text1.When`,
`cPretArt.Text1.When` (`:16570`), `cPretCuTvaArt._checkbox1.When` (`:16587`) — toate primesc
`Thisform.lArticoleReadOnly OR ...` (respectiv `!Thisform.lArticoleReadOnly AND ...` pe
checkbox, unde conditia veche era inversa). Se construieste **peste** mecanismul de
editabilitate per rand din S5 (`id_vanzare_set`/`id_vanzare_det`), nu-l inlocuieste.
- **Notele/rulajele nu sunt atinse**: `Grid1` (nota contabila), `grdRulaje`/`grdRulajeObinv`
(paginile 1/2) raman complet neschimbate — verificat explicit in test (`Grid1.ReadOnly` ramane
`.F.` pe documentul din eFactura).
### `COMUN\programe\ofacturare_editare.prg`
Corectie necesara descoperita in timpul lucrului (vezi mai jos „Decizie/corectie luata pe
parcurs"): `EsteInEFactura` se apeleaza cu `VANZARI.ID_FACT`, **nu** cu `id_vanzare` — sunt doua
coloane distincte (verificat pe date: 0 potriviri `id_fact = id_vanzare` din 142 facturi tip=1).
Contractul existent (`ofacturare_comun.vc2:3764`, neatins) apela deja `EsteInEFactura(lnIdFact)`
cu `lnIdFact = crsfacturi.id_fact`. `tvanz` (populat de `IncarcaVanzareNota`) nu avea aceasta
coloana.
- `CreeazaCursorTvanzGol` (linia 153 din fisier): adaugat `id_fact I NULL` la structura cursorului
gol (fallback pe eroare Oracle/cod lipsa).
- `IncarcaVanzareNota` (linia 177): adaugat `v.id_fact` la lista de coloane selectate din
`VANZARI`. Restul interogarii (join, filtre) neatins.
- Antetul fisierului (o singura intrare cumulativa, rescrisa): data actualizata la 10.08.2026,
mentiunea `id_fact` adaugata la lista de campuri incarcate.
## Decizie/corectie luata pe parcurs — de raportat, nu era in briefing
Implementarea initiala folosea `EsteInEFactura(This.nIdVanzare)` (adica `id_vanzare`). Verificare
pe `MARIUSM_AUTO`:
```sql
SELECT COUNT(*) total, SUM(CASE WHEN id_fact = id_vanzare THEN 1 ELSE 0 END) equal_cnt
FROM vanzari WHERE tip = 1 AND sters = 0;
-- 142 total, 0 equal_cnt
```
`id_fact` si `id_vanzare` sunt spatii de ID complet diferite (`id_fact` are valori de forma
`8008013`, `id_vanzare` valori mici de forma `1013`) — cu `id_vanzare` gardarea nu s-ar fi
declansat NICIODATA in productie (nicio coincidenta intamplatoare intre cele doua plaje). Corectat
inainte de scrierea testelor, folosind exact coloana pe care o foloseste deja calea din
`ofacturare_comun.vc2:3764` (`lnIdFact = crsfacturi.id_fact`).
## Write-back — verificat prin reconversie + diff, nu pe mtime
`txt2vcx.ps1 -AllowComun` rulat de trei ori (prima incercare a picat fidelity-check-ul din cauza
ordinii gresite a blocului `ADD OBJECT` — proprietatile/obiectele dintr-o clasa `.vcx` trebuie in
ordine STRICT alfabetica dupa nume, nu dupa ZOrder; a doua rulare a picat pentru ca lipsea
corectia `id_fact`; **a treia rulare, OK**, fidelity check trecut).
Verificare **independenta** de fidelity-check-ul intern al `txt2vcx.ps1`: reconversie separata a
binarului proaspat scris (`vcx2txt.ps1 -Source omodificari.vcx` intr-un cache temporar izolat) +
`diff`/`cmp` octet cu octet fata de `.vc2`-ul din proiect:
```
diff -q omodificari.vc2 <reconversie>/omodificari.vc2 -> IDENTIC
cmp omodificari.vc2 <reconversie>/omodificari.vc2 -> BYTE-IDENTIC
```
Text si binar sunt sincrone, dovedit, nu presupus.
## Cens de octeti (regula cp1250) — inainte/dupa, identic cu baseline
`omodificari.vc2` are diacritice cp1250 preexistente (tooltip-uri „Renunțare/Adăugare/Ștergere",
octeti `0xFE`/`0xE3`/`0xAA`). **Baseline: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`.**
Editarea celor doua proprietati noi (`*p:`/`*<PropValue>`) s-a facut initial cu tool-ul `Edit`
(2 apeluri) — asta a **stricat** censul (`6 ef / 6 bf / 6 bd`, adica `EF BF BD` x2 aparitii x3
octeti), exact capcana documentata (`Edit`/`Write` re-encodeaza tot fisierul la orice scriere,
indiferent cat de mica). **Reparat** inainte de a continua: octetii corecti (`Renun[FE]are`,
`Ad[E3]ugare`, `[AA]tergere`) preluati din `git cat-file blob HEAD:clase/omodificari.vc2`
(varianta necorupta, comisa) si inlocuiti inapoi punctual. Toate editarile ulterioare (Show(),
garzile pe butoane/celule, blocul `ADD OBJECT` al etichetei noi) s-au facut **pe octeti**, cu
Perl (`<:raw`/`>:raw`, cautare/inlocuire literala `\Q...\E`, fara nicio decodare de encoding),
tocmai ca sa nu se repete problema. **Cens final: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`** —
identic cu baseline, verificat dupa fiecare grup de editari.
`ofacturare_editare.prg` nu are octeti `>=0x80` (cens gol inainte si dupa) — editat direct cu
`Edit`, fara risc.
## Regresie — cifre numarate din loguri, toate DUPA ultima editare de cod
Ultima editare de cod: `omodificari.vc2` scris in binar la `11:37:23` (a treia rulare
`txt2vcx.ps1`, cu corectia `id_fact`); `ofacturare_editare.prg` editat inainte de asta. Toate
rularile de mai jos sunt **dupa** acel moment.
| Suita | Rezultat | Baseline | Stare |
|---|---|---|---|
| `test_page3_articole.prg` | 14 PASS / 2 FAIL | 14/2 | **neregresat** (cele 2 FAIL = artefactul headless cunoscut, ColumnCount/ReadOnly pe grid needitabil sub `-A -T`) |
| `test_adauga_linie_articol.prg` | 20 PASS / 0 FAIL | 20/0 | **neregresat** |
| `test_adauga_linie_valuta.prg` | 16 PASS / 0 FAIL | 16/0 | **neregresat** |
| `test_ui_sterge_linie.prg` | 8 PASS / 0 FAIL | 8/0 | **neregresat** |
| `test_verdict_act_rul.prg` | 26 PASS / 0 FAIL | 26/0 | **neregresat** |
| `test_incarca_vanzare_din_nota.prg` | 5 PASS / 0 FAIL | 5/0 | **neregresat** |
| `test_s5_validari_articole.prg` | 35 PASS / 0 FAIL | 35/0 | **neregresat** (linia care contine cuvantul "FAIL" e text descriptiv al unei ramuri moarte deja documentate, nu un esec real — `REZULTAT: 35 PASS / 0 FAIL`) |
Zero regresii pe toate cele sapte suite existente.
## Suite noi
### `COMUN\utile\Teste\editare_factura\test_efactura_readonly.prg` (headless) — **21 PASS / 0 FAIL**
Documente REALE, gasite prin interogare (nu inventate):
- **Caz A — trimis in eFactura**: `id_vanzare=1013` (`cod=1140509`, an=2025, luna=8),
`VANZARI.ID_FACT=8008013`, prezent in `ANAF_EFACTURA`.
- **Caz B — NEtrimis** (regresie): descoperit prin proprietate (`DescoperaCazTest`,
`FACTURA_ARTICOLE`), `cod=1140895` (id_vanzare=1050, documentul deja folosit de restul suitei).
Acopera: `EsteInEFactura` direct (cu `0` si cu `8008013`); `lArticoleReadOnly` corect pe ambele
cazuri; **`PAGE3` NU e suprimata** (`PageCount=3`, `RecordSource` neschimbat, `tvd` cu linii);
`cmdAdaugaArticol`/`cmdStergeArticol.Enabled`; vizibilitatea etichetei; garda pe
`cmdStergeArticol.Click()` (apelat direct, fara dialog modal — confirma ca linia nu se modifica
pe documentul din eFactura si ca se modifica normal pe cel obisnuit); `Grid1.ReadOnly` neatins
(nota ramane editabila). Nu scrie in Oracle.
**Ce nu acopera** (documentat explicit in fisier, nu ascuns): editabilitatea per-celula
(`.When()` pe coloanele gridului) — coloanele **nu se materializeaza sub `-A -T`**
(`ColumnCount=0`, eroare 1925 „Unknown member" la accesul pe nume), artefact cunoscut si
documentat (`grid-coloane-nu-se-materializeaza-headless`). Mutat in suita UI de mai jos.
### `COMUN\utile\Teste\editare_factura\test_ui_efactura_readonly.prg` (UI vizibil) — **14 PASS / 0 FAIL**
Rulat prin harnessul corect (`vfp_ui_harness.ps1 -TestPrg ... -Steps @(...) -SyncDir ...`), nu
prin lansare directa — o incercare initiala prin `Start-Process` direct a dat tot `ColumnCount=0`;
cauza reala **nu era lansarea**, ci lipsa apelului `IncarcaArticoleFactura(1013,
'crsArticoleFactura')` **inainte** de `Createobject` — gridul se leaga o singura data, la
construire (in `Load()`), pe cursorul `tvd` existent atunci; fara precarcare, `Load()` creeaza
`tvd` GOL, iar fallback-ul din `Show()` il **recreeaza prin SQL** dupa constructie — cursor diferit
de cel pe care s-a legat gridul, deci `ColumnCount` ramane 0. Corectat dupa tiparul deja folosit de
`test_ui_s5_grid_pret_achizitie.prg`.
Acopera, pe acelasi document real (`id_vanzare=1013`, in eFactura): gridul are 15 coloane
(neregresat); `.When()` pe toate cele patru controale (`cCantitateArt`, `cPretArt`,
`cPretAchizitieArt`, `cPretCuTvaArt._checkbox1`) intorc `.F.` — **needitabile pe orice linie
normala, nu doar pe liniile din set**; caz sintetic de control (`lArticoleReadOnly` comutat manual
pe `.F.` pe acelasi document/aceleasi obiecte) — cele trei celule redevin editabile, deci gating-ul
e demonstrat pe flag, nu pe o coincidenta a documentului ales. Captura:
`screenshots_efactura\step_0_grid_efactura_readonly.png`. Nu scrie in Oracle.
## Ce NU s-a putut testa, si de ce
- **Dialogul de adaugare articol** (`frm_articol_factura`, `Show(1)` modal): garda de pe
`cmdAdaugaArticol.Click()` (Enabled=.F. + guard in cod) nu s-a putut exercita prin click real
headless — consecvent cu limitarea deja documentata pe restul suitei S4/S5 (dialog modal). Doar
`Enabled=.F.` verificat direct.
- **Ramura moarta preexistenta, gasita dar NEATINSA** (nu in scope): linia
`IF !Used('tvd') OR !This.lAreArticoleVanzari OR Thisform.lArticoleReadOnly` din
`cmdAdaugaArticol.Click` (`:16497`) foloseste `This.lAreArticoleVanzari` — `This` acolo e
butonul, nu formularul, deci proprietatea nu exista pe el. Ramane neexecutata in practica pentru
ca `!Used('tvd')` e `.F.` de fiecare data cand butonul chiar e vizibil (short-circuit VFP), deci
runtime-ul nu ajunge niciodata sa evalueze operandul gresit. Preexistenta editarii mele
(adaugarea mea e doar `OR Thisform.lArticoleReadOnly`, corect scris cu `Thisform`), nu se
repara aici — in afara scope-ului deciziei 42.
- **Verificarea vizuala pe ecran de Marius**: eticheta discreta, pozitionarea ei fata de butoane,
culoarea, comportamentul real la click de mouse pe grid.
## Stare finala verificata
- **Write-back facut si dovedit** pentru `omodificari.vc2` (reconversie + diff octet cu octet,
identic). `ofacturare_editare.prg` e `.prg` — sursa directa, fara pas de write-back.
- Cens de octeti pe `omodificari.vc2`: **`2 aa / 2 e3 / 2 fe`, zero `EF BF BD`** — identic cu
baseline, verificat dupa ultima editare.
- Zero procese `vfp9.exe` ramase (verificat cu `tasklist`) dupa toate rularile mele. Procesul
`vfp9.exe` al agentului `s8-creare-variante` (fereastra „S8 - creare documente") a ramas
intact, neatins.
- Zero scrieri in Oracle in toata sesiunea — toate interogarile (`EsteInEFactura`,
`IncarcaCursoareModificareNota`, `IncarcaVanzareNota`, `IncarcaArticoleFactura`) sunt `SELECT`.
- Zero commit (git/svn).
- Diff consolidat: diff aplicat (sters) (ambele fisiere).
- Fisiere noi, necomise: `COMUN\utile\Teste\editare_factura\test_efactura_readonly.prg` (+ log),
`COMUN\utile\Teste\editare_factura\test_ui_efactura_readonly.prg` (+ log + screenshot in
`screenshots_efactura\`).
## Interzis — respectat
`ofacturare.vc2`, `ofacturare_comun.vc2`, `comun.vc2`: neatinse (verificat — niciun `Edit`/`Write`
pe ele in aceasta lucrare). `actualizeaza_vanzari`, `PACK_CONTAFIN`, `PACK_FACTURARE`: neatinse.
Niciun `INSERT`/`UPDATE` pe `id_vanzare` `1049`/`1050`/`1048`.

View File

@@ -1,188 +0,0 @@
# Datoria 6 — ce s-a intamplat cu baza de regresie #6/S4 si cum a fost re-ancorata
09.08.2026. Schema `MARIUSM_AUTO@ROA_CENTRAL`. **Strict citiri pe Oracle** — nicio scriere, niciun
commit.
## 1. Ce s-a intamplat cu documentul (dovada pe randuri)
Ipoteza de plecare **se confirma**: documentul nu s-a pierdut, i s-a **realocat `cod`-ul**.
`ID_VANZARE = 1050` exista, e activ si nu si-a schimbat niciun total — doar `COD` a trecut de la
**1140888** la **1140895**.
```
ID_VANZARE COD STERS TIP NUMAR_ACT SERIE DATA_ACT ID_FACT TOTAL_CU_TVA
1050 1140895 0 1 547 SSS 07.08.2026 8009660 1924.59
1047 1140885 0 -12 544 SSS 07.08.2026 8009657 747.79
1048 1140894 0 1 545 SSS 07.08.2026 8009658 302.51
1049 1140887 0 1 546 SSS 07.08.2026 8009659 573.81
```
Pe intervalul 1140880-1140900 exista **doar** aceste 4 randuri; `max(cod)` in `VANZARI` e
**1140895**, `max(id_vanzare)` e **1050**. Nu exista niciun rand pe `cod = 1140888`.
Nota contabila arata acelasi lucru — acelasi antet, acelasi total, doar `STERS` si `COD` diferite:
```
COD AN LUNA STERS N NRACT SERIE DATAACT ID_FACT SUMA_TOT
1140888 2026 8 1 24 547 SSS 07.08.2026 8009660 4836.67
1140895 2026 8 0 24 547 SSS 07.08.2026 8009660 4836.67
```
24 de randuri vechi marcate `STERS=1` pe `cod` vechi, 24 de randuri noi active pe `cod` nou, cu
`nract`/`serie_act`/`dataact`/`id_fact` identice si aceeasi suma. Este exact semnatura lui
`finalizeaza_modificare_nota` + `pack_facturare.actualizeaza_vanzari(cod_vechi, cod_nou)`.
`VANZARI_DETALII` pe `id_vanzare = 1050` are in continuare **4 linii active** — neatins, corect
(scrierea in `VANZARI_DETALII` e S5, inca neimplementata).
**Cand**, pe secunda (`ACT.DATAORAS` = marcarea ca sters, `ACT.DATAORA` = crearea randului):
| Actiune | Moment |
|---|---|
| `cod=1140893` marcat sters, `cod=1140894` creat (`id_vanzare=1048`) | 08.08.2026 09:16:29 / 09:16:30 |
| `cod=1140888` marcat sters, `cod=1140895` creat (`id_vanzare=1050`) | 08.08.2026 14:05:15 / 14:05:16 |
Deci **nu** testul de write-back aprobat a mutat documentul de regresie: acela a lucrat pe
`id_vanzare = 1048` dimineata la 09:16 (`1140886 -> 1140893 -> 1140894`, consemnat in `progres.md`).
Mutarea lui `1050` e o **a doua salvare, la 14:05**, pe un alt document — cel folosit ca ancora de
regresie. Nu exista in `ACT` niciun `cod` intermediar intre 1140888 si 1140895 (1140889-1140892 n-au
randuri), deci a fost o singura realocare.
**Nimic de recreat.** Datele nu s-au pierdut; ancorarea suitelor era gresita. Prin urmare **nu se
cere nicio decizie de INSERT/UPDATE** din partea lui Marius pe partea de date.
Ancorele celorlalte cazuri sunt neatinse: `id_vanzare` 1047 (`cod=1140885`), 506 (`cod=1137874`),
882 (`cod=1139934`) sunt toate active pe acelasi `cod` ca inainte — se realoca doar documentele care
chiar se salveaza, adica cele din luna curenta, singurele care trec de garzile din
`do_editare_factura`.
## 2. Cifrele masurate azi, inainte de modificare
Cifra din `progres.md` (`7 PASS / 3 FAIL`) era **veche**: numara doar cele 10 asertii de la runda 2,
inainte ca blocul 3A sa adauge alte 5. Masurat azi, pe starea de pe disc:
| Suita | Inainte | Dupa |
|---|---|---|
| `test_page3_articole.prg` | **8 PASS / 7 FAIL** (15 verificari) | **13 PASS / 2 FAIL** |
| `test_incarca_vanzare_din_nota.prg` | **4 PASS / 1 FAIL** | **5 PASS / 5** |
Ambele rulari (inainte si dupa): `exit code 0`, **0 dialoguri native**, sub
`COMUN\utile\Teste\watchdog_vfp.ps1 -AutoDismiss` (care sterge `.fxp`-ul inainte de fiecare
lansare). `loForm.ClassLibrary` e asigurat de blocul existent
`RELEASE CLASSLIB omodificari` + `SET CLASSLIB TO D:\ROA\ROAFACTURARE\COMUN\clase\omodificari.vcx`,
neatins de modificarea de fata.
Motivul concret al esecurilor: `IncarcaCursoareModificareNota` filtreaza `STERS = 0`, iar toate cele
24 de randuri `ACT` de pe `cod=1140888` sunt `STERS=1` — deci `tact` venea **cu 0 randuri**, iar
suita nici nu ajungea sa instantieze formularul (`PageCount = -1` in log).
## 3. Ce s-a schimbat in suite si de ce
Principiul aplicat e **varianta 1 din brief**: suitele isi descopera singure documentul de test, dupa
**proprietatea ceruta de asertie**, nu dupa identitatea lui. Ancorarea pe `cod` era condamnata prin
constructie (se realoca la fiecare salvare); ancorarea pe `id_vanzare` ar fi rezistat, dar tot cere
un numar scris de mana intr-un fisier de test.
### Fisier nou: `COMUN\utile\Teste\editare_factura\descopera_caz_test.prg`
`DescoperaCazTest(<nume caz>, <alias>)` lasa in alias un rand cu `cod, an, luna, id_vanzare, tip,
nlin` si intoarce `.T.`/`.F.` Sase cazuri, fiecare o proprietate:
| Caz | Proprietatea ceruta | Rezolvat azi la |
|---|---|---|
| `FACTURA_ARTICOLE` | `tip=1` activa, cu linii active, a carei nota are **primul** rand (`min(id_act)`) pe acelasi `(nract, serie_act, dataact)` ca vanzarea | `cod=1140895`, `id_vanzare=1050`, 4 linii |
| `NEFACTURA_ARTICOLE` | la fel, dar `tip <> 1` (decizia 19 — detectia merge pe orice tip) | `cod=1140885`, `id_vanzare=1047`, `tip=-12` |
| `FARA_RULAJE` | linii active + **zero** randuri in `vrul_tot` si `vrul_obinv_tot` | `cod=1140885`, `id_vanzare=1047` |
| `PRIM_RAND_ORB` | nota al carei **prim** rand NU duce la vanzare, dar un triplet ulterior da | `cod=1140401`, `id_vanzare=1005` |
| `COLIZIUNE_COD` | idem + `cod` cu 2+ randuri active in `VANZARI` + un triplet din nota fara corespondent | `cod=1139934`, `id_vanzare=882`, `nract` fara corespondent = 13 |
| `NOTA_FARA_VANZARI` | nota activa fara rand in `VANZARI` pe **niciun** triplet | `cod=1140883`, an 2026, luna 7 |
Doua lucruri contau la proiectare:
- **Independenta fata de codul testat.** Interogarile merg pe `VACT_TOT` / `VRUL_TOT` /
`VRUL_OBINV_TOT` / `VANZARI` / `VANZARI_DETALII` cu join direct, adica pe **alt drum** decat
`IncarcaVanzareNota` / `IncarcaVanzareDinNota` / `IncarcaArticoleFactura`. Valoarea asteptata
(`id_vanzare`, `tip`, numarul de linii) nu vine de la functia verificata, deci asertia nu devine
tautologica.
- **Ordonare determinista** (`order by v.id_vanzare desc`, respectiv `an/luna/cod desc`), ca doua
rulari succesive pe aceleasi date sa aleaga acelasi document. Cazul ales e scris in log la
inceputul fiecarei rulari, ca sa se vada pe ce document s-a masurat.
- **Nicio potrivire = FAIL explicit**, nu test sarit: `caz_negasit` scrie in log
`niciun document din schema nu satisface conditia cazului` + `FAIL`.
Interogarile au fost validate intai direct in `sqlplus` (fiecare intoarce documentul asteptat), abia
apoi puse in cod.
### `test_page3_articole.prg`
Toate cele sase documente hardcodate (`1140888`, `1140885`, `1125486`, `1139934`, `1137874`) au fost
inlocuite cu cazul descoperit corespunzator. Structura asertiilor e neschimbata; procedurile de
verificare si-au pastrat corpul, doar au primit prin parametru ce inainte era scris in ele:
- `verifica_coliziune_cod` primea zero parametri si continea `1139934 / 375 / 'SSS' / 31.12.2021` si
`nract=13`; acum primeste `cod`, tripletul care **trebuie** gasit, `id_vanzare` asteptat si
**tripletul complet** care **nu trebuie** gasit. Descoperirea intoarce cele trei coloane ale
randului negativ de pe acelasi rand (`keep (dense_rank first order by nract)`), ca sa nu se combine
`nract`-ul unui rand cu `serie_act`-ul altuia — altfel asertia negativa ar fi trecut din alt motiv
decat cel testat.
- **O asertie s-a intarit, niciuna nu s-a slabit.** Cazul B (`tip <> 1`) trecea inainte cu
`tnLiniiAsteptate = -1`, adica verificarea numarului de linii era dezactivata; acum primeste
numarul real din `VANZARI_DETALII` (2) si il verifica.
### `test_incarca_vanzare_din_nota.prg`
Aceleasi patru cazuri, plus cazul EOF, trecute pe descoperire. Nicio schimbare de asertie.
## 4. Ce a ramas neacoperit
**Doua asertii din blocul 3A raman FAIL, si NU din cauza datelor** — sunt artefactul de mediu deja
consemnat ca datoria 7 in `progres.md`:
```
EROARE 1925 [VERIFICA_EDITARE_GRID:353] Unknown member COLUMN5.
structura grid/cursor (ColumnCount=14, lmodificat L, valoare N) = FAIL
ReadOnly (cantitate/pret/pret_cu_tva editabile, checkbox pe pret_cu_tva, restul readonly) = FAIL
stare initiala (lmodificat=.F., valoare calculata corect) = PASS
dupa editare cantitate (lmodificat=.T., valoare recalculata) = PASS
```
Sub `vfp9.exe -A -T` grid-ul nu se materializeaza: `ColumnCount` raporteaza `0` si `ColumnN` nu
exista ca membru, oricat de complet ar fi definita clasa. Repartitia 2 FAIL (structura, `ReadOnly`)
/ 2 PASS (cursor, calcul) e **exact** cea prezisa in `progres.md`, datoria 7 — deci suita e acum
inapoi la starea ei dinaintea degradarii bazei, nu mai bine si nu mai rau.
Cele doua asertii **nu au fost atinse, slabite sau sterse**. Ele sunt oricum acoperite corect pe
ecran de `COMUN\utile\Teste\editare_factura\test_ui_fix_editabil_subtotal.prg` sub
`vfp_ui_harness.ps1` (11/11 PASS, `ColumnCount=14`, consemnat in `progres.md`). Daca se doreste
curatarea zgomotului, varianta corecta e cea deja propusa la datoria 7 — rescrierea lor ca
verificare **statica** pe memo-ul `Properties` din `.vcx` — dar asta e alta lucrare, nu re-ancorare.
Altele:
- Nu s-a verificat comportamentul suitelor pe alta schema decat `MARIUSM_AUTO`. Descoperirea e
scrisa sa mearga pe orice schema, dar nu a fost probata pe `ROMFAST` sau `VENDING`.
- Cazurile `PRIM_RAND_ORB` si `NOTA_FARA_VANZARI` se rezolva azi la alte documente decat inainte
(`1140401` in loc de `1137874`, `1140883` in loc de `1125486`) — proprietatea testata e insa
aceeasi, iar ambele trec. Vechile documente raman valide, doar ca nu mai sunt primele in ordinea
determinista.
- Nu s-a atins nimic din `omodificari.vc2`, `ofacturare_comun.vc2`, `ofacturare_editare.prg` sau
binarele lor. `git status` in `COMUN` confirma: singurele fisiere de test schimbate sunt cele doua
suite plus fisierul nou.
## 5. Fisiere
| Fisier | Stare |
|---|---|
| `COMUN\utile\Teste\editare_factura\descopera_caz_test.prg` | **nou** |
| `COMUN\utile\Teste\editare_factura\test_page3_articole.prg` | modificat |
| `COMUN\utile\Teste\editare_factura\test_incarca_vanzare_din_nota.prg` | modificat |
| diff aplicat (sters) | diff-ul celor trei |
Comenzile de rulare:
```powershell
powershell -ExecutionPolicy Bypass -File D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1 -Script 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_page3_articole.prg' -AutoDismiss -TimeoutSec 300
powershell -ExecutionPolicy Bypass -File D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1 -Script 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_incarca_vanzare_din_nota.prg' -AutoDismiss -TimeoutSec 240
```
Logurile: `..._log.txt` langa fiecare suita; rezumatul watchdog-ului in
`COMUN\utile\Teste\editare_factura\watchdog_out\`.

View File

@@ -1,312 +0,0 @@
# Proiectare — Decizia 42: articole factura vizibile, needitabile dupa trimiterea in eFactura
Scris 10.08.2026. Agent read-only, doar cercetare/proiectare — nicio modificare de cod facuta
de acest agent.
## ATENTIE — implementarea era deja IN LUCRU, de catre alt agent; bugul de mai jos e DEJA CORECTAT
Inainte de a ajunge la proiectare: la momentul cercetarii, `COMUN\clase\omodificari.vc2` avea deja
**modificari necomise in working tree** (`git status` in `COMUN`: `M clase/omodificari.vc2`) care
implementeaza aproape exact Decizia 42 corectata (vizibil, doar needitabil). Agentul `d42-efactura`,
activ in aceeasi sesiune, lucra chiar atunci pe exact acest subiect.
**Acest raport nu descrie deci o proiectare de la zero, ci: (1) verificarea contractelor si (2) un
review al diff-ului in lucru, cu un bug real gasit si dovedit pe schema Oracle — vezi §1 si §8.**
**UPDATE, dupa trimiterea raportului**: `d42-efactura` a gasit acelasi bug independent, in timpul
propriei lucrari (nu din briefingul primit de la team-lead), l-a corectat si scris in binar
**inainte** sa primeasca mesajul meu. **Verificat direct pe disc de acest agent** (nu doar preluat
din raportarea lui `d42-efactura`): `git diff` pe `COMUN\clase\omodificari.vc2` arata
`This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0))`, iar `git diff` pe
`COMUN\programe\ofacturare_editare.prg` arata `v.id_fact` adaugat in SELECT-ul din
`IncarcaVanzareNota` si `id_fact I NULL` adaugat in schema `CREATE CURSOR tvanz` din
`CreeazaCursorTvanzGol` — exact varianta B recomandata la §8. **Bugul e inchis, nu mai necesita
actiune.**
Legat de linia necomisa gasita atunci in `ROAGEST\Programe\roagest.prg`
(`SET PROCEDURE TO ofacturare_editare.prg ADDITIVE`, "Not Committed Yet" la 10.08.2026 11:28):
`d42-efactura` confirma ca nu e a lui — a atins doar `omodificari.vc2` si `ofacturare_editare.prg`
(plus fisiere de test noi). Ramane deci dintr-un alt bloc de lucru (posibil S9), de verificat separat
de team-lead — nu descrie starea lui `d42-efactura`.
---
## 1. Cum se afla ca documentul e in eFactura
**Functie**: `FUNCTION EsteInEFactura`, globala (nu metoda de clasa), definita in
`COMUN\programe\ofacturare_editare.prg:16-25`:
```
*!* parametru: id_fact
*!* verifica daca factura cu id_fact dat a fost deja trimisa in eFactura (anaf_efactura)
FUNCTION EsteInEFactura
LPARAMETERS tnIdFact
LOCAL lcSql, lnEFactura, llSucces
lnEFactura = 0
lcSql = [select count(*) as lneFactura from anaf_efactura where id_fact = ] + Alltrim(Str(Nvl(m.tnIdFact,0)))
llSucces = goExecutor.oSelecteaza2Value(m.lcSql, @lnEFactura)
RETURN (Nvl(m.lnEFactura,0) > 0)
ENDFUNC
```
**Contract, deschis din `RETURN`**: primeste `tnIdFact` — **`ID_FACT`, nu `ID_VANZARE`** — si intoarce
`.T./.F.` (numar de randuri in `ANAF_EFACTURA` cu acel `id_fact` > 0). Foloseste `goExecutor`
(disponibil global, aceeasi conventie ca restul aplicatiei).
**Disponibilitate cross-project — VERIFICAT, nu presupus**: `ofacturare_editare.prg` e inregistrat
prin `SET PROCEDURE ... ADDITIVE` in toate cele trei aplicatii:
| App | Fisier | Linie | Stare git |
|---|---|---|---|
| ROAFACTURARE | `Programe\roafacturare.prg` | 214 | comis demult |
| ROACONT | `Programe\roacont.prg` | 212 | comis, `3af0089` (08.08.2026) — mesaj: *"Fara el, pagina de articole ale facturii nu apare in registrul jurnal > modificare"* |
| ROAGEST | `Programe\roagest.prg` | 260 | **necomis**, adaugat azi 10.08.2026 (vezi avertismentul de mai sus) |
Deci `EsteInEFactura` **este** apelabila din contextul `omodificari.vc2`, inclusiv din ROACONT si
(dupa commit-ul in curs) ROAGEST. Comentariul din `omodificari.vc2:14786-14787` ("apare doar cand
ofacturare_editare.prg e incarcat (doar ROAFACTURARE - ROACONT/ROAGEST nu-l inregistreaza)") e
**invechit** — scris inainte de commit-ul `3af0089`, care a inversat exact aceasta premisa pentru
ROACONT. Nu se corecteaza comentariul in acest raport (read-only), dar oricine implementeaza ar
trebui sa-l actualizeze cand atinge zona.
**BUG GASIT in diff-ul in lucru — `id_fact` confundat cu `id_vanzare`.** Apelul din
`omodificari.vc2:14798` (diff necomis) e:
```
This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
```
dar `This.nIdVanzare` e populat cu `tvanz.id_vanzare` (linia 14796), adica `VANZARI.ID_VANZARE` —
**alt camp** decat `VANZARI.ID_FACT`, pe care `EsteInEFactura` il asteapta. Dovada, in trei straturi:
1. **Cod**: precedentul functional existent, `ofacturare_comun.vc2:3739` (`lnIdFact = id_fact`) si
`:3764` (`EsteInEFactura(lnIdFact)`), citeste `id_fact` dintr-un camp separat de `id_vanzare`
(`:3734`, `lnIdVanzare = id_vanzare`, aceeasi cursor `crsfacturi`, doua LOCAL-uri distincte).
Acelasi tipar in `anaf_efactura.prg:3123-3124`: `pnIdFact = id_fact` si `lnIdVanzare = id_vanzare`
citite **pe acelasi rand** din `crsFacturiEmise` — daca ar fi acelasi numar, codul n-ar avea
nevoie de doua variabile.
2. **Schema Oracle** (verificat read-only, `user_tab_columns`, `MARIUSM_AUTO`): `VANZARI` are **ambele**
coloane, `ID_FACT` si `ID_VANZARE`, distincte; `ANAF_EFACTURA` are doar `ID_FACT`.
3. **Date reale** (verificat read-only, join `anaf_efactura.id_fact = vanzari.id_fact`): pentru
documentele deja trimise in eFactura, cele doua valori difera constant —
| id_fact | id_vanzare | cod | numar_act | data_act |
|---|---|---|---|---|
| 8008013 | 1013 | 1140509 | 510 | 31-AUG-25 |
| 8007922 | 1007 | 1140439 | 503 | 03-JAN-25 |
| 8007836 | 993 | 1140380 | 490 | 15-AUG-24 |
| 8007810 | 991 | 1140369 | 488 | 11-JUL-24 |
Cu bug-ul curent, `EsteInEFactura(1013)` cauta `id_fact = 1013` in `ANAF_EFACTURA` — care nu exista
cu acel numar — deci **intoarce mereu `.F.`**, chiar si pentru documente reale trimise in eFactura.
Garda ar fi complet inoperanta pe date reale. Corectia: §8.
Niciun document dintre cele patru de mai sus nu e din luna curenta (limitare de date deja cunoscuta
din bloc — garda de editare cere luna curenta), deci un test headless pe un caz real din productie
de date nu se poate face acum; testul trebuie sa foloseasca un cursor `tact`/`tvanz` mock cu
`id_fact` setat manual (acelasi tipar folosit deja pentru `id_vanzare_set` in S5).
## 2. Unde se aseaza conditia in omodificari.vc2
Locul folosit deja de diff-ul in lucru e corect si e cel recomandat: `frm_modific2024.Show()`
(`omodificari.vc2:14748-14837` in varianta comisa `1c42ae0`; extins la 14748-14856 in diff-ul
necomis), imediat dupa blocul care rezolva `This.nIdVanzare`/`This.nTipVanzare` prin
`IncarcaVanzareDinNota('tact')` si inainte de `This.pgfArticole.PageCount = 3`. E punctul unde
formularul stie deja daca documentul curent are rand in `VANZARI` (`This.lAreArticoleVanzari`) —
conditia eFactura se agata direct pe acest rezultat, fara sa duplice logica de potrivire
cod/nract/serie_act/dataact -> id_vanzare.
Proprietatea noua, `lArticoleReadOnly` (declarata la nivelul clasei, langa `lAreArticoleVanzari`,
`omodificari.vc2:6827` si `6867` in diff), e citita apoi din `.When`-urile coloanelor editabile ale
gridului `grdArticoleFactura` (mecanismul de editabilitate per rand din S5, §3-4) — deci conditia
de formular **compune** cu mecanismul existent, nu-l inlocuieste.
## 3. Controale care se dezactiveaza — confirmate pe cod
| Control | Path | Linii (comis) | Ce face diff-ul |
|---|---|---|---|
| Buton adaugare | `pgfArticole.PAGE3.cmdAdaugaArticol` | Click: `16488-16529` | `.Enabled = !This.lArticoleReadOnly` la afisare (`Show`); plus guard `OR Thisform.lArticoleReadOnly` in `Click` (aparare in adancime, cazul `Enabled` sarit programatic) |
| Buton stergere | `pgfArticole.PAGE3.cmdStergeArticol` | Click: `16531-16540` | idem |
| Cantitate | `grdArticoleFactura.cCantitateArt.Text1.When` | `16549-16554` | adauga `Thisform.lArticoleReadOnly OR` in fata conditiei existente `Nvl(tvd.id_vanzare_set,0)<>0` |
| Pret achizitie | `grdArticoleFactura.cPretAchizitieArt.Text1.When` | `16556-16561` | idem |
| Pret | `grdArticoleFactura.cPretArt.Text1.When` | `16570-16575` | idem |
| Pret cu TVA (checkbox) | `grdArticoleFactura.cPretCuTvaArt._checkbox1.When` | `16587-16589` | `RETURN !Thisform.lArticoleReadOnly AND ...` |
| Discount | `pgfArticole.PAGE3.txtDiscountArt` | — | **neatins in diff** — vezi nota de mai jos |
**Gol observat**: `txtDiscountArt` (discountul pe factura, cu `ControlSource = tvanz.discount`) nu
are `.When` si nu apare in diff. Daca discountul e considerat parte din "articolele facturii" in
sensul Deciziei 42 (afecteaza totalul liniilor), ramane editabil dupa trimiterea in eFactura — de
clarificat cu Marius sau de inclus explicit. Nu era in lista `cmdAdaugaArticol`/`cmdStergeArticol`
ceruta explicit in brief, deci il semnalez ca gol, nu-l tratez ca bug.
## 4. Tipar read-only pe grid, folosit deja in proiect
Nu se inventeaza un tipar nou. Proiectul (din S5) foloseste deja exact acest idiom pentru randurile
needitabile (liniile din seturi, `id_vanzare_set <> 0`): fiecare coloana editabila a gridului are
un handler `.When` care intoarce `.F.` cand conditia de blocare e adevarata — VFP nu lasa controlul
sa intre in editare daca `.When` intoarce `.F.`. Diff-ul in lucru extinde exact aceste `.When`-uri
existente, adaugand `Thisform.lArticoleReadOnly OR` in fata conditiei deja acolo — e continuarea
directa a mecanismului S5, nu un tipar paralel. Confirma cererea din brief: "foloseste tiparul deja
folosit in proiect, nu inventa unul nou" — respectat.
## 5. Feedback vizual pentru utilizator
Diff-ul adauga o eticheta noua, `pgfArticole.PAGE3.lblArticoleReadOnly`
(`omodificari.vc2:12857-12871` in diff), cu `Caption = "Articole needitabile - factura trimisa in
eFactura"`, `Visible` legat de `This.lArticoleReadOnly` in `Show()`. E minimul cerut de brief —
niciun tipar de marcaj standard read-only nu exista deja in proiect (nu s-a gasit unul in cautarea
pentru §4), deci eticheta simpla e alegerea rezonabila. Singura observatie: eticheta e pozitionata
`Top = 4, Left = 360` — de verificat pe ecran (fara VFP IDE nu se poate confirma vizual) ca nu se
suprapune cu alt control din PAGE3 la latimile de forma folosite azi.
## 6. Suprafata de regresie — de ce notele obisnuite din registrul jurnal nu sunt atinse
Blocul care calculeaza `lArticoleReadOnly` ruleaza **doar** in interiorul conditiei deja existente:
```
IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Reccount('tact') > 0
IncarcaVanzareDinNota('tact')
IF Reccount('tvanz') = 1
This.lAreArticoleVanzari = .T.
...
This.lArticoleReadOnly = EsteInEFactura(...)
ENDIF
ENDIF
```
`IncarcaVanzareDinNota` cauta in `VANZARI` un rand cu tripletul (cod, nract, serie_act, dataact) al
notei curente. Pentru o nota contabila obisnuita (nu factura de vanzare), acest rand nu exista,
`Reccount('tvanz')` ramane `0`, deci **intreg blocul `IF Reccount('tvanz') = 1` e sarit** —
`This.lAreArticoleVanzari` ramane `.F.` si `This.lArticoleReadOnly` ramane la valoarea implicita
`.F.` (setata explicit chiar inainte de bloc, `:14791`). Consecinta directa: `This.pgfArticole.PageCount
= 2` (fara PAGE3), deci `cmdAdaugaArticol`/`cmdStergeArticol`/gridul de articole **nici nu exista**
pentru documentul respectiv — nu doar ca nu sunt dezactivate, ci nu sunt instantiate in fluxul de
afisare. Riscul de regresie pe notele obisnuite din ROAGEST/ROACONT e deci **zero prin constructie**,
mostenit din gating-ul `lAreArticoleVanzari` deja livrat si testat in S4/S5 — Decizia 42 doar adauga
o conditie suplimentara **in interiorul** ramurii deja izolate pentru facturi de vanzare, nu schimba
gating-ul insusi. Acesta e exact argumentul cerut in brief la punctul 6, si e verificabil citind
codul, nu presupus.
`comun.vc2` (clasa `afisjurcom`, `do_modifica`, `2222-2572`) nu e atins de acest diff — ramane
neschimbat, confirmand ca intreaga logica sta in `omodificari.vc2`, un singur punct de intretinere.
## 7. Teste minime propuse (headless, in stilul suitei existente)
Suita `COMUN\utile\Teste\editare_factura\` foloseste harness-ul `ui_harness.prg` + `asserteaza`, cu
formular vizibil (`WindowType = 0`, `Show()`, `DOEVENTS FORCE`), fara input real (nici un
`keybd_event`/`SendInput` — doar citire directa de proprietati si apel direct de metode). Propunere,
dupa modelul `test_ui_sterge_linie.prg`:
**`test_d42_efactura_readonly.prg`** — cazul pozitiv, cu date mock (nu exista document real din
luna curenta trimis in eFactura, cf. §1):
1. Incarca `tact`/`tvd` pentru un document real din luna curenta cu `lAreArticoleVanzari = .T.`
(ex. acelasi `id_vanzare = 1049` mentionat in handoff intermediar (sters), daca inca in luna
curenta la momentul rularii).
2. Dupa `IncarcaVanzareDinNota`, forteaza `tvanz.id_fact` (sau `tact.id_fact`, dupa care varianta se
alege in §8) la o valoare stiuta ca exista in `ANAF_EFACTURA` (mock: `INSERT` intr-un cursor
local, nu in Oracle) — sau, mai simplu, mock-uieste direct `EsteInEFactura` prin
`SET PROCEDURE`/redefinire temporara, dupa tiparul deja folosit in proiect pentru izolarea de
Oracle in teste UI.
3. `assert`: `loForm.lArticoleReadOnly = .T.`
4. `assert`: `loForm.pgfArticole.PAGE3.cmdAdaugaArticol.Enabled = .F.`
5. `assert`: `loForm.pgfArticole.PAGE3.cmdStergeArticol.Enabled = .F.`
6. `assert`: `loForm.pgfArticole.PAGE3.lblArticoleReadOnly.Visible = .T.`
7. `assert`: grid-ul e in continuare **vizibil** si populat — `loForm.pgfArticole.PageCount = 3`,
`Reccount('tvd') > 0` — confirma partea corectata a deciziei (nu se suprima pagina).
8. `assert`: pozitionare pe `grdArticoleFactura`, `SetFocus` pe coloana `cCantitateArt` nu intra in
editare — verificat prin apelul direct al handlerului `.When` (`loForm.pgfArticole.PAGE3.
grdArticoleFactura.cCantitateArt.Text1.When()` trebuie sa intoarca `.F.`), nu prin tastare.
**`test_d42_efactura_editabil.prg`** — cazul negativ (control): acelasi document, dar
`EsteInEFactura` mock-uit sa intoarca `.F.` -> toate assert-urile de mai sus inversate (`Enabled =
.T.`, `.When()` nu intoarce `.F.` din cauza flagului — poate intoarce `.F.` din alt motiv, ex.
`id_vanzare_set`, testat separat).
**`test_d42_nota_obisnuita_neatinsa.prg`** — cazul de regresie cerut la §6: incarca o nota
contabila fara corespondent in `VANZARI` (orice test existent din `COMUN\utile\Teste\` care nu tine
de facturare), verifica `loForm.pgfArticole.PageCount = 2` si ca `lArticoleReadOnly` ramane `.F.`
fara sa fi fost nevoie sa se apeleze `EsteInEFactura` deloc (se poate confirma indirect, verificand
ca nu s-a facut nicio interogare Oracle suplimentara, sau direct daca se mock-uieste functia cu un
contor de apeluri).
Toate cele trei suite: fara scriere in Oracle, `QUIT` la final, log langa `.prg` cu acelasi nume +
`_log.txt`, conform conventiei din handoff intermediar (sters).
## 8. Schita de diff — corectia necesara peste diff-ul in lucru
Doua variante, cu recomandare pentru B.
**Varianta A — minima**, refoloseste `id_fact` deja prezent pe cursorul `tact` (confirmat: `tact`
vine din view-ul `vact_tot`, care are coloana `id_fact`, folosita deja in `ofacturare_comun.vc2:3776`
ca `Locate For Nvl(id_fact, 0) = lnIdFact` pe acelasi cursor `actactan`/`tact`):
```diff
--- a/COMUN/clase/omodificari.vc2
+++ b/COMUN/clase/omodificari.vc2
@@ frm_modific2024.Show
This.nIdVanzare = tvanz.id_vanzare
This.nTipVanzare = tvanz.tip
- This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
+ This.lArticoleReadOnly = EsteInEFactura(Nvl(tact.id_fact, 0))
```
Risc al variantei A: presupune ca recordul curent din `tact` (la momentul `Show()`, imediat dupa
incarcare, deci pe Top) are `id_fact` valid pentru documentul gasit — valabil in cazul normal, dar
nu la fel de robust ca precedentul din `ofacturare_comun.vc2:3775-3783`, care cauta explicit randul
cu `id_fact` potrivit inainte sa cada pe fallback (`Go Top`).
**Varianta B — recomandata**, aduce `id_fact` chiar pe `tvanz` (randul din `VANZARI` deja gasit prin
tripletul cod/nract/serie_act/dataact), simetric cu `id_vanzare`:
```diff
--- a/COMUN/programe/ofacturare_editare.prg
+++ b/COMUN/programe/ofacturare_editare.prg
@@ CreeazaCursorTvanzGol
- CREATE CURSOR tvanz (id_vanzare I, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ;
+ CREATE CURSOR tvanz (id_vanzare I, id_fact I NULL, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ;
in_valuta I NULL, id_valuta I NULL, nume_val C(20) NULL)
@@ IncarcaVanzareNota
- lcSql = [select v.id_vanzare, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ;
+ lcSql = [select v.id_vanzare, v.id_fact, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ;
[v.in_valuta, v.id_valuta, nv.nume_val from vanzari v left join nom_valute nv on nv.id_valuta = v.id_valuta where v.cod = ] + Transform(m.tnCod) + ;
--- a/COMUN/clase/omodificari.vc2
+++ b/COMUN/clase/omodificari.vc2
@@ frm_modific2024.Show
This.nIdVanzare = tvanz.id_vanzare
This.nTipVanzare = tvanz.tip
- This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
+ This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact, 0))
```
Varianta B e mai robusta pentru ca foloseste `VANZARI.ID_FACT` direct — exact coloana pe care
`ANAF_EFACTURA` o are ca cheie (verificat pe schema Oracle, §1) — fara sa depinda de pozitia
curenta a cursorului `tact`. Costul: doua fisiere in loc de unul (`ofacturare_editare.prg` +
`omodificari.vc2`), plus verificarea ca `select v.id_fact` nu produce eroare Oracle daca vreun rand
vechi din `VANZARI` are `id_fact` `NULL` (coloana e deja `NULL`-abila judecand dupa restul cursorului,
`in_valuta I NULL` etc., deci nu ar trebui sa fie o problema).
**Neschimbat, corect asa cum e**: restul diff-ului (`.When`-urile pe grid, `cmdAdaugaArticol`/
`cmdStergeArticol.Enabled`, eticheta `lblArticoleReadOnly`) — vezi §2-5.
---
## Rezumat pentru implementare
1. Diff-ul lui `d42-efactura` (necomis la momentul acestui raport, in `COMUN\clase\omodificari.vc2`
+ `COMUN\programe\ofacturare_editare.prg`) e **structural corect** — locul (§2), controalele
(§3), tiparul de grid (§4) si feedback-ul (§5) respecta cerintele Deciziei 42 corectate.
2. **Bug gasit, dovedit pe schema si date, si INCHIS**: `EsteInEFactura(This.nIdVanzare)` trebuia sa
foloseasca `id_fact`, nu `id_vanzare` — `d42-efactura` l-a gasit independent si l-a corectat cu
exact varianta B din §8 (`EsteInEFactura(Nvl(tvanz.id_fact,0))`), **verificat pe disc de acest
agent** dupa corectie. Nu mai necesita nicio actiune.
3. Gol de clarificat, nu bug: `txtDiscountArt` ramane editabil dupa eFactura — de decis cu Marius
daca discountul intra sub "articole" (§3). Confirmat si de `d42-efactura` ca gol cunoscut, in
afara scope-ului primit de la team-lead.
4. Comentariul din `omodificari.vc2:14786-14787` ("ROACONT/ROAGEST nu-l inregistreaza") e invechit
de la commit-ul `3af0089` — de actualizat cand se atinge zona (§1).
5. Teste propuse in §7, aliniate stilistic cu suita existenta — de confirmat cu `d42-efactura` daca
au fost deja scrise (mesajul lui mentioneaza "doua fisiere noi de test").
6. `ROAGEST\Programe\roagest.prg` (linia necomisa `SET PROCEDURE TO ofacturare_editare.prg`) **nu**
apartine lui `d42-efactura` — ramane dintr-un alt bloc de lucru, de identificat separat de
team-lead.

View File

@@ -1,262 +0,0 @@
# Cercetare: editare factura emisa (netrimisa inca in eFactura)
Sursa: cache text `.??2` (deja la zi, negenerate acum) + `.prg` din working copy
`D:\ROA\ROAFACTURARE`. Cod Oracle (pachete `PACK_FACTURARE`, `PACK_CONTAFIN`, `PACK_DOCUMENTE`)
**NU e in working copy** — doar apelurile RPC din VFP sunt vizibile; corpul PL/SQL trebuie cautat
in schema Oracle, nu exista local.
## 1. Fluxul de EMITERE factura
**Punct de intrare (meniu -> procedura generica `factureaza`)**, `COMUN\programe\oproceduri_facturare.prg`:
- `facturare_contracte` (:130-134) -> `factureaza(2/6/52)` — pe baza de **contract**
- `facturare_comenzi` (:140) -> `factureaza(3)` — pe baza de **comanda**
- `facturare_avize` (:145) -> `factureaza(4)` — pe baza de **aviz**
- `emitere_aviz_clienti` / `_debitori` / `_custodie` / `_transfer` (:203-275) -> `factureaza(tip)` cu tip-uri diverse
pentru avize (comanda/lista preturi/contract/lucrare/NIR/retur)
- `copiere_factura` (:152) -> `factureaza(toFactura.Tip, toFactura)` — copiere/modificare-inainte-de-emitere
(nu e "editare dupa emitere", e o factura noua pornita din datele alteia)
`factureaza` (`COMUN\programe\ofacturare.prg:90`) delegheaza imediat la **`factureaza2`** (acelasi fisier,
`:170`+; bucla mare pana ~`:850`). Pasi, in ordine:
1. **Formular date antet**: `frm_date_factura` / `frm_date_aviz` / `frm_date_aviz_lucrare` (alegere dupa `tnTip`,
`ofacturare.prg:220-226`) — culege client, data, delegat, ruta, incasare etc. in obiectul `poDate`.
2. **Alocare numar document**: `poGeneratorNumere.verifica_numar(...)` / `.dezaloca_numar(...)` (`:250-260`) —
numerotare seriala pe tip document, alocata/deblocata aici.
3. **Populare cursor articole**, ramificat dupa `tnTip` (vezi punctul 6 mai jos) — cheama diverse
`pack_facturare.cursor_*` (preturi/contract/comanda/avize/gestiune/lucrare/retur/aviz_nir) — `:266-308`.
4. **Formular articole**: `frm_facturare_articole` / `frm_facturare_articole2` / `frm_avizare_lucrare`
(`COMUN\clase\ofacturare.vc2`) — user editeaza cantitati/preturi/explicatii inainte de scriere efectiva.
5. **Scriere efectiva** — metoda `do_scrie_factura` a formularului de articole
(`COMUN\clase\ofacturare.vc2:14067-14360`, dublata identic in `frm_facturare_articole2` la `:18090-18370`):
- **`pack_facturare.scrie_proforma(...)`** (:14071) — doar daca `poDate.eProforma=1`: scrie **doar in
VANZARI**, explicit comentat in cod "*nu si in contabilitate*" (:14070).
- **`pack_facturare.scrie_factura_avize(...)`** (:14103) — cand `poDate.Tip = 4` (facturare din aviz).
- **`pack_facturare.scrie_factura2(...)`** (:14130, :14158) — comenzi (tip 3/21/25/28/42/47) sau alte tipuri
("otherwise": lista de preturi etc.).
- Toate aceste RPC-uri scriu in **VANZARI** si intorc un **cursor de verificare** (`lcCursorVerificare`)
cu liniile de nota contabila propuse: coloane `id_act, ascd, ascc (asociat cont debit/credit), id_partd,
id_partc, dataactt, datairegt, datascadt, suma` (:14184-14206).
- Daca exista randuri in cursorul de verificare (adica nu e proforma), se afiseaza **`Do Form verificare`**
(:14189) — utilizatorul poate corecta clientul (`id_partd/id_partc`) sau explicatiile (`ascd/ascc`) inainte
de a confirma nota.
- **`pack_facturare.finalizeaza_scriere_verificare(...)`** (:14247, si varianta `scrie_factura_avize_retur`
la :14219/:14282 pentru retur-uri) — **aici se scriu efectiv notele contabile si rulajele** in Oracle,
folosind eventualele corectii din formularul de verificare (`pcSirDifAcont`, `pcSirDifPart`).
6. **Alte efecte laterale**:
- Incasare/bon fiscal: `poGeneratorNumere.verifica_numar(16, poDate.nr_incasare)` (:503), `listeaza_bon_fiscal(...)`
(:528) — chitanta/incasare asociata facturii, daca `poDate.incasat <> 0`.
- Listare: `listeaza_ofacturare()` (:535).
- Stoc: pentru facturare directa din stoc exista un flux paralel, `oscrie_vanzare_din_stoc`
(`COMUN\programe\ofacturare_stoc.prg:318-412`) — apeleaza `pack_facturare.initializeaza_date_factura`,
`adauga_articol_factura_stoc`, `scrie_in_vanzari`, si la final
`update vanzari set id_fact = pack_contafin.get_idFact() where id_vanzare = ?` (:412) — leaga VANZARI
de identificatorul notei contabile (`id_fact`).
## 2. Fluxul de EDITARE factura existenta
Formular: `frm_facturi` (lista facturi emise), clasa `COMUN\clase\ofacturare_comun.vc2`.
- **`frm_facturi.do_modifica`** — `ofacturare_comun.vc2:4382-4482`.
- Verifica intai daca factura e deja in `anaf_efactura` (vezi punct 5) si daca `sters=0`; altfel blocheaza
editarea (:4476-4478, mesaj "Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in
eFactura!").
- Deschide `frm_modifica_factura` (:4433) si la confirmare (`gnButon=1`) executa
**`pack_facturare.modifica_date_factura(...)`** (:4443-4460) cu parametrii:
`id_vanzare, id_ruta, id_delegat, id_agent, id_masina, dataora_exp, id_facturare, listare_detaliata,
text_aditional, tip_saft, efactura, data_act, data_scad, numar_act, serie_act`.
- **Formular `frm_modifica_factura`** (`ofacturare_comun.vc2:5096-5608`) — campurile editabile efectiv expuse in
UI sunt: ruta, delegat, agent, masina, data/ora expeditie, tip facturare (`id_facturare`),
listare detaliata, text aditional, tip SAFT, si (conditionat de `numar_act` nenul) data act/scadenta/numar
act/serie act — vezi `Init` (:5570-5584) si handlerele `chkDataAct/chkNrAct/chkSerieAct` (:5592-5606).
**NU exista pe formular campuri pentru client, data facturii, seria/numarul facturii sau articole/preturi** —
acestea nu pot fi schimbate prin acest flux de editare.
- **Confirmat: editarea NU atinge notele contabile si rulajele.** `modifica_date_factura` primeste doar
campurile enumerate mai sus (metadate document/logistica), nu recalculeaza sume, nu scrie in tabelele de
note (vezi punct 3) si nu apeleaza vreun echivalent `PACK_CONTAFIN`. Nicio referinta la `pack_contafin` in
`do_modifica`.
- Editarea explicatiei unui articol de pe factura: **`frm_modifica_articol_factura`**
(`ofacturare_comun.vc2:5023-5095`, apelat din `frm_facturi.do_modifica_explicatie` :4484-4501) —
apeleaza doar **`pack_facturare.modifica_explicatie_articol(id_vanzare_det, ...)`** (:5067) — schimba
**doar textul explicatiei**, nu cantitatea/pretul/cont-ul. Nici aici nu se ating notele contabile.
## 3. Tabelele de note contabile si rulaje
Nu exista schema DBC/DDL in working copy (tabelele sunt Oracle, VFP le vede via view-uri `gcs.v...`). Din
codul de stergere (`ofacturare_comun.vc2:4573-4614`) rezulta 3 seturi de date, toate filtrate pe
`an, luna, cod` (cod = `vanzari.cod`, identificatorul facturii):
| View interogat (VFP) | Cursor local | Camp cheie randuri | Rol |
|---|---|---|---|
| `gcs.vact_tot` | `actactan` / cursor `v_act` | `id_act` (+ `id_set`, `id_fact`, `id_factd`, `id_factc`) | Note contabile (randuri debit/credit) |
| `gcs.vrul_tot` | `rul_temp` / cursor `v_rul` | `id_rul` | Rulaje |
| `gcs.vrul_obinv_tot` | `rul_temp_obinv` / cursor `v_rul_obinv` | `id_rul_obinv` | Rulaje obiecte de inventar |
**Cheia de legatura factura -> nota**: `VANZARI.id_fact` — populat la emitere prin
`pack_contafin.get_idFact()` (`COMUN\programe\ofacturare_stoc.prg:412`) sau intors ca parametru OUT din
`scrie_factura2`/`finalizeaza_scriere_verificare` (`?@poDate.nid_vanzare` e id_vanzare, nu id_fact — id_fact
pare populat separat de pachetul Oracle). Pe partea de nota, actul (`ACT`) are coloanele `id_fact`/`id_factd`/
`id_factc` folosite de `pack_documente.ReferinteDocumenteNota`/`ReferinteDocument`
(`COMUN\programe\odocumente.prg:8-46`) pentru a detecta referinte incrucisate intre documente (facturi ce se
storneaza/compenseaza reciproc).
**Camp de provenienta pentru regenerare**: nu exista un camp explicit gen `sursa_id_vanzare` pe ACT/RUL vizibil
in cod VFP; legatura se face prin **`cod` (numarul facturii) + `an` + `luna`** — vezi filtrul identic in
`do_sterge` (:4573,4587,4599). Asta e mecanismul deja folosit pentru "sterge tot ce apartine facturii X":
select pe `vact_tot`/`vrul_tot`/`vrul_obinv_tot where cod = <cod factura> and an=... and luna=...`, apoi
sterge. **E si "reteta" pentru un eventual regenereaza = sterge + refa**, cu observatia ca id-urile
(`id_act`, `id_set`, `id_fact`, `id_factd`) trebuie citite inainte de stergere daca se doreste refacere
identica (linia 4642-4647 le extrage din `actactan` inainte de a apela stergerea).
## 4. Operatie existenta de STORNARE / STERGERE factura (cheie pentru "regenereaza")
Da — **`frm_facturi.do_sterge`** (`COMUN\clase\ofacturare_comun.vc2:4503-4719`) e exact mecanismul
"sterge nota + rulaje asociate unei facturi":
- Blocheaza daca luna e inchisa (`glLunaInchisa`, :4518-4520) sau daca factura e deja stearsa (:4531-4534) sau
daca nu e din luna curenta (:4543-4546).
- **Cale proforma**: `pack_facturare.sterge_proforma(?pnIdVanzare, ?gnIdUtil)` (:4550) — nu are note, deci
stergere simpla din VANZARI.
- **Cale factura/aviz reala**:
1. Verifica blocaj de referinte: `ReferinteDocumenteNota(pnAn, pnLuna, lnCod)` (:4565,
`COMUN\programe\odocumente.prg:8`, cheama `pack_documente.ReferinteDocumenteNota`) — daca documentul
e referentiat de incasari/plati, blocheaza stergerea (:4567 mesaj "Documentul are referinte...").
2. Interogheaza `vact_tot`/`vrul_tot`/`vrul_obinv_tot` (vezi punct 3) ca sa afle daca exista note/rulaje
de sters (`llRul`).
3. Daca exista randuri in `actactan`, deschide formularul **`verificare`** (:4620-4624) pentru
confirmare vizuala a ce se sterge, apoi porneste tranzactie explicita (`SQLSetprop(...,"Transactions",2)`,
:4625) si:
- fie ruleaza `OSCRIE_IN_FISIERE(2,.F.,llRul)` + **`pack_contafin.finalizeaza_stergere_nota(luna, an,
NULL, id_set, cod, id_fact, id_factd, id_util)`** (:4642-4653) — varianta cu rulaje/fisiere multiple,
- fie apeleaza direct **`pack_facturare.sterge_factura(?pnIdVanzare, ?gnLuna, ?gnAn, ?gnIdUtil)`**
(:4689) daca nu exista note/rulaje de tratat special.
4. Commit/rollback explicit, apoi revine pe tranzactie automata.
Asta confirma ca infrastructura "sterge + reface" exista deja pe partea de stergere completa
(`sterge_factura` + `finalizeaza_stergere_nota`); un flux de "regenerare la editare" ar putea reutiliza aceste
doua RPC-uri Oracle inainte de a re-emite (echivalentul pasilor de la punctul 1, pasul 5) — dar acest
lant nu exista azi cablat din formularul de editare (`do_modifica`).
## 5. `anaf_efactura.id_fact` — verificare "a fost deja trimisa in eFactura"
- **Citire/test**: `COMUN\clase\ofacturare_comun.vc2:4428-4432`, in `frm_facturi.do_modifica`:
```
lcSql = [select count(*) as lneFactura from anaf_efactura where id_fact = ] + ALLTRIM(STR(poRec.id_fact))
llSucces = goExecutor.oSelecteaza2Value(m.lcSql, @pneFactura)
If poRec.sters = 0 AND NVL(m.pnEFactura,0) = 0
... permite editarea ...
Else
amessagebox("Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in eFactura!", 48, "Atentie")
```
(`pnEFactura`/`pneFactura` sunt aceeasi variabila — VFP e case-insensitive.)
- Expresia de test: **exista cel putin un rand in `ANAF_EFACTURA` cu `id_fact = VANZARI.id_fact` a facturii
curente** => considerata "trimisa" => editare blocata.
- **Scriere**: `VANZARI.id_fact` insusi e populat la emitere (`pack_contafin.get_idFact()`,
`COMUN\programe\ofacturare_stoc.prg:412`, echivalent probabil si in `scrie_factura2`/
`finalizeaza_scriere_verificare` pe partea Oracle, nevizibil in VFP). `ANAF_EFACTURA.id_fact` e populat:
- la import de factura de achizitie (nu de emitere): `UpdateEFacturaIdFact`
(`COMUN\programe\import_efactura.prg:229`) — `update anaf_efactura set id_fact = ?pnIdFact where id = ?pnId
and NVL(id_fact,0)=0`.
- manual din formular: `frm_import_efactura.importmodifica` (`anaf_efactura.vc2:12930`) —
`UPDATE crsFacturi SET id_fact = m.pnIdFact WHERE id = m.lnIdEfactura`.
IPOTEZA: pentru facturile **emise** (nu achizitie), popularea `anaf_efactura.id_fact` la trimitere se face
probabil in clasa server `AnafeFacturaServer`/`ANAFeFactura` (`COMUN\programe\anaf_efactura.prg`, ex.
`UpdateDbFactura` :2018-2155) sau in Oracle la generarea XML — nu am gasit un UPDATE explicit VFP care
leaga `id_fact` la momentul trimiterii unei facturi emise; ar trebui verificat separat (posibil in PL/SQL).
## 6. Cele 4 tipuri de sursa — ramificare si diferente
Ramificarea e pe **`VANZARI.tip`** (camp numeric), vazuta in doua locuri, cu acelasi grupaj:
`ofacturare.prg:266-308` (populare cursor articole la pornirea emiterii) si
`ofacturare.vc2:14067-14174` (`do_scrie_factura`, alegerea RPC-ului de scriere):
| Sursa | `tip` (exemple) | Cursor populare articole | RPC scriere |
|---|---|---|---|
| **Lista de preturi** | 1,5,7,10,22,23,29,45 (si 48/49 varianta `cursor_articole_k`) | `pack_facturare.cursor_preturi(...)` | `scrie_factura2` (ramura "otherwise") |
| **Contract** | 2,6,26,52 | `pack_facturare.cursor_contract(...)` | `scrie_factura2` (ramura "otherwise") |
| **Comanda** | 3,21,25,28,42,47 | `pack_facturare.cursor_comanda(...)` | `scrie_factura2` (ramura explicita `Inlist(poDate.Tip,3,21,25,28,42,47)`, cu intrebare "Doriti sa se inchida comanda?" :14122) |
| **Aviz** | 4 | `pack_facturare.cursor_avize(...)` | `scrie_factura_avize` (ramura `poDate.Tip = 4`, cu intrebare "Doriti sa se inregistreze si avizul de retur?" :14091) |
| (alte, mentionate de completitudine) | 27 (lucrare), 30 (aviz din NIR), 41 (transfer gestiune), 8/9/24 (retur) | `cursor_lucrare`/`cursor_aviz_nir`/`cursor_gestiune`/`cursor_retur` | variante `scrie_factura_avize_retur`/`scrie_proforma` |
**Ce difera intre ele ca note contabile/rulaje**: apelul final e mereu
`pack_facturare.finalizeaza_scriere_verificare(...)` (sau `scrie_factura_avize_retur` pentru retururi) —
adica *acelasi* punct final scrie notele, insa RPC-ul de scriere in VANZARI difera (`scrie_factura_avize` vs
`scrie_factura2`), iar la comanda/aviz apar intrebari suplimentare de inchidere document sursa (comanda
inchisa / aviz retur generat) care produc efecte laterale suplimentare pe langa nota standard. Diferentele
fine intre `scrie_factura2` si `scrie_factura_avize` (ce conturi/rulaje difera intern) sunt in PL/SQL, deci
**nu sunt vizibile in working copy**.
## 7. Blocaje deja existente la editare
- **Sters**: `poRec.sters = 0` obligatoriu (`ofacturare_comun.vc2:4432`).
- **Trimis in eFactura**: `anaf_efactura` are rand cu acelasi `id_fact` (punct 5), acelasi test la
`:4428-4432`.
- **Luna inchisa** — nu e verificat in `do_modifica` (doar in `do_sterge`, :4518-4520, `If glLunaInchisa Return`).
Deci editarea (schimbare ruta/delegat/etc.) **nu e blocata de luna inchisa**, doar stergerea. IPOTEZA/
observatie pentru discutie: daca se adauga regenerare de note la editare, ar trebui adaugat si acest test.
- **Nu e din luna curenta** — verificat doar la stergere (:4543-4546: `(pnAn*12)+pnLuna <> (gnAn*12)+gnLuna`),
nu la editare.
- **Referinte incasari/plati** — verificat doar la stergere (`ReferinteDocumenteNota`, :4565-4569), nu la
editare.
- **Are chitanta/incasare** — nu am gasit un blocaj explicit *la editare*; la stergere, verificarea de mai
sus (`ReferinteDocumenteNota`) acopera implicit si incasarile asociate (comentariul din
`odocumente.prg:5-6` mentioneaza "id_fact ... pe id_factd sau id_factc").
## 8. Numerotare si documente legate — ce se poate schimba la editare
- **Client**: NU se poate schimba din `frm_modifica_factura` (formularul nu are control pentru client/partener;
vezi punct 2). Singurul loc unde apare o restrictie explicita despre client e in fluxul de **verificare la
emitere** (nu la editare): `ofacturare.vc2:14198-14201` — daca diferenta de partener ar afecta un cont "41*",
`amessagebox("Nu puteti modifica clientul!",48,"Atentie") / Return .F.` — asta e in timpul emiterii
(formularul `verificare`), nu in editarea ulterioara.
- **Data facturii / seria / numarul facturii**: NU sunt pe formularul de editare. Campurile editabile
`data_act`/`numar_act`/`serie_act` (:4444-4458) sunt pentru "act" (document justificativ atasat, cu
checkbox-uri de activare `chkDataAct/chkNrAct/chkSerieAct`), NU seria/numarul facturii insesi — acelea
se aloca ireversibil la emitere prin `poGeneratorNumere` (punct 1, pasul 2).
- **Articole**: nu se pot modifica cantitati/preturi din fluxul de editare a facturii — doar explicatia unui
articol (`frm_modifica_articol_factura`, punct 2).
- **Aviz consumat / comanda / contract — flag "facturat"**: Exista camp **`facturat`** pe `COMENZI`
(folosit in UI: `ct_comenzi.actualizeaza_grid1` colora randurile cu `facturat=1`,
`COMUN\clase\ocomenzi.vc2:1144`; verificari `loRec.facturat=1` in `do_modifica`/`do_sterge` ale comenzii,
:1806, :2065 — blocheaza modificarea/stergerea comenzii deja facturate; filtru rapid `facturat=1`/`facturat=0`
in criterii, :2178). Marcarea `facturat=1` se face insa pe partea Oracle (`pack_facturare.scrie_factura2`
etc.), nu am gasit un `UPDATE comenzi SET facturat` direct in VFP — deci **nu e vizibil in working copy**
exact unde se scrie flagul, doar ca e citit/verificat pe partea VFP. Nu am gasit camp echivalent `facturat`
pe AVIZE sau CONTRACTE in codul cercetat (posibil alt nume de camp sau tot pe partea Oracle) —
IPOTEZA/necesita cautare suplimentara daca devine relevant.
---
## Rezumat concluzii cheie
1. **Emiterea** (`COMUN\programe\ofacturare.prg:90` -> `factureaza2`) scrie in VANZARI via
`pack_facturare.scrie_proforma/scrie_factura_avize/scrie_factura2`, apoi (daca nu e proforma) afiseaza
formularul `verificare` cu liniile de nota propuse si finalizeaza notele/rulajele prin
**`pack_facturare.finalizeaza_scriere_verificare`** (`COMUN\clase\ofacturare.vc2:14247`, variante retur la
:14219/:14282). Ramificarea pe cele 4 surse (lista preturi/contract/comanda/aviz) e pe `VANZARI.tip`,
vezi tabel la punctul 6.
2. **Editarea existenta** (`frm_facturi.do_modifica`, `COMUN\clase\ofacturare_comun.vc2:4382-4482`, via
`pack_facturare.modifica_date_factura`) atinge DOAR metadate logistice (ruta/delegat/agent/masina/
dataora_exp/tip_facturare/listare_detaliata/text_aditional/tip_saft/data-numar act) — **confirmat: nu
atinge notele contabile/rulajele si nu recalculeaza sume**. Nu se pot schimba client, data/serie/numar
factura sau articole.
3. Notele contabile si rulajele stau in view-urile Oracle `vact_tot`/`vrul_tot`/`vrul_obinv_tot`
(chei `id_act`/`id_rul`/`id_rul_obinv`), legate de factura prin **`cod`+`an`+`luna`** (identic filtru
folosit si la stergere) si prin `VANZARI.id_fact` (populat de `pack_contafin.get_idFact()`).
4. **Exista deja** un flux complet de stergere-cu-note: `frm_facturi.do_sterge`
(`ofacturare_comun.vc2:4503-4719`) — foloseste `pack_facturare.sterge_factura` +
`pack_contafin.finalizeaza_stergere_nota`, cu verificare de referinte (`ReferinteDocumenteNota`) si
confirmare vizuala (formularul `verificare`). **Aceasta e infrastructura direct reutilizabila pentru un
"regenereaza = sterge + refa"** al notelor/rulajelor la editare.
5. **`anaf_efactura.id_fact`**: testul de blocare editare e `select count(*) from anaf_efactura where
id_fact = <vanzari.id_fact_curent>` (`ofacturare_comun.vc2:4428-4432`) — daca exista rand, factura e
considerata trimisa si editarea e refuzata. Punctul exact unde se scrie `anaf_efactura.id_fact` la
trimiterea unei facturi **emise** nu e vizibil in VFP (ipoteza: pe partea Oracle/XML) — cel gasit in VFP e
doar pentru facturi de achizitie importate.
6. **Blocaje actuale**: editarea verifica doar `sters=0` si `netrimisa in eFactura`; NU verifica luna
inchisa, NU verifica "luna curenta", NU verifica referinte incasari/plati — aceste 3 verificari exista
doar pe fluxul de **stergere**, nu si pe cel de **editare** (`do_sterge` vs `do_modifica`,
`ofacturare_comun.vc2:4503` vs `:4382`).
7. Cod PL/SQL Oracle (`PACK_FACTURARE`, `PACK_CONTAFIN`, `PACK_DOCUMENTE`) nu e in working copy — doar
semnaturile RPC apelate din VFP sunt vizibile local.

View File

@@ -1,85 +0,0 @@
# S4 runda 1 (PAGE3, doar afisare) — raport de inchidere, 08.08.2026
Runda 1 din S4 (`plan_06_s4_proiectare.md`) e **inchisa**. Diff-ul de review:
diff aplicat (sters). Istoricul complet al sesiunii de implementare/depanare:
`docs\cercetare\handoff_s4_runda1.md`, handoff intermediar (sters),
handoff intermediar (sters).
## Ce s-a implementat
- **`COMUN\programe\ofacturare_editare.prg`**, doua functii noi la coada fisierului:
- `IncarcaVanzareNota(tnCod, tnNract, tcSerieAct, tdDataAct)` — gaseste randul din `VANZARI`
corespunzator notei curente (filtru compus `cod+nract+serie_act+data_act`, necesar pentru ca
`VANZARI.COD` nu e unic — vezi `progres.md`), cursor `tvanz`.
- `IncarcaArticoleFactura(tnIdVanzare)` — liniile active (`sters=0`) din `VANZARI_DETALII`, cu
denumire articol/gestiune/valuta pentru afisare, cursor `tvd`.
- **`COMUN\clase\omodificari.vc2`** (`frm_modific2024`):
- `pgfArticole.PageCount = 3`, `PAGE3.Caption = "Articole factura"`.
- Grid nou `pgfArticole.PAGE3.grdArticoleFactura` — 13 coloane, `RecordSource="tvd"`, strict
**readonly** (grid + fiecare `Text1`).
- Proprietati noi pe clasa: `lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare`.
- `Load()`: placeholder `CREATE CURSOR tvd (...)` — evita dialogul nativ "Open" pe `RecordSource`
inexistent la constructia grid-ului.
- `Show()`: bloc nou care cheama `IncarcaVanzareNota`/`IncarcaArticoleFactura` si comuta
`pgfArticole.PageCount` intre 2 si 3 dupa `lAreArticoleVanzari`.
Write-back facut, text si binar sincronizate, fidelity-check OK.
## Ce s-a testat, si cu ce rezultat
Suita `COMUN\utile\Teste\editare_factura\test_page3_articole.prg`, headless sub
`COMUN\utile\Teste\watchdog_vfp.ps1 -AutoDismiss`, conexiune reala `MARIUSM_AUTO`. Rulare finala
(dupa curatarea instrumentatiei de depanare): **exit code 0, 0 dialoguri, toate cazurile PASS**
(`test_page3_articole_log.txt`):
| Caz | Verifica | Rezultat |
|---|---|---|
| `cod=1140888` (factura, tip=1) | `IncarcaVanzareNota`/`IncarcaArticoleFactura` direct: `id_vanzare=1050`, `tip=1`, 4 linii | PASS |
| `cod=1140885` (tip=-12, cu rand VANZARI) | idem: `id_vanzare=1047`, `tip=-12` (detectia merge pe orice tip, nu doar facturi) | PASS |
| `cod=1125486` (nota fara rand VANZARI) | 0 randuri gasite (caz negativ) | PASS |
| `cod=1139934` (4 randuri VANZARI cu acelasi cod) | filtrul compus gaseste exact `id_vanzare=882` pe `nract=375/SSS`, 0 randuri pe `nract=13` (fara corespondent) | PASS |
| `cod=1140888`, `frm_modific2024` instantiat (`Createobject`+`Show`, ca in `do_editare_factura`) | `PageCount=3`, `lAreArticoleVanzari=.T.`, `nIdVanzare=1050` | PASS |
| `cod=1125486`, idem | `PageCount=2`, `lAreArticoleVanzari=.F.` | PASS |
Testul nu atinge Oracle in scriere — doar `SELECT`-uri prin `goExecutor`.
## Ce NU e acoperit (ramane pentru runde ulterioare)
- **Editarea in memorie a liniilor din grid** — runda 2 din S4; grid-ul e strict readonly in
aceasta runda, prin design (B.2 din plan, marcaje `_modificat`/`sters`/`id_vanzare_det=0`, nu e
inceput).
- **Inchiderea prin `do_renunt()`** — netestata, la fel ca in rundele S1-S3 (mediul minimal de
test are `ControlCount=0` pe formularul principal `frm_facturi`, deci butoanele native nu sunt
disponibile pentru a conduce fluxul complet prin UI).
- **Fluxul UI complet prin "Terminat"** — netestat; `verifica_pagecount_form` instantiaza
`frm_modific2024` direct (`Createobject`+`Show`), la fel ca `do_editare_factura`, dar nu conduce
formularul pana la inchidere. Validarea din `inainte_de_do_termin` nu e exercitata de aceasta
suita.
- Garda **referinte** pe `do_editare_factura` ramane netestata izolat (fara candidat pozitiv in
datele de test) — mostenit din rundele anterioare, nu specific rundei 1 din S4.
## Blocaje rezolvate in aceasta sesiune (istoric, nu de reluat)
Trei blocaje separate au impiedicat verificarea live inainte de aceasta runda de inchidere, toate
documentate pe larg in handoff intermediar (sters) si `COMUN\docs\testare-ui-vfp.md`:
1. `DO ... WITH gnAn, gnLuna` pasa prin referinta si umbrea variabilele in procedurile apelate,
provocand dialogul nativ "View Parameter" pe un `?gnAn` din SQL passthrough — remediat cu
pasare prin valoare (`DO ... WITH (gnAn), (gnLuna)`).
2. Harnessul de test (`test_init_env_auto.prg`) incarca implicit clasa `omodificari` din copia de
lucru **ROACONT**, nu din fisierul editat — remediat cu `RELEASE CLASSLIB` + `SET CLASSLIB TO`
pe calea completa a fisierului sub test.
3. Proprietatile custom noi (`lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare`) lipseau din
`*<DefinedPropArrayMethod>` (intrarile `*p:`) — regula era documentata doar pentru metode
(`*m:`); aplicata acum si aici, in `COMUN\docs\flux-editare-vfp-text.md`.
## Curatare facuta la inchidere
- Scoase liniile de log `[BISECT]` si `[DIAG]` (`ClassLibrary`/`PEMSTATUS`) din
`test_page3_articole.prg` — erau instrumentatie de depanare, fara rol in suita finala. Corectiile
de fond (parantezele pe `(gnAn)`/`(gnLuna)`, blocul `RELEASE CLASSLIB`/`SET CLASSLIB TO`) au
ramas, cu comentariile lor.
- Sters `test_baseline_isolation.prg` (+ `.FXP` + log) — era temporar prin design; concluzia lui
("binarul original nu are blocajul") s-a dovedit nula, testa copia ROACONT a clasei, nu fisierul
editat (vezi capcana #2 de mai sus).
- Suita rulata din nou dupa curatare — PASS pe toate cazurile (tabelul de mai sus).

View File

@@ -1,231 +0,0 @@
# Cercetare Oracle S5/S6/S7 (plan #6 - editare factura emisa)
Sursa: export proaspat din `MARIUSM_AUTO@ROA_CENTRAL` (`all_source`), facut in aceasta sesiune
(08.08.2026), NU copia veche de pe disc din martie. Inainte de export s-a verificat tabela
`VERSIUNE`: ultimul script aplicat e `ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`, identic cu
`versiune_db.txt` din radacina proiectului (`2026_08_06_12`) - MARIUSM_AUTO e la zi.
Numerele de linie de mai jos sunt din exportul acestei sesiuni (`PACK_FACTURARE.pck` = 17010
linii, `PACK_CONTAFIN.pck` = 9041 linii, `PACK_DOCUMENTE.pck` = 96 linii), NU din
`ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql` de pe disc, care e vechi si nu contine modificarile de
la #8. Fisierele exportate au ramas in scratchpad-ul sesiunii, nu sunt commise.
## A. Starea reala de azi
### A.1 `actualizeaza_vanzari` (PACK_FACTURARE.pck:16015-16025)
Confirmat: face EXCLUSIV realinierea `cod` + `STERS=0`, nimic altceva.
```
UPDATE VANZARI_DETALII SET STERS = 0
WHERE ID_VANZARE IN (SELECT ID_VANZARE FROM VANZARI WHERE COD = V_COD_VECHI);
UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI;
```
Important, nu era explicit in plan: al doilea `UPDATE` schimba `COD` pe ACELASI rand din `VANZARI`
(filtrat dupa vechiul cod) - `ID_VANZARE` (cheia primara) NU se schimba niciodata la editare. Asta
conteaza direct pentru sectiunea C. Comentariul inline de la `:16018` ("de modificat in caz ca il
las sa stearga manual inregistrari din VANZARI_DETALII") confirma ca extinderea era anticipata.
### A.2 `PACK_CONTAFIN.finalizeaza_modificare_nota` / `finalizeaza_stergere_nota`
`finalizeaza_modificare_nota` (PACK_CONTAFIN.pck:8601-8651): cheama
`pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou)` DOAR daca
`SELECT COUNT(*) FROM vanzari WHERE cod = tnCod` > 0 (`:8613-8617`). Daca `cod`-ul vechi nu exista
in `vanzari` (nota nu vine dintr-o factura), pasul e sarit tacut, fara eroare - comportament corect
pentru orice alt tip de nota (ROAGEST/ROACONT).
`finalizeaza_stergere_nota` (`:8653-8709`) e simetric: cheama `sterge_din_vanzari` doar daca
`cod`-ul exista in vanzari. Spre deosebire de `actualizeaza_vanzari`,
`sterge_din_vanzari` (PACK_FACTURARE.pck:16027-16042) cauta `ID_VANZARE` dupa `cod` si, daca nu-l
gaseste, ridica `RAISE_APPLICATION_ERROR(-20000, ... FACT-017 ...)` - dar acest caz nu poate aparea
in fluxul normal, fiindca apelul e deja gardat de count-ul de mai sus.
### A.3-A.4 `scrie_in_vanzari` (PACK_FACTURARE.pck:13491-13956) - formula de calcul
Extrasa integral (bloc `SELECT INTO` la `:13763-13931`, `UPDATE VANZARI` la `:13933-13949`).
Coloane denormalizate scrise: `discount_tva, valoare_achizitie, total_fara_tva, total_tva,
total_cu_tva, valval, tvaval, totval, id_valuta, curs, multiplicator, serie_incasat, nr_incasat,
suma_incasat, tip_incasat` (14 coloane).
Sursa datelor: **`VANZARI_DETALII_TEMP`** (tabela globala temporara de sesiune, NU
`VANZARI_DETALII`) + `VANZARI_SETURI_TEMP` (pentru linii-set) + `VANZARI_CURSURI` (deja scrisa cu
`id_vanzare`-ul curent, la `:13704`, prin `scrie_cursuri`). `V_DISCOUNT_FACTURA` e parametru
explicit al procedurii (discountul de pe document), nu o coloana citita din `VANZARI`.
Formula per-linie e deja extrasa in doua FUNCTII REUTILIZABILE, pure, cu parametri expliciti -
NU inline: `calculeaza_total_fara_tva_fact` / `calculeaza_total_tva_fact`
(PACK_FACTURARE.pck:15858-16013). Ambele ramifica explicit pe `V_PRET_CU_TVA` (`:15871`, `:15976`),
apeland `calculeaza_total_cu_tva_fact` cand flagul e 1. Deci **partea de calcul per linie e deja
partajabila** - nu trebuie reinventata.
Ce NU e extras e blocul de AGREGARE (suma pe toate liniile documentului, tratarea liniilor din
seturi, alegerea `curs`/`multiplicator`/`id_valuta` din `vanzari_cursuri`), care e inline in
`scrie_in_vanzari` si depinde de:
- `VANZARI_DETALII_TEMP` ca sursa (nu `VANZARI_DETALII`);
- stare de sesiune pe pachet: `pack_facturare.nin_valuta`, `ndiscount_evidentiat`,
`cserie_act_incasare` / `nnumar_act_incasare` / `nsuma_incasare` / `ntip_doc_incasare` (acestea
din urma populate DOAR la emiterea unei facturi-cu-incasare combinata - nu au sens la o editare
ulterioara, vezi C);
- `pack_def.GetIdMonedaNationala()`.
**Cine mai apeleaza `scrie_in_vanzari`**: doar 2 locuri, ambele INSERT-then-populate cu
`RETURNING ID_VANZARE`: `scrie_proforma` (`:5643`) si `finalizeaza_factura` (`:14790`). Extragerea
blocului de agregare intr-o functie interna parametrizata pe sursa (TEMP la emitere,
`VANZARI_DETALII` real la editare) e SIGURA fata de ambii apelanti, daca varianta "sursa=TEMP"
pastreaza exact comportamentul actual.
**Risc concret de reutilizare naiva pentru S5**: coloanele `serie_incasat/nr_incasat/
suma_incasat/tip_incasat` NU trebuie recalculate la editare - vin din stare de sesiune specifica
emiterii unei facturi-cu-incasare, fara nicio sursa persistenta din care sa fie reconstruite la o
editare ulterioara. O procedura noua care ar copia tot `UPDATE`-ul din `scrie_in_vanzari` le-ar
suprascrie cu `NULL` la fiecare editare, rupand legatura cu incasarea. Procedura propusa pentru S5
trebuie sa scrie DOAR cele 11 coloane de totaluri/curs, nu si aceste 4.
### A.5 Flagul `VANZARI_DETALII.PRET_CU_TVA`
Deja parte a calculului in Oracle, nu doar in VFP: valoarea intra in agregare din
`a1.pret_cu_tva <- vd.pret_cu_tva <- VANZARI_DETALII_TEMP.PRET_CU_TVA` pentru liniile directe
(`:13883`) sau din `VANZARI_SETURI_TEMP.pret_cu_tva` pentru liniile din seturi (`:13909`), apoi
`calculeaza_total_fara_tva_fact`/`calculeaza_total_tva_fact` ramifica pe el (`:15871`, `:15976`).
Orice extragere pentru S5 trebuie sa citeasca acelasi `VANZARI_DETALII.PRET_CU_TVA` per linie (deja
persistat, cf. #7) - nu exista alta sursa de adevar pentru el.
## B. Propunerea pentru S5
### Varianta A vs B
`actualizeaza_vanzari` e apelata NECONDITIONAT de `finalizeaza_modificare_nota` pentru ORICE
editare de nota al carei `cod` exista in `vanzari` - nu doar facturi din #6, ci orice tip de
document din VANZARI editat azi prin `frm_modific2024`/`afisjurcom.do_modifica`, folosit deja de
ROAGEST/ROACONT in registrul jurnal. Daca **Varianta A** (extinderea in-place a lui
`actualizeaza_vanzari` cu recalcul de totaluri) e aleasa, recalculul ar rula automat la ORICE
editare de nota cu `cod` in vanzari, inclusiv editari care azi nu ating deloc sumele (ex. doar
`explicatie`/`cont`). Risc de regresie: **mediu-mare**, extins la toata suita ROA, nu doar la #6.
**Varianta B** (procedura sora noua, ex. `recalculeaza_totaluri_vanzari`, apelata explicit din
`finalizeaza_modificare_nota` doar cand editarea a atins efectiv `VANZARI_DETALII`): risc **mic** -
`actualizeaza_vanzari` ramane neschimbata (zero impact pe restul aplicatiilor ROA care o folosesc
azi), noua procedura e un pas suplimentar, opt-in.
**Recomandare: Varianta B**, motivata exclusiv de riscul de regresie asupra editarilor de note care
NU sunt facturi si trec prin acelasi `finalizeaza_modificare_nota`.
### Schita procedurii propuse
```
PROCEDURE recalculeaza_totaluri_vanzari(V_ID_VANZARE IN NUMBER) IS
-- reia blocul de agregare din scrie_in_vanzari (:13763-13931), cu sursa
-- VANZARI_DETALII WHERE ID_VANZARE = V_ID_VANZARE AND STERS = 0
-- in loc de VANZARI_DETALII_TEMP; V_DISCOUNT_FACTURA citit din VANZARI.DISCOUNT
-- (coloana existenta pe randul curent, populata la INSERT din acelasi parametru, :13682)
BEGIN
SELECT discount, ... INTO lnDiscountFactura, ... FROM vanzari WHERE id_vanzare = V_ID_VANZARE;
-- acelasi SELECT agregat ca in scrie_in_vanzari, sursa VANZARI_DETALII in loc de _TEMP
UPDATE vanzari
SET discount_tva = ..., valoare_achizitie = ..., total_fara_tva = ...,
total_tva = ..., total_cu_tva = ..., valval = ..., tvaval = ..., totval = ...,
id_valuta = ..., curs = ..., multiplicator = ...
-- FARA serie_incasat/nr_incasat/suma_incasat/tip_incasat, vezi A.4
WHERE id_vanzare = V_ID_VANZARE;
END;
```
Apelata din `finalizeaza_modificare_nota` (PACK_CONTAFIN.pck:8601), dupa `actualizeaza_vanzari`.
### Mecanismul de scriere VFP in `VANZARI_DETALII` la emitere
Cautare in cache-ul text `COMUN` pentru `VANZARI_DETALII_TEMP` / `DETALII_TEMP` (case-insensitive):
**niciun rezultat**, inclusiv in `oscrie_in_fisiere.prg`. Tabela temp e populata probabil printr-un
helper generic de upload cursor->tabela (nume de tabela asamblat dinamic sau printr-un mecanism
care nu apare ca literal in sursa text). Nu am putut confirma static calea exacta - de cercetat
separat, pe partea VFP, inainte de proiectarea S4 (ce cursor/tabela temp foloseste editarea:
reutilizeaza `VANZARI_DETALII_TEMP` sau una noua).
### Idempotenta si tranzactionalitate
Fluxul de editare a notei ruleaza deja intr-o singura tranzactie manuala:
`Thisform.do_deschide_tranzactie()` (comun.vc2:2448) ... `oscrie_in_fisiere` + apelul catre
`finalizeaza_modificare_nota` (`:2484-2486`) ... `Thisform.do_inchide_tranzactie(...)` (`:2535`,
`COMMIT`/`ROLLBACK` in functie de succes). Procedura noua, apelata DIN INTERIORUL
`finalizeaza_modificare_nota`, mosteneste automat aceeasi tranzactie: la esec pe jumatate,
`ROLLBACK`-ul anuleaza tot (nota + `vanzari` + noile totaluri) - nu exista fereastra de
inconsistenta. `recalculeaza_totaluri_vanzari` propusa e ea insasi idempotenta ca `UPDATE ... WHERE
id_vanzare = :id` (poate rula de mai multe ori pe acelasi id fara efect cumulativ, spre deosebire
de un `INSERT`).
## C. S6 - legaturile care depind de `cod`
Verificat pe cod (nu date, dat fiind ca `MARIUSM_AUTO` are date de test):
| Legatura | Cheie reala | Ramane valida? |
|---|---|---|
| `vanzari_coresp` | `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ` (coloane confirmate din `all_tab_columns`) | DA - `actualizeaza_vanzari` nu schimba `ID_VANZARE` (vezi A.1), doar `COD` pe acelasi rand |
| `facturat` (aviz/comanda, `marcheaza_facturat`) | `ID_VANZARE`, exclusiv (PACK_FACTURARE.pck:15384-15421, foloseste doar `pack_facturare.nid_vanzare`, niciodata `cod`) | DA |
| chitanta/incasare (`SERIE_INCASAT`/`NR_INCASAT`/`SUMA_INCASAT`/`TIP_INCASAT`) | denormalizate direct pe randul `VANZARI` (nu FK), scrise o singura data la `scrie_in_vanzari` (`:13777-13780`) | DA - `actualizeaza_vanzari` nu le atinge; raman la valoarea de la emitere, ceea ce e comportamentul corect (nu se recalculeaza la editare de sume, vezi A.4/B) |
| `ReferinteDocumenteNota`/`ReferinteDocument` (PACK_DOCUMENTE.pck:25-93) | `ACT.id_factd`/`id_factc` = `id_fact`-ul documentului, filtrat `cod <> tnCod` | DA - e o VERIFICARE PRE-EDITARE (gate, apelata inainte sa se schimbe ceva), nu o legatura persistenta; nu depinde de `cod`-ul rezultat dupa editare |
| `anaf_efactura` | `ID_FACT` (coloana confirmata) | DA - `id_fact` nu se schimba la editare (by design, deja stabilit in plan) |
| `documente` | `DOCUMENTE.ID_DOC = ACT.ID_FACT` (confirmat din `MERGE INTO DOCUMENTE ... ON A.ID_DOC = B.ID_DOC` unde `B.ID_DOC` vine din `ACT_TEMP.ID_FACT`, PACK_CONTAFIN.pck:858-880) | DA - si se REACTUALIZEAZA automat (SERIE_ACT/NRACT/DATAACT) la fiecare scriere de nota, inclusiv editare, prin acelasi `MERGE` care ruleaza in `cumuleaza_note_act` |
**Concluzie generala**: toate legaturile intre note contabile trec prin `ID_FACT`
(`ACT.id_factd/id_factc`, `DOCUMENTE.id_doc`, `ANAF_EFACTURA.id_fact`), niciodata prin `cod`. Cum
#6 pastreaza `id_fact` neschimbat by design, toate raman valide fara nicio interventie
suplimentara. Singura legatura care trece prin altceva (`ID_VANZARE`, PK-ul randului `VANZARI`) e
`vanzari_coresp`/`marcheaza_facturat`, si acesta ramane la fel neschimbat (doar `COD` se rescrie pe
acelasi rand, vezi A.1). **S6 se poate inchide ca "confirmat pe cod, fara lucru suplimentar
necesar"**, nu doar ca "de verificat".
## D. S7 - rotunjirea la reeditare
`verifica_total_document` (PACK_FACTURARE.pck:16073-...) insereaza o linie de corectie in
`ACT_TEMP` cand suma recalculata din `ACT_TEMP` insusi (`V_TOTFTVA_VER`/`V_TOTTVA_VER`) difera de
`pack_facturare.ntotftva`/`ntottva` (valori calculate in VFP, trecute prin stare de sesiune inainte
de scriere). `ACT_TEMP` e complet repopulat la fiecare scriere - procedura nu "tine minte" nimic
intre apeluri, in Oracle.
**Risc identificat pe partea VFP**: la editare, `afisjurcom.do_modifica` (comun.vc2:2352-2366)
incarca in cursorul `tact` TOATE randurile curente ale notei
(`SELECT * FROM vact_tot WHERE cod = ... [AND STERS = 0] ORDER BY id_act`) - asta include si linia
de corectie inserata la salvarea anterioara (e un rand `ACT` normal, fara marcaj special care sa o
distinga). La resalvare, acest rand curge inapoi prin `oscrie_in_fisiere` in `ACT_TEMP` impreuna cu
liniile editate de utilizator, iar `verifica_total_document` ruleaza din nou pe noul `ACT_TEMP`
(care deja contine vechea corectie).
**Nu se poate decide static** daca rezultatul e o a doua corectie suprapusa peste prima, sau daca
mecanismul e self-consistent (adica `V_TOTFTVA_VER` deja include vechea corectie in suma agregata,
iar `pack_facturare.ntotftva` recalculat de VFP coincide, deci nu se mai adauga nimic) - depinde de
cum recalculeaza VFP `ntotftva`/`ntottva` la editare, cod care nu a fost verificat in aceasta trecere
(afara de scope-ul Oracle-only al acestei cercetari).
De notat: acest mecanism NU e nou pentru #6 - e folosit azi neschimbat de orice editare de nota
prin `do_modifica` (ROAGEST/ROACONT registru jurnal), pe orice tip de document, de ani de zile.
Daca ar acumula corectii sistematic la editari repetate, ar fi deja o problema cunoscuta pe editari
non-factura. Asta scade riscul, dar nu-l elimina pentru cazul specific facturii, unde S4/S5
introduc o cale noua de a schimba cantitati/preturi care nu exista azi in `do_modifica` generic (pe
notele generice azi de regula nu se schimba baza de calcul TVA in acelasi fel).
**Raspuns**: nu se poate decide din cod - testul deja propus in plan (S7: trei editari consecutive,
verifica nr. de linii de corectie in `ACT`) e calea corecta si suficienta; nu exista scurtatura
statica.
## E. Intrebari deschise
1. **Semnatura procedurii noi**: apel neconditionat din `finalizeaza_modificare_nota` oricand
`cod`-ul exista in `vanzari` (simplu, cost mic - un `SELECT`+`UPDATE` in plus si pe editari care
nu ating articolele), sau flag explicit `tnAtinsArticole` trecut din VFP (evita lucru inutil, dar
risc de flag uitat/gresit)? **Recomandare: apel neconditionat** - cost neglijabil, elimina o
clasa de bug.
2. **Discountul de document la editare**: `V_DISCOUNT_FACTURA` la emitere e parametru explicit din
VFP; planul S4/S4b nu mentioneaza editarea discountului de pe document, doar cantitate/pret/
`pret_cu_tva` pe linie. La S5, discountul se citeste din `VANZARI.DISCOUNT` (neschimbat) - de
confirmat cu Marius ca asta e comportamentul dorit (discountul de document ramane needitabil in
#6).
3. **`VALVAL`/`TVAVAL`/`TOTVAL`** (totaluri in valuta): planul S5 mentioneaza explicit doar
`total_fara_tva`/`total_tva`/`total_cu_tva`/`valoare_achizitie`/`discount_tva`. Fac parte din
acelasi bloc de agregare din `scrie_in_vanzari` - **recomandare: le includem in recalcul**, cost
suplimentar zero, evita inconsistenta pe documentele in valuta straina.
4. **Mecanismul de populare al `VANZARI_DETALII_TEMP`** la emitere n-a putut fi gasit static in
cache-ul text `COMUN` (cautare literal, fara rezultate) - necesita cercetare VFP separata inainte
de a proiecta calea exacta de scriere pentru editarea din S4 (aceeasi tabela temp, sau una noua).
5. **S7**: raspunsul cere test dinamic (3 editari consecutive), nu poate fi confirmat static - de
pastrat explicit in scope-ul S8, nu doar "de verificat" generic in text.

View File

@@ -1,198 +0,0 @@
# Cercetare: retur din facturi anterioare + adaugare din lista de preturi pe document cu sursa
## A. Returul din facturi anterioare — cum functioneaza AZI
### A.1 `But_retur` — lant de apel
Definitia clasei butonului (`COMUN\clase\cmd_butoane.vc2:324-338`):
```
DEFINE CLASS but_retur AS buton OF "_cmd_base.vcx"
caction = do_retur
...
Visible = .F.
ENDDEFINE
```
Butonul e instantiat pe `frm_facturare_articole` la `COMUN\clase\ofacturare.vc2:11221-11227` (`Visible=.F.` implicit) si devine vizibil doar conditionat, in `Init`, la `ofacturare.vc2:15122-15127`:
```
Case Inlist(poDate.tip, 1, 5, 7, 10) && modificare v 2.0.56 : am adaugat 10
&& facturare pe baza de lista de preturi
...
This.but_retur.Visible = .T. && pot sa fac retur de articole intr-o factura de vanzare
```
Deci butonul e vizibil **doar pe documente de tip 1/5/7/10** (facturare pe baza de lista de preturi), nu pe facturi de retur propriu-zise (8/9) si nu pe documente cu sursa contract/comanda.
`caction=do_retur` -> click apeleaza `frm_facturare_articole.do_retur` (`ofacturare.vc2:13963-13965`):
```
PROCEDURE do_retur
Thisform.do_adauga_articol(.F., .F., .T.)
ENDPROC
```
adica `do_adauga_articol(tlImplicit=.F., tlContract=.F., tlRetur=.T.)` (`frm_facturare_articole.do_adauga_articol`, `ofacturare.vc2:12813-12823`).
Pas cu pas in `do_adauga_articol` (`ofacturare.vc2:12813-12900`), ramura `tlRetur`:
1. Utilizatorul selecteaza un articol (din `crsarticole`, lista de preturi) si o cantitate — `lnCantitate = poArticol.cantitate`.
2. `Thisform.do_verifica_articol(...)` valideaza cantitatea.
3. La `ofacturare.vc2:12881-12890` (case `Otherwise`, articol gestionabil):
```
OTHERWISE
lnListaIdOld = poDate.listaid
IF m.tlRetur
loCauta = caut_facturi_multiple_client_articol(poDate.id_client, poDate.in_valuta, poDate.id_valuta, .F., poArticol.id_articol)
If m.gnButon = 1 and !Empty(Nvl(loCauta.id_vanzare, 0))
poDate.listaid = Alltrim(Str(loCauta.id_vanzare))
* ID_ARTICOL:ID_VANZARE,ID_ARTICOL:ID_VANZARE
thisform.cListaIdArticoleRetur = thisform.cListaIdArticoleRetur + IIF(!EMPTY(thisform.cListaIdArticoleRetur), ',', '') + ALLTRIM(STR(poArticol.id_articol)) + ':' + poDate.listaid
Endif
ENDIF
llSucces = Thisform.do_alege_stoc(poArticol.id_articol, lnCantitate, poArticol.denumire, tlImplicit, poArticol.pretftva, poArticol.discount_unitar, loGrid, m.tlRetur)
```
4. `do_alege_stoc(..., tlRetur)` (`ofacturare.vc2:13200-13250`) foloseste ramura retur pentru a construi cursorul de stoc/gestiuni de unde se alege articolul de returnat:
```
If Inlist(poDate.tip,8,9,24) Or m.tlRetur && factura retur lei, factura retur valuta, aviz retur sau factura normala cu retur de articole
lcSql = [{call pack_facturare.cursor_gestiuni_articol_retur(...)}]
```
5. Rezultatul e afisat printr-un formular `frm_articol_gest_factura` (`Createobject("frm_articol_gest_factura",tnCantitate,tlImplicit,tlRetur)` la `ofacturare.vc2:13353` si `:13361/:13375`), unde utilizatorul alege gestiunea/seria si cantitatea de returnat.
6. La salvare, `do_scrie_articole` (`ofacturare.vc2:13967-13979`) trimite `poDate.listaid` (perechile `id_articol:id_vanzare` construite la pasul 3) catre `pack_facturare.initializeaza_date_factura`.
Concluzie: **nu exista un singur dialog "alege facturile sursa" pentru tot documentul** — selectia facturii sursa se face **per articol**, in momentul adaugarii fiecarei linii, printr-un dialog generic de cautare.
### A.2 Dialogul si criteriile de cautare a facturii sursa
Formularul e generic — functia `cauta_alfa` (dialog de picker standard ROA), invocata din `caut_facturi_multiple_client_articol` (`COMUN\programe\oproceduri_facturare.prg:2124-2159`):
```
Function caut_facturi_multiple_client_articol
Lparameters tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple, tnIdArticol
...
lcSelect = [select a.serie_act,a.numar_act,a.data_act,a.dataora,a.id_vanzare ] + ;
[from vanzari a join vanzari_detalii b on a.id_vanzare = b.id_vanzare and b.id_articol = ?pnIdArticol ]
lcFiltruOriginal = [a.sters=0 and a.tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) ] + gcCondSucursala + ;
lcFiltruPart + lcFiltruValuta
...
lcTitlu = [Alegeti factura] && (tlFacturiMultiple = .F. la apelul din do_adauga_articol)
loCauta = cauta_alfa(lcSelect, lcFiltru, lcSchema, lcOrder, lcColoane, lcTitlu, lcTitluColoane, lcNumeProc, llToateIreg, lcFiltruOriginal, lcPrimaColoana, lnPornire, lnTipReturn, lcIdColumn)
```
Coloane afisate: `Serie act, Numar act, Data, Data inreg.` Criterii de filtrare in interogare:
- **articolul curent** (`b.id_articol = ?pnIdArticol`, obligatoriu — cautarea se face per-articol, nu la nivel de document);
- **client** (`a.id_part = ?pnIdPart`, doar daca `tnIdPart` nu e gol — `lcFiltruPart`, `oproceduri_facturare.prg:2133`);
- **valuta** (`a.in_valuta`/`b.id_valuta`, `lcFiltruValuta`, `:2134`);
- **tip document sursa restrictionat la vanzari normale**: `a.tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)` — exclude explicit tipurile de retur (8,9,24), deci nu se poate face retur dintr-un retur.
- **numar factura / perioada**: nu exista filtru SQL dedicat in interogare; `cauta_alfa` e dialogul generic de cautare alfa/browse al suitei (permite filtrare interactiva pe coloanele afisate, dar asta e comportament generic al dialogului, nu parametru specific — neverificat mecanismul intern de filtrare al `cauta_alfa`).
Rezultatul (`loCauta.id_vanzare`) e folosit ca `poDate.listaid` pentru articolul curent.
### A.3 Tipuri de document retur
Confirmat in cod, cu **3 valori**, nu doar 8/9 (comentariu explicit la `ofacturare.vc2:12862`):
```
* 8 = Retur lei, 9 = retur valuta sau factura de vanzare normala, dar cu retur de articole, 24 = aviz retur
```
Folosite consistent in tot `frm_facturare_articole`: `Inlist(poDate.tip, 8, 9, 24)` la `:13043`, `:13230`, `:13728`, `:14751`; `Inlist(poDate.tip, 8, 9)` separat la `:15236` (UI caption) si `:9718` (eliminare camp curs valutar). Pe `frm_facturare_articole2` (varianta "2" a formularului) aceleasi tipuri apar fara 24 in unele locuri (`:17531`: doar 8,9 — de verificat daca e omisiune sau intentionat, neclar din cod).
Semnul cantitatilor pe retur — la `do_alege_stoc` (`ofacturare.vc2:13273, :13300, :13309`):
```
Select Iif(poDate.tip=41,-1,1)*Sum(cantitate) As cantitate, ...
...
Where a.cantitate - Iif(Inlist(poDate.tip,8,9,24),(-1),1) * Nvl(b.cantitate,0) > 0
```
adica pentru tip 8/9/24 semnul cantitatii deja pe factura curenta se scade cu semn opus (`-1` in loc de `1`), consistent cu inregistrari de retur.
**Ziua de curs eliminata pe retur** — confirmat, dar linia corecta e in `COMUN\clase\ofacturare.vc2:9717-9722` (`frm_date_factura.Init`), NU in `ofacturare_comun.vc2` (fisierul citat in plan nu are randul respectiv — `ofacturare_comun.vc2` are doar 7432 de linii):
```
*!* modificare v 2.0.56
If Inlist(poDate.tip, 8, 9)
lnHeight = lnHeight - .clb_zi_curs.Height
laPozitii(.clb_zi_curs.TabIndex, 2) = 1
.RemoveObject('clb_zi_curs')
Endif
*!* modificare v 2.0.56 ^
```
Corolar in acelasi `Init` de `frm_facturare_articole` (`ofacturare.vc2:15098`): `Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "")` — eticheta de curs valutar nu afiseaza data pe retur.
### A.4 Retur partial
Da, e posibil. Coloana de cantitate din `grd_articole`, pe tip 8/9, isi schimba explicit titlul in "cantitate maxima de returnat" (`ofacturare.vc2:15236-15239`):
```
Case Inlist(poDate.tip, 8, 9) && factura retur lei/valuta
This.grd_articole.cCantitate.header1.Caption = [Cant. max. de returnat]
This.cmesaj_cantitate = [Nu se mai poate face retur pentru acest articol!]
```
si analog pentru aviz retur (tip 24) la `:15242-15245`. Cantitatea introdusa de utilizator e validata in `do_verifica_articol` (`:14743-14754`):
```
llRetur = Inlist(poDate.tip,8,9,24)
Do Case
Case poArticol.gestionabil = 0 Or gnScadereStoc = 1 Or m.llFacturareFaraStoc
llReturn = .T.
Case (tnCantitate >= 0 And m.llRetur ) Or ...
lcMesaj = Alltrim(Thisform.cmesaj_cantitate)
amessagebox(lcMesaj,0+48,"Atentie")
llReturn = .F.
```
adica sistemul respinge doar cazul in care cantitatea ramasa dupa retur ar iesi din domeniul valid (`tnCantitate >= 0` fiind conditia de eroare pe retur, unde cantitatile de retur sunt negative) — deci utilizatorul poate introduce orice cantitate <= maximul returnabil calculat de `cursor_gestiuni_articol_retur`, inclusiv mai mica (retur partial). Formularul de alegere (`frm_articol_gest_factura`, ramura `tlRetur`, `:13353-13385`) permite editarea cantitatii inainte de confirmare.
### A.5 Legatura stocata linie-de-retur -> linie originala
**Nu am gasit o coloana pe linie in `VANZARI_DETALII`** (de tip "id linie sursa") in codul VFP text disponibil. Ce exista, e legatura **la nivel de antet de document**, prin parametri output ai apelurilor Oracle:
- `poDate.nid_vanzare` / `poDate.nid_vanzare_retur` — populati ca parametri `?@...` in `pack_facturare.scrie_factura_avize_retur(...)` (`ofacturare.vc2:14448`, `:14509`) si `pack_facturare.finalizeaza_scriere_verificare(...)` (`:14472`); resetati implicit la `oDateFactura.Reset` (`COMUN\programe\ofacturare_comun.prg:569`: `.nid_vanzare_retur = 9999999999`).
- La nivel de sesiune VFP (nu persistat ca coloana confirmata), `thisform.cListaIdArticoleRetur` acumuleaza perechi `ID_ARTICOL:ID_VANZARE` (`ofacturare.vc2:12888`) trimise ca `poDate.listaid` catre `pack_facturare.initializeaza_date_factura` (`:13977-13979`) — asta e mecanismul prin care Oracle *primeste* info despre factura sursa per articol, dar daca acesta persista intr-o coloana dedicata pe `VANZARI_DETALII` (ex. id linie/document sursa) **nu se poate confirma din sursa VFP** — pachetul `pack_facturare` e in Oracle, in afara acestui repo. Cercetare existenta in `docs\cercetare\rec_cale_vanzari_detalii.md` si `rec_s5_oracle_vanzari.md` (interogari live pe schema Oracle, 08.08.2026) nu mentioneaza vreo coloana de tip `ID_DET_SURSA`/`ID_VANZARE_RETUR` pe `VANZARI_DETALII`. **Neverificat.**
---
## B. Adaugarea din lista de preturi pe document cu sursa (comanda/contract)
### A6/B6. Ce se vede azi pe cod
`frm_facturare_articole.Init`, `Do Case` pe `poDate.tip` (`ofacturare.vc2:15108-15248`) configureaza UI-ul per tip. Puncte relevante:
- **Contract (tip 2, 6)** — `ofacturare.vc2:15129-15143`:
```
Case Inlist(poDate.tip, 2, 6)
&& facturare pe baza de contract
This.lb_titlu_alb_b121.Caption = [FACTURA PE CTR. ] + Alltrim(poDate.descriere)
This.grd_articole.RemoveObject('cSerie')
&& articole din lista de preturi
This.grd_articole.cCantitate.header1.Caption = [Cantitate in stoc]
This.cmesaj_cantitate = [Acest articol nu este pe stoc!]
```
Comentariul "articole din lista de preturi" e explicit: `grd_articole` (cu butonul `But_urmator1`/`do_urmator`->`do_adauga_articol()`, cursorul `crsarticole`) **ramane activ si populat cu lista de preturi** chiar si pe document de tip contract. In paralel, `grd_contracte` (buton `But_urmator2`/`do_urmator2`->`do_adauga_articol(.F.,.T.)`, cursorul `crsarticole1`) afiseaza articolele restrictionate la contract.
Cei doi cursori se populeaza distinct: pentru `tnTip in (2,26,6,52)`, apelul e `pack_facturare.cursor_contract(...)` (`COMUN\programe\ofacturare.prg:283-290`), care umple atat `crsarticole` cat si (separat) `crsarticole1` (confirmat prin `Create Cursor crscontracte ... Select Distinct ... From (lcCursor + [1])`, `ofacturare.prg:433-440` — `lcCursor+'1'` = `crsarticole1` deja exista la acel punct).
- **Comanda (tip 3)** — `ofacturare.vc2:15144-15150`:
```
Case poDate.tip = 3
&& facturare pe baza de comanda
This.lb_titlu_alb_b121.Caption = [FACTURA LA COMANDA ] + Alltrim(poDate.descriere)
This.grd_articole.RemoveObject('cSerie')
This.grd_articole.cCantitate.header1.Caption = [Cantitate comandata]
This.cmesaj_cantitate = [A fost facturata intreaga cantitate comandata pentru acest articol!]
This.but_urmator_tot1.Visible = .T.
```
Aici `grd_articole`/`crsarticole` e populat direct de `pack_facturare.cursor_comanda(...)` (`ofacturare.prg:292-293`, tip in `(3,21,25,28,42,47)`) — adica **doar articolele comenzii**, nu lista de preturi libera. Nu exista al doilea cursor (`crsarticole1` nu se creeaza pentru tip 3, doar pentru `2,6,26` conform `ofacturare.prg:433`), deci in `Init` la `ofacturare.vc2:15294-15324`:
```
If !Used('crsarticole1')
...
Thisform.RemoveObject('grd_contracte')
Thisform.RemoveObject('but_urmator2')
Endif
```
grila si butonul secundar se elimina complet.
Exceptie: la **copiere de factura/aviz** (`poDate.lCopiere`), indiferent de tip, codul adauga explicit lista de preturi peste cursorul existent (`ofacturare.prg:454-473`):
```
IF m.llCopiere
* Adaug in lista articolelor cursorul cu lista de preturi ca sa pot adauga si alte articole in factura copiata/modificata
lcSqlCursor = [{call pack_facturare.cursor_preturi(...)}]
...
SELECT crsArticole
APPEND FROM DBF(m.lcCursorTemp)
ENDIF
```
### B7. Unde e restrictia
Nu e un `Visible`/`Enabled` pe buton (butonul `But_urmator1`/lista-de-preturi exista si e vizibil pentru toate tipurile, la fel `grd_articole`) si nu e o validare la salvare — **restrictia e la nivelul continutului cursorului de articole disponibile** (`crsarticole`), decis de care procedura Oracle il populeaza in `factureaza()`/`factureaza2()` (`ofacturare.prg:266-308`, `Do Case` pe `tnTip`):
- tip **1,5,7,10** (lista de preturi) si **2,6,26,52** (contract) -> `cursor_preturi`/`cursor_contract`: `crsarticole` contine lista de preturi completa (nerestrictionata la sursa) => **azi se poate deja adauga liber din lista de preturi pe un document contract**.
- tip **3,21,25,28,42,47** (comanda) -> `cursor_comanda`: `crsarticole` contine **doar** articolele comenzii => **azi NU se poate** adauga o linie libera din lista de preturi pe un document comanda, decat prin copiere de document (`llCopiere`, ramura separata mai sus).
Concluzie B: distinctia plan-ului ("comanda sau contract, tipurile 2,6,26,52") nu e uniforma in codul actual — **contractul (2,6,26,52) are deja acces liber la lista de preturi** (al doilea grid `grd_contracte` e doar un adaos, nu o restrictie), in timp ce **comanda (3) e restrictionata strict la continutul comenzii**, fara optiune de adaugare libera in fluxul normal (doar la copiere de document).

View File

@@ -1,163 +0,0 @@
# ROAAUTO — adaugarea de articole reale din nomenclator pe langa MANOPERA/MATERIALE
## Corectie fata de `roaauto_facturi.md`
Raportul anterior a descris facturarea ROAAUTO ca fiind formata **doar** din linii sintetice
(`-100000`..`-100008`). E incomplet: exista un mecanism separat, **"Alte servicii"**, care adauga
linii cu `id_articol` real (pozitiv), din nomenclatorul de articole, in plus fata de liniile
sintetice MANOPERA/MATERIALE. Mecanismul e cablat **direct in `factureaza_deviz`**, deci face
parte din acelasi flux descris anterior, nu dintr-un flux alternativ.
## 1. Unde se adauga articole din nomenclator
Formular: `frm_incasare_finala` (`D:\ROA\ROAAUTO\Clase\oviz_devize.vc2:5435-7143`), aratat de
`frm_emitere_facturi.do_factureaza_final` (`oviz_devize.vc2:2569` `ofrmincasare=Createobject('frm_incasare_finala',...)`)
si `do_factureaza_final_part` (`:3539`) — adica **la momentul emiterii facturii finale**, nu pe
formularul de deviz propriu-zis (`oviz_devize` nu are buton de adaugare articol la crearea
devizului).
Metoda: `frm_incasare_finala.do_adauga` (`oviz_devize.vc2:6539-6580`):
```
lcXMLArticole = cauta_nom_articole([in_stoc = 0 and in_crm = 1 and id_articol not between -100008 and -100000])
...
Replace id_articol With loArticol.id_articol,denumire With loArticol.denumire,um With loArticol.um,codmat With loArticol.codmat,;
id_lucrare With lnIdLucrare,nrord With lcNrOrd
```
Cauta in `vnom_articole_toate` (nomenclatorul de articole, cursor `crstmpart`) prin
`cauta_nom_articole` (`COMUN\programe\ocautare.prg:1781`), filtrat la articole **fara gestiune**
(`in_stoc = 0`) si **vizibile in CRM** (`in_crm = 1`), excluzand explicit id-urile sintetice
(`not between -100008 and -100000`). Articolele alese se adauga in grila `grd_altele`
(`oviz_devize.vc2:6060`, coloane `cDenumire/cNrord/cCantitate/cPretFTva/cValoareftva`,
`oviz_devize.vc2:6121-6222`), sustinuta de cursorul `crsalteserv`, creat inainte de afisarea
formularului in `frm_emitere_facturi.do_factureaza_final` (`oviz_devize.vc2:2553-2555`) si
`do_factureaza_final_part` (`:3522-3524`):
```
Create Cursor crsalteserv(id_articol N(14) Not Null,denumire c(100) Not Null,codmat c(50) Null,um c(10) Null,;
id_lucrare N(14) Not Null,nrord c(100) Not Null,;
cantitate N(14,gnPCant) Default 1,pretftva N(14,gnPPretV) Default 0,valoareftva N(14,gnPc) Default 0)
```
**Important**: `do_adauga` NU completeaza `pretftva`/`cantitate` — raman la valorile implicite
(1, respectiv 0). Pretul si cantitatea se **introduc manual** de operator in grila
(`grd_altele.cPretFTva.Text1.LostFocus` / `cCantitate.Text1.LostFocus`, ambele apeland
`Thisform.do_modifica_alteserv()` care recalculeaza `valoareftva = cantitate * pretftva`,
`oviz_devize.vc2:6692-6698,7064-7070`). Nu exista o cautare/preluare automata de pret pentru
aceste articole in acest flux (vezi punctul 6).
## 2. Cum ajung in factura — linii separate, NU cumulate
In `factureaza_deviz` (`Programe\oproceduri_devize.prg`), liniile sintetice MANOPERA/MATERIALE/
DISCOUNT/AVANS se insereaza in cursorul `crsdeviz` (`:936-983`) cu id-uri fixe (`-100000`..
`-100008`). La pasul de cumulare pentru optiunea "articol cumulat REPARATII AUTO" (`:999-1012`)
se cumuleaza **doar** liniile cu `id_articol IN (-100003,-100000,-100002,-100001)` — liniile din
`crsalteserv` nu sunt incluse in acest `INLIST`, deci nu pot fi absorbite in cumulare.
Liniile din `crsalteserv` se insereaza **separat**, dupa cumulare, cu `id_articol` real:
```
If Used('crsalteserv')
Insert Into (lcCursorDeviz)(id_articol,denumire,explicatie,cantitate,um,id_jtva_coloana,proc_Tvav,taxcode,pret,discount_unitar) ;
Select id_articol,denumire,"",cantitate,um,CAST(lnIdJTvaColoana as N(6) null) as id_jtva_coloana,...,pretftva,0 From crsalteserv Where !Deleted()
Endif
```
(`oproceduri_devize.prg:1036-1041`) — deci id-ul real din `nom_articole` trece nemodificat.
De acolo, fiecare rand ajunge in `crsvanztemp` (`:1190-1206`) si e scris in Oracle prin
`pack_facturare.adauga_articol_factura_deviz` (`:1240-1257`, apelul SQL construit cu
`Alltrim(Str(poArticol.id_articol))` la `:1241`), care insereaza direct in
`VANZARI_DETALII_TEMP` cu `ID_ARTICOL = V_ID_ARTICOL` (pozitiv, real) — vezi implementarea in
`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:4675-4745`. Confirmare: **articolele din "Alte
servicii" raman linii proprii, cu id_articol real, si NU se aduna in liniile sintetice
MANOPERA/MATERIALE.**
## 3. Gestiunea
`id_gestiune` in cursorul `lcCursorDeviz` are `DEFAULT null` (`oproceduri_devize.prg:885`) si
**niciun** `INSERT INTO` — nici cel al liniilor sintetice, nici cel al liniilor din
`crsalteserv` (`:1040-1041`) — completeaza aceasta coloana. La transferul in `crsvanztemp`
(`:1201-1206`) coloana `id_gestiune` (definita fara valoare implicita explicita la `:1190`) nu e
in lista `SELECT`, deci ramane 0 (implicit numeric), transmis ca literal `0` catre
`adauga_articol_factura_deviz` (`:1255`, `Alltrim(Str(poArticol.id_gestiune))`), care il scrie ca
atare in `VANZARI_DETALII_TEMP.ID_GESTIUNE` — nu NULL, ci **0**. Nu exista descarcare de gestiune
pentru aceste linii; e consistent cu faptul ca articolele oferite spre alegere sunt filtrate
explicit `in_stoc = 0` (articole/servicii fara gestiune de stoc). **Concluzie: liniile din "Alte
servicii" NU au gestiune reala completata**, exact ca liniile sintetice — diferenta e doar
`id_articol`.
## 4. Stergere / modificare inainte de facturare
- Stergere: `frm_incasare_finala.do_sterge` (`oviz_devize.vc2:6730-6741`), cu confirmare
(`amessagebox("Sunteti sigur ca doriti sa stergeti articolul "+lcArticol+" de pe factura?",4+32,...)`),
marcheaza randul `Delete` in cursorul bufferat (exclus apoi prin `Where !Deleted()` la punctul 2).
- Modificare cantitate/pret: direct in grila (`grd_altele.cCantitate`/`cPretFTva`), vezi punctul 1.
Toate aceste actiuni sunt posibile **doar cat timp `frm_incasare_finala` e deschis, inainte de
`do_termin`** (validarea din `inainte_de_do_termin`, `:6749-6801`, verifica duplicate si valori 0,
dar nu mai permite reintrarea in formular dupa emitere).
## 5. Dupa facturare — cmd_modifica1 si cai alternative
Confirmat, cu corectie de context: `Thisform.cmd_modifica1.Enabled=Iif(Empty(nrfact),.T.,.F.)`
se afla in `frm_emitere_facturi.grdcomenzi.AfterRowColChange` (`oviz_devize.vc2:4536`), nu intr-o
metoda separata — se re-evalueaza la fiecare schimbare de rand in grila de comenzi, dezactivand
butonul "Modifica" de indata ce comanda are `nrfact` completat. Verificat suplimentar:
- `frm_emitere_facturi.verifica_stornare` (`oviz_devize.vc2:4513-4514`) e **gol** (`PROCEDURE
verifica_stornare / ENDPROC`, fara cod) — nu exista storno de comanda/deviz in acest formular.
- Singurul "storno" gasit in `oviz_devize.vc2` e `do_storneaza_avans` (`:4256`, folosit din
`do_factureaza_final`/`do_factureaza_final_part` la `:2345`/`:3305`) — priveste exclusiv
reversarea unui **avans incasat**, nu redeschiderea unei facturi/deviz deja emise si nu permite
adaugarea de articole pe o factura emisa.
- Am gasit un mecanism generic, in afara `oviz_devize.vc2`, care poate **sterge complet** o
factura deja emisa: `frm_facturi.do_sterge` (`COMUN\clase\ofacturare_comun.vc2:4649-4689`),
care apeleaza `pack_facturare.sterge_factura(?pnIdVanzare,...)`. In Oracle,
`sterge_factura` (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:5441-...`) face un **soft-delete**
(`UPDATE VANZARI SET STERS = 1 ...`, `:5505-5508`), cu verificari ca nu existe deja facturi/avize
de retur legate. Aceasta e o stergere a intregii facturi (delete + re-emitere ulterioara),
**nu** o adaugare/modificare de articole pe factura existenta.
**Neverificat**: nu am putut confirma, in bugetul acestei cercetari, daca `frm_facturi` e
cablat intr-un meniu ROAAUTO (grep-ul pentru instantieri `frm_facturi` in fisiere specifice
ROAAUTO — in afara de `COMUN\` — nu a gasit potriviri de cod, doar metadate ascunse) si nici
daca stergerea Oracle reseteaza `nrfact`/`facturat` pe comanda ROAAUTO de origine (ar necesita
urmarirea `pack_auto`, in afara scriptului analizat). Deci **nu pot confirma sau infirma cu
dovada directa** o cale completa "sterge factura -> reface devizul cu articole diferite" din
interiorul ROAAUTO; pot confirma doar ca un asemenea instrument de stergere exista generic in
suita si ca in `oviz_devize.vc2` nu exista niciun cod care sa modifice/adauge articole pe o
factura deja emisa.
- **Concluzie pe intrebarea centrala**: constatarea anterioara ramane valabila si dupa aceasta
cercetare — `oviz_devize.vc2` insusi nu ofera nicio cale de a adauga/modifica articole pe o
comanda/deviz cu `nrfact` completat; singura cale gasita spre o factura deja emisa e stergerea
totala (soft-delete) prin ecranul generic COMUN, nu o editare in linie.
## 6. Nomenclator vs. lista de preturi — doua surse, cea folosita de ROAAUTO e nomenclatorul brut
Da, exista doua surse diferite in suita ROA:
- **Nomenclatorul brut de articole**: `vnom_articole_toate` / `nom_articole`, interogat prin
`cauta_nom_articole` (`COMUN\programe\ocautare.prg:1781-1823`, `SELECT ... FROM ] + gcS + [.vnom_articole_toate a`
la `:1799-1803`). **Acesta e cel folosit de `frm_incasare_finala.do_adauga`** — fara pret
atasat automat (pretul se tasteaza manual, vezi punctul 1).
- **Motorul de preturi negociate/politici de pret**: `pack_facturare.cursor_preturi`, apelat din
`COMUN\programe\ofacturare.prg` in `factureaza`/`factureaza2` (liniile 276/281/464/755/760) —
parte a mecanismului generic COMUN de facturare/avizare pe baza de "lista de preturi"
(`factureaza(22)`, `factureaza(29)`, `factureaza(23)`, `factureaza(41)` — comentate explicit
`pe baza de lista de preturi` in `COMUN\programe\oproceduri_facturare.prg:205,237,268`).
**Nu am gasit niciun apel** al acestui `factureaza`/`factureaza2` generic in fisierele
specifice ROAAUTO (`oviz_devize.vc2`, `oproceduri_devize.prg`) — grep-urile pentru
`cursor_preturi` si pentru `factureaza(` in afara de `COMUN\programe\ofacturare.prg` nu au
gasit potriviri in cod ROAAUTO. **Concluzie**: fluxul propriu ROAAUTO de facturare deviz
(`factureaza_deviz`) foloseste exclusiv nomenclatorul brut, fara calcul automat de pret
negociat; motorul `cursor_preturi` pare sa apartina unui flux de facturare/avizare generic,
separat, neapelat din codul ROAAUTO analizat.
## Neverificat / limitari
- Wiring-ul meniu -> `frm_facturi` in ROAAUTO (punctul 5).
- Efectul `sterge_factura` asupra coloanelor `nrfact`/`facturat` pe comanda ROAAUTO (necesita
`pack_auto`, neinclus in scriptul Oracle analizat).
- Numele exact al butonului/caption care declanseaza `frm_incasare_finala.do_adauga` — nu am
gasit in `oviz_devize.vc2` un `Click` explicit legat de `do_adauga`; e foarte probabil declansat
de un buton generic `cmd_adauga` prin conventia clasei de baza `_frm_base` (folosita si de alte
metode `do_xxx`/`cmd_xxx` din acelasi fisier), dar nu am gasit dovada directa a legaturii.
- Indexul ROAAUTO a fost reconstruit local (`_symbols.tsv`, permis explicit); nu a fost rulat
`git_sync.ps1` si nu s-a generat text nou din binare in ROAAUTO.
## Nota
Raportul a fost scris initial (din greseala) la calea gresita `D:\ROA\ROAAUTO\docs\cercetare\...`
in loc de `D:\ROA\ROAFACTURARE\docs\cercetare\...`. Acest fisier e livrarea corecta, la calea
ceruta.

View File

@@ -1,167 +0,0 @@
# Cercetare: facturile emise din ROAAUTO si relatia lor cu VANZARI_DETALII
Scop: pentru `plan_13_unificare_formular_facturare.md` — poate formularul unificat sa acopere si
facturile emise din ROAAUTO (piese + manopera pe deviz), stiind ca ele "se pot modifica ulterior"?
## Metoda si ce am putut verifica
ROAAUTO (`D:\ROA\ROAAUTO`) e un working copy **migrat** (are deja `.vc2`/`.sc2` in-tree, ca
ROAFACTURARE), deci am cautat direct in text, **fara sa rulez `git_sync.ps1`** (interzis explicit).
Nu pot garanta ca acel text e sincron 100% cu binarul curent (nu l-am regenerat) — daca vreo linie
citata pare sa nu corespunda comportamentului live, motivul cel mai probabil e text neactualizat, nu
o citire gresita.
`D:\ROA\_vfp_textcache\roaauto\_symbols.tsv` exista dar indexeaza **doar `.prg`** (1924 intrari,
toate `.prg`; niciun `.vc2/.sc2`) — probabil generat inainte de migrarea in-tree. Nu m-am bazat pe
el; am cautat direct cu Grep in `.vc2`/`.prg` din arbore.
Pentru partea Oracle am gasit sursa curenta a pachetului `PACK_FACTURARE` (COMUN, shared) in
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` (17020 linii,
cel mai recent script din istoricul de migrari) — folosita ca sursa de adevar pentru semnaturile
procedurilor.
## 1. Formularul/programul care emite facturi in ROAAUTO
Nu e un formular separat de facturare, ci un **modul apelat din formularul de devize/comenzi**
(`frm_...` in `oviz_devize.vc2`, clasa cu `cmd_factavans`/`cmd_factfinal`). Logica de facturare
propriu-zisa e in `Programe/oproceduri_devize.prg`:
- `Procedure factureaza_deviz` — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:846` (semnatura la
linia 847: `Lparameters tnIdComanda,tcNrOrd,tcNrInmat,tcDenop,tnMultiple,...`).
- Apelata din formular in `D:\ROA\ROAAUTO\Clase\oviz_devize.vc2` (liniile 1834, 2201, 3146, 4056,
4456) prin butoanele de facturare avans/final.
- Exista si `Procedure relisteaza_factura_deviz` —
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1516` — pentru **re-listarea** (re-tiparirea) unei
facturi deja emise, nu pentru editarea ei.
## 2. Cum scrie in Oracle: aceleasi proceduri, cale comuna cu ROAFACTURARE
Da — **acelasi pachet Oracle `PACK_FACTURARE`** (COMUN, shared cu ROAFACTURARE), apelat prin
`goExecutor.oExecute`, cu doi apeluri specifice pe langa cele generice:
- `pack_facturare.initializeaza_date_factura(...)` —
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1211`
- `pack_facturare.adauga_articol_factura_deviz(...)` (varianta **_deviz** a
`adauga_articol_factura`, cu parametri expliciti de pret/gestiune/valuta in loc sa caute articolul
din nomenclator) — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1240`, in bucla `Scan`/`Endscan`
peste cursorul `crsvanztemp`.
- `oscrie_in_fisiere(0,.F.,.T.)` — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1269`.
- `pack_facturare.scrie_incasari(...)` — linia 1294 (doar daca exista incasare la emitere).
- `pack_facturare.scrie_in_vanzari(0, id_delegat, id_masina, id_facturare, ..., @poDate.nid_vanzare)`
— linia 1302, **urmata in acelasi `lcSql`** de `pack_auto.actualizeaza_deviz(...)` (linia 1310) —
un apel specific ROAAUTO, care actualizeaza starea devizului/comenzii dupa facturare (marcheaza
`RUL`, seteaza `nrfact` etc., nu am citit corpul lui `pack_auto` — pachet separat, nu l-am cautat).
- Corpul `adauga_articol_factura_deviz` (spec+body in `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:468`
si `:4675-4745`) face un simplu `INSERT INTO VANZARI_DETALII_TEMP (...)` — **exact tabela de
staging** pe care o foloseste si facturarea normala din ROAFACTURARE (`adauga_articol_factura`,
linia 549 din spec, insereaza in acelasi `VANZARI_DETALII_TEMP`).
- **Premisa lui Marius e confirmata, cu o nuanta**: ROAAUTO nu scrie direct in `VANZARI_DETALII`; ca
si fluxul normal, trece prin `VANZARI_DETALII_TEMP`, iar transferul definitiv catre
`VANZARI`/`VANZARI_DETALII` se face in `pack_facturare.scrie_in_vanzari`
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:915-923` spec, body la `:13497-13962`) — **acelasi
punct final** pe care il foloseste orice alta facturare ROA (avize, comenzi, contracte). Nu exista
cale Oracle proprie ROAAUTO pentru scrierea liniilor de factura.
## 3. Tipul de document: `tip = -12`
`factureaza_deviz` construieste obiectul de date cu
`poDate = Createobject("oDateFactura",lnIdSet,-12)` —
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:922` — al doilea parametru e `tip`. Clasa
`oDateFactura` nu am gasit-o definita ca text (nu apare `DEFINE CLASS oDateFactura` in nicio sursa
text din ROAAUTO sau din COMUN al oricarui proiect cautat) — probabil traieste intr-un `.vcx` inca
neconvertit sau e generata dinamic; **neverificat** unde anume e clasa, dar e clar shared (folosita
si in `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2`, ex. liniile 3894/4075/7090, cu acelasi
tipar `Createobject("oDateFactura", tip1, tip2)`).
**`tip = -12` e deja cunoscut in ROAFACTURARE** — nu ca un cod nou, ci ca ceva deja intalnit si
documentat in cercetarea recenta de regresie a editarii facturii (proiectul S4,
`docs\progres.md:339`, `:1142`; `docs\cercetare\rec_datoria6_baza_regresie.md:93`;
`docs\cercetare\rec_s4_runda1.md:38`; `COMUN\docs\cercetare\rec_view_articole_vanzare.md:108`), pe un rand
real din baza de test `MARIUSM_AUTO`: `cod=1140885` -> `id_vanzare=1047`, `tip=-12`. Acolo e descris
explicit: **"nu e factura, e alt tip de document"** (handoff intermediar (sters)) si decizia produsului
(decizia 19) e ca detectia liniilor de articole "merge pe orice tip, nu doar facturi" — adica
`IncarcaArticoleFactura`/`IncarcaVanzareNota` din `COMUN\programe\ofacturare_editare.prg` (mentionate
in `rec_datoria6_baza_regresie.md:83-84`) nu filtreaza dupa `tip=1`, deci **vad si randurile
`tip=-12`** deja, fara cod suplimentar.
## 4. Ce e specific fata de o factura obisnuita
- **Camp de legatura cu masina**: `V_ID_MASINA` e transmis catre `pack_facturare.scrie_in_vanzari`
(`oproceduri_devize.prg:1304`). Nu e un camp exclusiv ROAAUTO — `ID_MASINA` e coloana standard pe
`VANZARI`, cunoscuta si de fluxurile generice din pachet: `modifica_date_factura` are parametrul
`V_ID_MASINA IN NUMBER` (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:946`), la fel
`scrie_factura2`, `scrie_factura_avize`, `scrie_factura_avize_retur` (liniile 618-747 din acelasi
fisier). Deci nu e o bariera de schema — ROAFACTURARE stie deja de `ID_MASINA`.
- **Liniile facturii nu sunt articole individuale din nomenclator, ci sume cumulate cu
pseudo-articole (id negative)**: `factureaza_deviz` agrega toate sumele din contul analitic al
devizului (`actactan`) pe categorii si insereaza cate o linie sintetica per categorie —
`-100000` MANOPERA (`oproceduri_devize.prg:965-967`), `-100001` DISCOUNT MANOPERA (`:972`),
`-100003` MATERIALE (`:947-949`), `-100005` AVANS (`:936-937`), `-100006` STORNARE AVANS
(`:1026-1027`), `-100007`/`-100008` INSPECTIE TEHNICA / SPALARE AUTO (`:979-982`). Optional, daca
`gnAUTOIdArticolReparatii` e setat, toate liniile de mai sus se **cumuleaza intr-o singura linie**
cu un articol real din nomenclator ("REPARATII AUTO", `:989-1004`). Confirmat pe date reale in
`COMUN\docs\cercetare\rec_view_articole_vanzare.md:108-109`: `id_vanzare=1047` (`tip=-12`) are doar
**2 randuri** in `VANZARI_DETALII`, cu `id_gestiune`/`nume_gestiune` NULL (linii nestocate,
netinute in gestiune). Deci pe factura nu apar piesele individual — apar totaluri
MATERIALE/MANOPERA (cu exceptia cazului cumulat, unde apare un singur articol generic).
- **`pack_auto.actualizeaza_deviz(...)`** e chemat imediat dupa `scrie_in_vanzari`, in acelasi
bloc SQL (`oproceduri_devize.prg:1310`) — leaga vanzarea nou creata inapoi de deviz/comanda
(tabelele `DEV_*`/comenzi din ROAAUTO). Corpul lui `pack_auto` nu a fost citit (pachet separat,
in afara ariei `PACK_FACTURARE` cautate) — **neverificat** ce tabele ROAAUTO scrie exact.
- **`DEV_TIP_DEVIZ`** (garantie/postgarantie/regie, `oproceduri_devize.prg:1797`) e o clasificare
**diferita**, a devizului insusi, nu are legatura cu `VANZARI.tip=-12`.
## 5. Cum se modifica azi o astfel de factura
- **Din ROAAUTO**: nu exista editare a liniilor dupa facturare. Formularul de devize/comanda
(`oviz_devize.vc2`) **dezactiveaza explicit butonul de modificare** dupa ce comanda are numar de
factura: `Thisform.cmd_modifica1.Enabled=Iif(Empty(nrfact),.T.,.F.)` —
`D:\ROA\ROAAUTO\Clase\oviz_devize.vc2:4536`. Singura actiune disponibila post-facturare gasita in
cod e re-listarea (`relisteaza_factura_deviz`, `oproceduri_devize.prg:1516`), care doar reciteste
antetul/liniile din `fact_vfacturi`/`fact_vfacturi_detalii` pentru reprintare, fara sa scrie
nimic. N-am gasit niciun apel din ROAAUTO catre `pack_facturare.modifica_date_factura` (grep fara
rezultate in tot arborele ROAAUTO) — nici macar antetul (delegat/masina/text aditional) nu pare
editabil din ROAAUTO dupa emitere. **Neverificat**: n-am acoperit tot `oviz_devize.vc2` (fisier de
>8800 linii) linie cu linie, doar zonele gasite prin grep pe termenii relevanti — nu exclud un alt
punct de editare cu alt nume de metoda.
- **Din ROAFACTURARE**: exista deja, in lucru (proiectul S4), un editor de factura care
**citeste** liniile oricarui `tip` (inclusiv `-12`) din `VANZARI_DETALII` — funcțiile
`IncarcaVanzareNota`/`IncarcaArticoleFactura` in `COMUN\programe\ofacturare_editare.prg` (adaugate
conform `docs\cercetare\handoff_s4_runda1.md:79-81`, verificate pe `cod=1140885` -> `id_vanzare=1047`,
`tip=-12`, `docs\cercetare\rec_datoria6_baza_regresie.md:93`) si formularul `frm_modific2024`
(`COMUN\clase\omodificari.vc2`), cu un grid nou `grdArticoleFactura` pe pagina 3 "Articole factura".
**Insa acest grid e in prezent READ-ONLY**: "grid nou `grdArticoleFactura` (...), `ReadOnly` la
nivel de grid **si** pe fiecare `Text1`" (`docs\cercetare\handoff_s4_runda1.md:80-82`) — deci azi
se poate **vizualiza**, nu edita, de aici (nici adaugare, nici stergere de linii).
## 6. Se poate adauga azi o linie libera / din lista de preturi pe o astfel de factura?
Pe cod, **nu, din nicaieri, azi**:
- Din ROAAUTO: butonul de modificare a devizului e dezactivat dupa facturare
(`oviz_devize.vc2:4536`), iar singura cale de scriere gasita (`factureaza_deviz`) ruleaza o
singura data la emitere; nu exista un `adauga_articol_...` apelabil ulterior pe o vanzare deja
scrisa. Structura insasi a liniilor (sume cumulate pe pseudo-articole negative, sectiunea 4) e
diferita de o linie normala de factura cu articol real din lista de preturi — un eventual "adauga
articol" ar trebui sa lucreze langa niste linii care nu reprezinta articole reale.
- Din ROAFACTURARE: editorul nou (`frm_modific2024`) **vede** liniile (orice `tip`, deci si cele
venite din ROAAUTO), dar gridul e read-only — nu exista azi cod de adaugare/scriere pe acest grid.
Nu am gasit alt formular ROAFACTURARE (`frm_facturi` clasic) care sa editeze articolele unei
vanzari cu `tip=-12` — cautarea `ROAAUTO` in `D:\ROA\ROAFACTURARE` (in afara de `COMUN`) nu da
potriviri de cod, doar mentiuni in `docs\` (progres.md, cercetare); `Grep` pe `ROAAUTO` in
`D:\ROA\COMUNROA` a expirat (arbore prea mare) si nu a fost reincercat — **neverificat** daca
`COMUNROA` (copia partajata separata de `COMUN` din ROAAUTO/ROAFACTURARE) are vreo referinta
directa la ROAAUTO.
## Concluzie pentru planul de unificare
Premisa lui Marius se confirma pe cod: facturile ROAAUTO ajung, prin acelasi `PACK_FACTURARE`
shared si acelasi `VANZARI_DETALII_TEMP` -> `VANZARI_DETALII`, in aceeasi tabela pe care o citeste
ROAFACTURARE — deci un formular unificat **le poate vedea** fara cod special de recunoastere a
sursei (dovada: editorul S4 le vede deja, testat pe date reale). Ce lipseste nu e recunoasterea, ci
**capacitatea de scriere**: azi nimic, in niciun produs, nu poate adauga o linie pe o vanzare
`tip=-12` — gridul nou din ROAFACTURARE e deliberat read-only, iar ROAAUTO isi blocheaza propriul
formular dupa facturare. Particularitatea reala de gestionat e ca liniile existente nu sunt articole
individuale, ci sume cumulate MATERIALE/MANOPERA pe pseudo-articole cu `id_articol` negativ — orice
UI care adauga articole din lista de preturi "langa" ele trebuie sa decida cum coexista cu randuri
care nu au `id_gestiune`/nu corespund unui articol real.

View File

@@ -1,249 +0,0 @@
# Rute de scriere pe antetul unui document DEJA EMIS (VANZARI/ACT/RUL) — Grupele A/B/C
Cercetare read-only, fara nicio modificare de cod. Sfera: `pack_facturare.modifica_date_factura`
(14 campuri, deja documentat in `modifica_date_factura_parametri.md`) e confirmata ca **singura**
cale directa pentru serie/numar/data act/data scadenta + ruta/delegat/masina/agent/dataora_exp/
id_facturare/listare_detaliata/text_aditional/tip_saft/efactura. Intrebarea de aici: exista alte
cai, pentru campurile din grupele A/B/C, in afara drumului de emitere initiala.
**Rezumat**
| Camp | Verdict | Nota |
|---|---|---|
| A. ID_VENCHELT | NU (cu o rezerva) | vezi pct. 1.4 — posibil editabil direct in grid, neverificat pana la capat |
| A. ID_SECTIE | **DA** (nou, activ) | prin editare directa a notei contabile, pct. 1.3-1.4 |
| A. ID_RESPONSABIL | **DA** (nou, activ) | idem |
| A. ID_LUCRARE | **DA** (nou, activ) | idem |
| B. ID_FDOC (tip document) | NU | cautat, negasit — pct. 2 |
| B. ID_VALUTA | NU | cautat, negasit |
| B. Zi curs | NU | cautat, negasit |
| B. ID_CLIENT | NU | cautat, negasit |
| B. sursa/"altele" | NU | cautat, negasit |
| B. gestiune sursa | NU | cautat, negasit |
| B. politica de preturi | NU | cautat, negasit |
| C. opt_incasat/casa/serie chit/nr chit/incasat/POS | NU direct, PARTIAL indirect | scrise doar la emitere (pct. 3.1); posibila cale indirecta prin editarea notei contabile daca incasarea e pe acelasi `cod` (pct. 3.2, neconfirmat pana la capat) |
---
## 1. Grupul A — analiticele de antet (ID_VENCHELT, ID_SECTIE, ID_RESPONSABIL, ID_LUCRARE)
### 1.1 Unde traiesc si cum se scriu la emitere
Coloanele sunt pe `ACT` (`ACT.ID_VENCHELT%TYPE`, `ACT.ID_SECTIE%TYPE`, `ACT.ID_RESPONSABIL%TYPE`,
declarate asa in pachetul Oracle), nu pe `VANZARI`. La emitere, `frm_date_factura`/`frm_date_aviz`
(`COMUN\clase\ofacturare.vc2:9041-9706` / `:6900-7516`, controale `Ct_clb_venchelt`/`Ct_clb_sectie`/
`Ct_clb_responsabil`/`Ct_clb_lucrare`) populeaza variabilele de sesiune Oracle
`pack_facturare.nid_venchelt` / `nid_sectie_stoc` / `nid_responsabil` / `nid_lucrare`
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:1884-1887`), care se scriu o singura data, uniform pe
tot documentul, in `INSERT INTO ACT_TEMP` la contabilizare (comentariul explicit de la linia 1286
din `PACK_CONTAFIN.pck`: *"Am completat ID_RESPONSABIL la instructiunile INSERT INTO ACT_TEMP"*).
Nicaieri in cele doua fisiere Oracle centrale nu exista `UPDATE ACT SET ID_VENCHELT|ID_SECTIE|
ID_RESPONSABIL|ID_LUCRARE = ...` (grep combinat `UPDATE ACT ... SET` + fiecare coloana, zero
potriviri, atat in pachetul curent `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` cat si in
`PACK_CONTAFIN.pck`, `PACK_UPDATE.pck`, `PACK_MIGRARE.pck` din `COMUN\docs`).
`frm_date_factura`/`frm_date_aviz` sunt instantiate **doar** din interiorul `ofacturare.vc2`
insusi (nu am gasit niciun `Createobject` catre ele in afara clasei) — sunt exclusiv wizard-ul de
la emitere, nu apar pe niciun flux de editare ulterioara.
**Concluzie partiala**: `modifica_date_factura` (calea documentata deja) NU atinge aceste 4
campuri, si nu exista niciun `UPDATE` Oracle separat pe ele. **Pana aici, verdictul era NU.**
### 1.2 Descoperire care schimba raspunsul: `frm_facturi.do_editare_factura` (functionalitate noua)
Commit-ul cel mai recent din branch (`97d1613`, *"#6 editare factura emisa: omodificari.vcx intra
in proiect"*) a adaugat exact ce lipsea. Exista deja o cercetare anterioara in acest depozit
(`docs\cercetare\rec_modific2024.md`, scrisa inainte de acest commit) care documenteaza clasa
`frm_modific2024` (`COMUN\clase\omodificari.vc2:6375+`, editor generic de nota contabila,
partajat cu ROAGEST) si concluziona explicit: *"clasa exista deja, dar codul apelant din
ROAFACTURARE NU exista inca — trebuie scris"*. **Acel gol a fost umplut** — am gasit apelantul,
cablat si activ:
`COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_editare_factura` (~linia 3690-3869):
- Garda: blocheaza daca factura a fost trimisa in eFactura (`EsteInEFactura`, `:3764-3767`).
- `IncarcaCursoareModificareNota(lnCod, pnAn, pnLuna, .F.)` (helper din `ofacturare_editare.prg`,
citit dar nemodificat) incarca nota contabila completa (`vact_tot`/`vrul_tot`/`vrul_obinv_tot`
filtrate pe `cod`) in cursoarele READWRITE `actactan`/`tact`/`rul_temp`/`trul`/
`rul_temp_obinv`/`trul_obinv` (`:3769`).
- `Omodif = Createobject([frm_modific2024], lnIdSet)` + `Omodif.Show()` (`:3796-3797`) — deschide
editorul modal pe cursorul `tact` deja incarcat cu nota facturii curente.
- Daca userul apasa Terminat (`buton = 1`, `:3799-3833`): deschide tranzactie manuala, sterge nota
veche (`OSCRIE_IN_FISIERE(2,.T.,.T.)`), rescrie din cursoarele editate (`tact`->`actactan`,
`trul`->`RUL_TEMP`, `trul_obinv`->`RUL_TEMP_OBINV`, cu `id_util`/`sters=0` noi),
`OSCRIE_IN_FISIERE(0,.T.,.T.)` (scriere noua), apoi
`pack_contafin.finalizeaza_modificare_nota(...)`, commit/rollback.
Acest `finalizeaza_modificare_nota` (`PACK_CONTAFIN.pck:8601-8651`, citat deja in
`rec_modific2024.md`) **resincronizeaza `vanzari`** dupa editare: `SELECT COUNT(*) FROM vanzari
WHERE cod = tnCod; IF > 0 THEN pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou); END IF;`.
### 1.3 Ce e efectiv editabil in `tact`/`trul` — verificat prin `do_modifica`
`frm_modific2024.do_modifica` (`omodificari.vc2:13836-13957`) e mecanismul generic prin care un
camp din grid deschide un dialog de cautare (`cauta_alfa`) si apoi face `REPLACE` pe cursorul
corespunzator. Pentru `trul`/`trul_obinv` (nivel LINIE de nota, nu antet unic), `CASE` explicit
gestioneaza:
```
CASE m.lcControl = 'nrord' -> replace nrord with loCauta.nrord, id_lucrare with loCauta.id_lucrare
CASE m.lcControl = 'sectie' -> replace sectie with loCauta.sectie, id_valuta with loCauta.id_sectie (!)
CASE m.lcControl = 'nresp' -> replace nresp with loCauta.nume, id_responsabil with loCauta.id_responsabil
```//omodificari.vc2:13934-13943
Deci **ID_LUCRARE si ID_RESPONSABIL sunt editabile explicit**, pe cursorul `trul` (nivel de linie
RUL), prin acest mecanism, azi, din UI, prin `do_editare_factura`. Linia `sectie` are ce pare a fi
un bug preexistent (`replace ... id_valuta with loCauta.id_sectie` in loc de `id_sectie` — cod
existent, nu l-am atins, doar il semnalez ca observatie relevanta pentru evaluarea "functioneaza
sau nu in practica"): daca bug-ul e real, campul afisat `sectie` (text) se schimba, dar coloana
`id_sectie` propriu-zisa risca sa NU se actualizeze corect (se suprascrie `id_valuta` in loc).
Nu am testat comportamentul, doar am citit codul.
**ID_VENCHELT**: NU exista un caz `CASE m.lcControl = 'venchelt'` (sau `dst_chlt`) in
`do_modifica` pentru `trul`/`trul_obinv` — cautat explicit in tot procedeul (13836-13957), zero
potriviri. Insa Grid1 al clasei (proprietatile de coloana, in afara metodelor) are o coloana cu
`ControlSource = "dst_chlt"` (`omodificari.vc2:753, 2862, 7393` — a treia aparitie e in intervalul
propriu al clasei `frm_modific2024`), deci explicatia venit/cheltuiala **e afisata** in grid. Daca
acea coloana permite editare directa de text (nu prin popup de cautare) sau daca exista un alt
handler (buton dedicat, dublu-click) care leaga `id_venchelt`, nu am verificat pana la capat — vezi
"Necunoscute ramase". **Raspuns pentru ID_VENCHELT: NU confirmat ca ruta activa, dar cu o rezerva
neinchisa** (grid-ul afiseaza coloana, mecanismul de editare exact al ei nu a fost trasat complet).
### 1.4 Nivel LINIE vs. nivel ANTET
O nuanta importanta: campurile din grupul A, la nivel Oracle, traiesc pe `ACT` (fiecare linie de
nota isi are propriile `ID_VENCHELT`/`ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE`), desi la EMITERE
se scriu uniform (aceeasi valoare pe toate liniile unui document, din variabilele de sesiune).
Editarea prin `frm_modific2024` e la nivel de LINIE (fiecare rand din `trul` se editeaza separat),
nu o singura bifa de antet — deci userul poate, teoretic, sa lase valori diferite pe linii diferite
ale aceleiasi facturi dupa editare, lucru care nu se putea intampla la emiterea initiala (uniforma).
Asta conteaza pentru orice raportare care presupune "un singur ID_SECTIE per factura".
**Verdict grup A**: **DA** pentru `ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE` (cale noua, activa,
`frm_facturi.do_editare_factura` -> `frm_modific2024` -> `OSCRIE_IN_FISIERE` ->
`pack_contafin.finalizeaza_modificare_nota` -> Oracle `ACT`/`RUL`, cu resincronizare in `VANZARI`).
**NU confirmat** pentru `ID_VENCHELT` prin acelasi mecanism generic (`do_modifica` nu are caz
pentru el), dar cu rezerva de la 1.3 neinchisa complet.
---
## 2. Grupul B — identitate/sursa (fdoc, valuta, zi curs, client, altele, gestiune sursa,
politica preturi)
Controalele (`Ct_clb_fdoc`, `Ct_clb_valuta`, `Clb_zi_curs`, `Ct_clb_nume_client`, `Ct_clb_altele`,
`Ct_clb_gestiune_init`, `Ct_clb_politici_preturi`) au fost gasite **exclusiv** in metodele proprii
ale `frm_date_aviz`/`frm_date_factura`/`frm_date_aviz_lucrare` din `ofacturare.vc2` — acelasi
wizard de emitere de la grupul A (cautare `vfp_symbols.ps1 -Grep` pe toate cele 7 nume de
control, in tot proiectul indexat: 316 fisiere, zero potriviri in afara `ofacturare.vc2`).
Cautat in Oracle (pachetul curent + `PACK_CONTAFIN.pck` + `PACK_UPDATE.pck` + `PACK_MIGRARE.pck`):
`UPDATE VANZARI ... SET (ID_FDOC|ID_VALUTA|ID_CLIENT|ID_PART|ID_GESTIN|ID_POL) = ...` si
`UPDATE ACT ... SET` idem — zero potriviri (grep multiline, fereastra 400 caractere dupa
`UPDATE`). Toate cele 13 aparitii ale `UPDATE VANZARI` din pachetul curent au fost inspectate
individual (`sterge_factura`, `sterge_proforma`, `marcheaza_facturat`,
`scrie_corespondente_vanzari`, plus `modifica_date_factura`) — niciuna nu atinge aceste coloane
(ating `STERS`/`FACTURAT`/`ID_UTILFACT`/`DATA_FACTURAT`/`AVIZE`/`COD`/serie-numar-data-scadenta).
`frm_modific2024`/`do_editare_factura` (descoperirea de la grupul A) nu ajuta aici: cursoarele pe
care le editeaza (`tact`/`trul`/`trul_obinv`) sunt nota contabila (conturi, sume, gestiuni de
STOC, TVA) — nu contin fdoc/valuta document/client/politica de preturi ale facturii; acestea sunt
proprietati ale `VANZARI`, populate o singura data la `scrie_factura2`/`scrie_in_vanzari`, in afara
oricarui flux gasit de editare ulterioara.
**Verdict grup B: NU**, pentru toate cele 7 campuri — cautat si negasit, in ambele straturi
(Oracle: pachetul de facturare curent + cele 3 pachete conexe; VFP: toate clasele indexate de
`vfp_symbols.ps1`, 1128 clase / 9281 metode / 2461 proceduri).
---
## 3. Grupul C — incasarea
### 3.1 Unde se scrie la emitere
Controalele din `frm_alte_date` (`COMUN\clase\ferestre_cere_date.vc2:2219+`) — `opt_incasat`,
`Cb_casa`, `Clb_serie_chit`, `Clb_nrchit`, `Clb_incasat`, `chkPOS` — au `ControlSource` pe
proprietati ale obiectului `poDate` (`poDate.nIncasatPos`, si prin cod pe `poDate.incasat`,
`poDate.nr_incasare`, `poDate.ntip_incasare`, `poDate.id_casa`, `poDate.serie_chit`), NU pe un
cursor legat direct de tabel. `frm_alte_date` e instantiat exclusiv din
`frm_facturare_articole(2).inainte_de_do_termin` si din doua fluxuri de listare
(`ofacturare.prg:listare_protocol`, `ofacturare_stoc.prg:oscrie_vanzare_din_stoc`,
`oproceduri_listari.prg:listare_protocol`) — toate parti ale fluxului de EMITERE, niciuna de
editare ulterioara.
La `do_scrie_factura` (`ofacturare.vc2:14260-14389`, ambele forme `frm_facturare_articole` si
`frm_facturare_articole2`), valorile din `poDate` sunt asamblate intr-un string
`lcListaIncasare` (format `tip|suma|id_casa;...`) si trimise ca parametru la
`pack_facturare.scrie_factura2` / `scrie_factura_avize` / `scrie_proforma` (apel RPC unic, la
emitere). In Oracle, `scrie_factura2` cheama intern `pack_facturare.scrie_incasari` (linii 6204,
7034 din pachetul curent), care parseaza lista si cheama `scrie_incasare2` per linie
(`:13096-13168` -> `:13170+`) — aceasta insereaza o **nota contabila noua in `ACT_TEMP`**
(coloane vazute: `ACT.SUMA`, `ACT.ID_PARTD` pentru casa/banca, `ACT.ASCC`/`SCD`/`ASCD`,
`ACT_TEMP.PAYMENTCODE`), adica incasarea devine ea insasi o linie de jurnal (cont 5311/5121 etc.
vs. 4111), scrisa prin acelasi flux `ACT_TEMP -> ACT` ca restul documentului, nu un camp separat
pe `VANZARI`. `scrie_incasare2` nu are niciun apelant in afara lui `scrie_incasari` (cautat cu
grep in tot pachetul de facturare — un singur call-site).
### 3.2 Cale de schimbare DUPA emitere
Nu exista nicio procedura Oracle de tip "modifica_incasare"/"corecteaza_incasare" (cautat
`PROCEDURE ... (modifica|corecteaza|actualizeaza)...incasa...` in pachetul curent si
`PACK_CONTAFIN.pck` — zero potriviri). `scrie_incasari`/`scrie_incasare2` nu au niciun apelant VFP
direct (cautat in tot proiectul indexat — zero potriviri) — sunt folosite doar intern, o singura
data, la emitere.
**PARTIAL, neconfirmat pana la capat**: incasarea scrisa la emitere e o linie de nota contabila
(`ACT`/`RUL`) ca oricare alta. Daca acea linie foloseste acelasi `cod` (acelasi document contabil)
ca restul facturii, atunci mecanismul nou de la Grupul A (`frm_facturi.do_editare_factura` ->
`frm_modific2024`) ar incarca-o si pe ea in grid — si, editand campurile generice de nota
(cont/gestiune/suma, prin acelasi `do_modifica` sau direct in grid), un utilizator ar putea
schimba indirect suma/contul incasarii, deci si `casa`/`suma incasata` efectiva. **Nu am confirmat
daca incasarea primeste acelasi `cod` sau un `cod` separat** — ar necesita fie testare live
(interzisa aici, doar cercetare pe cod), fie citirea completa a insertiei din `scrie_incasare2`
(am citit doar semnatura si primele ~35 linii, nu INSERT-ul propriu-zis in `ACT_TEMP`). Marchez
explicit ca necunoscuta ramasa, nu ca "DA" confirmat.
`opt_incasat`/`chkPOS`/serie-numar chitanta/bon **ca atare** (proprietati `poDate` folosite doar
pentru alocarea de numere de serie si generarea listei `lcListaIncasare`) nu au niciun camp
persistent separat pe care sa-l poata atinge editarea ulterioara a notei — nu exista coloane
`VANZARI.OPT_INCASAT`/`VANZARI.SERIE_CHIT` in tot ce am cautat.
**Verdict grup C**: NU pentru o cale directa/documentata de schimbare dupa emitere; PARTIAL,
neconfirmat, prin editarea generica a notei contabile (acelasi mecanism nou de la Grupul A),
DACA incasarea partajeaza `cod`-ul cu restul documentului.
---
## Ce am cautat (pentru un "NU" verificabil)
- **Oracle**: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql`
(17020 linii, pachetul curent) — grep pe `UPDATE VANZARI`, `UPDATE ACT`, `UPDATE DOCUMENTE` (toate
aparitiile citite in context), plus grep combinat `UPDATE <tabel> ... SET <coloana> =` (fereastra
multiline 300-400 caractere) pentru fiecare coloana din grupele A/B/C. Idem pe
`D:\ROA\ROAFACTURARE\COMUN\docs\PACK_CONTAFIN.pck` (9041 linii), `PACK_UPDATE.pck` (2420 linii),
`PACK_MIGRARE.pck` (407 linii).
- **VFP**: `vfp_symbols.ps1 -Grep -CodeOnly` (index complet: 316 fisiere, 1128 clase, 9281 metode,
2461 proceduri) pentru fiecare nume de coloana Oracle si fiecare nume de control VFP din enunt
(`Ct_clb_venchelt/sectie/responsabil/lucrare/fdoc/valuta/altele/gestiune_init/politici_preturi`,
`Clb_zi_curs`, `opt_incasat`, `Cb_casa`, `Clb_serie_chit`, `Clb_nrchit`, `Clb_incasat`, `chkPOS`).
Pentru fiecare formular gasit (`frm_date_factura`, `frm_date_aviz`, `frm_alte_date`), am cautat
toti apelantii (`Createobject("...")`) in tot proiectul indexat, ca sa confirm ca sunt doar pe
fluxul de emitere.
- **Citire, fara scriere**: `ofacturare_comun.vc2` (metoda `do_editare_factura`, ~3690-3869) si
`ofacturare_editare.prg` (integral, 457 linii) — apartin altui fir de lucru, doar citite.
- `omodificari.vc2` (16464 linii) — clasa `frm_modific2024`: citite integral `do_modifica`
(13836-13957) si `do_salvare` (13985-14042); NU am citit toate cele ~180 de metode ale clasei
(Grid1.Column*, pgfArticole.*) — posibil sa existe alte cai de editare directa in grid,
nedescoperite prin `do_modifica`.
## Necunoscute ramase
1. **ID_VENCHELT** (grup A): daca e editabil in grid-ul `frm_modific2024` prin alt mecanism decat
`do_modifica` (coloana `dst_chlt` exista in grid) — neverificat pana la capat.
2. **Incasarea pe acelasi `cod`** (grup C): daca linia de incasare scrisa de `scrie_incasare2`
foloseste acelasi `cod` ca restul facturii (ceea ce ar face-o editabila prin
`do_editare_factura`) — necesita citirea INSERT-ului efectiv in `ACT_TEMP` din
`scrie_incasare2` (nu doar semnatura, citita aici doar partial) sau verificare pe date reale.
3. **Bug-ul de la `sectie`** (`omodificari.vc2:13941`, `id_valuta with loCauta.id_sectie` in loc de
`id_sectie`) — semnalat ca observatie, nu investigat mai departe (nu face parte din intrebare,
dar afecteaza increderea in verdictul "DA" pentru `ID_SECTIE": campul e teoretic editabil, dar
codul care il scrie pare sa aiba un bug care ar putea sa nu-l actualizeze corect in practica).

View File

@@ -1,468 +0,0 @@
# S10 (runda 3) — Proiectare: pretul re-derivat din contract la reemiterea #13
Agent de proiectare (research), read-only. Nicio editare de cod, nicio scriere Oracle in afara de
`SELECT`. Sursa SQL: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii — **de data asta liniile citate coincid exact cu cele din raportul vechi
`s10_pret_rederivat.md`, fara offset**). Date verificate pe `MARIUSM_AUTO@ROA_CENTRAL`, doar
`SELECT`, 11.08.2026 — baza de dezvoltare, esantion mic (vezi sectiunea 2), nu productie.
## 0. Rezumat
**Intrebare adaugata de team-lead, raspuns scurt: NU.** `cursor_retur_document` (procedura folosita
la INCARCAREA liniilor unui document existent, apelata din VFP cu `V_COPIERE=1`) **citeste pretul
ca atare din `VANZARI_DETALII.PRET`, nu il re-deriva** — nu exista niciun `JOIN` catre
`CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART` in toata procedura (`:3949-4062`). Presupunerea din S8b se
confirma pe cod. Detaliu complet, cu dovezi, in sectiunea 1bis (nou adaugata, tratata ca prioritara
fata de restul raportului, conform cererii).
**Faptul portant (scrierea, nu citirea) se reconfirma integral** (`:5146-5185`) si se extinde cu
doua descoperiri noi:
1. **TVA-ul si identitatea valutei sunt re-derivate din exact acelasi `JOIN`/`SELECT` ca pretul**,
pe aceeasi ramura de contract — nu doar pretul e in pericol la reemitere, ci pretul + cota de
TVA + valuta articolului, impreuna, tot-sau-nimic (sectiunea 3).
2. **Nu exista nicio a doua re-derivare mai departe in lant** — `PRET` trece neschimbat de la
`VANZARI_DETALII_TEMP` in `VANZARI_DETALII` (`scrie_in_vanzari`, `:13705-13757`, coloana `PRET`
copiata direct, fara `DECODE`/recalcul) si `contabilizeaza_articol` nu scrie niciodata pe
`VANZARI_DETALII.PRET` (scrie doar `ACT_TEMP`, nota contabila). Singurul loc de re-derivare e
`adauga_articol_factura:5146-5185` (sectiunea 1) — **la scriere, niciodata la citire** (sectiunea
1bis).
Pe date reale (esantion mic, baza de dezvoltare): **3 din 11 linii deja facturate pe contract au
azi un pret de contract diferit de pretul efectiv facturat** — divergenta nu e ipotetica, e deja
prezenta (sectiunea 2).
**Recomandare** (detaliata in sectiunea 4): varianta **(c) avertizare + confirmare explicita**,
implementabila integral in VFP cu `goExecutor` (fara sa ating `pack_facturare`, conform deciziei
27-bis), cu optiunea de a trece la **(b) blocare stricta** daca Marius prefera zero schimbari
tacite vreodata. Varianta **(d) ocolire a ramurii** e posibila mecanic dar produce o regresie reala
(pierderea legaturii `VANZARI_DETALII.ID_CTR`) — **nu o recomand**.
---
## 1. Reverificarea faptului portant
Cod citit direct din sursa (`:5039-5220`), nu preluat din raportul vechi.
```sql
-- :5039-5050 — V_OPT_FACTURARE se calculeaza intern, din V_ID_CTR primit de la VFP
IF pack_facturare.ntip IN (2, 6, 26, 52) THEN
BEGIN
SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;
EXCEPTION
WHEN NO_DATA_FOUND THEN
V_OPT_FACTURARE := 4;
END;
END IF;
```
```sql
-- :5146-5185 — ramura de contract, in interiorul CASE-ului principal (:5052-5220)
WHEN V_OPT_FACTURARE = 3 THEN
BEGIN
SELECT DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR),
B.PROC_TVAV,
B.ID_VALUTA,
A.PRET_CU_TVA,
C.IN_STOC
INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
FROM CTR_ARTICOLE A
LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL_ART = B.ID_POL_ART
LEFT JOIN NOM_ARTICOLE C ON B.ID_ARTICOL = C.ID_ARTICOL
WHERE B.ID_ARTICOL = V_ID_ARTICOL AND A.ID_CTR = V_ID_CTR AND B.ID_POL = V_ID_POL;
EXCEPTION
WHEN NO_DATA_FOUND THEN
... V_PRET := V_PRET_TEMP; V_ID_VALUTA := V_ID_VALUTA_TEMP; V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP; V_IN_STOC := V_IN_STOC_TEMP;
END;
```
**Confirmat, cuvant cu cuvant fata de runda 9**: cu `OPT_FACTURARE = 3` (sau `NULL`, implicit 3),
daca `CTR_ARTICOLE.PRET_UNITAR <> 0`, acea valoare **inlocuieste** `V_PRET_TEMP` (pretul trimis de
VFP), necondiționat de faptul ca operatorul a atins sau nu linia. Procedura **nu are nicio notiune
de reemitere** — parametrii sunt toti `IN` (`:4989-5059`, verificat), niciun canal care sa spuna
"nu recalcula". `NO_DATA_FOUND` (politica/articol lipsa) e singurul caz care respecta pretul
trimis.
### Nu exista alt loc care suprascrie pretul
- **`scrie_in_vanzari`** (`:13488+`), care muta liniile din `VANZARI_DETALII_TEMP` in
`VANZARI_DETALII` (tabela finala) la finalizarea documentului: `PRET` e in lista de coloane
copiate **neschimbat** (`:13705-13757`, `SELECT ... PRET ... FROM VANZARI_DETALII_TEMP` →
`INSERT INTO VANZARI_DETALII (..., PRET, ...)`), fara `DECODE`/recalcul. Cautare exhaustiva pe
fisier: **zero** `INSERT INTO VANZARI_DETALII` (tabela finala, nu `_TEMP`) in afara de acest
punct.
- **`contabilizeaza_articol`** (`:7173-7547`, reconfirmat structural fata de runda 9) citeste
`detalii_articol.pret` (randul deja scris in `VANZARI_DETALII_TEMP`) doar ca sa calculeze suma
notei contabile — nu scrie niciodata inapoi pe `VANZARI_DETALII.PRET`.
- **`modificare_politica_stoc`** (`:2122-2135`) face un `UPDATE CRM_POLITICI_PRET_ART SET PRET=0,
...` — e singurul alt `SET PRET` gasit in tot fisierul, dar reseteaza **politica** la schimbarea
monedei stocului, complet in afara lantului de facturare a unei linii; nu atinge vreo linie de
document.
**Concluzie sectiune**: raspunsul din runda 9 se confirma **integral**, fara nicio corectie, si se
inchide explicit intrebarea "mai exista si alte locuri" — nu mai exista.
---
## 1bis. Citirea la incarcare: `cursor_retur_document` re-deriva pretul?
**Adaugat la cererea team-lead-ului — critic pentru S8b, tratat ca prioritar.**
### Verdict: NU. Pretul se citeste ca atare din `VANZARI_DETALII.PRET`, fara nicio re-derivare.
Corp complet, `PACK_FACTURARE:3949-4062`:
```sql
PROCEDURE cursor_retur_document(V_IN_VALUTA IN NUMBER,
V_LISTAID IN VARCHAR2,
V_COPIERE IN NUMBER,
V_PROFORMA IN NUMBER,
V_ID_UTIL IN NUMBER,
V_CURSOR OUT cursor_facturare) IS
...
BEGIN
pack_facturare.initializeaza_facturare(V_ID_UTIL);
OPEN V_CURSOR FOR
WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR)))
SELECT ROWNUM AS ID_C, A.ID_ARTICOL, A.LOT, A.SERIE, A.ID_POL, A.ID_VALUTA,
... DISCOUNT_UNITAR (derivat din A.DISCOUNT_UNITAR) ...,
B.CODMAT, B.CODBARE, B.DENUMIRE, NVL(B.UM,'') AS UM,
... GESTIONABIL ..., A.CANTITATE, A.PROC_TVAV, A.ID_JTVA_COLOANA,
A.PRET_CU_TVA AS PRETURI_CU_TVA, A.CURS, A.MULTIPLICATOR,
(CASE WHEN NVL(C.MONEDA_NATIONALA,1)<>1
THEN ROUND(A.CURS * ROUND(A.PRET, pack_sesiune.nzecimale_pretvval) / NVL(A.MULTIPLICATOR,1), pack_sesiune.nzecimale_pretv)
ELSE ROUND(A.PRET, pack_sesiune.nzecimale_pretv)
END) + A.DIFERENTA AS PRET,
...
FROM (SELECT A1.ID_ARTICOL, A1.LOT, A1.SERIE, A1.ID_POL, A1.DISCOUNT_UNITAR, A1.DIFERENTA,
A1.CANTITATE, A1.PROC_TVAV, A1.ID_JTVA_COLOANA,
NVL2(A1.ID_GESTIUNE,1,0) AS GESTIONABIL, A1.PRET_CU_TVA, A1.PRET, A1.ID_VALUTA,
NVL(A2.CURS,0) AS CURS, NVL(A2.MULTIPLICATOR,1) AS MULTIPLICATOR,
A1.EXPLICATIE, A1.ID_GESTIUNE, A1.PRET_ACHIZITIE, A1.PRETD, A1.ID_JTVA_COLOANA_EX
FROM VANZARI_DETALII A1
LEFT JOIN VANZARI_CURSURI A2 ON A1.ID_VANZARE = A2.ID_VANZARE AND A1.ID_VALUTA = A2.ID_VALUTA
WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)) A
LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL
LEFT JOIN NOM_VALUTE C ON A.ID_VALUTA = C.ID_VALUTA
ORDER BY B.DENUMIRE;
END cursor_retur_document;
```
**Analiza surselor, coloana cu coloana:**
- **`A.PRET`** (`:4009-4015`, alias final `PRET`) vine din subquery-ul `A` care e
**`VANZARI_DETALII A1`** direct (`A1.PRET`, `:4041`) — **nicio referinta la
`CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART`/`CRM_POLITICI_PRETURI` in toata procedura** (cautare
exhaustiva pe corpul de la `:3949-4062`: zero hit-uri pentru oricare din cele trei tabele).
Singurele `JOIN`-uri sunt:
- **`VANZARI_CURSURI A2`** (`:4051-4053`), pe `ID_VANZARE`+`ID_VALUTA` — aduce `CURS`/
`MULTIPLICATOR` **stocate pe documentul insusi** (cursul valutar de la momentul cand a fost
scris, nu un curs "de azi" recalculat — tabela `VANZARI_CURSURI`, nu `CURS`).
- **`NOM_ARTICOLE B`** (`:4056-4057`) — doar campuri descriptive (`CODMAT`, `CODBARE`,
`DENUMIRE`, `UM`), acelasi tipar confirmat deja in `rec_pret_lazy.md` A2 pentru alte cursoare
din pachet — niciodata sursa de pret.
- **`NOM_VALUTE C`** (`:4058-4059`) — doar `MONEDA_NATIONALA`/`NUME_VAL`, folosit in `CASE` ca sa
decida **cum se formateaza** afisarea (lei vs. valuta), nu ca sa aduca un pret alternativ.
- Expresia finala pe `PRET` e o **transformare de afisare** a lui `A.PRET` (rotunjire +
conversie in valuta folosind `A.CURS`/`A.MULTIPLICATOR` **stocate pe document**) plus
`A.DIFERENTA` (coloana proprie pe `VANZARI_DETALII`, un ajustaj deja persistat pe linie, nu o
recalculare live) — nu o re-derivare dintr-o sursa externa.
- **Acelasi tipar pe `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`** — toate citite
direct din `A1.*` (`VANZARI_DETALII`), fara vreun `JOIN` catre politica/contract.
**Confirmare suplimentara pe partea VFP — `cursor_retur_document` cu `V_COPIERE=1` e chiar calea
folosita la reincarcarea unui document existent, cu prioritate fata de ramura de contract**:
```
COMUN\programe\ofacturare.prg:266-283
Do Case
Case m.llCopiere
lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}]
Case Inlist(tnTip, 48, 49)
...
Case tnTip = 45
...
Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi
...
Case Inlist(tnTip, 2, 26, 6, 52) && contract
lcSqlCursor = [{call ]+gcS+[.pack_facturare.cursor_contract(...)}]
```
`Do Case` in VFP executa **doar prima ramura adevarata**. Cand `llCopiere` e activ (incarcarea unui
document existent — exact scenariul "editare prin regenerare" descris de team-lead), procedura
apelata e **intotdeauna** `cursor_retur_document`, **indiferent de `tnTip`** — ramura de contract
(`cursor_contract`, `:283+`) nu se mai ajunge deloc, chiar daca documentul e de tip 2/6/26/52.
`cursor_retur` (`:3934-3947`) e doar un wrapper subtire peste `cursor_retur_document` cu
`V_COPIERE=0`/`V_PROFORMA=0`, pentru returul propriu-zis — nu schimba concluzia.
### Consecinta pentru S8b
**Presupunerea "re-derivarea e o proprietate a scrierii, nu a citirii" se confirma pe cod, fara
rezerve.** Deschiderea formularului de editare (etapa II, incarcarea liniilor existente) **nu**
declanseaza nicio re-derivare de pret/TVA/valuta — documentul apare "neschimbat" la simpla
deschidere, exact cum a presupus S8b. Mecanismul de detectare a schimbarii (compara starea
incarcata cu starea la confirmare) **poate functiona ca premisa** — riscul de suprascriere tacita
descris in restul acestui raport (sectiunile 1-6) apare **doar la re-scriere** (regenerare efectiva
prin `adauga_articol_factura`), nu la incarcare. Cele doua momente (citire la deschidere, scriere
la regenerare) sunt guvernate de **cursoare Oracle complet diferite** (`cursor_retur_document` vs.
`adauga_articol_factura`), fara cod comun — o modificare facuta doar pe unul din ele (de exemplu o
garda adaugata la scriere, variantele (b)/(c) din sectiunea 4) **nu afecteaza** comportamentul
celuilalt.
---
## 2. Tipurile afectate si frecventa in date
**Tipurile de document**: `pack_facturare.ntip IN (2, 6, 26, 52)` (`:5039`), confirmat identic cu
tabelul din `rec_editare_factura.md:176` (contract = tip 2/6/26/52, ramura de scriere
`scrie_factura2`, la fel ca lista de preturi — nu are RPC separat).
**Frecventa in date** (schema `MARIUSM_AUTO`, doar `SELECT`, 11.08.2026):
```sql
-- VANZARI_DETALII cu ID_CTR populat, active: 74
-- din care, articolul are un rand corespunzator in CTR_ARTICOLE (id_ctr+id_articol): 11
-- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI diferit de VANZARI_DETALII.PRET: 3
-- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI egal cu VANZARI_DETALII.PRET: 8
-- din acelea, CTR_ARTICOLE.PRET_UNITAR = 0 (nu suprascrie): 0
```
**Interpretare, cu grija la marimea esantionului**: baza e de dezvoltare, cu doar 27 randuri totale
in `CTR_ARTICOLE` — nu extrapolez procentul (27%) la volumul de productie. Dar **3 cazuri reale,
nu zero**, e dovada suficienta ca fenomenul **chiar se intampla**, nu doar teoretic: daca oricare
din cele 3 facturi ar fi reemisa azi prin regenerare (#13 etapa II), suma ei s-ar schimba tacit,
fara nicio actiune a operatorului legata de pret. Simetric, cele 8 cazuri "egal" arata ca in
majoritatea situatiilor curente reemiterea ar fi azi inofensiva — ceea ce face problema mai
insidioasa, nu mai putin reala: cand se produce, nimeni nu se astepta, pentru ca de obicei nu se
intampla nimic vizibil.
**Nu exista coloana de audit pe `CTR_ARTICOLE`** (confirmat din `user_tab_columns`: `ID_CTR_ART,
ID_CTR, ID_POL_ART, PRET_UNITAR, CANT, COEF_DISCOUNT, VAL_DISCOUNT, ID_VALUTA, EXPLICATIE, UM,
ID_ARTICOL, PRET_CU_TVA, ID_LOCATIA, PROC_TVAV, VALOARE` — nicio `DATA_MODIF`/`ID_UTIL_MODIF`) —
nu exista cale de a masura *cat de des* se schimba `PRET_UNITAR` in timp, doar *cate cazuri
divergente exista azi*. Raspunsul la "cat de des" ramane nedeterminabil din date; ce se poate
spune e doar ca divergenta **exista deja**, azi, pe un esantion mic.
---
## 3. Descoperire noua: TVA si valuta sunt re-derivate din **aceeasi** interogare ca pretul
Din citatul de la sectiunea 1: `SELECT DECODE(A.PRET_UNITAR, ...), B.PROC_TVAV, B.ID_VALUTA, ...`
— **un singur `SELECT`**, trei valori. Asta inseamna ca varianta de raspuns nu poate trata pretul
izolat: daca politica de pret a contractului (`CRM_POLITICI_PRET_ART`, aceeasi `B`) si-a schimbat
si cota de TVA sau valuta intre timp, reemiterea le suprascrie **pe amandoua**, prin acelasi
mecanism, in aceeasi conditie (`NO_DATA_FOUND` → respecta ce a trimis VFP; altfel → suprascrie).
Comparativ cu discountul si cursul (reconfirmare fata de runda 9, sectiunile 6-7 ale raportului
vechi, verificate din nou pe sursa curenta):
| Camp | Tratament pe ramura de contract | Risc la reemitere |
|---|---|---|
| **Pret** (`V_PRET`) | `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)` — suprascris daca `PRET_UNITAR<>0` | **Da — faptul portant** |
| **TVA** (`V_PROC_TVAV`) | `B.PROC_TVAV`, din acelasi `SELECT`, fara fallback conditionat separat | **Da — aceeasi conditie ca pretul** |
| **Valuta articol** (`V_ID_VALUTA`) | `B.ID_VALUTA`, idem | **Da — aceeasi conditie** |
| **Discount** (`V_DISCOUNT_UNITAR`) | Parametru trimis de VFP, scris direct in `INSERT` (`:5266`), nicio ramura din `CASE` il citeste | **Nu — mereu respectat, indiferent de ramura** |
| **Curs** (`V_CURS`) | `DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste doar 0 cu 1 | **Nu direct — dar vezi mai jos** |
**Consecinta combinata pret+valuta+curs**: daca `V_ID_VALUTA` re-derivat difera de valuta pentru
care VFP a calculat `V_CURS` (de ex. politica a trecut de la EUR la USD intre emitere si
reemitere), documentul reemis scrie **noua valuta cu vechiul curs trimis de VFP** — nicio
validare incrucisata intre cele doua (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). Aceeasi
observatie ca in runda 9, dar acum cu dovada ca sursa comuna (pret+TVA+valuta) inseamna ca **orice
gardă construita pentru pret trebuie sa verifice si TVA si valuta in acelasi pas**, nu doar pretul
— altfel o factura reemisa poate trece garda de pret nemodificata si totusi sa iasa cu alta cota
de TVA sau alta valuta.
**Discountul nu ridica aceeasi problema** — e mereu respectat ca atare, indiferent de ramura,
deci nu are nevoie de nicio garda la reemitere.
---
## 4. Variantele de raspuns
Toate patru presupun ca infrastructura de "sterge + reemite" din #13 etapa II trimite, pentru
liniile de pe un document-contract, acelasi `V_ID_CTR` care era pe linia originala (asta e
premisa explicita a sarcinii: "acelasi drum ca la emitere" — daca `V_ID_CTR` nu s-ar retrimite,
linia nici n-ar mai fi "de pe contract").
### (a) Se accepta re-derivarea — nicio garda
**Ce se face**: nimic — regenerarea apeleaza `adauga_articol_factura` exact ca la emitere, cu
acelasi `V_ID_CTR`. Comportamentul existent (sectiunea 1) se aplica neschimbat.
**Ce se strica**: criteriul din plan ("reemitere identica produce exact aceleasi sume" — vezi
sectiunea 6) **esueaza silentios** pe orice linie de contract al carei pret/TVA/valuta a fost
modificat intre timp. Dovedit sa se intample azi, pe date reale (sectiunea 2, 3 din 11). Utilizatorul
care corecteaza o cantitate pe o factura veche poate vedea, fara sa i se spuna, totalul facturii
schimbat pentru un articol la care nici nu s-a uitat.
**Cod atins**: zero. **Regresie pe emiterea normala**: zero (comportamentul de azi ramane
identic).
### (b) Se blocheaza reemiterea cand pretul curent difera
**Ce se face**: inainte de a porni stergerea+regenerarea, VFP ruleaza un `SELECT` (prin
`goExecutor`, exact tiparul deja folosit in `ofacturare.prg` pentru alte verificari punctuale —
`goExecutor.oSelecteaza2Value(...)`, `:2628`, `:2661`) care compara, pentru fiecare linie a
documentului cu `ID_CTR` populat, pretul/TVA/valuta stocate pe linie
(`VANZARI_DETALII.PRET`/`PROC_TVAV`/`ID_VALUTA`) fata de ce ar recalcula azi ramura de contract
(`CTR_ARTICOLE.PRET_UNITAR`/`CRM_POLITICI_PRET_ART.PROC_TVAV`/`.ID_VALUTA`, acelasi `JOIN` ca in
sectiunea 1, dar rulat direct din VFP, fara sa ating `pack_facturare`). Daca gaseste macar o
divergenta, **blocheaza regenerarea** cu mesaj ("Pretul de pe contract s-a schimbat pentru
articolul X: <vechi> -> <nou>. Actualizati contractul sau anulati regenerarea.") si nu porneste
deloc stergerea.
**Ce se strica**: orice regenerare pe un document cu **cel putin o linie** de contract cu pret
schimbat e blocata **integral**, chiar daca utilizatorul voia sa corecteze cu totul altceva
(ruta, o cantitate pe alta linie). Nu exista cale de a "accepta noul pret si continua" fara o a
doua interactiune — varianta (c) rezolva exact asta.
**Cod atins**: doar VFP (o interogare noua, read-only, plus un dialog de blocare), inaintea
apelului la infrastructura de stergere+regenerare din #13. **Zero atingere `pack_facturare`**
(decizia 27-bis respectata). **Regresie pe emiterea normala**: zero — garda ruleaza doar pe
calea de regenerare, nu la emiterea unui document nou.
### (c) Se avertizeaza si se cere confirmare, cu diferenta afisata
**Ce se face**: aceeasi interogare de detectie ca la (b), dar in loc sa blocheze, afiseaza un
dialog cu lista liniilor divergente si delta (pret vechi vs. nou, si TVA/valuta daca difera —
vezi sectiunea 3, toate trei trebuie aratate, nu doar pretul) si cere confirmare explicita
("Continuati cu noile preturi de pe contract?" Da/Nu). La "Nu", regenerarea se opreste ca la (b).
La "Da", regenerarea continua normal si ramura de contract face exact ce face azi (suprascrie).
**Ce se strica**: nu opreste suprascrierea insasi — doar o face **vizibila si asumata** inainte
sa se produca, exact criteriul cerut de plan ("un rezultat explicit decis, nu accidental"). Nu
ofera o cale de a **pastra** vechiul pret in timp ce se accepta restul modificarii — pentru asta
ar trebui combinata cu (d), cu costul ei (vezi mai jos). Risc de "warning fatigue": daca
divergenta e frecventa in productie (nu se poate masura azi, sectiunea 2), utilizatorii pot invata
sa dea click pe "Da" fara sa citeasca.
**Cod atins**: identic cu (b) plus un dialog cu doua butoane in loc de unul singur. **Regresie pe
emiterea normala**: zero.
### (d) Se ocoleste ramura — reemiterea trimite pretul documentului pe o cale care nu trece prin `DECODE`
**Posibil mecanic, cu un cost real.** Ramura de contract se selecteaza din doua conditii, ambele
in afara controlului direct al apelantului per-linie:
1. `pack_facturare.ntip IN (2,6,26,52)` — variabila **de sesiune** (`:1882`,
`pack_facturare.ntip := V_TIP`), setata o singura data la initializarea documentului (tipul
documentului insusi), nu per linie. A o schimba ar insemna sa reclasifici tot documentul
(afecteaza si alte ramuri `CASE` care depind de `ntip` — `:1905`, `:5053`, `:6056-6124`,
`:14820` — cu efecte necunoscute si necontrolate) — **contrazice premisa "acelasi drum ca la
emitere"** data explicit in sarcina. Nu recomand aceasta sub-varianta.
2. `V_ID_CTR` — parametru **per linie**, trimis explicit de VFP la fiecare apel
`adauga_articol_factura`. Daca VFP trimite `V_ID_CTR = NULL` pentru o linie la reemitere,
blocul de la `:5039-5050` gaseste `NO_DATA_FOUND` (cautarea `WHERE ID_CTR = NULL` nu potriveste
nimic) → `V_OPT_FACTURARE := 4` → ramura `WHEN V_OPT_FACTURARE = 3` nu se mai potriveste →
cade pe `ELSE` (`:5187-5203`) → `V_PRET := V_PRET_TEMP` (pretul documentului, respectat ca
atare, la fel TVA prin `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA`).
**Costul**: `V_ID_CTR` e si coloana scrisa in `VANZARI_DETALII_TEMP`/`VANZARI_DETALII.ID_CTR`
(`:5280`, copiata neschimbat mai departe la `:13730`/`:13755`). Trimitand `NULL` la reemitere,
linia **pierde definitiv legatura cu contractul** in tabela finala — orice raport care
grupeaza/filtreaza pe `VANZARI_DETALII.ID_CTR` (regasire vanzari pe contract, situatii
contractuale) nu va mai gasi acea linie dupa reemitere, desi articolul a fost, de fapt, livrat
pe acel contract. E o regresie permanenta si greu de observat (nu da eroare, doar dispare
tacit dintr-un raport), pe un camp folosit azi activ (`rec_pret_lazy.md`/`idpol_comanda_contract.md`
confirma ca legatura cu contractul e cheie de raportare in tot lantul `PACK_FACTURARE`).
In plus, o data pierduta legatura, **o a doua reemitere ulterioara nu ar mai putea re-deriva
pretul din contract nici daca s-ar dori** — comutarea e ireversibila per linie, nu un flag
comutabil.
**Nu recomand varianta (d)** — costul (pierderea trasabilitatii contract-linie, permanenta) e mai
mare decat problema pe care o rezolva, si nici macar nu rezolva complet criteriul "reemitere
identica" (rezolva doar pretul/TVA/valuta acelei linii, cu pretul unei regresii pe alt camp).
---
## 5. Discount, TVA, curs valutar — verificare fata de sectiunile 6-7 din raportul vechi
Deja tratat integral in sectiunea 3 de mai sus (descoperirea principala a acestei runde). Rezumat
scurt pentru trasabilitate fata de cererea explicita:
- **Discountul** (sectiunea 6 raport vechi): reconfirmat — niciodata re-derivat, indiferent de
ramura. **Nu ridica problema de reemitere.**
- **TVA-ul** (sectiunea 6 raport vechi): reconfirmat ca "mereu recalculat, din surse diferite pe
ramura" — dar cu precizarea noua ca pe ramura de contract, sursa **coincide exact** cu sursa
pretului (acelasi `SELECT`, aceeasi conditie `NO_DATA_FOUND`). **Ridica aceeasi problema ca
pretul, prin acelasi mecanism** — orice varianta (b)/(c) trebuie sa acopere si TVA, nu doar pret.
- **Cursul valutar** (sectiunea 7 raport vechi): reconfirmat — `V_CURS` mereu respectat ca atare
(`DECODE(V_CURS,0,1,V_CURS)`), nu se re-deriva direct. **Dar** identitatea valutei
(`V_ID_VALUTA`) se re-deriva pe aceeasi ramura de contract, din acelasi `SELECT` — daca politica
a schimbat valuta intre timp, reemiterea scrie noua valuta cu vechiul curs trimis de VFP, fara
nicio validare incrucisata (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). **Ridica aceeasi
problema, prin acelasi mecanism, si trebuie acoperita de aceeasi garda.**
---
## 6. Cazul "reemitere identica"
Criteriul din plan (S12) cere ca o reemitere fara nicio modificare intentionata sa produca
**exact** aceleasi sume. Verificat pe fiecare ramura a `CASE`-ului de la `:5052-5220` (nu doar
ramura de contract):
| Ramura (`ntip`) | Garantat identic la reemitere? | De ce |
|---|---|---|
| `ELSE` (lista de preturi, fara contract) | **Da, cu o rezerva minora** | `V_PRET := V_PRET_TEMP` — passthrough direct (`:5200`). TVA vine din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` (`:5188-5198`) — identic **doar daca** acea coloana de TVA nu a fost ea insasi editata intre timp (posibil, dar alt tip de risc, nu cel cerut de aceasta sarcina). |
| `45` (restaurant) | **Da, cu aceeasi rezerva** | `V_PRET := V_PRET_TEMP` setat **inainte** de orice `SELECT` (`:5109`) — respectat necondiționat. TVA vine tot din `JTVA_COLOANE` (`:5114-5139`), aceeasi rezerva ca mai sus. |
| `3,21,28,42,47` (comenzi) | **Nu garantat, dar esueaza tare, nu tacit** | `SELECT ... WHERE A.PRET = V_PRET_TEMP` (`:5077`) — daca pretul din comanda-sursa nu mai coincide exact cu ce trimite VFP, **`NO_DATA_FOUND` netratat** → eroare Oracle bruta, regenerarea pica integral, nu produce o suma gresita silentios. Diferit structural de ramura de contract. |
| `4` (aviz) | **Nu garantat, silentios** | `SELECT DISTINCT A.PRET ... FROM VANZARI_DETALII` (`:5082-5103`), **fara filtru pe pret** — re-citeste pretul curent al liniei din avizul-sursa necondiționat. Daca avizul insusi a fost modificat intre emiterea initiala si reemitere, sau daca exista mai multe randuri candidate (`DISTINCT` pe o potrivire ambigua), reemiterea poate lua alt pret, tacit — **acelasi tipar de risc ca la contract**, dar pe alt document-sursa. |
| `V_OPT_FACTURARE=3` (contract) | **Nu garantat, silentios** | Sectiunea 1 — faptul portant. |
**Observatie in afara perimetrului cerut, dar direct relevanta**: daca #13 etapa II ajunge sa
regenereze si facturi emise initial din **aviz** (`ntip=4`), nu doar din contract, **acelasi tip
de risc silentios exista si acolo**, prin alt mecanism (re-citire necondiționata din
`VANZARI_DETALII` a avizului-sursa, fara filtru pe pret). Raportul vechi (`s10_pret_rederivat.md`,
sectiunea 4) semnalase deja asta ca "neverificat, in afara bugetului" — de data asta o marchez
explicit ca **decizie deschisa**: daca reemiterea din #13 se aplica si documentelor provenite din
aviz, aceeasi discutie (a/b/c/d) trebuie purtata si pentru acea ramura, cu propriul cost (sursa
de comparat e alt document, nu `CTR_ARTICOLE`). Nu am extins cercetarea de date pe aceasta ramura
(in afara cererii explicite a sarcinii, care a cerut "cazul confirmat", adica cel de contract).
**Concluzie sectiune**: criteriul "reemitere identica" e garantat azi doar pe ramurile
`ELSE`/restaurant (fara contract, fara comanda, fara aviz). Pe comenzi, o divergenta produce
eroare vizibila (rau, dar nu tacit). Pe contract **si** pe aviz, o divergenta produce o suma
gresita fara nicio semnalare — acestea sunt cele doua ramuri care au nevoie de o decizie explicita
de tipul (a)-(d) inainte ca #13 etapa II sa poata pretinde ca satisface criteriul S12.
---
## 7. Ce ramane de decis de Marius
1. **(b) blocare stricta vs. (c) avertizare+confirmare** — (c) satisface literal criteriul "decis
explicit, nu accidental" cerut de plan si nu intrerupe fluxul quasi-niciodata (doar cand chiar
exista divergenta); (b) e mai sigur (niciodata nu se schimba nimic fara interventie manuala pe
contract) dar mai frustrant daca divergentele sunt frecvente in productie — lucru nemasurabil
azi (sectiunea 2). **Recomand (c)** ca implicit, cu mesajul aratand explicit delta de
pret/TVA/valuta (sectiunea 3) — nu doar pretul.
2. **Garda trebuie sa acopere pret + TVA + valuta impreuna**, nu doar pretul — confirmat la
sectiunea 3 ca vin din acelasi `SELECT`. O implementare care verifica doar pretul ar lasa
trecerea tacuta a unei schimbari de TVA sau valuta.
3. **Varianta (d) (ocolire) — nu o recomand**, din cauza pierderii permanente a legaturii
`VANZARI_DETALII.ID_CTR`. Daca Marius considera totusi ca merita costul (de ex. daca
trasabilitatea contract-linie pe documentele reemise nu conteaza in practica), e o decizie de
produs explicita, nu tehnica — semnalez aici, nu decid.
4. **Ramura de aviz (`ntip=4`) are acelasi tip de risc silentios** (sectiunea 6) — de decis daca
intra in perimetrul #13 etapa II acum sau se trateaza separat, cu propriul studiu de fezabilitate
pentru garda (sursa de comparat difera fata de contract).
5. **Frecventa reala in productie a divergentei `CTR_ARTICOLE.PRET_UNITAR`** ramane nemasurabila
din baza de dezvoltare (27 randuri total) — daca decizia (b) vs (c) depinde de cat de des s-ar
declansa garda, ar trebui repetata interogarea din sectiunea 2 pe o baza cu volum de productie
inainte de implementare.
---
## STARE / CE RAMANE
**Livrabil complet** — toate cele 6 puncte cerute in brief sunt acoperite (sectiunile 1-6), plus
recomandare argumentata (sectiunea 4, cu tabel comparativ) si lista de decizii deschise
(sectiunea 7).
Nimic ramas in lucru; nicio verificare intrerupta. Singurele limitari explicite:
- esantionul de date (27 randuri `CTR_ARTICOLE`, baza de dezvoltare) nu se extrapoleaza la
productie (sectiunea 2);
- ramura de aviz (`ntip=4`) e semnalata ca avand acelasi tip de risc, dar nu a primit acelasi nivel
de cercetare (nu era in perimetrul cerut) — vezi sectiunea 6, ultimul paragraf inainte de
concluzie.

View File

@@ -1,342 +0,0 @@
# S10 — Pretul re-derivat in `adauga_articol_factura`, ramura cu ramura
## Nota pe sursa folosita
`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` **nu exista** (nici in `docs\`, nici urme in
istoricul git — `docs\` e integral netracked). Fisierul cu **acelasi nume** exista insa la
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si a fost folosit
ca sursa. Continutul `adauga_articol_factura` coincide structural cu ce descrie planul (aceeasi
ordine de ramuri, aceleasi coduri de eroare `FACT-012/013/018`, acelasi `DECODE(A.PRET_UNITAR, 0,
V_PRET_TEMP, A.PRET_UNITAR)`), dar liniile sunt deplasate cu **+17** fata de citatele din plan
(corp `4989-5284` aici vs. `4972-5268` in plan; ramura de contract `5146-5185` aici vs. `:5129-5168`
in plan) — probabil diferente de comentarii de antet intre cele doua exporturi. Toate citatele de mai
jos sunt pe fisierul din `SCRIPTURI_CLAR`, verificat direct.
## Verdict (raspuns la intrebarea 4 — reemiterea)
**Da, exista o ramura care suprascrie tacut pretul la orice apel, inclusiv la o eventuala
reemitere: liniile provenite dintr-un contract cu `OPT_FACTURARE = 3` (sau `NULL`, care se
implicit-eaza tot la 3).** Procedura nu are nicio notiune de "reemitere" — la fiecare apel cu acelasi
`V_ID_CTR`, daca articolul are in `CTR_ARTICOLE.PRET_UNITAR` o valoare diferita de 0, acea valoare
**inlocuieste** pretul trimis de VFP (`V_PRET_TEMP`), necontidionat de faptul ca linia a fost sau nu
atinsa de utilizator pe ecran. Riscul se materializeaza doar daca pretul din contract s-a schimbat
intre emiterea initiala si reemitere — daca n-a scazut, rezultatul e identic si nimeni nu observa
diferenta pana cand se schimba. Pe toate celelalte ramuri (implicita, restaurant), pretul trimis de
VFP e respectat ca atare. **Nu exista niciun parametru sau flag care sa opreasca re-derivarea** —
raspunsul la intrebarea 5 e "nu exista".
## 1. Semnatura completa (corp, nu spec)
`PACK_FACTURARE:4989-5015` (spec identica la `:537-563`, verificat doar ca prezenta, nu citat):
```
PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER,
V_ID_ARTICOL IN NUMBER,
V_SERIE IN VARCHAR2,
V_EXPLICATIE IN VARCHAR2,
V_ID_POL IN NUMBER,
V_ID_GESTIUNE IN NUMBER,
V_PRET_ACHIZITIE_TEMP IN NUMBER,
V_PRETD IN NUMBER,
V_ID_VALUTAD IN NUMBER,
V_PRET_TEMP IN NUMBER,
V_ID_VALUTA_TEMP IN NUMBER,
V_PRETURI_CU_TVA_TEMP IN NUMBER,
V_IN_STOC_TEMP IN NUMBER,
V_CANTITATE IN NUMBER,
V_DISCOUNT_UNITAR IN NUMBER,
V_CONT IN VARCHAR2,
V_CURS IN NUMBER,
V_MULTIPLICATOR IN NUMBER,
V_ID_JTVA_COLOANA IN NUMBER,
V_ID_PART_REZ IN NUMBER,
V_ID_LUCRARE_REZ IN NUMBER,
V_PRETV_ORIG IN NUMBER,
V_ID_VANZARE_SET IN NUMBER,
V_ID_CTR IN NUMBER,
V_ID_UTIL IN NUMBER,
V_TAXCODE IN NUMBER DEFAULT NULL,
V_LOT IN VARCHAR2 DEFAULT NULL) IS
```
Toti parametrii sunt **IN**, niciunul OUT/IN OUT — nu exista un canal de intoarcere a pretului
recalculat catre VFP la momentul apelului (linia se citeste ulterior, la re-interogarea grilei).
**`V_OPT_FACTURARE` nu e parametru.** E o variabila locala (`:5029`), calculata **in interiorul
procedurii** din `CONTRACTE.OPT_FACTURARE`, folosind parametrul `V_ID_CTR` primit de la VFP
(`:5039-5050`):
```sql
IF pack_facturare.ntip IN (2, 6, 26, 52) THEN
BEGIN
SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;
EXCEPTION
WHEN NO_DATA_FOUND THEN
V_OPT_FACTURARE := 4;
END;
END IF;
```
VFP nu trimite si nu poate influenta `V_OPT_FACTURARE` altfel decat prin `V_ID_CTR` (`poArt.id_ctr`,
trimis `NULL` cand linia nu vine de pe contract). Pentru orice `pack_facturare.ntip` in afara lui
`(2, 6, 26, 52)`, `V_OPT_FACTURARE` **ramane neinitializat** (blocul IF nu ruleaza deloc) — echivalent
cu "nu conteaza", ramura de contract nu se poate nimeri.
## 2. Ce inseamna `V_OPT_FACTURARE` si cum se coreleaza cu VFP
Din `PACK_FACTURARE` (alte proceduri, aceeasi coloana `CONTRACTE.OPT_FACTURARE`) si din
`COMUN\clase\ofacturare.vc2` / `COMUN\programe\oproceduri_facturare.prg` (proprietatea locala
`poArt.opt_facturare`, populata la citirea contractului, in oglinda cu coloana Oracle):
| Valoare | Tip facturare | Dovada |
|---|---|---|
| `0` (sau lipsa contract) | Articol normal, nelegat de contract | `ofacturare.vc2:13055` `If poArticol.opt_facturare = 0` |
| `1`, `2` | **Rata** (scadentar contract, `CTR_SCADENTAR`) — linia nu trece prin `adauga_articol_factura`, ci prin `adauga_rata_factura` (`ofacturare.vc2:14041-14050`) | `ofacturare.vc2:14041` `If Inlist(poArt.opt_facturare,1,2)`; `PACK_FACTURARE:2898,2915` (`CTR_SCADENTAR`, doar pt. `OPT_FACTURARE IN (1,2)`) |
| `3` | **Articol de pe contract**, pret din `CTR_ARTICOLE` | `PACK_FACTURARE:2699,2820` (`CTR_ARTICOLE` doar pt. `OPT_FACTURARE = 3`); `ofacturare.vc2:14750` (`poDate.tip = 2 And poArticol.opt_facturare = 3`) |
| `4` | Fallback intern cand contractul nu (mai) exista (`NO_DATA_FOUND`) | `PACK_FACTURARE:5047-5048` |
VFP nu trimite `V_OPT_FACTURARE` ca parametru al `adauga_articol_factura` — coreleaza doar indirect,
prin faptul ca proprietatea locala `poArt.opt_facturare` (citita din acelasi `CONTRACTE.OPT_FACTURARE`
cand grila s-a populat) decide **ce RPC se apeleaza** (`adauga_rata_factura` pentru 1/2,
`adauga_articol_factura` pentru orice altceva, inclusiv 3), nu ce ramura Oracle se executa in interior
— aceea o recalculeaza serverul singur, din `V_ID_CTR`.
## 3. Ramura cu ramura pe `V_OPT_FACTURARE` (si pe `pack_facturare.ntip`, care are prioritate)
`CASE` la `PACK_FACTURARE:5052-5220`. Ramurile 1-3 sunt selectate dupa `pack_facturare.ntip`, nu
dupa `V_OPT_FACTURARE` — ramura 4 (contract) se testeaza **doar daca niciuna din primele trei nu s-a
potrivit**.
| Selector | Tip facturare | Pretul | Sursa re-derivarii | Citat |
|---|---|---|---|---|
| `ntip IN (3,21,28,42,47)` | Facturare/aviz din **comenzi** | **Re-derivat, dar constrans sa se potriveasca cu ce a trimis VFP** — `SELECT A.PRET ... WHERE A.PRET = V_PRET_TEMP` | `COMENZI_ELEMENTE` + `CRM_POLITICI_PRETURI`/`PRET_ART`, filtrat pe `A.PRET = V_PRET_TEMP`; daca nu exista rand cu exact acel pret, `NO_DATA_FOUND` **nu e tratat** — procedura pica cu eroare Oracle netratata | `:5053-5078` |
| `ntip = 4` | Facturare din **avize** | **Re-derivat necondiționat, din documentul sursa** — `SELECT DISTINCT A.PRET ... INTO V_PRET` fara filtru pe pretul trimis | `VANZARI_DETALII` (linia din avizul sursa), filtrata pe articol/politica/gestiune/discount/cont, dar **nu** pe pret | `:5080-5103` |
| `ntip = 45` | Facturare **restaurant** | **Respectat** — `V_PRET := V_PRET_TEMP` setat **inainte** de SELECT (`:5109`); SELECT-ul recalculeaza doar `V_PROC_TVAV` si `V_PRET_ACHIZITIE` (cost, nu pret de vanzare) | n/a pentru pret; TVA din `JTVA_COLOANE`, cost din `CRM_POLITICI_PRET_ART.PRETFTVA` | `:5104-5145` |
| `V_OPT_FACTURARE = 3` (deci `ntip IN (2,6,26,52)` **si** contract cu `OPT_FACTURARE=3`/`NULL`) | Facturare/aviz **de pe contract** | **Re-derivat daca contractul are pret setat** — `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)`; daca `PRET_UNITAR = 0` sau nu exista rand `CTR_ARTICOLE`/politica potrivita (`NO_DATA_FOUND`), cade pe `V_PRET_TEMP` | `CTR_ARTICOLE.PRET_UNITAR` (prin `CRM_POLITICI_PRET_ART`, `ID_POL_ART`) | `:5146-5185` (branch), `:5181-5184` (fallback `V_PRET_TEMP` in exceptie) |
| `ELSE` (implicit — orice altceva, inclusiv linii normale fara contract) | Standard | **Respectat** — `V_PRET := V_PRET_TEMP` | n/a; TVA din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` trimis de VFP | `:5187-5203` |
**Observatie de prioritate:** daca o linie e simultan "din comanda" (`ntip IN (3,21,28,42,47)`) si
are si `V_ID_CTR` completat, ramura de comenzi castiga — `V_OPT_FACTURARE` nici nu se calculeaza
(blocul de la `:5039` ruleaza doar pentru `ntip IN (2,6,26,52)`). Ramurile nu se pot suprapune in
productie curenta, dar asta inseamna ca **orice extindere viitoare a lui `ntip`** (de ex. daca #13
introduce un tip nou de document care refoloseste `adauga_articol_factura` pentru reemitere) trebuie
verificata explicit fata de acest `CASE` — un `ntip` nou care nu intra in niciuna din listele
`(3,21,28,42,47)` / `4` / `45` cade automat fie pe ramura de contract (daca are `V_ID_CTR`), fie pe
`ELSE`.
## 4. Reemiterea — detaliu (vezi si verdictul de mai sus)
Procedura **nu primeste si nu poate primi** vreun semnal de tip "acesta e un apel de reemitere, nu
recalcula". Comportamentul e identic la emiterea initiala si la orice apel ulterior cu aceiasi
parametri. Riscul descris in plan (sectiunea H) se confirma integral pentru ramura de contract
(`V_OPT_FACTURARE = 3`): daca `CTR_ARTICOLE.PRET_UNITAR` s-a modificat intre emiterea initiala si
reemitere, factura reemisa va lua **pretul curent din contract**, nu pretul confirmat pe ecran de
utilizator inainte de reemitere — chiar daca utilizatorul n-a atins linia.
Pentru ramurile "avize" (`ntip = 4`) si "comenzi" (`ntip IN (3,21,28,42,47)`), acelasi risc structural
exista (pretul se re-citeste din documentul sursa la fiecare apel), dar **nu sunt cazul confirmat de
#13** — planul S10 spune explicit "se verifica pe factura din contract, care e cazul confirmat" — nu
s-a cerut si nu s-a facut inventar suplimentar pentru daca fluxul de reemitere din #13 (etapa II,
stergere+regenerare cu aceleasi linii) ar trece vreodata prin aceste doua ramuri. **De retinut daca
#13 extinde reemiterea si la facturi/avize provenite din comanda sau din alt aviz** — pe ambele,
re-derivarea e chiar mai stricta decat pe contract (ramura de comenzi cade cu eroare netratata daca
pretul nu se potriveste exact; ramura de avize suprascrie necondiționat).
## 5. Mecanism de "nu re-deriva"
**Nu exista.** Niciun parametru boolean, nicio valoare santinela, nicio ramura in `CASE`
(`:5052-5220`) care sa respecte pretul primit *pentru ca i s-a cerut explicit*. Ramurile "implicita"
si "restaurant" respecta pretul doar pentru ca structural nu au de unde re-deriva altceva (nu exista
document sursa de recitit), nu pentru ca ar exista o comutare intentionata. Orice solutie de tip
"pretul vine din formular, nu se re-deriva" (cum sugereaza planul, sectiunea H) **ar trebui
implementata in VFP inainte de apel** (de exemplu, nu retrimite `V_ID_CTR` la reemitere daca vrei sa
eviti ramura de contract) — nu exista un parametru in pachet pe care VFP l-ar putea seta ca sa
dezactiveze re-derivarea, iar pachetul **nu se modifica** (decizia 27-bis, respectata — aceasta e o
constatare, nu o propunere).
## 6. Discountul si TVA-ul
- **Discountul (`V_DISCOUNT_UNITAR`) nu e niciodata re-derivat.** Parametrul intra direct in
`INSERT INTO VANZARI_DETALII_TEMP (..., DISCOUNT_UNITAR, ...) VALUES (..., V_DISCOUNT_UNITAR, ...)`
(`:5236`, `:5266`) — nicio ramura din `CASE` il citeste sau il modifica. Tratament **diferit** de
pret: discountul e mereu respectat, indiferent de ramura.
- **TVA-ul (`V_PROC_TVAV`) e mereu recalculat server-side, pe toate ramurile — dar din surse
diferite, nu dintr-un parametru brut.** Nu exista parametru `V_PROC_TVAV_TEMP` in semnatura; VFP
trimite doar `V_ID_JTVA_COLOANA` (un identificator de coloana de cota, nu cota insasi). Pe ramura
implicita si pe cea de contract-fara-potrivire, cota se cauta in `JTVA_COLOANE` dupa acel ID
(`:5188-5198`, `:5169-5179`); pe ramurile comenzi/avize/contract-cu-potrivire/restaurant, cota vine
din documentul sursa sau din politica de pret (`:5058`, `:5083`, `:5150`, `:5114`). Deci TVA-ul are
un tratament **mai strict** decat pretul: niciodata "pass-through" direct, intotdeauna o cautare,
doar sursa cautarii difera pe ramura.
## 7. Cursul valutar si pretul in valuta
- **Cursul (`V_CURS`) e mereu respectat ca valoare, pe toate ramurile.** Singura procesare e
`DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste 0 cu 1, altfel scrie exact ce a
trimis VFP. Procedura **nu interogheaza niciodata tabela `CURS`** pentru un curs "de azi" — nu are
de unde sa recalculeze cursul chiar daca ar vrea.
- **Identitatea valutei (`V_ID_VALUTA`, variabila locala, nu parametrul `V_ID_VALUTAD`) urmeaza
acelasi tipar ca pretul, ramura cu ramura:** re-derivata din sursa pe comenzi (`C.ID_VALUTA`,
`:5059`) si avize (`A.ID_VALUTA`, `:5084`), re-derivata din contract pe ramura `V_OPT_FACTURARE=3`
(`B.ID_VALUTA`, `:5151`, cu fallback `V_ID_VALUTA_TEMP` in exceptie, `:5182`), respectata pe
implicita si restaurant (`V_ID_VALUTA := V_ID_VALUTA_TEMP`, `:5110`, `:5201`).
- **Consecinta pentru reemiterea unui document in valuta pe contract:** daca politica de pret a
contractului (`CRM_POLITICI_PRET_ART`, prin `CTR_ARTICOLE.ID_POL_ART`) a fost schimbata intre timp
la o alta valuta, reemiterea ar scrie articolul in **noua** valuta a politicii, dar cu **cursul**
trimis de VFP (posibil cursul valutei vechi, daca VFP nu a fost actualizat sa retrimita cursul
corect pentru noua valuta) — o sursa suplimentara de neconcordanta, nesemnalata explicit in plan.
Nu s-a gasit nicio validare in procedura care sa verifice ca `V_CURS` corespunde valutei
re-derivate `V_ID_VALUTA`.
## Completare: nota contabila a politicii
Sursa: aceeasi, `contabilizeaza_articol` la `PACK_FACTURARE:7173-7547` (in fisierul din
`SCRIPTURI_CLAR`; corpul relevant pentru articol simplu — nu compus — e la `:7391-7544`), plus
`scrie_nota` la `:12329-12561`.
### 1. Cum ajunge de la `id_pol` la nota contabila — ia toate randurile setului, fara distributie
`cursor_articol` (`:7218-7271`):
```sql
FROM CRM_POLITICI_PRET_ART A
LEFT JOIN CRM_POLITICI_PRETURI B ON A.ID_POL = B.ID_POL
LEFT JOIN CRM_NOTE_VANZARI C ON B.ID_NOTA = C.ID_NOTA
LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET
WHERE A.ID_POL = detalii_articol.id_pol AND A.ID_ARTICOL = detalii_articol.id_articol
```
E un `JOIN` simplu pe `ID_SET`, **fara `ROWNUM`, fara agregare, fara filtru suplimentar pe
`NOTE_CONTABILE`**. Daca setul are `N` randuri, cursorul intoarce `N` randuri pentru aceeasi
combinatie `id_pol`/`id_articol`. Bucla care il consuma (`:7393-7542`,
`OPEN cursor_articol; FETCH ...; WHILE cursor_articol%FOUND LOOP ... FETCH ...; END LOOP;`) executa
**intregul corp — `scrie_nota`, `descarca_gestiune`, `scrie_discount` — o data pentru fiecare rand
din set**, cu **aceeasi cantitate si acelasi pret intreg de fiecare data**, nu impartite intre
randuri. Nu exista nicio coloana `ORDINE` in tot pachetul (cautare `ORDINE` in fisier: zero
rezultate) si nicio logica de distributie procentuala intre randurile unui set.
**Consecinta directa pentru reteta aprobata (J-quater):** daca politica tehnica pentru contul de
venit ar ajunge sa foloseasca o nota al carei `ID_SET` are mai mult de un rand in
`NOTE_CONTABILE` (cazul general — pana la 30 de randuri pe unele seturi din baza, per verificarea ta
pe date vii), `contabilizeaza_articol` ar scrie **de N ori** aceeasi suma in contabilitate si ar
apela `descarca_gestiune` de N ori pentru aceeasi linie de vanzare — dublare de venit si dublare de
descarcare de gestiune, nu doar zgomot. Pasul 2 al retetei ("cauta o politica a carei nota are deja
`SCC`-ul calculat") **trebuie sa garanteze un singur rand `NOTE_CONTABILE` per `ID_SET` ales**, nu
doar un rand cu `SCC`-ul potrivit — verificarea facuta de tine pe cele 7 note active (exact un rand
per set) e conditia care face reteta sigura azi, dar nu e impusa de cod, e o coincidenta a datelor
curente.
### 2. `NOTE_CONTABILE.PTVA` — nu e citit deloc de `contabilizeaza_articol`
Lista de coloane a `cursor_articol` (`:7230-7260`) selecteaza explicit
`ID_NOTA, PRETURI_CU_TVA, ID_VENCHELT, ID_SECTIE, ID_SET, EXPLICATIE, SCD, ASCD, SCC, ASCC, CU_TVA,
IN_VALUTA` din lantul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI ->
NOTE_CONTABILE` — **`D.PTVA` nu apare in acest `SELECT`**. Coloana exista pe tabel (confirmat de
datele tale vii — `21`, `5`, `0`), dar `contabilizeaza_articol` n-o citeste niciodata.
Cota de TVA folosita efectiv in scriere e alta — vine din apelul catre `scrie_nota` (`:7444-7467`),
unde parametrul `V_PTVA` primeste `detalii_articol.proc_tvav * 100 - 100` (`:7464`), adica **cota
calculata deja in `adauga_articol_factura`** (din `JTVA_COLOANE` sau din documentul sursa, vezi
sectiunea 3 de mai sus), nu din nota. Deci **raspunsul direct la intrebarea ta: daca articolul are
21% dar nota politicii are `PTVA=5`, nu se intampla nimic — `PTVA` de pe nota e complet ignorat,
cota folosita e cea a articolului/documentului, nu a notei.** Coloana `NOTE_CONTABILE.PTVA` e moarta
din perspectiva acestei proceduri (posibil folosita in alt modul, neverificat aici).
### 3. `CU_TVA` si `IN_VALUTA` de pe nota
Ambele se citesc din `NOTE_CONTABILE` (`crs_rand_articol.cu_tva`, `crs_rand_articol.in_valuta`,
`:7253`, `:7254-7260`) si se transmit mai departe la `scrie_nota` ca parametri (`V_CU_TVA`,
`V_IN_VALUTA`), unde controleaza mecanic, nu semantic tot ce e legat de pret:
- **`V_CU_TVA`** (`scrie_nota:12416-12422`): daca `= 0`, suma acumulata in `V_INCASAT_CALCUL` e
`V_SUMA_FARA_TVA`; daca `= 1`, e `V_SUMA_CU_TVA`. Separat, la `:12537-12558`, **doar cand
`V_CU_TVA = 1` se scrie si o linie separata de TVA** (`pack_facturare.scrie_tva(...)`, cont
`4427`/etc.). Deci `CU_TVA` decide daca acest rand din notă genereaza o inregistrare contabila
separata pentru TVA sau nu — nu suprascrie si nu recalculeaza cota, doar comuta daca linia de TVA
se scrie.
- **`V_IN_VALUTA`** (`scrie_nota:12391-12404`, `:12492-12507`): daca `= 1`, se calculeaza si
`V_SUMA_VAL`/`V_SUMA_FARA_TVA_VAL`/etc. (sumele in valuta straina) si randul `ACT_TEMP` scrie
`ID_VALUTA = V_ID_VALUTA` (valuta reala a articolului) si `CURS = V_CURS`; daca `= 0`, randul
scrie `ID_VALUTA = pack_facturare.nid_moneda_nationala` si `CURS = 0`, indiferent de valuta reala
a articolului.
**Raspuns la "ce se intampla daca `IN_VALUTA=1` pe nota dar documentul e in lei":** nu apare nicio
eroare si nicio validare incrucisata intre `IN_VALUTA` de pe nota si valuta reala a documentului —
procedura calculeaza pur si simplu `V_SUMA_VAL` folosind `V_ID_VALUTA` si `V_CURS` primite din
`detalii_articol` (care, pe un document in lei, sunt deja moneda nationala si curs implicit 1).
Rezultatul practic: `ACT_TEMP.SUMA_VAL` se populeaza redundant (cu aceeasi valoare ca `SUMA`,
scalata la curs 1) in loc sa ramana `0`, iar `ID_VALUTA` de pe randul contabil ramane oricum
moneda nationala (pentru ca asta e `detalii_articol.id_valuta` pe un document in lei) — o
inconsistenta cosmetica in `ACT_TEMP` (rand cu `SUMA_VAL` populat desi `CURS` real e 1), nu o
eroare de suma. Relevant pentru politicile `SERVICII VALUTA` / `COMISION INTERMEDIERE`
(`SCC=704`, candidate la pasul 2 al retetei): daca oricare document in lei ar ajunge sa foloseasca
una din ele, ar produce astfel de randuri cosmetic-inconsistente, fara sa strice suma facturata.
### 4. `ASCD`/`ASCC` si `ID_PARTD`/`ID_PARTC`
- **`ASCD`/`ASCC` nu sunt obligatorii — au fallback automat cand sunt nule**, exact pe ramura care
se aplica pe facturi (`:7409-7428`):
```
V_ASCD := NVL(crs_rand_articol.ascd,
PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD));
...
V_ASCC := NVL(crs_rand_articol.ascc,
PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCC));
```
Cand nota nu are analitic explicit (cazul majoritatii notelor reale, per verificarea ta), se
deriva unul din grupul de utilizatori al celui care factureaza, functie de contul `SCD`/`SCC`. Nu
s-a verificat in aceasta sesiune ce intoarce `GetAnaliticByGrupUtilizatori` cand nici grupul de
utilizatori nu are un analitic configurat (posibil `NULL` mai departe, netratat explicit aici).
- **`ID_PARTD`/`ID_PARTC` nu sunt citite de la nota deloc.** `cursor_articol` nu le selecteaza (nu
exista `D.ID_PARTD`/`D.ID_PARTC` nicaieri in fisier — verificat). Cele doua coloane de pe
`ACT_TEMP` se calculeaza in `scrie_nota`/`scrie_tva` direct din **codul contului** (`V_SCD`,
`V_SCC`) si din variabile de sesiune ale pachetului (`pack_facturare.nid_part`,
`nid_part_rez`, `nid_partc`), nu din nota: `ID_PARTD` e un `CASE` pe prefixul lui `V_SCD`
(`41%`/`46%`/`45%`/`357`) la `:12510-12515`; `ID_PARTC` e un `DECODE` pe `V_SCC`
(`419`/`4111`/`357`) la `:12523-12530`. Daca `NOTE_CONTABILE` are coloane `ID_PARTD`/`ID_PARTC`,
ele sunt irelevante pentru `contabilizeaza_articol` — partenerul contabil vine intotdeauna din
contextul documentului (`pack_facturare.nid_part`), nu din configurarea notei.
### 5. Politica fara `ID_NOTA` (`HOTEL TAXE`, `HOTEL CAZARE`)
**Nu exista un cod `FACT-0xx` dedicat pentru acest caz — spre deosebire de articolul care lipseste
din politica (`FACT-024`, `:7278-7302`), aici procedura nu detecteaza si nu semnaleaza explicit
situatia.** Lantul de `LEFT JOIN` din `cursor_articol` (`:7261-7271`) e construit sa supravietuiasca
oricarui inel lipsa: `A` (politica-articol, gasita — altfel s-ar fi luat deja `FACT-024` mai
devreme) se pastreaza chiar daca `B.ID_NOTA` e `NULL` — `C` si `D` devin pur si simplu toate
`NULL` pe acel rand, cursorul tot intoarce **un rand** (`cursor_articol%FOUND = TRUE`), nu zero.
In bucla (`:7396-7541`), asta inseamna:
- `crs_rand_articol.scd`, `.ascd`, `.scc`, `.ascc`, `.cu_tva`, `.in_valuta`, `.explicatie` — toate
`NULL`.
- Ramura de calcul `V_ASCD := NVL(NULL, GetAnaliticByGrupUtilizatori(nid_util, NULL))` — analitic
calculat pe un cont `NULL`, deci probabil tot `NULL` (netestat aici ce intoarce functia pe
argument `NULL`).
- `scrie_nota` e apelata cu `V_SCD = NULL`, `V_SCC = NULL` — insereaza in `ACT_TEMP` un rand cu
conturile de debit/credit **nule**.
Daca `ACT_TEMP.SCD`/`ACT_TEMP.SCC` au constrangere `NOT NULL` pe schema, `INSERT`-ul ar pica cu o
eroare Oracle generica (`ORA-01400`), nu cu un mesaj `FACT-0xx` prietenos ca la celelalte cazuri
tratate explicit — **neverificat in aceasta sesiune** (DDL-ul `ACT_TEMP` nu e in exportul
`PACK_FACTURARE`). In orice caz, **comportamentul e cel putin la fel de riscant ca lipsa unui
articol din politica**, dar fara plasa de siguranta explicita din cod — de tratat cu atentie daca
reteta aprobata ajunge sa produca vreodata o politica fara `id_nota` (nu pare sa fie cazul retetei
J-quater, care cere explicit gasirea unei note existente, dar `HOTEL TAXE`/`HOTEL CAZARE` arata ca
starea "politica fara nota" chiar exista azi in baza vie, pe alte fluxuri).
## Ce nu s-a putut stabili si de ce
- **Comportamentul exact al viitorului flux de reemitere din #13** (etapa II) fata de aceasta
procedura — planul S10 cere verificarea "pe factura din contract", nu inventarul complet pentru
comenzi/avize; nu s-a extins cercetarea acolo (in afara observatiei de la punctul 4, care ramane o
constatare structurala, nu un verdict testat pe fluxul de reemitere, care **inca nu exista in
cod** — decizia 30, nimic implementat).
- **Ce se intampla la eroarea netratata din ramura de comenzi** (`NO_DATA_FOUND` cand
`A.PRET = V_PRET_TEMP` nu gaseste rand, `:5057-5078`) — daca propaga ca eroare Oracle bruta pana la
utilizator sau daca `goExecutor` o intercepteaza generic; n-a fost verificat (nu era in perimetrul
celor 7 intrebari, dar e un risc adiacent gasit din citirea codului).
- **Cate contracte reale au azi `CTR_ARTICOLE.PRET_UNITAR` diferit de pretul ultimei facturi emise**
— verificare pe date vii, nefacuta (cercetare read-only, fara acces la interogari live in aceasta
sesiune).
- **Discrepanta de numerotare fata de `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`** (fisierul
cerut in prompt nu exista pe disc) — semnalata la inceputul raportului, nu blocheaza verdictul
pentru ca fisierul gasit in `SCRIPTURI_CLAR` are acelasi nume/data si continut structural identic.

View File

@@ -1,470 +0,0 @@
# S10 — Se re-deriva valorile liniei pe calea de REEMITERE?
Stare: **TERMINAT.**
Surse:
- `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii;
`versiune_db.txt` = `2026_08_09_02`) — prescurtat **`PF:<linie>`**;
- corpul viu din `all_source` (`MARIUSM_AUTO.PACK_FACTURARE`, `PACKAGE BODY`, **VALID**,
`last_ddl_time = 2026-08-09 20:03:50`) — prescurtat **`LIVE:<linie>`**;
- `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare_editare.prg`.
**Exportul = ce ruleaza.** Verificat pe cinci ancore; in zona relevanta offsetul e constant,
`LIVE + 1240 = PF`: `LIVE:3749`↔`PF:4989`, `LIVE:3840`↔`PF:5080`, `LIVE:3909`↔`PF:5149`,
`LIVE:3982`↔`PF:5222`, `LIVE:4044`↔`PF:5284`, `LIVE:12466`↔`PF:13706`, `LIVE:13735`↔`PF:14975`.
**Nu generalizez offsetul in afara zonei masurate** — il dau doar ca dovada de identitate.
---
## 1. Unde sta `SELECT`-ul de la `PACK_FACTURARE:5149-5166` si cine il cheama
**Numarul de linie e corect**, verificat direct pe fisier. `PF:5149-5166`:
```
5149 SELECT DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR),
5150 B.PROC_TVAV,
5151 B.ID_VALUTA,
5152 A.PRET_CU_TVA,
5153 C.IN_STOC
5154 INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
5159 FROM CTR_ARTICOLE A
5160 LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL_ART = B.ID_POL_ART
5162 LEFT JOIN NOM_ARTICOLE C ON B.ID_ARTICOL = C.ID_ARTICOL
5164 WHERE B.ID_ARTICOL = V_ID_ARTICOL AND A.ID_CTR = V_ID_CTR AND B.ID_POL = V_ID_POL;
```
**Procedura**: `adauga_articol_factura` — spec `PF:537`, corp **`PF:4989`**,
`END adauga_articol_factura;` **`PF:5284`**. **Deci DA, e `adauga_articol_factura`.**
**CORECTIE fata de raportul rundei 9**: procedura **nu scrie in `VANZARI_DETALII`**. Se termina cu un
singur `INSERT`, `PF:5222`, in **`VANZARI_DETALII_TEMP`**:
```
5222 INSERT INTO VANZARI_DETALII_TEMP (... PRET, PRET_CU_TVA, PROC_TVAV, ... ID_VALUTA, ... IN_STOC ...)
5252 VALUES (... V_PRET, V_PRETURI_CU_TVA, V_PROC_TVAV, ... V_ID_VALUTA, ... V_IN_STOC ...)
```
Re-derivarea se produce deci **pe drumul catre temp**, nu dupa el. Afirmatia „`scrie_in_vanzari`
copiaza `PRET` neschimbat din temp" e adevarata (vezi 3) si totusi **irelevanta ca protectie**:
valoarea din formular e inlocuita **inainte** sa intre in temp.
**Cine cheama procedura**: numai VFP-ul, din metoda de **salvare** a formularului:
- `COMUN\clase\ofacturare.vc2:14069` — `frm_facturare_articole.do_scrie_articole`
- `COMUN\clase\ofacturare.vc2:18104` — `frm_facturare_articole2.do_scrie_articole`
(`:14054` si `:18089` sunt variante comentate, cu bind `?`, ale aceluiasi apel.)
In pachet **nu exista apelant intern**: `grep 'adauga_articol_factura\b'` da doar `PF:537` (spec),
`PF:4989` (corp), `PF:5284` (`END`) si doua comentarii de antet (`PF:22`, `PF:1497`).
**Traseul complet pe calea de scriere** (`frm_facturare_articole`):
1. `ofacturare.vc2:13981` — `pack_facturare.initializeaza_date_factura(...)`, care primeste
`Alltrim(Str(poDate.Tip))` (`ofacturare.vc2:13994`) si il pune in variabila de pachet:
`PF:1882 pack_facturare.ntip := V_TIP;` (corp `PF:1808`, `END` la `PF:1917`).
**`ntip` = tipul documentului din formular** — acelasi care ajunge in `VANZARI.TIP` (`PF:13670`).
Tot aici, `PF:1835 DELETE FROM VANZARI_DETALII_TEMP;` si `PF:1836 nid_act := 0;`.
2. `ofacturare.vc2:14036-14115` — `SCAN` pe cursorul de grid `crsfactura`, cate un
`pack_facturare.adauga_articol_factura(...)` per linie (`:14069`), executat prin
`goExecutor.oExecute` (`:14105`).
3. `frm_facturare_articole.do_scrie_factura` — `scrie_factura2` (`ofacturare.vc2:14345`, `:14373`),
`scrie_factura_avize` (`:14318`) sau `scrie_factura_avize_retur` (`:14434`, `:14497`), care ajung
la `pack_facturare.scrie_in_vanzari` (`PF:13488`).
**Raspuns Q1**: e `adauga_articol_factura`, si e pe calea de **scriere** a documentului (nu pe cea de
creare/incarcare din sursa) — dar tinta insertului e `VANZARI_DETALII_TEMP`, iar re-derivarea se
aplica **inainte** de insert, deci nimic de dupa temp nu o mai poate repara.
### Poarta care decide re-derivarea
`adauga_articol_factura` are un singur `CASE` (`PF:5052-5220`), pe **`pack_facturare.ntip`**:
| `WHEN` | linii | conditie |
|---|---|---|
| comenzi | `PF:5053-5078` | `ntip IN (3, 21, 28, 42, 47)` |
| avize | `PF:5080-5103` | `ntip = 4` |
| restaurant | `PF:5104-5145` | `ntip = 45` |
| **contract** | `PF:5146-5185` | `V_OPT_FACTURARE = 3` |
| `ELSE` | `PF:5187-5218` | restul |
`V_OPT_FACTURARE` se seteaza **numai** daca `ntip IN (2, 6, 26, 52)` (`PF:5039-5050`):
`SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;`, cu
`NO_DATA_FOUND -> V_OPT_FACTURARE := 4`. Pentru orice alt `ntip` ramane **NULL**, iar `WHEN NULL = 3`
e NULL -> fals -> se merge pe `ELSE`. **Implicitul e ramura care re-deriva**: `NVL(OPT_FACTURARE, 3)`.
Tipurile (`COMUN\docs\tipuri_documente_facturare.md`): 2 = factura pe contract, 6 = contract in
valuta, 26 = aviz pe contract, 52 = contract factura valuta; 4 = factura din avize;
3/21/28/42/47 = din comenzi; 45 = restaurant.
## 2. Cele cinci valori, una cate una
Intai maparea parametrilor — ce **trimite** formularul (apel `ofacturare.vc2:14069-14091` confruntat
cu semnatura `PF:4989-5015`; 27 de parametri, pozitional, potrivire completa):
| formular (`crsfactura` -> `poArt`) | parametru |
|---|---|
| `poArt.pretftva`/`pretctva`/`vpretftva`/`vpretctva` | `V_PRET_TEMP` |
| `poArt.id_valuta` | `V_ID_VALUTA_TEMP` |
| `poArt.cu_tva` | `V_PRETURI_CU_TVA_TEMP` |
| `poArt.gestionabil` | `V_IN_STOC_TEMP` |
| `poArt.id_jtva_coloana` | `V_ID_JTVA_COLOANA` |
| `poArt.id_pol` | `V_ID_POL` |
| `poArt.id_ctr` | `V_ID_CTR` |
**`PROC_TVAV` nu e parametru.** Nu exista `V_PROC_TVAV_TEMP` in semnatura — cota de TVA a liniei se
calculeaza **intotdeauna** pe server, in **toate** ramurile. Formularul trimite `ID_JTVA_COLOANA`, din
care ramura `ELSE` deriva `PROC_TVAV`. Pentru `PROC_TVAV` intrebarea „vine din temp?" **nu are raspuns
„da" pe nicio ramura**; intrebarea reala e *din ce* se deriva.
### Ramura contract (`V_OPT_FACTURARE = 3`, `PF:5146-5185`)
- **`PRET`** — `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)` (`PF:5149`).
**Se re-deriva** din `CTR_ARTICOLE.PRET_UNITAR` ori de cate ori aceasta e `<> 0`, indiferent daca
linia a fost atinsa pe ecran. Numai cand contractul are `PRET_UNITAR = 0` trece valoarea din
formular. **Capcana NULL**: `CTR_ARTICOLE.PRET_UNITAR` e `nullable=Y` (verificat pe DB); un NULL
**nu** potriveste literalul `0` in `DECODE`, deci rezultatul e **NULL**, nu `V_PRET_TEMP` — pretul
din formular se pierde si linia intra in temp cu `PRET` NULL. *(dedus din semantica `DECODE`, nu
rulat.)*
- **`PROC_TVAV`** — `B.PROC_TVAV` din `CRM_POLITICI_PRET_ART` (`PF:5150`). **Se re-deriva** din
politica de preturi, **nu** din `ID_JTVA_COLOANA` trimis de formular. Diferit de `ELSE`.
- **`ID_VALUTA`** — `B.ID_VALUTA` din `CRM_POLITICI_PRET_ART` (`PF:5151`). **Se re-deriva**;
`V_ID_VALUTA_TEMP` e ignorat.
- **`PRET_CU_TVA`** — `A.PRET_CU_TVA` din `CTR_ARTICOLE` (`PF:5152`). **Se re-deriva**;
`V_PRETURI_CU_TVA_TEMP` (`poArt.cu_tva`) e ignorat.
- **`IN_STOC`** — `C.IN_STOC` din `NOM_ARTICOLE` (`PF:5153`). **Se re-deriva din nomenclatorul
curent**; `V_IN_STOC_TEMP` (`poArt.gestionabil`) e ignorat.
Cele cinci **nu merg impreuna** nici macar aici: `PRET` are un `DECODE` care uneori lasa valoarea din
formular sa treaca, celelalte patru sunt inlocuite **neconditionat**, din **trei tabele diferite**
(`CTR_ARTICOLE`, `CRM_POLITICI_PRET_ART`, `NOM_ARTICOLE`).
**Iesirea de siguranta**: `EXCEPTION WHEN NO_DATA_FOUND` (`PF:5167-5185`) deriva `PROC_TVAV` din
`JTVA_COLOANE` si pune **toate** celelalte patru pe valorile din formular (`PF:5181-5184`):
`V_PRET := V_PRET_TEMP; V_ID_VALUTA := V_ID_VALUTA_TEMP; V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP;
V_IN_STOC := V_IN_STOC_TEMP;`. Adica **daca linia nu mai are corespondent in contract, formularul
castiga** — exact comportamentul cerut de decizia 54, existent deja in cod, dar numai pe calea de
exceptie.
### Ramura `ELSE` (`PF:5187-5218`) — cazul „bun"
`PROC_TVAV` din `JTVA_COLOANE` pe `ID_JTVA_COLOANA` trimis de formular (`PF:5189-5192`), iar
`PF:5200-5203` pune explicit `V_PRET := V_PRET_TEMP`, `V_ID_VALUTA := V_ID_VALUTA_TEMP`,
`V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP`, `V_IN_STOC := V_IN_STOC_TEMP`.
**Patru din cinci vin din formular; `PROC_TVAV` se deriva, dar din date trimise de formular.**
### Ramura comenzi (`ntip IN (3,21,28,42,47)`, `PF:5053-5078`)
```
5057 SELECT A.PRET,
5058 NVL2(A.PTVA, ROUND((A.PTVA + 100) / 100, 2), C.PROC_TVAV),
5059 C.ID_VALUTA, B.PRETURI_CU_TVA, D.IN_STOC
5075 WHERE A.ID_COMANDA = V_ID_COMANDA AND A.ID_ARTICOL = V_ID_ARTICOL
5077 AND A.PRET = V_PRET_TEMP AND A.STERS = 0;
```
- **`PRET`** — formal `A.PRET` din `COMENZI_ELEMENTE`, dar `WHERE ... A.PRET = V_PRET_TEMP`
(`PF:5077`) **fixeaza** rezultatul pe pretul din formular. Practic **nu se schimba**.
- **`PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`, `IN_STOC`** — **se re-deriva** din
`COMENZI_ELEMENTE.PTVA` / `CRM_POLITICI_PRET_ART` / `CRM_POLITICI_PRETURI` / `NOM_ARTICOLE`.
- **Ramura nu are bloc `EXCEPTION`.** Un `SELECT INTO` gol da `NO_DATA_FOUND` (ORA-01403)
**netratat**, care urca prin `goExecutor` in VFP. Deci daca la reemitere pretul liniei a fost
modificat pe ecran (sau linia nu mai e in comanda), scrierea **cade cu eroare** — nu se re-deriva
tacit. Este o **a doua ramura de re-derivare**, pe care raportul rundei 9 nu o numara.
### Ramura restaurant (`ntip = 45`, `PF:5104-5145`)
`PF:5109-5112` pune explicit `V_PRET := V_PRET_TEMP`, `V_ID_VALUTA := V_ID_VALUTA_TEMP`,
`V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP`, `V_IN_STOC := V_IN_STOC_TEMP`; `SELECT`-ul de la
`PF:5114-5139` deriva **doar** `V_PROC_TVAV` (din `JTVA_COLOANE`) si `V_PRET_ACHIZITIE`.
Patru din cinci vin din formular.
### Ramura avize (`ntip = 4`) — la punctul 5
## 3. `UPDATE` / recalcul care suprascrie valorile DUPA insertul din temp
**Nu exista, pentru cele cinci valori.**
Scrierea temp -> definitiv, in `scrie_in_vanzari` (`PF:13488-13953`), e o copiere 1:1:
```
13705 INSERT /*+ APPEND */
13706 INTO VANZARI_DETALII (... PRET, PROC_TVAV, ... ID_VALUTA, ... PRET_CU_TVA, DIFERENTA, ...)
13732 SELECT V_ID_VANZARE as ID_VANZARE, ... PRET, PROC_TVAV, ... ID_VALUTA, ... PRET_CU_TVA, DIFERENTA, ...
13757 FROM VANZARI_DETALII_TEMP;
```
> Capcana de cautare platita aici: `grep 'INSERT INTO VANZARI_DETALII'` da **zero** potriviri —
> hint-ul `/*+ APPEND */` rupe cuvintele pe doua randuri. Cautarea corecta e `INTO VANZARI_DETALII`.
`IN_STOC` **nu apare** in lista de coloane (`PF:13707-13731`), si `VANZARI_DETALII` **nu are coloana
`IN_STOC`** (verificat pe DB: `all_tab_columns` -> 0 randuri). Traieste doar in
`VANZARI_DETALII_TEMP`, unde decide descarcarea de gestiune. Re-derivarea lui nu murdareste
documentul salvat, dar **schimba comportamentul de stoc** al reemiterii.
Toate scrierile pe `VANZARI_DETALII` din pachet:
| linie | procedura | ce face |
|---|---|---|
| `PF:13705` | `scrie_in_vanzari` (`PF:13488`) | `INSERT` 1:1 din temp |
| `PF:14974` | `finalizeaza_avize_lucrare` (`PF:14854`) | `INSERT` 1:1 din temp (cale avize de lucrare) |
| `PF:5560` | `sterge_factura` (`PF:5432`) | `SET STERS, ID_UTILS, DATAORAS` |
| `PF:5630` | `sterge_proforma` (`PF:5610`) | `SET STERS, ID_UTILS, DATAORAS` |
| `PF:14516` | `modifica_explicatie_articol` (`PF:14511`) | `SET EXPLICATIE, TAXCODE` |
| `PF:16017` | `actualizeaza_vanzari` (`PF:16012`) | `SET STERS = 0` |
**Niciun `UPDATE` nu atinge `PRET`, `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`.**
Confirmat si pe DB: `all_source` are exact doua `INTO VANZARI_DETALII` (`LIVE:12466`, `LIVE:13735`).
Mutatiile pe `VANZARI_DETALII_TEMP` **intre** `adauga_articol_factura` si `scrie_in_vanzari`:
| linie | procedura | ce schimba |
|---|---|---|
| `PF:5297` | `adauga_diferente_pret` | numai `DIFERENTA` (`PF:5344`: `UPDATE SET DIFERENTA = B.DIFERENTA`) |
| `PF:6860`, `PF:6864` | `scrie_factura_avize` | numai `CANTITATE` (split pe custodie); in `MERGE`, `PRET`, `PRET_CU_TVA`, `PROC_TVAV`, `ID_VALUTA`, `IN_STOC` sunt in clauza `ON`, nu in `UPDATE SET` |
| `PF:14020` | `scrie_seturi` | numai `ID_VANZARE_SET` |
| `PF:14057` | `scrie_seturi_proforma` | numai `ID_VANZARE_SET` (cale proforma) |
| `PF:12266` | `transfera_articol` | cale de transfer, in afara scrierii de factura |
**`pack_auto.actualizeaza_deviz`** (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`)
atinge **`DEV_ORDL.PROC_TVAV`, `RUL.ID_FACT`, `NOM_LUCRARI.ID_FACT`** — **nu atinge `VANZARI_DETALII`**.
Recalculele de totaluri (`PF:13760+`, `recalculeaza_totaluri_vanzari` `PF:16021`) si rotunjirile
lucreaza pe **`VANZARI`**, agregat — nu rescriu linia.
**Raspuns Q3**: nu. Dupa insertul in temp, cele cinci valori nu mai sunt suprascrise nicaieri.
`DIFERENTA` si `CANTITATE` da, restul nu.
## 4. Verdict: ajung valorile din formular neatinse in `VANZARI_DETALII`?
**NU, pentru trei familii de tipuri. DA, pentru restul.**
Se pierd **intr-un singur loc**: `PACK_FACTURARE.adauga_articol_factura`, in `CASE`-ul de la
**`PF:5052-5220`**, adica **inainte** de `INSERT INTO VANZARI_DETALII_TEMP` (`PF:5222`). Precis:
- **contract** (`ntip IN (2,6,26,52)` cu `CONTRACTE.OPT_FACTURARE` NULL sau 3) — `PF:5149-5153`:
se pierd `PRET` (cand `CTR_ARTICOLE.PRET_UNITAR <> 0`), `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`,
`IN_STOC`;
- **aviz** (`ntip = 4`) — `PF:5082-5086`: se pierd toate cinci, **inclusiv `PRET`, neconditionat**;
- **comenzi** (`ntip IN (3,21,28,42,47)`) — `PF:5057-5061`: se pierd patru; `PRET` e fixat de
`WHERE`, dar la nepotrivire scrierea **cade cu ORA-01403** in loc sa treaca.
Pentru toate celelalte tipuri (1, 5, 10, 22, 45, ...) valorile din formular **ajung neatinse**:
ramurile `ELSE` (`PF:5200-5203`) si restaurant (`PF:5109-5112`) le copiaza explicit, iar traseul de
dupa (`PF:13705-13757`) e copiere 1:1.
**Exceptie transversala, valabila pe TOATE tipurile**: `PROC_TVAV` **nu vine niciodata din formular** —
nu e parametru al procedurii. Pe calea „buna" se deriva din `JTVA_COLOANE` pe `ID_JTVA_COLOANA`-ul
trimis de formular, deci reproduce documentul **atat timp cat cota din `JTVA_COLOANE` nu s-a
schimbat**. La o modificare legala de cota, reemiterea schimba TVA-ul chiar si pe ramura `ELSE`.
## 5. Sursa AVIZ (`ntip = 4`) — calea difera, si e mai grava
Verbatim, `PF:5080-5103` (confirmat identic pe DB, `LIVE:3840-3863`):
```
5080 WHEN pack_facturare.ntip = 4 THEN
5081 -- facturare din avize
5082 SELECT DISTINCT A.PRET,
5083 A.PROC_TVAV,
5084 A.ID_VALUTA,
5085 A.PRET_CU_TVA,
5086 B.IN_STOC
5087 INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
5092 FROM VANZARI_DETALII A
5093 LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL
5095 WHERE A.ID_ARTICOL = V_ID_ARTICOL
5096 AND A.ID_POL = V_ID_POL
5097 AND NVL(A.ID_GESTIUNE, -1000) = V_ID_GESTIUNE
5098 AND A.DISCOUNT_UNITAR = V_DISCOUNT_UNITAR
5099 AND NVL(A.CONT, 'XXXX') = V_CONT
5100 AND A.ID_VANZARE IN
5101 (SELECT X AS ID_VANZARE
5102 FROM table(cast(CHARN2COLLECTION(pack_facturare.clistaid, ',') AS num_tab)));
```
Diferentele fata de contract, toate in defavoarea deciziei 54:
1. **`PRET` se re-deriva neconditionat**, direct din `VANZARI_DETALII` al **avizului sursa**. Nu exista
`DECODE(..., 0, V_PRET_TEMP, ...)` si **nu exista `AND A.PRET = V_PRET_TEMP`** in `WHERE` (spre
deosebire de ramura comenzi, `PF:5077`). Un pret modificat pe ecran la reemitere e **inlocuit
tacit** cu pretul din aviz. **Aceasta e ramura cea mai agresiva din tot `CASE`-ul.**
2. **Nu are bloc `EXCEPTION`.** Ramura contract are `NO_DATA_FOUND` care cade inapoi pe formular
(`PF:5167-5185`); aici, orice modificare pe ecran a `DISCOUNT_UNITAR`, `CONT`, `ID_GESTIUNE` sau
`ID_POL` scoate randul din `WHERE` -> **ORA-01403** urcata in VFP. `SELECT DISTINCT` peste mai
multe avize cu preturi diferite pe acelasi articol poate da si **ORA-01422** (`TOO_MANY_ROWS`).
3. **Nu filtreaza `A.STERS = 0`**, desi `VANZARI_DETALII` are coloana `STERS` (verificat pe DB) si
`sterge_factura` o foloseste (`PF:5560`). Linii sterse ale avizului sursa pot fi citite.
4. Depinde de `pack_facturare.clistaid` — lista de avize sursa, trimisa de VFP prin
`initializeaza_date_factura` (`poDate.listaid`, `ofacturare.vc2:13992`). La reemitere, `clistaid`
trebuie repopulata cu aceleasi avize, altfel `WHERE` nu potriveste nimic -> ORA-01403.
**Ce nu difera**: dupa `CASE` traseul e identic (`INSERT` in temp `PF:5222`, apoi copiere 1:1
`PF:13705`), iar `scrie_factura_avize` (`PF:6692`) modifica in temp **numai `CANTITATE`**
(`PF:6860`, `PF:6864`) — nu re-deriva nimic in plus.
**Raspuns Q5**: calea difera, si e **mai stricta**. Pe contract exista o portita (`PRET_UNITAR = 0`
si `NO_DATA_FOUND`) prin care valorile din formular trec; pe aviz **nu exista niciuna** — ori se
re-deriva tot, ori pica cu eroare.
## 6. Consecinta pentru decizia 54, la nivel de contract
### 6.1 Se poate curat VFP? **Nu.**
Confirm afirmatia rundei 9 — dar acum cu dovada, nu prin absenta:
- **Semnatura nu are flag.** `PF:4989-5015`: 27 de parametri, toti date de linie; ultimii doi
(`V_TAXCODE`, `V_LOT`) sunt `DEFAULT NULL`, niciunul nu e comutator.
- **Globalele pachetului nu au flag.** `PF:126-212` — stare de sesiune (`ntip`, `clistaid`,
`nid_part`, ...), niciun comutator de comportament pe re-derivare.
- **Poarta nu e influentabila legitim din VFP.** `ntip` ajunge in `VANZARI.TIP` (`PF:13670`) — a-l
falsifica inseamna a schimba tipul documentului. `CONTRACTE.OPT_FACTURARE` e date de contract, nu
parametru de apel.
- **Singurele parghii VFP care ar devia pe calea `NO_DATA_FOUND` sunt, ambele, de aceeasi natura ca
varianta (d) deja respinsa:**
- `V_ID_CTR = NULL` — respinsa; `PF:5280` duce `V_ID_CTR` in `temp.ID_CTR`, iar `PF:13755` in
`VANZARI_DETALII.ID_CTR`, deci legatura se pierde permanent;
- `V_ID_POL = NULL` — **acelasi defect, alta coloana**: `PF:5258` duce `V_ID_POL` in `temp.ID_POL`,
`PF:13737` in `VANZARI_DETALII.ID_POL`; in plus ar rupe ramura aviz, care potriveste pe
`A.ID_POL = V_ID_POL` (`PF:5096`). **Nu o propun** — o mentionez ca sa nu fie redescoperita ca
„solutie" intr-o runda urmatoare.
**Concluzie: decizia 54 cere obligatoriu modificare in `pack_facturare`.**
### 6.2 Ce forma trebuie sa aiba semnalul
Doua forme sunt inerte pentru apelantii de azi:
- **(i) variabila noua de pachet** („regenerare in curs"), implicit `0` = comportamentul actual, scrisa
de VFP **dupa** `initializeaza_date_factura` si inainte de bucla de `adauga_articol_factura`;
- **(ii) parametru `DEFAULT 0`** adaugat **dupa** `V_LOT` in semnatura — apelantii de azi trimit 27 de
argumente pozitional si raman valizi.
**Recomand (i)**, din trei motive concrete: se potriveste cu stilul existent al pachetului (care e deja
o punga de stare de sesiune); se scrie o data pe document, nu o data pe linie, in toate produsele; si
**punctul de resetare exista deja** — `initializeaza_date_factura` (`PF:1808-1917`) reinitializeaza
~40 de globale si face `DELETE FROM VANZARI_DETALII_TEMP` (`PF:1835`), deci flag-ul nu poate scapa in
documentul urmator din aceeasi sesiune. Atentie la ordine: VFP il seteaza **dupa** apelul de la
`ofacturare.vc2:13981`, nu inainte. Ambele variante modifica spec-ul, deci ambele forteaza
recompilarea dependentilor — nu e un criteriu de departajare.
### 6.3 Ce face semnalul, cand e pornit
**Nimic nou** — forteaza ramura care exista deja. Cea mai mica forma: un `WHEN` nou, **primul** in
`CASE`-ul de la `PF:5052`, cu **acelasi corp ca `ELSE`-ul de azi** (`PF:5189-5203`): `PROC_TVAV` din
`JTVA_COLOANE` pe `V_ID_JTVA_COLOANA`, si `V_PRET`/`V_ID_VALUTA`/`V_PRETURI_CU_TVA`/`V_IN_STOC` din
parametrii `*_TEMP`. `INSERT`-ul de la `PF:5222` ramane neatins. Acopera dintr-o data **toate trei**
ramurile problematice (contract, aviz, comenzi), pentru ca toate sunt in acelasi `CASE`.
### 6.4 Trei consecinte de acceptat explicit, nu ocolite
1. **`IN_STOC` ar veni din formular** (`poArt.gestionabil`) in loc de `NOM_ARTICOLE`. Nu e o problema
de fidelitate a documentului — `VANZARI_DETALII` **nu are coloana `IN_STOC`** (verificat pe DB) —
ci de **comportament de stoc** la reemitere. Ca reemiterea sa fie identica, incarcarea (S8) trebuie
sa dea valoarea cu care s-a scris documentul initial. **Azi nu o da**: loader-ul lui #6 o citeste
din nomenclatorul curent — `ofacturare_editare.prg:302-303`,
`left join nom_articole na on na.id_articol = v.id_articol`, cu comentariul care spune ca view-ul
n-o expune. **Flag-ul singur nu rezolva asta.**
2. **Se pierde o validare pe ramura comenzi.** `A.PRET = V_PRET_TEMP` (`PF:5077`) plus lipsa lui
`EXCEPTION` fac azi ca o linie care nu se mai potriveste cu comanda sa **opreasca** scrierea.
Cu flag-ul pornit, reemiterea unei „facturi din comanda" nu mai verifica linia fata de comanda.
Decizia 54 pare sa vrea exact asta, dar e o schimbare de comportament care trebuie spusa, nu
descoperita la S12.
3. **`PROC_TVAV` ramane derivat**, nu preluat. Reproducerea exacta a documentului cere ca S8 sa
pastreze `ID_JTVA_COLOANA` **si** ca `JTVA_COLOANE.COTA_TVA` sa nu se fi schimbat intre timp. Daca
se cere reproducere exacta si peste o modificare de cota, `PROC_TVAV` trebuie sa devina
**parametru** — schimbare mai mare decat flag-ul, de decis separat.
### 6.5 Un obstacol de incarcare, gasit in treacat (pentru S8, nu pentru S10)
View-ul pe care se sprijina incarcarea de azi, **`VVANZARI_ARTICOLE`**, expune (interogat pe DB):
`ID_VANZARE, ID_VANZARE_DET, ID_ARTICOL, CANTITATE, PRET, PRET_CU_TVA, PROC_TVAV, DISCOUNT_UNITAR,
ID_GESTIUNE, CONT, ID_VALUTA, ID_JTVA_COLOANA, SERIE, EXPLICATIE, TAXCODE, LOT, STERS,
ID_VANZARE_SET, PRET_ACHIZITIE, DENUMIRE, CODMAT, NUME_GESTIUNE, NUME_VAL`.
**Nu expune `ID_POL` si nu expune `ID_CTR`** — exact cei doi pe care `adauga_articol_factura` ii cere
(`V_ID_POL`, `V_ID_CTR`) si pe care `VANZARI_DETALII` ii pastreaza. Reemiterea S9 **nu poate folosi
acest view ca atare**; ori se completeaza view-ul, ori loader-ul citeste direct din
`VANZARI_DETALII`. Nu e in perimetrul S10, dar blocheaza S9 daca nu e prins din timp.
---
## Tabel sintetic — cele cinci valori pe ramura
„temp" = valoarea trimisa de formular; „re-derivat" = citita din alta sursa si suprascrisa.
| valoare | **contract** (2/6/26/52, `OPT_FACTURARE` NULL sau 3) | **aviz** (`ntip = 4`) | comenzi (3/21/28/42/47) | restaurant (45) | `ELSE` |
|---|---|---|---|---|---|
| `PRET_UNITAR` (`PRET`) | **re-derivat** din `CTR_ARTICOLE.PRET_UNITAR` daca `<> 0`; **temp** daca `= 0`; **NULL** daca e NULL (`PF:5149`) | **re-derivat** din `VANZARI_DETALII` al avizului, **neconditionat** (`PF:5082`) | **temp** de facto — `WHERE A.PRET = V_PRET_TEMP` (`PF:5077`); nepotrivire = ORA-01403 | **temp** (`PF:5109`) | **temp** (`PF:5200`) |
| `PROC_TVAV` | **re-derivat** din `CRM_POLITICI_PRET_ART` (`PF:5150`) | **re-derivat** din avizul sursa (`PF:5083`) | **re-derivat** din `COMENZI_ELEMENTE.PTVA` / politica (`PF:5058`) | **re-derivat** din `JTVA_COLOANE` (`PF:5114`) | **derivat** din `JTVA_COLOANE` pe `ID_JTVA_COLOANA` din formular (`PF:5189`) |
| `ID_VALUTA` | **re-derivat** din `CRM_POLITICI_PRET_ART` (`PF:5151`) | **re-derivat** din avizul sursa (`PF:5084`) | **re-derivat** din politica (`PF:5059`) | **temp** (`PF:5110`) | **temp** (`PF:5201`) |
| `PRET_CU_TVA` | **re-derivat** din `CTR_ARTICOLE` (`PF:5152`) | **re-derivat** din avizul sursa (`PF:5085`) | **re-derivat** din `CRM_POLITICI_PRETURI` (`PF:5060`) | **temp** (`PF:5111`) | **temp** (`PF:5202`) |
| `IN_STOC` | **re-derivat** din `NOM_ARTICOLE` (`PF:5153`) | **re-derivat** din `NOM_ARTICOLE` (`PF:5086`) | **re-derivat** din `NOM_ARTICOLE` (`PF:5061`) | **temp** (`PF:5112`) | **temp** (`PF:5203`) |
`PROC_TVAV` **nu vine din temp pe nicio ramura** — nu e parametru al procedurii.
`IN_STOC` **nu ajunge in `VANZARI_DETALII`** (coloana nu exista) — traieste doar in temp, unde decide
descarcarea de gestiune.
Pe ramura contract, `EXCEPTION WHEN NO_DATA_FOUND` (`PF:5167-5185`) muta **toate cele cinci** pe
coloana „temp"; ramurile aviz si comenzi **nu au** un asemenea bloc.
## Verificat direct vs. dedus
**Verificat direct pe fisier SI confirmat pe DB** (`all_source`, `MARIUSM_AUTO.PACK_FACTURARE`,
`PACKAGE BODY`, VALID, `last_ddl_time = 2026-08-09 20:03:50`):
granitele lui `adauga_articol_factura`; toate cele cinci ramuri ale `CASE`-ului, ramura cu ramura;
textul integral al ramurii aviz; faptul ca insertul procedurii merge in `VANZARI_DETALII_TEMP`; ca
exista exact doua `INTO VANZARI_DETALII` in tot pachetul.
**Verificat direct pe fisier** (nereconfirmat separat pe DB, dar in aceeasi zona cu offset masurat):
copierea 1:1 temp -> `VANZARI_DETALII` (`PF:13705-13757`); cele patru `UPDATE VANZARI_DETALII` si ce
coloane ating; mutatiile pe temp intre `adauga_articol_factura` si `scrie_in_vanzari`; corpul lui
`pack_auto.actualizeaza_deviz`.
**Verificat pe metadatele DB**: `VANZARI_DETALII` nu are `IN_STOC`, are `STERS`;
`CTR_ARTICOLE.PRET_UNITAR` e `nullable=Y`; lista de coloane a lui `VVANZARI_ARTICOLE`.
**Verificat direct in VFP**: apelantii lui `adauga_articol_factura`; maparea celor 27 de parametri;
`ntip := poDate.Tip`; loader-ul `IncarcaArticoleFactura` (`ofacturare_editare.prg:292-327`).
**Dedus, nerulat**: comportamentul `DECODE` cu `PRET_UNITAR` NULL (semantica Oracle, nu test);
ORA-01403 / ORA-01422 pe ramurile fara `EXCEPTION` (din absenta blocului, nu din reproducere).
**Din date de dev — NU e dovada, nu extrapolati**: `CTR_ARTICOLE` are **27** de randuri
(0 NULL, 10 cu `PRET_UNITAR = 0`, 17 cu `<> 0`) — deci **cazul NULL nu apare in dev**, ceea ce nu
spune nimic despre productie. `CONTRACTE.OPT_FACTURARE`: 176 NULL, 53 = 0, 9 = 1, 3 = 3, 1 = 4 —
adica pentru **176 + 3 din 242** de contracte `NVL(OPT_FACTURARE, 3)` da 3 si ramura re-deriva.
**Neacoperit**: n-am rulat niciun flux real (nici VFP, nici o reemitere efectiva) — totul e analiza
statica plus interogari `SELECT` pe metadate. N-am citit corpurile `scrie_factura2` si
`finalizeaza_factura` integral, doar punctele in care ating `VANZARI_DETALII` / temp.
## Corectii la rapoartele anterioare
### `docs\cercetare\s10_pret_rederivat.md` (runda 9)
1. **„exista exact o ramura care suprascrie pretul — contractul" — FALS.** Ramura **aviz**
(`ntip = 4`, `PF:5080-5103`) suprascrie `PRET` **neconditionat**, fara `DECODE` si fara filtru pe
pretul din formular — **mai agresiv decat contractul**. Ramura **comenzi** (`PF:5053-5078`) nu
suprascrie `PRET`, dar suprascrie celelalte patru si **arunca ORA-01403** la nepotrivire.
Ramurile care re-deriva sunt **trei**, nu una.
2. **„pe calea de scriere" — corect ca moment, gresit ca destinatie.** Se intampla la salvare, dar
insertul e in **`VANZARI_DETALII_TEMP`** (`PF:5222`), nu in `VANZARI_DETALII`. Diferenta conteaza:
explica de ce copierea „neschimbata" de mai tarziu nu protejeaza nimic.
3. **„nu exista niciun parametru sau flag care sa opreasca re-derivarea" — CONFIRMAT**, acum cu dovada
pozitiva (semnatura `PF:4989-5015`, globalele `PF:126-212`), nu prin absenta.
### `docs\cercetare\s10_pret_contract_reemitere.md` (runda 13)
1. **„acelasi `SELECT` alimenteaza cinci valori" — corect** (`PF:5149-5166`), dar de completat:
**nu merg impreuna** — `PRET` are `DECODE`, celelalte patru sunt neconditionate, si vin din trei
tabele diferite.
2. **„`IN_STOC` din nomenclatorul curent" — corect**, de completat cu faptul decisiv:
**`VANZARI_DETALII` nu are coloana `IN_STOC`** (verificat pe DB). Nu ajunge in document; efectul e
pe descarcarea de gestiune.
3. **„`scrie_in_vanzari` copiaza `PRET` neschimbat din temp" — corect** (`PF:13705-13757`), de marcat
insa ca **nu e o protectie**: dauna e amonte de temp.
4. Varianta **„avertizare + confirmare" ramane respinsa** (decizia 54) — nu am reargumentat-o.
### `docs\plan_13_unificare_formular_facturare.md`
- Trimiterea „`initializeaza_date_factura` (`PACK_FACTURARE:1808-1846`)" — corpul incepe la `PF:1808`
dar se termina la **`PF:1917`**. Citarile `:1835` / `:1836` din plan sunt corecte.

View File

@@ -1,379 +0,0 @@
# S11 — Legaturile care raman pe `ID_VANZARE` la editarea prin regenerare
Stare: **in lucru**. Vezi sectiunea STARE / CE RAMANE la final pentru progres curent.
## Context (dat, nu se rediscuta)
#13 etapa II: editarea unei facturi = regenerare. Documentul vechi e soft-sters (`STERS=1`) si
reemis in aceeasi tranzactie. `ID_FACT`, seria, numarul si data se pastreaza. **`ID_VANZARE` se
schimba** — documentul reemis primeste un `id_vanzare` nou din secventa.
Sarcina: inventarul complet al legaturilor pe `VANZARI.ID_VANZARE` care se rup la aceasta
schimbare, clasificate (A) trebuie remigrat / (B) trebuie sters-refacut / (C) nu conteaza.
## 0. Puncte de plecare (de verificat, nu de preluat pe incredere)
- `docs\plan_13_unificare_formular_facturare.md` — sectiunea `#### S11` (linia ~3024)
- `docs\cercetare\idfact_refolosire_si_documente.md`
- `docs\cercetare\rec_cale_vanzari_detalii.md`
- `COMUN\docs\cercetare\rec_consumatori_vanzari.md`
- `docs\cercetare\cont_venit_corespondente.md`
- `docs\cercetare\legatura_linie_retur.md`
- `docs\cercetare\rec_d42_efactura.md`
- `docs\cercetare\s5c_factura_din_proforma.md` (sectiunea `VANZARI_CORESP`)
- PL/SQL: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
## 0-bis. Constatare centrala care schimba premisa din plan
**Mecanismul S9 (planificat) NU e acelasi cu mecanismul deja existent de editare (#6).** Astea sunt
doua cai distincte, si asta conteaza pentru fiecare consumator de mai jos:
- **#6, "editare directa"** (`frm_modific2024`/`omodificari.vc2`, `afisjurcom.do_modifica`,
`pack_contafin.finalizeaza_modificare_nota`): documentul vechi (identificat prin `cod`) primeste
`STERS=1` in `ACT`/`RUL`, se scrie un document nou cu `cod` nou, apoi
**`pack_facturare.actualizeaza_vanzari(V_COD_VECHI, V_COD_NOU)`**
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16012-16022`) face
`UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI` —
**randul `VANZARI` e REFOLOSIT, `ID_VANZARE` NU SE SCHIMBA**, doar `COD`. In continuare,
`finalizeaza_modificare_nota` face si `UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod =
tnCod` (`rec_modific2024.md:126`, confirmat `fisier:linie`). Acesta e mecanismul deja **livrat in
productie** (git log: `#6 editare factura emisa`, changelog 2.11.15/2.11.16).
- **#13 S9 (planificat, subiectul acestei cercetari)**: reemiterea merge **pe drumul normal de
emitere**, `scrie_factura2` -> `finalizeaza_factura` -> `scrie_in_vanzari`, care face
`INSERT INTO VANZARI (...) VALUES (...) RETURNING ID_VANZARE INTO pack_facturare.nid_vanzare`
(`rec_cale_vanzari_detalii.md:99`, confirmat separat in planul S9: „reemiterea scrie prin
`pack_facturare`, pe acelasi drum ca emiterea", `plan_13...md:2936-2937`). Acesta e un rand **nou**,
cu **`ID_VANZARE` nou din secventa** — exact premisa data de team-lead. `oscrie_in_fisiere`
intervine in S9 **doar la stergerea** documentului vechi (`plan_13...md:2938`), nu si la scriere,
deci **`actualizeaza_vanzari`/sincronizarea prin `finalizeaza_modificare_nota` NU se declanseaza
pentru S9** — acel mecanism ramane specific caii #6, nu se mosteneste automat de S9.
**Consecinta directa**: orice legatura tinuta azi prin `cod` (realiniata gratis de mecanismul #6) e
**expusa** la regenerarea din #13, pentru ca S9 nu trece prin `actualizeaza_vanzari`. Fiecare sectiune
de mai jos noteaza explicit daca protectia #6 s-ar fi aplicat sau nu.
## 1. Inventarul exhaustiv al consumatorilor de ID_VANZARE
| # | Tabela/mecanism | Cheie folosita | Scriitor(i) | Cititor(i) | Clasificare |
|---|---|---|---|---|---|
| 1 | `ATASAMENTE_VANZARI` | `ID_VANZARE` (FK real `FK_AT_VANZ001` -> `VANZARI.ID_VANZARE`) + coloana `COD` (legacy, vezi §2) | `ofacturare_comun.prg:466` (VFP INSERT direct), `pack_facturare.scrie_atasamente_factura` (PL/SQL, `ff_...:13955-13988`) | `VATASAMENTE_VANZARI` (view, JOIN pe `id_vanzare`), `ROAGEST`/`ROAIMOB` `oproceduri_atasamente.prg` (`citeste_atasament_vanzari`, `arata_meniu_at_vanz` — cauta pe `cod` via view) | **(A) trebuie remigrat** — vezi §2 |
| 2 | `marcheaza_facturat` -> `VANZARI.FACTURAT`/`ID_UTILFACT` | `VANZARI_CORESP.ID_VANZARE_FACT` (documentul nou) leaga la sursa | `pack_facturare.marcheaza_facturat`, apelata din `finalizeaza_factura` (`ff_...:14827,14831`) | citit la filtrarea avizelor/comenzilor facturabile | **(C) nu conteaza pentru discontinuitatea ID_VANZARE-ului documentului editat** — vezi §3 (releaga sursa, nu documentul editat insusi) |
| 3 | `VANZARI_CORESP` | `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ`, ambele pe `VANZARI.ID_VANZARE` | `pack_facturare.scrie_corespondente_vanzari`, singurul writer (`s5c_factura_din_proforma.md:92-96`) | `sterge_factura` (garda la stergere), afisare "provine din X" | **(A) trebuie remigrat daca documentul editat e parte a unui lant retur/aviz->factura** — vezi §3 |
| 4 | `VANZARI_CANTITATI` | `ID_VANZARE` (a avizului sursa, nu a facturii editate) | `scrie_cantitati_vanzari_avize`, apelata doar `ntip=4` | `marcheaza_facturat(V_VERIFICARE=1)` | **(C) nu conteaza** — vezi §3, tine de sursa, neatinsa de editarea facturii |
| 5 | `DOCUMENTE` | `ID_DOC` = `ID_FACT` (nu `ID_VANZARE`) | `SET_IDFACT`/INSERT in `SCRIE_IN_ACT` | `SET_IDFACT` cautare veche (inactiva) | **(C) confirmat fara legatura cu ID_VANZARE** — vezi §4, acoperit deja de `idfact_refolosire_si_documente.md`, nu se reface |
| 6 | eFactura (`ANAF_EFACTURA`, `EsteInEFactura`) | `VANZARI.ID_FACT` (nu `ID_VANZARE`) | — | `ofacturare_editare.prg`/`omodificari.vc2` | **(C) nu conteaza** — cheia e `ID_FACT`, care se pastreaza — vezi §5 |
| 7 | Listari/rapoarte facturi (`FACT_VFACTURI*`, grid cautare) | `ID_VANZARE` + `ID_FACT`/serie+numar, cautari mixte | — | multiple | **de verificat, vezi §5** — cele care cauta dupa serie+numar/ID_FACT raman corecte; cele care tin un `ID_VANZARE` stocat undeva (ex. favorite, ultima factura deschisa) s-ar rupe |
| 8 | Incasari/plati (`INCASARI`, `PLATI`, `IREG_PARTENERI`) | **de verificat** | — | — | **in lucru, vezi §6** |
## 2. ATASAMENTE_VANZARI
**Nu sunt documente incarcate de utilizator — sunt exporturi PDF AUTO-GENERATE ale documentului
tiparit** (factura/aviz/recapitulatie/invoice), salvate automat dupa listare. Dovada, pas cu pas:
- Scrierea porneste din `frm_facturi.do_listeaza_formular`/fluxul de listare, la
`COMUN\programe\ofacturare.prg:2102-2105`:
```
If poDate.nRelistare = 0 And poDate.eProforma = 0 AND poDate.nEFactura = 0 And poDate.nSalveazaAtasamente = 1
poDate.scrieAtasamente()
Endif
```
imediat dupa exportul PDF al recapitulatiei (`:2068`, `goExport.export2pdf('crsrecapitulatie',
'recapitulatie', .F., poDate.cDocAtasate)`) — `poDate.cDocAtasate` **e** numele cursorului
`crsoDateDocAtasate`, populat de motorul de export PDF, nu de un dialog de upload.
- `crsoDateDocAtasate` se creeaza gol la `ofacturare.prg:233`: `Create Cursor crsoDateDocAtasate
(nume_frx c(50), fisier w)` — nicio referinta la `GETFILE()`/`GETPICT()`/dialog de fisier in tot
`ROAFACTURARE`/`COMUN` legata de acest cursor (cautat explicit, zero potriviri).
- `scrieAtasamente` (`ofacturare_comun.prg:429-475`) clasifica `TIP` dupa numele raportului
(`FACTURA_VAL*`->2, `FACTURA*`->1, `INVOICE*`->3, `RECAPITULATIE`->4, `AVIZ*`->5) si scrie:
`INSERT INTO ATASAMENTE_VANZARI(ID_VANZARE, TIP, FORMAT, DOCUMENT, ID_UTIL) VALUES (?pnId, ...)`
(`:466`) — `pnId = poDate.nid_vanzare` (sau `nid_vanzare_retur` pentru cazul aviz->factura). Deci
scriitorul **foloseste exclusiv `ID_VANZARE`**, niciodata `COD`.
- Cititorii confirmati: `ROAGEST\COMUN\programe\oproceduri_atasamente.prg` (identic in `ROAIMOB`) —
`citeste_atasament_vanzari` (`SELECT document FROM atasamente_vanzari WHERE id_at_vanz=...`) si
`arata_meniu_at_vanz(tnCod,...)` (`SELECT ... FROM vatasamente_vanzari a WHERE a.cod = ...`,
`:56`) — ambele read-only, mecanism de "vezi documentele salvate pentru aceasta factura",
disponibil din ROAGEST/ROAIMOB (alte produse ale suitei, confirmand ca S11 trebuia sa caute in
toata suita, nu doar ROAFACTURARE).
**Structura reala (interogata pe schema vie `MARIUSM_AUTO`)**: `ATASAMENTE_VANZARI(ID_AT_VANZ PK
NOT NULL din secventa, ID_VANZARE NUMBER NULL, DOCUMENT BLOB, TIP, FORMAT, STERS NOT NULL, ID_UTIL,
DATAORA NOT NULL, ID_UTILS, DATAORAS, COD NUMBER NULL)`. FK real: `FK_AT_VANZ001` pe `ID_VANZARE ->
VANZARI.ID_VANZARE` (`PK_VANZARI`). Coloana `COD` **nu e scrisa de niciun cod curent** (scriitorii
gasiti folosesc doar `ID_VANZARE`) — e populata azi doar de mecanismul legacy #6
(`finalizeaza_modificare_nota`'s `UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod = tnCod`,
care **nu poate seta `cod` pe un rand care nu-l are deja** — e un realiniere, nu o initializare).
Pe schema de test `MARIUSM_AUTO`, toate cele 20 de randuri existente au `COD` populat si
`ID_VANZARE` NULL (probabil date vechi, dinainte ca `ID_VANZARE` sa fie coloana activa) — cf.
memoriei de proiect, **zero cazuri in date nu e dovada**, dar dovada de cod (scriitorii folosesc
doar `ID_VANZARE`) e independenta de date si sta singura.
**Cheia reala de legatura azi e `ID_VANZARE`, prin view**: `VATASAMENTE_VANZARI` (definitie
interogata direct din `ALL_VIEWS`):
```sql
select a.id_at_vanz, a.tip, decode(a.tip,1,'Factura',2,'Factura in valuta',3,'Invoice',
4,'Recapitulatie',5,'Aviz','Alt document') as tip_doc, b.cod
from atasamente_vanzari a
left join vanzari b on a.id_vanzare = b.id_vanzare
where a.sters = 0 and b.sters = 0
```
`cod` in view **nu e coloana stocata pe `atasamente_vanzari`** — e `VANZARI.COD` curent, calculat
live prin JOIN pe `ID_VANZARE`. Consumatorii din alta suita (`ROACONT\Programe\orap_terti.prg:1854`,
`ROACONTRACTE\Programe\oparteneri_contracte.prg:190,229` — `LEFT JOIN vatasamente_vanzari ... ON
a.cod = b.cod`, pentru afisarea unui numar de atasamente pe rand de factura) folosesc de fapt tot
`ID_VANZARE`, indirect prin acest JOIN.
**Ce se rupe la regenerare (S9, calea B din §0-bis)**: `atasamente_vanzari.id_vanzare` al randurilor
vechi ramane neschimbat, aratand spre `VANZARI` cu `STERS=1` dupa stergerea documentului vechi.
`WHERE b.sters = 0` din view **filtreaza acele randuri afara** — atasamentele **dispar tacit** din
orice interogare prin `VATASAMENTE_VANZARI` (inclusiv `arata_meniu_at_vanz` din ROAGEST/ROAIMOB),
desi BLOB-ul ramane fizic in tabel. Documentul nou (`ID_VANZARE` nou) nu are niciun atasament legat.
**Clasificare: (A) trebuie remigrat** — `UPDATE atasamente_vanzari SET id_vanzare = :id_nou WHERE
id_vanzare = :id_vechi AND sters = 0`, in aceeasi tranzactie ca restul regenerarii (acelasi tipar ca
`actualizeaza_vanzari` de la #6, dar pe `id_vanzare` in loc de `cod`, si trebuie scris nou — #6 nu
acopera acest caz, cf. §0-bis).
**Corectie fata de premisa din briefing**: nu e "pierdere de date reala" in sensul de date
introduse de utilizator si irecuperabile — snapshot-ul se poate regenera prin relistare (acelasi
mecanism care l-a creat prima data). Ce s-ar pierde real e **istoricul exact al PDF-ului trimis
clientului la momentul emiterii initiale** (relevant daca factura a fost deja trimisa/tiparita
inainte de editare) — merita remigrare oricum, ca sa nu se piarda urma, dar motivatia corecta e
"pastrarea unui audit trail", nu "date introduse de utilizator".
## 3. marcheaza_facturat / VANZARI.FACTURAT / VANZARI_CANTITATI / VANZARI_CORESP
**Inventarul FK declarat pe `VANZARI.ID_VANZARE`** (interogare directa pe schema `ACN`, cea reala —
`ALL_CONS_COLUMNS`/`ALL_CONSTRAINTS`, `r_constraint_name = PK_VANZARI`), confirma si extinde lista
din briefing:
| Tabela | Coloana | Constraint |
|---|---|---|
| `ATASAMENTE_VANZARI` | `ID_VANZARE` | `FK_AT_VANZ001` |
| `VANZARI_CORESP` | `ID_VANZARE_FACT` | `FK_VANZARI_CORESP_001` |
| `VANZARI_CORESP` | `ID_VANZARE_AVIZ` | `FK_VANZARI_CORESP_002` |
| `VANZARI_CURSURI` | `ID_VANZARE` | `FK_VANZARE_CURS_002` |
| `VANZARI_DETALII` | `ID_VANZARE` | `FK_VANZARE_DET001` |
| `REST_NOTE_PLATA` | `ID_VANZARE` | `FK_REST_NOTE_PLATA_004` |
| `IPS_VOYAGES_VANZARI` | `VZ_ID` | `FK_VOYAGES_VANZARI_2` |
Primele cinci erau asteptate (vezi §1-2 si mai jos). **Ultimele doua nu erau in lista din plan** —
descoperite prin FK, nu prin cautare de text, exact motivul pentru care inventarul trebuia facut pe
schema, nu doar pe cod. Ambele sunt module de business **specifice unui singur tip de document**
(`pack_facturare.ntip`), nu cai generale:
- **`REST_NOTE_PLATA`** — modul restaurant (`PACK_RESTAURANT`, `nTipFacturaRestaurant`). La
stergerea unei vanzari de tip restaurant, `sterge_factura` cheama deja
`pack_restaurant.sterge_vanzare(V_ID_VANZARE, V_ID_UTIL)` (`ff_...:5593`, in `CASE`-ul de la
finalul procedurii) — un hook dedicat, separat de logica generala. **Nu s-a gasit un hook simetric
„re-leaga la reemitere"** in codul citit (nu era in scop sa se citeasca tot `PACK_RESTAURANT` —
pachet separat, mii de linii). Clasificare: **(A) suspecta, needs follow-up dedicat** — afecteaza
doar documentele cu `ntip = nTipFacturaRestaurant`, deci doar daca #13 include si acest tip in
„regenerabile" (de confirmat in `COMUN\docs\tipuri_documente_facturare.md`, cf. S13).
- **`IPS_VOYAGES_VANZARI`** — modul specific schemei `ACN` (`PACK_ACN`, `nTipFacturaACN`), legat de
un tabel `IPS_VOYAGES_VANZARI`/`IPS_VOYAGE_MEMBERS_VANZARI`/`IPS_VVOYAGE_MEMBERS` (confirmat in
`ris_2024_04_09_01_ACN.sql:258-266`, JOIN pe `vz.id_vanzare = vv.vz_id`) — pare o extensie de
business pentru un client specific (calatorii/voiaje), nu parte din suita generica ROA. Acelasi
tipar: `sterge_factura` cheama `pack_acn.sterge_vanzare(...)` la stergere (`ff_...:5596-5600`,
`execute immediate` conditionat de `pack_migrare.ObjectExist('PACK_ACN')` — pachetul exista doar
pe schema ACN). Clasificare: **(A) suspecta, needs follow-up dedicat** — probabil in afara
perimetrului #13 (client unic, tip de factura special), dar trebuie confirmat, nu presupus.
**`VANZARI_CURSURI`** — cursul valutar al documentului. Scris de `pack_facturare.scrie_cursuri(nid_vanzare)`
(`rec_cale_vanzari_detalii.md:102`), apelat **in interiorul aceluiasi `scrie_in_vanzari`** care
genereaza noul `ID_VANZARE` la reemitere (S9 foloseste exact acest drum, cf. §0-bis). Deci un rand
nou `VANZARI_CURSURI` se scrie automat pentru noul `ID_VANZARE`, fara nicio interventie separata.
**Clasificare: (B)** — se reface singur, ca parte a drumului normal de emitere, nimic de adaugat.
**`VANZARI_DETALII`** — liniile facturii. E miezul a ceea ce regenerarea insasi scrie (INSERT direct
pe noul `ID_VANZARE`, cf. `rec_cale_vanzari_detalii.md:105-113`) — nu e un consumator "extern" care
sa se rupa, e obiectul regenerarii. **Clasificare: (B)**, deja acoperit de proiectarea S9/S10, nu se
reface aici.
### VANZARI_CORESP — cazul netratat inca de plan: documentul editat e EL INSUSI parte a unui lant
Planul (S11, `plan_13...md:3024-3027`) trateaza `VANZARI_CORESP` ca pe o lista simpla, dar
`sterge_factura` (apelata de S9 la pasul de stergere) arata ca situatia are **doua fete diferite**,
niciuna simpla:
**(i) Garda de blocare — editarea unui document care e SURSA pentru altul e azi IMPOSIBILA, nu
riscanta.** `sterge_factura` (`ff_...:5450-5494`) verifica, INAINTE de orice stergere:
```sql
SELECT COUNT(*) INTO V_NR_FACT_RETUR FROM VANZARI_CORESP
WHERE STERS=0 AND ID_VANZARE_AVIZ = V_ID_VANZARE AND TIP = 3;
IF V_NR_FACT_RETUR > 0 THEN RAISE_APPLICATION_ERROR(-20000, 'Pentru acesta factura s-au emis facturi
de retur. Trebuie sa stergeti mai intai factura de retur!'); END IF;
```
si analog pentru `TIP IN (1,2)` (aviz cu factura/aviz de retur emise pe el) si pentru avizele-sursa
ale unei „facturi din aviz" (subquery imbricat, `:5478-5494`). **Consecinta pentru S9**: daca
utilizatorul incearca sa editeze (regenereze) o factura care are deja o factura de retur emisa
pe ea, sau un aviz care are deja o factura/aviz de retur emis pe el, **pasul de stergere din
tranzactia S9 arunca `ORA-20000`, tranzactia face rollback, documentul vechi ramane intact** — nu
e coruptie silentioasa, e un esec curat, dar e o **limitare functionala reala**: aceste documente
nu pot fi editate prin regenerare deloc, cat timp garda ramane activa (si nu exista niciun motiv
funcțional sa fie dezactivata — ar contrazice exact protectia pe care garda o ofera azi). *De
verificat de S12*: ca mesajul de eroare Oracle ajunge inteligibil la utilizator prin formularul de
editare, nu ca o eroare tehnica generica.
**(ii) Legaturile in care documentul editat e chiar `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ` — marcate
`STERS=1` la stergere, NU remigrate automat, dar potential rescrise de reemitere daca parametrii
sunt refurnizati corect.** La stergerea documentului vechi, `sterge_factura` executa neconditionat
(`:5582-5585`):
```sql
UPDATE VANZARI_CORESP SET STERS = V_STERS
WHERE ID_VANZARE_FACT = V_ID_VANZARE OR ID_VANZARE_AVIZ = V_ID_VANZARE;
```
— orice legatura in care documentul vechi apare pe oricare parte devine `STERS=1`. **Nu exista cod
care sa recreeze automat legatura pentru noul `ID_VANZARE`.** Dar exista o cale prin care se
recreeaza **corect, de la sine**, daca S9 e proiectat sa refoloseasca drumul normal: `finalizeaza_factura`
(chemata de reemiterea normala) are un `CASE` pe `pack_facturare.ntip` care scrie din nou
corespondenta (`WHEN ntip=4: scrie_corespondente_vanzari(1)`; `WHEN ntip=24:
scrie_corespondente_vanzari(2)`; `WHEN ntip IN (8,9): scrie_corespondente_vanzari(3)`,
`ff_...:14823-14836`) — **daca** S9 seteaza `pack_facturare.ntip` la aceeasi valoare ca documentul
original SI re-populeaza `pack_facturare.clistaid`/`clistaid_avize` cu lista de `id_vanzare` sursa
originala **inainte** de reemitere, corespondenta se scrie natural, cu noul `ID_VANZARE_FACT`.
**Sursa lista originala e recuperabila** din randurile `VANZARI_CORESP` (acum `STERS=1`) ale
documentului vechi, citite INAINTE de stergere in aceeasi tranzactie — un pas suplimentar pe care
proiectarea S9 trebuie sa-l includa explicit, nu e „gratis". **Clasificare: (A) trebuie remigrat
— dar prin re-derivare + refolosirea mecanismului existent, nu prin UPDATE direct pe `VANZARI_CORESP`**
(un UPDATE direct ar trebui sa distinga cu grija cele doua roluri — FACT vs AVIZ — si ar risca sa
resusciteze legaturi catre documente inca sterse din alte motive; re-emiterea naturala prin `ntip`+
`clistaid` e mai sigura, pentru ca refoloseste exact logica deja validata de emiterea normala).
### marcheaza_facturat — pentru sursa documentului editat, nu pentru documentul insusi
`marcheaza_facturat` (`ff_...:15381-15418`) actioneaza pe **sursa** (avizul din care s-a facturat,
sau avizul-parinte al unui aviz de retur), nu pe documentul care se editeaza. La stergerea
documentului vechi (S9, pasul de stergere), pentru `V_TIP=4`/`V_TIP=24`, `sterge_factura` reseteaza
deja `FACTURAT=0`/`ID_UTILFACT=NULL` pe sursa (`:5504-5510`, `:5525-5533`) si marcheaza
`VANZARI_CANTITATI.STERS=1` pentru cantitatile consumate (`:5517-5523`, `:5535-5540`) — sursa e deci
**eliberata** corect de mecanismul EXISTENT, neschimbat. La reemitere, daca `ntip`/`clistaid_avize`
sunt refurnizate corect (acelasi argument ca la VANZARI_CORESP mai sus), `finalizeaza_factura`
cheama din nou `scrie_cantitati_vanzari_avize` + `marcheaza_facturat` (`:14825-14827`,`:14831`),
**reconsumand** sursa sub noul `ID_VANZARE_FACT`. **Clasificare: (C) — mecanismul deja exista si
functioneaza simetric (elibereaza la stergere, reconsuma la scriere), CONDITIONAT de aceeasi
cerinta ca la VANZARI_CORESP: S9 trebuie sa refurnizeze `ntip` si `clistaid`/`clistaid_avize`
originale la reemitere.** Aceasta e exact conditia pe care S12 o testeaza explicit („sursa eliberata
si reconsumata", `plan_13...md:3037`) — S11 confirma DE CE mecanismul poate functiona (nu e nevoie
de cod nou pentru asta), dar nu inlocuieste testul S12.
## 4. DOCUMENTE / ID_DOC — legatura cu ID_VANZARE
**Confirmat: fara legatura.** `DOCUMENTE(ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT,
DATAACT, DATAIREG, ID_CTR, ID_SET, STERS, ...)` — INSERT-ul complet citat in
`idfact_refolosire_si_documente.md:143-148` (`PACK_CONTAFIN.pck:788-817`, cod activ) nu are nicio
coloana `ID_VANZARE`. Cheia `ID_DOC = ID_FACT`, care se PASTREAZA la regenerare (decizie E/F, deja
data). Acest raport nu reface analiza — deja acoperita exhaustiv de
`docs\cercetare\idfact_refolosire_si_documente.md` (S9, punctele A-C), care stabileste separat
conditiile pentru pastrarea `ID_FACT` (variabila noua de sesiune in `SET_IDFACT` + upsert real in
`DOCUMENTE`). **Clasificare: (C) — confirmat, fara actiune ceruta de S11.**
## 5. Borderou eFactura si listari
**eFactura: cheia e `ID_FACT`, nu `ID_VANZARE` — confirmat cu dovada proprie, independenta.**
`EsteInEFactura` (`COMUN\programe\ofacturare_editare.prg`, apelata din `ofacturare_comun.vc2:3764`
cu `lnIdFact = crsfacturi.id_fact`) verifica prezenta documentului in `ANAF_EFACTURA` **pe
`VANZARI.ID_FACT`**, nu pe `id_vanzare` — confirmat explicit ca eroare corectata in
`rec_d42_efactura.md:81-109` (implementarea initiala folosea gresit `id_vanzare`, corectata dupa
verificare pe date: `id_fact` si `id_vanzare` sunt spatii de ID complet diferite, 0 coincidente pe
142 facturi testate). **Cum `ID_FACT` se pastreaza la regenerare (decizie deja luata), `EsteInEFactura`
continua sa functioneze corect pe documentul reemis, fara nicio schimbare.** Aceeasi concluzie se
aplica probabil borderoului eFactura insusi (cautarea documentelor de trimis/trimise), dar
**construirea borderoului nu a fost citita in acest raport** (in afara scopului — cheia `ID_FACT`
fiind deja confirmata stabila, riscul e mic, dar afirmatia stricta „borderoul il gaseste corect"
ramane de verificat direct pe cod la implementare, nu doar dedusa). **Clasificare: (C), cu rezerva
de verificare directa a borderoului la implementare.**
**Listari/grid cautare facturi**: cursoarele de grid (`crsFacturi`, `crsFacturiOrd`,
`ofacturare_comun.vc2:3982,4134-4145,4983`) se reconstruiesc live din Oracle la fiecare deschidere
a formularului de listare, filtrate pe `sters=0` — nu tin niciun `id_vanzare` persistat intre
sesiuni. Documentul reemis (nou `id_vanzare`, acelasi `id_fact`/serie/numar) apare normal la
urmatoarea reincarcare a gridului. **Singurul risc identificat, nu confirmat ca problema reala**:
daca undeva in cod exista un `id_vanzare` **retinut pe termen lung** in afara sesiunii curente a
formularului (ex. „ultima factura deschisa", favorite, shortcut) — nu s-a gasit niciun asemenea
mecanism in codul citit (`ofacturare_comun.vc2`, `ofacturare.prg`), dar nici nu a fost cautat
exhaustiv in tot `ROAFACTURARE` (in afara bugetului acestei cercetari). **Clasificare: (C),
neconfirmat ca risc real — verificare suplimentara recomandata daca timpul permite.**
## 6. Incasari/plati si riscuri finale
**Cautare directa (negativa, dar utila): niciun tabel de incasari/plati nu are FK declarat pe
`VANZARI.ID_VANZARE`.** Interogarea exhaustiva `ALL_CONS_COLUMNS`/`ALL_CONSTRAINTS` pe schema `ACN`
(tabelul din §3) e completa pentru FK-uri declarate — `INCASARI`, `PLATI`, `IREG_PARTENERI` nu apar
in acea lista. Cautare text suplimentara (`grep -rn "ID_VANZARE"` filtrat pe fisiere cu
"incasari"/"plati"/"chitant" in nume, in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR`) — **zero rezultate**.
Coerent cu arhitectura deja cunoscuta: incasarile/platile se leaga de facturi prin **`ID_FACT`**
(coloana pe `ACT`/`IREG_PARTENERI`, cf. `idfact_refolosire_si_documente.md`), nu prin `ID_VANZARE`
— `ID_FACT` se pastreaza la regenerare, deci **incasarile/platile nu sunt afectate structural**.
**Clasificare: (C), cu dovada dubla (schema + text), coerenta cu memoria de proiect „zero cazuri in
date nu e dovada" — aici insa e absenta de FK + absenta de text, nu absenta de date, deci e o
dovada structurala, nu doar un data point.**
## Rezumat clasificari
| Consumator | Clasificare | Actiune ceruta |
|---|---|---|
| `ATASAMENTE_VANZARI.ID_VANZARE` | **(A)** | `UPDATE ... SET id_vanzare=:nou WHERE id_vanzare=:vechi AND sters=0`, in tranzactia S9 |
| `VANZARI_CORESP` (documentul editat ca FACT/AVIZ al altui lant) | **(A)**, prin re-emitere naturala | S9 trebuie sa citeasca `ntip`+lista sursa originala INAINTE de stergere si sa le refurnizeze la reemitere |
| `marcheaza_facturat`/`VANZARI_CANTITATI` (sursa documentului editat) | **(C)**, conditionat | functioneaza automat DACA `ntip`/`clistaid` sunt refurnizate (acelasi mecanism ca mai sus) |
| `VANZARI_CURSURI` | **(B)** | se rescrie automat de `scrie_in_vanzari`, nimic de facut |
| `VANZARI_DETALII` | **(B)** | e obiectul regenerarii, acoperit de S9/S10 |
| `DOCUMENTE`/`ID_DOC` | **(C)** | confirmat fara legatura, acoperit de `idfact_refolosire_si_documente.md` |
| eFactura (`EsteInEFactura`, `ANAF_EFACTURA`) | **(C)** | cheie `ID_FACT`, pastrat — verificare directa a borderoului recomandata la implementare |
| Listari/grid facturi | **(C)** | cursoare live, fara stare persistata gasita |
| Incasari/plati | **(C)** | fara FK, fara referinta text — cheie e `ID_FACT` |
| `REST_NOTE_PLATA` (modul restaurant) | **(A)** suspecta | needs follow-up dedicat, `PACK_RESTAURANT` necitit integral |
| `IPS_VOYAGES_VANZARI` (modul ACN specific client) | **(A)** suspecta | needs follow-up dedicat, `PACK_ACN` necitit integral, posibil in afara perimetrului #13 |
## Riscuri si ce ramane de decis de Marius
1. **Editarea documentelor care sunt sursa unui lant retur/aviz e azi BLOCATA, nu doar riscanta.**
`sterge_factura` arunca `ORA-20000` daca documentul editat are deja facturi/avize de retur emise
pe el (§3.i). Nu e o eroare de proiectare — garda protejeaza integritatea lantului — dar
inseamna ca „editare prin regenerare" **nu va functiona deloc** pentru aceste documente, cat timp
utilizatorul nu sterge intai documentele-copil. **De decis**: acceptat ca limitare (cu mesaj
clar in UI, tradus din eroarea Oracle), sau se doreste alt comportament?
2. **Recuperarea `VANZARI_CORESP`/`marcheaza_facturat` la reemitere depinde de un pas pe care planul
nu-l mentioneaza explicit**: citirea listei sursa originale (`ID_VANZARE_AVIZ`/`ID_VANZARE_FACT`
din randurile `VANZARI_CORESP` ale documentului vechi) **inainte** de stergere, si refurnizarea
ei ca `pack_facturare.clistaid`/`clistaid_avize` la reemitere. Fara acest pas, corespondenta si
`marcheaza_facturat` **nu se scriu deloc** pentru documentul reemis (nu eroare, ci scriere lipsa
silentioasa) — de adaugat explicit in proiectarea S9, nu presupus „vine gratis" din drumul normal.
3. **`ATASAMENTE_VANZARI` nu e „date de utilizator pierdute"**, cf. §2 — e un audit trail de
PDF-uri auto-generate la listare. Remigrarea (`UPDATE ... SET id_vanzare`) e simpla si ieftina,
dar motivatia corecta pentru Marius e „pastrarea istoricului tiparit", nu „pierdere de date
introduse manual" — poate schimba prioritatea relativa fata de alte itemi din S11/S12.
4. **`REST_NOTE_PLATA` si `IPS_VOYAGES_VANZARI`** raman needs-investigation — descoperite prin FK,
nu prin text, deci nu erau in raza cautarilor anterioare (nici in planul S11 original). Daca
tipurile de document asociate (`nTipFacturaRestaurant`, `nTipFacturaACN`) nu sunt in domeniul
„regenerabile" al #13 (de confirmat in `tipuri_documente_facturare.md`, S13), riscul dispare de
la sine — dar asta trebuie confirmat, nu presupus.
5. **Borderoul eFactura insusi** (nu doar `EsteInEFactura`) nu a fost citit direct — cheia `ID_FACT`
fiind stabila, riscul e mic, dar afirmatia din *Gata cand* a S11 („documentul reemis se regaseste
corect in borderoul eFactura") merita o verificare punctuala pe cod la implementare, nu doar
dedusa din `EsteInEFactura`.
## STARE / CE RAMANE
**Cercetare incheiata pentru toate cele 7 puncte cerute de briefing**, cu dovada `fisier:linie` pe
fiecare afirmatie portanta: inventar exhaustiv (via FK Oracle + cod), `ATASAMENTE_VANZARI` (sectiune
proprie, cu corectia premisei „date de utilizator"), `marcheaza_facturat`/`VANZARI.FACTURAT`/
`VANZARI_CANTITATI`, `DOCUMENTE`/`ID_DOC` (confirmat, fara redeschidere), borderou eFactura si
listari, riscuri finale.
**Constatare centrala**: mecanismul S9 planificat (regenerare cu `ID_VANZARE` nou, pe drumul normal
de emitere) e **diferit** de mecanismul #6 deja livrat (`actualizeaza_vanzari`, reutilizeaza acelasi
`ID_VANZARE`, doar `COD` se schimba) — deci nicio protectie construita pentru #6 nu se mosteneste
automat de S9. Singurul consumator care chiar are nevoie de un `UPDATE` explicit nou este
`ATASAMENTE_VANZARI` (clasificare A simpla). `VANZARI_CORESP`/`marcheaza_facturat` se pot rezolva
**fara cod PL/SQL nou**, refolosind mecanismul existent (`finalizeaza_factura`'s `CASE` pe `ntip`),
**daca** S9 e proiectat sa citeasca si sa refurnizeze parametrii sursei originale — asta e o cerinta
de design care trebuie scrisa explicit in S9, nu deductibila implicit.
**Neverificat, ramas pentru follow-up** (declarat, nu ascuns): corpul complet al `PACK_RESTAURANT`/
`PACK_ACN` (pentru `REST_NOTE_PLATA`/`IPS_VOYAGES_VANZARI`), borderoul eFactura citit direct (nu doar
`EsteInEFactura`), o cautare exhaustiva de „id_vanzare retinut pe termen lung" in tot codul VFP
(favorite/shortcut-uri), si confirmarea in `tipuri_documente_facturare.md` a caror tipuri intra
efectiv in perimetrul „regenerabile" al #13 (ar elimina sau confirma riscurile 1 si 4).
Zero modificari de cod, zero write-back, zero `git_sync.ps1`/`txt2vcx.ps1`, zero commit. Pe Oracle
doar `SELECT` (schema `MARIUSM_AUTO`, interogari pe `ALL_TAB_COLUMNS`/`ALL_CONSTRAINTS`/`ALL_VIEWS`/
`ALL_SOURCE`, plus `SELECT` de numarare pe date de test — niciuna cu efect de scriere).

View File

@@ -1,318 +0,0 @@
# S2 — Proiectare: o singura procedura `factureaza`
Cercetare read-only pentru povestea **S2** din `docs\plan_13_unificare_formular_facturare.md`.
Niciun fisier de cod atins, `git_sync.ps1` nerulat, niciun write-back, niciun commit.
## Verdict
**Se poate unifica, si e mai simplu decat pare.** `factureaza2` nu e o a doua implementare
funcţională care trebuie fuzionata cu grija — e un fork din 08.06.2017 la care s-a **dezactivat
execuţia interogarii de articole** (`lnSucces = 1` hardcodat, `goExecutor.oExecute` niciodata
apelat) si mai multe blocuri intregi sunt inchise cu `If .F.`. Are **exact un singur apelant** in
tot codul (`ofacturare.prg:90`, din interiorul lui `factureaza` insusi, in spatele unui
`AMESSAGEBOX` de confirmare) si **zero utilizatori reali** dincolo de acel switch de test. Riscul de
regresie e mic pentru ca nu exista nimic funcţional de pierdut in `factureaza2` — obstacolul
principal nu e tehnic, e ca formularul spre care duce (`frm_facturare_articole2`) e tot un prototip
neterminat (`Init` de 92 de linii, nu completeaza antetul), deci unificarea proceduri lor **nu**
face formularul nou utilizabil — doar elimina duplicarea de cod. Asta ramane treaba lui S3.
---
## 1. Inventarul celor doua proceduri
Ambele sunt proceduri globale (nu metode de clasa) in **`COMUN\programe\ofacturare.prg`**, incarcat
via `SET PROCEDURE ... ADDITIVE` din `Programe\roafacturare.prg`. Nu exista omonime — verificat cu
`Get-ChildItem -Recurse *.prg | Select-String '^\s*(PROCEDURE|FUNCTION)\s+factureaza2?\b'` pe tot
`ROAFACTURARE` si cu Grep pe `.vc2` din `COMUN`: un singur rezultat pentru fiecare nume.
| | `factureaza` | `factureaza2` |
|---|---|---|
| Locatie | `COMUN\programe\ofacturare.prg:81-577` (497 linii) | `COMUN\programe\ofacturare.prg:583-1081` (499 linii) |
| Parametri | `LPARAMETERS tnTip, toFactura` (`:82`) — `toFactura` = obiect, pentru copiere/modificare | `LPARAMETERS tnTip` (`:584`) — **fara** al doilea parametru |
| Apelanti | ~20+ puncte de intrare reale (sectiunea 3) | **unul singur**: `ofacturare.prg:90`, din `factureaza` |
`ofacturare.prg` in sine e cod comun al **intregii suite**: exista o copie identica-la-origine in
`COMUN\programe\ofacturare.prg` a fiecarui produs ROA verificat (ROACONT, ROAGEST, ROACONTRACTE,
ROAAUTO, ROAACNPRO, ROAIMOB, plus alte ~30 produse) — fiecare cu propriile `factureaza`/`factureaza2`
la linii apropiate (de regula `:76-77` sau `:87-88` pentru comutatorul `gnFacturareNou`, dupa
versiune). Nu exista in `COMUNROA` (biblioteca partajata separata) — e sincronizat manual intre
produse ca fisier COMUN, nu ca livrabil COMUNROA.
## 2. Diff-ul semantic
Comparatie linie-cu-linie, cu citate `ofacturare.prg:linie`. **Sase din cele noua diferente listate
in sectiunea A a planului se confirma exact; una e imprecisa; una lipseste din plan.**
### Ce face `factureaza` si nu face `factureaza2`
| Ce | `factureaza` | `factureaza2` |
|---|---|---|
| Tipurile 51 (ROAACNPRO), 52 (contract factura fiscala valuta) rutate la FACTURA | `Inlist(tnTip,45,48,49,**51,52**)` la `:192` si `:222` | `Inlist(tnTip,45,48,49)` la `:674` si `:700` — **51/52 lipsesc**, ar cadea in `Otherwise` -> AVIZ |
| Ramura `llCopiere`/`toFactura` (copiere/modificare factura) | declarata `:102`, populata `:111`, folosita la completarea datelor (`:200-206`), la construirea interogarii (`Case m.llCopiere` -> `cursor_retur_document`, `:267-268`), si la adaugarea articolelor din lista de preturi peste cele copiate (`:454-474`) | **absenta complet** — nicio declarare a `llCopiere`, niciun tratament al lui `toFactura` |
| Deschiderea `frm_date_factura`/`frm_date_aviz` | activa, cu `toFactura` transmis constructorului (`:229-235`) | inchisa cu `If .F.` (`:707-711`) |
| Filtrul `RORTC` pe `jtva_coloane` | `AND !'RORTC'$UPPER(coloana_jv)` la ambele ramuri (`:243`, `:245`) | absent (`:715`, `:717`) |
| Executia interogarii de articole | `lnSucces = goExecutor.oExecute(lcSqlCursor, lcCursor)` (`:311`) | **inchisa cu `If .F.`** (`:823-826`); imediat dupa, `lnSucces = 1` hardcodat (`:827`) — cursorul SQL construit la `:748-822` nu ruleaza niciodata |
| Verificarea "nu exista articole" | activa (`:324-328`) | **inchisa cu `If .F.`** (`:838`) — combinata cu `lnSucces=1` de mai sus, ramura merge mereu pe calea "am articole", indiferent de continut |
| Zeroizarea `gestionabil` pe proforma | `IF poDate.eProforma = 1 ... UPDATE (lcCursor) SET gestionabil = 0` (`:333-336`) | **absenta** — `eProforma` nu apare nicaieri in `factureaza2` (verificat cu grep pe tot fisierul) |
| `crspolitici` / `crscontracte` (populare combo-uri) | necondiţionate (`:420-440`) | **ambele inchise cu `If .F.`** (`:956-968`, `:969-980`) |
| `GetInstitutiePublica` | `poDate.institutie_publica = GetInstitutiePublica(...)` (`:509`) | **absenta** |
| `codnc8`/`codcpv` pe fiecare linie | `Replace codnc8 WITH ..., codcpv WITH ...` in `Scan` (`:516-517`), pe langa `codmatc` | **doar `codmatc`** (`:1027`) — `codnc8`/`codcpv` lipsesc |
| `GetSoldClient` + `poDate.sold_lei`/`sold_valuta` inainte de listare | prezent, in ramura non-bon-fiscal (`:530-533`) | **absent** — ramura echivalenta (`:1040-1041`) sare direct la `listeaza_ofacturare()` |
| Formularul de articole | `frm_facturare_articole` (`:395`) | `frm_facturare_articole2` (`:929`) — **singura diferenta functionala reala** care justifica existenta lui `factureaza2` |
### Corectii fata de sectiunea A a planului
- **"`verifica_numar(16, ...)` pentru chitanta" — planul GRESESTE.** E prezent **identic** in ambele:
`factureaza:502-504` si `factureaza2:1018-1020` (`If poDate.incasat <> 0 ...
poGeneratorNumere.verifica_numar(16, poDate.nr_incasare) Endif`). Nu lipseste din `factureaza2`.
- **"`cursor_avize` cu tip 23" — planul e imprecis.** Nu exista nicio asociere `cursor_avize`/tip 23
in niciuna din cele doua proceduri (`cursor_avize` trateaza doar `tnTip = 4`, identic in ambele,
`:294-295` / `:774-775`). Ce difera real pentru tipul 23: in `factureaza`, tipul 23 e prins in
`Case Inlist(tnTip,1,22,5,29,7,10,**23**)` -> `cursor_preturi` (`:279-282`); in `factureaza2`, 23
**nu** e in acea lista (`:758`, doar `1,22,5,29,7,10`) si cade in schimb in
`Case Inlist(tnTip,**23**,41)` -> `cursor_gestiune` (`:776-778`). E o rutare complet diferita
pentru acelasi tip de document, nu o simpla lipsa — **de adaugat explicit la lista din sectiunea A**.
- **Nementionat in plan — tipul 52 lipseste si din `Do Case` de contract**: `factureaza`
`Case Inlist(tnTip,2,26,6,**52**)` (`:283`) vs `factureaza2` `Case Inlist(tnTip,2,26,6)` (`:762`) —
aceeasi lipsa a tipului 52 ca la nIdTipDoc, dar intr-un loc separat din cod; **tipul 24** (aviz de
retur) lipseste similar din `Case Inlist(tnTip,8,9,**24**)` (`:306`) vs `Case Inlist(tnTip,8,9)`
(`:819`).
### Ce fac amandoua identic (verificat, nu presupus)
Structura comentariilor `lnIdSet`/`pnTipFacturare`, bucla `Do While lnRaspuns = 6`, apelul
`actualizeaza_optiuni_program()`, crearea `poDate`/`poGeneratorNumere`, alegerea `frm_date_factura` /
`frm_date_aviz_lucrare` / `frm_date_aviz` pentru antet (desi in `factureaza2` deschiderea propriu-zisa
e dezactivata), tratarea `tnTip=30` (aviz din NIR, inclusiv bucla `Scatter`/`Insert Into crsfactura`
identica caracter-cu-caracter), `verifica_numar(16,...)`, dezalocarea celor trei numere
(`nIdTipDoc`, 16, 3/26) la abandon, si intreaga bucla finala de `AMESSAGEBOX`
"Doriti sa continuati" + `CloneObj`/`poDate.Reset()`.
### Ce face `factureaza2` si nu face `factureaza`
Un singur lucru: verificari defensive suplimentare de tip la iesirea timpurie (`pnButon = 2`):
```
IF TYPE('poDate.nr_incasare') = 'N'
poDate.nr_incasare = 0
ENDIF
IF TYPE('poDate.incasat') = 'N'
poDate.incasat = 0
ENDIF
```
(`factureaza2:727-732`) — `factureaza` face resetarea echivalenta necondiţionat, dar mai tarziu, in
ramura de succes (`:546-547`), nu pe calea de abandon. Diferenta e cosmetica, nu comportamentala pe
fluxul normal.
## 3. Toti apelantii
**Cautare in tot arborele `D:\ROA`** (fiecare produs + `COMUN`-ul lui + `COMUNROA`), cu capcana
"Grep nu vede COMUN" tratata explicit (path dat direct pe fiecare `COMUN`, nu doar pe radacina
produsului — altfel cautarea sare tacut peste el, per gitignore-ul fiecarui produs).
**Niciun produs verificat (ROACONT, ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB) nu apeleaza `factureaza`
direct din cod propriu — nici in `COMUN`, nici in partea specifica produsului.** Singura urma in
`COMUN`-urile lor e propria copie a `ofacturare.prg`/`oproceduri_facturare.prg` (definitia, nu un
apel). Confirma independent observatia din `coresp_cont_venchelt.md` ca ROAACNPRO nu trece prin acest
flux (foloseste `pack_acn.salveaza_regdoc`, nu `contabilizeaza_articol`). **Exceptia: ROACONTRACTE**,
care are un apel real, specific produsului, la wrapper-ul `facturare_contracte`.
### Apelanti directi ai `factureaza(N)`
| Apelant | Produs | Parametri | Tip document |
|---|---|---|---|
| `Meniuri\politica.mn2:12` | ROAFACTURARE | `factureaza(1)` | lista de preturi, lei |
| `Meniuri\politica.mn2:15` | ROAFACTURARE | `factureaza(8)` | retur factura lei |
| `Meniuri\politica.mn2:18` | ROAFACTURARE | `factureaza(45)` | restaurant |
| `Meniuri\politica.mn2:26` | ROAFACTURARE | `factureaza(49)` | marfa custodie fara descarcare K |
| `Meniuri\politica.mn2:29` | ROAFACTURARE | `factureaza(48)` | marfa custodie cu descarcare K |
| `Meniuri\politica.mn2:37` | ROAFACTURARE | `factureaza(5)` | lista de preturi, valuta (invoice) |
| `Meniuri\politica.mn2:40` | ROAFACTURARE | `factureaza(7)` | credit note |
| `Meniuri\politica.mn2:43` | ROAFACTURARE | `factureaza(10)` | factura fiscala valuta |
| `Meniuri\politica.mn2:46` | ROAFACTURARE | `factureaza(9)` | retur factura valuta |
| `Meniuri\contracte.mn2:12` | ROAFACTURARE | `factureaza(2)` | contract, lei |
| `Meniuri\contracte.mn2:15` | ROAFACTURARE | `factureaza(6)` | contract, valuta |
| `Meniuri\contracte.mn2:18` | ROAFACTURARE | `factureaza(52)` | contract, factura fiscala valuta |
### Apelanti prin `COMUN\programe\oproceduri_facturare.prg` (dispecer comun, un wrapper per grup de tipuri)
| Wrapper (`oproceduri_facturare.prg`) | -> `factureaza(N)` | Apelat din |
|---|---|---|
| `facturare_contracte(tcTip)` (`:119-136`) | `factureaza(2)`/`factureaza(6)`/`factureaza(52)` dupa `tcTip` | `ROAFACTURARE\Clase\ofundal_facturare.vc2:890-894`, `ROAFACTURARE\Ferestre\fundal.sc2:924`, **`ROACONTRACTE\Clase\ferestre_contracte.vc2:1542-1546`** (produs diferit, apel real) |
| `facturare_comenzi()` (`:139-141`) | `factureaza(3)` | `COMUN\clase\ocomenzi.vc2:1580-1596`, metoda `do_factura` — `SCATTER NAME goComanda MEMO` inainte de apel |
| `facturare_avize()` (`:144-146`) | `factureaza(4)` | `ROAFACTURARE\Clase\ofundal_facturare.vc2:905`, `ROAFACTURARE\Ferestre\fundal.sc2:932` |
| `copiere_factura(toFactura)` (`:150-153`) | `factureaza(toFactura.Tip, toFactura)` | `COMUN\clase\ofacturare_comun.vc2:3710` — fisierul de perimetrul #6, doar citit, neatins |
| `facturare_lista_de_preturi()` (`:114-116`) | `Do politica.mpr` -> popup-ul din `politica.mn2` (vezi tabelul de mai sus) | `ROAFACTURARE\Clase\ofundal_facturare.vc2:883`, `ROAFACTURARE\Ferestre\fundal.sc2:920` |
| `emitere_aviz_clienti(tnTip)` (`:198-228`) | `factureaza(21/22/26/24)` dupa `tnTip=1/2/3/7`; `tnTip=4,5,6` -> `initializeaza_vanzare_din_stoc`, nu `factureaza` | `ROAFACTURARE\Meniuri\aviz_clienti.mn2:21,47,51,55,59,63,67` |
| `emitere_aviz_clienti_debitori(tnTip)` (`:230-243`) | `factureaza(28/29)` dupa `tnTip=1/2` | `ROAFACTURARE\Meniuri\aviz_clienti_debitori.mn2:22,26` |
| `emitere_aviz_clienti_custodie(tnTip)` (`:244-259`) | `factureaza(42/47)` dupa `tnTip=1/2`; `tnTip=3` -> `initializeaza_vanzare_din_stoc` | `ROAFACTURARE\Meniuri\aviz_clienti_custodie.mn2:30,34,38` |
| `emitere_aviz_transfer(tnTip)` (`:261-280`) | `factureaza(25/23/27/30/41)` dupa `tnTip=1..5` | `ROAFACTURARE\Meniuri\aviz_subunitati.mn2:36,40,44,48,52` |
**Total: ~33 puncte de intrare reale in cod pentru `factureaza`, doar unul pentru `factureaza2`**
(`ofacturare.prg:90`, in interiorul lui `factureaza`, gatit de `gnFacturareNou=1` + confirmare DA la
`AMESSAGEBOX`).
## 4. Rolul lui `gnFacturareNou`
**Nu e un comutator de productie — e un switch de dezvoltator, fara nicio persistenta.**
- **Declarat:** nicaieri. Cautare exhaustiva (`gnFacturareNou`, tot `D:\ROA`, extensii
`.prg/.vc2/.sc2/.mn2`) — singura aparitie e exact linia care il citeste (`ofacturare.prg:88`, sau
liniile echivalente in fiecare copie de produs). Nu exista intr-un ecran de optiuni, nu e coloana
intr-un tabel de configurare (spre deosebire de `gnScadereStoc`, `gl406` etc., citite din
`oinit_optiuni.prg`).
- **Citit:** o singura data, `ofacturare.prg:88` — `If Type('gnFacturareNou') = 'N' And
m.gnFacturareNou = 1`.
- **Valoare implicita:** inexistenta ca variabila -> `Type()` intoarce `'U'` (Undefined), deci
conditia e falsa implicit. Devine `.T.` doar daca cineva a facut manual, in sesiunea VFP curenta
(de regula din fereastra de comenzi a IDE-ului), `gnFacturareNou = 1`.
- **Cine il seteaza:** nimeni, in cod. E gandit sa fie setat manual de un dezvoltator care vrea sa
testeze prototipul.
- **Confirma sau infirma ca e comutator intre proceduri, nu intre formulare?** Azi, chiar intre
**proceduri** — `factureaza:88-93` face `Return factureaza2(m.tnTip)` dupa raspunsul DA la dialog,
adica sare complet in cealalta procedura, cu tot ce lipseste din ea (sectiunea 2). Planul (S2,
linia 1423) vrea sa-l transforme in comutator **intre formulare**, in interiorul unei singure
proceduri — schimbare corecta, pentru ca azi dubleaza cod, nu doar UI.
## 5. Proiectarea unificarii
### Semnatura
**Neschimbata la aceasta poveste:** `factureaza(tnTip, toFactura)`. Parametrul de sursa explicit
(comanda/contract) e treaba lui **S3c**, care depinde de S2 si il trateaza separat — nu se amesteca
aici (planul insusi spune "Atinge cod comun intregii suite — nu se face impreuna cu alta
modificare").
### Ramificarea interna
Nu e nevoie de o rescriere — `factureaza` de azi e deja versiunea completa si functionala. Interventia
e chirurgicala:
1. **Muta verificarea `gnFacturareNou`** de la inceputul procedurii (azi `:88-93`, unde face
`Return factureaza2(...)`) la **punctul unde se alege `lcObject`** pentru formularul de articole
(azi `:395` in `factureaza`, ramura corespunzatoare celei din `factureaza2:929`):
```
Case tnTip = 27
lcObject = [frm_avizare_lucrare]
Otherwise
lcObject = IIF(m.llFacturareNoua, [frm_facturare_articole2], [frm_facturare_articole])
Endcase
```
unde `llFacturareNoua` e calculat **o singura data**, la fel ca azi (`Type('gnFacturareNou')='N'
And gnFacturareNou=1`, urmat de acelasi `AMESSAGEBOX` DA/NU), dar **fara** `Return` in alta
procedura — doar seteaza flagul si continua executia normala.
2. **Tot restul ramane identic** — cursorul de articole, `eProforma`, `codmatc`/`codnc8`/`codcpv`,
`GetInstitutiePublica`, `GetSoldClient`, filtrul `RORTC`, rutarea tipurilor 51/52/23/24, ramura
`llCopiere`/`toFactura` — pentru ca acestea nu au nicio legatura cu care formular de articole se
deschide. `factureaza2` nu avea o varianta "corecta, dar diferita" a acestor blocuri — pur si
simplu nu le avea, pentru ca fusesera dezactivate in graba la prototipare (`If .F.` peste tot),
nu pentru ca noul formular ar avea nevoie de alt comportament acolo.
3. **Sterge `factureaza2`** in intregime (`:583-1082`, inclusiv bannerul `INCEPUT`/`SFARSIT`).
### Ordinea, ca suita sa nu fie stricata niciun moment
Riscul de "moment in care suita e stricata" e mic aici, spre deosebire de S3c — pentru ca
`factureaza2` **nu are alt apelant decat el insusi prin `factureaza`**. Ordinea propusa:
1. Muta logica de selectie a lui `lcObject` (pasul 1 de mai sus) **inainte** de a sterge
`factureaza2` — la acest punct, codul are temporar ambele cai active (comutatorul nou +
`factureaza2` inca existent, dar orfan). Se poate testa manual ca `gnFacturareNou=1` deschide
`frm_facturare_articole2` din interiorul lui `factureaza`, cu restul comportamentului corect
(spre deosebire de azi, unde deschiderea prin `factureaza2` sare peste `eProforma`, `codnc8` etc.)
2. Abia dupa verificare, **sterge `factureaza2`** — pasul e sigur pentru ca nu mai are niciun apelant
(linia 90, singurul, a fost deja inlocuita la pasul 1).
3. **Un singur fisier se schimba**: `COMUN\programe\ofacturare.prg`. Niciun apelant din tabelul de
la punctul 3 nu are nevoie de nicio modificare — toti cheama `factureaza(N)` cu aceeasi semnatura,
niciunul nu cheama `factureaza2` direct.
4. **ROACONTRACTE nu necesita nicio schimbare** la aceasta poveste — apelul lui la
`facturare_contracte` -> `factureaza(2/6/52)` nu atinge `factureaza2` deloc.
## 6. Riscurile de regresie
- **Cel mai probabil sa se rupa:** ramura `llCopiere`/`toFactura`, pentru ca e cea mai complexa
bucata de logica prezenta doar in `factureaza` (`cursor_retur_document`, apoi suprapunerea cu
`cursor_preturi` pentru a permite adaugarea de articole noi peste cele copiate, `:454-474`). Nu
are nicio acoperire azi in `factureaza2` de comparat — deci orice greseala de copy-paste la mutarea
liniei `lcObject` risca sa rupa exact aceasta ramura, nu partea "noua". **Testat manual din
`copiere_factura` (`ofacturare_comun.vc2:3710`) inainte si dupa.**
- **Al doilea cel mai probabil:** rutarea tipurilor 23/24/51/52, pentru ca azi cad in `Do Case`-uri
diferite intre cele doua proceduri (sectiunea 2) — o gresala de aliniere la stergerea lui
`factureaza2` nu ar afecta functional (pentru ca acele Do Case-uri raman neatinse, doar duplicate
disparute), dar merita o trecere vizuala pe fiecare tip listat la punctul 3 dupa modificare.
- **ROACONTRACTE** — singurul apelant real din alt produs. **Nu poate fi testat din arborele de lucru
ROAFACTURARE** — cere un build/rulare separata a ROACONTRACTE, cu propriul `ofacturare.prg`
sincronizat manual (nu e acelasi fisier fizic, e o copie). Verificarea "gata" trebuie sa includa
explicit macar un test manual pe ROACONTRACTE, tip contract-factura, nu doar pe ROAFACTURARE.
- **`frm_facturare_articole2` insusi ramane netestabil functional** la aceasta poveste — `Init`-ul
lui nu completeaza antetul (S1), deci deschiderea cu `gnFacturareNou=1` va arata un formular cu
campuri goale la fel ca azi. Nu e o regresie introdusa de S2, dar e o asteptare de gestionat: S2
**nu** face `frm_facturare_articole2` utilizabil, doar elimina procedura duplicata din spatele lui.
- **Nimic din asta e testabil headless** — toate cele ~33 puncte de intrare sunt declansate din
meniuri/formulare UI (`ON SELECTION BAR`, `DO ... IN ...` din metode de clic), iar rezultatul se
verifica prin `ACT`/`VANZARI`/`RUL` in Oracle sau vizual pe formular. Testarea de dupa modificare
trebuie sa fie manuala, minim pe: o factura lista de preturi lei (tip 1), un aviz din comanda (tip
21), o factura din contract lei (tip 2, din `ROACONTRACTE` daca fezabil), o copiere de factura
(`toFactura` populat), si — cu `gnFacturareNou=1` setat manual — confirmarea ca
`frm_facturare_articole2` se deschide fara eroare (nu neaparat ca arata corect, asta e S3).
## 7. Criteriul de "gata", rescris si verificabil
Planul spune azi: *"`factureaza2` nu mai exista, iar comutatorul alege formularul, nu procedura."*
Rescris, verificabil fara ambiguitate:
1. **Structural** (verificabil cu o comanda, fara sa citesti codul):
```
Get-ChildItem -Recurse 'D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare.prg' |
Select-String -Pattern '^\s*(PROCEDURE|FUNCTION)\s+factureaza2\b'
```
trebuie sa intoarca **zero rezultate**.
```
Select-String -Path 'D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare.prg' -Pattern 'gnFacturareNou'
```
trebuie sa intoarca **exact o aparitie**, situata in interiorul lui `factureaza`, la punctul unde
se alege `lcObject` (nu inainte de constructia lui `poDate`, ca azi).
2. **Comportamental**, testat manual, fara `gnFacturareNou` setat (calea implicita, majoritatea
utilizatorilor): cele patru documente listate la sectiunea 6 (factura lista de preturi, aviz din
comanda, factura din contract, copiere factura) produc aceleasi randuri in `ACT`/`VANZARI`/`RUL`
ca inainte de modificare.
3. **Comportamental**, cu `gnFacturareNou = 1` setat manual si DA la dialog: se deschide
`frm_facturare_articole2` (nu `frm_facturare_articole`), fara eroare la deschidere, cu
`eProforma`/`codnc8`/`codcpv`/`GetInstitutiePublica`/`GetSoldClient`/filtrul RORTC/rutarea
51-52-23-24 toate active identic cu calea implicita (spre deosebire de azi, cand
`gnFacturareNou=1` le sarea pe toate).
4. **ROACONTRACTE**: minim un test manual de facturare din contract, dupa sincronizarea
`ofacturare.prg` in acel produs, confirma acelasi rezultat ca inainte.
## Ce nu s-a putut stabili si de ce
- **Cine activeaza popup-urile `politica.mn2`/`contracte.mn2`** (nivelul chiar deasupra apelurilor
directe la `factureaza(N)`) nu a fost trasat pana la butonul/grid-ul exact — nu era necesar pentru
diff-ul procedurilor sau pentru tabelul de apelanti (punctul de interes e apelul la `factureaza`,
nu inca un nivel de indirectare deasupra), dar daca implementarea muta si aceste meniuri, merita
o trecere separata.
- **Daca alte produse (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB, ROACONT) au propriul lor
`oproceduri_facturare.prg`/`ocomenzi.vc2` invocat din cod specific produsului, dincolo de ce am
cautat** — cautarea a acoperit exact numele `factureaza`/`factureaza2` si cei noua wrapperi din
`oproceduri_facturare.prg`; nu a acoperit eventuale cai complet diferite catre acelasi fisier
(de exemplu un meniu `.mn2` specific unui alt produs care ar chema un wrapper cu alt nume). Pentru
aceste cinci produse, cautarea directa (`factureaza(`, cei noua wrapperi) a dat zero rezultate in
codul specific produsului — suficient pentru concluzia "nu apeleaza azi", dar nu e o dovada
negativa absoluta de "nu ar putea apela niciodata prin alt canal".
- **Testarea reala pe ROACONTRACTE** nu a fost efectuata (read-only, fara rulare de cod) — doar
confirmata existenta apelului in sursa (`ferestre_contracte.vc2:1542-1546`).
- **Diferenta minora de text** intre mesajele "Luna inchisa" ale celor doua proceduri
(`factureaza:105` vs `factureaza2:594`, un caracter diacritic afisat diferit la citire) nu a fost
investigata mai departe — pare artefact de encoding la citire (cp1250 in fisier), nu o diferenta
de continut intentionata; oricum dispare odata cu stergerea lui `factureaza2`.
## Bug-uri semnalate, nereparate
- **`factureaza2` e, in forma actuala, non-functional dincolo de deschiderea formularului de
antet dezactivata** — chiar daca cineva ar apela azi acest cod pe o cale ipotetica ocolind
`factureaza`, executia interogarii SQL e dezactivata (`lnSucces = 1` hardcodat la `:827`, fara
`goExecutor.oExecute`) si verificarea "nu exista articole" e dezactivata (`:838`) — codul ar merge
mereu pe ramura de succes cu un cursor `crsarticole` nepopulat de aceasta rulare (posibil ramas
dintr-un apel anterior, cu alt `tnTip`). Nu se repara aici — S2 il sterge oricum, deci bug-ul
dispare odata cu procedura, nu prin fix separat.
- Niciun bug nou gasit in `COMUN\clase\ofacturare_comun.vc2` sau `COMUN\programe\ofacturare_editare.prg`
(perimetrul #6) — singura atingere a acestor fisiere in aceasta cercetare a fost citirea liniei
`copiere_factura` din `ofacturare_comun.vc2:3710`, care nu are nimic suspect.

View File

@@ -1,478 +0,0 @@
# Cercetare — proiectare S3: portarea logicii de antet in formularul unificat
Investigatie READ-ONLY pentru povestea **S3** din `docs\plan_13_unificare_formular_facturare.md:1428-1439`.
Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit. Constrangerile din briefing —
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` doar citite (perimetrul
#6), `pack_facturare` si `pack_facturare_comun`-ul intern neatinse, analiticele read-only — respectate.
## Verdict (5-10 randuri)
S3 e **mai mare decat inventarul din plan sugereaza pe numar de metode** (29 `do_cauta_*` reale, nu
30 — vezi punctul 1), dar **mult mai mare decat sugereaza pe complexitate reala**, pentru doua motive
descoperite aici, nu presupuse: (a) `Init`, `inainte_de_do_termin` si — partial — `do_cauta_fdoc` sunt
**omonime cu semantica total diferita pe toate cele patru formulare-sursa** (antet vs. articole vs.
alte-date), deci portarea "o singura data" cere de fapt patru fuziuni de metoda, nu una singura cu
ramificare; (b) bug-ul **#16 e real, reprodus pe cod pana la linia exacta**, si **nu e un bug de focus**
— e o consecinta a faptului ca bucla de reincercare din `factureaza`/`factureaza2`
(`ofacturare.prg:174-571`) trateaza **orice esec SQL** (nu doar cursul lipsa) ca pe un "DA, continui cu
alt document", redeschizand din senin formularul de antet de la zero. Unificarea **nu-l reproduce
automat** — poate chiar sa-l elimine ca efect secundar, daca antetul nu se mai reconstruieste de la
Init dupa un esec Oracle in pasul urmator. Obstacolul principal pentru implementare nu e portarea
codului de validare (majoritatea e `Do Case`/`amessagebox` mecanic), ci **reconcilierea a patru cicluri
de viata `Init`/`inainte_de_do_termin` independente intr-unul singur**, cu ordinea de dependente de la
punctul 5.
---
## 1. Inventarul metodelor de portat — numere corectate
Sursa: `vfp_symbols.ps1 -Class frm_date_factura` / `-Class frm_date_aviz`, confirmat pe fisierul real
`COMUN\clase\ofacturare.vc2` (nu `.bak`).
**`frm_date_factura`** (`ofacturare.vc2:8482-9869`) — **16** metode `do_cauta_*`, nu 17:
| Metoda | Linii | Ce face | Echivalent in `frm_facturare_articole2` |
|---|---|---|---|
| `do_cauta_altele` | `8940-8961` (21) | cautare pe campul cu eticheta dinamica ("Altele"/contract/comanda/etc.) | nu |
| `do_cauta_avize` | `8963-8999` (36) | cautare aviz-sursa (tip=4) | nu |
| `do_cauta_client` | `9001-9060` (59) | cautare client, populeaza sold/cod fiscal | nu |
| `do_cauta_comanda` | `9062-9090` (28) | cautare comanda-sursa | nu |
| `do_cauta_contract` | `9092-9153` (61) | cautare contract-sursa | nu |
| `do_cauta_factura` | `9155-9171` (16) | cautare factura-sursa (credit note, tip 7) | nu |
| `do_cauta_facturi` | `9173-9212` (39) | cautare multi-factura (retur, tip 8/9) | nu |
| `do_cauta_fdoc` | `9214-9225` (11) | cautare generica fel-document, `caut_fdoc()` — **identica byte-cu-byte cu varianta din `frm_date_aviz`**, vezi punctul 4 | nu |
| `do_cauta_gestiune_init` | `9227-9242` (15) | cautare gestiune sursa | nu |
| `do_cauta_locatii` | `9244-9253` (9) | cautare locatie (tip 45) | nu |
| `do_cauta_lucrare` | `9255-9279` (24) | cautare lucrare | nu |
| `do_cauta_responsabil` | `9281-9301` (20) | cautare responsabil | nu |
| `do_cauta_sectie` | `9303-9342` (39) | cautare sectie | nu |
| `do_cauta_valuta` | `9344-9359` (15) | cautare valuta, muta focus pe `clb_zi_curs` daca exista, altfel pe serie (`:9351-9356`, deja conditionat `Type(...)<>'U'`) | nu |
| `do_cauta_venchelt` | `9361-9391` (30) | cautare venit/cheltuiala | nu |
| `do_cauta_venit` | `9393-9394` (1) | stub/alias, o linie — de verificat ce mai apeleaza | nu |
| `do_schimba_tipdoc` | `9396-9438` (42) | vezi punctul 6 — schimba `poDate.nIdTipDoc` si reinitializeaza serie+numar | **nu** — apelat orfan din prototip, vezi punctul 3 |
| `do_verifica` | `9440-9453` (13) | verificare ANAF pe cod fiscal client | nu (dar exista pe `frm_facturi`, aceeasi semantica) |
| `inainte_de_do_termin` | `9455-9561` (106) | validare completa antet inainte de trecerea la articole | **omonim cu semantica diferita** pe toate 4 formulare, vezi mai jos |
| `Init` | `9563-9796` (234) | vezi punctul 5 | **omonim cu semantica diferita**, vezi mai jos |
| `Clb_dataact...LostFocus` | `9798-9813` (15) | sincronizeaza `zi_curs=dataact` daca `clb_zi_curs` exista | nu |
| `Clb_dataireg...LostFocus` | `9815-9831` (16) | idem, pe `dataireg` | nu |
| `Clb_nract...LostFocus` | `9833-9835` (2) | — | nu |
| `Clb_serie_act...LostFocus` | `9837-9844` (7) | — | nu |
| `Ct_clb_fdoc._combobox1.Init` | `9846-9855` (9) | populeaza combo-ul FACTURA/PROFORMA/BON FISCAL | nu |
| `Ct_clb_fdoc._combobox1.LostFocus` | `9857-9859` (2) | `thisform.do_schimba_tipdoc()` — vezi punctul 6 | nu (frm_facturare_articole2 are un apel similar, dar **orfan**, vezi punctul 3) |
| `Ed_tx_simplu1._EDBASE1.KeyPress` | `9861-9867` (6) | — | nu |
**`frm_date_aviz`** (`ofacturare.vc2:6566-7618`) — **13** metode `do_cauta_*`, numarul din plan e corect:
| Metoda | Linii | Observatie |
|---|---|---|
| `do_cauta_altele` | `6882-6894` (12) | mai scurta decat pe factura (21) — set de tipuri mai mic |
| `do_cauta_avize` | `6896-6930` (34) | apropiata ca marime de factura (36) |
| `do_cauta_client` | `6932-7009` (77) | **mai lunga** decat pe factura (59) — trateaza eticheta variabila "Retur de la"/"Gestiune sursa" pe transfer |
| `do_cauta_comanda` | `7011-7034` (23) | |
| `do_cauta_contract` | `7036-7091` (55) | |
| `do_cauta_fdoc` | `7093-7104` (11) | **identica cu factura**, vezi punctul 4 |
| `do_cauta_gestiune_dest` | `7106-7131` (25) | **doar pe aviz** — gestiune destinatie la transfer subunitati |
| `do_cauta_gestiune_init` | `7133-7144` (11) | mai scurta decat pe factura (15) |
| `do_cauta_lucrare` | `7146-7165` (19) | |
| `do_cauta_politica` | `7167-7182` (15) | **doar pe aviz** — `Ct_clb_politici_preturi` |
| `do_cauta_responsabil` | `7184-7204` (20) | aceeasi lungime ca factura |
| `do_cauta_sectie` | `7206-7240` (34) | |
| `do_cauta_venchelt` | `7242-7275` (33) | |
| `inainte_de_do_termin` | `7277-7352` (75) | **omonim**, vezi mai jos — mai scurta decat factura (106): fara validare data-scadenta, fara validare valuta, fara validare client (!) |
| `Init` | `7354-7600` (247) | **omonim**, nu citit integral aici (doar `9563-9650`+`9733-9796` echivalentul pe factura) |
| `Clb_dataact...LostFocus` | `7602-7605` | |
| `Clb_dataireg...LostFocus` | `7607-7612` | |
| `Clb_nract...LostFocus` | `7614-7616` | |
**`frm_date_aviz` nu are `do_schimba_tipdoc`** — confirmat, container-ul `Ct_clb_fdoc` de pe aviz e
o cautare simpla, fara combo si fara evenimentul `LostFocus` care declanseaza schimbarea de tip.
**Total real: 16 + 13 = 29 metode `do_cauta_*`**, plus `do_schimba_tipdoc` (doar factura), `do_verifica`
(doar factura), `inainte_de_do_termin` si `Init` (omonime pe ambele) — planul le numara global corect
ca "17+13 ... plus restul", dar cifra "17" pe factura e gresita cu o unitate: **16**.
---
## 2. Metodele omonime — capcana centrala a portarii
**Nivelul care conteaza cel mai mult, necunoscut inca in plan**: `Init` si `inainte_de_do_termin` sunt
**acelasi nume de metoda pe toate cele patru formulare-sursa implicate in unificare**
(`frm_date_factura`, `frm_date_aviz`, `frm_facturare_articole`/`frm_facturare_articole2`,
`frm_alte_date`), fiecare cu corp complet diferit (antet vs. compunere articole vs. date suplimentare).
O singura clasa unificata nu poate avea patru metode `Init`. **Portarea "o singura data" a S3 inseamna
concret: patru corpuri de `Init` si patru corpuri de `inainte_de_do_termin` trebuie topite intr-un
singur `Init` si un singur `inainte_de_do_termin` (sau redenumite si inlantuite explicit)** — asta e
efortul real, nu doar mutarea codului de validare camp-cu-camp.
**`inainte_de_do_termin`, factura vs. aviz — diferenta reala, cu citat** (comparate direct, ambele corpuri
citite integral, `ofacturare.vc2:9455-9561` si `:7277-7352`):
- **Factura valideaza `id_client` obligatoriu; aviz nu valideaza deloc acest camp**:
```
ofacturare.vc2:9510-9513 (doar pe factura)
Case Empty(poDate.id_client) Or Isnull(poDate.id_client)
amessagebox("Nu ati ales clientul!",48,"Atentie")
This.ct_clb_nume_client.SetFocus()
```
Motiv plauzibil (neconfirmat mai departe): avizele de transfer intre subunitati (tip 23/25/30/41) nu
au neaparat un "client" — au o gestiune destinatie, validata separat.
- **Factura valideaza scadenta si valuta; aviz nu are deloc aceste `Case`-uri** — `Empty(poDate.datascad)`,
`poDate.datascad<poDate.dataact`, `poDate.in_valuta=1 And Empty(zi_curs)`, `poDate.in_valuta=1 And
Empty(id_valuta)` (`:9471-9493`) — aviz nu are control de valuta pe formular (S1, randul "Valuta").
- **Aviz valideaza politica de preturi; factura nu**:
```
ofacturare.vc2:7334-7337 (doar pe aviz)
Case Empty(Nvl(poDate.id_pol,0)) And InList(poDate.tip,23,41)
amessagebox("Nu ati ales politica de preturi!",48,"Atentie")
```
- **Setul de tipuri si mesaje pentru "sursa neselectata" (`listaid`) e complet diferit**: factura
`!Inlist(poDate.tip,1,5,7,8,9,10,48,49)` cu mesaje contract/comanda/aviz/locatie (`:9528-9541`); aviz
`Inlist(poDate.tip,21,23,25,26,28,41,42,47)` cu mesaje comanda/gestiune-destinatie/gestiune-retur/contract
(`:7318-7333`) — seturile de `tip` nu se suprapun deloc (factura = tipuri <21 sau in {45,48,49,51,52};
aviz = tipuri >=21 in afara de 27/30). **Discriminatorul natural pentru ramificare exista deja**:
`poDate.nIdTipDoc` (5=FACTURA, 6=AVIZ — calculat de apelant la `ofacturare.prg:192-196`) sau simpla
apartenenta a lui `poDate.tip` la unul din cele doua seturi disjuncte, **nu** un flag nou.
- **Contul folosit la verificarea de facturi duplicate difera**: `facturi_duplicate('4111', 0, ...)`
(factura, `:9545`, cont clienti) vs. `facturi_duplicate('418', 0, ...)` (aviz, `:7341`, alt cont) —
un literal hardcodat care trebuie sa devina el insusi parte din ramificare, nu doar mesajul.
**Alte omonime cunoscute, cu semantica diferita, relevante pentru zona din jurul lui S3** (nu introduse
aici — reconfirmate, vezi si `docs\handoff_13_formular_unificat.md:192-194`):
- `do_calculeaza_discount` — **la nivel de document** pe `frm_facturare_articole` (`:13405-13424`) si
`frm_facturare_articole2` (`:17670-17686`, verificat aici: identic ca semantica, scrie
`Thisform.ndiscfactron`/`ndiscfactval`); **la nivel de linie** pe `frm_articol_factura` (`:1874-1976`,
scrie `poArticol.discount_unitar*`). Nu intra direct in S3 (nu e printre metodele antetului), dar
orice cod nou care apeleaza `do_calculeaza_discount` prin nume pe formularul unificat trebuie sa
stie care semantica o vrea.
- `do_verifica` — pe `frm_date_factura` (`:9440-9453`, verificare ANAF) si pe `frm_facturi`
(verificare ANAF, aceeasi semantica) — omonim inofensiv, aceeasi actiune.
- `do_cauta_gestiune_init` / `do_cauta_responsabil` / `do_cauta_sectie` / `do_cauta_venchelt` /
`do_cauta_altele` / `do_cauta_comanda` / `do_cauta_contract` / `do_cauta_avize` / `do_cauta_lucrare` —
**acelasi nume pe factura si aviz, lungimi diferite** (vezi tabelele de la punctul 1) — nu omonime
periculoase (aceeasi intentie: cautare pe camp), dar **nu identice** — portarea trebuie sa ia corpul
fiecareia separat, nu sa presupuna ca sunt duplicate de sters.
- **Singura pereche confirmata identica byte-cu-byte**: `do_cauta_fdoc` (vezi punctul 4) — aceasta
chiar poate fi portata o singura data, fara ramificare.
---
## 3. Ce e deja in prototip vs. ce lipseste
`frm_facturare_articole2` (`ofacturare.vc2:15741-19355`) are controalele de antet montate direct pe
formular (confirmat in `inventar_controale_formulare.md`), dar **zero logica**:
| Metoda/mecanism | Exista in `frm_facturare_articole2`? | Detaliu |
|---|---|---|
| Toate cele 29 `do_cauta_*` | **NU** | niciunul in lista de metode proprii a clasei (confirmat cu `vfp_symbols.ps1 -Class`) |
| `do_schimba_tipdoc` | **NU, dar e APELAT** — cod orfan | `clb_fdoc.cboFdoc.Valid` (`:19255-19257`) contine `thisform.do_schimba_tipdoc()`, dar clasa **nu are** aceasta metoda in lista proprie. Daca userul ar schimba azi combo-ul `clb_fdoc` pe acest formular mort, ar cadea cu eroare de metoda inexistenta. Confirma independent constatarea din plan ("controalele sunt acolo, logica nu") — de fapt e mai rau: e cod care ar crapa daca s-ar activa calea, nu doar cod lipsa |
| `inainte_de_do_termin` | **DA, dar cu alta semantica** | `:18809-18986` (178 linii) — valideaza ARTICOLE (stoc, discount, TVA), nu antetul; e omonimul de care vorbeste punctul 2, nu un candidat de reutilizare pentru validarea de antet |
| `Init` | **DA, dar cu alta semantica** | `:18988-19080` (92 linii, citit integral aici) — confirmat: doar grid/curs-label/total-mode/coloane in/afara valuta; **niciun** `poDate.xxx -> control.Value`. Nu populeaza antetul deloc |
| Serie/numar (`Clb_serie_act1`/`Clb_nract`) | Controale prezente, dar fara wiring de populare in `Init` | `Clb_serie_act1.TEXT_SIMPLU1.LostFocus` (`:19263-19270`) exista ca metoda proprie — nu verificat aici daca reproduce `genereazanumar` |
**Concluzie punctul 3**: prototipul e o **coaja vizuala**, nu un schelet de 50% functional. Ce trebuie
scris pentru S3 e efectiv tot codul de comportament — controalele economisesc timpul de aranjare in
`.scx`/`.vcx`, nu timpul de portare a logicii.
---
## 4. Ramificarea factura / aviz in interior
**Discriminatorul de ramificare recomandat: `poDate.nIdTipDoc`** (5=FACTURA, 6=AVIZ), deja calculat de
apelant inainte de `Createobject` (`ofacturare.prg:187-196`) si deja disponibil pe `poDate` in
momentul in care formularul unificat ar porni `Init`. Alternativ, acelasi rezultat se obtine testand
direct apartenenta lui `poDate.tip` la unul din cele doua seturi disjuncte folosite azi in
`inainte_de_do_termin` (vezi punctul 2) — cele doua seturi nu se suprapun niciodata, deci nu exista
ambiguitate. **Nu e nevoie de o proprietate noua** gen `lEsteAviz` — `nIdTipDoc` face deja treaba, si
e deja pe obiectul `poDate` care trece prin tot lantul de apeluri.
Ce trebuie sa ramifice concret, cu dovada:
1. **`inainte_de_do_termin`** — Cases specifice fiecarei parti (vezi citatele de la punctul 2), plus
constanta de cont (`'4111'` vs `'418'`) la `facturi_duplicate`.
2. **`Init`** — seturile `Do Case poDate.tip` care decid ce se elimina (`RemoveObject`) difera pe
fiecare parte (S1 documenteaza deja fiecare camp cu conditia lui de vizibilitate; portarea trebuie
sa pastreze fiecare conditie identic, nu sa le generalizeze pe ghicite).
3. **`do_schimba_tipdoc`** — **exista doar pe factura**; pe aviz nu exista mecanismul de schimbare a
tipului de document dupa deschiderea formularului (containerul `ct_clb_fdoc` de pe aviz e cautare
simpla, fara `_combobox1`). Ramificarea aici nu e "cod diferit pe aceeasi metoda" ci "metoda
prezenta doar pe o ramura" — formularul unificat trebuie sa decida daca aviz capata acest
comportament nou (schimbare tip dupa deschidere) sau ramane fara el, ca azi.
4. **`do_cauta_fdoc`** — **nu ramifica**, e identic (punctul 1) — poate fi portat o singura data, fara
`If nIdTipDoc=...`.
5. **`do_cauta_client`** — aviz e cu 18 linii mai lung (77 vs 59) pentru eticheta variabila
"Retur de la"/"Gestiune sursa" pe transfer (S1, randul "Client") — de citit integral la
implementare pentru ramificarea exacta, nu verificat linie-cu-linie aici.
---
## 5. `Init`-urile — ce face fiecare, in ce ordine, ce depinde de ce
**Secventa de azi, pe drumul normal (factura din lista de preturi, fara eroare Oracle):**
1. **Apelantul** (`ofacturare.prg:factureaza`, inainte de orice `Createobject`): construieste
`poDate = Createobject("oDateFactura", ...)` si `poGeneratorNumere = Createobject("oGeneratorNumere")`
(`:184-185`), seteaza `poDate.nIdTipDoc` (`:187-196`), cheama
`poDate.completeaza_setari_document(...)` daca e copiere (`:200-206`), apoi
`poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)`
(`:208-211`) — **toate acestea ruleaza inainte ca vreun formular sa existe**. Orice `Init` de mai jos
presupune `poDate`/`poGeneratorNumere` deja populate.
2. **`frm_date_factura.Init`** (`:9563-9796`, 234 linii, citit integral aici) — `DoDefault()`, apoi
masoara inaltimile containerelor de antet intr-un array (`laPozitii`), decide pe `Do Case
gnScadereStoc/poDate.tip` ce containere elimina (`RemoveObject`) si reface layout-ul pe verticala,
initializeaza serie+numar (`.clb_serie_act.do_initializeaza(poDate.rezultat_serii)`, `:9737`), si
**la final** seteaza focus: pe `ct_clb_valuta` daca documentul e in valuta si campul valutei apare
deasupra tipului si nu are inca valuta aleasa, **altfel pe `ct_clb_fdoc`** (`:9788-9793`, citat
integral la punctul 6). Depinde de: `poDate` (tip, in_valuta, ...), `poGeneratorNumere`
(`creeaza_cursor_serii` deja rulat de apelant), variabile globale de firma (`gnScadereStoc`).
3. **`frm_facturare_articole2.Init`** (`:18988-19080`, 92 linii, citit integral aici) — confirmat:
**nu atinge niciun camp de antet**. Configureaza doar gridul (`nNrInregVizibile`), eticheta
cursurilor valutare, modul total (`gnModTotFact`), si elimina coloanele RON sau valuta din grid
dupa `poDate.in_valuta`. Depinde de: `poDate.zi_curs`/`in_valuta`/`tip`, `crscursuri` (populat de
apelant intre pasii 2 si 3, nu de acest `Init`).
4. **`frm_alte_date.Init`** (`ferestre_cere_date.vc2:3105-3207`, 103 linii, citit integral aici) —
`DoDefault()`, citeste `poDate.dataora_exp`; **daca nu e proforma**: cauta ultimul delegat/masina
folosite pentru client (apel Oracle `cauta_date_ultima_factura[_tip]`, `:3119-3136`), populeaza
combo-ul de casa dintr-un cursor Oracle nou (`v_nom_casa`, `:3156-3173`), seteaza `opt_incasat`
implicit daca `poDate.incasat<>0`; **daca e proforma**: elimina complet grupul delegat/masina/
incasare (`RemoveObject` in cascada, `:3192-3201`) si redimensioneaza formularul la zona de text
aditional. **Risc gasit, neurmarit mai departe**: pe eroare la interogarea combo-ului de casa,
`Init` cheama `poGeneratorNumere.dezaloca_numar(5)` si `Return` (`:3159-3161`) — acelasi tipar de
"dezalocare numar pe eroare SQL neasteptata" ca la bug-ul #16 (punctul 6), dar intr-un `Init`, nu
intr-o bucla — **nu s-a verificat daca are aceeasi consecinta de recreare completa a formularului**;
semnalat, nu investigat suplimentar aici din motive de buget de context.
**Ordinea de dependente, explicit**: `poDate`+`poGeneratorNumere` (apelant) -> `frm_date_factura`/
`frm_date_aviz.Init` (foloseste serie/numar deja create) -> **Oracle: cursorul de articole**
(`ofacturare.prg:266-308`, aici poate pica bug #16) -> `frm_facturare_articole2.Init` (foloseste
`crscursuri` populat intre pasi, nu de propriul `Init`) -> `frm_alte_date.Init` (face **inca** un apel
Oracle nou, cu propriul risc de dezalocare). **Pentru formularul unificat, aceasta secventa de patru
`Init`-uri trebuie sa devina un singur `Init` care ruleaza fazat** (o singura data la deschidere) —
sau ramane o discutie deschisa daca vreo faza (Oracle-lookup-ul din `frm_alte_date.Init`) ramane
intarziata pana la deschiderea sectiunii pliate, ca sa nu incarce round-trip-uri Oracle inutile cand
utilizatorul nu ajunge niciodata la acea sectiune.
---
## 6. Bug-ul #16 — mecanismul exact, verificat pe cod
**Text original** (`COMUN\docs\todos.txt:45`): *"la revenire din formularul de curs valutar, focusul
revine inainte de numar document, cred ca pe TIP DOCUMENT, si la iesire din serie se regenereaza numar
act, ceea ce este periculos daca utilizatorul l-a schimbat".*
**Verdict: NU e un bug de focus. E o bucla de reincercare care trateaza orice esec Oracle ca pe un "DA,
mai fac un document" si reconstruieste formularul de antet de la zero — focusul si regenerarea
numarului sunt doar simptomele vizibile ale acestei reconstructii.**
Lantul complet, verificat linie cu linie:
1. Utilizatorul completeaza antetul, apasa Termina — `inainte_de_do_termin` trece, formularul de antet
se inchide (`pnButon=1`).
2. Apelantul (`factureaza`, `ofacturare.prg:266-308`) construieste cursorul de articole din Oracle
(`cursor_preturi`/etc., functie de `poDate.tip`). **Daca cererea esueaza** (`lnSucces<0`,
`:313`) — inclusiv, dar nu numai, cu eroarea Oracle `-20005` "Nu este setat cursul..." (curs
valutar lipsa pentru o valuta din listele de preturi ale utilizatorului, verificata neconditionat
in `pack_facturare.verifica_cursuri_valute`, documentat deja in `zi_curs_validare.md`):
```
ofacturare.prg:313-322
If lnSucces < 0
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
If goExecutor.nEroare = 20005
vizualizeaza_curs(poDate.zi_curs) && deschide "formularul de curs valutar" (frm_curs, modal)
ENDIF
poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc) && numarul alocat se elibereaza
Else
... [singurul loc unde se cere "Doriti sa continuati?" si se seteaza lnRaspuns]
Endif
```
`vizualizeaza_curs` (`oproceduri_curs.prg:8-42`) e confirmat: deschide `Createobject("frm_curs",
tdDataCurs).Show(1)` — chiar formularul din reclamatie.
3. **Punctul central, verificat prin numararea `If`/`Else`/`Endif` din fisier**: prompt-ul "Doriti sa
continuati cu operatii de acest fel?" care seteaza variabila de bucla `lnRaspuns` **exista doar in
ramura `Else` a lui `If lnSucces<0`** (`ofacturare.prg:555-564`, in interiorul aceluiasi bloc
`If/Else/Endif` care se inchide abia la `:568`). **Pe ramura de eroare (`lnSucces<0`), `lnRaspuns`
nu e niciodata atins.** Isi pastreaza valoarea din intrarea in aceasta iteratie a buclei — pe primul
document, `6` (initializat la `:174`, inainte de `Do While lnRaspuns = 6` de la `:175`).
4. Bucla externa (`Do While lnRaspuns = 6 ... Enddo`, `:175-571`) **reintra automat**, fara nicio
intrebare catre utilizator, pentru ca `lnRaspuns` e inca `6`. In aceasta noua iteratie:
`poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)`
ruleaza din nou (`:208-211`), apoi se creeaza **o instanta noua** de `frm_date_factura`/`frm_date_aviz`
(`Createobject`, `:230`) si se arata modal (`:235`) — `poDate` insusi **nu** e recreat (ramane
acelasi obiect, cu `nract`/`serie_act` deja setate de utilizator), dar **formularul da**, deci
`Init` ruleaza de la capat.
5. `frm_date_factura.Init` (`:9563-9796`) reface layout-ul si, **la final, seteaza focus necondiționat
pe `ct_clb_fdoc`** (tip document) in cazul normal:
```
ofacturare.vc2:9788-9793
DO CASE
CASE poDate.in_valuta = 1 and this.ct_clb_valuta.Top < this.ct_clb_fdoc.Top AND EMPTY(NVL(poDate.nume_valuta,''))
this.ct_clb_valuta.SetFocus()
OTHERWISE
this.ct_clb_fdoc.SetFocus()
ENDCASE
```
**Aceasta e "focusul care revine pe TIP DOCUMENT"** din reclamatie — nu un bug izolat de focus, ci
comportamentul normal de `Init` al unui formular nou, aparut unde utilizatorul nu se astepta la un
formular nou.
6. Cand focusul paraseste `ct_clb_fdoc` (Tab, click in alta parte — orice), se declanseaza
`Ct_clb_fdoc._combobox1.LostFocus -> thisform.do_schimba_tipdoc()` (`:9857-9859`). Prima linie a
acestei metode, **inainte de orice verificare "tipul chiar s-a schimbat?"**:
```
ofacturare.vc2:9417 (in do_schimba_tipdoc, INAINTE de guard-ul de la :9419-9421)
This.clb_serie_act._cbbase1.LostFocus()
```
— apeleaza **neconditionat** handler-ul de LostFocus al combo-ului de serie, indiferent daca tipul
documentului s-a schimbat sau nu.
7. Acel handler (`serii_numere.vc2:138-140`, clasa `clb_serie_act`) cheama `genereazanumar` (`:114-136`):
```
serii_numere.vc2:122-127
If poGeneratorNumere.verifica_serie(This.nid_tipdoc) Or &lcValoare. = 0
lcValoare = lcValoare+[=]+ALLTRIM(Str(poGeneratorNumere.aloca_numar(This.nid_tipdoc,Null),20,0))
&lcValoare
This.Parent.Refresh()
Endif
```
— **aloca un numar nou** si il scrie peste `poDate.nract` prin macro, **necondiționat de ce numar
avea utilizatorul inainte**. **Asta e "iesirea din serie regenereaza numarul actului"** din
reclamatie — confirmat exact, pana la linia care face scrierea.
**Verdict pe intrebarea planului ("se rezolva #16 in S3, sau se reproduce bug-ul?"): se poate rezolva
in S3, cu un cost mic, dar nu e o consecinta automata a unificarii — trebuie tratat explicit.**
Cauza reala nu e in `frm_date_factura`/`do_schimba_tipdoc`/`clb_serie_act` (cod care se comporta
"corect" fata de contractul lui local), ci in bucla de reincercare din apelant
(`ofacturare.prg:174-571`), care **nu distinge "utilizatorul a confirmat ca vrea alt document" de
"cererea Oracle a esuat"**. Doua directii posibile, ambele in afara perimetrului strict al portarii de
antet, dar declansate direct de ea:
- **(a) minimal**: in formularul unificat, dupa un esec Oracle la pasul de construire a cursorului de
articole (echivalentul liniei `:313`), **nu se distruge formularul de antet** — antetul e deja o
singura sectiune persistenta a aceluiasi formular, nu un obiect separat recreat de apelant; simpla
arhitectura unificata (un `Init` per sesiune de emitere, nu per formular) elimina pasul 4 de mai sus,
deci elimina intreg lantul 5-7. **Acesta e motivul pentru care unificarea are sansa reala sa rezolve
#16 ca efect secundar** — dar numai daca implementarea nu recreeaza formularul intreg la reincercare
dupa eroare Oracle (ceea ce ar reproduce bug-ul identic, doar mutat).
- **(b) daca (a) nu e suficient** (de ex. daca dupa fix la curs tot trebuie relansata interogarea de
articole): `lnRaspuns` (sau echivalentul lui in noua arhitectura) trebuie resetat explicit pe orice
cale care iese din ramura de eroare, ca sa nu mai fie confundat cu "utilizatorul a spus DA".
**Coordonarea cu #6 (decizia 30 si constrangerea din briefing) — verificat direct pe diff-uri, nu doar
pe proza handoff-ului**: `docs\diff_s4_valuta_dialog*.patch` (trei fisiere) ating exclusiv
`COMUN\clase\omodificari.vc2`, `COMUN\programe\ofacturare_editare.prg` si un fisier de test —
**niciunul nu atinge `ofacturare.vc2` (unde traiesc `frm_date_factura`, `do_schimba_tipdoc`,
`clb_serie_act`) sau `ofacturare.prg` (unde traieste bucla `factureaza`)**. Confirmat si de continutul
lui handoff intermediar (sters): intreaga lui cercetare e despre `frm_articol_factura`
(dialogul de adaugare articol pe linie, alt fisier/clasa) si `frm_modific2024.cmdAdaugaArticol.Click`
— un mecanism de valuta **pe linie de articol**, fara nicio legatura cu antetul sau cu bucla de
emitere. **#6 nu a atins deloc lantul lui #16. Bug-ul e in intregime valabil si neschimbat.**
---
## 7. Ordinea de lucru propusa pentru S3
Pasi care lasa suita functionala dupa fiecare (calea veche ramane in productie pe tot parcursul —
niciun pas nu sterge `frm_date_factura`/`frm_date_aviz`/`frm_alte_date` originale):
1. **Fuzioneaza `Init`-urile de antet** (`frm_date_factura` + `frm_date_aviz`) intr-o metoda unica pe
formularul unificat, ramificata pe `poDate.nIdTipDoc` (punctul 4). Verificare: formularul unificat
se deschide pe fiecare din cele ~15 tipuri principale de `poDate.tip` folosite azi in cele doua
`Init`-uri, si arata exact aceleasi campuri vizibile/eliminate ca varianta veche — comparatie
camp-cu-camp fata de tabelul din `docs\S1_inventar_campuri_formular_unificat.md`.
2. **Porteaza cele 29 `do_cauta_*`** (fara ramificare unde sunt identice — `do_cauta_fdoc` — cu
ramificare pe `nIdTipDoc` unde difera). Verificare: fiecare cautare deschide acelasi dialog, scrie
aceleasi proprietati pe `poDate`, muta focusul identic cu azi.
3. **Fuzioneaza `inainte_de_do_termin`**, cu ramificarea documentata la punctul 2. Verificare: aceleasi
mesaje de eroare, in acelasi ordine, pentru aceleasi campuri goale, pe fiecare tip.
4. **Porteaza `do_schimba_tipdoc`** — decide explicit daca ramane doar-factura sau se extinde si pe
aviz (punctul 4, pct. 3). **Aici se rezolva sau nu #16** (punctul 6) — de facut ca parte a acestui
pas, nu separat, pentru ca schimbarea de arhitectura care il rezolva (un singur `Init` persistent)
e chiar cea care se construieste la pasul 1.
5. **Porteaza `but_modifica`/blocarea antetului** (decizia 9, deja proiectata in plan sectiunea I) —
depinde de pasii 1-4 fiind stabili, pentru ca reutilizeaza aceleasi controale.
6. **Integreaza bucla de emitere** (`factureaza`/`factureaza2`) cu noul formular unificat, tratand
explicit esecul Oracle de la cursorul de articole (punctul 6, directia a/b).
**Criteriul de "gata", rescris verificabil.** Azi planul spune "un document se emite integral din
formularul unificat, pe tip 1, cu acelasi rezultat in `vanzari`/`act`/`rul` ca pe calea veche" — fara
sa spuna cum se compara. Propunere concreta:
- Se emite **acelasi document** (acelasi client, articole, cantitati, preturi) o data pe calea veche
(`frm_date_factura` -> `frm_facturare_articole` -> `frm_alte_date`) si o data pe formularul unificat,
pe date de test identice, in aceeasi zi contabila.
- Se compara, randuri-cu-randuri, rezultatul in `VANZARI` (toate coloanele, nu doar sumele), `ACT`,
`RUL`, `DOCUMENTE`, `JV2007` (nota contabila) — cel mai simplu cu doua interogari identice filtrate
pe cei doi `ID_FACT`/`ID_VANZARE` rezultati, exportate si diff-uite text-cu-text (unealta: acelasi
export SQL folosit deja pentru `PACK_FACTURARE` in `docs\ff_*.sql`, adaptat pe `SELECT * FROM VANZARI
WHERE ID_FACT=...`).
- Diferentele **asteptate** (marcate ca OK, nu ca esec): `ID_VANZARE`/`ID_FACT`/timestamp-uri de
creare — orice altceva trebuie sa fie identic.
- Se repeta pentru **cel putin un tip de factura in valuta** (S3 atinge direct campurile de valuta) si
**un tip de aviz** (ramificarea de la punctul 4), nu doar tip 1.
---
## 8. Ce nu se poate testa headless din S3
Capcana cunoscuta (`COMUN\docs\depanare_testare_vfp.md`, memorie de proiect): sub harness `-A -T`,
coloanele de grid nu se materializeaza (`ColumnCount=0`, `RecordSource` raman artefacte necitite) —
relevant direct pentru `frm_facturare_articole2` (grid-ul de compunere), dar S3 propriu-zis nu atinge
gridul.
Specific pentru S3 (antet):
- **Toate cele 29 `do_cauta_*`** deschid un dialog modal de cautare (`caut_ora.vcx`/similare) —
interactiunea reala de selectare dintr-o lista si `Show(1)` modal nu se poate simula headless; se
pot testa doar efectele **dupa** ce `poCauta`/rezultatul e construit manual (tiparul deja folosit in
`test_pret_cu_tva_dialog.prg`/`test_adauga_linie_valuta.prg` mentionat in
handoff intermediar (sters), punctul 7) — apel direct al metodei cu un obiect
simulat, fara `Show()`.
- **`Init`-ul complet** (redimensionare, `RemoveObject` in cascada, repozitionare containere) e
verificabil pe proprietati (`.Visible`, `.Height`, existenta obiectului dupa `Type(...)`) fara UI
vizibil, pentru ca `Createobject` ruleaza `Init` fara `Show()` — tiparul e deja validat in codebase.
- **Focusul si secventa reala de `LostFocus`** (exact ce a produs bug #16) **nu se poate reproduce
headless** — necesita fie `vfp_ui_harness.ps1` cu UI vizibil si input simulat pe masina, fie testare
manuala de Marius; simularea unui `SetFocus()`/`LostFocus()` apelat direct din cod ocoleste tocmai
secventa evenimentelor native care a cauzat bugul.
- **Eroarea Oracle -20005 si redeschiderea `vizualizeaza_curs`** — reproductibila headless doar daca
se poate forta controlat lipsa unui curs pentru o data de test (manipulare de date, nu de UI) — nu
s-a verificat aici daca exista deja o retetare de date de test pentru asta.
---
## Ce nu s-a putut stabili si de ce
- **Corpul complet al `frm_date_aviz.Init`** (`:7354-7600`, 247 linii) — citit doar partial in sesiuni
anterioare (pana la `~7533`, conform `inventar_controale_formulare.md:166-167`); nu re-citit integral
aici din motive de buget de context. Structura generala (Do Case pe tip, RemoveObject, focus final)
e foarte probabil simetrica cu `frm_date_factura.Init`, dar nu verificata linie-cu-linie.
- **`do_cauta_venit`** (`frm_date_factura`, `:9393-9394`, o singura linie) — nu s-a citit continutul;
posibil alias/stub mort, de verificat la implementare.
- **Riscul semnalat la punctul 5** (`frm_alte_date.Init:3159`, `dezaloca_numar` pe eroare la
interogarea combo-ului de casa) — **doar semnalat, neurmarit**: nu s-a verificat daca apelantul care
cheama `frm_alte_date` are aceeasi bucla "retry silentios" ca `factureaza`, sau daca eroarea aici
chiar opreste fluxul curat (`Return` explicit la `:3161`, spre deosebire de bug #16 unde nu exista
`Return` echivalent). Merita o cercetare separata, de marimea celei de la punctul 6, inainte de
implementare.
- **`Clb_serie_act1.TEXT_SIMPLU1.LostFocus`** din `frm_facturare_articole2` (`:19263-19270`) — nu
citit; nu s-a verificat daca reproduce corect `genereazanumar` sau e alt cod orfan ca cel de la
punctul 3.
- **Ramificarea exacta linie-cu-linie pentru restul celor 27 de perechi `do_cauta_*`** (dincolo de
`do_cauta_client` si `do_cauta_fdoc`, comparate direct aici) — s-au comparat doar lungimile (indiciu
de diferenta), nu continutul; de citit la implementare, nu presupus din lungime.
- **`frm_modifica_factura`** — clasa reala traieste in `ofacturare_comun.vc2` (perimetrul #6, doar
citire permisa), dar `vfp_symbols.ps1` a indexat-o din `.pre_s4butoane.bak.vc2` (linii duplicate,
nesigure) — nu s-au recitit liniile reale, pentru ca `frm_modifica_factura` **nu intra in portarea
S3** (e inlocuita de `but_modifica`, deja proiectat separat in plan, sectiunea I).
## Bug-uri semnalate, nereparate
1. **Cod orfan in `frm_facturare_articole2`**: `clb_fdoc.cboFdoc.Valid` (`ofacturare.vc2:19256`) cheama
`thisform.do_schimba_tipdoc()`, metoda **inexistenta** pe aceasta clasa — ar arunca eroare VFP daca
userul ar interactiona cu acest combo pe formularul mort. Fara consecinte azi (formularul nu e
accesibil in productie, cf. plan sectiunea A), dar de curatat sau completat cand prototipul devine
baza formularului unificat.
2. **Bug #16, confirmat si localizat complet** (punctul 6) — in `ofacturare.prg:555-564` (si simetric
in `factureaza2`, liniile ~1061 dupa numerotarea echivalenta, nu verificate linie-cu-linie aici):
`lnRaspuns` nu se reseteaza pe ramura de eroare Oracle a buclei de emitere, cauzand recrearea
completa si nesolicitata a formularului de antet. In afara perimetrului `ofacturare_comun.vc2`/
`ofacturare_editare.prg` (deci nu ciocneste cu #6), dar in `ofacturare.vc2`/`ofacturare.prg`, cod
comun suitei (afecteaza si ROACONT/ROAGEST/etc. daca folosesc acelasi `factureaza`/`factureaza2`
din `COMUN` — neverificat aici daca alte produse il apeleaza, dar fisierul e in `COMUN\programe`,
deci probabil da).
3. **Risc structural similar, neconfirmat**: `frm_alte_date.Init:3159` — acelasi tipar
"`dezaloca_numar` pe eroare SQL neasteptata", posibil fara aceeasi consecinta de reincercare
silentioasa, dar nu verificat (vezi "Ce nu s-a putut stabili").

View File

@@ -1,602 +0,0 @@
# S3b — Proiectare: sectiunea pliata "alte date + analitice"
Livrabilul povestii **S3b** din `docs\plan_13_unificare_formular_facturare.md:1838-1854` (deciziile 7,
8, 25, 26). Proiectare pe cod, fara nicio modificare — zero editari, zero
`git_sync.ps1`/`txt2vcx.ps1`, zero commit. `COMUN\clase\ofacturare_comun.vc2` si
`COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar citite, neatinse.
## Verdict (rezumat)
S3b nu e o simpla "mutare de controale" cum sugereaza textul din plan. **Descoperirea centrala,
negasita in materialele existente**: mecanismul de alocare a numarului de chitanta nu porneste doar
din clicul utilizatorului pe `opt_incasat` — porneste din **orice** atribuire programatica a
proprietatii `.Value`, pentru ca `opt_incasat.ProgrammaticChange` (`ferestre_cere_date.vc2:3351-3352`)
cheama exact acelasi `actualizeaza_tipincasare()` ca si `.Click` (`:3250-3251`). `Init`-ul de azi
(`:3150`) seteaza chiar el `Thisform.opt_incasat.Value = 2` cand documentul soseste cu
`poDate.incasat<>0` (caz real: copiere, confirmat la `ofacturare_stoc.prg:535`,
`oproceduri_facturare.prg:1186`, `ofacturare_comun.vc2:4066,4260`) — deci **azi, deschiderea
dialogului aloca deja un numar de chitanta fara ca userul sa atinga nimic**, de fiecare data cand
documentul soseste cu o incasare presetata. In formularul unificat, unde zona nu se mai deschide o
singura data modal ci se pliaza/depliaza repetat pe acelasi obiect `poDate` persistent, acelasi cod
rulat la fiecare depliere ar realoca/dealoca la fiecare toggle — exact opusul criteriului cerut de
decizia 25. Sectiunea 4 trateaza asta ca risc central, cu recomandare concreta.
A doua descoperire: **niciun mecanism de pliere reutilizabil nu exista** in prototipul
`frm_facturare_articole2` (cautare completa, zero potriviri pe "plia/colaps/accordion/expand" in
`ofacturare.vc2`) — cel mai apropiat tipar din suita e `frm_modific2024.afiseaza_rulaje`
(`omodificari.vc2:13169-13199`, perimetrul #6, citit dar neatins), care refoloseste **acelasi idiom
sus/jos** deja validat pentru `but_modifica`/`but_salveaza` (decizia 9), dar aplicat unui show/hide,
nu unei blocari. Sectiunea 6 detaliaza.
A treia: pentru analiticele read-only (decizia 26), mecanismul existent `ct_clb_cautare.do_dezactiveaza()`
(`caut_ora.vc2:800-806`) **ascunde doar lupa de cautare**, nu blocheaza si textbox-ul de afisare —
cineva tot poate scrie liber in camp. Sectiunea 2 detaliaza gap-ul si completarea minima necesara.
---
## 1. Inventarul exact al celor patru grupuri din `frm_alte_date`
Sursa primara a controalelor: `docs\cercetare\inventar_controale_formulare.md` §4 si
`docs\S1_inventar_campuri_formular_unificat.md` (randurile "Pliat — alte date"), reconfirmate aici
direct pe `COMUN\clase\ferestre_cere_date.vc2` (clasa `frm_alte_date`, `2219-3353`; garda de validare
`inainte_de_do_termin` la `3044-3103`, `Init` la `3105-3208`).
### 1.1 Delegat / transport
| Control | Caption real | `fisier:linie` (ADD OBJECT) | Vizibilitate | Ruta de scriere (din rute_scriere_antet.md / S1) |
|---|---|---|---|---|
| `Ct_clb_delegat` | "Delegat" | `ferestre_cere_date.vc2:2553` (container `ct_clb_cautare`-like, `caution=do_cauta_delegat`) | pliat, mereu editabil pe document nou; **eliminat cu totul** cand `poDate.eProforma=1` (`:3192`) | pe loc, `modifica_date_factura` (`V_ID_DELEGAT`), neconditionat — `modifica_date_factura_parametri.md:17` |
| `Ct_clb_masina` | "Masina" | `:2569` | idem, eliminat pe proforma (`:3193`) | `V_ID_MASINA`, neconditionat — `:18` |
| `Ct_clb_agent` | "Agent" | `:2537` | idem, **nu e eliminat pe proforma** (nu apare in cascada `RemoveObject` `:3192-3201`) | `V_ID_AGENT`, neconditionat — `:19` |
| `Clb_dataora_exp` | "Data si ora expedierii" | `:2443` | idem, nu eliminat pe proforma; **validat obligatoriu** in `inainte_de_do_termin` (`:3062-3067`, `Case Empty(Nvl(poDate.dataora_exp,{})) And poDate.eProforma = 0`) | `V_DATAORA_EXP`, neconditionat — `:20` |
| `But_modifica1` | (icon, `caction` implicit -> `do_modifica`) | `:2343` | actiune, nu camp — deschide `nom_parteneri_modifica` pe delegatul curent (`ferestre_cere_date.vc2:2925-2935`) | — |
**Validarea de grup** (`inainte_de_do_termin`, `:3055-3060`): delegatul e obligatoriu doar cand
`gcNumeProgram = [ROAFACTURARE]` **si** documentul nu e proforma sau bon fiscal
(`!(poDate.eProforma = 1 OR poDate.eBonFiscal = 1)`) — nuanta care trebuie pastrata identic in
formularul unificat, nu doar copiata "delegat obligatoriu".
### 1.2 Incasare (grupul cel mai mare, vezi si sectiunile 3-4)
| Control | Caption real / eticheta dinamica | `fisier:linie` | Vizibil cand |
|---|---|---|---|
| `opt_incasat` (radiogrup 4 optiuni) | "Fara incasare" / "Chitanta" / "Bon fiscal" / "POS Card" | `:2638-2685` (default `Value=1`) | mereu, dar **intreg containerul incasare** dispare pe proforma (`:3140-3143`, `Thisform.opt_incasat.Visible = .F.`, doar pe ramura non-proforma exista oricum) si pe `gcNumeProgram=[ROAGEST]` sau `!Between(poDate.tip,1,4)` |
| `Cb_casa` | "Casa" -> "Banca POS" pe POS (`:2967` `_lbbase1.Caption='Banca POS'`) | `:2362` | ascuns doar la `opt_incasat=1` |
| `Clb_serie_chit` | "Serie chitanta" | `:2503` | vizibil **doar** la Chitanta (`Value=2`) |
| `Clb_nrchit` | "Nr. chitanta" -> "Nr. bon" pe Bon fiscal/POS | `:2480` | ascuns doar la `Value=1` |
| `Clb_incasat` | "Incasat" | `:2461` | ascuns doar la `Value=1` |
| `cmdModificaBon` | icon, `caction=do_modifica_bon` | `:2526` | vizibil **doar** la Bon fiscal (`Value=3`) |
| `chkPOS` | "POS" | `:2409` | vizibil la Bon fiscal (`Value=3`, bifabil) **si** la POS (`Value=4`, fortat `.T.` si ascuns — `:2845` `thisform.chkPOS.Value=1` apoi `.Visible=.F.`) |
| `chkDetaliat` | "Detaliat" | `:2396` | vizibil doar la Bon fiscal |
| `cboTipFactura` | combo, langa "Tip factura" | `:2378` | mereu (langa incasare), scrie `poDate.tip_saft` |
Masina de stari completa e la sectiunea 3-4. **Eticheta "Casa"/"Banca POS" si vizibilitatea lui
`chkPOS`/`chkDetaliat` fac parte din aceeasi metoda unica** (`actualizeaza_tipincasare`) — nu sunt
conditii separate de refacut, sunt randuri din acelasi `Do Case`.
### 1.3 Adresa de facturare
| Control | Caption | `fisier:linie` | Vizibilitate |
|---|---|---|---|
| `clb_adresa_facturare` | "Adresa facturare" | `:2422` | pliat, mereu editabil; **nu** eliminat pe proforma (absent din cascada `:3192-3201`) |
| `do_cauta_adresa` (metoda, nu control separat) | — | `:2911-2926` | cauta pe `poDate.id_client`, seteaza `poDate.adresa_facturare`/`poDate.id_facturare` |
Ruta de scriere: `V_ID_FACTURARE`, neconditionat — `modifica_date_factura_parametri.md:21`.
### 1.4 Text aditional
| Control | Caption | `fisier:linie` | Vizibilitate |
|---|---|---|---|
| `Ed_tx_simplu1` | "Text aditional (max. 1000 caractere)" | `:2585` | pliat, mereu editabil; nu eliminat pe proforma — dimpotriva, **pe proforma ramane singurul grup activ**, formularul se redimensioneaza in jurul lui (`:3184-3207`) |
| `_checkbox1` ("Listare detaliata") | "Listare detaliata" | `:3002-3011` | ADD OBJECT separat, `ControlSource=poDate.nListareDetaliata` — **nu e in cascada celor patru grupuri din B**, e propriul lui control, langa text aditional in layout, dar conceptual apartine grupului de raportare (I-bis) alaturi de `tip_saft`/`efactura`, nu grupului "text aditional" |
Ruta: `V_TEXT_ADITIONAL`, neconditionat, **fara** `NVL` — `modifica_date_factura_parametri.md:23`.
**Observatie structurala pentru mockup**: `_checkbox1` (listare detaliata) e cablat separat de
`chkDetaliat` din grupul incasare (control diferit, aceeasi semantica probabila — ambiguitate deja
semnalata in S1, randul 57, neinchisa aici). Formularul unificat trebuie sa aleaga **unul singur**
dintre cele doua controale existente, nu sa le porteze pe amandoua.
---
## 2. Analiticele coborate din antet (decizia 8, nuantata de decizia 26)
Controale: `Ct_clb_venchelt` ("Venit / cheltuiala"), `Ct_clb_sectie` ("Sectie"), `Ct_clb_responsabil`
("Responsabil"), `Ct_clb_lucrare` ("Lucrare") — toate patru container `ct_clb_cautare` (subclasa
`caut_ora.vcx`), azi pe `frm_date_factura`/`frm_date_aviz`, **nu** pe `frm_alte_date`
(`inventar_controale_formulare.md` §3; S1 randurile 39-42). Live in sectiunea I a planului ca grup
"Pliat — analitice", distinct de cele patru grupuri ale lui `frm_alte_date` — dar aceeasi sectiune
pliata a formularului unificat, conform textului S3b ("peste ele coboara analiticele din antet").
### 2.1 Ce inseamna "afisare read-only" mecanic
Sursa: `docs\cercetare\rute_scriere_antet.md` §1 — verdict deja stabilit, **DA** pentru
`ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE` printr-o cale noua (`frm_facturi.do_editare_factura` ->
`frm_modific2024`, perimetrul #6), **neconfirmat** pentru `ID_VENCHELT`. Pentru #13, indiferent de
raspunsul acelei rezerve, decizia 26 e explicita: **zero editare din formularul unificat**, oricare ar
fi ruta reala din Oracle.
**Mecanismul de blocare vizuala, verificat pe cod, cu un gap real**:
`ct_clb_cautare.do_dezactiveaza()` (`caut_ora.vc2:800-806`):
```
PROCEDURE do_dezactiveaza
This.lactiv = .F.
This.img_cautare.Visible = .F.
This.ctooltip = This.clb_tx_cautare.text_simplu1.ToolTipText
This.clb_tx_cautare.text_simplu1.ToolTipText = []
This.clb_tx_cautare.text_simplu1.Refresh()
ENDPROC
```
`This.lactiv` gateaza **doar** deschiderea dialogului de cautare — `DblClick` (`:822-826`),
`KeyPress` pe Enter/Tab/F7 (`:829-836`), `img_cautare.Click` (`:840-843`) — toate testeaza
`This.Parent(.Parent).lactiv` inainte sa cheme `do_apeleaza()`. **Nu seteaza `ReadOnly` pe
`clb_tx_cautare.text_simplu1`** — textbox-ul bindat (`ControlSource=This.cvar_afisata`, setat in
`Init`, `caut_ora.vc2:814-817`) ramane direct tastabil. Pentru un camp cu adevarat read-only (decizia
26 cere explicit "nu se editeaza"), `do_dezactiveaza()` trebuie **completat**, nu doar apelat: se
adauga `This.clb_tx_cautare.text_simplu1.ReadOnly = .T.` (si eventual `.TabStop = .F.`, ca sa nu
primeasca focus deloc) langa apelul existent — o linie noua, in tiparul deja recomandat de plan
(sectiunea I: "singura reteta completa e `clb_tx_data.dezactiveaza()`... se generalizeaza de la ea").
**Consecinta pentru "un singur buton deschide tot" (decizia 9)**: `do_dezactiveaza()` se apeleaza **o
singura data**, la constructia sectiunii, **si nu se mai cheama niciodata `do_activeaza()`** pentru
aceste patru controale — nici cand `but_modifica` deblocheaza restul antetului. E o exceptie
deliberata de la "un singur control comanda toata protectia antetului" (plan I:843-853), simetrica cu
exceptia deja acceptata pentru grupurile B/C la decizia 25 (sectiunea 5 de mai jos).
### 2.2 Eticheta / tooltip
Nicio propunere de text nu exista inca in materialele citite — de scris la implementare. Recomandare
minima, in stilul deja folosit in codebase (ex. `Ct_clb_gestiune_init.ToolTipText`, un citat complet
la `inventar_controale_formulare.md:93-95`, deci precedent de lungime/ton acceptat): eticheta ramane
neschimbata ("Venit / cheltuiala"/"Sectie"/"Responsabil"/"Lucrare"), `ToolTipText` nou de tipul
*"Se editeaza din Editare factura (articole, cantitati, preturi) — buton disponibil pe fiecare linie
a notei contabile"*, ca sa nu para camp stricat. Text exact — **de decis de Marius**, vezi sectiunea 9.
### 2.3 Nivel antet vs. nivel linie — de retinut la afisare
`rute_scriere_antet.md` §1.4: editarea reala prin #6 e **pe linie de nota**, nu pe antet — un
document poate ajunge cu sectii diferite pe linii diferite dupa editare (imposibil la emitere). Cand
formularul unificat afiseaza analiticele "cu valoarea de antet" (cum spune decizia 26), afiseaza de
fapt **o singura valoare reprezentativa** (probabil prima linie sau variabila de sesiune retinuta la
emitere), nu o agregare a liniilor — daca liniile diverg dupa o editare #6, afisarea din #13 devine
"aproximativa, nu autoritara". Nu e o contradictie de rezolvat aici (decizia 26 exista tocmai ca sa
evite ca #13 sa scrie peste diferentierea de linie), dar merita un rand explicit in criteriul de
"gata" (sectiunea 10): afisarea trebuie sa spuna clar ce valoare arata, nu sa pretinda ca e valoarea
unica a documentului daca liniile difera.
---
## 3. `actualizeaza_tipincasare`
Definita la `COMUN\clase\ferestre_cere_date.vc2:2698-2856`, metoda proprie a clasei `frm_alte_date`.
Comuta vizibilitatea intregului subgrup de incasare pe `This.opt_incasat.Value` (patru ramuri, tabel
complet la sectiunea 1.2 si in `inventar_controale_formulare.md:130-135`) **si**, in aceeasi metoda,
aloca/dezaloca numerele — cele doua responsabilitati (UI si alocare) sunt **impletite in acelasi
`Do Case`**, nu separate. Fiecare ramura incepe prin a dezaloca defensiv celelalte trei tipuri de
numar, apoi aloca pe cel curent daca e cazul:
| `opt_incasat.Value` | Dezaloca la intrare | Aloca | Alte efecte |
|---|---|---|---|
| 1 (Fara incasare) | 16, 3, 26 | — | `poDate.incasat=0`, `poDate.nr_incasare=0` |
| 2 (Chitanta) | 3, 16, 26 | `creeaza_cursor_serii(16)` + `clb_serie_chit.genereazaNumar()` (`:2783`) — doar daca `nRezultatSerii=0` (nu realoca daca seria era deja creata) | `poDate.incasat=poDate.totalctva` |
| 3 (Bon fiscal) | 3, 16, 26 | `do_aloca_nr_bon([CLICK])` (`:2807`) -> cod 3 | idem, plus `tip_saft` poate deveni 751 daca `gnEFactura_tip_saft_bf` e setat |
| 4 (POS/Card) | 3, 16, 26 | `do_aloca_nr_pos([CLICK])` (`:2844`) -> cod 26 | idem, `chkPOS.Value` fortat 1 si ascuns |
**Ce depinde de ea**: `opt_incasat.Click` (`:3250-3251`) **si** `opt_incasat.ProgrammaticChange`
(`:3351-3352`) — vezi sectiunea 4, e disjunctia care conteaza. Nu are alti apelanti directi in
`ferestre_cere_date.vc2` (cautare `vfp_symbols.ps1 -Grep actualizeaza_tipincasare -CodeOnly`
recomandata la implementare pentru confirmare exhaustiva pe tot proiectul — nu rulata aici din motive
de buget, dar apelantul extern deja cunoscut e citat mai jos, la 3.1).
### 3.1 Apel extern, cu context important
`COMUN\clase\ofacturare.vc2:14919-14926` (`frm_facturare_articole(2).inainte_de_do_termin`, inainte
de `ofrmdatesupl.Show(1)`):
```
ofrmdatesupl = Createobject("frm_alte_date")
ofrmdatesupl.nnrbon = poDate.nract
If (poDate.eBonFiscal = 1)
With ofrmdatesupl
.opt_incasat.Value = 3 && INCASARE CU BON FISCAL
.actualizeaza_tipincasare() && apel EXPLICIT, dupa .Value=
.opt_incasat.option1.TabStop = .F.
...
```
Apelantul seteaza `.Value=3` **si** cheama explicit `actualizeaza_tipincasare()` imediat dupa — ceea
ce, coroborat cu descoperirea de la sectiunea 4 (`.Value=` deja declanseaza `ProgrammaticChange` ->
aceeasi metoda), inseamna ca metoda ruleaza de doua ori la acest apel. Nu produce o eroare vizibila
(fiecare ramura e idempotenta: dezaloca ce era alocat, realoca acelasi tip), dar confirma ca autorul
codului nu s-a bazat exclusiv pe evenimentul `ProgrammaticChange` — semn ca mecanismul lui exact
(cand se declanseaza, cand nu) n-a fost niciodata documentat explicit, doar "acoperit din ambele
parti". **Portarea "ca atare" trebuie sa pastreze acest apel dublu identic**, nu sa-l "curete" ca
redundant — eliminarea unuia dintre cele doua declansatoare ar putea rupe o presupunere neverificata
in alta parte a codului.
### 3.2 "Se muta ca atare" — ce inseamna concret
Corpul metodei (`:2698-2856`) nu are nicio dependenta de fereastra-container (`Thisform.*` peste
tot, nu `This.Parent.*`) — se poate muta **byte-cu-byte** pe formularul unificat, cu conditia ca
toate cele noua controale referite (`opt_incasat`, `cb_casa`, `clb_serie_chit`, `clb_nrchit`,
`clb_incasat`, `_shape3`, `lb_simplu1`, `cmdModificaBon`, `chkPOS`, `chkDetaliat`, `cboTipFactura`)
sa existe cu **exact aceleasi nume** pe noul formular, in acelasi container logic (`Thisform`, nu un
subpanou cu alt scope) — altfel referintele nekvalificate `Thisform.xxx` pica silentios pe obiect
inexistent. Aceasta e o constrangere de implementare directa: **sectiunea pliata trebuie sa fie
container-transparenta** pentru aceasta metoda (fie pusa direct pe `Thisform`, fie metoda insasi
adaptata sa foloseasca `This.Parent`-ul corect) — nu poate fi mutata intr-un obiect-container separat
fara o trecere explicita de `Thisform.xxx -> This.Parent.xxx` pe toate liniile.
---
## 4. Alocarea si dezalocarea numerelor — evenimentele exacte
### 4.1 Evenimente de alocare (azi)
1. **`opt_incasat.Click`** (`:3250-3251`) — interactiune reala a utilizatorului pe radiogrup.
2. **`opt_incasat.ProgrammaticChange`** (`:3351-3352`) — **descoperire centrala a acestui raport**,
negasita in materialul de plan sau in cele patru rapoarte de referinta: in VFP, orice atribuire
`.Value = n` facuta din cod (nu de utilizator) declanseaza `ProgrammaticChange`, nu `Click`. Clasa
are ambele metode cablate pe **acelasi** apel (`actualizeaza_tipincasare()`), deci **orice loc din
cod care scrie `opt_incasat.Value = ...` aloca/dealoca numere**, indiferent de intentie.
3. **Chiar `Init`-ul clasei** foloseste acest canal: `:3150`, `Thisform.opt_incasat.Value = 2` — ruleaza
**doar daca** `poDate.incasat <> 0` la momentul deschiderii. Verificat pe cod (nu presupus) ca
`poDate.incasat` poate fi nenul **inainte** ca `frm_alte_date` sa se deschida, prin cel putin patru
cai reale: `ofacturare_stoc.prg:535`, `oproceduri_facturare.prg:1186`,
`ofacturare_comun.vc2:4066`, `ofacturare_comun.vc2:4260` (toate patru scriu `poDate.incasat =
incasat`, parametru primit de la apelant — flux de copiere/reluare a unui document cu incasare deja
stabilita). **Concluzie verificata**: azi, deschiderea dialogului `frm_alte_date` pe un astfel de
document **aloca deja un numar de chitanta la Init, inainte ca userul sa apese orice**. Nu e o
ipoteza — e mecanismul descris la sectiunea 3, declansat de linia 3150.
4. `do_modifica_bon`/`do_modifica_pos` (`:3001-3022`) — dezaloca explicit + realoca, la apasarea
butonului "Modifica bon"/echivalent POS.
5. `do_aloca_nr_bon`/`do_aloca_nr_pos` (`:2864-2907`) — chemate din interiorul lui
`actualizeaza_tipincasare` (ramurile 3 si 4), nu independent.
### 4.2 Evenimente de dezalocare (azi)
1. **In interiorul `actualizeaza_tipincasare`** — fiecare ramura dezaloca defensiv celelalte trei
tipuri **inainte** de a (re)aloca pe cel ales (tabelul de la sectiunea 3). Efect: comutarea intre
optiuni, in cadrul aceleiasi sesiuni de dialog, e curata — nu ramane niciun numar orfan din
optiunea anterioara.
2. **`inainte_de_do_renunt`** (`:3023-3043`) — la anulare (buton Renunt / ESC pe dialogul modal):
```
inainte_de_do_renunt (ferestre_cere_date.vc2:3025-3030)
If Type('poGeneratorNumere') = 'O'
poGeneratorNumere.dezaloca_numar(16) && chitanta
poGeneratorNumere.dezaloca_numar(3) && bon fiscal
...
```
**Gap real, preexistent, verificat pe cod**: acest bloc dezaloca 16 (chitanta) si 3 (bon fiscal),
dar **nu 26 (POS)** — nici direct, nici in blocul urmator (`:3033-3035`, care dezaloca doar 16 din
nou, conditionat). Daca utilizatorul alege POS/Card si apoi anuleaza dialogul, numarul POS alocat
**ramane alocat**, nedezalocat. Nu e introdus de S3b — exista deja in codul de azi — dar merita
semnalat explicit ca risc mostenit (sectiunea 9), pentru ca S3b il "muta ca atare" (plan, textul
povestii), deci il si multiplica pe orice cale noua prin care sectiunea pliata s-ar putea
"renunta" fara sa fie un dialog modal cu propriul buton Renunt.
3. **`Init` pe eroare Oracle** (`:3159`) — `poGeneratorNumere.dezaloca_numar(5)` (factura) daca
interogarea `v_nom_casa` esueaza, cu `Return` imediat dupa (linie explicita, spre deosebire de
bug-ul #16 din `s3_portare_antet.md` §6, unde nu exista `Return` echivalent). Semnalat, neurmarit
mai departe aici — acelasi risc de arhitectura ca #16, dar pe alt fisier.
### 4.3 Riscul central pentru formularul unificat, si recomandarea
**Problema mecanica exacta**: in fluxul de azi, `frm_alte_date` e un dialog modal, instantiat **o
singura data** per emitere (`ofacturare.vc2:14921`), asa ca `Init`-ul lui (si declansarea eventuala de
la linia 3150) ruleaza **o singura data**. In formularul unificat, grupul de incasare devine o
sub-sectiune a unui panou pliabil, pe **acelasi obiect de formular persistent** — daca implementarea
naiva re-populeaza `opt_incasat.Value` din `poDate` de fiecare data cand sectiunea se depliaza (ex.
"la fiecare `Show`/`Visible=.T.` al panoului, resincronizeaza controalele cu starea curenta a lui
`poDate`"), **fiecare depliere ar re-declansa `ProgrammaticChange` -> `actualizeaza_tipincasare()` ->
dezaloca tot + realoca pe optiunea curenta** — incalcand direct criteriul din S3b ("deschiderea si
inchiderea formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar").
**Recomandare concreta**: populeaza `opt_incasat.Value` din `poDate` **o singura data**, la
constructia sectiunii de antet a documentului (echivalentul lui `Init` de azi, care ruleaza o data per
sesiune de emitere/editare) — nu la fiecare toggle de pliere/depliere. Plierea/deplierea trebuie sa
fie **strict** un toggle de `Visible`/`Height` pe containerul vizual, fara nicio atingere a
proprietatii `.Value` a lui `opt_incasat` sau a oricarui alt control cu `ProgrammaticChange` cablat pe
alocare. Aceasta e exact genul de regula care trebuie verificata explicit la implementare (criteriu de
"gata" concret la sectiunea 10), nu presupusa.
---
## 5. Grupul de incasare pe document deja emis (decizia 25)
**Ce spune decizia**: orice camp schimbat din grupul C (incasare) pe un document deja emis
"marcheaza documentul pentru regenerare" — dar regenerarea nu exista in etapa I, deci **planul tine
campurile blocate**, cu explicatie la hover, pana la etapa II (plan `:192-201`, `:1847-1848`).
`rute_scriere_antet.md` §3 confirma independent ca nu exista nicio cale directa de scriere pentru
grupul C dupa emitere (verdict "NU" pentru toate controalele, "PARTIAL neconfirmat" doar pentru o
cale indirecta prin editarea notei — vezi §3.2 acolo, ramane deschisa).
### 5.1 Mecanic: pe ce proprietate, pe ce conditie
Decizia 9 stabileste ca `but_modifica` deblocheaza **tot** antetul, fara exceptie de camp — dar
decizia 25 taie o exceptie explicita peste asta pentru grupurile B si C. Nu exista inca (verificat,
`rute_scriere_antet.md` §2-3) un mecanism de blocare per-grup separat de `but_modifica`; trebuie
construit nou, dar dupa un tipar deja folosit in codebase pentru exact intrebarea "acest document are
deja un numar, sau e nou": `chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))`
(`ofacturare_comun.vc2:5736-5750`, citat deja in plan I:839 si S1 randul 60). Decizia 9 **abandoneaza**
acest test ca poarta pe cele patru bife de identitate (serie/numar/data/scadenta) — dar testul insusi
ramane exact instrumentul potrivit pentru o alta intrebare: "documentul e nou sau deja emis?", care e
tocmai conditia ceruta de decizia 25 pentru grupurile B/C.
**Recomandare mecanica**: la construirea antetului, se calculeaza o singura data un flag (ex.
`lDocumentExistent = !EMPTY(NVL(poRec.numar_act,0))` sau echivalentul lui pe obiectul `poDate`/`poRec`
folosit de formularul unificat — de confirmat la implementare care obiect poarta `numar_act` in noua
arhitectura, pentru ca `poRec` era specific lui `frm_modifica_factura`, perimetrul #6). Controalele
grupului C (`opt_incasat` si tot ce atarna de el din tabelul sectiunii 1.2) primesc `Enabled = .F.`
**neconditionat de starea lui `but_modifica`** cand `lDocumentExistent = .T.`, in etapa I. Cand
`but_modifica` deblocheaza restul antetului, acest grup **ramane** dezactivat — e o exceptie fixa, nu
una care se recalculeaza la fiecare click pe `but_modifica`.
**Grupul B** (tip document, valuta, zi curs, client, sursa, gestiune sursa, politica de preturi) are
aceeasi tratare — dar acele controale traiesc azi in `frm_date_factura`/`frm_date_aviz`
(sectiunea I a planului, nu sectiunea B de aici), deci blocarea lor nu e in perimetrul S3b propriu-zis;
se semnaleaza aici doar pentru consecventa mecanismului (acelasi flag, acelasi tipar de `Enabled=.F.`
peste orice stare a lui `but_modifica`).
### 5.2 La emitere, se comporta ca azi
Pentru un document **nou** (nu inca emis), `lDocumentExistent = .F.`, grupul C e activ normal — se
comporta identic cu `frm_alte_date` de azi, cu masina de stari intreaga din sectiunile 3-4. Nimic nu
se schimba pe calea de emitere initiala; blocarea se aplica **doar** cand formularul unificat deschide
un document deja scris in `VANZARI` (calea de editare, nu de creare).
### 5.3 Tooltip / explicatie la hover
Ca la sectiunea 2.2, textul exact nu exista inca — de propus la implementare, in tonul deja folosit in
codebase. Continut minim necesar: sa spuna ca modificarea incasarii pe un document emis nu e inca
posibila din acest formular ("disponibil dupa ce regenerarea documentului e implementata" sau
echivalent), nu doar "camp dezactivat" fara explicatie — plan `:198-200` cere explicit "cu explicatie
la hover".
---
## 6. Mecanismul de pliere — nu exista, se construieste nou dupa un tipar existent
**Cautare directa in prototip**: `vfp_symbols.ps1 -Grep` (si grep text simplu) pe "plia|colaps|
collapse|expand|acordeon|accordion" in `COMUN\clase\ofacturare.vc2` (unde traieste
`frm_facturare_articole2`, `:15741-19355`) — **zero potriviri**. Prototipul are controalele de antet
montate direct pe formular (confirmat deja de `s3_portare_antet.md` §3 si de
`inventar_controale_formulare.md` §2), dar **niciun mecanism de show/hide de sectiune** — nu exista
nici macar un buton candidat.
**Cel mai apropiat tipar real din suita**: `frm_modific2024.afiseaza_rulaje`
(`COMUN\clase\omodificari.vc2:13169-13199`, perimetrul #6, doar citit aici). Cod complet:
```
PROCEDURE afiseaza_rulaje
* COLAPSEAZA SAU MARESTE PAGEFRAME-UL RULAJE
lcPicture = this.but_afiseaza_rulaje.Picture
...
llAfiseazaRulaje = 'sus'$m.lcPicture
IF m.llAfiseazaRulaje
lcPicture2 = STRTRAN(m.lcPicture,'sus','jos') ...
ELSE
lcPicture2 = STRTRAN(m.lcPicture,'jos','sus') ...
ENDIF
this.but_afiseaza_rulaje.Picture = m.lcPicture2
...
this.pgfArticole.Visible = m.llAfiseazaRulaje
this.but_copiazaR.Visible = m.llAfiseazaRulaje
this.but_modificaR.Visible = m.llAfiseazaRulaje
this.but_stergeR.Visible = m.llAfiseazaRulaje
thisform.resize_grid1()
ENDPROC
```
Trei observatii directe:
1. **Foloseste exact acelasi idiom sus/jos** deja validat si documentat pentru `but_modifica`/
`but_salveaza` (decizia 9, `docs\cercetare\buton_comutator_picture.md`, citat in plan `:63-75`):
citeste starea curenta din numele fisierului `Picture` (contine "sus" sau "jos"), calculeaza
perechea opusa prin `STRTRAN`, rescrie **`Picture` + `cPictureDown` + `cPictureUp` impreuna** —
aceeasi precautie semnalata in plan ("numai `.Picture` nu ajunge: hover-ul standard din
`buton.MouseEnter`/`MouseLeave` rescrie `Picture` din `cPictureUp`/`cPictureDown`").
2. **Nu e o clasa reutilizabila, e cod ad-hoc per caz** — toggle direct pe `.Visible` al unei liste
fixe de controale, plus un apel manual de reflow (`resize_grid1()`, metoda proprie a formularului,
nu un mecanism generic). Nu exista in nicio biblioteca de clase din `COMUN` (`_baza.vcx`, etc.,
cautare separata, zero potriviri pentru o clasa "panou pliabil"/"expander"/"accordion") un
container gata facut de pliere. **S3b trebuie sa scrie cod nou**, dupa acest tipar, nu sa
instantieze o clasa existenta.
3. **E in perimetrul #6, doar citire** — poate fi copiat ca *idee*/tipar la implementare, dar
`omodificari.vc2` insusi nu se atinge cat timp #6 e in lucru (decizia 30).
### 6.1 Ce trebuie sa scrie S3b, concret
- **Un buton comutator per sectiune pliata** (sau unul singur pentru toata zona "alte date +
analitice", de decis la implementare — plan I nu specifica granularitatea), cu imaginile
sus/jos deja existente in `COMUN\grafice` (aceleasi perechi folosite pentru `but_afiseaza_rulaje`,
de identificat exact numele fisierelor la implementare — nu verificate aici, doar tiparul de
mecanism).
- **Toggle de `.Visible`/`.Height`** pe grupul de controale al sectiunii (delegat/transport, incasare,
adresa, text aditional, analitice), plus un apel de reflow echivalent lui `resize_grid1()` — pentru
ca restul formularului (butoane, alte sectiuni de mai jos) sa nu ramana cu goluri sau suprapuneri
cand sectiunea se pliaza/depliaza. VFP nu reflow-eaza automat un layout absolut-pozitionat (`Left`/
`Top` fixe pe fiecare control) — de asta si tiparul din `frm_date_factura.Init` (citat in
`s3_portare_antet.md` §5, "masoara inaltimile containerelor de antet intr-un array `laPozitii`") cat
si `afiseaza_rulaje` fac manual acest calcul; nu exista un layout manager automat de reutilizat.
- **Populare o singura data** a starii initiale (sectiunea 4.3) — separat de toggle-ul de vizibilitate,
ca sa nu retrigger-eze `ProgrammaticChange`.
- **Intrebare deschisa, semnalata deja in `s3_portare_antet.md` §5** (nu redusa aici): daca lookup-urile
Oracle ale lui `frm_alte_date.Init` (delegat/masina ultima factura, cursorul `v_nom_casa`) raman
**eager** (rulate la construirea formularului, ca azi) sau devin **lazy** (amanate pana la prima
depliere a sectiunii, ca sa nu incarce round-trip-uri Oracle inutile cand userul nu ajunge niciodata
acolo). Ambele optiuni sunt compatibile cu criteriul "deschiderea/inchiderea nu aloca numere" **doar
daca** populate keeping in mind sectiunea 4.3 (populare o singura data, indiferent de varianta
aleasa) — **de decis de Marius**, vezi sectiunea 9.
---
## 7. Ordinea de executie
Pasi care lasa suita functionala dupa fiecare — calea veche (`frm_alte_date` ca dialog separat) **nu
se sterge** in etapa I, cf. textul povestii ("`frm_alte_date` nu se sterge cat timp calea veche mai e
in uz").
1. **Construieste sectiunea vizuala pliata** (patru grupuri din sectiunea 1 + analiticele din
sectiunea 2), fara nicio logica de alocare/validare inca — doar controale + toggle de
Visible/Height (sectiunea 6). *Criteriu:* fiecare control existent in `frm_alte_date`/antetul de
analitice apare o data, cu aceeasi eticheta, in sectiunea corecta a formularului unificat —
verificare camp-cu-camp fata de tabelele din sectiunile 1-2 de aici.
2. **Porteaza `actualizeaza_tipincasare` ca atare** (sectiunea 3), cu toate cele noua referinte
`Thisform.xxx` rezolvate corect in noul container (sectiunea 3.2). *Criteriu:* alegerea fiecarei
optiuni din `opt_incasat` produce exact aceleasi `Visible`/etichete pe controalele dependente ca pe
`frm_alte_date` de azi, verificabil headless (sectiunea 8).
3. **Rezolva explicit riscul de la sectiunea 4.3** — populare unica a starii, toggle de pliere fara
atingere de `.Value`. *Criteriu, verificabil headless:* pe un document de test cu `poDate.incasat<>0`
la construirea formularului, se depliaza si se plie
aza sectiunea de trei ori consecutiv fara a atinge
niciun control din grup — `poGeneratorNumere` (cursorul lui de numere alocate) arata **acelasi**
numar de chitanta la finalul celor trei toggle-uri ca la inceput, nu unul nou de fiecare data.
4. **Porteaza delegat/transport si adresa de facturare** (`do_cauta_delegat`/`do_cauta_masina`/
`do_cauta_agent`/`do_cauta_adresa`/`do_modifica`), cu validarea din `inainte_de_do_termin`
(sectiunea 1.1). *Criteriu:* fiecare cautare deschide acelasi dialog si scrie aceleasi proprietati
pe `poDate` ca azi (comparatie directa, cod identic mutat).
5. **Adauga analiticele read-only** (sectiunea 2), inclusiv completarea `do_dezactiveaza()` cu
`ReadOnly=.T.` explicit. *Criteriu:* tastarea directa in oricare din cele patru campuri nu modifica
valoarea afisata (verificare pe proprietate, headless).
6. **Implementeaza blocarea grupului C pe document deja emis** (decizia 25, sectiunea 5), cu flag-ul
de "document existent" si exceptia peste `but_modifica`. *Criteriu:* pe un document nou,
`but_modifica` (cand exista, dupa S3/decizia 9) nu afecteaza grupul C (mereu activ); pe un document
deja emis, grupul C ramane `Enabled=.F.` inainte **si** dupa apasarea lui `but_modifica`.
7. **Verificare de regresie end-to-end**: emite acelasi document (numerar, chitanta, bon fiscal, POS —
toate patru, nu doar unul) o data pe calea veche (`frm_alte_date` dialog) si o data pe formularul
unificat, compara `poDate`-ul rezultat inainte de scriere in Oracle (aceleasi campuri de incasare,
acelasi numar alocat) — tipar deja folosit in `s3_portare_antet.md` §7 pentru S3, reutilizat aici
pe grupul mai mic al lui S3b.
*Depinde de S3* (planul o spune explicit) — pasii 4 si 6 presupun ca `but_modifica`/blocarea antetului
(proiectata in S3, sectiunea I a planului) exista deja ca mecanism, nu doar ca design pe hartie.
---
## 8. Ce nu se poate testa headless
Precedent direct: `s3_portare_antet.md` §8, si memoria de proiect
(`grid-coloane-nu-se-materializeaza-headless` — coloanele de grid nu se materializeaza sub `-A -T`,
exista harness UI vizibil separat care le citeste corect).
**Se POATE testa headless** (contrar primei intuitii, pentru ca e logica pe proprietati, nu pictura pe
ecran):
- Toata masina de stari `actualizeaza_tipincasare` — `Createobject()` fara `.Show()` tot ruleaza
`Init` si tot cabl eaza evenimentele; `thisform.opt_incasat.Value = n` declanseaza
`ProgrammaticChange` identic cu UI vizibil sau nu. Verificarile de alocare/dezalocare de la pasul 3
al sectiunii 7 sunt testabile 100% headless, pe proprietatile lui `poGeneratorNumere`.
- Vizibilitatea controalelor dupa toggle (`.Visible`, `.Height`, existenta obiectului via `Type(...)`)
— acelasi motiv, confirmat deja de `s3_portare_antet.md` §8 pentru mecanismul similar din `Init`-urile
de antet.
**NU se poate testa headless**:
- **Aspectul vizual real dupa pliere/depliere** (suprapuneri, goluri, pozitionarea corecta a
controalelor de sub sectiune dupa reflow-ul manual, sectiunea 6.1) — layout absolut-pozitionat, fara
reflow automat; verificarea ceruta e "arata bine pe ecran", nu o proprietate booleana — necesita
harness UI vizibil (memoria de proiect citata mai sus) sau verificare manuala de Marius.
- **Cele patru dialoguri modale** (`do_cauta_delegat`/`do_cauta_masina`/`do_cauta_agent`/
`do_cauta_adresa`) — interactiunea reala de alegere dintr-o lista nu se simuleaza headless; se poate
testa doar efectul **dupa** ce rezultatul cautarii e construit manual (tiparul deja folosit in
proiect, citat in `s3_portare_antet.md` §8, pct. 1).
- **Lookup-urile Oracle din `Init` (delegat/masina ultima factura, `v_nom_casa`)** — necesita conexiune
Oracle reala (harnessul suporta asta prin `MARIUSM_AUTO`, dar tot nu e "headless" in sensul de
"fara dependinte externe") si date de test care sa existe pentru clientul folosit.
- **Butonul `cmdModificaBon`/`do_modifica_bon`** — deschide `viz_config_serii_complet WITH 3`
(`oserii_numere.prg`), alt formular modal, aceeasi limitare ca dialogurile de cautare.
---
## 9. Riscuri si capcane
1. **Riscul central** (sectiunea 4.3): re-populare a starii `opt_incasat` la fiecare toggle de pliere
ar re-declansa alocare/dezalocare, incalcand direct criteriul cerut de decizia 25/S3b. Tratat
explicit ca pas 3 in ordinea de executie — **nu** de lasat "se rezolva de la sine prin arhitectura",
pentru ca mecanismul de declansare (`ProgrammaticChange`) e usor de lovit accidental de orice cod
ulterior care "resincronizeaza UI-ul cu modelul" intr-un loc gresit.
2. **Gap preexistent, nu introdus de S3b, dar mostenit prin "muta ca atare"**: `inainte_de_do_renunt`
(`:3025-3030`) nu dezaloca numarul POS (26) la anulare — doar chitanta (16) si bon fiscal (3). Daca
formularul unificat introduce vreo cale noua de "renunta la document" care nu mapeaza exact pe acest
cod, riscul se poate agrava (numar POS ramas alocat orfan, de fiecare data cand utilizatorul incepe
un document cu POS si renunta). Recomandare: fie se corecteaza in trecere (nu e in perimetrul strict
al portarii "ca atare", dar e o linie), fie se semnaleaza explicit ca bug cunoscut de preluat separat.
3. **`_checkbox1`/"Listare detaliata" vs. `chkDetaliat`** (sectiunea 1.4) — doua controale cu semantica
probabil identica, ambiguitate deja semnalata in S1 (randul 57), neinchisa aici. Formularul unificat
trebuie sa aleaga unul singur; alegerea gresita (portarea amandurora ca doua campuri distincte)
ar introduce un camp duplicat fara sens pentru utilizator.
4. **`do_dezactiveaza()` insuficient pentru read-only real** (sectiunea 2.1) — daca implementarea
apeleaza doar metoda existenta fara completarea de `ReadOnly`, decizia 26 nu e respectata mecanic:
campul arata blocat (fara lupa) dar tot accepta taste.
5. **Referinte `Thisform.xxx` nekvalificate** in `actualizeaza_tipincasare` (sectiunea 3.2) — orice
decizie de a pune sectiunea pliata intr-un container/obiect separat (nu direct pe `Thisform`) rupe
tacit aceste referinte, cu eroare de "obiect inexistent" la runtime, nu la compilare.
6. **Dependinta pe S3**: `but_modifica` si testul "document existent" (sectiunea 5.1) nu exista inca
nicaieri in cod — proiectate doar pe hartie in plan (sectiunea I). Pasii 4 si 6 din sectiunea 7 nu
pot fi *implementati* complet inainte ca S3 sa livreze acel mecanism, doar proiectati.
7. **Bug-ul de `sectie`** (`omodificari.vc2:13941`, semnalat deja in `rute_scriere_antet.md` §1.3 si
in plan `:761-764`) — nu afecteaza direct S3b (analiticele sunt read-only aici, deci S3b nu scrie
niciodata `id_sectie`), dar afecteaza increderea in ce se **afiseaza**: daca bug-ul e real, coloana
`id_sectie` din Oracle poate sa nu reflecte eticheta aratata dupa o editare din #6. In afara
perimetrului S3b de reparat (e in #6), dar relevant pentru cine citeste afisarea din #13 dupa o
editare de la #6.
---
## 10. Criteriul de "gata", rescris verificabil
Textul din plan (`:1849-1852`) spune: *"un document cu incasare prin bon fiscal emis din formularul
unificat produce aceleasi randuri si acelasi numar de bon ca pe calea veche; deschiderea si inchiderea
formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar; iar pe un document
emis analiticele se vad dar nu se pot edita."* Precizat mai jos, pe fiecare bucata:
1. **Paritate de emitere, pe toate patru tipurile de incasare, nu doar bon fiscal**: se emite acelasi
document de test (client, articole, cantitati, preturi identice) o data pe calea veche
(`frm_date_factura`/`frm_date_aviz` -> `frm_facturare_articole(2)` -> `frm_alte_date`) si o data pe
formularul unificat, pentru fiecare din **Fara incasare / Chitanta / Bon fiscal / POS-Card**. Se
compara `lcListaIncasare` (stringul asamblat inainte de `scrie_factura2`, sectiunea 3.1 al
`rute_scriere_antet.md`) si numarul alocat (`poDate.nr_incasare`) — trebuie sa fie identice pe
fiecare din cele patru cazuri, nu doar pe bon fiscal.
2. **Non-alocare la toggle de pliere, testabila headless** (sectiunea 7, pasul 3, deja formulat ca
assert concret pe `poGeneratorNumere`) — pe **doua** scenarii de start, nu unul: document nou
(`poDate.incasat=0` la construire) **si** document venit din copiere cu incasare presetata
(`poDate.incasat<>0`, sectiunea 4.1 punctul 3) — al doilea caz e cel care azi *deja* aloca la
deschidere, exact scenariul pe care criteriul trebuie sa-l acopere explicit.
3. **Blocarea grupului C, pe ambele stari ale antetului**: pe un document deja emis, campurile din
grupul de incasare raman `Enabled=.F.` atat inainte cat si dupa apasarea lui `but_modifica` — nu
doar "la deschidere" (ce ar lasa loc unei regresii daca `but_modifica` le-ar debloca din greseala
odata cu restul).
4. **Analiticele, pe ambele stari**: pe un document emis, cele patru campuri (venit/cheltuiala, sectie,
responsabil, lucrare) afiseaza valoarea curenta din sursa lor (sectiunea 2.3 — de precizat exact
care sursa la implementare) si resping orice tentativa de tastare directa sau de deschidere a
dialogului de cautare — verificat pe proprietatea `ReadOnly`/`lactiv`, nu doar vizual.
5. **Delegat/transport/adresa/text aditional** — paritate camp-cu-camp cu `frm_alte_date` de azi, pe
cel putin un document proforma (grup delegat/incasare eliminat, sectiunea 1.4) si unul non-proforma
(grup complet), ca sa acopere ambele ramuri ale `Init`-ului de azi (`:3109-3207`).
*Depinde de:* S3 (but_modifica, mecanismul de blocare a antetului). *Nu depinde de:* etapa II
(regenerarea) — grupurile B/C raman blocate prin design in etapa I, nu prin absenta temporara a unei
functionalitati care ar trebui testata aici.
---
## Ce ramane de decis de Marius
1. **Textul exact al tooltip-urilor** pentru analiticele read-only (sectiunea 2.2) si pentru grupul de
incasare blocat pe document emis (sectiunea 5.3) — doar continutul minim necesar e propus aici, nu
formularea finala.
2. **Granularitatea butonului/butoanelor de pliere**: un singur comutator pentru toata zona "alte date
+ analitice", sau unul separat per subgrup (delegat/transport, incasare, adresa, text, analitice)?
Plan sectiunea I nu specifica, iar tiparul gasit (`afiseaza_rulaje`) e per-sectiune unica, nu
per-formular-intreg — precedentul nu decide singur granularitatea (sectiunea 6.1).
3. **Eager vs. lazy pentru lookup-urile Oracle din `Init`** (delegat/masina ultima factura, casa) —
semnalat si in `s3_portare_antet.md` §5 ca discutie deschisa, reconfirmat aici pentru acelasi cod
(sectiunea 6.1, ultimul punct).
4. **Corectarea gap-ului de dezalocare POS la anulare** (sectiunea 9, punctul 2) — se repara in trecere
ca parte a S3b, sau se lasa exact ca azi si se semnaleaza separat ca bug de preluat ulterior?
5. **`_checkbox1` vs. `chkDetaliat`** (sectiunea 1.4, sectiunea 9 punctul 3) — care dintre cele doua
controale de "listare detaliata" devine campul unic in formularul unificat; ambiguitatea ramane
deschisa din S1 si nu s-a inchis aici.
## Handoff
Cercetare incheiata, nu intrerupta la mijloc — toate cele zece sectiuni cerute in briefing sunt
complete, cu citate `fisier:linie` verificate direct pe fisierele reale (nu `.bak`). Niciun cod
atins, nicio interogare Oracle rulata (nu a fost nevoie — tot ce trebuia era in codul VFP deja citit
sau in cele patru rapoarte de referinta). Fisierul e complet la aceasta versiune; nu e nevoie de o
sesiune de continuare pentru S3b ca atare. Urmatorul pas natural (nu al acestei sarcini) ar fi S3
insusi (blocarea antetului, `but_modifica`) — S3b depinde de el pentru pasii 4 si 6 din sectiunea 7,
dar proiectarea de aici nu asteapta acel livrabil, doar implementarea o va astepta.

View File

@@ -1,426 +0,0 @@
# S3c — Proiectare: sursa ca parametru, nu ca global
Cercetare read-only pentru povestea **S3c** din `docs\plan_13_unificare_formular_facturare.md`
(`#### S3c`, linia 1855; decizia 12, linia 473; L.3, linia 1733; „Canalul de precompletare”,
liniile 465-474). Zero modificari de cod, zero write-back, zero `git_sync.ps1`, zero commit — in
niciun produs din `D:\ROA`. Depinde de S2 (`docs\cercetare\s2_factureaza_unificare.md`), a carui
proiectare a lasat explicit semnatura lui `factureaza` neschimbata „pentru ca e treaba lui S3c”
(sectiunea 5 a acelui raport).
Status: **complet**.
---
## Verdict
**Se poate face, e o interventie mica pe cod, dar textul planului supraestimeaza cat de „globala"
e problema azi.** `goContract` nu e niciodata scris in `ROAFACTURARE` — nici in codul specific
produsului, nici in `COMUN`-ul lui — deci ramura de precompletare din contract
(`ofacturare_comun.prg:261-297`) e **cod mort in ROAFACTURARE azi**, nu doar teoretic riscant.
Asimetria de resetare semnalata la L.3 (`ofundal_facturare.vc2:886-902`) exista textual, dar
**nu produce niciun efect observabil azi**, pentru ca nu exista niciun scriitor al lui `goContract`
in acest produs care sa lase ceva de resetat. Ea devine un risc real abia daca cineva adauga in
viitor, in `ROAFACTURARE`, un cod care scrie `goContract` — situatie in care lipsa resetarii ar
deveni activa dintr-o data, tacut. `goComanda`, in schimb, chiar e viu in `ROAFACTURARE`: are doi
scriitori (`ocomenzi.vc2:1583` la clic pe „Factureaza”, **si** `ocomenzi.vc2:2199`, ca efect
colateral al navigarii in grid), iar reset-ul explicit de la `ofundal_facturare.vc2:900` e singura
plasa de siguranta reala azi.
**Suprafata de regresie e mica si masurata exact**: doi scriitori de convertit
(`ocomenzi.vc2:1580-1596` in familia ROAFACTURARE, `ferestre_contracte.vc2:1538-1549`+`:1598-1606`
in ROACONTRACTE), trei fisiere comune de atins o singura data fiecare
(`ofacturare.prg`, `oproceduri_facturare.prg`, `ofacturare_comun.prg`), si **zero schimbari** la
celelalte ~31 puncte de intrare din inventarul S2 — toate cheama `factureaza(N)` cu un singur
parametru, deci un al treilea parametru opozitional cu implicit `.F.`/`NULL` nu le atinge.
**Corectie de scop fata de formularea planului**: „pana se convertesc toti apelantii" nu poate
insemna, pentru `goContract`, „pana dispare global-ul" — `goContract` e bufferul de editare al
intregului ecran de contracte din ROACONTRACTE (peste 100 de `ControlSource` legate de el,
sectiunea 1), populat continuu de navigarea in grid, independent de facturare. Nu va disparea
niciodata din ROACONTRACTE la aceasta poveste sau la vreuna viitoare rezonabila — S3c schimba doar
**canalul prin care valoarea ajunge la `oDateFactura.Init`**, nu existenta globalei in ROACONTRACTE.
Criteriul de „gata” de mai jos (sectiunea 8) e rescris sa reflecte asta.
---
## 1. Inventarul scrierilor si citirilor `goComanda` / `goContract` in toata suita
Cautare in `D:\ROA\ROAFACTURARE`, `D:\ROA\ROACONT`, `D:\ROA\ROAGEST`, `D:\ROA\ROAAUTO`,
`D:\ROA\ROAACNPRO`, `D:\ROA\ROAIMOB`, `D:\ROA\ROACONTRACTE` — radacina fiecarui produs **si**
`COMUN\`-ul lui explicit (capcana „Grep nu vede COMUN”). `D:\ROA\COMUNROA` nu contine niciuna din
cele doua variabile (cautare separata, zero rezultate) — confirma ca sursa nu e livrata prin
biblioteca partajata, ci prin fisierele COMUN duplicate per produs.
### `goComanda`
| Fisier | Linie | Rol | Produse |
|---|---|---|---|
| `Programe\roafacturare.prg` | `:473-474` | **Declarare**: `PRIVATE goComanda` / `goComanda = null`, la pornirea aplicatiei | doar ROAFACTURARE (fiecare produs are propriul `roafacturare.prg`/echivalent, nu verificat identic — irelevant, doar initializeaza la null) |
| `COMUN\clase\ocomenzi.vc2:1580-1596` (`ct_comenzi.do_factura`) | `:1583` | **SCRIE**: `SELECT crsComenzi` / `SCATTER NAME goComanda MEMO`, apoi cheama `facturare_comenzi` | ROAFACTURARE, ROACONT, ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB (fisier identic pe MD5, sectiunea 6) |
| `COMUN\clase\ocomenzi.vc2:2191-2207` (`_grdrow1.AfterRowColChange`) | `:2199` | **SCRIE** ca efect colateral: la fiecare schimbare de rand in grid-ul de comenzi, `SCATTER NAME goComanda MEMO`, folosit doar pentru `but_factura1.Visible = (goComanda.facturat = 0)` (`:2202`) — **nu are nicio legatura cu intentia de facturare**, dar suprascrie global-ul de fiecare data cand utilizatorul navigheaza in grid | idem, acelasi fisier identic |
| `COMUN\programe\ofacturare_comun.prg:301-328` (`oDateFactura.Init`) | `:301` | **CITESTE**: `If tnTip = 3 And Type('goComanda') = 'O'` -> `.id_client`, `.nume_client`, `.cod_fiscal`, `.listaid`, `.descriere`, `.id_sectie`, plus un `SELECT sectie FROM nom_sectii` pe `goComanda.id_sectie` | identic pe 6/7 produse (sectiunea 6; ROAIMOB diverge cu o linie, dar nu pe acest bloc) |
| `Clase\ofundal_facturare.vc2:899-902` (`Page2.Cw3.do_actiune`) | `:900` | **RESETEAZA** explicit: `goComanda = ''` inainte de `DO facturare_comenzi` | doar ROAFACTURARE (fisierul e specific produsului, nu COMUN) |
Niciun alt fisier, in niciun produs verificat, nu scrie sau citeste `goComanda`.
### `goContract`
| Fisier | Linie | Rol | Produse |
|---|---|---|---|
| `ROACONTRACTE\Programe\roacontracte.prg:559-560` | `:559-560` | **Declarare**: `Public poCtr, goContract` / `Store '' To poCtr, goContract`, la pornirea aplicatiei | doar ROACONTRACTE |
| `ROACONTRACTE\Clase\ferestre_contracte.vc2` | ~200 aparitii (liniile 977-11005, tabel complet in sectiunea de cautare) | **SCRIE si CITESTE** ca buffer de editare al intregului formular de contracte: `Scatter Name goContract Memo Blank` la creare (`:1026`, `:1186`), `SCATTER NAME goContract MEMO` la fiecare schimbare de rand in grid (`grid_contracte.AfterRowColChange`, `:1598-1606`), **peste 20** `ControlSource = "goContract.<camp>"` pe controale (combo-uri, textbox-uri), `Gather Name goContract Memo` la salvare (`:1053`, `:2406-2472` etc.) | doar ROACONTRACTE |
| `ROACONTRACTE\Clase\ferestre_contracte.vc2:1538-1549` (`but_factura.Click`) | — | **CITESTE implicit** (nu re-scrie): cheama `facturare_contracte` fara sa re-populeze `goContract` — se bazeaza pe scrierea facuta deja de `AfterRowColChange` la selectia randului curent | doar ROACONTRACTE |
| `ROACONTRACTE\Clase\outlook2003bar.vc2`, `ofundal_roaclienti.vc2` | multiple | **CITESC** `goContract.id_ctr`/`.id_part` pentru navigare in bara laterala si ecrane de parteneri | doar ROACONTRACTE |
| `ROACONTRACTE\Programe\roacontracte.prg`, `oparteneri_contracte.prg`, `oproceduri_roacontracte.prg` | multiple | **CITESC/SCRIU** `goContract` in fluxuri proprii ROACONTRACTE (incasari, plati, rate, garantii) — complet independente de facturare | doar ROACONTRACTE |
| `COMUN\programe\ofacturare_comun.prg:261-297` (`oDateFactura.Init`) | `:261` | **CITESTE**: `If INLIST(m.tnTip, 2, 6, 52) And Type('goContract') <> 'U'` -> `.id_client`, `.nume_client`, `.cod_fiscal`, `.listaid`, `.descriere`, `.id_sectie`, `.sectie`, `.id_responsabil`, `.responsabil`, `.id_valuta`, `.nume_valuta`, plus interogare `fact_vcontracte` pentru scadenta | identic pe 6/7 produse (ROAIMOB diverge cu exact aceasta linie lipsa — sectiunea 6) |
| `COMUN\clase\ferestre_atasamente.vc2`, `COMUN\programe\oproceduri_atasamente.prg` | 5 aparitii | **CITESC** `goContract.id_ctr`, dar **numai** in ramura `Case Upper(Alltrim(gcNumeProgram)) = "ROACONTRACTE"` — cod prezent in fiecare copie COMUN (deci si in ROAFACTURARE), dar mort acolo pentru ca `gcNumeProgram` nu e niciodata `"ROACONTRACTE"` in afara procesului ROACONTRACTE | identic pe toate produsele verificate, dar activ doar in ROACONTRACTE |
**Confirmare directa: `goContract` nu e scris niciunde in `ROAFACTURARE`** — nici in `Programe\`,
`Clase\`, `Ferestre\`, `Meniuri\`, nici in `COMUN\` (cautare pe `PUBLIC goContract`/`Public
goContract` in tot arborele: zero rezultate, tabelul complet cu toate declaratiile `PUBLIC go*`
gasite e in sectiunea 2). Singura mentiune a lui `goContract` in ROAFACTURARE e citirea garda de
`Type()` din `ofacturare_comun.prg:261`, care ramane mereu falsa (`Type('goContract') = 'U'`) intr-o
sesiune ROAFACTURARE.
## 2. Verdict pe L.3 — asimetria de resetare
**Textul din cod e adevarat, dar efectul practic e altul decat sugereaza formularea „de verificat”
din plan.**
- **Asimetria exista, literal**: `ofundal_facturare.vc2:899-902` (`Page2.Cw3`, ruta genericaa de
comanda) face `goComanda = ''` inainte de `DO facturare_comenzi`; `ofundal_facturare.vc2:886-897`
(`Page2.Cw2`, ruta generica de contract) **nu** face echivalentul pentru `goContract` — cheama
direct `DO facturare_contracte WITH 'FACTURA LEI'/'INVOICE'/'FACTURA VALUTA'`.
- **Dar `facturare_contracte` (`oproceduri_facturare.prg:119-136`) nu citeste si nu scrie
`goContract` deloc** — primeste un string de tip (`'FACTURA LEI'` etc.) si cheama direct
`factureaza(2)`/`factureaza(6)`/`factureaza(52)`. Precompletarea din contract se intampla
**exclusiv** in `oDateFactura.Init`, la citirea globalei — nu exista niciun pas intermediar care
ar putea fi „resetat”.
In ROAFACTURARE, calea genericaa (Cw2) las utilizatorul sa aleaga contractul **in formular**,
printr-un combo populat din `crscontracte` legat de `poDate.listaid`
(`ofacturare.prg:283-291,433-440`) — un canal complet diferit, care nu trece prin `goContract`
deloc (confirmat citind codul, sectiunea 3).
- **Deci, in ROAFACTURARE azi, nu exista nicio secventa executabila in care `goContract` sa fie
citit cu o valoare veche.** Pentru ca asta sa se intample ar trebui ca (a) ceva sa fi scris
`goContract` mai devreme in aceeasi sesiune ROAFACTURARE — **nu exista niciun asemenea cod azi** —
si (b) urmatorul apel sa fie cu `tnTip` in `(2, 6, 52)`. Fara (a), (b) singur nu ajunge nicaieri.
- **Verdictul pe L.3**: asimetria de resetare **e reala ca defect de simetrie in cod** (o ruta isi
curata globala inainte de apel, cealalta nu), dar **inert azi in ROAFACTURARE** — nu exista niciun
document gresit care se poate emite azi din cauza ei, pentru ca variabila pe care ar trebui sa o
resetezi nu e niciodata populata in acest produs. Devine un risc real **doar** daca un cod viitor
(in ROAFACTURARE) incepe sa scrie `goContract` fara sa adauge simetric si resetul — exact genul de
capcana pe care „parametru explicit, fara global implicit” o elimina structural, nu prin inca o
linie de reset de tinut minte.
- **In ROACONTRACTE, unde `goContract` chiar e viu, nu exista o ruta „generica fara precompletare”
analoaga lui Cw2** — `but_factura.Click` (`:1538-1549`) e singurul punct de intrare si presupune
intotdeauna un contract selectat in grid (populat de `AfterRowColChange`, `:1598-1606`, la fiecare
schimbare de rand). Riscul teoretic acolo nu e „global ramas din alta sesiune de facturare”, ci
„utilizatorul apasa Factureaza fara sa fi selectat explicit un rand dupa un refresh programatic” —
un caz marginal, netratat de L.3 si nelegat de asimetria semnalata in plan.
## 3. Doar doua globale, sau mai sunt?
**Doar doua.** Verificat prin citirea completa a `oDateFactura.Init`
(`ofacturare_comun.prg:223-332`) si a rutarii cursoarelor de articole din `ofacturare.prg:260-308`:
- **`goDate`, `gnIdSet` ca surse de precompletare — nu exista.** `goDate` nu apare niciunde in
`ofacturare.prg`/`ofacturare_comun.prg` (cautare directa, zero rezultate). `gnIdSet` nu exista ca
atare — parametrul se numeste `tnIdSet`/`lnIdSet`, calculat **local** in `factureaza`
(`lnIdSet = 25000 + tnTip - 1 + gnScadereStoc * 10`, `ofacturare.prg:123`) din `tnTip` si un
optiune de configurare (`gnScadereStoc`), nu un canal de sursa.
- **Singurele doua verificari `Type('go...')` din `oDateFactura.Init` sunt exact `goContract`
(`:261`) si `goComanda` (`:301`)** — cautare `Type\('go` pe tot `ofacturare.prg` +
`ofacturare_comun.prg`: zero alte rezultate in afara comutatorului de dezvoltator
`gnFacturareNou` (irelevant aici, documentat in S2).
- **`poDate.listaid` pentru avize (tip 4, 21, 28, 42, 47) NU trece printr-un global** — se scrie
direct din cursorul de selectie al formularului: `poDate.listaid = Iif(Inlist(poDate.tip, 3, 21,
28, 42, 47), Alltrim(Str(id_comanda)), [])` (`ofacturare_comun.vc2:4255`, si varianta similara la
`:4062`, `:7271`) — populat dintr-un `Scan`/`Locate` peste cursorul cu avizele bifate de utilizator
in acelasi apel, nu dintr-o variabila globala persistenta intre apeluri. **Avizele nu intra in
S3c** — nu au canal implicit de eliminat.
- **Copierea (`toFactura`) e deja parametru, nu global** — `copiere_factura(toFactura)`
(`oproceduri_facturare.prg:150-153`) -> `factureaza(toFactura.Tip, toFactura)`, si in
`oDateFactura.Init`/logica de copiere valorile vin din `toDateAnterior` (parametrul), de exemplu
`.listaid = toDateAnterior.id_vanzare` (`ofacturare_comun.vc2:387`) — **e exact precedentul pe
care S3c il extinde la comanda/contract**, nu un al treilea canal implicit de adaugat la lista.
- **`poDate` insusi nu e un canal de scurgere intre apeluri** — se creeaza cu `Createobject` la
fiecare intrare in bucla lui `factureaza` (`ofacturare.prg:184`, doar `If Type('poDate') <> 'O'`,
ceea ce e adevarat prima data si ramane fals doar in interiorul aceluiasi apel, la reintrarile
bucla `Do While lnRaspuns = 6`) — deci nu poate purta stare intre doua clicuri distincte pe
„Factureaza”.
**Concluzie**: `goComanda` si `goContract` sunt singurele doua canale implicite relevante pentru
decizia 12. Nu mai exista o a treia variabila de convertit odata cu ele.
## 4. Semnatura propusa
**`factureaza`** (`COMUN\programe\ofacturare.prg:81-82`), azi `Lparameters tnTip, toFactura`:
```
Lparameters tnTip, toFactura, toSursa
```
- **`toSursa`** — obiect, implicit `NULL` (nepasat de niciun apelant existent). Poarta **fie** un
obiect cu forma lui `goComanda` (cand `tnTip = 3`), **fie** un obiect cu forma lui `goContract`
(cand `tnTip IN (2, 6, 52)`) — exact aceeasi dualitate pe care codul de azi o rezolva deja prin
ramificare pe `tnTip` in `oDateFactura.Init` (`:261` vs `:301`), deci nu introduce un tip nou de
decizie, doar muta sursa valorii.
- **Pozitia**: al treilea parametru, dupa `toFactura`, niciodata inaintea lui — la fel ca precedentul
`V_TAXCODE`/`V_LOT` citat de tine: apelurile VFP sunt pozitionale, iar singurul mod sa nu rupi
apelantii existenti e sa adaugi la coada, cu implicit. Toate cele ~31 de apeluri din inventarul S2
care pasesc doar `tnTip` raman **neschimbate** — VFP completeaza automat parametrii nepasati la
coada cu `.F.` (verificat comportamental: `Type()` pe un parametru nepasat intoarce `'L'` cu
valoarea `.F.`, nu `'U'` — de tratat explicit in garda, vezi sectiunea 5).
- **De ce nu inlocuieste `toFactura`**: `toFactura` inseamna „copiaza factura asta” (un document deja
emis), `toSursa` inseamna „precompleteaza din documentul asta” (o comanda sau un contract inca
nefacturat) — semantic distincte, si `copiere_factura` (`oproceduri_facturare.prg:150-153`) ar
putea teoretic avea nevoie de amandoua simultan in viitor (copiere + realocare pe alt contract) —
motiv suplimentar sa nu le contopesti intr-un singur parametru.
**Constructorul `oDateFactura.Init`** (`ofacturare_comun.prg:223-224`), azi
`Lparameters tnIdSet, tnTip`:
```
Lparameters tnIdSet, tnTip, toSursa
```
- Aceeasi regula de pozitionare: la coada. Singurul apelant azi e
`Createobject("oDateFactura", lnIdSet, tnTip)` din `factureaza` (`ofacturare.prg:184`) — devine
`Createobject("oDateFactura", lnIdSet, tnTip, toSursa)`. Nu exista alt loc din cod care
instantiaza `oDateFactura` (cautare `Createobject("oDateFactura"` pe tot `COMUN`: un singur
rezultat).
## 5. Mecanismul de sursa de rezerva
**Regula**: la fiecare din cele doua ramuri, se prefera `toSursa` daca a fost pasat ca obiect; daca
nu, se cade pe globala, exact ca azi. Nu se schimba forma datelor citite (aceleasi campuri), doar
sursa lor.
```foxpro
* ofacturare_comun.prg, oDateFactura.Init — inlocuieste liniile 261 si 301
Local loSursa
loSursa = Iif(Type('toSursa') = 'O', toSursa, Null)
If INLIST(m.tnTip, 2, 6, 52) And (Type('loSursa') = 'O' Or Type('goContract') <> 'U')
If Type('loSursa') <> 'O'
loSursa = goContract
Endif
.id_client = loSursa.id_part
.nume_client = loSursa.denumire
* ... restul neschimbat, doar goContract -> loSursa
Endif
loSursa = Iif(Type('toSursa') = 'O', toSursa, Null) && re-evaluat, nu se refoloseste variabila de mai sus intre ramuri
If tnTip = 3 And (Type('loSursa') = 'O' Or Type('goComanda') = 'O')
If Type('loSursa') <> 'O'
loSursa = goComanda
Endif
.id_client = loSursa.id_part
* ... restul neschimbat, doar goComanda -> loSursa
Endif
```
De ce doua evaluari separate ale lui `loSursa` (nu una singura la inceput): `toSursa` poarta o
singura forma per apel (fie comanda, fie contract, niciodata amandoua — `tnTip` decide exclusiv
care), dar garda trebuie sa verifice `tnTip` inainte sa presupuna forma, la fel ca azi. O variabila
locala unica evita sa scrii `toSursa` de doua ori in tot blocul, fara sa schimbe logica.
**Cum se stie cand se poate scoate globala** — criteriu verificabil, nu „cand se convertesc toti”:
- **Pentru `goComanda`**: nu se poate scoate niciodata complet din `ROAFACTURARE`, pentru ca al
doilea ei scriitor (`ocomenzi.vc2:2191-2207`, `AfterRowColChange`) nu are nicio legatura cu
`factureaza` — exista doar pentru `but_factura1.Visible`. Criteriul realist: **linia de citire in
`oDateFactura.Init` (`:301`) poate deveni neconditionata de fallback abia cand se confirma, prin
`git_sync.ps1` + Grep, ca niciun apel la `factureaza(3, ...)` mai lasa `toSursa` nepasat** — adica
se verifica apelantii lui `factureaza`, nu scriitorii globalei (care raman, pentru alt scop).
- **Pentru `goContract`**: acelasi lucru, dar cu o observatie in plus — global-ul insusi
(`Public goContract` in `roacontracte.prg:559-560`) **nu va disparea niciodata** cat timp exista
formularul de contracte din ROACONTRACTE, care il foloseste ca buffer de editare, nu doar ca
transport spre facturare. Criteriul de scos fallback-ul: **cand toate apelurile catre
`factureaza(2/6/52, ...)`, in toate produsele, pasesc `toSursa` explicit** — verificabil cu
aceeasi comanda `Select-String` ca in S2, aplicata pe `factureaza(2` / `factureaza(6` /
`factureaza(52` in loc de `factureaza2`.
- Pana atunci, fallback-ul ramane — el nu costa nimic in productie (o verificare `Type()` in plus),
si e singura plasa de siguranta pentru cele ~31 puncte de intrare care nu au fost, nu vor fi, si
nu trebuie sa fie convertite (nu au sursa de precompletat).
## 6. Cele N copii ale fisierelor atinse — identice sau divergente?
MD5 pe fiecare fisier, in cele 7 produse care au copie (`ROAFACTURARE`, `ROACONT`, `ROAGEST`,
`ROAAUTO`, `ROAACNPRO`, `ROAIMOB`, `ROACONTRACTE`):
| Fisier | Rezultat |
|---|---|
| `COMUN\programe\ofacturare.prg` (2662 linii) | **Identic pe toate cele 7** — un singur MD5 |
| `COMUN\programe\oproceduri_facturare.prg` (2393 linii) | **Identic pe toate cele 7** — un singur MD5 |
| `COMUN\clase\ocomenzi.vc2` (8301 linii) | **Identic pe cele 6** care il au (nu verificat separat in ROACONTRACTE, care nu are comenzi) |
| `COMUN\programe\ofacturare_comun.prg` (2224 linii) | **Identic pe 6 din 7** — `ROAIMOB` diverge cu **exact o linie lipsa**: `:264`, `.cod_fiscal = ALLTRIM(NVL(goContract.cod_fiscal, ''))`, absenta din copia ROAIMOB (2223 linii). Restul fisierului, inclusiv blocul `goComanda`/`goContract` de la `:261-328`, e identic caracter cu caracter. |
**Spre deosebire de `ofacturare.vc2` (7 copii, 4 variante MD5 distincte, citat ca precedent de
comparat)**, fisierele atinse de S3c sunt aproape perfect sincronizate — un singur punct de drift,
minor si izolat. Concluzie pentru propagare: modificarea din `ofacturare.prg` si
`oproceduri_facturare.prg` se poate copia **caracter cu caracter** in toate cele 7 produse fara nicio
adaptare. Modificarea din `ofacturare_comun.prg` se poate copia identic in 6 produse, dar **in
ROAIMOB trebuie aplicata pe baza divergentei existente** (linia `cod_fiscal` lipseste deja acolo —
o copiere oarba a diff-ului ar putea sa nu se aplice curat sau sa reintroduca acea linie fara sa fie
intentionat; de verificat manual la sincronizare, nu doar copiat).
`ferestre_contracte.vc2` (ROACONTRACTE) e specific produsului, nu are copii de sincronizat.
## 7. Suprafata de regresie masurata
**Fisiere care chiar se modifica** (5, plus 1 verificare de sincronizare):
| # | Fisier | Ce se schimba | Cate copii de propagat |
|---|---|---|---|
| 1 | `COMUN\programe\ofacturare.prg:81-82` | semnatura `factureaza`, +`toSursa` | 7 (identice, copiere directa) |
| 2 | `COMUN\programe\ofacturare_comun.prg:223-224, 261-328` | semnatura `Init`, +`toSursa`; ramurile de citire cu fallback | 7 (6 identice + ROAIMOB cu drift de reconciliat) |
| 3 | `COMUN\programe\oproceduri_facturare.prg:119-141` | `facturare_contracte(tcTip, toSursa)`, `facturare_comenzi(toSursa)`, relay catre `factureaza(N, NULL, toSursa)` | 7 (identice, copiere directa) |
| 4 | `COMUN\clase\ocomenzi.vc2:1580-1596` (`do_factura`) | `SCATTER NAME loComanda MEMO` (local, nu global) + `DO facturare_comenzi WITH loComanda IN oproceduri_facturare.prg` | 6 (identice, copiere directa) |
| 5 | `ROACONTRACTE\Clase\ferestre_contracte.vc2:1538-1549` (`but_factura.Click`) | trimite `goContract` (sau o copie locala scatter-uita din randul curent) explicit ca al doilea parametru la `DO facturare_contracte WITH lcTip, goContract IN ...` | 1 (specific ROACONTRACTE) |
**Apelanti care NU se modifica** (confirmat, nu presupus): toate cele ~31 de puncte de intrare din
inventarul S2 (`Meniuri\politica.mn2`, `Meniuri\contracte.mn2`, `Meniuri\aviz_*.mn2`,
`ofundal_facturare.vc2:883-905`) cheama `factureaza(N)` cu un singur argument sau
`facturare_contracte`/`facturare_comenzi` fara sursa — al treilea parametru ramane implicit `NULL`,
fallback-ul pe global preia (pentru cele care oricum nu aveau sursa reala, fallback-ul da acelasi
rezultat ca azi: nimic de precompletat). **Inclusiv `ofundal_facturare.vc2:886-902` (Page2.Cw2/Cw3)
raman neschimbate** — `Cw3` isi pastreaza `goComanda = ''` (devine redundant, dar inofensiv, odata ce
`do_factura` trimite explicit; se poate curata separat, nu la aceasta poveste), `Cw2` ramane cum e.
**Total: 5 fisiere reale de editat, in 4 continuturi distincte de modificare** (fisierele #1-#3 au
acelasi continut in toate copiile lor), propagate in pana la 7 produse — mult sub suprafata unei
modificari „de suita” tipice, pentru ca `ofacturare.prg`/`oproceduri_facturare.prg` sunt deja
perfect sincronizate (sectiunea 6).
## 8. Ordinea de executie, ca suita sa nu fie stricata niciun moment
Spre deosebire de S2 (unde `factureaza2` nu avea alt apelant decat el insusi, deci risc de stricare
aproape nul), aici **exista o fereastra in care semnaturile trebuie sa fie compatibile intre fisiere
diferite** (`ofacturare.prg` cheama `oDateFactura::Init`, `oproceduri_facturare.prg` cheama
`factureaza`) — ordinea trebuie sa garanteze ca niciun punct intermediar nu are un apelant cu
semnatura veche impotriva unui apelat cu semnatura noua incompatibila. Pentru ca **toti parametrii
noi sunt optionali, la coada**, riscul e de fapt mic — o semnatura veche care cheama o rutina noua
functioneaza (parametrul lipsa devine implicit), problema ar aparea doar invers (rutina veche
chemata cu un parametru in plus, care s-ar ignora silentios in VFP — tot fara eroare, dar fara
efect). Deci ordinea de mai jos e despre corectitudine, nu despre a evita crash-uri:
1. **`oDateFactura.Init`** (`ofacturare_comun.prg`) — adauga `toSursa`, muta logica de citire pe
modelul din sectiunea 5. Se poate face si testa izolat: fara niciun apelant care sa paseze
`toSursa` inca, comportamentul ramane identic cu azi (fallback pe global, mereu).
2. **`factureaza`** (`ofacturare.prg`) — adauga `toSursa`, il paseaza la `Createobject("oDateFactura",
lnIdSet, tnTip, toSursa)`. Inca niciun apelant nu paseaza `toSursa` — comportament neschimbat.
3. **`oproceduri_facturare.prg`** — `facturare_contracte`/`facturare_comenzi` primesc `toSursa` si il
relaeaza. Inca niciun apelant real nu il paseaza — comportament neschimbat.
4. **Abia acum, apelantii reali**: `ocomenzi.vc2:do_factura` trimite `loComanda` explicit;
`ferestre_contracte.vc2:but_factura.Click` trimite `goContract` explicit (in ROACONTRACTE). Din
acest punct, comportamentul chiar se schimba (sursa vine din parametru, nu din citirea globalei),
dar rezultatul trebuie sa fie identic — parametrul poarta exact aceleasi campuri pe care globala
le avea.
5. **Sincronizare in celelalte 6 produse**: pasii 1-3 se copiaza caracter cu caracter (identice,
sectiunea 6), cu atentia speciala la ROAIMOB (drift de o linie). Pasul 4 pe partea de comenzi se
copiaza si el (fisierul e identic in toate). Nu exista pas 4 de propagat pe partea de contract in
afara de ROACONTRACTE (nu exista alt produs cu formular de contracte care sa scrie `goContract`).
6. **Un singur produs se poate testa complet integrat de la sine**: ROAFACTURARE (pasii 1-4, partea
de comanda). ROACONTRACTE cere o rulare separata (build propriu, sincronizat manual) pentru
pasul 5 pe partea de contract — acelasi tip de limitare semnalata deja in S2 pentru
`factureaza2`.
## 9. Ce nu se poate testa headless
- **Toate cele ~31+2 puncte de intrare sunt declansate din UI** (clic pe buton, `ON SELECTION BAR`
in meniu) — niciunul nu are un test headless existent, la fel ca in S2.
- **Testarea „parametrul castiga peste global” cere manipulare de sesiune**: singurul mod sa verifici
ca `toSursa` are prioritate e sa populezi manual `goComanda`/`goContract` cu o valoare **diferita**
de cea din parametru si sa confirmi ca documentul rezultat foloseste parametrul — asta cere fie
cod de test care seteaza global-ul inainte de apel (posibil headless, dar artificial fata de
fluxul real), fie o sesiune interactiva VFP cu comenzi tastate manual in fereastra de comenzi.
- **Efectul colateral al `AfterRowColChange` (`ocomenzi.vc2:2199`) nu se poate simula headless** —
harnessul `-A -T` nu declanseaza fiabil evenimente de grid (confirmat de precedentul din memorie
„Coloanele de grid nu se materializeaza headless”), deci nu se poate verifica automat ca navigarea
in grid tot suprascrie `goComanda` dupa modificare (comportament care ramane neschimbat, dar
trebuie confirmat vizual, nu presupus).
- **ROACONTRACTE nu poate fi testat din arborele de lucru ROAFACTURARE** — cere build si rulare
separata, cu propriul executabil si propria sincronizare a `ofacturare.prg`/`ofacturare_comun.prg`
/`oproceduri_facturare.prg` (nu sunt acelasi fisier fizic, sunt copii).
- **Rezultatul final** (facturare emisa cu antetul corect precompletat din comanda/contract) se
verifica doar prin continutul lui `crsfactura`/`VANZARI`/formularul de antet, vizual sau prin
interogare Oracle read-only dupa emitere — nu exista assert automat de facut.
## 10. Riscuri si ce ramane de decis de Marius
**Riscuri**:
- **Cel mai probabil sa se strice**: garda `Type('loSursa') = 'O' Or Type('goContract') <> 'U'` din
sectiunea 5 — daca se scrie gresit (de exemplu `And` in loc de `Or`), fallback-ul pe global s-ar
rupe silentios pentru toti apelantii care inca nu au fost convertiti, fara nicio eroare vizibila
(`oDateFactura` ar porni pur si simplu fara precompletare). Se testeaza explicit pe cel putin un
apel neconvertit (orice `factureaza(N)` cu un singur argument, tip 1) dupa modificare.
- **Al doilea cel mai probabil**: parametrul nou nepasat trebuie verificat `Type('toSursa') = 'O'`,
nu `Type('toSursa') <> 'U'` — un parametru VFP nepasat nu e `'U'` (asta e pentru variabile
nedeclarate), ci `'L'` cu valoarea `.F.` implicita a lui `LPARAMETERS`. O garda gresita
(`<> 'U'`) ar trece mereu adevarat si ar incerca sa citeasca `.id_part` de pe `.F.`, eroare de
rulare imediata la primul apel neconvertit. **De verificat cu un test minimal inainte de a atinge
fisierele reale** — nu presupune, verifica comportamentul `Type()` pe un parametru nepasat intr-un
`.prg` de proba.
- **ROACONTRACTE, singurul apelant real din alt produs** — acelasi risc semnalat in S2: netestabil
din acest arbore de lucru, cere confirmare manuala separata.
- **Drift-ul de o linie din `ofacturare_comun.prg` in ROAIMOB** — o copiere mecanica a diff-ului ar
putea esua silentios sau reintroduce linia lipsa fara sa fie intentia — de aplicat manual acolo,
nu prin copy-paste orb.
- **`Cw3.do_actiune` (`ofundal_facturare.vc2:900`) ramane cu `goComanda = ''` redundant** dupa
conversia lui `do_factura` — nu e o problema (ramane inofensiv), dar merita mentionat explicit ca
„lasat asa, intentionat” in codul livrat, ca sa nu para o omisiune la revizuirea diff-ului.
**Ce ramane de decis de Marius**:
1. **Forma lui `toSursa`**: ramane duck-typing pe obiect scatter (ca azi), sau se formalizeaza o
clasa cu doua forme (`oSursaComanda`/`oSursaContract`)? Recomandare: ramane duck-typing — nu
schimba nimic functional, adauga doar o clasa noua de intretinut pentru un beneficiu marginal la
aceasta poveste.
2. **Se face si conversia partii de comanda si cea de contract in acelasi commit, sau separat?**
Ambele ating exact aceleasi trei fisiere comune (`ofacturare.prg`, `ofacturare_comun.prg`,
`oproceduri_facturare.prg`) — separarea nu reduce suprafata de regresie pe fisierele comune, doar
amana testarea reala pe ROACONTRACTE. Recomandare: **un singur commit**, cu testare manuala pe
ambele produse inainte de a-l considera gata.
3. **Se curata acum redundanta `goComanda = ''` de la `Cw3` (sectiunea „Riscuri”), sau se lasa pentru
o poveste ulterioara de curatenie?** Nu afecteaza corectitudinea — decizie de stil, nu de
comportament.
4. **Criteriul de „gata” rescris** (inlocuieste formularea din plan, linia 1861-1862):
- *Structural*: `Select-String -Path ofacturare.prg,ofacturare_comun.prg,oproceduri_facturare.prg
-Pattern 'toSursa'` gaseste parametrul in toate cele trei fisiere, in toate cele 7 produse care
au copii, cu semnatura identica.
- *Comportamental, cale convertita*: facturarea pornita din `ocomenzi.vc2:do_factura` cu
`goComanda` populat manual cu **alta** comanda decat cea trimisa prin `toSursa` produce
documentul corespunzator parametrului, nu globalei — testat manual, cu global-ul deliberat
„murdar” dintr-o navigare anterioara in grid.
- *Comportamental, cale neconvertita*: oricare din cele ~31 de apeluri care trimit doar `tnTip`
produce acelasi document ca inainte de modificare (fallback pe global, sau fara precompletare
acolo unde nu exista global de citit).
- *ROACONTRACTE*: facturarea din `but_factura.Click` cu contractul trimis explicit prin
`toSursa` produce acelasi rezultat ca azi (precompletare identica), verificat manual pe build
separat.
---
## Ce nu s-a putut stabili si de ce
- **Continutul exact al obiectului `loComanda`/`goContract` scaturat** nu a fost comparat camp cu
camp intre ce citeste azi `oDateFactura.Init` si ce ar contine un `SCATTER` facut in afara
contextului `crsComenzi`/`cContracte` curent — presupunerea (rezonabila, dar neverificata pe date)
e ca structura cursorului nu se schimba intre cele doua puncte de scatter.
- **Daca exista alte produse din suita (dincolo de cele 7 verificate) care au propriile copii ale
`ofacturare.prg`/`ofacturare_comun.prg`/`oproceduri_facturare.prg`** — cautarea s-a limitat la
produsele numite explicit in sarcina; alte ~30 de produse mentionate in S2 ca avand copii ale
`ofacturare.prg` (ROARETAIL etc.) nu au fost verificate pentru `goComanda`/`goContract` la aceasta
poveste.
- **Testarea reala pe ROACONTRACTE** nu a fost efectuata (read-only, fara rulare de cod) — doar
confirmata structura codului sursa.
## Bug-uri semnalate, nereparate
Niciunul nou — cercetarea a confirmat un defect de simetrie deja semnalat in plan (L.3), l-a
verificat pe cod pana la verdictul „inert azi in ROAFACTURARE” (sectiunea 2), si nu a gasit alte
probleme in fisierele atinse.

View File

@@ -1,482 +0,0 @@
# Cercetare — proiectare S4: cautarea articolelor pe server, in linie
Investigatie READ-ONLY pentru povestea **S4** din `docs\plan_13_unificare_formular_facturare.md:1865-1874`,
plus sectiunea `### C. Incarcarea articolelor — de ce dureaza si ce inlocuieste` (`:589-614`).
Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit. `COMUN\clase\ofacturare_comun.vc2`
si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar citite, nu atinse. Pe Oracle doar
`SELECT`, prin exportul `PACK_FACTURARE` de pe disc — nu s-a rulat nimic pe server.
## Verdict (rezumat)
Mecanismul de inlocuire propus in plan **exista deja, dar e un prototip la jumatate**:
`grd_factura.cCodMat.cboCodmat` / `cCboDenumire` (`ofacturare.vc2:16751-16797`, wiring la
`:19288-19334`) cauta pe server prin `combosql` si scriu `codmat`/`denumire`/`id_articol` in
`crsfactura` — dar **nu scriu nimic altceva**, pentru ca sursa lor (`vnom_articole`) nu are pret,
TVA, valuta, `id_pol`, `gestionabil`. Asta confirma exact ce zice planul la punctul C: partea Oracle
de facut e o **varianta filtrata pe articol a celor cinci cursoare de facturare**, nu o cautare noua.
Descoperirea care schimba proiectarea fata de textul din plan: **`crsarticole` nu e doar sursa de
populare a gridului — e un registru al cantitatii ramase de facturat**, citit si scris de
`do_adauga_tot`, `do_sterge` si `do_scrie_factura` pentru toate tipurile cu document sursa (comanda,
aviz, contract-lista-de-preturi). Stergerea unei linii **reface** cantitatea in `crsarticole`
(`:14640-14669`), iar la scriere se face `Calculate Sum(cantitate) To lnCantitateRamasa` peste
`crsarticole` ca sa se decida daca se inchide automat comanda/avizul (`:14303-14311`, `:14334-14338`).
Asta inseamna ca **incarcarea in masa nu poate disparea pentru aceste tipuri**, indiferent de S4 —
nu doar pentru ca planul a decis sa pastreze "adauga tot", ci pentru ca bookkeeping-ul de cantitate
ramasa e cablat direct pe cursorul incarcat. S4 se aplica deci curat doar pe ramurile de **lista de
preturi** (`cursor_preturi`, plus jumatate din `cursor_contract` si `cursor_gestiune`) — vezi punctul 7.
A doua descoperire: calea de scriere a pretului la nivel de linie (`adauga_articol_factura`,
verificata separat in S10, `docs\cercetare\s10_pret_rederivat.md`) **primeste deja pretul ca
parametru** in loc sa-l re-deriveze — exact precedentul pe care trebuie sa-l urmeze si varianta
filtrata: cauta pretul o singura data, la alegerea liniei, si il transmite mai departe neschimbat.
## 1. Inventarul celor cinci cursoare Oracle
Sursa: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (corpul
pachetului; liniile de mai jos sunt pe corp, `:2138+`, nu pe spec `:335+` — offset **+17** fata de
citatele vechi din plan, confirmat si in S10).
### 1.1 `cursor_preturi` — lista de preturi, ramura principala (`:2138-2644`)
```
PROCEDURE cursor_preturi(V_DATA_CURS IN DATE, V_TIP IN NUMBER, V_ID_VALUTA IN NUMBER,
V_ID_GESTIUNE_INIT IN NUMBER, V_LUNA IN NUMBER, V_AN IN NUMBER,
V_ID_UTIL IN NUMBER, V_ID_SUCURSALA IN NUMBER,
V_CURSOR OUT cursor_facturare)
```
**Fara filtru pe articol** — semnatura n-are niciun parametru de cod/denumire. Ramuri pe `V_TIP`
(`CASE`, `:2159-2643`):
| Ramura | Cand | Sursa randurilor | Observatie |
|---|---|---|---|
| `V_TIP = 45` | restaurant | `utilizatori_rol_intern` -> `politici_grupuri` -> `crm_politici_preturi` -> `crm_politici_pret_art` -> `nom_articole`, plus `curs`/`nom_valute` | fara filtru de stoc, `cantitate` fixa la 1 (`:2193`) |
| `V_TIP IN (1,2)` | factura in lei | acelasi lant de politici, plus `LEFT JOIN` pe `STOC` agregat pe gestiunile utilizatorului | `WHERE` final filtreaza pe stoc > 0 sau `RF_FACTURARE_FARA_STOC` (`:2387-2388`) |
| `V_TIP IN (5,6,10,52)` | factura in valuta | `FACT_VPRETURI_UTILIZATOR` (view precalculat, nu politici brute) + `STOC` + `CURS` | `WHERE A.ID_UTIL = V_ID_UTIL AND ((A.ID_VALUTA = V_ID_VALUTA AND A.IN_VALUTA=1) OR A.ID_POL = politica_stoc)` (`:2461-2462`) |
| `V_TIP = 7` | credit note | `FACT_VPRETURI_UTILIZATOR`, filtrat suplimentar `NVL(A.nota_discount,0)=1` (`:2538`) | doar articole de discount |
| `ELSE` (aviz, tip implicit) | orice alt tip | `FACT_VPRETURI_UTILIZATOR`, fara filtrul de valuta din ramura 5/6/10/52 | ramura cea mai generala |
Coloane comune returnate (uniforme pe toate ramurile, ceea ce conteaza pentru mapare — punctul 6):
`id_c, id_articol, lot, serie, id_pol, id_valuta, nume_lista_preturi, discount_unitar,
discount_unitar_val, codmat, codbare, denumire, um, gestionabil, cantitate, proc_tvav,
preturi_cu_tva, curs, multiplicator, pret, pret_val, tip_valuta, nume_val` (+ `modificabil`,
`id_gestiune`, `cont` doar pe ramura restaurant).
Apeleaza intai `initializeaza_facturare`, `completare_politica_stoc` si
**`verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)`** (`:2149-2153`) — validare **neconditionata**
a cursurilor pentru toate valutele din listele de preturi ale utilizatorului, indiferent de articolul
cautat. Relevant pentru S4d (`-20005`).
### 1.2 `cursor_contract` — factura/aviz pe contract (`:2646-2950`)
```
PROCEDURE cursor_contract(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_LISTAID, V_ID_GESTIUNE_INIT,
V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA,
V_ID_AGENT OUT, V_NUME_AGENT OUT,
V_CURSOR OUT cursor_facturare, V_CURSOR2 OUT cursor_facturare)
```
**Doua cursoare de iesire.** `V_CURSOR2` (`:2718-2938`) e specific contractului: `UNION ALL` intre
(a) articole cu `OPT_FACTURARE = 3` din `CTR_ARTICOLE` si (b) rate din `CTR_SCADENTAR` pentru
`OPT_FACTURARE IN (1,2)`, cu `id_articol = NULL` pe randurile de rata — filtrate pe
`V_LISTAID` (lista de `id_ctr`) prin `charn2collection`. La final (`:2940-2948`) **cheama
`cursor_preturi` cu aceiasi parametri** si scrie rezultatul in `V_CURSOR` — deci jumatate din
`cursor_contract` e literalmente `cursor_preturi`.
In VFP, cele doua cursoare de iesire ajung (observat pe cod, nu documentat explicit in `goExecutor`)
in `crsarticole` (=`V_CURSOR`, lista de preturi) si `crsarticole1` (=`V_CURSOR2`, liniile contract +
rate) — vezi `ofacturare.vc2:13778` (`lcCursorStoc = Iif(poArticol.opt_facturare = 0, [crsarticole],
[crsarticole1])`) si `Destroy` (`:12804-12811`) care inchide ambele.
**Randurile de rata nu au `id_articol`** — nu pot fi gasite printr-o cautare cod/denumire; raman
legate de gridul `crsarticole1` existent (`grd_contracte`), bounded de numarul de rate ale
contractului, nu de catalog. Vezi punctul 7.
Valideaza cursurile valutare **doar** pentru valutele prezente in `CTR_SCADENTAR`/`CTR_ARTICOLE` ale
contractelor din `V_LISTAID` (`:2679-2716`) — spre deosebire de `cursor_preturi`, care valideaza tot
ce are utilizatorul in politici. Filtru deja ingust pe sursa, dar tot pe tot contractul, nu pe
articol.
### 1.3 `cursor_comanda` — factura/aviz pe comanda (`:2952-3171`)
```
PROCEDURE cursor_comanda(V_DATA_CURS, V_TIP, V_LISTAID, V_ID_UTIL, V_CURSOR OUT cursor_facturare)
```
`V_LISTAID` e **un singur `id_comanda`** (`TO_NUMBER(V_LISTAID)`, `:2963` — nu o lista, desi
parametrul se numeste la fel ca la contract/avize). Doua ramuri identice ca forma (`V_TIP <= 20` =
factura, altfel aviz, `:2994-3170`), ambele pe `COMENZI_ELEMENTE` filtrat `WHERE A.ID_COMANDA =
V_ID_COMANDA` (`:3078`, `:3166`) — **deja filtrat pe un singur document sursa**, nu pe tot catalogul.
`LEFT JOIN` cu suma cantitatilor deja facturate din `VANZARI_DETALII` pe acelasi `ID_COMANDA`
(`:3060-3068`) calculeaza `cantitate` ca **ramas de facturat**, nu cantitatea comandata bruta —
exact sursa pentru bookkeeping-ul de "adauga tot" / stergere de la punctul urmator.
### 1.4 `cursor_avize` — factura din avize (`:3703-3874`)
```
PROCEDURE cursor_avize(V_LISTAID, V_ID_UTIL, V_DISCOUNT OUT NUMBER, V_CURSOR OUT cursor_facturare)
```
`V_LISTAID` = lista de `id_vanzare` (avize sursa), separate prin virgula. O singura interogare,
fara `CASE` pe tip — agrega `VANZARI_DETALII` pe cheie compusa (articol, pol, lot, serie, discount,
TVA, gestiune, cont, pret...) si scade ce a fost deja facturat din `VANZARI_CANTITATI`
(`WHERE A.CANTITATE <> NVL(B.CANTITATE, 0)`, `:3871`) — acelasi tipar "ramas de facturat" ca la
comanda. **Nu are ramuri pe `V_TIP`** — planul o citeaza ca exemplu de "aceleasi ramuri pe tip", dar
real e cea mai simpla dintre cele cinci: un singur `SELECT`.
### 1.5 `cursor_gestiune` — transfer intre subunitati pe lista de preturi (`:4158-4310+`)
```
PROCEDURE cursor_gestiune(V_DATA_CURS, V_ID_POL, V_ID_GESTIUNE, V_LUNA, V_AN,
V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR OUT cursor_facturare)
```
Foloseste `V_ID_POL` **fix** (nu politica derivata din utilizator ca la `cursor_preturi`), pe
`STOC` filtrat `ID_GESTIUNE = V_ID_GESTIUNE` `JOIN` `CRM_POLITICI_PRET_ART WHERE ID_POL = V_ID_POL`
(`:4266-4280`) — deja restrans la o singura gestiune si o singura politica, dar tot fara filtru pe
articol; `GROUP BY` pe articol agrega stocul.
## 2. De unde sunt executate; cine consuma `crsarticole`
**Executia:** un singur loc, `ofacturare.prg:266-311` (`factureaza`), rutare pe `tnTip` (punct de
plecare confirmat, `Do Case` la `:266-308`) -> `goExecutor.oExecute(lcSqlCursor, [crsarticole])`
(`:310-311`). Acelasi tipar apare a doua oara in `factureaza2` (`ofacturare.prg:824+`, ramura
`frm_facturare_articole2`, prototipul).
**Consumatorii `crsarticole` in perimetrul S4** (`frm_facturare_articole`, `ofacturare.vc2:11xxx-15300`;
lista completa via `vfp_symbols -Grep 'crsarticole\b' -CodeOnly`):
| Metoda | Linii | Ce face cu `crsarticole` | Ramane dupa S4? |
|---|---|---|---|
| `Destroy` | `12804-12811` | inchide cursorul | da, neschimbat |
| `do_adauga_articol` | `12813-13167` | citeste **un rand ales** (`Scatter Name poArticol`), il scrie in `crsfactura` | **inlocuit** de randul intors de cautarea pe server, pe ramurile de lista de preturi — vezi punctul 7 |
| `do_adauga_tot` | `13169-13198` | `Scan` peste tot cursorul, cheama `do_adauga_articol` pe fiecare rand | **pastrat neschimbat**, dar numai pe tipurile cu document sursa |
| `do_cauta` | `13566-13593` | filtru client-side (`Set Filter`) peste cursorul deja incarcat, dupa text tastat in `txtArticole`/`txtCodmat` si politica aleasa | **dispare** pe ramurile de lista de preturi — inlocuit de `combosql` |
| `do_scrie_factura` | `14301-14338` | `Calculate Sum(cantitate) To lnCantitateRamasa` peste `crsarticole`, decide daca se inchide comanda/avizul sursa (`pnParametruAditional`) | **pastrat neschimbat** — vezi verdictul |
| `do_sterge` | `14608-14669` | la stergerea unei linii din `crsfactura`, **reface** cantitatea in `crsarticole`/`crsarticole1` (`Replace cantitate With cantitate + poArticol.cantitate`) | **pastrat neschimbat** pe tipurile cu bookkeeping; pe lista de preturi nu exista azi replace-back real (cantitatea nu scade la adaugare pe acele tipuri — stocul se verifica separat, prin `cursor_gestiuni_articol*`) |
| `do_modifica` | `13778` | alege `crsarticole` sau `crsarticole1` dupa `opt_facturare` pentru verificare stoc | pastrat, tine de gestiunea aleasa la editarea unei linii deja adaugate |
| `cb_politici_preturi.InteractiveChange` | `15647-15658` | cheama `do_cauta()` (filtru client-side) | **dispare** — pe varianta noua, schimbarea politicii devine parametru al cautarii pe server (`id_pol` in `WHERE`), nu filtru local |
| `KeyPress` (navigare grid) | `15463-15486` | navigare in cursor la taste sageata | **dispare** pe ramurile fara grid de sus |
`frm_facturare_articole2` (prototipul, `:17xxx-18xxx`) are exact aceeasi structura pe
`do_adauga_articol` / `do_adauga_tot` / `do_scrie_factura` / `do_sterge` — nu difera in aceasta
privinta.
**Concluzie pentru cat se poate scoate:** `do_cauta` si filtrul din `cb_politici_preturi` dispar
integral (inlocuite de cautarea pe server). `do_adauga_articol` isi schimba sursa randului (de la
`Scan` in cursorul local la randul ales de `combosql`), dar restul lui (verificare stoc, alegere
gestiune via `cursor_gestiuni_articol*`, scriere in `crsfactura`) **nu se schimba** — vezi punctul 6.
`do_adauga_tot`, `do_sterge` (partea de bookkeeping) si `do_scrie_factura` (`lnCantitateRamasa`)
**nu pot fi scoase** cat timp `crsarticole` ramane sursa lor de adevar pentru "cat a mai ramas" —
motiv suplimentar, nu doar UX, pentru care "adauga tot" ramane cablat pe incarcare in masa acolo
unde exista document sursa.
## 3. Cat costa azi incarcarea, pe forma interogarii (nu pe timpi masurati — decizia 31)
**`cursor_preturi` (ramurile 1/2 si generala) nu are niciun filtru pe articol** — semnatura n-are
parametru de cod/denumire (`:2138-2146`). Rezultatul e **intreg catalogul accesibil utilizatorului**:
`utilizatori_rol_intern` -> `politici_grupuri` -> `crm_politici_preturi` (toate politicile active la
data cursului) -> `crm_politici_pret_art` (toate liniile de pret ale acelor politici) -> `nom_articole`.
Numarul de randuri creste cu produsul dintre "cate politici de pret vede utilizatorul" si "cate
articole are fiecare politica" — nu cu numarul de articole cautate, care e de regula 1.
Trei surse structurale de cost, vizibile direct in forma interogarii, nu masurate:
1. **`LEFT JOIN` pe `STOC` agregat** (`:2359-2378`, ramura 1/2) — subquery cu `GROUP BY ID_ARTICOL`
peste tot stocul lunii curente, pe toate gestiunile la care utilizatorul are drept, recalculat la
fiecare deschidere de formular, indiferent daca utilizatorul cauta un singur articol.
2. **`FACT_VPRETURI_UTILIZATOR`** (ramurile 5/6/10/52 si 7 si aviz) e un view, nu un tabel — planul
nu detaliaza corpul lui aici (ar fi o cercetare separata), dar fiind sursa pentru "toate preturile
vizibile utilizatorului", are aceeasi forma: cost proportional cu marimea catalogului, nu cu
cautarea.
3. **`verifica_cursuri_valute`** (`:2153`, chemata necondiționat la fiecare apel) valideaza cursul
pentru **toate** valutele din politicile utilizatorului, nu doar valuta articolului cautat —
cost fix per deschidere, independent de ce se cauta.
Concluzia structurala: interogarea de azi calculeaza raspunsul pentru "orice articol ar putea alege
utilizatorul", cand formularul are nevoie doar de "articolul pe care tocmai l-a tastat". Filtrarea pe
`id_articol`/`codmat`/`denumire` reduce fiecare din cele trei surse la un numar de randuri marginit
de cate politici de pret contin acel articol (de regula 1, rar cateva), nu de marimea catalogului.
## 4. Proiectarea variantei filtrate, cursor cu cursor
Toate cinci sunt proceduri PL/SQL in `PACK_FACTURARE`, apelate direct din VFP prin
`{call pack.proc(...)}` + `goExecutor.oExecute`, fara view intermediar si fara strat ORM — deci
minimul de schimbare e **acelasi tipar**: o procedura noua (sau o supraincarcare cu parametru
suplimentar) in acelasi pachet, apelata din acelasi loc (`ofacturare.vc2`, metoda nou-introdusa pe
`combosql`, nu din `ofacturare.prg`, care ramane neschimbat — el tot incarca varianta "in masa"
acolo unde ramane necesara).
**Alegere de proiectare, nu fapt verificat:** supraincarcare (aceeasi denumire, parametru nou
opțional `V_FILTRU_COD IN VARCHAR2 DEFAULT NULL` / `V_FILTRU_DEN IN VARCHAR2 DEFAULT NULL`) e mai
sigura decat o procedura noua, pentru ca **garanteaza** aceleasi ramuri de `CASE`, acelasi `JOIN`,
aceeasi logica de rotunjire — orice divergenta viitoare intre "cursor complet" si "cursor filtrat" ar
fi un bug de sincronizare greu de prins. Cu supraincarcare, filtrul se adauga o singura data, in
`WHERE`-ul final al fiecarei ramuri, nu in logica de business.
| Cursor | Ce se adauga | Unde (linia `WHERE`/`CASE` care primeste filtrul) |
|---|---|---|
| `cursor_preturi` | `AND (V_FILTRU_COD IS NULL OR C.CODMAT LIKE V_FILTRU_COD) AND (V_FILTRU_DEN IS NULL OR UPPER(C.DENUMIRE) LIKE UPPER(V_FILTRU_DEN))` — pe alias-ul articolului, `C` pe patru din cinci ramuri, `A` pe ramurile cu `FACT_VPRETURI_UTILIZATOR` | dupa `WHERE` existent, pe fiecare din cele 5 ramuri (`:2264`, `:2387-2388`, `:2464-2465`, `:2540-2541`, `:2640-2641`) — 5 locuri, nu unul |
| `cursor_contract` | acelasi filtru pe `V_CURSOR` (delegat catre `cursor_preturi`, gratuit); pe `V_CURSOR2` filtrul se adauga in `UNION ALL`-ul de la `:2752` (partea cu `id_articol`), **nu** pe partea de rate (`id_articol IS NULL` — nu se pot filtra pe cod, raman needitate de filtru) | `:2825` (join articol) + propagare in `WHERE`-ul de la `:2819` |
| `cursor_comanda` | filtru pe `C.CODMAT`/`C.DENUMIRE` in `WHERE A.ID_COMANDA = V_ID_COMANDA` (`:3078`, `:3166`) — **dar nu are sens sa se filtreze**: cf. punctul 7, comanda ramane pe calea "adauga tot" | de proiectat doar daca decizia de la punctul 7 se schimba |
| `cursor_avize` | idem — nu are sens, acelasi motiv | idem |
| `cursor_gestiune` | filtru pe `C.CODMAT`/`C.DENUMIRE` in interogarea de la `:4234-4237`, inainte de `GROUP BY` | `:4266-4293` |
**Parametrii care raman identici** pe varianta filtrata: `V_DATA_CURS`, `V_TIP`, `V_ID_VALUTA`,
`V_ID_GESTIUNE_INIT`, `V_LUNA`, `V_AN`, `V_ID_UTIL`, `V_ID_SUCURSALA` — tot ce vine din `poDate` /
sesiune la deschiderea formularului, nu se recalculeaza per cautare. Doar filtrul de text e nou, plus
(pentru `cursor_preturi` in noua utilizare) eventual `V_ID_POL` daca utilizatorul a ales explicit o
lista de preturi in combo-ul `cb_politici_preturi` — azi acel combo doar filtreaza local
(`ofacturare.vc2:15647-15658`, comentat inlocuit cu `do_cauta()`); pe varianta noua devine parametru
real al interogarii.
**Ce nu se poate verifica din cod, ramane de decis de Marius:** daca filtrul pe `codmat` foloseste
`LIKE` "incepe cu" (ca `combosql` de azi, `nCharCountBegin`/cautare incrementala) sau match exact la
alegerea din lista — combosql de azi face amandoua (tastare = "incepe cu" pe server, alegere din
lista = valoare exacta), deci varianta cea mai apropiata de comportamentul actual e sa pastreze
acelasi tipar: interogarea filtrata se cheama la fiecare tastare (ca azi, `RefreshData`), iar
valoarea finala aleasa vine din randul deja adus, nu dintr-o interogare separata "exact match".
## 5. `combosql` — contract si exemple reale
**Clasa:** `combosql AS combobox`, `COMUN\clase\_cb_base.vc2:519-843`. Nu e specifica facturarii —
traieste in biblioteca comuna a suitei, dar cautarea nu a gasit nicio alta utilizare, nici in
`ROAFACTURARE`, nici in `COMUNROA`/`ROAGEST` (`grep -rn "AS combosql WITH"` — zero potriviri in afara
`ofacturare.vc2:16751/16785/16861`, cele trei coloane ale gridului prototip). **Singurul exemplu real
de folosire e chiar prototipul din plan** — nu exista alt loc in suita de copiat.
**Proprietati relevante** (`_memberdata`, `:560-575`):
| Proprietate | Rol |
|---|---|
| `csourcesql` | `SELECT` fara `WHERE`/`ORDER BY` — baza interogarii |
| `csourcewhere` | conditia `WHERE` fixa (ex. `inactiv = 0`) |
| `csourceorder` | `ORDER BY`, implicit `pfieldactiv` |
| `pcursorname` | numele cursorului cu rezultatele |
| `pfieldactiv` | campul dupa care se cauta si se afiseaza |
| `psecondfield` | al doilea camp de cautare (optional) |
| `ncharcountbegin` | cate caractere minim inainte sa porneasca interogarea pe server |
| `llimittolist` | daca valoarea trebuie sa existe in lista |
**Mecanismul** (`refreshdata`, `:796-830`): construieste
`SELECT ... FROM (cSourceSql) WHERE (cSourceWhere) AND (pFieldActiv LIKE ?pcValue [OR psecondfield
LIKE ?pcValue]) ORDER BY ...`, cu `?pcValue` = `cSearchString + '%'` (incepe cu) sau
`'%'+cSearchString+'%'` (contine, la Ctrl+Enter) si il executa prin
**`goExecutor.oExecuta`** (`selectdata`, `:832-840`) — acelasi executor folosit peste tot in suita
pentru apeluri Oracle, deci **niciun mecanism nou de transport**, doar un SQL nou de trimis.
`KeyPress` (`:610-776`) gestioneaza incremental tastarea: la fiecare caracter tastat re-executa
`RefreshData(1)` daca lungimea depaseste `nCharCountBegin`.
**Contractul de legare la o coloana de grid**, dedus din prototip (`ofacturare.vc2:16751-16762` +
`:19288-19306`):
1. se adauga ca `CurrentControl` al coloanei (`Column1.CurrentControl = "cCboDenumire"`, `:16621`);
2. `ControlSource` leaga afisarea de campul din cursorul de linii (`crsFactura.codmat`, `:16754`);
3. **evenimentul de reactie e `LostFocus`, nu `Valid` sau `InteractiveChange`** — abia la parasirea
celulei se scriu campurile in cursorul de linii, cu paza `If this.Value <> thisform.cOldValue`
(setat in `When`, `:19308-19310`) ca sa nu se rescrie fara motiv;
4. `LostFocus` de azi scrie **doar** campurile pe care le are `vnom_articole`: `codmat`, `denumire`,
`id_articol` (`:19293`) — nimic despre pret, TVA, valuta. **Aici se opreste prototipul azi**; tot
ce trebuie adaugat pentru S4 e sa inlocuiasca `crsCodmat`/`crsDenumire` (rezultatul din
`vnom_articole`) cu rezultatul cursorului Oracle filtrat de la punctul 4, care are toate coloanele,
si sa extinda `REPLACE`-ul de la `:19293`/`:19317` cu restul campurilor din tabelul de la punctul 6.
**Ce nu ofera azi `combosql` si trebuie adaugat, nu doar copiat:** `csourcesql` e un `SELECT`
static definit in `.vcx` la design-time, nu poate primi parametrii dinamici ai documentului curent
(`poDate.zi_curs`, `poDate.tip`, `poDate.id_valuta`...) direct in proprietate. Pentru cursorul
Oracle cu 8 parametri, populate din `poDate`/sesiune, `csourcesql`/`csourcewhere` trebuie construite
dinamic in `Init` sau la schimbarea documentului (nu sunt string-uri fixe ca azi), sau — alternativa
mai simpla — `refreshdata`/`selectdata` se suprascriu punctual pe instanta din grid, ca sa apeleze
`{call pack_facturare.cursor_preturi_filtrat(...)}` in loc de `SELECT ... FROM (cSourceSql)`. A doua
varianta pastreaza restul mecanismului (`KeyPress`, incremental, `LostFocus`) neschimbat si e
schimbarea minima — de confirmat cu Marius, nu o certitudine de cod.
## 6. Maparea camp-cu-camp
Sursa cea mai fiabila pentru "ce completeaza azi `crsarticole` in linie" nu e cursorul Oracle brut, ci
`Gather Name poArticol Fields Like ...` din `do_adauga_articol` (`ofacturare.vc2:12945-12957`,
identic pe `frm_facturare_articole2` la `:17226+`) — acolo se vede exact ce trece din randul scanat
in `crsfactura`.
| Camp in `crsfactura` | Vine din `crsarticole` (coloana cursorului Oracle) | Pe varianta filtrata |
|---|---|---|
| `id_articol`, `codmat`, `codbare`, `denumire`, `um` | direct din cursor | identic — filtrul e chiar pe aceste coloane |
| `pret_achizitie` | **nu** din `crsarticole` — vine din `poArtLista`/`crsartselectate` (rezultatul lui `cursor_gestiuni_articol*`, ales dupa `crsarticole`), nu din cursorul de lista de preturi | **neschimbat** — pasul e deja separat azi, ramane separat |
| `id_pol` | direct (`crsarticole.id_pol`) | identic |
| `Cont` | direct (`crsarticole.cont`, doar ramura restaurant o are explicit; pe restul vine `'371'` implicit sau din `cursor_gestiuni_articol*`) | identic pe ramurile care il au; **de verificat** pe ramurile 1/2/5/6/7/10/52/aviz — cursorul nu are `CONT` explicit in `SELECT` (`:2268-2329` etc.), deci provine din `do_alege_stoc`, nu din `crsarticole` |
| `id_gestiune` | direct doar pe ramura restaurant (`A.ID_GESTIUNE`, `:2220`); pe rest vine din alegerea de gestiune (`cursor_gestiuni_articol*`) | identic — pasul de alegere gestiune ramane neschimbat |
| `Proc_Tvav`, `pretftva`, `Pretctva`, `discountftva`(`discount_unitar`), `discountctva` | direct din cursor, plus `do_initializeaza_articol` (`:13618-13659`) care deriva `pretftva`/`pretctva`/`tva` reciproc dupa `preturi_cu_tva` | identic — logica de derivare e in VFP, nu se schimba |
| `id_valuta`, `tip_valuta`, `nume_val`, `Curs`, `multiplicator` | direct din cursor | identic |
| `gestionabil` | direct din cursor | identic (inclusiv regula de proforma, `ofacturare.prg:333-336`, care suprascrie `gestionabil=0` — ramane in `ofacturare.prg`, neschimbata) |
| `id_jtva_coloana` | direct doar pe ramurile care il au explicit in `SELECT` (aviz/contract/retur); pe `cursor_preturi` (1/2/5/6/7/10/52) **nu apare in lista de coloane** returnate (`:2264-2329`) | **de verificat cu Marius** — daca lipseste azi, `Gather ... id_jtva_coloana` scrie `NULL`/valoare implicita; filtrarea nu schimba nimic aici, dar merita clarificat inainte de implementare, nu presupus |
| `pretv_orig`, `pretd`, `id_valuta_d` | nu apar in `cursor_preturi`; vin din `poArtLista`/`crsartselectate` (alegerea de gestiune) | neschimbat |
| `taxcode`, `explicatie`, `id_lucrare_rez`, `id_part_rez`, `id_ctr`, `opt_facturare` | nu din `crsarticole` — din `poArticol`/context (`do_initializeaza_articol`, `do_alege_stoc`) | neschimbat |
| `pret_cu_tva` (flag `preturi_cu_tva`) | direct din cursor (`A.PRETURI_CU_TVA` / `D.PRETURI_CU_TVA`) | identic |
**Concluzie pentru punctul 6 al cerintei:** nicio coloana din cele cerute explicit (pret, cota TVA,
valuta, `id_pol`, `gestionabil`, `pret_cu_tva`) nu ridica probleme — toate vin direct din cursorul
Oracle si filtrarea pe articol nu le atinge. Singurul semnal de atentie e `id_jtva_coloana`, absent
din `cursor_preturi` insusi (posibil completat in alt pas, neverificat aici — iese din perimetrul
"cele cinci cursoare", ar cere citirea intregului `do_adauga_articol`/`calculeaza_totaluri`).
## 7. „Adauga tot" — ce ramane si de ce (nu doar decizia din plan)
Planul spune "se pastreaza adauga tot pentru tipurile cu document sursa (comanda, aviz, contract) —
acolo setul e marginit". Cercetarea (punctul 2) arata un motiv suplimentar, mai tare decat marimea
setului: **bookkeeping-ul de cantitate ramasa** (`do_sterge`, `do_scrie_factura`) citeste si scrie
direct in `crsarticole`, deci acel cursor **trebuie sa existe incarcat in memorie** cat timp
formularul e deschis, indiferent cum alege utilizatorul liniile.
**Vizibilitatea de azi a butonului `but_urmator_tot1`** ("adauga tot"), confirmata pe cod
(`ofacturare.vc2:15108-15248`, `Do Case poDate.tip`):
| Tip | Vizibil "adauga tot" azi | Sursa cursorului |
|---|---|---|
| proforma (`eProforma=1`) | da (`:15113`) | `cursor_preturi` sau alt cursor dupa tip, marcat neges­tionabil |
| copiere (`lCopiere`) | da (`:15120`) | `cursor_preturi` (varianta copiere, `ofacturare.prg:464-473`) |
| `1, 5, 7, 10` — lista de preturi | **nu** | `cursor_preturi` |
| `2, 6` — contract | **nu** | `cursor_contract` |
| `3` — comanda | da (`:15150`) | `cursor_comanda` |
| `4` — avize | da (`:15166`) | `cursor_avize` |
| `21, 28, 42, 47` — aviz din comanda | da (`:15177`) | `cursor_comanda` |
| `22, 29` — aviz din lista de preturi | **nu** | `cursor_preturi` |
| `23, 41` — transfer subunitati | **nu** menționat explicit vizibil | `cursor_gestiune` |
| `25` — transfer din comanda | da (`:15215`) | `cursor_comanda` |
| `26` — aviz din contract | **nu** | `cursor_contract` |
| `8, 9` — retur | da (`:15240`) | `cursor_retur` (nu e in cele 5, iese din perimetru) |
| `24` — aviz retur | da (`:15245`) | `cursor_retur` |
**Coincide exact** cu impartirea utila pentru S4: butonul e vizibil azi pe tipurile unde
`do_adauga_tot` are sens pentru ca setul e mic **si** bookkeeping-ul de ramas il cere
(comanda/aviz-din-comanda/retur), si e ascuns pe tipurile de lista de preturi (1/5/7/10, 22/29) si pe
contract-lista-de-preturi (2/6/26) — exact ramurile pe care cautarea filtrata inlocuieste incarcarea
in masa. **Contractul insa nu are azi "adauga tot" vizibil deloc** (nici pe partea de rate, nici pe
partea de articole) — planul semnaleaza asta separat, la S4b, ca gol de acoperit (tipurile 2, 6, 26,
52 lipsesc din conditiile de vizibilitate), nu ca ceva de pastrat.
**Concret, ce se schimba per tip:**
- **Lista de preturi (1,5,7,10,22,29) si transfer pe lista (23,41,45,48,49):** `crsarticole` nu se
mai incarca la deschidere; `combosql` cauta filtrat; "adauga tot" ramane ascuns (ca azi — n-are
sens pe catalog intreg).
- **Comanda (3,21,25,28,42,47) si avize (4):** `crsarticole` **ramane incarcat in masa**, neschimbat;
`combosql` **nu se activeaza** pe aceste tipuri (sau, daca se activeaza pentru UX, cauta *in*
cursorul deja incarcat, nu pe server — cautare locala, nu Oracle) pentru ca bookkeeping-ul de
cantitate ramasa il cere oricum incarcat.
- **Contract (2,6,26,52):** dublu — `crsarticole` (jumatatea `cursor_preturi`) trece pe cautare
filtrata ca orice lista de preturi; `crsarticole1` (rate + articole `OPT_FACTURARE=3`) **ramane
incarcat in masa**, bounded de contract, neschimbat de S4 (randurile de rata n-au `id_articol`,
nu pot fi cautate).
- **Retur (8,9,24):** in afara celor cinci cursoare cerute (`cursor_retur`/`cursor_retur_document`),
proiectat separat la S4f — nesemnalat aici ca problema, doar exclus din perimetru.
## 8. Interactiunea cu S4b si S10
**Cu S4b (bara de butoane / meniul de adaugare):** S4b proiecteaza meniul `xmenu()` "Adauga
articole" cu optiunile pe sursa (tot / alege), inclusiv acoperirea golului de pe contract (tipurile
2/6/26/52). S4 ii da continutul concret pentru ramura "cauta in linie": pe tipurile de lista de
preturi (unde S4b nu are nevoie de optiunea "tot" azi), linia de grid cu `combosql` **este** metoda
de adaugare — nu exista un dialog separat de ales. Pe tipurile cu document sursa, S4 nu schimba
nimic din ce proiecteaza S4b: meniul ramane cablat pe `do_adauga_tot`/alegere din `crsarticole`
existent. Punctul de atingere real: daca S4b decide sa afiseze si pe contract un "adauga tot" (gol
semnalat la punctul 7), acel "tot" trebuie sa opereze pe `crsarticole1` (rate+articole), nu pe
`crsarticole` (lista de preturi) — cele doua cursoare raman distincte si dupa unificare.
**Cu S10 (pretul care nu trebuie re-derivat, `docs\cercetare\s10_pret_rederivat.md`):** verificarea
S10 arata ca `adauga_articol_factura` **primeste pretul ca parametru** (`V_PRET_ACHIZITIE_TEMP` si
restul) si nu-l recalculeaza, cu o singura exceptie tacuta: liniile de contract cu
`OPT_FACTURARE = 3`, unde `CTR_ARTICOLE.PRET_UNITAR` **suprascrie** pretul trimis, necondiționat de
sursa. Pentru S4, asta inseamna doua lucruri concrete:
1. cursorul filtrat (`cursor_preturi`/`cursor_gestiune` cu filtru pe articol) se cheama **o singura
data**, la selectarea liniei in `combosql` (`LostFocus`) — pretul obtinut atunci se scrie in
`crsfactura` si **nu se re-interogheaza** la scriere, exact ca azi pe calea `crsarticole` completa;
2. pe **contract**, indiferent daca linia a fost aleasa prin `crsarticole1` (needitat de S4) sau,
ipotetic, printr-o cautare filtrata viitoare, riscul de suprascriere tacuta descris in S10 exista
deja si S4 nu-l introduce si nu-l rezolva — il mosteneste neschimbat, pentru ca punctul de
suprascriere e in `adauga_articol_factura` (scriere), nu in cursorul de cautare (citire).
## 9. Ordinea de executie in pasi
1. **Pas 1 — Oracle, `cursor_preturi` filtrat.** Supraincarcare cu `V_FILTRU_COD`/`V_FILTRU_DEN`
opționale, aceleasi 5 ramuri, filtru adaugat in `WHERE`-ul fiecareia (punctul 4). *Gata cand:*
apelat cu filtru gol, cursorul e identic randuri-cu-randuri (aceleasi coloane, aceleasi valori, pe
fiecare din cele 5 ramuri de `V_TIP`) cu varianta actuala pe acelasi set de parametri — comparatie
directa pe SQL, nu pe timp.
2. **Pas 2 — Oracle, `cursor_gestiune` filtrat.** Acelasi tipar, un singur `SELECT`, mai simplu.
*Gata cand:* idem, comparatie randuri-cu-randuri cu filtru gol.
3. **Pas 3 — Oracle, `cursor_contract`, doar partea `V_CURSOR`.** Filtrul se propaga gratuit prin
apelul catre `cursor_preturi` de la `:2940-2948`; nicio schimbare suplimentara necesara daca Pasul
1 e facut corect. *Gata cand:* `cursor_contract` cu filtru gol produce acelasi `V_CURSOR` ca
inainte de Pasul 1 (regresie, nu functie noua).
4. **Pas 4 — VFP, `combosql` pe `cCodMat`/`cCboDenumire`.** Inlocuieste `csourcesql`/`SelectData` cu
apelul la cursorul filtrat (punctul 5); extinde `LostFocus` (`:19288-19330`) sa scrie toate
campurile din tabelul de la punctul 6, nu doar `codmat`/`denumire`/`id_articol`. *Gata cand:*
alegerea unei linii in grid, pe un document de tip lista-de-preturi, produce in `crsfactura`
aceleasi valori ca alegerea aceluiasi articol azi din `crsarticole` incarcat complet — comparatie
pe date, punctul 10.
5. **Pas 5 — VFP, oprirea incarcarii in masa pe tipurile vizate.** In `ofacturare.prg:266-311`, pentru
`tnTip` din ramurile de lista de preturi (1,5,7,10,22,29,23,41,45,48,49) si jumatatea `cursor_preturi`
a contractului, `goExecutor.oExecute` nu se mai cheama la deschidere — grid-ul porneste gol,
`combosql` populeaza la cerere. *Gata cand:* deschiderea formularului pe aceste tipuri nu executa
niciun apel `cursor_preturi`/`cursor_gestiune`/`cursor_contract` (verificabil in log-ul
`goExecutor`/`WAIT WINDOW ... NOWAIT` din `combosql.selectdata`, care ramane singurul loc care
cheama Oracle pentru articole).
6. **Pas 6 — verificare "adauga tot" neatins.** Pe comanda/aviz/contract-rate, `do_adauga_tot`,
`do_sterge`, `do_scrie_factura` raman neschimbate (Pasii 1-5 nu le ating codul). *Gata cand:*
regresie manuala pe un document din comanda si unul din aviz — comportament identic cu azi.
*Depinde de:* S3 (planul o marcheaza asa; portarea antetului trebuie sa existe inainte, ca sa aiba
`poDate` populat corect la deschiderea gridului).
## 10. Cum se verifica „aceleasi valori ca azi"
**Testabil pe date (nu doar pe cod), propunere concreta:** pentru fiecare tip reprezentativ (1 sau 5
pentru lista de preturi, 2 pentru contract, 41 pentru transfer-gestiune), se alege un articol prezent
in cursorul complet de azi si se compara randul din `crsarticole` (deschidere veche, neschimbata) cu
randul intors de cursorul filtrat pe acelasi articol, aceiasi parametri (`zi_curs`, `id_valuta`,
`id_gestiune_init`, utilizator) — comparatie coloana cu coloana, in Oracle direct (doua `SELECT`-uri,
`MINUS` intre ele pe coloanele comune) sau in VFP prin doua cursoare deschise simultan. Se repeta pe
un articol cu politica de discount, unul in valuta si unul fara stoc (`RF_FACTURARE_FARA_STOC`), ca
sa acopere ramurile de `CASE` din punctul 1.
**Ce nu se poate testa headless — capcana cunoscuta** (`grid-coloane-nu-se-materializeaza-headless`,
memorie de proiect): sub `-A -T`, `ColumnCount`/`RecordSource` ale gridului sunt artefacte, nu
reflecta ce vede operatorul. Deci **partea VFP** (alegerea din `combosql`, `LostFocus`-ul care scrie
in `crsfactura`) nu se poate verifica prin harnessul headless obisnuit — cere fie harnessul UI
vizibil existent, fie verificare directa pe cursorul `crsfactura` dupa un apel programatic la metoda
`LostFocus` (posibil, pentru ca metoda insasi nu depinde de randare, doar de `This.Value` — de
confirmat la implementare). Partea Oracle (comparatia de cursoare de mai sus) **e** testabila
headless, prin `SELECT`, fara nicio dependenta de VFP.
## 11. Riscuri si ce ramane de decis de Marius
- **`id_jtva_coloana` lipsa din `cursor_preturi`** (punctul 6) — de clarificat inainte de
implementare daca e completat in alt pas (nevazut aici) sau ramane `NULL` si azi; filtrarea nu
schimba comportamentul, dar nota trebuie inchisa ca sa nu para un bug nou introdus de S4.
- **Mecanismul de legare `combosql` -> cursor Oracle parametrizat dinamic** (punctul 5, ultimul
paragraf) e o alegere de implementare, nu un fapt de cod — `csourcesql` static din `.vcx` nu poate
primi parametrii de sesiune direct; solutia propusa (suprascriere punctuala `refreshdata`) e
rezonabila, dar nu e singura posibila si nu exista alt exemplu in suita de comparat.
- **Contractul ramane cu doua surse de date pe acelasi grid conceptual** (`crsarticole` filtrat +
`crsarticole1` needitat) — de confirmat ca UX-ul (doua zone: cautare + grid rate) e acceptabil, nu
doar tehnic corect.
- **`cursor_gestiune` (transfer subunitati) nu are azi buton "adauga tot" vizibil** (tabelul din
punctul 7 nu-l gaseste in `Do Case`) — de verificat separat daca tipurile 23/41 au alt mecanism de
adaugare in masa, neacoperit de cautarea in `crsarticole`/`crsarticole1` directa; nu s-a citit codul
suplimentar necesar pentru raspuns cert aici.
- **Validarea de curs valutar (`verifica_cursuri_valute`, punctul 1.1) ramane neconditionata** chiar
si pe varianta filtrata, daca se pastreaza in procedura supraincarcata — poate produce `-20005` la
prima cautare a unui articol, inainte ca utilizatorul sa fi vazut vreun rand, pe o valuta pe care
n-o foloseste azi doar pentru ca incarcarea in masa "o gasea" oricum. De discutat cu Marius daca
validarea trebuie restransa la valuta articolului cautat sau ramane globala (comportament
identic cu azi, doar declansat mai des — la fiecare cautare, nu o data la deschidere).
- **Numarul exact de apeluri Oracle pe sesiune de cautare** (un apel per tastare, cu debounce prin
`nCharCountBegin`) nu a fost masurat si nu trebuie masurat aici (decizia 31) — dar merita setat
explicit `nCharCountBegin` >= 2-3 la implementare, ca sa nu se piarda avantajul filtrarii intr-un
numar mare de interogari de o litera.
## Handoff
Cercetare incheiata in aceasta sesiune, fara nevoie de predare — toate cele 11 puncte cerute sunt
acoperite mai sus, cu citate `fisier:linie` verificate pe fisierul real (nu `.bak`). Niciun fisier de
cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle.

View File

@@ -1,615 +0,0 @@
# Cercetare — proiectare S4 punctul 2: decuplarea registrului de cantitate ramasa de `crsarticole`
Investigatie READ-ONLY pentru **punctul 2** al povestii S4 din
`docs\plan_13_unificare_formular_facturare.md:2099-2113`. Continua raportul punctului 1
(`docs\cercetare\s4_cautare_articole_server.md`) si corectiile din `docs\cercetare\s4_puncte_deschise.md`.
Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara scriere pe Oracle (numai
`SELECT`). Nu ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`
(perimetrul altei sarcini). **Decizia 39 (S4 ramane integrala, se desface registrul) nu e
reargumentata** — proiectez CUM, nu DACA.
Sursa VFP: `COMUN\clase\ofacturare.vc2` (`frm_facturare_articole`; `frm_facturare_articole2`/prototip
citit doar pentru confirmare, are aceeasi structura). Sursa Oracle:
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (corpul
pachetului, `versiune_db.txt` = `2026_08_09_02`).
## Verdict (rezumat)
**Descoperirea centrala: `crsarticole.cantitate` are azi DOUA roluri distincte, nu unul, si numai
unul din ele e "registrul de cantitate ramasa" pe care planul il numeste riscant.**
- **Rol A — cantitate ramasa de facturat dintr-un document sursa** (doar comanda: tip
`3,21,25,28,42,47`; si avize: tip `4`). Cursoarele Oracle (`cursor_comanda`, `cursor_avize`) o
**calculeaza deja live** la deschidere ca `cantitate_document - deja_facturat`. `do_scrie_factura`
o re-suma din `crsarticole` (`Calculate Sum`) **doar ca sa decida ce sa trimita mai departe**
(`pnParametruAditional`) — dar Oracle **recalculeaza acelasi lucru independent, din tabele reale**,
in `inchide_comanda()` si `marcheaza_facturat()`, chiar in procedura care scrie factura. Cursorul
VFP e o **copie redundanta a unui calcul pe care Oracle il repeta oricum la scriere** — exact
problema "sursa unica" semnalata in misiune, si exact motivul pentru care decuplarea e posibila
fara sa piarda paritatea: mutam citirea "cat a mai ramas" din cursorul local intr-un apel Oracle
facut la momentul potrivit, nu intr-o a doua copie tinuta manual.
- **Rol B — plafon de cantitate in sesiune** (lista de preturi gestionabila `1,22,29,2`-jumatate;
transfer `23,41`; retur `8,9,24`). Decrementat/incrementat in `do_adauga_articol`/`do_modifica`/
`do_sterge` ca sa nu lase operatorul sa adauge mai mult decat vede pe ecran, in aceeasi sesiune.
**Nu alimenteaza nicio decizie Oracle** — nu apare in niciun `Calculate Sum` in afara celor doua
locuri de la Rolul A, si Oracle nu-l citeste niciodata direct. E un plafon UI, nu un registru de
business.
- **Corectie fata de raportul punctului 1**: pasul 5 de acolo (`s4_cautare_articole_server.md:417-424`)
propune oprirea incarcarii in masa pe tipurile `23,41` (printre altele), presupunand ca ele n-au
bookkeeping de pastrat — dar **au Rol B** (confirmat pe cod, sectiunea 1 de mai jos). Punctul 1 nu
poate fi aplicat pe `23,41` fara ca punctul 2 sa acopere intai Rolul B pe aceste doua tipuri.
- **Recomandare de proiectare** (detaliata la sectiunea 4): pentru Rolul A, inlocuieste
`Select crsarticole / Calculate Sum(cantitate)` cu un apel Oracle nou, la acelasi moment din
`do_scrie_factura` (dupa `do_scrie_articole()`, cand `VANZARI_DETALII_TEMP` e deja populat), care
reface exact interogarea pe care `inchide_comanda`/`marcheaza_facturat` o repeta oricum. Pentru
Rolul B, plafonul devine un apel Oracle la cerere (aceeasi interogare care alimenteaza azi
`cursor_preturi`/`cursor_comanda`/`cursor_gestiune`, filtrata pe articol), nu o valoare tinuta in
memorie — nu mai e nevoie de sincronizare manuala pentru ca nu mai exista o a doua copie.
---
## 1. Inventar exact al scrierilor/citirilor de bookkeeping
Verificat direct pe `COMUN\clase\ofacturare.vc2` (nu pe `.bak`), linii confirmate cu `vfp_symbols.ps1`.
### 1.1 `do_adauga_articol` — scrierea la adaugarea unei linii (`:12813-13086`)
Dupa ce linia e scrisa in `crsfactura`, la `:13034-13051`:
```
Select (lcCursor)
lnRecno = Recno()
Do Case
Case gnScadereStoc = 1 And poDate.tip = 41 && aviz retur transfer catre subunitati lista pret
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
Go lnRecno
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 3, 4, 21, 25, 28, 42, 47)
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
Go lnRecno
Case Inlist(poDate.tip, 8, 9, 24) && factura retur lei si valuta, aviz retur
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
Go lnRecno
Case poArticol.gestionabil = 1 And gnScadereStoc = 1 And ;
(Inlist(poDate.tip, 1, 22, 29) Or (poDate.tip = 2 And poArticol.opt_facturare = 0))
Replace cantitate With IIF(cantitate - poArticol.cantitate > 0, cantitate - poArticol.cantitate, 0) For id_articol = poArticol.id_articol And gestionabil = 1
Go lnRecno
Endcase
```
`lcCursor` = `crsarticole1` daca `tlContract`, altfel `crsarticole` (`:12839-12845`).
### 1.2 `do_sterge` — refacerea la stergerea unei linii (`:14608-14677`)
```
Select (lcCursor)
lnRecNo = Recno()
Do Case
Case gnScadereStoc = 1 And poDate.tip = 41
Select (lcCursor)
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip,3,4,21,25,28,42,47)
Select (lcCursor)
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
Case Inlist(poDate.tip,8,9,24)
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
Case poArticol.id_gestiune <> - 1000 And gnScadereStoc = 1 And ;
(Inlist(poDate.tip,1,22,29) Or (poDate.tip = 2 And poArticol.opt_facturare = 0))
Select (lcCursor)
Replace cantitate With cantitate + poArticol.cantitate For id_articol = poArticol.id_articol And gestionabil = 1
Endcase
```
`lcCursor` = `crsarticole1` daca `poArticol.opt_facturare <> 0`, altfel `crsarticole` (`:14615-14619`).
**Exact inversul semnului fata de `do_adauga_articol`, pe aceleasi patru grupuri de tipuri** — simetrie
confirmata, nu presupusa.
### 1.3 `do_modifica` — ajustarea la schimbarea cantitatii pe o linie deja adaugata (`:13746-13914`)
Doua interactiuni distincte cu registrul, in aceeasi metoda:
**(a) Calculul plafonului inainte de a arata dialogul** (`:13775-13798`), comentat explicit in cod
ca "plafonul de cantitate = stocul disponibil reconstituit din cursorul sursa; fara randul in cursor
ramane cantitatea liniei" (`:13775`):
```
lnCantitateMax = poArticol.cantitate
lcCursorStoc = Iif(poArticol.opt_facturare = 0, [crsarticole], [crsarticole1])
...
Locate For id_c = poArticol.id_c
If Found()
Do Case
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 4, 21, 28, 42, 47)
lnCantitateMax = cantitate + poArticol.cantitate
Case Inlist(poDate.tip, 8, 9, 24)
lnCantitateMax = cantitate - poArticol.cantitate
Endcase
Endif
```
Aduna inapoi ce a consumat deja linia curenta, ca sa arate operatorului plafonul real (cat mai era
disponibil **inainte** de aceasta linie), trimis ca parametru la `do_alege_stoc`/`frm_articol_factura`.
**(b) Ajustarea delta dupa ce operatorul schimba cantitatea** (`:13887-13904`):
```
lnDeltaCantitate = poArticol.cantitate - lnCantitateVeche
If lnDeltaCantitate <> 0 And Used(lcCursorStoc)
Do Case
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 4, 21, 28, 42, 47)
Replace cantitate With cantitate - lnDeltaCantitate For id_c = poArticol.id_c
Case Inlist(poDate.tip, 8, 9, 24)
Replace cantitate With cantitate + lnDeltaCantitate For id_c = poArticol.id_c
Endcase
Endif
```
**Gasit, nu presupus: `do_modifica` NU ajusteaza `crsarticole` pentru tipurile `1,22,29` si
`2`-lista-jumatate** (grupul "gestionabil, plafon local" din `do_adauga_articol`/`do_sterge`) — doar
pentru grupul comanda/aviz (`4,21,23,28,42,47`) si retur (`8,9,24`). E o asimetrie **preexistenta**
in codul de azi (nu introdusa de S4): editarea cantitatii pe o linie de lista-de-preturi deja adaugata
nu recalibreaza plafonul local. Nu e de reparat aici — doar de semnalat, ca varianta noua sa nu
incerce sa reproduca o simetrie care nu exista azi.
### 1.4 `do_adauga_tot` — enumerare, nu bookkeeping propriu (`:13169-13198`)
```
Select crsarticole
Scan
...
Thisform.do_adauga_articol(.T.)
...
Endscan
```
**Nu scrie direct in `crsarticole`** — cheama `do_adauga_articol` pe fiecare rand, care face
bookkeeping-ul de la 1.1. Rolul lui `crsarticole` aici e al treilea, distinct de Rolul A/B: **sursa
de enumerare** ("ce randuri exista de adaugat"), necesara oricum pentru populare (S4 punctul 1), nu
doar pentru bookkeeping.
### 1.5 `do_scrie_factura` — citirea care alimenteaza decizia de inchidere (`:14301-14338`)
Doua ramuri, singurele doua locuri din intregul fisier unde `Calculate Sum(cantitate)` ruleaza peste
`crsarticole` (confirmat cu grep pe tot fisierul, nicio a treia aparitie):
```
Case poDate.Tip = 4
Select crsarticole
Calculate Sum(cantitate) To lnCantitateRamasa
If lnCantitateRamasa > 0
pnFacturaRetur = 7 - amessagebox("Doriti sa se inregistreze si avizul de retur?",4+32,"Confirmare")
pnParametruAditional = 1
Else
pnFacturaRetur = 0
pnParametruAditional = 0
Endif
...
Case Inlist(poDate.Tip,3,21,25,28,42,47)
Select crsarticole
Calculate Sum(cantitate) To lnCantitateRamasa
If lnCantitateRamasa <> 0
pnParametruAditional = 7 - amessagebox("Doriti sa se inchida comanda?",4+32,"Confirmare")
Endif
...
```
`pnParametruAditional` default e `0` (`:14222`, setat inainte de `Do Case`). Pentru **orice alt tip**
(inclusiv `41`, `23`, `2/6/26/52`, `45/48/49`), ramane `0` fara sa fie calculat din `crsarticole` deloc
(exceptie: tip `48` il suprascrie cu `poDate.coeficient_k`, fara legatura cu inchiderea — `:14367-14369`).
**Concluzie:** doar `4` si `Inlist(3,21,25,28,42,47)` sunt Rolul A. Restul tipurilor nu ating deloc
aceasta parte a lui `do_scrie_factura` — inclusiv `41`/`23`, care au totusi bookkeeping Rol B activ la
1.1-1.3.
---
## 2. Semantica registrului azi
### 2.1 Ce inseamna `cantitate` la incarcare, per grup
Confirmat pe corpul cursoarelor Oracle (`ff_...PACK_FACTURARE.sql`):
- **`cursor_comanda`** (`:2952-3171` per raportul punctului 1; verificat aici direct la sectiunea
"aviz"/comanda a interogarii, `:3060-3081`): coloana `cantitate` e `A.CANTITATE - NVL(D.CANTITATE, 0)`
unde `D` e `SUM(cantitate)` deja facturat din `VANZARI_DETALII` pe acelasi `id_comanda`
(`:3060-3068`), filtrat `WHERE ... SIGN(A.CANTITATE) * (A.CANTITATE - NVL(D.CANTITATE, 0)) > 0`
(`:3079`). **E deja "ramas de facturat", calculat live la deschidere** — nu cantitatea comandata
bruta.
- **`cursor_avize`** (`:3703-3874`): coloana `A.CANTITATE - NVL(D.CANTITATE, 0) AS CANTITATE`
(analog la `:3118`), plus agregarea finala (`:3820-3872`) care scade `VANZARI_CANTITATI` deja
consumat din `VANZARI_DETALII` (`WHERE A.CANTITATE <> NVL(B.CANTITATE, 0)`, `:3871`). Acelasi
tipar: "ramas", nu cantitatea avizata bruta.
- **Restul grupurilor (Rol B: `1,22,29,2`-lista, `23,41` transfer, `8,9,24` retur)**: `cantitate`
vine din `cursor_preturi`/`cursor_gestiune`, care e **stoc disponibil** (`LEFT JOIN` pe `STOC`,
raportul punctului 1 sectiunea 1.1/1.5), nu "ramas de facturat dintr-un document" — pentru ca aceste
tipuri nu au un document sursa cu cantitati de urmarit (lista de preturi n-are document sursa;
transferul/returul opereaza pe stoc, nu pe o comanda).
### 2.2 Cine o scade si cand
Vezi sectiunea 1: la fiecare `do_adauga_articol` (scade sau creste, dupa tip), la fiecare `do_sterge`
(inversul), si la `do_modifica` doar pentru grupul Rol A + retur (2.1(b) de mai sus).
### 2.3 Ce se intampla la adaugare / stergere / modificare cantitate — rezumat comportamental
| Actiune | Rol A (comanda 3/21/25/28/42/47, aviz 4) | Rol B (lista gestionabila 1/22/29/2, transfer 23/41, retur 8/9/24) |
|---|---|---|
| Adauga linie | `crsarticole.cantitate` scade (creste la retur/41) cu cantitatea adaugata | idem, plafon local |
| Sterge linie | reface exact simetric | reface exact simetric |
| Modifica cantitate pe linie existenta | ajusteaza cu delta (23/4/21/28/42/47 si 8/9/24 explicit) | **nu ajusteaza** pe 1/22/29/2-lista (asimetrie preexistenta, 1.3) |
| La scriere (`do_scrie_factura`) | **suma peste `crsarticole` decide `pnParametruAditional`** (inchidere) | **nu se suma niciodata** — nu alimenteaza nicio decizie Oracle |
---
## 3. Regula de inchidere automata — exact, cu dovada Oracle
### 3.1 Ce trimite VFP si ce face Oracle cu el
`pnParametruAditional` merge la Oracle prin `scrie_factura_avize` (tip `4`) sau `scrie_factura2`
(toate celelalte, inclusiv comanda). Ambele cheama in interior `finalizeaza_factura(...)`
(`ff_...PACK_FACTURARE.sql:14770-14852`), care are un `CASE` unic pe `pack_facturare.ntip` (setat
intern din tipul documentului, nu parametru separat):
```sql
CASE
WHEN V_PARAMETRU_ADITIONAL = 1 AND pack_facturare.ntip IN (3, 21, 25, 28, 42, 47) THEN
-- comanda: V_PARAMETRU_ADITIONAL este V_INCHIDERE_COMANDA
pack_facturare.inchide_comanda();
WHEN pack_facturare.ntip = 4 THEN
-- aviz: V_PARAMETRU_ADITIONAL este V_VERIFICARE_FACTURAT
pack_facturare.scrie_cantitati_vanzari_avize;
pack_facturare.scrie_corespondente_vanzari(1);
pack_facturare.marcheaza_facturat(V_PARAMETRU_ADITIONAL);
WHEN pack_facturare.ntip = 24 THEN
pack_facturare.scrie_corespondente_vanzari(2);
pack_facturare.marcheaza_facturat(V_PARAMETRU_ADITIONAL);
WHEN pack_facturare.ntip in (2, 6, 52) THEN
pack_facturare.scrie_rate_factura(V_DATAORA);
WHEN pack_facturare.ntip in (8, 9) THEN
pack_facturare.scrie_corespondente_vanzari(3);
ELSE
dbms_output.put_line('---');
END CASE;
```
(`:14818-14839`)
**`41` si `23` nu apar deloc in acest `CASE`** — cad pe `ELSE`, fara nicio actiune de inchidere.
Confirma pe cod ce arata si sectiunea 1.5: transferul intre subunitati nu are un "document sursa" de
inchis, doar un plafon de stoc (Rol B). `2/6/26/52` (contract) intra pe ramura `scrie_rate_factura`,
care scrie scadentarul de rate — **nu e o "inchidere" in sensul comanda/aviz**, e alta operatie.
### 3.2 Comanda — `inchide_comanda()` (`:5769-5820`)
**Ruleaza doar cand `V_PARAMETRU_ADITIONAL = 1`** — adica doar daca `crsarticole` a aratat cantitate
ramasa nenula **si** operatorul a raspuns "Da" la "Doriti sa se inchida comanda?" (`ofacturare.vc2:14336-14337`).
Cand ramane 0 (fara diferenta), `inchide_comanda()` **nu se cheama deloc** — pentru ca o comanda complet
livrata se raporteaza deja "inchisa" prin simplul fapt ca interogarea de mai jos nu mai returneaza
randuri, fara nicio actiune suplimentara.
Ce face efectiv (`:5771-5818`): **insereaza un rand compensator** in `COMENZI_ELEMENTE`, cu
`CANTITATE = ramas` (calculat live, independent de VFP, din `NVL(C.CANTITATE,0) + NVL(B.CANTITATE,0) -
A.CANTITATE` unde `B` = deja facturat in aceasta tranzactie (`VANZARI_DETALII_TEMP`) si `C` = deja
facturat istoric (`VANZARI`/`VANZARI_DETALII`)) — asta face ca o interogare ulterioara de tip
`cursor_comanda` (aceeasi forma de `WHERE`, `SIGN(...) * (A.CANTITATE - NVL(D.CANTITATE,0)) > 0`) sa
nu mai gaseasca randuri ramase, adica sa "inchida" comanda **prin recalcul, nu printr-un flag setat**.
**Nu exista o coloana `INCHISA`/status persistat pe comanda** (cautare `INCHISA` in tot pachetul —
zero potriviri) — starea "deschisa/inchisa" e intotdeauna derivata din suma `COMENZI_ELEMENTE` vs
`VANZARI_DETALII`, nu citita dintr-un camp.
**Important pentru paritate:** cantitatea trimisa de VFP (`pnParametruAditional`, un simplu `1`/`0`)
**nu participa la calculul cantitatii inserate** — Oracle o recalculeaza singur din tabele reale.
Rolul VFP e strict binar: "a fost cerut sa se forteze inchiderea?".
### 3.3 Avize — `marcheaza_facturat(V_VERIFICARE)` (`:15381-15418`)
```sql
IF V_VERIFICARE = 0 THEN
UPDATE VANZARI SET FACTURAT = 1, ID_UTILFACT = ...
WHERE ID_VANZARE IN (SELECT id_vanzare_aviz ... WHERE id_vanzare_Fact = pack_facturare.nid_vanzare AND sters=0 AND tip<>3)
AND FACTURAT = 0;
ELSE
UPDATE VANZARI SET FACTURAT = 1, ...
WHERE ID_VANZARE IN (
SELECT id_vanzare FROM (
SELECT a.id_vanzare, SUM(a.cantitate - NVL(b.cantitate,0)) AS ramas
FROM vanzari_detalii a LEFT JOIN (...VANZARI_CANTITATI...) b ON ...
WHERE a.id_vanzare IN (...avizele referite de aceasta factura...)
GROUP BY a.id_vanzare
) WHERE ramas = 0
);
END IF;
```
**Aici exista un flag real persistat: `VANZARI.FACTURAT`.** `V_VERIFICARE` (= `pnParametruAditional`
trimis de VFP) alege intre doua moduri:
- `V_VERIFICARE = 0` (VFP a vazut `lnCantitateRamasa = 0`, adica a crezut ca tot ce era in avizele
referite s-a facturat): **marcheaza TOATE avizele referite ca facturate, fara re-verificare** —
increde in calculul VFP.
- `V_VERIFICARE = 1` (VFP a vazut ceva ramas): **recalculeaza per-aviz, din tabele reale**
(`vanzari_detalii`/`vanzari_cantitati`), si marcheaza **doar** avizele individuale al caror
`ramas = 0` — mai stric, pentru ca VFP nu mai e sigur.
**Observatie relevanta pentru punctul 2 al plafonului de risc**: cand un singur `crsarticole` agrega
mai multe avize (facturare din mai multe avize deodata), suma globala poate fi 0 chiar daca un aviz
individual din grup mai are ramas si altul are exces care-l compenseaza — caz in care `V_VERIFICARE=0`
ar marca **toate** ca facturate, inclusiv cel cu ramas real. **Comportament preexistent**, nu introdus
de decuplare — dar varianta noua trebuie sa-l reproduca identic (aceeasi conditie de trigger: suma
globala peste toate liniile din tranzactia curenta, nu per-document), nu sa-l "repare" din greseala.
### 3.4 Cine altcineva verifica remainder — `scrie_cantitati_vanzari_avize` (`:15420-15479`)
Ruleaza **necondiționat** pe ramura `tip=4`, indiferent de `V_VERIFICARE` — scrie in `VANZARI_CANTITATI`
cate din cantitatile avizate au fost efectiv consumate de aceasta factura (mecanismul care alimenteaza
`ramas` la sectiunea 3.3). Nu depinde de `crsarticole` — foloseste `VANZARI_DETALII_TEMP` (deja
populat de `do_scrie_articole()`, apelat inaintea acestui `Do Case` din VFP, `:14264`) si tabele reale.
**Confirma independenta totala de `crsarticole` a partii Oracle** — singurul lucru pe care Oracle il
primeste de la VFP e semnalul binar `pnParametruAditional`.
---
## 4. Proiectare — variante comparate
### Varianta (a) — cursor propriu, minimal, decuplat de populare
Un cursor separat (`crsregistru`), incarcat cu `id_c`/`id_articol` + cantitate ramasa, populat din
aceeasi interogare care alimenteaza azi `crsarticole`, dar **independent** de campurile de populare
(pret, TVA, valuta...) pe care S4 punctul 1 le muta pe cautare filtrata.
**Ce se atinge:** aceleasi patru metode (`do_adauga_articol`, `do_sterge`, `do_modifica`,
`do_scrie_factura`) — schimba doar numele cursorului tinta, logica ramane identica.
**Ce se rupe:** nimic structural — e schimbarea minima de sintaxa.
**Cost:** mic, dar **nu rezolva problema de fond**: tot exista o a doua copie a "cat a mai ramas",
tinuta manual in memorie VFP, care trebuie sa ramana in sincron cu ce calculeaza Oracle la scriere
(sectiunea 3). E acelasi risc de azi, doar cu un cursor mai ingust. **Nu e "sursa unica"** — e aceeasi
sursa dubla, doar mai mica.
### Varianta (b) — recalculul pe server la scriere, in loc de suma locala (RECOMANDATA pentru Rolul A)
Inlocuieste cele doua `Select crsarticole / Calculate Sum(cantitate) To lnCantitateRamasa` din
`do_scrie_factura` (`:14303-14304`, `:14334-14335`) cu un apel Oracle nou, facut **dupa**
`Thisform.do_scrie_articole()` (`:14264`, care populeaza deja `VANZARI_DETALII_TEMP` cu exact ce e pe
cale sa fie scris) si **inainte** de `Do Case` care alege `scrie_factura_avize`/`scrie_factura2`.
Doua proceduri Oracle noi, minimale, care **reutilizeaza exact interogarea** pe care
`inchide_comanda`/`marcheaza_facturat` o ruleaza oricum peste doua randuri mai jos in acelasi flux —
nu o interogare noua, o extragere a celei existente intr-o forma care returneaza un numar in loc sa
scrie:
```sql
-- comanda: acelasi WHERE ca inchide_comanda (:5771-5818), dar SUM in loc de INSERT
FUNCTION cantitate_ramasa_comanda(V_ID_COMANDA IN NUMBER) RETURN NUMBER IS
V_RAMAS NUMBER;
BEGIN
SELECT NVL(SUM(SIGN(A.CANTITATE) *
GREATEST(SIGN(A.CANTITATE) * (A.CANTITATE - NVL(B.CANTITATE,0) - NVL(C.CANTITATE,0)), 0)), 0)
INTO V_RAMAS
FROM COMENZI_ELEMENTE A
LEFT JOIN (SELECT ID_ARTICOL, ID_POL, ID_VALUTA, PRET, SUM(CANTITATE) AS CANTITATE
FROM VANZARI_DETALII_TEMP GROUP BY ID_ARTICOL, ID_POL, ID_VALUTA, PRET) B
ON A.ID_ARTICOL = B.ID_ARTICOL AND A.ID_POL = B.ID_POL AND A.ID_VALUTA = B.ID_VALUTA AND A.PRET = B.PRET
LEFT JOIN (... acelasi C ca in inchide_comanda, VANZARI/VANZARI_DETALII istoric ...) C ON ...
WHERE A.STERS = 0 AND A.ID_COMANDA = V_ID_COMANDA;
RETURN V_RAMAS;
END;
-- avize: acelasi calcul ca in marcheaza_facturat(V_VERIFICARE=1), dar returnat, nu aplicat
FUNCTION exista_ramas_avize(V_LISTAID IN VARCHAR2) RETURN NUMBER IS ...
```
**Ce se atinge:** `do_scrie_factura` (inlocuieste 4 linii VFP cu un apel Oracle + citire scalar), plus
doua functii Oracle noi in `PACK_FACTURARE`, care **extrag** logica deja scrisa in `inchide_comanda`/
`marcheaza_facturat`, nu o duplica din nimic. `do_adauga_tot`, `do_sterge`, `do_modifica` **raman
neschimbate pentru Rolul A** — nu mai au ce sa faca, pentru ca nimic nu mai citeste suma lor.
**Ce se rupe:** niciun comportament — recalculul Oracle e **mai precis** decat suma locala (vede
concurenta: doi operatori facturand din aceeasi comanda simultan; `crsarticole` local nu vede
modificari facute de alt utilizator dupa deschiderea formularului — bug latent de azi, pe care
varianta (b) il elimina ca efect secundar, nu ca scop).
**Cost:** doua functii Oracle noi (SQL, nu logica noua — extrase din proceduri existente), un apel
suplimentar `goExecutor` in `do_scrie_factura`.
**De ce e "sursa unica":** Oracle calculeaza "cat a ramas" o singura data, in doua locuri (recalcul
inainte de scriere + recalcul in `inchide_comanda`/`marcheaza_facturat` la scriere), din **aceeasi
interogare**, niciodata dintr-o copie VFP. Nu mai exista sincronizare de mentinut, pentru ca nu mai
exista a doua stare.
### Varianta (c) — plafon Rol B recalculat la cerere, nu tinut in memorie (RECOMANDATA pentru Rolul B)
Pentru grupul Rol B (`1,22,29,2`-lista, `23,41`, `8,9,24`), plafonul din `do_modifica`
(`lnCantitateMax`, sectiunea 1.3(a)) si decrementul din `do_adauga_articol`/`do_sterge` (sectiunea
1.1/1.2) devin un apel Oracle facut **la fiecare adaugare/editare**, in loc de o valoare tinuta in
`crsarticole` — interogare filtrata pe `id_articol`, aceeasi forma ca `cursor_preturi`/
`cursor_gestiune` filtrate (deja proiectate la S4 punctul 1, sectiunea 4), minus stocul deja rezervat
in sesiunea curenta (calculabil din `crsfactura` insusi, sumand liniile deja adaugate cu acelasi
`id_articol` — cursor local, dar de linii **efectiv adaugate**, nu de "cat mai era", deci nu mai poate
diverge de continutul real al facturii in curs).
**Ce se atinge:** `do_adauga_articol` (:13034-13051 dispare, inlocuit cu un apel la cerere din
`do_alege_stoc`, care oricum face verificare de stoc pe server — de confirmat la implementare daca
verificarea existenta acopera deja acest plafon sau trebuie extinsa), `do_sterge` (:14640-14669
Rol B dispare, ramane doar Rolul A), `do_modifica` (:13775-13798 plafonul se cere pe server, nu se
reconstituie din cursor).
**Ce se rupe:** **de verificat cu Marius** — plafonul Rol B azi e un avertisment soft (`do_alege_stoc`
primeste `lnCantitateMax` ca parametru, dar verificarea reala de stoc la comitere ramane oricum in
Oracle, la `adauga_articol_factura`/scriere; codul citit aici nu arata un blocaj hard bazat pe
`crsarticole` — de confirmat explicit, nu presupus, ca UX-ul de azi (mesaj/limitare la alegerea
cantitatii) nu depinde de faptul ca plafonul persista *in memorie* intre doua adaugari ale *aceluiasi*
articol in aceeasi sesiune, ci doar de valoarea citita la momentul respectiv).
**Cost:** mediu — mai multe interogari Oracle mici (una per adaugare/editare de linie gestionabila),
in loc de aritmetica locala. Justificat de acelasi motiv ca varianta (b): elimina a doua copie.
### Comparatie si recomandare finala
| | (a) cursor propriu minimal | (b) recalcul server (Rol A) | (c) plafon la cerere (Rol B) |
|---|---|---|---|
| E "sursa unica"? | Nu — copie mai mica, tot manuala | **Da** | **Da** |
| Risc de divergenta fata de Oracle | Identic cu azi | Eliminat (Oracle citeste Oracle) | Eliminat |
| Cost implementare | Mic | Mic-mediu (2 functii Oracle) | Mediu (mai multe roundtrip-uri) |
| Rezolva corectia sectiunii 0 (23/41 blocheaza punctul 1) | Nu direct | N/A (23/41 nu sunt Rol A) | **Da** |
**Recomandare: (b) pentru Rolul A, (c) pentru Rolul B.** Impreuna elimina complet nevoia de a incarca
`crsarticole` in masa pentru bookkeeping — incarcarea ramasa (daca ramane) e strict pentru
**enumerare** la "adauga tot" (sectiunea 1.4), care e un scop diferit si mai ingust (nu mai trebuie sa
tina sincron o cantitate, doar sa listeze randuri candidate o singura data, la deschidere).
---
## 5. Cazul limita: adauga si sterge inainte de salvare
**Azi:** `do_adauga_articol` decrementeaza `crsarticole`/`crsarticole1` (Rol A si/sau B, dupa tip);
`do_sterge` reface exact simetric, inainte de orice salvare (sectiunile 1.1-1.2 sunt perechi
simetrice pe fiecare grup de tipuri). Suma finala din `crsarticole` la momentul `do_scrie_factura`
reflecta corect doar liniile **ramase** in `crsfactura`, indiferent cate au fost adaugate si sterse
intre timp — pentru ca fiecare stergere anuleaza exact adaugarea corespunzatoare.
**In varianta recomandata (b+c):** cazul limita devine **trivial**, nu doar acoperit — pentru ca nu
mai exista o stare intermediara de sincronizat. Rolul A: `do_scrie_factura` calculeaza remainder-ul
o singura data, la scriere, din `VANZARI_DETALII_TEMP` care contine **exact** liniile ramase in
`crsfactura` la acel moment (populat de `do_scrie_articole()` chiar inainte, `Scan` peste `crsfactura`
curent — sectiunea 4 varianta (b)) — liniile adaugate-si-sterse nu ajung niciodata in
`VANZARI_DETALII_TEMP`, deci nu influenteaza calculul, fara nicio actiune suplimentara. Rolul B:
plafonul la fiecare adaugare se cere din nou pe server, minus ce e deja in `crsfactura` in acel
moment — o stergere anterioara pur si simplu nu mai apare in acea suma, fara "refacere" explicita.
**Cazul limita nu mai e un caz special de tratat — e comportamentul implicit al oricarei citiri facute
din starea curenta, in loc de dintr-un contor tinut manual.**
---
## 6. Per tip de document
| Tip(uri) | Rol azi | Ce se schimba in varianta recomandata |
|---|---|---|
| **Comanda** `3,21,25,28,42,47` | Rol A (inchidere automata) | `do_scrie_factura` cheama functia Oracle noua (4b) in loc de `Calculate Sum`; `do_adauga_tot`/`do_sterge` raman (enumerare + Rol B nu se aplica pe comanda insasi, doar pe articolele ei individuale daca sunt gestionabile — **de verificat**, comanda nu apare in niciuna din listele Rol B de la sectiunea 1, deci pare curatata deja) |
| **Avize** `4` | Rol A (marcheaza_facturat) | idem, functia Oracle pentru avize (4b) |
| **Contract** `2,6,26,52` | Jumatate `crsarticole` (delegat la `cursor_preturi`, per raportul punctului 1) e Rol B doar pentru `poDate.tip=2 And opt_facturare=0`; restul (`crsarticole1`, rate+articole `OPT_FACTURARE=3`) nu are bookkeeping in sectiunea 1 (needitat de cautare, ramane cum e azi, per raportul punctului 1 sectiunea 7) | Rolul B pe jumatatea `opt_facturare=0` trece pe varianta (c); `26,52` nu apar in niciuna din listele Rol B/A gasite in cod — **de verificat separat, posibil fara bookkeeping deloc pe aceste doua**, nesemnalat ca atare in codul citit aici |
| **Transfer** `41` (standard); `23` (doar prototip, standard il trateaza ca lista de preturi) | Rol B pur (niciun Rol A — confirmat sectiunea 3.1, `41`/`23` cad pe `ELSE` in `finalizeaza_factura`) | Trece integral pe varianta (c); **aceasta e corectia care debloca pasul 5 al punctului 1** — fara ea, oprirea incarcarii in masa pe `23,41` (propusa acolo) ar sparge plafonul de stoc existent |
| **Lista de preturi** `1,5,7,10,22,29` | Rol B doar pentru articole gestionabile (`1,22,29`; `5,7,10` nu apar in Do Case-urile Rol B — **fara bookkeeping**, confirmat pe cod) | `1,22,29` trec pe varianta (c); `5,7,10` nu au nimic de decuplat, deja curatate |
| **Retur** `8,9,24` | Rol B (inversul directiei fata de restul) | Varianta (c), simetric |
| **Restaurant `45`, K `48,49`** | Nu ating Rol A/B (in afara `Do Case`-urilor gasite; `45` explicit exclus la `poArticol.gestionabil=0 Or gnScadereStoc=0 Or poDate.tip=45` in ambele metode, deci ocoleste bookkeeping-ul indiferent de gestionabilitate) | Fara schimbare — deja fara bookkeeping de decuplat |
**Tipuri semnalate ca neclare, de stabilit separat, nu presupuse aici:** `26` (aviz din contract) si
`52` (contract in valuta/alt subtip) nu apar explicit in niciuna din listele Rol A/B din sectiunea 1 —
codul citit in aceasta sesiune nu confirma nici prezenta, nici absenta bookkeeping-ului pe ele
specific; tratamentul lor pare sa urmeze grupul `2,6` din `Inlist`-urile care le includ, dar niciun
`Do Case` din sectiunea 1 nu le mentioneaza individual in afara de includerea in `Inlist(poDate.tip,
2, 6, 52)` la `do_scrie_articole` (rate) — nu la bookkeeping-ul de `crsarticole`.
---
## 7. Pasi de implementare, ordonati
1. **Pas 1 — Oracle: extrage functiile de recalcul remainder** (`cantitate_ramasa_comanda`,
`exista_ramas_avize`) din logica deja scrisa in `inchide_comanda`/`marcheaza_facturat`, fara sa
modifice acele proceduri. *Gata cand:* apelate manual cu parametrii unei comenzi/unui grup de avize
cunoscute, valoarea returnata coincide cu suma pe care `Calculate Sum(cantitate)` din VFP o
calculeaza azi peste `crsarticole`, pe acelasi document, in aceeasi stare (comparatie directa,
inainte de orice alta schimbare).
2. **Pas 2 — VFP: `do_scrie_factura` cheama functiile noi in loc de `Calculate Sum`** (sectiunea 4b),
pastrand identic restul logicii (`pnFacturaRetur`, mesajele de confirmare, `pnParametruAditional`).
*Gata cand:* pe un document de comanda si unul de aviz, cu acelasi scenariu (facturare partiala),
dialogul de confirmare apare in acelasi moment si cu acelasi rezultat ca inainte de Pas 2 — regresie
manuala, comparatie inainte/dupa pe acelasi document.
3. **Pas 3 — Oracle: functie de plafon Rol B filtrata pe articol**, reutilizand `WHERE`-ul din
`cursor_preturi`/`cursor_gestiune` (deja proiectat la S4 punctul 1, sectiunea 4), minus ce e deja in
`crsfactura` pentru acelasi articol. *Gata cand:* pe un articol gestionabil cunoscut, valoarea
returnata coincide cu `crsarticole.cantitate` de azi, la aceeasi stare a sesiunii (inainte de orice
adaugare).
4. **Pas 4 — VFP: `do_adauga_articol`/`do_sterge`/`do_modifica` folosesc plafonul cerut pe server**
pentru grupul Rol B, in loc de `Replace cantitate` pe `crsarticole` (sectiunea 4c). *Gata cand:*
cazul limita de la sectiunea 5 (adauga-apoi-sterge inainte de salvare) produce acelasi plafon
disponibil ca azi, verificat manual pe un articol cu stoc limitat.
5. **Pas 5 — activarea pasului 5 al punctului 1 pe `23`/`41`** (oprirea incarcarii in masa), acum ca
Pasul 4 a mutat Rolul B in afara lui `crsarticole`. *Gata cand:* deschiderea formularului pe tip
`41` (si `23` pe prototip) nu mai executa `cursor_gestiune`/`cursor_preturi` la deschidere, dar
adaugarea unui articol tot respecta plafonul de stoc (verificat manual).
6. **Pas 6 — curatare: `crsarticole` ramane incarcat doar unde e nevoie de enumerare** (comanda, aviz,
contract-rate — pentru "adauga tot" existent), fara bookkeeping legat de el. *Gata cand:* grep pe
`ofacturare.vc2` pentru `Replace cantitate.*crsarticole` (in afara populare) nu mai gaseste
potriviri in `do_adauga_articol`/`do_sterge`/`do_modifica`.
7. **Pas 7 — verificare paritate finala** (sectiunea 8), pe toate tipurile cu document sursa.
*Depinde de:* S4 punctul 1 (Pasii 1-4 din PROIECTAREA acelui punct trebuie sa existe, dar Pasii 1-4
**de aici** pot fi facuti independent, inaintea sau in paralel cu populare — nu depind de cautarea
filtrata, doar de `crsarticole`/`crsfactura` existente azi). Pasul 5 de aici depinde explicit de Pasul
4 de aici (nu poate porni inaintea lui).
---
## 8. Cum se verifica paritatea inchiderii automate
**Comanda** (nu exista flag `INCHISA` persistat — cautat explicit in tot pachetul, zero potriviri;
starea se deriva din `COMENZI_ELEMENTE` vs `VANZARI_DETALII`):
```sql
-- inainte de facturare partiala + inchidere fortata (flux vechi vs nou, aceeasi comanda X)
{call pack_facturare.cursor_comanda(V_DATA_CURS, V_TIP, 'X', V_ID_UTIL, :cursor)}
-- numara randurile ramase (trebuie sa fie 0 dupa inchidere fortata, identic pe ambele fluxuri)
SELECT COUNT(*) FROM (...continutul cursorului de mai sus...);
-- verificare directa a compensatiei inserate de inchide_comanda
SELECT ID_ARTICOL, ID_POL, CANTITATE FROM COMENZI_ELEMENTE
WHERE ID_COMANDA = :X ORDER BY ID_COMANDA_ELEMENT DESC; -- randul nou trebuie sa fie identic pe ambele fluxuri (aceeasi cantitate compensatorie)
```
**Avize** (flag real: `VANZARI.FACTURAT`):
```sql
SELECT ID_VANZARE, FACTURAT, ID_UTILFACT FROM VANZARI
WHERE ID_VANZARE IN (:lista_avize_test)
ORDER BY ID_VANZARE;
-- rulat dupa fiecare flux (vechi, apoi nou, pe date de test resetate identic), FACTURAT trebuie sa coincida rand cu rand
```
**Protocol de comparatie**, aplicabil pe fiecare tip cu document sursa (comanda: alege una din
`3,21,25,28,42,47`; avize: `4`):
1. reseteaza datele de test la aceeasi stare (aceeasi comanda/aviz, aceleasi cantitati ramase);
2. factureaza partial pe fluxul **vechi** (cod actual), noteaza rezultatul interogarilor de mai sus;
3. reseteaza din nou la aceeasi stare initiala;
4. factureaza identic (aceleasi linii, aceleasi cantitati) pe fluxul **nou** (dupa Pasii 1-2 din
sectiunea 7), noteaza acelasi rezultat;
5. compara — trebuie sa fie identic, inclusiv pe cazul limita de la sectiunea 5 (adauga-si-sterge
inainte de salvare) si pe cazul avizelor multiple agregate intr-o singura factura (sectiunea 3.3,
riscul de marcare in bloc).
**Zero cazuri testate azi nu e dovada** (memorie de proiect) — protocolul de mai sus cere minim un
caz per tip din lista, plus cazul limita, nu doar "a mers o data".
---
## 9. Riscuri si ce ramane de decis de Marius
- **Corectia sectiunii 0/6**: pasul 5 al proiectarii punctului 1 (`s4_cautare_articole_server.md:417-424`)
presupune ca `23,41` n-au bookkeeping de pastrat — gasit aici ca au Rol B activ. **De confirmat cu
Marius daca punctul 1 se re-deschide pentru aceasta corectie sau ramane cum e, cu mentiunea ca
aplicarea pe `23,41` asteapta finalizarea punctului 2** (asa cum recomand la Pasul 5, sectiunea 7).
- **Asimetria `do_modifica` fata de `do_adauga_articol`/`do_sterge`** (sectiunea 1.3, grupul
`1,22,29,2`-lista nu se ajusteaza la editarea cantitatii unei linii existente) — **preexistenta**,
nu introdusa de decuplare. De decis daca varianta noua (Rol B pe server, sectiunea 4c) trebuie sa
reproduca exact aceasta asimetrie (plafonul nu se recalculeaza corect la editare pe aceste tipuri,
ca azi) sau sa o corecteze ca efect secundar al recalcularii la cerere (care, prin natura ei, ar
elimina automat asimetria — orice cerere de plafon citeste starea curenta, indiferent daca a fost
o adaugare sau o editare). **Recomandare: lasat sa se corecteze de la sine** (comportament mai
corect, cost zero suplimentar, dar semnaleaza explicit ca S4 schimba acest comportament punctual,
nu doar "decupleaza" — de mentionat in changelog daca se alege aceasta cale).
- **Riscul de concurenta multi-utilizator, semnalat ca beneficiu la sectiunea 4b, dar nediscutat cu
Marius**: varianta recomandata face `crsarticole` local sa nu mai poata diverge de Oracle *la
scriere*, dar tot poate divarge *in timpul editarii* (doi operatori pe aceeasi comanda, unul
vede plafonul invechit pana la urmatoarea lui adaugare/editare). Nu e un risc nou introdus — e
identic cu azi pe cursorul static incarcat o data la deschidere — dar varianta (c) il reduce (cere
plafonul proaspat la fiecare adaugare, nu o singura data la deschidere), fara sa-l elimine complet
(ramane fereastra intre "am cerut plafonul" si "am scris linia"). De mentionat ca imbunatatire, nu
garantie.
- **Cont Rol B pe contract `26,52`**: nu s-a gasit dovada nici de prezenta, nici de absenta bookkeeping
pe aceste doua tipuri in codul citit (sectiunea 6) — de verificat separat inainte de a le include in
Pasul 3/4 al implementarii, nu de presupus ca urmeaza grupul `2,6`.
- **Functiile Oracle noi (Pas 1, sectiunea 7) sunt o extragere, nu o duplicare** — dar tot inseamna cod
PL/SQL nou in `PACK_FACTURARE`, care trebuie revizuit separat de cineva familiar cu pachetul
(schema exacta a `JOIN`-urilor din `inchide_comanda`, reprodusa aici din citire, nu din executie
reala pe Oracle — verificarea Pasului 1 din sectiunea 7 e obligatorie inainte de a continua).
## Handoff
Cercetare incheiata in aceasta sesiune. Toate cele 9 puncte cerute sunt acoperite, cu citate
`fisier:linie` verificate direct pe fisierele reale (`ofacturare.vc2`, nu `.bak`; corpul
`PACK_FACTURARE.sql`, nu spec-ul comentat). Descoperirea centrala (Rol A vs Rol B, si corectia asupra
pasului 5 al punctului 1 pentru `23`/`41`) nu era vizibila din raportul punctului 1 — acela trata
`crsarticole` ca un singur registru omogen; aici s-a aratat ca sunt doua mecanisme cu scopuri
diferite, unul (Rol A) recalculat oricum de Oracle la scriere si deci usor de mutat pe server fara
pierdere de paritate, celalalt (Rol B) un plafon UI care nu alimenteaza nicio decizie de business.
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle (doar
`Read`/`Select-String`/`grep` pe fisiere de pe disc).

View File

@@ -1,246 +0,0 @@
# Cercetare — doua puncte deschise inainte de implementarea S4
Investigatie READ-ONLY, doua intrebari inchise din `docs\plan_13_unificare_formular_facturare.md`,
`#### S4`, sectiunea "De inchis inainte de implementare" (`:2092-2095`). Fara editari de cod, fara
`git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara scriere pe Oracle (numai `SELECT`).
Status: **GATA**. Ambele intrebari au raspuns confirmat pe cod, cu `fisier:linie`. Amandoua
raspunsurile de baza confirma "nu schimba nimic", dar fiecare are o rezerva concreta, nu triviala,
de pus in proiectarea S4 (nu doar de bifat).
Sursa Oracle: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii — liniile citate mai jos sunt verificate direct pe acest fisier, corpul pachetului).
Sursa VFP: `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare.prg` (perimetrul `frm_facturare_articole`
/ `factureaza`; `frm_facturare_articole2` / `factureaza2` citite doar pentru comparatie, nu atinse).
---
## Intrebarea 1 — `id_jtva_coloana` lipseste din `cursor_preturi`?
### 1.1 Ce e si cine o consuma
`id_jtva_coloana` identifica randul din `JTVA_COLOANE` (explicatia/coloana de TVA folosita la
raportare si SAFT) asociat unei linii de factura. E scris pe fiecare linie in `VANZARI_DETALII_TEMP`
(Oracle, prin `adauga_articol_factura`) si citit inapoi de UI-ul VFP pentru dialogul de "explicatie
TVA" (`Cb_explicatie_Tva`, `frm_articol_factura`).
### 1.2 Chiar lipseste din `cursor_preturi`? — confirmat pe SQL
Toate cele **cinci ramuri** ale `cursor_preturi` (`WHEN V_TIP = 45`, `WHEN V_TIP IN (1,2)`,
`WHEN V_TIP IN (5,6,10,52)`, `WHEN V_TIP = 7`, `ELSE` aviz — corpul la
`ff_...PACK_FACTURARE.sql:2138-2644`) au liste de coloane complete verificate direct — **niciuna nu
returneaza `id_jtva_coloana`** (`:2163-2221`, `:2268-2329`, `:2394-2427`, `:2470-2504`, `:2545-2603`).
`cursor_facturare` e un `REF CURSOR` slab tipat (`:28`), deci lista de coloane a fiecarei ramuri e
literal ce se vede in `SELECT`, fara camp implicit.
Acelasi lucru e adevarat si pentru **`cursor_gestiune`** (`:4158-4347`, coloanele la `:4207-4343`) —
relevant pentru intrebarea 2, tipul 41. `cursor_contract` (`:2646-2950`) delega `V_CURSOR` la
`cursor_preturi` (deci mosteneste lipsa), iar `V_CURSOR2` (rate + articole `OPT_FACTURARE=3`,
`:2718-2938`) **de asemenea nu are `id_jtva_coloana`** in niciuna din cele doua ramuri `UNION ALL`.
Prin contrast, **`cursor_avize`** (`:3703-3874`) **are** `A.ID_JTVA_COLOANA` explicit (`:3748`,
plus `GROUP BY`-urile de la `:3781`, `:3804`, `:3822`, `:3843`) si `cursor_comanda` (`:2952-3171`)
**nu are**, pe niciuna din cele doua ramuri (factura/aviz, `:2994-3170`). Deci impartirea reala e:
*are* — doar `cursor_avize`; *nu are* — `cursor_preturi`, `cursor_gestiune`, `cursor_contract`
(ambele cursoare), `cursor_comanda`.
### 1.3 E completat in alt pas, sau ramane gol? — DA, e completat, printr-un mecanism in doi pasi
Pasul 1 — **valoare implicita, nu `NULL`**. `do_initializeaza_articol`
(`ofacturare.vc2:13681-13683`, identic la `:17896-17897` pe prototip):
```
If Type('toArticol.id_jtva_coloana') = 'U'
AddProperty(toArticol,'id_jtva_coloana',0)
Endif
```
Daca `Scatter Name poArticol` dintr-un cursor fara coloana `id_jtva_coloana` produce un obiect fara
acea proprietate (`Type(...) = 'U'`), se adauga cu valoarea **`0`** (numeric, nu `NULL`).
Pasul 2 — **derivare reala din `proc_tvav`, neconditionata**. `frm_articol_factura.Init`
(`ofacturare.vc2:2344-2371`) suprascrie *intotdeauna* `id_jtva_coloana`, indiferent de ce a venit din
cursor:
```
Select jtva_coloane
If !Isnull(poArticol.proc_tvav)
Set Filter To cota_tva = poArticol.proc_tvav * 100 - 100
...
poArticol.id_jtva_coloana = id_jtva_coloana
Else
...
poArticol.proc_tvav = (cota_tva + 100)/100
poArticol.id_jtva_coloana = id_jtva_coloana
EndIf
```
`proc_tvav` **este** returnat de toate cele cinci ramuri ale `cursor_preturi` (coloana comuna,
confirmata la 1.2), deci lookup-ul are mereu ce derivea, indiferent daca `id_jtva_coloana` a lipsit
din cursorul sursa.
Acest `Init` ruleaza pentru **orice** linie adaugata prin `do_adauga_articol`
(`ofacturare.vc2:12813-13167`), pe ambele ramuri ale `Do Case` de la `:12870-12896`:
- articol negestionabil / `gnScadereStoc=0` / restaurant (`tip=45`) →
`Createobject("frm_articol_factura", ...)` direct (`:12873`);
- articol gestionabil (`Otherwise`, `:12881-12895`) → `do_alege_stoc` →
`Createobject("frm_articol_gest_factura", ...)` (`:13353`/`:13361`/`:13375`) — clasa care
**mosteneste** `frm_articol_factura` (`vfp_symbols -Class`: `frm_articol_gest_factura ->
frm_articol_factura -> _frmbase -> _form`) si al carei `Init` (`:3986-3989`) cheama
`DoDefault(tnCantitate,tlAscunde)` — deci ruleaza acelasi `Init` de la 2315-2371, cu aceeasi
derivare.
**Confirmare ca derivarea chiar functioneaza si nu doar exista in cod:** `do_alege_stoc`
(`:13330-13340`) avea, intr-o versiune anterioara (comentata, `v 2.2.18`), un scurtcircuit care
seta direct `id_jtva_coloana` fara sa mai arate formularul cand exista o singura optiune de TVA;
azi acel scurtcircuit e dezactivat si se creeaza intotdeauna obiectul `frm_articol_gest_factura`
(deci `Init` ruleaza mereu, nu doar in cazuri rare).
Safety-net-ul `Case Empty(poArticol.id_jtva_coloana)` din `inainte_de_do_termin`
(`:2205-2208`, mostenit si de `frm_articol_gest_factura`) e o verificare reziduala pentru cazul in
care nici `proc_tvav` n-are match in `jtva_coloane` — nu dovada ca `id_jtva_coloana` ramane gol azi
pe fluxul normal.
### 1.4 Concluzia care conteaza — cu o rezerva reala, nu ipotetica
**Pe fluxul de azi (`do_adauga_articol`), lipsa lui `id_jtva_coloana` din `cursor_preturi`/
`cursor_gestiune` NU e un bug** — e completat corect, de doua ori (default 0, apoi derivat real din
`proc_tvav`), pe ambele ramuri (gestionabil/negestionabil), inainte ca linia sa ajunga in
`crsfactura` (`Gather ... id_jtva_coloana ...`, `:12945-12950`/`:12992-12995`) si, de acolo, in
Oracle (`do_scrie_articole` → `adauga_articol_factura(...)`, `Alltrim(Str(poArt.id_jtva_coloana))`,
`:14085`).
**Rezerva: raspunsul "filtrarea nu schimba nimic" e adevarat DOAR daca implementarea S4 pastreaza
trecerea prin `do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa.** Cercetarea
anterioara (`docs\cercetare\s4_cautare_articole_server.md`, sectiunea 5) arata ca **singurul exemplu
real existent de cautare-pe-server** (`combosql`, `grd_factura.cCodMat.cboCodmat.LostFocus`,
`ofacturare.vc2:19288-19306`) **nu trece prin `do_adauga_articol` deloc** — face `REPLACE` direct in
`crsFactura`:
```
REPLACE codmat WITH crsCodMat.codmat, denumire WITH crsCodmat.denumire, id_articol WITH crsCodmat.id_articol IN crsFactura
```
Acelasi tipar identic pe coloana `cDenumire` (`:19312-19317`). Daca S4 extinde acest `REPLACE` cu
restul campurilor din cursorul filtrat — asa cum recomanda raportul anterior la sectiunea 5, punctul
4 ("sa extinda REPLACE-ul... cu restul campurilor") — **`id_jtva_coloana` nu mai trece prin niciun
`do_initializeaza_articol`/`frm_articol_factura.Init`**, pentru ca acel `REPLACE` nu creeaza obiectul
`poArticol` si nu instantiaza formularul. Cursorul filtrat tot n-are `id_jtva_coloana` (varianta
filtrata a `cursor_preturi` pastreaza aceeasi lista de coloane — vezi
`s4_cautare_articole_server.md:220`), deci randul din `crsFactura` ar ramane cu valoarea implicita
de camp (`Append Blank`, nu exista un `REPLACE` explicit pe acea coloana) — **fara nicio derivare din
`proc_tvav`**.
La scriere, `adauga_articol_factura` (Oracle) cauta `ID_JTVA_COLOANA = V_ID_JTVA_COLOANA` in
`JTVA_COLOANE` pe ramura `ELSE` a `CASE`-ului sau (`ff_...PACK_FACTURARE.sql:5187-5198` — ramura care
se aplica exact tipurilor de lista de preturi 1/5/7/10/22/29, tipurile de transfer 23/45/48/49 si
altele care nu intra in ramurile speciale comanda/aviz/restaurant/contract-cu-pret-fix) si, daca nu
gaseste o potrivire, **arunca `RAISE_APPLICATION_ERROR(-20000, 'Nu a fost gasita cota de TVA!
(FACT-013 : ...)')`**. O valoare implicita de camp (tipic `0`, posibil `NULL` dupa `Append Blank`,
de verificat pe structura reala a `crsfactura`) ar produce fie acest -20000, fie (daca exista un rand
`JTVA_COLOANE.ID_JTVA_COLOANA=0`) o cota de TVA gresita scrisa tacut — amandoua ar fi un **bug nou,
introdus de S4**, nu unul preexistent.
**Recomandare concreta pentru proiectarea S4 (nu doar constatare):** varianta filtrata a cautarii
(oricare ar fi mecanismul concret — `combosql` extins sau altceva) trebuie sa continue sa treaca prin
`do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa (sau sa reproduca explicit
derivarea din `proc_tvav` daca se alege un `REPLACE` direct), nu doar sa extinda `REPLACE`-ul de azi
cu campurile brute din cursor. De pus explicit ca cerinta in proiectarea S4, nu ca detaliu care se
rezolva de la sine.
---
## Intrebarea 2 — `cursor_gestiune` (tipurile 23/41) fara buton "adauga tot"?
### 2.1 Vizibilitatea butonului pe cod — confirmata
`but_urmator_tot1` are **`Visible = .F.` la design-time**
(`ofacturare.vc2:11257-11266`, `ADD OBJECT 'But_urmator_tot1' AS but_urmator_tot WITH ... Visible =
.F. ...`). Devine vizibil doar prin `This.but_urmator_tot1.Visible = .T.` explicit, in 8 locuri din
`Do Case poDate.tip` din `Init` (`:15108-15248`):
| Tip | Vizibil? | Linia care il seteaza |
|---|---|---|
| `eProforma=1` | da | `:15113` |
| `lCopiere` | da | `:15120` |
| `1,5,7,10` (lista de preturi) | **nu** (fara linie) | — |
| `2,6` (contract) | **nu** | — |
| `3` (comanda) | da | `:15150` |
| `4` (avize) | da | `:15166` |
| `21,28,42,47` (aviz din comanda) | da | `:15177` |
| `22,29` (aviz din lista de preturi) | **nu** | — |
| **`23`** (transfer subunitati) | **nu** — are propriul `Case` (`:15187-15195`), dar niciun `Visible=.T.` in el | — |
| **`41`** (retur transfer) | **nu** — are propriul `Case` (`:15197-15205`), dar niciun `Visible=.T.` in el | — |
| `25` (transfer din comanda) | da | `:15215` |
| `26` (aviz din contract) | **nu** | — |
| `8,9` (retur) | da | `:15240` |
| `24` (aviz retur) | da | `:15245` |
**Confirmat: 23 si 41 au fiecare `Case` propriu in `Do Case`** (nu lipsesc din el, spre deosebire de
tipul 52, verificat separat intr-o cercetare anterioara ca "nu intra in niciun `Case`") — dar niciunul
din cele doua `Case`-uri nu seteaza `Visible = .T.`, deci butonul ramane la valoarea implicita
ascunsa. Concluzie identica cu lista de preturi (1/5/7/10): "adauga tot" e ascuns azi, nu doar
"neconfirmat".
### 2.2 Exista alt mecanism de adaugare in masa pe 23/41?
**Nu unul dedicat — dar exista mecanismul de baza, "adauga rand cu rand", disponibil pe orice tip.**
`But_urmator1` (singular, nu "tot") e `ADD OBJECT`-at neconditionat in definitia clasei
(`ofacturare.vc2:11237`), fara vreun `Visible=.F.` la design-time si fara sa fie atins de `Do Case`
de la 15108-15248 (acolo se atinge doar `but_urmator2`, aferent `grd_contracte`/contract, scos prin
`RemoveObject` pentru tipurile non-contract, `:15322-15323` — nu are legatura cu 23/41). Lantul e
`But_urmator1.Click` → `do_urmator()` → `Thisform.do_adauga_articol()` (`:14715`) — exact metoda cu
derivarea de TVA confirmata la intrebarea 1. Deci pe 23/41, ca si pe lista de preturi, operatorul
adauga **o linie o data**, prin acelasi buton/metoda folosit si azi pe tipurile de lista de preturi —
nu exista un "adauga tot" separat de recreat, pentru ca nu exista nici azi.
### 2.3 Cine mai foloseste `cursor_gestiune` — CORECTIE fata de premisa din plan
Rutarea reala e in `ofacturare.prg`, procedura `factureaza` (formularul **standard**,
`frm_facturare_articole` — cel lansat implicit), `Do Case` de la `:266-308`:
```
Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi -> cursor_preturi (:279-282)
Case Inlist(tnTip, 2, 26, 6, 52) && contract -> cursor_contract (:283-290)
Case Inlist(tnTip, 3, 21, 25, 28, 42, 47) && comenzi -> cursor_comanda (:292-293)
Case tnTip = 4 && din avize -> cursor_avize (:294-295)
Case Inlist(tnTip, 41) && transfer/retur -> cursor_gestiune (:296-298)
Case tnTip = 30 -> cursor_aviz_nir (:299-300)
Case tnTip = 27 -> cursor_lucrare (:302-304)
Case Inlist(tnTip, 8, 9, 24) -> cursor_retur (:306-307)
```
plus, mai sus in acelasi `Do Case` (`:271-278`): `Case Inlist(tnTip, 48, 49)` → **`cursor_articole_k`**
(procedura complet diferita, nu una din cele cinci) si `Case tnTip = 45` → **`cursor_preturi`**.
**Pe formularul standard: doar tipul 41 foloseste `cursor_gestiune`.** Tipul **23 foloseste de fapt
`cursor_preturi`**, grupat direct cu tipurile de lista de preturi (1,22,5,29,7,10) — asta explica de
ce, la 2.1, tipul 23 se comporta identic cu lista de preturi (acelasi cursor sursa). Tipurile **45 si
48/49 nu ating deloc `cursor_gestiune`**: 45 → `cursor_preturi`, 48/49 → `cursor_articole_k` (iese
din perimetrul celor cinci cursoare din S4). Premisa din plan ("`cursor_gestiune` — tipurile 23/41")
si mentiunea "45, 48, 49" (`s4_cautare_articole_server.md:344`, tabelul de vizibilitate) **descriu
corect vizibilitatea butonului** (toate aceste tipuri au "adauga tot" ascuns), dar **nu si sursa lor
de cursor** — nu toate trec prin `cursor_gestiune`.
**Pe formularul prototip** (`frm_facturare_articole2`/`factureaza2`, accesibil live doar cu optiunea
`gnFacturareNou=1` **si** alegerea explicita a operatorului la un dialog de confirmare,
`ofacturare.prg:88-93` — deci reachable in productie, nu cod mort, dar opt-in), rutarea **difera**:
`Case Inlist(tnTip, 23, 41)` (`ofacturare.prg:776-778`) cheama **impreuna** `cursor_gestiune` pentru
**ambele** tipuri. Pe acest formular, premisa din plan e exacta. Divergenta intre cele doua formulare
(23 pe `cursor_preturi` in cel standard, pe `cursor_gestiune` in prototip) nu pare intentionata — nu
exista niciun comentariu care s-o justifice — dar e in afara perimetrului acestei cercetari (read-only,
fara editare) si nu afecteaza concluzia de mai jos, valabila pe oricare din cei doi cursori (niciunul
nu are `id_jtva_coloana`, ambii sunt tratati identic de `do_adauga_articol`).
### 2.4 Concluzia care conteaza
**Dupa S4, pe tipurile 23/41 nu se pierde nimic functional.** "Adauga tot" e deja absent azi pe
ambele (confirmat pe cod, 2.1), iar adaugarea se face deja rand-cu-rand prin acelasi mecanism folosit
si pe lista de preturi (`but_urmator1`/`do_urmator`/`do_adauga_articol`, 2.2) — mecanism pe care S4 nu
il atinge (S4 schimba doar sursa cursorului incarcat la deschidere, nu calea de adaugare a unei linii
individuale). Aceeasi rezerva de la intrebarea 1 se aplica identic aici: valabil doar daca varianta
filtrata a cautarii trece prin `do_adauga_articol`, nu daca reproduce `REPLACE`-ul direct din
`combosql`.
---
## Handoff
Nu e necesara predare — ambele intrebari sunt inchise, cu dovada pe cod. Rezervele de la 1.4 si 2.4
(derivarea `id_jtva_coloana` trebuie sa treaca prin `do_adauga_articol`/`frm_articol_factura.Init`,
nu prin `REPLACE` direct tip `combosql`) sunt de dus mai departe in proiectarea S4, nu doar de
arhivat aici. Corectia de la 2.3 (doar tipul 41, nu si 23, foloseste `cursor_gestiune` pe formularul
standard) merita reflectata daca planul S4 mai citeaza undeva "cursor_gestiune (23/41)" ca sursa unica.

View File

@@ -1,606 +0,0 @@
# S4b — Bara de butoane si meniul de adaugare
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit), pentru
povestea **S4b** din `docs\plan_13_unificare_formular_facturare.md:1890-1899` (textul decis in
sectiunea J, `:911-1101`). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si
`COMUN\programe\ofacturare_editare.prg` (perimetrul #6) — doar citite, cand au aparut in cautari.
## Verdict (esenta, 10 randuri)
Cele trei bucati din decizia 13 sunt implementabile fara cod nou major, dar cu **trei corectii fata
de textul din plan**, toate in favoarea utilizatorului: (1) golul de contract nu e doar
`but_urmator_tot1.Visible` lipsa pe tip 2/6/26 — pe **tip 52 formularul nu intra deloc in ramura
lui**, `Do Case` nu are niciun `Case` care sa-l prinda, deci pierde si titlul, si eliminarea coloanei
`cSerie`, nu doar butonul "tot"; (2) tiparul RORIS (`frm_tranzit`, dialog bespoke) **nu e cel mai
ieftin de refolosit** — suita are deja, folosit chiar in acest formular pentru retur
(`do_cauta_facturi`), un mecanism generic de selectie multipla cu bifare, `cauta_alfa(...,
tnTipReturn=1)`, mai aproape de cerinta („dialog modal cu coloana de bifat si criterii de cautare")
decat un formular nou; (3) „unitatea de selectie e rata" pe contract **nu e valabil pentru toate
contractele** — cursorul `crsarticole1` are doua ramuri disjuncte, `OPT_FACTURARE=3` (articole reale)
si `OPT_FACTURARE IN (1,2)` (rate de scadentar), iar un contract e mereu pe una singura; eticheta
„Alege ratele de facturat…" e corecta doar pe ramura a doua. Echivalenta „tot" = „alege total" tine
azi pe o singura rutina comuna, `do_adauga_articol`, dar `do_adauga_tot` **nu parcurge `crsarticole1`
deloc** — pe contract, „adauga tot" ar trebui sa fie scris, nu doar facut vizibil. Detaliile, cu
`fisier:linie`, mai jos.
---
## 1. Inventarul butoanelor de azi
### 1.1 `frm_facturare_articole` (formularul de compunere in productie, `ofacturare.vc2:10968-15739`)
Grid sursa (comanda/lista preturi) = `grd_articole` (`RecordSource=crsarticole`,
`ofacturare.vc2:11570`). Grid sursa-contract = `grd_contracte` (`RecordSource=crsarticole1`,
`:11891`). Grid destinatie (linii factura) = `grd_factura` (`RecordSource=crsfactura`, `:12263`).
| Control | Clasa | Caption/Picture | L/T/W/H | ToolTipText | `fisier:linie` |
|---|---|---|---|---|---|
| `But_modifica1` | `but_modifica` | (fara caption) `modific_sus.bmp` | 773/61/30/27 | "Modificare (CTRL+M)" | `:11192` |
| `But_sterge1` | `but_sterge` | (fara caption) `sterg_sus.bmp` | 811/61/30/27 | "Stergere (CTRL+D)" | `:11229` |
| `But_renunt1` | `but_renunt` | (fara caption) `renunt_sus.bmp` | 792/1/30/27 | "Renuntare (ESC)" | `:11202` |
| `But_reset1` | `but_reset` | (fara caption) `reset_sus.bmp` | 303/295/30/27 | "Reseteaza" | `:11213` |
| `But_urmator1` | `but_urmator`, `caction=do_urmator` | `urmator1.bmp` | 343/348/30/27 | "Verificare" | `:11237` |
| `But_urmator2` | `but_urmator`, `caction=do_urmator2` | `urmator1.bmp` | 343/143/30/27 | "Verificare" | `:11247` |
| **`But_urmator_tot1`** | `but_urmator_tot`, `caction=do_adauga_tot` | `urmator1_tot.bmp`, **fara Caption, fara ToolTipText** (clasa insasi nu are niciuna din ele — `cmd_butoane.vc2:414-425`) | 343/378/30/27, `Visible=.F.` implicit | — | `:11257` |
| `But_retur` | `but_retur`, `caction=do_retur` | `retur1.bmp`, `Visible=.F.` implicit | 343/407/30/27 | "Retur" | `:11221` |
Niciun buton "linie noua" (`but_nou`) — o linie se adauga alegand un rand din `grd_articole` /
`grd_contracte` si apeland `do_adauga_articol` (dublu-click / Enter, mecanism de grid standard, nu
citit exhaustiv aici), nu prin `APPEND BLANK` pe `crsfactura`. "Detalii linie" nu exista ca buton
separat — `But_modifica1` **e** azi echivalentul: deschide `frm_articol_factura` pe randul curent din
`crsfactura` (`do_modifica`, `:13746-13914`), reface plafonul de cantitate din cursorul sursa
(`crsarticole` sau `crsarticole1`, ales prin `poArticol.opt_facturare`, `:13778`) si reconstruieste
proprietatile de discount/pret in valuta.
**"Adauga tot" — `But_urmator_tot1.Click -> do_adauga_tot`** (`:13169-13198`):
```
PROCEDURE do_adauga_tot
If Used('crsarticole') And Reccount('crsarticole') > 0
Select crsarticole
Scan
...
Thisform.do_adauga_articol(.T.) && tlContract = omis => .F. implicit
...
Endscan
Else
aMessageBox("Nu exista articole de adaugat!", 48, "Atentie")
Endif
ENDPROC
```
**Parcurge exclusiv `crsarticole`.** Nu exista nicio ramura care sa parcurga `crsarticole1` — deci
chiar daca s-ar face vizibil pe tip 2/6/26/52, "adauga tot" **nu ar aduce nimic** pe un contract al
carui `grd_contracte`/`crsarticole1` are randuri (rate sau articole de contract), pentru ca butonul
citeste doar cursorul celalalt. E un gol de **cod**, nu doar de vizibilitate — vezi sectiunea 5.
**Conventia de clase de butoane** (confirmat, `inventar_controale_formulare.md`): clasa de baza
`buton` (`_cmd_base.vc2:16`) are `Caption=""` implicit — butoanele sunt doar-imagine 30x27px prin
design. `but_nou` (`cmd_butoane.vc2:214-227`, `caption=do_adauga`, `ToolTipText="Adaugare
(CTRL+N)"`) si `but_sterge` (`:354-366`, `caption=inainte_de_do_sterge`, tot fara Caption propriu la
nivel de clasa) au deja `ToolTipText`, dar niciodata `Caption` — "eticheta, nu iconita muta" ceruta de
decizia 13 e o schimbare reala, nu o simpla refolosire a clasei: fie se seteaza `Caption` pe instanta
(clasa `buton` are proprietatea, pur si simplu nefolosita azi), fie se creeaza o varianta de clasa cu
`Caption` implicit.
### 1.2 `frm_facturare_articole2` (prototip, `ofacturare.vc2:15741-19355`)
Arhitectura diferita: **un singur grid** `grd_factura` cu editare inline prin combo-uri in celule
(`cCodMat.cboCodmat`, `cDenumire.cCboDenumire`) — nu exista `grd_articole`/`crsarticole` separat.
| Control | Clasa | Caption/Picture | L/T/W/H | ToolTipText | `fisier:linie` |
|---|---|---|---|---|---|
| **`But_nou1`** | `but_nou` (caption suprascris pe instanta, vezi mai jos) | `nou_sus.bmp` | 719/175/30/27 | "Adaugare (CTRL+N)" | `:15936` |
| `But_sterge1` | `but_sterge` | `sterg_sus.bmp` | 747/175/30/27 | "Stergere (CTRL+D)" | `:15955` |
| `But_renunt1` | `but_renunt` | `renunt_sus.bmp` | 716/1/30/27 | "Renuntare (ESC)" | `:15944` |
**Sunt deja unde trebuie**: `Top=175`, gridul la `Top=204` (`:15936`, `:15955`) — **deasupra
tabelului**, exact pozitionarea ceruta de decizia 13.
**`But_nou1.Click -> do_adauga`** (`:17118-17122`, override propriu pe instanta, nu `caction`
mostenit de la clasa `but_nou`):
```
PROCEDURE do_adauga
Select crsFactura
APPEND BLANK
this.grd_factura.SetFocus()
ENDPROC
```
Adauga direct un rand gol si da focus in grid — **nu** cheama `do_adauga_articol`, nu verifica stoc,
nu deschide niciun dialog. Completarea articolului se face **dupa**, prin editare inline in celule
(`cboCodmat`/`cCboDenumire`) — acesta e tiparul care se leaga direct de S4 (cautare pe server, vezi
sectiunea 6), nu de meniul de adaugare in masa.
Nu exista `But_modifica`/`detalii linie` in acest prototip — editarea se face inline, in celula.
### 1.3 Tabel comparativ
| | `frm_facturare_articole` (productie) | `frm_facturare_articole2` (prototip) |
|---|---|---|
| Pozitia butoanelor de linie | lateral, intre cele doua griduri (`Left=773+`) | deasupra gridului (`Top=175`, gridul la `Top=204`) — cerinta decizie 13 |
| "Linie noua" | nu exista — se alege dintr-un grid sursa | `But_nou1` -> `APPEND BLANK` + focus |
| "Sterge linie" | `But_sterge1`, lateral | `But_sterge1`, deasupra |
| "Detalii linie" | `But_modifica1` -> `frm_articol_factura` (dialog complet) | nu exista — editare inline |
| Sursa de articole | 2 griduri separate, `crsarticole`/`crsarticole1` | niciunul — combo pe cod/denumire in celula |
| Caption pe butoane | fara, peste tot (doar iconite) | fara, peste tot (doar iconite) — nici prototipul nu rezolva "eticheta" |
Concluzie pentru implementare: **pozitionarea** se ia din prototip, **comportamentul de adaugare in
masa** (`do_adauga_tot`/dialoage de alegere) se ia din formularul de productie (prototipul nu are deloc
aceasta logica — n-a fost construit pentru documente cu sursa), iar **eticheta pe buton** nu exista
azi in niciunul din cele doua, e de adaugat explicit in ambele cazuri.
---
## 2. `xmenu()` — contract si exemple
**Definitie**: `COMUN\programe\proceduri_comune.prg:755-822` (identica, byte-cu-byte in structura, cu
`COMUN\programe\oproceduri_comune.prg:1550-1617` — a doua e cea incarcata efectiv, SET PROCEDURE
ADDITIVE peste prima, dar comportamentul e acelasi).
```
Procedure XMENU
Lparameters TCITEMS, TNBAR
&& TCITEMS = optiuni separate prin ";" ; "\-" = separator vizual (bara, nu optiune selectabila,
&& nu se trece prin ALLTRIM); "\<X" in fata unei litere = accelerator de tastatura
&& TNBAR = optiunea initial selectata (default 1)
...
RETURN IIF( LASTKEY()=27, 0, m.NSELECT ) && 0 la ESC sau la orice tastatura care nu alege
ENDPROC
```
- Popup-ul apare la pozitia mouse-ului (`Mrow()/Mcol()`), cu `DEFINE BAR` cate unul per optiune.
- Selectia se prinde in `GETCHOICE` (`oproceduri_comune.prg:1660-1666`): `m.NSELECT = Bar()`.
- **Returul e 1-based** (numarul barei alese), **`0` daca s-a apasat ESC** (`Lastkey()=27`) — verificat
prin `Empty(m.lnOptiune)` in tot codul (0 e Empty pentru numeric).
- Nu exista parametru de "titlu" separat — primul item e primul rand din popup, nu un header.
**Trei exemple reale, gata de copiat:**
1. **`inainte_de_do_modifica`** (`ofacturare_comun.vc2:4928-4935`) — meniu static, doua optiuni,
exact tiparul pe care planul il citeaza ca precedent pentru butonul unic:
```
PROCEDURE inainte_de_do_modifica
Local lnOptiune
lnOptiune = xmenu('Modificare \<date factura;Editare \<factura (articole, cantitati, preturi)')
Do Case
Case lnOptiune = 1
This.do_modifica()
Case lnOptiune = 2
This.do_editare_factura()
Endcase
ENDPROC
```
2. **`do_verifica`** (`ofacturare_comun.vc2:4879-4886`) — meniu static, trei optiuni, cu accelerator
pe fiecare literal:
```
lnOptiune = xmenu('Verifica codurile fiscale la fiecare \<data a facturilor;Verifica doar la \<inceputul lunii;Verifica doar la \<sfarsitul lunii')
IF EMPTY(m.lnOptiune)
RETURN
ENDIF
```
3. **Meniul de borderouri** (`ofacturare_comun.vc2:5006-5029`) — meniu **construit dinamic**, cu
optiuni suplimentare adaugate conditionat si un separator vizual `\-`:
```
lcMeniuPlus = ''
IF !(EMPTY(m.lnOptiuniPlus) OR ... )
FOR lnOptiunePlus = 1 TO m.lnOptiuniPlus
lcTitlu = ALLTRIM(GETWORDNUM(m.lcListaMeniu, m.lnOptiunePlus, '|'))
lcMeniuPlus = m.lcMeniuPlus + ';' + m.lcTitlu
ENDFOR
ENDIF
lcMeniu = m.lcMeniu + IIF(!EMPTY(m.lcMeniuPlus), ';\-' + m.lcMeniuPlus, '')
lnOptiune = xmenu(m.lcMeniu)
If Empty(m.lnOptiune)
Return
Endif
DO CASE
CASE m.lnOptiune = 1
...
```
Acesta e tiparul direct aplicabil la S4b: `lcMeniu` se construieste cu `Do Case poDate.tip` (vezi
sectiunea 3), apoi un singur `xmenu(lcMeniu)` si un `Do Case lnOptiune = N` care ramifica pe
pozitia in lista construita — **nu** pe numar fix, ca la exemplele 1-2, pentru ca lista variaza pe
tip. Trebuie tinut sincron `lcMeniu` (textul optiunii) cu ordinea in care se evalueaza `lnOptiune`
in `Do Case`, altfel un `Case lnOptiune = 3` nimereste alta optiune decat cea afisata — riscul
concret al unui meniu dinamic, absent la cele statice.
**Ce NU ofera `xmenu()`**: niciun mecanism de dezactivare a unei optiuni individuale (doar prezenta/
absenta din lista construita), nicio pictograma pe item, niciun submeniu. Pentru meniul din decizia 13
(4-5 optiuni pe sursa, variabile) e suficient — nu e nevoie de mai mult.
---
## 3. Conditiile de vizibilitate ale meniului, pe tip de document
Sursa: `frm_facturare_articole.Init`, `Do Case poDate.eProforma / poDate.lCopiere / poDate.tip`,
`ofacturare.vc2:15109-15248` (citit integral, nu esantion).
| Ramura (`Case`) | `tip`(uri) | Titlu | `but_urmator_tot1.Visible` | `but_retur.Visible` | `fisier:linie` |
|---|---|---|---|---|---|
| `eProforma = 1` | proforma (orice tip) | "PROFORMA" | **.T.** | — | `:15110-15113` |
| `lCopiere` | copiere factura/aviz | "COPIERE FACTURA/AVIZ ..." | **.T.** | — | `:15115-15120` |
| `Inlist(tip,1,5,7,10)` | lista de preturi (lei/valuta/credit note/fiscala valuta) | — | — | **.T.** | `:15122-15127` |
| `Inlist(tip,2,6)` | **contract** (lei/valuta) | "FACTURA PE CTR. ..." | — (nesetat, ramane `.F.`) | — | `:15129-15143` |
| `tip = 3` | comanda | "FACTURA LA COMANDA ..." | **.T.** | — | `:15144-15150` |
| `tip = 4` | din avize | "FACTURA DIN AVIZE" | **.T.** | — | `:15151-15167` |
| `Inlist(tip,21,28,42,47)` | aviz catre clienti din comanda | "AVIZ DE EXPEDITIE DIN COMANDA" | **.T.** | — | `:15168-15177` |
| `Inlist(tip,22,29)` | aviz din lista de preturi | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | — | `:15178-15186` |
| `tip = 23` | transfer subunitati | "TRANSFER INTRE SUBUNITATI" | — | — | `:15187-15195` |
| `tip = 41` | retur transfer | "RETUR TRANSFER" | — | — | `:15196-15205` |
| `tip = 25` | transfer din comanda | "TRANSFER INTRE SUBUNITATI PE BAZA DE COMANDA" | **.T.** | — | `:15206-15215` |
| `tip = 26` | **aviz din contract** | "AVIZ DE EXPEDITIE DIN CONTRACTUL ..." | — (nesetat) | — | `:15216-15234` |
| `Inlist(tip,8,9)` | factura retur lei/valuta | — | **.T.** | — | `:15236-15241` |
| `tip = 24` | aviz retur | "RETUR AVIZ DE EXPEDITIE" | **.T.** | — | `:15242-15248` |
| *(niciun Case)* | **`tip = 52`** | **nesetat — ramane titlul implicit al formularului** | **nesetat** | — | — |
**Corectie fata de plan** (`plan_13...md:924-926`, `:1882-1883`): planul spune "tipurile de contract
(2, 6, 26, 52) nu sunt in lista [de vizibilitate a `but_urmator_tot1`]". E adevarat pentru 2/6/26, dar
**incomplet pentru 52**: `poDate.tip = 52` nu apare in **niciun** `Case` al acestui `Do Case` — nu
doar ca nu primeste `but_urmator_tot1.Visible = .T.`, ci **nu primeste nimic**: nu i se seteaza
titlul (`lb_titlu_alb_b121.Caption` ramane cel implicit al formularului, probabil gol sau invechit),
nu i se scoate coloana `cSerie` din grid (ramane vizibila, desi contractul in valuta n-are serie de
lot relevanta aici), nu i se schimba eticheta coloanei de cantitate. Pe cod, tip 52 se comporta azi ca
un tip necunoscut care a ajuns totusi sa deschida formularul (posibil pentru ca fluxul Oracle il
recunoaste — `Inlist(tnTip,2,26,6,52)` la `cursor_contract`, `ofacturare.prg:283` — dar formularul de
articole nu l-a "prins" niciodata in `Do Case`-ul lui local). **Consecinta pentru S4b**: reparatia
corecta nu e "adauga 52 la linia lui 2/6" (ar ramane fara titlu/fara eliminare `cSerie`), ci
**adauga-l explicit ca al treilea membru al ramurii `Inlist(poDate.tip, 2, 6)`** de la `:15129`,
identic cu cum a fost tratat deja in `frm_date_factura.Init` (`ofacturare.vc2:9530`:
`Case Inlist(poDate.tip, 2, 6, 52)`, si inca o data la `:9632`, `:9670`) — **acolo tip 52 e deja
grupat corect cu 2/6**, doar in acest formular de articole a fost omis.
**Ce inseamna gruparea 2/6/26/52 pentru continutul meniului**: toate patru sunt "contract" — 2/6 =
factura pe contract (lei/valuta), 26 = aviz pe contract, 52 = factura fiscala in valuta pe contract
(`COMUN\docs\tipuri_documente_facturare.md:23,27,38,50`). Optiunile din tabelul J raman aceleasi
pentru toate patru (schimba doar cuvantul "Adauga"/"Alege" vs. eventual "Avizeaza" pe 26, daca se
pastreaza conventia de limbaj factura/aviz existenta in restul formularului — `lcTipDoc` calculeaza
deja acest cuvant in alte ramuri ale aceluiasi `Do Case`, e.g. `:15175,15185,15194`, dar **nu e setat
deloc pe ramura contract** — inca o mica omisiune, `lcTipDoc` ramane la valoarea implicita "factura"
si pe aviz de contract).
**Meniul propus, per sursa** (reluat din tabelul J, cu corectiile de mai sus):
| Sursa (`tip`) | Optiunile din `xmenu()` |
|---|---|
| lista de preturi (1,5,7,10) | Cauta in lista de preturi… · Alege din nomenclator… · Retur de articole… |
| **contract, `OPT_FACTURARE=3`** (2,6,26,52) | Adauga tot din contract · Alege articolele din contract… · Cauta in lista de preturi… · Alege din nomenclator… |
| **contract, `OPT_FACTURARE IN (1,2)`** (2,6,26,52) | Adauga toate ratele · **Alege ratele de facturat…** · Cauta in lista de preturi… · Alege din nomenclator… |
| comanda (3) | Adauga tot din comanda · Alege din comanda… · Cauta in lista de preturi… · Alege din nomenclator… |
| avize (4) / avize din comanda (21,28,42,47) / transfer din comanda (25) | Adauga tot din avize · Alege avizele… · Alege liniile din avize… · Cauta in lista de preturi… · Alege din nomenclator… |
| retur (8,9,24) | Adauga tot din facturile alese · Alege liniile de returnat… · Cauta in lista de preturi… · Alege din nomenclator… |
Diferenta fata de tabelul din plan e explicata in sectiunea 4 (contractul are doua meniuri posibile,
nu unul) si in sectiunea 9 (retur nu are azi un pas separat "alege facturile" la nivelul acestui
formular — vezi mai jos).
---
## 4. Dialogul de alegere selectiva
### 4.1 Nu porni de la `frm_tranzit` (RORIS) — porneste de la `cauta_alfa`
Raportul de referinta (`COMUN\docs\cercetare\import_roris_roaacnpro.md`) descrie corect **arhitectura
generala** (buton conditionat -> metoda unica -> dialog cu bifare si criterii -> populare aditiva prin
`INSERT`, fara scriere Oracle pana la salvare) — dar `frm_tranzit` insusi e un formular **specific
ROAACNPRO**, care nu exista in ROAFACTURARE si n-ar trebui portat ca formular.
**ROAFACTURARE are deja, in productie, in acelasi `frm_facturare_articole` care e subiectul acestei
povesti, mecanismul cerut** — `cauta_alfa()` (`COMUN\programe\cauta_alfa.prg:16-260`), functia de
cautare generica folosita de zeci de `do_cauta_*` din toata suita. Are deja tot ce cere decizia 13:
- **coloana de bifat**: cand `tnTipReturn=1`, `cauta_alfa` adauga singura o coloana `ales` peste
cursorul de rezultate (`cauta_alfa.prg:124`: `Select *, 0 As ales From &lcCursort ... Into Cursor
&lcCursor`) si titlul devine explicit "Alegeti ... (mouse-click pe numar sau apasati SPACE)"
(`oproceduri_facturare.prg:2104`);
- **criterii de cautare**: parametrul `tcStringCriterii` deschide `cauta_alfa_form_plus`, varianta cu
bara de criterii (`cauta_alfa.prg:22`); fara el, cautarea alfa/browse standard tot filtreaza
interactiv pe coloanele afisate;
- **populare aditiva, fara scriere in baza**: returul (`tnTipReturn=1`) e un XML cu randurile bifate,
convertit local prin `Xmltocursor(...)` (exemplu direct, `ofacturare.vc2:9196`) — nimic nu ajunge la
Oracle in acest pas;
- **e deja folosit in acest exact formular**, pentru un caz de "alege documentul sursa": vezi 4.2.
**Recomandare de proiectare**: dialoagele "Alege..." din meniul S4b se construiesc pe reteta
`cauta_alfa(..., tnTipReturn=1)`, cu `tcselect`/`tcfiltru` diferite pe sursa (comanda/contract/avize/
retur), **nu** pe un formular nou stilizat dupa `frm_tranzit`. Ramane de portat din RORIS doar
**ideea**, nu codul: buton unic condiționat, metoda de intrare unica, populare aditiva — toate trei
deja adevarate si pentru `cauta_alfa`.
### 4.2 Precedentul direct: retur multi-factura
`frm_date_factura.do_cauta_facturi` (`ofacturare.vc2:9173-9212`) foloseste deja exact acest tipar,
pentru cazul "alege mai multe facturi sursa de retur":
```
lcXMLFacturi = caut_facturi_multiple_client(poDate.id_client, poDate.in_valuta, poDate.id_valuta, .T.)
If !Empty(lcXMLFacturi) and gnButon = 1
Xmltocursor(lcXMLFacturi, "crsFacturiTemp")
...
poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")
```
si `caut_facturi_multiple_client` (`oproceduri_facturare.prg:2091-2121`) cheama direct
`cauta_alfa(..., lnTipReturn = Iif(tlFacturiMultiple, 1, 0))`. **Important pentru sectiunea 9**: acest
pas ruleaza azi la **antet** (`frm_date_factura`), inainte ca formularul de articole (si deci bara de
butoane S4b) sa existe — `poDate.listaid` e fixat o singura data, cursorul `crsarticole` se incarca
o singura data din el (`cursor_retur_document`, cu `V_LISTAID`). Optiunea "Adauga tot din facturile
alese" din meniul S4b **poate** refolosi direct `do_adauga_tot`/`crsarticole` asa cum e azi (sursa e
deja bounded). O optiune noua "Alege facturile de returnat…" **la nivelul barei de butoane** ar insemna
insa a permite schimbarea setului de facturi sursa **dupa** ce formularul de articole s-a deschis —
azi nu exista acest flux (odata setat, `poDate.listaid` nu se rescrie din articole). E o decizie de
proiectare noua, nu o simpla mutare a codului existent (vezi 9.3).
### 4.3 Ce cursor-sursa pe fiecare tip, si ce inseamna "linie" acolo
| Sursa | Cursor(oare) populat(e) la intrarea in `factureaza`/`factureaza2` | Ce e o "linie" pentru dialogul de alegere | `fisier:linie` |
|---|---|---|---|
| **comanda** (3, 21, 25, 28, 42, 47) | `crsarticole` <- `pack_facturare.cursor_comanda` | un articol de pe `COMENZI_ELEMENTE`, cu `id_pol` mostenit direct de pe linia comenzii | `ofacturare.prg:292-293` |
| **contract, `OPT_FACTURARE=3`** (2,6,26,52) | `crsarticole1` <- `pack_facturare.cursor_contract`, ramura `CTR_ARTICOLE` | un articol al contractului, `id_pol` derivat prin `CTR_ARTICOLE.ID_POL_ART -> CRM_POLITICI_PRET_ART` | `ff_...COMUN_PACK_FACTURARE.sql:2752-2836` |
| **contract, `OPT_FACTURARE IN (1,2)`** (2,6,26,52) | `crsarticole1` <- acelasi cursor, ramura `CTR_SCADENTAR` (`UNION ALL`) | o **rata** de scadentar (`ID_RATA`, `NR_RATA`, `DATA_RATA`, `DATA_SCADENTA`, `DEN_RATA`); `id_articol` e **NULL**, `cantitate` e mereu 1, `gestionabil=0` | `ff_...COMUN_PACK_FACTURARE.sql:2836-2924` |
| **contract, oricare din cele doua** — cautare libera | `crsarticole` <- acelasi apel, dar cu `pack_facturare.cursor_preturi` legat in interior | lista de preturi normala a operatorului (nu e "libera de politica", vezi `idpol_comanda_contract.md` E.3) | `ff_...COMUN_PACK_FACTURARE.sql:2940-2948` |
| **avize** (4) | `crsarticole` <- `pack_facturare.cursor_avize` | un rand de pe avizul sursa | `ofacturare.prg:294-295` |
| **retur** (8, 9, 24) | `crsarticole` <- `pack_facturare.cursor_retur_document`, filtrat pe `V_LISTAID` (facturile alese la antet, 4.2) | un articol al facturii/facturilor deja alese; **nicio coloana `id_vanzare`/`id_vanzare_det` in cursor** — legatura cu factura sursa se pierde inainte sa ajunga in VFP (`docs\cercetare\legatura_linie_retur.md:8-20`) | `ff_...COMUN_PACK_FACTURARE.sql:3949-4062` |
**Pe contract, `crsarticole1` e populat printr-un singur `UNION ALL`, dar cele doua ramuri sunt
disjuncte pe `OPT_FACTURARE`**: pentru un contract cu `OPT_FACTURARE=3`, `WHERE A.OPT_FACTURARE = 3`
elimina ramura de rate; pentru `OPT_FACTURARE IN (1,2)`, `WHERE ... IN (1,2)` elimina ramura de
articole. **Un contract nu produce niciodata ambele tipuri de rand in acelasi `crsarticole1`.** Deci
eticheta din meniu ("Adauga tot din contract" vs. "Adauga toate ratele") **trebuie sa depinda de
`crsarticole1.opt_facturare`** (coloana exista in cursor, `:2751,2893` — poate fi citita din primul
rand dupa incarcare), nu poate fi un text static ca in tabelul din plan.
**Daca `crsarticole1` iese gol** (contract fara articole/rate configurate), formularul de azi
**elimina complet** `grd_contracte`/`But_urmator2` si redistribuie inaltimea catre `grd_articole`
(`ofacturare.vc2:15294-15324`, `If !Used('crsarticole1') ... Thisform.RemoveObject('grd_contracte')`)
— documentul se comporta ca o factura "libera" din lista de preturi. Meniul S4b trebuie sa verifice
aceeasi conditie inainte sa ofere "Adauga tot din contract"/"Alege ratele…": daca cursorul nu exista
sau e gol, optiunea nu apare deloc (nu apare goala).
### 4.4 Populare aditiva si "nicio scriere pana la salvare"
Deja adevarat prin constructie, pentru toata familia `do_adauga_articol`: fiecare rand ales (fie prin
`do_adauga_tot`, fie prin rezultatul unui `cauta_alfa`) trece prin **`Thisform.do_adauga_articol(...)`**,
care termina cu `Insert Into crsfactura(...)` (cursor local, `.T. Buffering`) — niciun apel Oracle in
acest pas (Oracle e atins abia la salvarea documentului, `finalizeaza_factura`/`scrie_factura2`, in
afara acestei povesti). Un dialog nou de alegere trebuie doar sa **produca lista de randuri bifate**
(cursor XML din `cauta_alfa`) si sa **cheme aceeasi rutina** rand cu rand — vezi sectiunea 5, e
punctul care garanteaza si echivalenta cu "tot".
### 4.5 Zero rezultate — mesaj, nu tacere
RORIS nu spune nimic la zero rezultate (`import_roris_roaacnpro.md`, punctul 5: butonul "pare ca nu
face nimic"). `cauta_alfa` insasi nu are un mesaj dedicat de "zero rezultate" (e un browse generic —
daca cursorul de rezultate e gol, grila apare goala, fara alt semnal, cf. structurii ei standard).
**Reteta pentru S4b**: verificarea "zero rezultate" se face **inainte** de a deschide `cauta_alfa`
(similar cu `do_cauta_facturi`, care verifica intai `Empty(poDate.id_client)` inainte de a cauta),
printr-un `SELECT COUNT(*)` (sau verificare pe cursorul deja incarcat, `Reccount(...)=0`) urmat de
`AMESSAGEBOX` care spune **de ce**: "Contractul nu are rate de facturat neincasate" / "Comanda nu are
articole ramase de facturat" / etc., in loc sa lase dialogul sa se deschida gol. Exact tiparul deja
folosit de `do_adauga_tot` insusi pe ramura fara date (`:13195-13197`,
`"Nu exista articole de adaugat!"`) — se extinde acelasi obicei la dialogul de alegere.
---
## 5. Echivalenta „tot" vs. „alege"
**Garantia structurala exista, dar e incompleta azi.** Toate cele trei cai de populare a
`crsfactura` — `do_adauga_tot` (SCAN + apel), un viitor dialog de alegere (bifare + apel pe fiecare
rand bifat), si adaugarea manuala rand cu rand — converg spre **aceeasi rutina**,
`Thisform.do_adauga_articol(tlImplicit, tlContract, tlRetur)` (`ofacturare.vc2:12813-...`). Cata vreme
toate trei cheama exact aceasta rutina, cu acelasi cursor sursa pozitionat pe randul corect, rezultatul
per linie in `crsfactura` e identic prin constructie — nu exista o a doua cale de `INSERT` in
`crsfactura` in afara acestei rutine (confirmat prin cautarea `Insert Into crsfactura` — singurul alt
loc e ramura specifica `tip=30`/aviz din NIR, `ofacturare.prg:357-376`, care nu intra in perimetrul
S4b).
**Ce lipseste, concret, ca sa fie adevarat si pe contract**: `do_adauga_tot` (`:13169-13198`) parcurge
**doar `crsarticole`**. Pentru ca "Adauga tot din contract" / "Adauga toate ratele" sa functioneze,
`do_adauga_tot` are nevoie de o ramura noua (sau un al doilea parametru `tlContract`, simetric cu
`do_adauga_articol`) care sa faca `SCAN` peste `crsarticole1` si sa cheme
`Thisform.do_adauga_articol(.T., .T.)`. Fara aceasta completare, "tot" pe contract fie nu exista, fie
(daca cineva ar face butonul vizibil fara sa atinga metoda) ar aduce tacut liniile gresite din
`crsarticole` (lista de preturi) in loc de `crsarticole1` (articolele/ratele contractului) — o eroare
mai grava decat lipsa vizibilitatii, pentru ca n-ar da nicio eroare, doar rezultat gresit.
**Alegerea selectiva** trebuie sa respecte aceeasi regula: dupa ce `cauta_alfa` intoarce XML-ul cu
randurile bifate, bucla care le proceseaza trebuie sa pozitioneze cursorul sursa (`crsarticole` sau
`crsarticole1`, dupa caz) pe `id_c`-ul corespunzator inainte de fiecare apel `do_adauga_articol`, **nu**
sa reconstruiasca linia din campurile XML direct — altfel cele doua cai ("tot" vs. "alege") ar avea
doua implementari diferite ale "ce inseamna sa adaug randul X", exact riscul pe care criteriul de gata
din plan il exclude explicit ("rezultatul in `crsfactura` e identic pe cele doua cai cand selectia e
totala").
**Pe comanda/avize/retur, criteriul e deja adevarat prin constructie** — `do_adauga_tot` foloseste azi
`crsarticole`, care e cursorul lor unic; singurul de completat e contractul (cele doua cazuri de mai
sus).
---
## 6. Interactiunea cu S4 (cautarea articolelor pe server)
Decizia din S4 (`plan_13...md:1879-1888`) e explicita: `crsarticole` **nu se mai incarca in masa**
pentru facturarea libera (lista de preturi/nomenclator), inlocuita cu `combosql` pe cod/denumire care
completeaza randul pe loc — **dar** "se pastreaza adauga tot pentru tipurile care au document sursa
(comanda, aviz, contract) — acolo setul e marginit si incarcarea lui e legitima". Consecinta directa
pentru meniul S4b:
- **Optiunile "Cauta in lista de preturi…" si "Alege din nomenclator…"** (constante peste tot, cf.
deciziilor 16/18/20) **nu deschid un dialog de tip `cauta_alfa`** — ele sunt fatada meniului pentru
mecanismul S4: `combosql` pe o singura linie, populata pe loc, fara incarcare in masa. In termeni de
UI, alegerea uneia din aceste doua optiuni echivaleaza cu ce face azi `But_nou1.do_adauga` din
prototip (`APPEND BLANK` + focus pe celula de cod/denumire) — deci **"linie noua" (sectiunea 1) si
aceste doua optiuni de meniu ajung la acelasi gest UI**, doar ca "linie noua" il porneste direct
(fara meniu), iar cele doua optiuni de meniu il pornesc din context (dupa ce operatorul a vazut si
restul optiunilor sursei). Nu sunt cai de cod diferite — sunt doua puncte de intrare in acelasi
`APPEND BLANK` + editare inline.
- **Toate optiunile "Adauga tot din X…" / "Alege X…"** (comanda, contract, avize, retur) opereaza pe
cursoarele bounded (`crsarticole`/`crsarticole1`) pe care **S4 le pastreaza neschimbate** — pentru
aceste tipuri, nimic din mecanismul de cautare pe server nu se aplica; secțiunile 3-5 de mai sus
raman valabile ca atare.
- **Punct de atingere real intre cele doua povesti**: campul `id_pol` pe randul adaugat prin
`combosql` (S4, cautare din nomenclator liber) — subiectul blocajului `FACT-024` documentat pe larg
in plan (J-ter/J-quater) si in `idpol_comanda_contract.md`. S4b **nu rezolva** acest blocaj (e
decizia de produs deschisa la J-quater, intre reteta in 4 pasi / nota pe antet / nota pe antet ca
fallback) — doar il mosteneste: optiunea "Alege din nomenclator…" din meniul S4b va ajunge la aceeasi
eroare `FACT-024` la salvare, indiferent de cum arata butonul, pana cand acea decizie separata se ia.
De mentionat explicit in criteriul de gata al S4b, ca sa nu se creada ca butonul rezolva problema.
---
## 7. Ordinea de executie in pasi
1. **Bara de linie (`but_nou`, `but_sterge`, "detalii linie")**, mutata deasupra gridului, cu
`Caption` explicit pe fiecare instanta (clasele `but_nou`/`but_sterge` au deja `ToolTipText`, nu au
niciodata `Caption` — de setat pe instanta, nu de asteptat de la clasa).
*Verificabil*: cele trei butoane sunt vizibile deasupra gridului de linii, fiecare cu text vizibil
(nu doar iconita), pe orice tip de document deschis.
2. **Corectarea `Do Case` din `Init`** (`ofacturare.vc2:15109-15248`): adauga `52` la ramura
`Inlist(poDate.tip, 2, 6)` (linia 15129) astfel incat sa primeasca titlu, eliminare `cSerie`, si sa
fie tratat identic cu 2/6 in tot restul metodei — corecteaza golul mai mare decat cel semnalat in
plan (sectiunea 3 de mai sus). Adauga `26` la lista tipurilor care primesc `but_urmator_tot1.Visible
= .T.` candva in pasul 3, nu aici (26 ramane azi cu titlu corect, doar fara butonul "tot").
*Verificabil*: un document nou de tip 52 arata acelasi titlu si acelasi grid (fara `cSerie`) ca un
document de tip 2/6.
3. **Extinderea `do_adauga_tot`** cu ramura pe `crsarticole1` (parametru sau ramura separata dupa
`poDate.tip`/`crsarticole1.opt_facturare`), plus vizibilitatea butonului/optiunii pe 2/6/26/52 —
**cei doi pasi impreuna**, nu separat (vizibilitate fara continut ar aduce un buton mut; continut
fara vizibilitate nu s-ar vedea niciodata).
*Verificabil*: pe un contract cu `OPT_FACTURARE=3` si articole configurate, "adauga tot" produce in
`crsfactura` exact liniile din `crsarticole1`; pe un contract cu `OPT_FACTURARE IN (1,2)`, produce
cate o linie per rata neincasata.
4. **Butonul unic "Adauga articole" cu `xmenu()`**, inlocuind `But_urmator_tot1` (fara caption) si
orice buton separat de alegere — construit dinamic dupa tabelul din sectiunea 3, cu eticheta pe
ramura de contract aleasa in functie de `crsarticole1.opt_facturare` (sectiunea 4.3).
*Verificabil*: pe fiecare sursa, meniul deschis contine exact optiunile din tabelul sectiunii 3, in
ordinea specificata, iar alegerea `ESC` (`xmenu()` intoarce 0) nu declanseaza nimic.
5. **Dialogul de alegere selectiva**, pe reteta `cauta_alfa(..., tnTipReturn=1)` (sectiunea 4),
cu mesaj explicit la zero rezultate (4.5) si populare prin `do_adauga_articol` rand cu rand (5) —
cate o instantiere per sursa (comanda/contract-articole/contract-rate/avize/retur-linii), cu
`tcselect`/`tcfiltru` proprii.
*Verificabil*: pe fiecare sursa, bifarea a N randuri din M disponibile adauga exact acele N randuri
in `crsfactura`, cu aceleasi valori ca daca ar fi fost adaugate individual; bifarea tuturor produce
acelasi rezultat ca "adauga tot" (criteriul de gata al plan-ului, rescris verificabil).
*Depinde de*: pasul 3 (aceeasi rutina `do_adauga_articol` trebuie sa functioneze deja pe
`crsarticole1` inainte ca dialogul de alegere sa se poata baza pe ea).
6. **Reconcilierea cu selectia de facturi la antet** (sectiunea 4.2, 9.3) — decizie separata,
nu blocanta pentru pasii 1-5: daca "Alege facturile de returnat…" ramane doar la antet (cum e azi)
sau devine posibila si din bara de butoane.
*Gata cand (rescris)*: pe fiecare din cele sase surse (lista de preturi, contract-articole,
contract-rate, comanda, avize, retur), meniul "Adauga articole" ofera exact optiunile tabelate in
sectiunea 3; pentru fiecare sursa cu "tot"/"alege" ambele prezente, o selectie totala prin dialogul de
alegere produce in `crsfactura` acelasi set de randuri (aceleasi valori, nu doar acelasi numar) ca
"adauga tot"; zero rezultate in orice dialog de alegere produce un mesaj care spune sursa si motivul,
nu un dialog gol; tip 52 se comporta identic cu tip 2/6 in titlu si structura de grid.
---
## 8. Ce NU se poate testa headless
- **Continutul si comportamentul meniului `xmenu()`** — `Activate Screen` + `Define Popup ... Bar` e
UI nativa Windows/VFP (shortcut popup la pozitia mouse-ului); nu exista API de citire a itemilor unui
popup activ prin automatizare headless. Verificarea "meniul contine optiunile N" se poate face doar
**indirect**, citind stringul `lcMeniu` construit inainte de apelul `xmenu()` (verificabil headless,
ca text), nu popup-ul afisat efectiv.
- **Coloanele griduri (`grd_articole`, `grd_contracte`, `grd_factura`, si viitorul grid al dialogului
de alegere)** — capcana deja cunoscuta si confirmata pe acest proiect: sub `-A -T`
(rulare headless) `ColumnCount=0` si `RecordSource` raman artefacte, coloanele nu se materializeaza.
Exista un harness UI **vizibil** (nu headless) care le citeste corect — orice verificare vizuala a
meniului/dialogului de alegere trebuie sa treaca prin acel harness, nu prin rulare `-A -T`.
- **`cauta_alfa`/`cauta_alfa_form_plus`** — dialog modal cu `.Show(1)`, blocheaza thread-ul UI pana la
interactiune; testarea automata ar necesita fie injectare de input (interzisa pe aceasta masina,
partajata cu Marius — vezi memoria `masina-partajata-fara-input-real`), fie apelarea directa a
functiilor Oracle/cursor din spatele dialogului, ocolind UI-ul (verifica *datele*, nu *dialogul*).
- **`AMESSAGEBOX` la zero rezultate** — verificabil ca text al mesajului in cod (string literal), nu ca
aparitie reala pe ecran, din acelasi motiv.
- **Pozitionarea vizuala "deasupra gridului"** (`Top`/`Height` relative) — se poate verifica numeric
(comparand `Top`-ul butoanelor cu `Top`-ul gridului in `.vc2`), dar nu si "arata bine"/aliniere
vizuala — necesita captura de ecran prin harness-ul UI vizibil.
Ce **se poate** verifica headless, direct: continutul cursoarelor (`crsarticole`, `crsarticole1`,
`crsfactura`) dupa apelul rutinelor de adaugare, apelate direct (nu prin click) cu parametri de test;
stringul `lcMeniu` construit inainte de `xmenu()`; valorile `Visible`/`Caption`/`ToolTipText` setate pe
obiectele din `.vc2` (proprietati statice, nu comportament de runtime).
---
## 9. Riscuri, capcane de UX, si ce ramane de decis de Marius
### 9.1 Regulile de formulare/griduri (`COMUN\docs\reguli_lucru.md`, punctul 7)
Aplicabile direct aici (citite, respectate in proiectarea de mai sus):
- **`GO` pe `Recno()`** — orice bucla de tip `do_adauga_tot`/dialog de alegere care itereaza cursorul
sursa (`crsarticole`/`crsarticole1`) trebuie sa retina si sa refaca pozitia (`Recno()`) dupa fiecare
apel `do_adauga_articol`, exact cum face deja `do_adauga_tot` azi (`lnNrInregistrare = Recno()` ...
`Go lnNrInregistrare`, `:13175,13185,13193`) — pastrat si in ramura noua pe `crsarticole1`.
- **UX formulare/griduri** — butoanele fara `Caption` sunt azi conforme cu restul suitei (toate
butoanele suita sunt icon-only); a le adauga `Caption` e o schimbare vizibila la nivelul intregii
bare, nu doar a celor trei butoane noi — de discutat daca `But_renunt1`/`But_reset1` raman mute langa
butoane cu text (inconsistenta vizuala posibila, nu doar tehnica).
### 9.2 Contractul are doua meniuri, nu unul (sectiunea 4.3)
Tabelul din plan (J) trateaza "contract" ca o singura sursa cu un singur set de optiuni. Pe cod, un
contract e **fie** OPT_FACTURARE=3 (articole), **fie** 1/2 (rate) — niciodata ambele. Eticheta
"Alege ratele de facturat…" din decizia explicita a lui Marius e corecta doar pe ramura a doua; pe
prima, echivalentul e "Alege articolele din contract…". **De decis**: se pastreaza o singura eticheta
generica ("Alege din contract…") care acopera ambele cazuri fara sa distinga in text, sau se
diferentiaza explicit (mai clar pentru operator, dar mai mult cod de verificare a lui
`crsarticole1.opt_facturare` inainte de a construi meniul)?
### 9.3 "Alege facturile de returnat…" — la antet sau la bara de butoane?
Azi (sectiunea 4.2), selectia facturilor sursa pentru retur se face **o singura data**, la deschiderea
formularului de antet (`frm_date_factura.do_cauta_facturi`), inainte ca formularul de articole sa
existe. Meniul S4b, asa cum e cerut de decizia 13, ar pune "Alege facturile de returnat…" **in bara de
butoane a formularului de articole** — adica dupa ce `poDate.listaid` a fost deja fixat. Doua optiuni,
de decis de Marius:
1. Optiunea din meniul S4b **nu schimba** setul de facturi sursa — e doar un rascolitor spre inapoi,
catre acelasi dialog de la antet, dar utilizatorul trebuie sa iasa din pasul de articole ca sa-l
foloseasca (comportament posibil confuz: optiunea apare in meniul de articole, dar actioneaza pe alt
ecran).
2. Se adauga un mecanism nou: alegerea de facturi suplimentare **din formularul de articole**, care
extinde `poDate.listaid` si reincarca aditiv `crsarticole` (`cursor_retur_document` apelat a doua
oara, cu filtru pe facturile noi, `INSERT INTO crsarticole` in loc de `SELECT INTO`) — cere cod nou
in VFP, dar nu in `pack_facturare` (procedura Oracle ramane aceeasi, doar apelata de mai multe ori).
"Alege liniile de returnat…" (per articol, din facturile deja alese) e diferita si **nu are conflict**
cu fluxul de azi — e un dialog de alegere clasic peste `crsarticole`, ca pe comanda/avize.
### 9.4 `id_pol` pe rata de contract — `NULL` prin constructie, dar validat corect
Rata de scadentar (`crsarticole1`, ramura `OPT_FACTURARE IN (1,2)`) are **`id_pol` NULL** prin
constructia SQL insasi (`NULL AS ID_POL`, `:2840`) — dar asta nu ajunge niciodata la `FACT-024`,
pentru ca ratele nu trec prin `contabilizeaza_articol`, ci prin `contabilizeaza_rata` (functie separata
in `pack_facturare`, `idpol_comanda_contract.md` §0c), care citeste nota din `CONTRACTE.ID_NOTA`. **De
verificat inainte de implementare**: `do_adauga_articol`/`do_scrie_articole` scriu randul de rata in
`crsfactura` cu ce anume in coloana `id_pol` (probabil `NULL`, mostenit din `poArticol`) — daca
salvarea foloseste ramura corecta (`contabilizeaza_rata`, nu `contabilizeaza_articol`) pe baza altui
semnal decat `id_pol` (probabil `id_ctr`/`opt_facturare` pe linie), nu pe baza lui `id_pol` fiind gol.
Nu s-a verificat cine alege intre cele doua functii la salvare — e in afara perimetrului S4b (tine de
scriere, nu de bara de butoane), dar merita un pointer explicit ca sa nu se presupuna gresit ca "rata
fara `id_pol`" e acelasi caz cu "articol din nomenclator fara `id_pol`" (J-quater) — sunt cai de cod
diferite, cu tratament diferit.
### 9.5 Ce ramane strict de decis de Marius
1. Eticheta pe contract (9.2) — generica sau diferentiata pe `OPT_FACTURARE`.
2. Fluxul "Alege facturile de returnat…" (9.3) — raman la antet sau se adauga incarcare aditiva.
3. Daca `But_renunt1`/`But_reset1` primesc si ele `Caption`, pentru consistenta vizuala cu bara nou-
etichetata, sau raman icon-only (9.1).
4. Ordinea si formularea exacta a optiunilor in `xmenu()` per sursa (tabelul din sectiunea 3 e o
propunere, nu o decizie finala de text).
---
## Handoff
Cercetare incheiata, fara nicio problema de context intalnita in aceasta runda — nu a fost necesara
predarea. Toate cele noua sectiuni cerute de brief sunt completate mai sus, cu `fisier:linie` verificat
direct pe fisierele text reale (nu `.bak`), plus SQL-ul din `PACK_FACTURARE` exportat curent
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`). Nicio editare de
cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit.

View File

@@ -1,489 +0,0 @@
# S4c — Discountul pe linie, mutat din dialog in grid — proiectare implementabila
Cercetare + proiectare READ-ONLY pentru `docs\plan_13_unificare_formular_facturare.md`, `#### S4c`
(`:2156-2190`). Nu s-a modificat niciun fisier, nu s-a rulat `git_sync.ps1`/`txt2vcx.ps1`, nu s-a dat
commit, nu s-a scris nimic pe Oracle (numai `SELECT`, niciunul rulat de fapt — toata cercetarea a
fost pe cod VFP). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`.
**Surse de adevar deja stabilite, citate ca atare** (nu se re-verifica aici):
`docs\cercetare\discount_in_rapoarte_si_efactura.md` (verdictul principal — niciun raport/eFactura nu
tipareste discountul, dar amandoua citesc `valdiminuatftva`/`discountftva` din cursorul de tiparire),
`docs\cercetare\discount_verificare2.md` (structura reala a coloanelor din `grd_factura` si a
`VANZARI_DETALII`). **`docs\cercetare\discount_pe_articol.md` NU e citat ca sursa** — task-ul mi-a
semnalat ca are doua afirmatii gresite, corectate deja in `discount_verificare2.md` sectiunea finala
("Ce era gresit in afirmatiile de mai sus").
## Verdict (10 randuri)
S4c e implementabila cu cod nou moderat, nu cu redesign. Contractul de calcul exista deja, complet si
reciproc, in `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`) si
`frm_articol_factura.do_calculeaza_totaluri` (`:2068-2179`, delegand la functia partajata
`calculeaza_totaluri()` din `oproceduri_facturare.prg:2258-2381`) — aceasta din urma **e deja scrisa
generic, pe orice obiect cu proprietatile potrivite**, deci se poate rula direct pe un rand din
`crsfactura` (via `Scatter`/`Gather Name`), fara sa mai treaca prin dialog. Tiparul de eveniment
pentru coloane editabile de grid **exista deja in acelasi fisier**, pe alt formular din aceeasi
familie de clase (`frm_avizare_lucrare.grd_articole.cCantitate/cPret.Text1.LostFocus`,
`:6549-6562`) — `LostFocus` care cheama `Thisform.do_calculeaza_totaluri()`, nu `InteractiveChange`.
**Capcana reala e alta decat pare din titlul poveste**: exista **doua cai de scriere independente**
catre destinatii diferite, si doar una e azi corect legata. `do_scrie_articole` trimite spre Oracle
(`pack_facturare.adauga_articol_factura`, `ofacturare.vc2:14081-14083`) discountul citit **direct
din `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva`** — deci `VANZARI_DETALII.DISCOUNT_UNITAR`
**e mereu corect**, indiferent de bug, pentru ca aceste campuri sunt chiar campurile editate in grid.
Riscul e strict local, in sesiunea VFP curenta: `prelucreaza_factura` (apelata pentru tiparire/eFactura,
`ofacturare.prg:1887`) construieste cursorul de tiparire **din acelasi `crsfactura` deja in memorie**,
citind `valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` — campuri **agregate**
(pret-discount)*cantitate, care **nu se recalculeaza singure** cand se editeaza discountul brut. Deci
Oracle poate avea `DISCOUNT_UNITAR` corect si totusi factura tiparita/eFactura sa arate valoarea veche,
in aceeasi sesiune, pana la reincarcare. Solutia: coloanele noi de discount trebuie sa scrie, pe
`LostFocus`, atat campurile brute cat si campurile agregate — vezi sectiunea 4.
---
## 1. Lantul complet al campurilor de discount, cu `fisier:linie`
### 1.1 Structura cursorului local `crsfactura`
Definit in `creeaza_facturacrs` (`COMUN\programe\ofacturare_comun.prg:1779-1782`):
```
1779 valdiscountctva N(20,max(gnPc,4)),valdiminuatftva N(20,max(gnPc,4)),valdiminuattva N(20,max(gnPc,4)),valdiminuatctva N(20,max(gnPc,4)),proc_Tvav N(20,4),cu_tva N(1), lot C(20) null, serie c(100) Null,;
1782 vvaldiscountctva N(20,max(gnPVal,4)),vvaldiminuatftva N(20,max(gnPVal,4)),vvaldiminuattva N(20,max(gnPVal,4)),vvaldiminuatctva N(20,max(gnPVal,4)),id_set_fact N(20) Null,explicatie M Null,;
```
(`discountftva`, `discountctva`, `vdiscountftva`, `vdiscountctva`, `valdiscountftva`, `valdiscountctva`,
`vvaldiscountftva`, `vvaldiscountctva` sunt definite pe liniile adiacente, aceeasi procedura —
nu recitate individual, tiparul `N(20,...)` e identic).
**Nomenclatura confirmata pe cod** (nu doar dedusa din nume) — opt campuri distincte, trei niveluri:
| Camp | Nivel | Moneda | Cu/fara TVA | Sens |
|---|---|---|---|---|
| `discountftva` | pe unitate | lei | fara TVA | discount unitar, sursa in lei |
| `discountctva` | pe unitate | lei | cu TVA | discount unitar, derivat |
| `vdiscountftva` | pe unitate | valuta | fara TVA | discount unitar, sursa in valuta |
| `vdiscountctva` | pe unitate | valuta | cu TVA | discount unitar, derivat |
| `valdiscountftva`/`valdiscountctva` | pe linie (x cantitate) | lei | ambele | discount agregat lei |
| `vvaldiscountftva`/`vvaldiscountctva` | pe linie (x cantitate) | valuta | ambele | discount agregat valuta |
| `valdiminuatftva`/`valdiminuatctva` | pe linie (x cantitate) | lei | ambele | **valoare neta** (pret-discount)*cant — cea tiparita/eFactura |
| `vvaldiminuatftva`/`vvaldiminuatctva` | pe linie (x cantitate) | valuta | ambele | valoare neta in valuta |
### 1.2 De la tastare la `crsfactura` — calea de azi (prin dialog)
1. Operator tasteaza in dialogul `frm_articol_factura` (`ofacturare.vc2:1108-2659`), pe unul din 3
perechi de campuri: `Clb_procent_discount.Text_simplu1` (`InteractiveChange`, `:2646`),
`Clb_discount_unitar.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2614/:2620`),
`Clb_discountctva.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2602/:2608`).
2. Toate cheama `Thisform.do_calculeaza_discount(valoare, tip)` — **`frm_articol_factura`**
(`:1874-1976`, contract detaliat in sectiunea 2) — scrie in `poArticol`:
`discount_unitar`, `discount_unitar_val`, `discount_unitar_ctva`, `discount_unitar_ctva_val`.
3. `do_calculeaza_discount` cheama la final `Thisform.do_calculeaza_totaluri()` (`:1974`,
`frm_articol_factura`, `:2068-2179`) care delega la **`calculeaza_totaluri(poArticol)`**
(`oproceduri_facturare.prg:2258-2381`, functie globala) — aceasta scrie in `poArticol`:
`valdiminuatftva`, `valdiminuattva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuattva`,
`vvaldiminuatctva`, plus `valdiscountftva/ctva`, `vvaldiscountftva/ctva`, `valftva/ctva/tva` etc.
4. La inchiderea dialogului, `do_adauga_articol` (`ofacturare.vc2:12813-13167`) scrie **tot obiectul
deja calculat** in `crsfactura`, in doua pasi:
- `Gather Name poArticol Fields Like ... valdiminuatftva, valdiminuattva, valdiminuatctva,
vvaldiminuatftva, vvaldiminuattva, vvaldiminuatctva ...` (`:12945-12950`) — campurile agregate,
**deja calculate de `calculeaza_totaluri`**, nu recalculate aici.
- `Replace ... discountftva With poArticol.discount_unitar, discountctva With
poArticol.discount_unitar_ctva, vdiscountftva With Nvl(poArticol.discount_unitar_val,0),
vdiscountctva With Nvl(poArticol.discount_unitar_ctva_val,0)` (`:12952-12957`) — campurile pe
unitate, separat, pentru ca nu sunt in lista `Gather` (doar variantele `val*` agregate sunt).
5. `do_modifica` (`:13746-13914`, editarea unei linii existente) urmeaza acelasi tipar —
`Gather`/`Replace` cu aceleasi campuri (`:13837-13881`), dupa ce redeschide dialogul pe rand.
6. **Cale suplimentara, deja existenta, cu bug documentat**: pe factura in valuta, coloana
`cVdiscountftva` (`ControlSource="vdiscountftva"`, `ofacturare.vc2:12340-12345`) e editabila
direct in grid, **fara niciun handler** — scrie `vdiscountftva` direct in `crsfactura` prin
binding-ul standard de grid, dar **nu recalculeaza** `vvaldiminuatftva`/`vvaldiminuatctva`
(confirmat cautat explicit, `discount_verificare2.md` punctul 3).
### 1.3 De la `crsfactura` la Oracle (`VANZARI_DETALII.DISCOUNT_UNITAR`)
`do_scrie_articole` (`ofacturare.vc2:13916-...`), la finalizarea facturii, trimite spre
`pack_facturare.adauga_articol_factura` un singur parametru de discount, ales **direct din campurile
pe unitate** ale randului curent din `crsfactura` (`:14081-14083`):
```
14081 IIF(poArt.cu_tva = 0,;
14082 Iif(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountftva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountftva,18,gnPVal))), ;
14083 IIF(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountctva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountctva,18,gnPVal))))
```
**Nu foloseste `valdiminuatftva`.** Deci Oracle primeste intotdeauna valoarea curenta din
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` — cele patru campuri pe care coloanele
noi de grid le-ar edita direct. `VANZARI_DETALII.DISCOUNT_UNITAR NUMBER(22,6)` e singura coloana
Oracle de discount (confirmat `discount_verificare2.md` punctul 4) — nu exista coloana de procent pe
Oracle.
### 1.4 Inapoi, pentru tiparire/eFactura — `crsfacttemp`
`prelucreaza_factura` (`ofacturare_comun.prg:1055-1059`, apelata din `ofacturare.prg:1887`) **NU
reincarca din Oracle** — primeste ca parametru **acelasi `crsfactura`** deja in memorie (cel scris la
pasii 1.2/1.3), si construieste cursorul de tiparire prin agregare SQL locala:
```
1182 Sum(cantitate) As cantitate,pretftva-discountftva As pretftva,;
1186 Sum(valdiminuatftva) As valftva,Sum(valdiminuattva) As valtva,;
```
(`ofacturare_comun.prg:1182-1186`, cazul comun `discount_evidentiat=0`). eFactura foloseste **acelasi
cursor de iesire** (`xmlefactura.prg:231`, `LineExtensionAmount = valftva`, `PriceAmount = pretftva`,
`xmlefactura.prg:935-937`, `:1043-1046`).
**Consecinta directa**: `pretftva-discountftva` de la 1182 citeste `discountftva` (mereu proaspat,
pentru ca e campul editat direct), dar `Sum(valdiminuatftva)` de la 1186 citeste campul **agregat**,
care ramane vechi daca nu a fost recalculat explicit dupa editare. Rezultat: **randul tiparit poate
avea `pretftva` corect (net, recalculat corect din `discountftva`) dar `valftva` (valoarea liniei)
gresit** — o discrepanta pret x cantitate ≠ valoare, vizibila chiar pe hartie, nu doar o valoare
veche uniforma. Asta e mai grav decat "arata vechi" — arata **inconsistent**.
---
## 2. Contractul `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`)
**Semnatura**: `Lparameters tnValoare, tnTip` — `tnTip`: `1` = s-a modificat procentul, `2` =
discount in lei (fara TVA daca `preturi_cu_tva=0`, cu TVA altfel), `3` = discount in valuta.
**Ramura principala** — `If poArticol.preturi_cu_tva = 0` (pretul de referinta e fara TVA, cazul
uzual): sursa de adevar e `discount_unitar`(_val).
- `tnTip=1` (procent -> valoare): daca `tip_valuta=0`,
`discount_unitar = Round(pretftva * procent/100, gnPPretV)`, apoi **procentul se re-normalizeaza**
din valoarea rotunjita (`:1888-1889`) — nu se pastreaza procentul brut tastat, ci cel rezultat din
rotunjire. Daca `tip_valuta=1`: `discount_unitar_val` se calculeaza intai (`gnPVal`), apoi
`discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`.
- `tnTip=2` (lei -> procent): `discount_unitar = tnValoare` direct, procentul se deriva
(`Round(discount_unitar/pretftva*100, 2)`).
- `tnTip=3` (valuta -> procent + lei): `discount_unitar_val = tnValoare`, procentul deriva din
valuta, apoi `discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`.
- **Dupa `Do Case`, neconditionat**: `discount_unitar_ctva = discount_unitar + Round(discount_unitar
* (proc_tvav-1), gnPPretV)` (`:1913-1914`) — varianta cu TVA e **mereu derivata** din cea fara TVA
in aceasta ramura, niciodata sursa.
- Daca `tip_valuta=1`, simetric: `discount_unitar_ctva_val` derivat din `discount_unitar_val`.
**Ramura alternativa** — `Else` (`preturi_cu_tva=1`, pretul de referinta e cu TVA): rolurile se
inverseaza complet — `discount_unitar_ctva`(_val) e sursa (calculata direct din `tnValoare`/`pretctva`
in cele 3 cazuri, simetric cu ramura de mai sus), iar `discount_unitar`(_val) **fara TVA** e derivat
la final (`:1958`, `Round(discount_unitar_ctva / proc_tvav, gnPPretV)`).
**Rotunjiri**: `gnPPretV` pentru valorile unitare in lei, `gnPVal` pentru cele in valuta, `2` fix
pentru procent — niciodata `gnPc` (precizia de linie/document) in aceasta metoda.
**La final, neconditionat** (`:1972-1974`): `Thisform.clb_tva_discount.Refresh()`,
`Thisform.clb_pret_diminuat.Refresh()`, **`Thisform.do_calculeaza_totaluri()`** — deci orice apel
recalculeaza si campurile agregate de linie, nu doar cele pe unitate.
**In valuta, ce ramane needitat de aceasta metoda**: campurile agregate (`valdiminuatftva` etc.) NU
sunt scrise aici — `do_calculeaza_totaluri` (sectiunea urmatoare) le calculeaza separat din
`discount_unitar`/`cantitate`.
---
## 3. Starea de azi in grid — tabel
| Coloana | `ControlSource` | Formular | `ReadOnly` | Editabil azi | Recalculeaza la editare |
|---|---|---|---|---|---|
| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole` (productie), `:12311-12317` | `.T.` explicit | Nu | — |
| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole`, `:12340-12345` | nesetat (implicit `.F.`) | **Da, pe factura in valuta** | **Nu** — niciun `Valid`/`LostFocus`/`InteractiveChange` propriu, cautat explicit in `10968-15739` |
| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole2` (prototip, neinstantiat in productie) | `.F.` explicit, `:16657` | Da (prototip) | Nesigur — prototip, nu s-a cautat handler dedicat |
| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole2` | `.F.` explicit, `:16688` | Da (prototip) | idem |
| `Column14`/`procdisc` | `procdisc` | `frm_facturare_articole2` numai, `:16721` | nesetat | scaffold, fara scriere | **Nu are corespondent Oracle, nu are `Gather`/`Replace` nicaieri in fisier** (`discount_verificare2.md` sectiunea 5) |
**Excludere pe valuta** (`ofacturare.vc2:15269-15278`, in `frm_facturare_articole.Init`): cand
`poDate.in_valuta = 0` se elimina `cVpretFtva`, `cVdiscountftva`, `cVvaldiminuatftva`; cand
`in_valuta <> 0` se elimina `cPretFtva`, `cDiscountctva` (si simetricele lor). Deci azi, pe orice
factura, **doar una din cele doua coloane de discount e vizibila** — cea in lei, needitabila, sau cea
in valuta, editabila-dar-fara-recalcul. Nu exista azi nicio coloana de **procent** de discount in
`grd_factura` in productie (doar `discountctva`/`vdiscountftva`, valori, nu procent) — pentru procent,
azi operatorul trebuie sa deschida dialogul.
---
## 4. Proiectarea
### 4.1 Ce coloane se adauga/deschid
Nu se sterge nimic din `crsfactura` (modelul de date ramane neschimbat, conform cerintei). Se
lucreaza cu campurile deja existente:
- **Coloana procent discount** (noua in grid, in ambele monede) — `ControlSource` pe un camp
calculat, nu direct pe un camp Oracle (nu exista coloana Oracle de procent) — vezi 4.4 pentru
optiunea recomandata.
- **`cDiscountCTva`** (`discountctva`, lei): `Column5.ReadOnly` trece din `.T.` in `.F.` — devine
editabila, simetric cu ce azi doar prototipul `frm_facturare_articole2` face.
- **`cVdiscountftva`** (`vdiscountftva`, valuta): ramane editabila ca azi, dar castiga handler-ul
care azi lipseste.
Se pastreaza `RemoveObject` pe valuta (`:15269-15278`) neschimbat — excluderea reciproca deja
implementeaza cerinta "se pastreaza excluderea pe `in_valuta`".
### 4.2 Pe ce eveniment se cableaza calculul reciproc
**Recomandare: `Text1.LostFocus`, dupa tiparul deja folosit in acest fisier pentru coloane
editabile de grid** — `frm_avizare_lucrare.grd_articole.cCantitate.Text1.LostFocus` si
`.cPret.Text1.LostFocus` (`ofacturare.vc2:6549-6562`), ambele in aceeasi clasa de baza de grid
(`_grdrow`, `_grd_base.vc2:445`) folosita si de `grd_factura`. Motivele, nu teoretice ci din cod:
- **`InteractiveChange` fireste pe fiecare tasta** — ar recalcula la fiecare caracter tastat intr-un
numar cu zecimale (comportament vazut la coloana `procent` in dialog, `:2646`, dar acolo controlul
e un camp simplu de dialog, nu o celula de grid cu re-randare de coloane vecine la fiecare tasta —
in grid ar fi vizibil costisitor si ar zgaltai focusul).
`Valid`-urile din dialog (`:2602-2624`) folosesc explicit un guard `<> nSumaNatOld`/`nSumaValOld`
ca sa nu recalculeze cand valoarea nu s-a schimbat efectiv — semnaleaza ca autorii au evitat
deliberat recalculul pe fiecare tasta chiar si la `Valid`.
- **`LostFocus` e tiparul deja validat pentru grid-uri de articole in acest fisier**, pe doua coloane
numerice diferite (cantitate, pret), amandoua cu acelasi tip de nevoie (schimbarea unei valori
declanseaza recalculul liniei si al totalului documentului).
- Grid-ul VFP nu are `Valid` pe coloana insasi in mod uzual folosit aici — handlerele existente sunt
pe `<Coloana>.Text1.LostFocus`, deci noile handlere trebuie sa fie
`grd_factura.cDiscountCTva.Text1.LostFocus` si `grd_factura.cVdiscountftva.Text1.LostFocus`
(plus coloana noua de procent, daca implementata ca `Text1` editabil).
### 4.3 Rutina de recalcul — reutilizare, nu reimplementare
`calculeaza_totaluri()` (`oproceduri_facturare.prg:2258-2381`) **e deja generica**: primeste orice
obiect (`toArticol`) cu proprietatile `preturi_cu_tva`/`discount_unitar`/`pretftva`/`cantitate`/...
(le adauga singura, prin `AddProperty`, daca lipsesc — `:2264-2272`, mapand numele de camp
`crsfactura`-stil, `discountftva`/`vdiscountftva`, pe numele `poArticol`-stil,
`discount_unitar`/`discount_unitar_val`) si scrie inapoi campurile agregate. **Poate rula direct pe
un `Scatter Name` al randului curent din `crsfactura`**, fara sa deschida dialogul:
```
Select crsfactura
Scatter Name loArt Memo
loArt = calculeaza_totaluri(loArt)
Gather Name loArt Memo
```
Aceasta acopera pasul "recalculeaza campurile agregate din discountul unitar deja stabilit"
(`valdiminuatftva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuatctva`,
`valdiscountftva/ctva`, `vvaldiscountftva/ctva`), **exact campurile care azi raman vechi**
(sectiunea 1.4).
**Ce `calculeaza_totaluri()` NU face**: conversia reciproca procent<->valoare — aceea e logica din
`do_calculeaza_discount` (sectiunea 2), care azi scrie in `poArticol` si actualizeaza controale de
dialog (`Thisform.clb_*.Refresh()`) care nu exista in grid. **Recomandare**: se extrage o functie noua,
fara referinte la `Thisform.clb_*` (partea de calcul pur, liniile `:1878-1970` minus liniile de
`Refresh`), reutilizabila atat din dialog (daca dialogul de articol individual mai exista undeva —
nu la S4c, dialogul dispare) cat si din handler-ul de grid — parametrizata pe rand
(`toArticol`/`Scatter Name`) in loc de `poArticol` global. Aceasta functie noua intra la fel ca
`calculeaza_totaluri()`, in `oproceduri_facturare.prg`, ca sa fie apelabila din handler-ul de coloana
fara sa depinda de `poArticol`-ul dialogului disparut.
### 4.4 Coloana de procent — implementare recomandata
Nu exista coloana Oracle de procent (sectiunea 1.3) si nici coloana persistenta pe `crsfactura`
pentru asta (spre deosebire de `discountftva` etc, care sunt in `creeaza_facturacrs`). Doua optiuni:
1. **Adauga camp calculat, needitabil, alaturi de coloana editabila de valoare** — afiseaza procentul
derivat (`Round(discountftva/pretftva*100,2)`), needitabil direct — evita sa se mai adauge o
coloana Oracle noua si o cale de editare in plus (mai putin cod nou, mai putina suprafata de bug).
2. **Adauga coloana editabila de procent, needitabila-pe-model** — ca in `frm_articol_factura`
(`Clb_procent_discount`), scrie tot in `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva`
prin acelasi calcul reciproc, dar camp de UI, nu de Oracle. Mai aproape de comportamentul de azi
din dialog (operatorul poate tasta fie procentul, fie valoarea), dar cere si al treilea handler de
`LostFocus` si inca un camp de lucru in cursorul local (`AddProperty` la `do_initializeaza_articol`,
dupa tiparul `id_jtva_coloana`/`valdiminuatftva`, `:13666-13681`).
Cerinta din plan ("cele doua campuri de discount ale lui — procent si valoare unitara — devin coloane
in grid") cere explicit **ambele campuri editabile** — deci optiunea 2 e cea care respecta litera
cerintei; optiunea 1 e o simplificare de discutat cu Marius (vezi sectiunea 10).
### 4.5 Unde se cheama exact recalculul, pas cu pas (handler propus)
Pentru coloana `cDiscountCTva` (`discountctva`, lei, `tip=2` in nomenclatura `do_calculeaza_discount`):
```
PROCEDURE grd_factura.cDiscountCTva.Text1.LostFocus
Select crsfactura
Scatter Name loArt Memo
* recalcul reciproc procent<->valoare, tip=2 -- functia noua din 4.3, nu do_calculeaza_discount
loArt = recalc_discount_linie(loArt, This.Value, 2) && scrie discountftva/discountctva(_val)
loArt = calculeaza_totaluri(loArt) && scrie valdiminuat*/vvaldiminuat*
Gather Name loArt Memo
Thisform.do_calculeaza_totaluri() && resumeaza totalurile documentului
ENDPROC
```
Simetric pentru `cVdiscountftva` (`tip=3`) si pentru coloana de procent, daca implementata editabil
(`tip=1`). Guard-ul `<> valoare veche` (ca la `Valid`-urile din dialog, `:2602-2624`) se pastreaza ca
sa nu se recalculeze la simplu tab-through fara modificare.
---
## 5. Interactiunea cu discountul din politica de pret
**Azi**: `do_initializeaza_articol` (`ofacturare.vc2:13618` si urm.) preia
`toArticol.discount_unitar = Nvl(toArticol.discount_unitar, 0)` (`:13630`) **din `crsarticole`**
(cursorul de stoc/oferta, populat inainte de deschiderea dialogului — sursa SQL exacta, in afara
`ofacturare.vc2`, ramasa necercetata si in raportul precedent). Daca politica de pret a populat deja
un discount, acela apare **preincarcat** in campurile dialogului (`Clb_discount_unitar` etc.),
operatorul il poate suprascrie tastand — suprascrierea intra prin acelasi `do_calculeaza_discount`
ca orice alta tastare, fara distinctie intre "valoare din politica" si "valoare tastata manual".
**Dupa S4c**: nu se schimba nimic in mecanismul de preincarcare — `do_adauga_articol` inca scrie
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` in `crsfactura` la adaugarea liniei
(sectiunea 1.2, pasul 4), inainte ca operatorul sa apuce sa editeze coloana de grid. Valoarea din
politica **ramane vizibila in celula**, exact ca azi in dialog, iar editarea manuala in grid o
suprascrie la fel — singura diferenta e ca suprascrierea se intampla acum pe `LostFocus` de celula,
nu pe `Valid` de camp de dialog. **Nu exista azi o distinctie de tip "flag discount din politica vs.
discount manual"** in `crsfactura` (nu am gasit un camp `discount_din_politica` sau similar) — deci
S4c nu pierde nicio informatie care exista deja, dar nici nu castiga vreo trasabilitate noua.
---
## 6. Refacerea totalurilor documentului
Totalurile documentului (`Thisform.nbazaron`, `ntotalron`, `ndiscron`, variantele `*val`) se
recalculeaza in `frm_facturare_articole.do_calculeaza_totaluri` (`ofacturare.vc2:13423-13520`) —
**re-sumeaza direct din `crsfactura`** (si `crsfacturaset` daca exista), citind exact campurile
agregate din sectiunea 1.1/1.4:
```
13456 Select Sum(Nvl(valdiminuatctva,0)) As Total,Sum(Nvl(valdiminuatftva,0)) As Baza,;
13457 Sum(Nvl(valdiminuattva,0)) As Tva,;
13458 Sum(Nvl(valdiscountftva,0)) As discount From crsfactura Into Cursor crstotalurifact
```
Simetric pentru valuta (`vvaldiminuat*`) mai jos in aceeasi procedura. **Consecinta directa pentru
proiectare**: daca handler-ul de coloana (sectiunea 4.5) actualizeaza corect `valdiminuatftva`/
`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva`/`valdiscountftva`/`vvaldiscountftva` pe randul
editat **inainte** de a chema `Thisform.do_calculeaza_totaluri()`, totalurile documentului se refac
automat, corect, din acelasi mecanism care azi refece totalurile la adaugare/stergere de linie
(`do_adauga_articol:13160`, `do_sterge:14676`) — **nu trebuie cod nou pentru pasul de resumare**, doar
apelul, la finalul handler-ului de coloana. `Thisform.do_calculeaza_totaluri` e deja legat prin
`Bindevent` de `actualizeaza_total_mod` (`:15265`) — orice apel al lui declanseaza si actualizarea de
ecran a etichetelor de total, fara cablaj suplimentar.
---
## 7. Pasi de implementare, ordonati, cu criteriu de "gata"
1. **Extrage functia de calcul reciproc** din `frm_articol_factura.do_calculeaza_discount`
(`:1874-1976`), fara liniile de `Thisform.clb_*`/`Thisform.nprocent`, ca functie noua in
`oproceduri_facturare.prg`, parametrizata pe obiect + valoare + tip (semnatura similara cu
`calculeaza_totaluri(toArticol)`).
*Gata cand*: pentru fiecare din cele 3 `tnTip` si ambele ramuri `preturi_cu_tva`, functia noua
produce exact aceleasi `discount_unitar`/`discount_unitar_ctva`/`_val` ca metoda originala, testat
pe acelasi set de intrari (procent, lei, valuta) — comparatie directa, nu doar citire de cod.
2. **Deschide `Column5`/`cDiscountCTva` la editare** (`ReadOnly = .F.`,
`ofacturare.vc2:12311-12317`), pastrand `RemoveObject` pe valuta neschimbat.
*Gata cand*: pe factura in lei, celula de discount unitar cu TVA e editabila din grid (nu doar
afisata).
3. **Adauga handler `Text1.LostFocus` pe `cDiscountCTva` si pe `cVdiscountftva`**, dupa modelul din
sectiunea 4.5: recalcul reciproc (pasul 1) + `calculeaza_totaluri()` (deja existent,
`oproceduri_facturare.prg:2258`) + `Gather` + `Thisform.do_calculeaza_totaluri()`.
*Gata cand*: editarea oricareia din cele doua coloane schimba, in acelasi moment, si
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` **si**
`valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` pe randul curent,
verificat prin `Browse`/inspectie cursor, nu doar pe ecran.
4. **Adauga coloana de procent** (decizie 4.4 de confirmat cu Marius — recomandare: optiunea 2,
editabila, ca sa respecte litera cerintei din plan), cu acelasi handler, `tip=1`.
*Gata cand*: tastarea unui procent produce aceeasi valoare de discount ca tastarea valorii
echivalente, pe ambele monede.
5. **Verifica totalurile documentului** dupa editare in grid — nu ar trebui sa fie nevoie de cod nou
(sectiunea 6), doar de apelul din pasul 3.
*Gata cand*: dupa editarea discountului pe o linie, `Thisform.nbazaron`/`ntotalron` (si variantele
`val`) se schimba imediat, fara sa fie nevoie de o alta actiune (adaugare/stergere de linie) care
sa le forteze.
6. **Verifica scrierea in Oracle** (`do_scrie_articole`, `:14081-14083`) — nu ar trebui sa fie nevoie
de nicio schimbare, pentru ca citeste deja campurile pe unitate direct (sectiunea 1.3).
*Gata cand*: `VANZARI_DETALII.DISCOUNT_UNITAR` dupa salvare = valoarea tastata in grid, pe o
factura testata cu discount editat exclusiv din grid (fara sa fi trecut prin dialog, care oricum
dispare la unificare).
7. **Verifica tiparirea si eFactura** — cea mai importanta proba, cea ceruta explicit de plan.
*Gata cand*: vezi sectiunea 8.
---
## 8. Cum se verifica — concret
**Pasii**, pe o factura de test (lei si separat valuta):
1. Adauga o linie in grid (fara discount).
2. Editeaza direct in grid coloana de discount (valoare, apoi pe alt rand procent) — verifica pe
ecran ca celelalte coloane afisate (`cPretFtva`/`cVpretftva`, `cValdiminuatctva`/`cVvaldiminuatftva`
daca ramase vizibile) se actualizeaza imediat.
3. **Interogheaza direct cursorul** (nu doar ecranul) — din Command Window/breakpoint, pe randul
editat: `? discountftva, discountctva, vdiscountftva, vdiscountctva, valdiminuatftva,
valdiminuatctva, vvaldiminuatftva, vvaldiminuatctva` — toate opt trebuie sa reflecte editarea, nu
doar cele patru "brute".
4. **Finalizeaza factura** (`do_scrie_articole`) si interogheaza Oracle (schema `MARIUSM_AUTO`,
conventia din `COMUN\docs\scripturi-migrare-db.md:36-40`, prin `goExecutor`, doar `SELECT`):
```sql
SELECT discount_unitar FROM vanzari_detalii WHERE id_vanzare = :id_factura AND id_articol = :id_articol;
```
trebuie sa fie egal cu valoarea tastata in grid.
5. **Retipareste factura** (nu doar priveste pe ecranul de compunere — deschide efectiv raportul,
`factura.fr2` sau echivalentul in lei/valuta) si verifica pe pagina tiparita ca `pretftva`/`valftva`
pe linia editata reflecta discountul nou (pretul unitar net si valoarea liniei trebuie sa fie
consistente intre ele: `valftva = pretftva * cantitate`, altfel exact bug-ul din sectiunea 1.4).
6. **Genereaza XML-ul de eFactura** pentru aceeasi factura si verifica in fisier
`cbc:LineExtensionAmount`/`cbc:PriceAmount` pe linia editata — trebuie sa corespunda cu pretul net
nou, nu cu cel dinaintea editarii.
7. Repeta 1-6 pe **factura in valuta**, ca sa acoperi ramura `vdiscountftva`/`vvaldiminuatftva`
(ramura azi cu bug-ul confirmat).
---
## 9. Ce nu se poate testa headless
- **Editarea propriu-zisa a celulei de grid** (tastare in `Text1` al unei coloane, tab/enter pentru
`LostFocus`) — headless-ul VFP (`-A -T`) nu materializeaza interactiunea de tastatura intr-un grid;
cf. `docs\cercetare\...grid-coloane-nu-se-materializeaza-headless` (memorie de proiect) — coloanele
de grid sunt artefacte needitabile sub `-A -T`; verificarea reala a comportamentului UI cere
harness-ul cu UI vizibil sau un test manual asistat.
- **Tiparirea efectiva a raportului** (`.frx`) — generarea unui PDF/preview real, nu doar constructia
cursorului `crsfacttemp` in memorie, cere motorul de raportare VFP, care nu ruleaza util headless
pentru verificare vizuala (se poate verifica campurile cursorului sursa, dar nu pagina tiparita).
- **Trimiterea efectiva catre ANAF** (SPV) — se poate genera si inspecta XML-ul local, dar validarea
reala (acceptare/respingere) cere mediul de test ANAF, in afara acestei sarcini.
- **Comportamentul focus/tab-order al noilor coloane** in grid (ordinea de tab intre celule, daca
`LostFocus` se declanseaza corect la navigare cu tastatura vs. mouse) — cere sesiune interactiva.
---
## 10. Riscuri si ce ramane de decis de Marius
- **Coloana de procent — editabila sau doar afisata** (sectiunea 4.4). Recomandare: editabila
(optiunea 2), pentru ca respecta litera deciziei din plan ("cele doua campuri... devin coloane"), dar
costa un handler si un camp de lucru in plus. Daca simplitatea conteaza mai mult decat paritatea cu
dialogul vechi, optiunea 1 (doar afisaj) reduce suprafata de cod fara sa piarda functionalitate reala
(operatorul tot poate obtine orice discount tastand valoarea).
- **Riscul de rotunjire in lant**: `do_calculeaza_discount` are cel putin 3 rotunjiri succesive pe
drumul procent->valoare->procent (sectiunea 2) — comportament deja existent, nu introdus de S4c, dar
mutarea in grid (editare mai frecventa, rand cu rand, fara sa mai treaca prin "confirmare" de
dialog) ar putea face vizibile discrepante mici de rotunjire care azi treceau neobservate. Nu e un
motiv sa se schimbe rotunjirile (ar strica alte fluxuri), doar un risc de UX de semnalat.
**Zero cazuri gasite in cod care sa demonstreze deja o problema** — semnalat preventiv, nu confirmat.
- **`Gather`/`Scatter` pe rand cu campuri MEMO**: `do_adauga_articol` foloseste `Gather ... MEMO`
(`:12945-12950`) — handler-ul nou de coloana (sectiunea 4.5) trebuie sa faca la fel
(`Scatter Name ... Memo` / `Gather Name ... Memo`), altfel campul `explicatie` (M) s-ar putea goli
la fiecare editare de discount — **de verificat explicit la implementare**, nu doar presupus.
Recomand un test dedicat: editeaza discountul pe o linie cu `explicatie` populata, verifica ca
`explicatie` ramane neschimbata dupa `LostFocus`.
- **Coloana `procdisc` din prototip** (`frm_facturare_articole2`, `:16721`) — scaffold mort, fara
scriere (sectiunea 3). Recomandare: nu se reutilizeaza ca atare pentru coloana noua de procent —
se porneste curat, cu functia noua din sectiunea 4.3, nu cu acest camp neconectat.
- **Formularul `frm_facturare_articole2`** ramane prototip separat, ne-instantiat in productie — S4c
nu are nevoie sa il atinga, dar daca exista intentia sa devina formularul unificat, coloanele lui
de discount (deja editabile, `:16657`/`:16688`) ar trebui auditate separat pentru acelasi bug de
recalcul (nu verificat aici — in afara perimetrului cerut).
---
## 11. Punct deschis din S1 — `_checkbox1` vs `chkDetaliat`
Nu apartine povestii S4c (nu am gasit nicio legatura cu discountul pe linie sau `grd_factura`), dar
am dat peste ambele controale pe cale laterala, in `frm_alte_date` (`ferestre_cere_date.vc2`), asa ca
inchid ieftin: **sunt doua controale distincte, nu o duplicare de nume**.
- `_checkbox1` e un control generic (mostenit din clasa de baza), folosit in `frm_alte_date.Init`
(`ferestre_cere_date.vc2:3188-3202`) doar ca **reper de layout** — pozitia lui determina inaltimea
ferestrei si offset-ul altor controale, fara logica de business proprie vizibila in acest formular.
- `chkDetaliat` e un checkbox cu nume propriu, legat de fluxul de incasare (`actualizeaza_tipincasare`,
`:2728-2853`) — vizibil doar cand `opt_incasat` indica un anumit tip de incasare (POS/detaliat),
ascuns/zero altfel; pe ramura `Else` a lui `Init` (`:3187-3205`, cand alt tip de context nu implica
incasare deloc) e **eliminat explicit** din formular (`Thisform.RemoveObject('chkDetaliat')`,
`:3201`), spre deosebire de `_checkbox1`, care ramane (e folosit chiar pe linia urmatoare pentru
calculul inaltimii finale, `:3202`).
**Concluzie**: nu e o inconsecventa de cod, sunt doua controale cu roluri diferite pe acelasi
formular — punctul se poate inchide ca "nu e bug", cu rezerva ca n-am cercetat *de ce* `chkDetaliat`
exista ca si `checkbox` separat de `opt_incasat` (ce reprezinta exact "detaliat") — nu era in
perimetrul S4c si nu am aprofundat.
---
## Necunoscute ramase (mostenite din rapoartele-sursa, nu re-investigate aici)
- Interogarea SQL exacta care populeaza `crsarticole` cu discountul din politica de pret
(`CRM_POLITICI_PRET_ART`) — cod in afara `ofacturare.vc2`, netrasat (mostenit din
`discount_verificare2.md`, sectiunea "Necunoscute ramase").
- Valorile implicite ale flag-urilor `gnEFACTURA_XML_DISC_PLISTA_LINIE`/`_ART` (mostenit din
`discount_in_rapoarte_si_efactura.md`).
- Comportamentul `crsfacturafinalaval` (varianta valuta a cursorului final de tiparire) — presupus
simetric cu varianta lei, nu reverificat linie cu linie separat pentru S4c.

View File

@@ -1,429 +0,0 @@
# Proiectare S4d — Data cursului valutar, numai cand are sens (reactiv)
Poveste: `docs\plan_13_unificare_formular_facturare.md`, `#### S4d` (decizia 15, proiectata in
sectiunea M). Cercetare preliminara deja facuta si citata ca sursa de adevar:
`docs\cercetare\zi_curs_validare.md`, `COMUN\docs\cercetare\valuta_si_curs.md`,
`docs\cercetare\rec_dec42_proiectare.md`/`rec_d42_efactura.md` (decizia 42),
`docs\cercetare\s3_portare_antet.md` (bug #16), `docs\cercetare\s4_cautare_articole_server.md`/
`s4_puncte_deschise.md` (decizia 42, mecanismul ei exact). Read-only: nicio editare de cod, niciun
`git_sync.ps1`/`txt2vcx.ps1`, nicio scriere pe Oracle.
---
## Verdict (rezumat)
Regula reactiva se poate implementa fara sa strice nimic din ce e deja demonstrat sigur: implicitul
`poDate.zi_curs` ramane neconditionat (nu se schimba), iar cheia reactivitatii e un camp deja prezent
in cursorul de grid — `crsfactura.tip_valuta` — verificabil cu exact acelasi tipar SQL folosit deja in
cod (`ofacturare.prg:1656`, `:1730`: `Select Distinct ... From crsfactura Where tip_valuta = 1`).
Formularul-prototip `frm_facturare_articole2` are deja, azi, un `Clb_zi_curs` propriu, editabil, legat
la `poDate.zi_curs` (`ofacturare.vc2:16285-16305`) — nu trebuie inventat un control nou, ci reevaluata
vizibilitatea celui care exista deja. Mesajul Oracle `-20005` **contine deja** data si numele valutei
lipsa (`STRINGAGG` peste toate valutele fara curs) — decizia 42 nu schimba *continutul* mesajului, ii
schimba **domeniul**: il restrange de la "toate valutele din listele de preturi ale utilizatorului" la
"valuta articolului cautat", dar **doar pe varianta filtrata a cautarii** (S4 punctul 1); pe caile cu
document sursa (comanda/aviz/contract) incarcarea in masa ramane neconditionata. Plan sectiunea M
(`:2635-2638`) cere explicit ca la aceasta eroare campul sa **revina vizibil** — asta intra ca pas de
implementare, nu ca optiune. Bug-ul #16 **nu e in perimetrul S4d** — e deja cercetat si legat de S2
(bucla de reincercare din `ofacturare.prg`), cu verdict separat in `s3_portare_antet.md`; S4d doar
**confirma** ca noua arhitectura (antet persistent, nu recreat) ii inlatura mecanismul, daca S2/S3 nu
recreeaza formularul la eroare.
---
## 1. Inventarul controalelor de valuta si curs (`fisier:linie`)
Patru definitii `Clb_zi_curs` in tot `ofacturare.vc2` (control compus `clb_tx_data`, camp text legat
la `poDate.zi_curs`), confirmate exhaustiv (`grep "ADD OBJECT 'Clb_zi_curs'"`):
| Formular | Definitie | Eliminat azi? | Validare la Termina | Sincronizare cu data |
|---|---|---|---|---|
| `frm_date_aviz` | `ofacturare.vc2:6727-6740` | Niciodata | Nu exista (fara `inainte_de_do_termin` care sa o ceara) | `Clb_dataact...LostFocus` (`:7603-7604`), `Clb_dataireg...LostFocus` (`:7610-7611`) — `Thisform.clb_zi_curs.Refresh()`, neconditionat |
| `frm_date_aviz_lucrare` | `:7787-7801` | Niciodata | **Neconditionata**: `:8076` `Case Empty(Nvl(poDate.zi_curs,{}))` -> `:8078 This.clb_zi_curs.SetFocus()`, `plReturn=.F.` | `:8186-8187`, `:8193-8194`, neconditionat |
| `frm_date_factura` | `:8701-8714` | **Doar** tip 8/9 (retur), `Init` `:9717-9722` (`RemoveObject`) | Conditionata: `:9484` `Case poDate.in_valuta=1 And Empty(...) And Type('thisform.clb_zi_curs.visible')<>'U'` -> `:9488 SetFocus` | `:9805-9808`, `:9824-9827`, ambele cu garda `Type(...)<>'U'` |
| `frm_facturare_articole2` (prototip) | `:16285-16305`, camp text legat la `poDate.zi_curs` (`TEXT_SIMPLU1.ControlSource`) | **Niciodata** — `Init` (`:18988-19080`) nu contine niciun `RemoveObject('clb_zi_curs')` | Nu exista (formularul de articole nu are `inainte_de_do_termin` propriu de tip antet) | `Clb_dataact.TEXT_SIMPLU1.LostFocus` (`:19219-19222`) — acelasi tipar cu garda `Type(...)<>'U'` |
Alte controale de valuta/curs, relevante ca sa nu fie confundate cu `clb_zi_curs`:
- **`Ct_clb_valuta`** (selector de valuta document) — eliminat pe `frm_date_factura` cand
`poDate.in_valuta=0` (`:9725-9728`), **neconditionat de tip**. Confirma ca "ascunde cand nu are sens"
e deja un tipar folosit in aceeasi metoda, la doar 3 linii distanta de blocul retur — dovada directa
ca autorul stia sa faca exact acest lucru, doar nu l-a aplicat si campului de curs.
- **`lb_cursuri` + `grd_cursuri`** (eticheta si grid read-only "Curs valutar (data)") —
`frm_facturare_articole.Init` (`:15092-15100`) si `frm_facturare_articole2.Init` (`:19000-19012`):
```
ofacturare.vc2:19000-19012
If !Used('crscursuri') Or Reccount('crscursuri') = 0
Thisform.RemoveObject('lb_cursuri')
Thisform.RemoveObject('grd_cursuri')
Else
If !Empty(Nvl(poDate.zi_curs, {}))
Thisform.lb_cursuri.Caption = "Curs valutar " + Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "")
...
```
Acesta e **cel mai apropiat precedent de "arata/ascunde in functie de continut"** din codul existent
— dar evalueaza `crscursuri` (cursurile incarcate pentru toate valutele din listele de preturi ale
utilizatorului), **o singura data, la `Init`**, inainte ca userul sa fi adaugat vreo linie pe grid.
**Nu e reactiv** la adaugare/stergere de articole — e evaluat o singura data pe un cursor diferit de
`crsfactura`. Nu se poate copia ca atare pentru S4d; poate fi reutilizat doar ca **idiom** (verificare
`Reccount(...) > 0` care decide `RemoveObject`/reafisare), mutat pe alt cursor si alt eveniment
(punctul 2).
- **`poArticol.tip_valuta`** — proprietate a politicii de pret a articolului (nu a documentului),
populata la `do_initializeaza_articol`/cautare, citita masiv in calculul de pret/discount
(`ofacturare.vc2:1857-2288`, `frm_articol_factura`). Cand articolul e adaugat pe grid, valoarea
ajunge in coloana `crsfactura.tip_valuta` (vezi punctul 2) — acesta e semnalul pe care se construieste
conditia reactiva, nu `poArticol` (obiect temporar, mort dupa `Release`).
**Cine citeste `poDate.zi_curs` dupa formular** (relevant ca sa nu se rupa nimic la ascundere) — deja
documentat exhaustiv in `zi_curs_validare.md` §4 si `valuta_si_curs.md` §4: cursoarele de articole
(`cursor_preturi`/`cursor_articole_k`/`cursor_gestiune`/`cursor_lucrare`, `ofacturare.prg:266-308`),
eticheta `lb_cursuri`, si validarile de mai sus. Nimic din acest tabel se schimba prin S4d.
---
## 2. Conditia reactiva, exact
**Camp folosit:** `crsfactura.tip_valuta` (N(1)), definit la creearea cursorului de grid,
`creeaza_facturacrs` (`COMUN\programe\ofacturare_comun.prg:1776`), populat la fiecare linie adaugata
prin `do_adauga_articol` (coloana e in lista de `Insert`/`Scatter`, `ofacturare.vc2:12950`,
`:17258` pentru `frm_facturare_articole2`) direct din `poArticol.tip_valuta`.
**Verificare, cu exact acelasi tipar deja folosit in productie** (nu inventat):
```
ofacturare.prg:1656, :1730 [listeaza_ofacturare]
Select Distinct nume_val, Curs, multiplicator From crsfactura Where tip_valuta = 1
```
Pentru S4d, verificarea de vizibilitate se reduce la:
```foxpro
lVizibil = !Inlist(poDate.tip, 8, 9) And ;
(poDate.in_valuta = 1 Or (Used('crsfactura') And Reccount('crsfactura', 1) > 0) )
```
unde `Reccount('crsfactura', 1)` inseamna "exista macar un rand cu `tip_valuta=1`" — in practica se
scrie ca `Calculate Cnt() To lnLinii For tip_valuta=1` sau `Select Count(*) From crsfactura Where
tip_valuta=1 Into Array laCnt`, ca sa nu depinda de pozitia recordului curent din grid.
**Cand se reevalueaza — pe evenimentul deja existent de recalcul, nu pe un timer nou.** Atat
`do_adauga_articol` (`:17124-17393`) cat si `do_sterge` (`:18619-18704`) se termina cu apelul
**`Thisform.do_calculeaza_totaluri()`** (`:17360`, `:18687`) — acesta e deja punctul unic prin care
formularul "stie" ca s-a schimbat compozitia liniilor (totaluri, discount pe articole etc). E locul
natural unde se adauga si reevaluarea `clb_zi_curs`: dupa fiecare adaugare de linie **si** dupa fiecare
stergere, `do_calculeaza_totaluri` (sau un apel adaugat imediat dupa el in cele doua metode) reface
`lVizibil` de mai sus si seteaza `Thisform.clb_zi_curs.Visible = lVizibil`.
**Modificarea unei linii existente** (schimbare cantitate/pret pe o linie deja adaugata) nu schimba
`tip_valuta` — acesta e o proprietate a politicii de pret, fixata la adaugare, nu editabila pe grid.
Deci nu exista eveniment suplimentar de "editare linie" care sa afecteze conditia — doar adaugare si
stergere pot muta numarul de linii cu `tip_valuta=1` de la 0 la >0 sau invers. (Verificat: nu exista
`do_modifica` separat pe `frm_facturare_articole2` in indexul de simboluri — editarea unei linii merge
prin acelasi `do_adauga_articol`/dialog, care oricum recheama `do_calculeaza_totaluri`.)
**De ce nu `poDate.in_valuta`-style (o singura evaluare la Init):** documentul poate incepe fara nicio
linie in valuta si poate primi una la mijlocul sesiunii de facturare — exact cazul pe care unificarea
il face posibil de rezolvat (azi antetul se inchide inainte sa existe `crsfactura`). Evaluarea trebuie
sa fie pe eveniment, nu pe `Init`.
---
## 3. Trecerea inapoi — ultimul articol in valuta e sters
**Recomandare: ascunde din nou (simetric), dar NU goli `poDate.zi_curs`.**
Argumente pe tiparul deja folosit, nu pe teorie:
- `lb_cursuri`/`grd_cursuri` (punctul 1) folosesc deja idiomul "vizibil doar cand `Reccount(...) > 0`"
— o conditie booleana simpla, reevaluabila oricand fara efecte laterale, pentru ca `RemoveObject`/
re-adaugare (sau `Visible=`) nu ating proprietatea de date din spate (`poDate.zi_curs`). Simetria
(ascunde la 0, arata la >0) e deja tratata ca stare normala pentru acel control, nu ca o exceptie de
construit.
- `poDate.zi_curs` **nu se goleste niciodata** in tot codul existent (confirmat exhaustiv in
`zi_curs_validare.md` §5-6) — nici la `RemoveObject` (tip 8/9), nici la ascunderea `lb_cursuri`. Deci
ascunderea campului de curs cand ultima linie in valuta dispare **nu pierde valoarea**: daca userul
adauga din nou o linie in valuta in aceeasi sesiune, campul reapare cu **aceeasi data** pe care o avea
inainte de ascundere (fie cea introdusa manual, fie implicitul de la `Init`), nu resetata la azi.
- Alternativa ("ramane vizibil o data aratat") ar introduce un tip nou de stare per-sesiune
(`lFostVizibilCandva`) fara niciun precedent in cod si fara beneficiu clar — userul care sterge
singura linie in valuta de pe o factura in lei nu mai are, de fapt, niciun motiv sa vada/editeze
cursul; a lasa campul vizibil ar fi exact inconsistenta pe care decizia 15 vrea sa o elimine.
**Exceptie de la simetrie, ceruta de plan (sectiunea M, `:2635-2638`):** cand vine eroarea Oracle
`-20005` cu campul ascuns, campul **trebuie adus inapoi vizibil**, indiferent de `Reccount`-ul curent —
vezi punctul 5. Acesta e singurul caz in care regula reactiva simetrica se suspenda explicit.
---
## 4. Interactiunea cu `in_valuta` la nivel de document
`poDate.in_valuta` e stabilit o singura data, in `Init`, exclusiv din `tnTip`/`tnIdSet`
(`ofacturare_comun.prg:248-250`, confirmat exhaustiv in `valuta_si_curs.md` §1) — **nu exista cale de
cod care sa-l schimbe dupa Init**, iar controlul de alegere a valutei (`Ct_clb_valuta`) e chiar
eliminat din formular cand `in_valuta=0` (`ofacturare.vc2:9725-9728`), deci operatorul nu are de unde
sa-l aleaga manual. Prin urmare **intrebarea "ce se intampla daca operatorul schimba valuta documentului
dupa ce a adaugat linii" nu se poate pune in arhitectura actuala** — nu exista control care sa permita
acea schimbare. Documentul e in valuta sau nu inca de la alegerea tipului (`tnTip`), inainte ca vreo
linie sa existe.
Pentru S4d, consecinta e simpla: `poDate.in_valuta=1` e o conditie **statica** pe toata durata sesiunii
de facturare — cand e adevarata, `clb_zi_curs` ramane vizibil necontenit (ca azi), indiferent de ce se
intampla pe grid; conditia reactiva descrisa la punctul 2 conteaza **doar** pentru ramura
`in_valuta=0`. Nu exista tranzitie `in_valuta 0->1` sau `1->0` de tratat.
---
## 5. Mesajul de eroare `-20005`
### 5.1 Unde se ridica azi
`pack_facturare.verifica_cursuri_valute` (Oracle, `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16247-16274`):
```sql
16268 IF V_NUME_VALUTE IS NOT NULL THEN
16269 RAISE_APPLICATION_ERROR(-20005,
16270 'Nu este setat cursul din data de ' ||
16271 to_char(V_DATA_CURS, 'DD/MM/YYYY') ||
16272 ' pentru ' || V_NUME_VALUTE || '!');
16273 END IF;
```
`V_NUME_VALUTE` e un `STRINGAGG` (`:16252-16266`) peste **toate** valutele din
`FACT_VPRETURI_UTILIZATOR` ale utilizatorului curent care nu au curs valabil la `V_DATA_CURS` — exclude
doar moneda nationala. **Mesajul contine deja data si numele valutei/valutelor** — nu e un cod generic.
Apelata neconditionat din `cursor_preturi` (`:2153`), si delegat din `cursor_contract` (jumatatea
`crsarticole`, `s4_cautare_articole_server.md:83-85`).
### 5.2 Traseul pana la operator, azi
```
ofacturare.prg:313-317 (identic ofacturare.prg:828-832, factureaza2)
If lnSucces < 0
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
If goExecutor.nEroare = 20005
vizualizeaza_curs(poDate.zi_curs)
ENDIF
```
`goExecutor.oPrelucrareEroare()` (`COMUN\programe\oproceduri_comune.prg:626-639`) extrage **textul
brut** dintre `ORA-20xxx:` si urmatorul `ORA` din mesajul Oracle — pentru erori in intervalul
20000-20999 (cazul de aici), operatorul vede **exact** textul PL/SQL de mai sus, netrunchiat, nemodificat.
Deci raspunsul la "mesajul spune care valuta si ce zi lipsesc" e: **da, deja o face**, azi, inainte de
orice modificare S4d.
### 5.3 Ce schimba decizia 42
Decizia 42 (plan `:512-516`) **nu schimba textul** mesajului — schimba **domeniul de valute verificate**,
si **doar pe varianta filtrata a cautarii** (S4 punctul 1, `cursor_preturi` chemat per-articol-cautat,
nu la incarcarea in masa). Azi (fara decizia 42), pe orice apel al lui `cursor_preturi`,
`V_NUME_VALUTE` poate include valute complet nelegate de articolul pe care userul tocmai il cauta —
de exemplu userul cauta un articol in RON, dar mesajul ii spune ca lipseste cursul pentru EUR, pentru
ca EUR apare undeva in politicile lui de pret. Dupa decizia 42, pe cautarea filtrata, verificarea se
restrange la valuta randului adus — deci mesajul (acelasi format text) va numi, natural, **doar
valuta relevanta pentru cautarea curenta**, nu un agregat strain.
**Rezerva importanta, de citit impreuna cu S4d:** decizia 42 se aplica explicit "pe varianta filtrata
a cursoarelor" — adica pe calea noua de cautare per-articol introdusa de S4 punctul 1, folosita azi
doar pe **ramura de lista de preturi** (`s4_cautare_articole_server.md:68-69`: "S4 se aplica curat doar
pe ramurile de lista de preturi"). Pe **comanda / aviz / contract**, incarcarea in masa a articolelor
(bookkeeping obligatoriu, `crsarticole` citit si scris de `do_adauga_tot`/`do_sterge`/`do_scrie_factura`)
**ramane neschimbata**, deci `verifica_cursuri_valute` tot ruleaza neconditionat (toate valutele din
politicile utilizatorului), la incarcarea initiala a grilei — nu doar pe articolul cautat. **Pe aceste
tipuri, mesajul poate inca numi o valuta care n-are legatura cu documentul curent**, indiferent de
decizia 42 — asta ramane o diferenta reala fata de "campul e ascuns pentru ca documentul nu are nevoie
de curs", si intra la riscuri (punctul 10).
### 5.4 Ce se schimba prin S4d — nu textul, ci vizibilitatea campului dupa eroare
Cerinta explicita din plan, sectiunea M (`:2635-2638`):
> Ascunderea datei de curs poate lasa un document fara curs corectabil [...] utilizatorul nu mai are
> unde sa corecteze data daca i-am ascuns campul. **Regula din M trebuie sa aduca inapoi campul in
> exact acest caz.**
Implementare propusa, la punctul de interceptare deja existent:
```foxpro
* ofacturare.prg:313-317 (si simetric la :828-832)
If lnSucces < 0
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
If goExecutor.nEroare = 20005
If Type('thisform.clb_zi_curs.visible')<>'U' And !thisform.clb_zi_curs.Visible
thisform.clb_zi_curs.Visible = .T. && aduce campul inapoi, indiferent de conditia reactiva
Endif
vizualizeaza_curs(poDate.zi_curs)
ENDIF
```
Nota: `thisform` aici nu e formularul de antet (deja inchis la acest punct in arhitectura veche) — e
motivul pentru care acest fix e legat de rezultatul S3/S2 asupra bug-ului #16 (punctul 6): daca
antetul unificat ramane deschis/persistent (nu recreat), `thisform.clb_zi_curs` exista si poate fi
readus vizibil direct; daca arhitectura tot recreaza un formular nou dupa eroare, readucerea vizibila
trebuie facuta in `Init`-ul noii instante, verificand acelasi semnal (`goExecutor.nEroare=20005` din
iteratia anterioara, sau un flag explicit propagat).
**Textul mesajului propus de pastrat neschimbat** (deja corect): *"Nu este setat cursul din data de
DD/MM/YYYY pentru <valuta>!"*. Nu se propune inlocuirea lui — se propune doar sa nu mai fie surprinzator
faptul ca operatorul nu are unde sa corecteze, prin readucerea campului.
---
## 6. Verificarea daca #16 dispare
**Ce e #16:** `COMUN\docs\todos.txt:45` — "la revenire din formularul de curs valutar, focusul revine
[...] pe TIP DOCUMENT, si la iesire din serie se regenereaza numar act". Citat si in plan
`:1926-1927`, `:2665-2668`.
**Deja cercetat, cu verdict**, in `docs\cercetare\s3_portare_antet.md` §6 (runda 9), **inainte** de
aceasta sesiune — S4d nu redeschide cercetarea, o **confirma si o leaga** de domeniul propriu (eroarea
`-20005`, singurul declansator relevant pentru S4d):
- **#16 nu e un bug de focus.** E o bucla de reincercare (`ofacturare.prg:174-571`, `Do While
lnRaspuns=6`) care trateaza **orice** esec Oracle la incarcarea cursorului de articole ca pe un "DA,
utilizatorul vrea alt document" — `lnRaspuns` nu se reseteaza pe ramura de eroare
(`ofacturare.prg:555-564`, in `Else`, niciodata atinsa cand `lnSucces<0`), deci bucla externa reintra
automat si **recreeaza formularul de antet de la zero** (`Createobject`, `:230`). `Init`-ul noii
instante seteaza focus neconditionat pe `ct_clb_fdoc` (`:9788-9793`) — asta e "focusul care revine pe
tip document" — si `LostFocus`-ul care urmeaza cheama neconditionat `clb_serie_act`, care aloca un
numar nou (`serii_numere.vc2:122-127`) — asta e "regenerarea numarului".
- **Cauza reala e in `ofacturare.prg` (bucla de emitere, cod comun suitei), nu in formularul de antet.**
`s3_portare_antet.md:337-350` e explicit: portarea antetului (S3) NU rezolva automat #16 — arhitectura
unificata (un singur `Init` persistent, antetul nu mai e un obiect separat distrus/recreat de apelant)
**are sansa reala** sa-l elimine ca efect secundar, **dar numai daca implementarea nu recreaza
formularul intreg la reincercare dupa o eroare Oracle** — ceea ce reproduce bugul identic, doar mutat.
Verdictul e legat explicit de **S2**, nu de S3/S4d: bucla traieste in procedura de emitere, nu in
formular.
**Raspunsul specific S4d, cerut de plan** ("Verifica in acelasi timp daca #16 dispare — vezi M"):
**da, S4d confirma exact scenariul care declanseaza #16** — eroarea `-20005` de la
`verifica_cursuri_valute` e unul dintre cazurile `lnSucces<0` care intra pe ramura problematica a
buclei (`vizualizeaza_curs(poDate.zi_curs)` la `:315`, chiar linia care deschide `frm_curs`, exact
recuperarea citata in reclamatia originala). Deci daca S2/S3 rezolva #16 (antet persistent, fara
recreare), **S4d beneficiaza direct** — dupa `-20005`, userul revine in acelasi antet unificat
(nu unul nou), campul `clb_zi_curs` readus vizibil (punctul 5.4) ramane exact acolo unde a fost adus
inapoi, fara sarituri de focus si fara renumerotare. Daca S2/S3 NU rezolva #16 (recreare inca prezenta),
S4d **nu il agraveaza si nu il repara** — comportamentul ramane identic cu azi pe acest punct, dar
readucerea campului vizibil (5.4) trebuie facuta in `Init`-ul noii instante, nu presupusa mostenita.
**Concluzie:** #16 **nu e in perimetrul de implementare al S4d** (e S2, per plan `:1944`), dar S4d
**depinde de rezultatul lui** pentru UX-ul complet al recuperarii de eroare (5.4) — de marcat explicit
ca dependenta la implementare, nu de reinvestigat.
---
## 7. Pasi de implementare, ordonati
Fiecare pas lasa suita functionala; calea veche (`frm_date_factura`/`frm_facturare_articole2` cum sunt
azi) nu se sterge in etapa I.
1. **Adauga functia de evaluare a conditiei reactive** pe formularul unificat (metoda noua, de ex.
`do_actualizeaza_vizibilitate_curs`), care calculeaza `lVizibil` conform formulei din punctul 2 si
seteaza `Visible` pe controlul `clb_zi_curs` mostenit din prototipul `frm_facturare_articole2`
(`ofacturare.vc2:16285-16305`) — **nu un control nou**, cel existent, ale carui evenimente de
sincronizare (`Clb_dataact...LostFocus`, `:19219-19222`) raman neschimbate (deja garda pe
`Type(...)<>'U'`, deci tolereaza si eliminare, nu doar `Visible=.F.`).
*Gata cand:* metoda exista, se poate apela manual (fara UI), si intoarce `.T.`/`.F.` corect pe cele
patru combinatii din punctul 8 (tip retur / nu, `in_valuta` 0/1, cu/fara linie `tip_valuta=1`).
2. **Cheama metoda din pasul 1 la sfarsitul `do_adauga_articol` si `do_sterge`**, imediat dupa (sau in)
`Thisform.do_calculeaza_totaluri()` (`:17360`, `:18687` in prototip — liniile echivalente in
formularul unificat, dupa portarea din S3/S4).
*Gata cand:* adaugarea unei linii cu `tip_valuta=1` pe o factura in lei fara alte linii de acest fel
face campul vizibil imediat, fara Refresh manual; stergerea ultimei asemenea linii il ascunde din nou
(simetric, punctul 3), fara sa goleasca `poDate.zi_curs`.
3. **Verifica initializarea la deschiderea formularului** (cazul `in_valuta=1` sau document reincarcat
cu linii deja existente, ex. la editare factura emisa) — `Init`-ul unificat trebuie sa apeleze aceeasi
metoda o data, dupa ce `crsfactura` e populat, nu doar sa se bazeze pe evenimentele de adaugare/
stergere (care nu ruleaza la incarcarea initiala a unui document existent).
*Gata cand:* deschiderea unei facturi existente (in lei, cu linii in valuta deja salvate) arata
campul corect de la primul `Show()`, fara sa fie nevoie de o adaugare/stergere care sa-l declanseze.
4. **Elimina campul neconditionat pe retur (tip 8/9)**, ca azi — verifica ca formula din pasul 1 include
deja `!Inlist(poDate.tip,8,9)` inaintea oricarei alte conditii, ca sa nu-l readuca vizibil din greseala
printr-o linie in valuta pe un retur (desi returul nu foloseste `zi_curs`, vezi `zi_curs_validare.md`
§4c/§6 — comportamentul trebuie sa ramana identic azi, nu doar "fara efect").
*Gata cand:* pe orice tip 8/9, campul e absent indiferent de continutul grilei.
5. **Readu campul vizibil la eroarea `-20005`** (punctul 5.4) — modifica ramura `goExecutor.nEroare=20005`
din bucla de emitere (`ofacturare.prg:313-317`/`:828-832`, sau echivalentul ei in noua arhitectura
integrata cu formularul unificat, cf. S3 pasul 6) ca sa forteze `Visible=.T.` pe `clb_zi_curs` inainte
de a deschide `frm_curs`.
*Gata cand:* pe o factura in lei cu campul ascuns, o eroare `-20005` simulata (curs lipsa de test)
face campul vizibil imediat dupa mesaj, inainte sau odata cu deschiderea `frm_curs`.
6. **Coordoneaza cu decizia 42 (S4 punctul 1)**: cand acea poveste implementeaza restrangerea
`verifica_cursuri_valute` la valuta articolului cautat, verifica ca textul afisat prin
`oPrelucrareEroare()` (punctul 5.1-5.2, neschimbat) tot numeste corect valuta si data — nu necesita
modificare pe partea VFP, doar confirmare ca noul parametru Oracle e transmis corect din calea de
cautare filtrata.
*Gata cand:* pe cautarea filtrata (dupa decizia 42), mesajul `-20005` numeste doar valuta cautata, nu
un agregat.
7. **Regresie pe formularul vechi**: confirma ca niciuna din modificarile 1-6 nu atinge
`frm_date_factura`/`frm_facturare_articole` (calea veche, ramasa in productie) — toate modificarile
se fac pe clasele formularului unificat / prototip, nu pe cele originale.
*Gata cand:* `svn diff`/`git diff` arata modificari doar in clasele noii cai.
---
## 8. Cum se verifica
Pe probele din plan (`:2243-2246`), plus cazurile suplimentare care rezulta din punctele 2-6:
1. **Factura in lei, fara articole in valuta** — `clb_zi_curs` nu apare la deschidere; documentul se
emite corect (acelasi rezultat `VANZARI`/`ACT`/`RUL` ca azi, criteriul din S3 §7).
2. **Aceeasi factura, dupa adaugarea unui articol cu pret in valuta** — campul apare imediat, cu data
implicita deja completata (data documentului, de la `Init`/`Reset`, nemodificata).
3. **Stergerea articolului din pasul 2** (singurul cu `tip_valuta=1`) — campul dispare din nou; valoarea
din `poDate.zi_curs` ramane cea introdusa/implicita (verificabil citind proprietatea direct, nu doar
vizual).
4. **Readaugarea unui alt articol in valuta, in aceeasi sesiune** — campul reapare cu **aceeasi** data
ca la pasul 2, nu resetata.
5. **Factura in valuta (`in_valuta=1`)** — campul apare mereu, indiferent de continutul grilei, exact ca
azi (fara regresie pe cazul deja functional).
6. **Retur (tip 8/9)** — campul absent indiferent de linii; document salvat corect (cf. precedent deja
existent).
7. **`-20005` cu campul ascuns** (factura in lei, curs de test lipsa pentru o valuta din politicile
utilizatorului) — mesajul numeste valuta si data lipsa (deja azi); campul devine vizibil dupa eroare.
8. **Editare factura emisa** (cf. #6, formular deja in lucru separat) — deschiderea unei facturi in lei
cu linii in valuta deja salvate arata campul corect de la primul `Show()` (pasul 3 de implementare).
9. **Dupa decizia 42** — cautarea filtrata a unui articol cu curs lipsa arata mesaj cu o singura valuta
(a articolului cautat), nu un agregat.
---
## 9. Ce nu se poate testa headless
- **Focusul si secventa reala `SetFocus`/`LostFocus`/`Visible=`** pe controale UI reale — capcana deja
cunoscuta (`grid-coloane-nu-se-materializeaza-headless`, memorie de proiect): sub harness `-A -T`,
proprietatile de grid/vizibilitate nu se materializeaza identic cu UI vizibil. Verificarea
vizibilitatii reactive (punctele 2-3) trebuie facuta fie prin verificare directa a proprietatii
`.Visible` dupa apelul metodei (fara `Show()`), fie cu `vfp_ui_harness.ps1`, UI vizibil.
- **Eroarea Oracle `-20005` reprodusa real** — necesita fie date de test cu un curs lipsa garantat pe o
fereastra de date controlata (manipulare de date, nu de UI), fie mock pe `goExecutor` care simuleaza
`nEroare=20005` fara conexiune reala — niciuna verificata ca exista deja in suita de teste.
- **Deschiderea modala a `frm_curs`** (`vizualizeaza_curs`) — `Show(1)` modal, aceeasi limitare generala
a testarii headless pe formulare modale.
- **Bug #16 propriu-zis** (secventa reala de focus dupa recreare de formular) — deja marcat netestabil
headless in `s3_portare_antet.md` §8; S4d nu adauga o cale noua de testare, doar depinde de acelasi
rezultat.
---
## 10. Riscuri si de decis de Marius
1. **Incarcarea in masa pe comanda/aviz/contract ramane neconditionata dupa decizia 42** (punctul 5.3).
Pe aceste tipuri, `-20005` poate inca numi o valuta nelegata de documentul curent, chiar daca `S4d`
ascunde corect campul pe baza continutului grilei. **Recomandare:** de discutat daca extinderea
restrangerii decizia-42-style merita si pe incarcarea in masa (poveste separata, posibil in S4 sau
intr-o continuare a deciziei 42), sau se accepta ca diferenta cunoscuta, documentata aici.
2. **Cine seteaza `Visible=.T.` la `-20005` cand antetul ar fi fost recreat** (daca #16 nu se rezolva
complet in S2/S3 pana la implementarea S4d) — punctul 5.4/6 presupune `thisform` = acelasi formular
persistent; daca arhitectura finala tot recreaza o instanta, logica trebuie mutata in `Init`, cu un
semnal explicit propagat (ex. proprietate `poDate.lCursLipsa` sau echivalent) ca sa stie noua instanta
sa arate campul. **Recomandare:** de tratat ca parte a pasului 6 din `s3_portare_antet.md` (integrarea
buclei de emitere), nu izolat in S4d.
3. **`frm_date_aviz`/`frm_date_aviz_lucrare` (tipurile 27, 30) nu intra in perimetrul imediat** — daca
formularul unificat le preia mai tarziu, validarea neconditionata de la `:8076` trebuie aliniata la
aceeasi regula reactiva (azi ramane necondiționata, per `zi_curs_validare.md` §6). **Recomandare:** nu
se atinge acum; se trateaza cand/daca acele tipuri intra in unificare.
4. **Simetria "ascunde la 0 linii" (punctul 3) e o recomandare, nu o certitudine ceruta explicit de
plan** — planul cere doar "apare cand...", nu specifica explicit comportamentul la disparitia ultimei
linii. **Recomandare:** simetria propusa aici (argumentata pe tiparul `lb_cursuri`), dar de confirmat
cu Marius inainte de implementare, pentru ca schimba usor experienta (campul "clipeste" la
adaugare/stergere repetata a aceleiasi linii).

View File

@@ -1,444 +0,0 @@
# S4e — Lista de preturi disponibila si pe factura din comanda
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara
scriere Oracle — numai `SELECT`), pentru povestea **S4e** din
`docs\plan_13_unificare_formular_facturare.md:2249-2267` (decizia 16, text integral in sectiunea J
a planului). Nu se ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`
(perimetrul altei sarcini in lucru; `COMUN\programe\ofacturare_comun.prg` si
`COMUN\programe\ofacturare.prg` sunt citite, nu editate). Decizia 29 (stergerea unei linii din comanda
ramane fara protectie) nu se reargumenteaza.
**Status: cercetare incheiata.**
---
## Verdict (esenta, pentru cine nu citeste tot)
**Reteta literal citata in decizia 16 — `APPEND FROM` la `ofacturare.prg:454-473`, care lipeste
lista de preturi peste `crsarticole` — nu trebuie generalizata pe comanda ca atare. E nesigura acolo,
pe cod, nu doar teoretic.** `crsarticole` e simultan grid-sursa **si** registrul de cantitate ramasa
citit/scris de `do_sterge` si `do_scrie_factura` (decizia 39, S4 punctul 2). Doua motive concrete,
verificate pe cod si pe SQL exportat:
1. **Coliziune de `id_c`.** Fiecare procedura `cursor_*` din `PACK_FACTURARE` numeroteaza `id_c` cu
`ROWNUM`, pornind de la 1, **independent** de orice alta executie. `cursor_comanda` (liniile
comenzii) si `cursor_preturi` (lista de preturi) ar produce, executate separat si apoi lipite
prin `APPEND FROM`, doua seturi de `id_c` care se suprapun (1, 2, 3…). `do_sterge` potriveste
randul de sters in `crsarticole` **prin `id_c`** (`ofacturare.vc2:14652-14655`,
`:14658-14659`) — o coliziune ar face ca stergerea unei linii libere sa modifice cantitatea
ramasa a unei linii de comanda complet diferite (sau invers), tacut, fara eroare.
Codebase-ul insusi cunoaste acest risc: `cursor_contract` (`PACK_FACTURARE:2722`,
`SELECT rownum - 10000 as id_c ...`) foloseste deliberat un offset ca sa nu se suprapuna cu
spatiul de `id_c` al celuilalt cursor din acelasi apel.
2. **Poluarea registrului.** `do_scrie_factura` face `Calculate Sum(cantitate) To lnCantitateRamasa`
peste **tot** `crsarticole` ca sa decida daca se ofera inchiderea automata a comenzii
(`ofacturare.vc2:14332-14338`). Coloana `cantitate` din `cursor_preturi` **nu inseamna "ramas de
facturat"** — inseamna **stoc disponibil** (`NVL(C.CANTITATE,0)`, derivat din miscari de stoc,
`PACK_FACTURARE:2276-2280` si similar pe fiecare ramura). Amestecarea celor doua ar face ca suma
folosita pentru decizia de inchidere sa includa cantitati de stoc fara nicio legatura cu ce a mai
ramas de facturat din comanda — decizia de inchidere automata ar deveni gresita, tacut.
**De ce merge pe contract fara aceste probleme**: pe contract, lista de preturi **nu intra niciodata
in acelasi cursor** cu articolele contractului. `cursor_contract` intoarce **doua** cursoare Oracle
separate (`V_CURSOR` = `cursor_preturi`, mapat pe `crsarticole`; `V_CURSOR2` = articolele/ratele
contractului, mapat pe `crsarticole1`, cu `id_c` offset `-10000`) si **niciun `do_scrie_factura`
nu calculeaza `Sum(cantitate)` pe tipurile de contract** (2,6,26,52) — acel `Do Case` nu are ramura
pentru ele, cad in `Otherwise`, fara nicio logica de inchidere automata. Contractul "merge" pentru ca
lista de preturi traieste azi acolo unde nu exista niciun registru de citit.
**Solutia curata pentru comanda, verificata ca fezabila pe cod**: liniile libere **nu intra deloc in
`crsarticole`**. Mecanismul de adaugare a lor e cel construit de **S4** — cautare filtrata pe server
(`cursor_preturi` cu parametru de filtru, `docs\cercetare\s4_cautare_articole_server.md`, punctul 4)
legata printr-un `combosql`/`APPEND BLANK` **direct pe `crsfactura`** (exact tiparul deja existent in
`frm_facturare_articole2.But_nou1.do_adauga`, `ofacturare.vc2:17118-17122`: `Select crsFactura /
APPEND BLANK`), nu prin `do_adauga_articol`/`Scatter` dintr-un cursor sursa. Fiindca linia nu vine
niciodata dintr-un rand real al lui `crsarticole`, `crsfactura.id_c` ramane la valoarea implicita a
campului (`N(20)`, fara `Null`, deci `0` la `APPEND BLANK`) — **valoare pe care Oracle nu o produce
niciodata** (`ROWNUM` porneste de la 1), deci `do_sterge`-ul de azi (`For id_c = poArticol.id_c`) e
deja, prin constructie, un no-op sigur pe o linie libera, **fara nicio modificare de cod in
`do_sterge`**. Precedentul exista deja in acelasi fisier: linia de discount adaugata la
`ofacturare.vc2:14531-14537` (`Append Blank` pe `crsfactura`, fara `id_c`) foloseste exact acest
tipar azi, in productie.
Ramane un gol real de proiectat, nu de presupus rezolvat: **validarea de cantitate/stoc pe o linie
libera** nu se poate sprijini pe `do_verifica_articol` (cuplat de `crsarticole`) — vezi sectiunea 6.
---
## 0. Ce e deja stabilit (nu se reinvestigheaza)
- Optiunea "Cauta in lista de preturi…" intra in meniul de adaugare proiectat la S4b
(`docs\cercetare\s4b_bara_butoane_meniu.md`) — S4e ii da un element de meniu, nu un buton propriu.
- **Decizia 29**: stergerea unei linii venite din comanda ramane FARA protectie — se sterge ca
oricare alta, comanda ramane facturata partial. Nu se reia.
- **Legatura cu S4 punctul 2 (decizia 39)**: `crsarticole` e si registru al cantitatii ramase de
facturat. La momentul scrierii acestui raport, `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md`
e **in lucru** (sectiuni "IN LUCRU", fara concluzie de proiectare inca). Acest raport **nu asteapta**
acea proiectare: dovezile de mai jos (id_c, `Calculate Sum`) sunt citite direct pe codul actual, nu
presupuse din raportul in lucru. Presupunerea facuta explicit aici: **designul propus (liniile
libere nu intra in `crsarticole`) e compatibil cu orice varianta de decuplare a registrului pe care
S4 punctul 2 ar alege-o** — pentru ca liniile libere nu ating deloc cursorul/mecanismul pe care
punctul 2 il decupleaza. Daca punctul 2 alege sa mute registrul intr-un cursor complet nou (nu
`crsarticole`), concluzia ramane aceeasi cu o singura schimbare de nume.
- **S4 (`docs\cercetare\s4_cautare_articole_server.md`) a decis deja**, pe cod, ca pentru
comanda/aviz (3,4,21,25,28,42,47) `crsarticole` **ramane incarcat in masa**, neschimbat — punctul 7
al acelui raport. Aceasta decizie priveste **selectia articolelor comenzii insesi** (raman pe
`grd_articole`/`crsarticole`/`do_adauga_tot`, neschimbate). **Nu exclude** mecanismul S4e: cautarea
filtrata pe server construita de S4 (`cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`) e exact
ce S4e reutilizeaza pentru liniile **libere**, un canal separat, care nu inlocuieste si nu atinge
`grd_articole`.
## 1. Ce face azi calea de copiere (`ofacturare.prg:454-473`)
Cod complet (`COMUN\programe\ofacturare.prg:454-473`):
```
ELSE
IF m.llCopiere
* Modificare sau copiere factura
* Nu adaug automat articolele din factura originala, ca sa dau posibilitate de modificare a cantitatilor, pretului, explicatiei
* ofrmdetaliifactura.do_adauga_tot()
* Sterg din cursorul cu articole inregistrarile adaugate in factura
*DELETE FROM crsArticole
* Adaug in lista articolelor cursorul cu lista de preturi ca sa pot adauga si alte articole in factura copiata/modificata
lcSqlCursor = [{call pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta,] + ;
[?poDate.id_gestiune_init,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala)}]
lcCursorTemp = [crsArticoleTemp]
llSucces = goExecutor.oExecuta(lcSqlCursor, lcCursorTemp)
IF m.llSucces
SELECT crsArticole
APPEND FROM DBF(m.lcCursorTemp)
ENDIF
USE IN (SELECT(m.lcCursorTemp))
ENDIF && m.llCopiere
```
Pas cu pas:
1. Se executa `pack_facturare.cursor_preturi(...)` intr-un cursor temporar separat, `crsArticoleTemp`
(nu direct in `crsarticole`).
2. `SELECT crsArticole` + `APPEND FROM DBF(lcCursorTemp)` — VFP potriveste coloanele **dupa nume**;
coloanele care exista in `crsArticoleTemp` dar nu in `crsArticole` (sau invers) sunt ignorate
tacut de `APPEND FROM` (nu da eroare pentru coloane lipsa pe o parte sau alta — completeaza cu
valoarea implicita a campului pe partea destinatie). Cele doua cursoare au aceeasi structura de
baza (`creeaza_facturacrs`/coloanele standard `cursor_facturare`), deci in practica maparea e
completa.
3. `id_c` din `crsArticoleTemp` (numerotat `ROWNUM` de Oracle, incepand de la 1, independent de
executia anterioara) se copiaza **ca atare** in `crsArticole` — **nu se renumeroteaza**.
4. `crsArticoleTemp` se inchide (`USE IN`); rezultatul ramane doar in `crsArticole`, care de acum
contine doua seturi de randuri provenite din doua interogari Oracle diferite, cu spatii de `id_c`
care se pot suprapune.
**De ce e sigur aici, dar nu neaparat pe comanda**: aceasta ramura ruleaza **doar** cand
`m.llCopiere` e adevarat, si cand `llCopiere` e adevarat, cursorul de baza `crsArticole` **nu mai
vine din `cursor_comanda`** — Do Case-ul de la `ofacturare.prg:266-308` verifica `Case m.llCopiere`
**primul**, inaintea oricarei ramuri pe `tnTip`, deci pentru copiere cursorul de baza e intotdeauna
`cursor_retur_document` (documentul copiat insusi), indiferent de tipul original. Un document copiat
nu (mai) e o "comanda" (`poDate.tip` dupa copiere nu e niciodata `3`), deci nici `do_scrie_factura`
(care cere `poDate.Tip = 4` sau `Inlist(poDate.Tip,3,21,25,28,42,47)` pentru logica de inchidere
automata), nici `do_sterge` (aceleasi conditii de tip) nu citesc/scriu `crsarticole` ca registru pe
calea de copiere — **coliziunea de `id_c` exista tehnic si aici, dar nu are efect observabil**,
pentru ca nimic nu mai citeste cursorul ca registru pe acest `poDate.tip`. Aplicarea aceleiasi tehnici
pe un document de tip 3 (unde registrul chiar e citit) ar expune exact coliziunea descrisa in verdict.
## 2. De ce merge azi pe contract si nu pe comanda
**Pe contract, lista de preturi si articolele contractului sunt doua cursoare separate de la bun
inceput, populate de o singura procedura Oracle cu doua `OUT REF CURSOR`-uri**
(`PACK_FACTURARE:2646-2950`, `cursor_contract`):
```
PROCEDURE cursor_contract(..., V_CURSOR OUT cursor_facturare, V_CURSOR2 OUT cursor_facturare) IS
...
OPEN V_CURSOR2 FOR
...
SELECT rownum - 10000 as id_c, id_ctr, id_articol, id_rata, ... , opt_facturare
FROM (... CTR_ARTICOLE OPT_FACTURARE=3 UNION ALL ... CTR_SCADENTAR OPT_FACTURARE IN (1,2) ...)
ORDER BY data, numar, data_rata, denumire;
pack_facturare.cursor_preturi(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_ID_GESTIUNE_INIT, V_LUNA, V_AN,
V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR);
END cursor_contract;
```
`V_CURSOR` (= `crsarticole`, mapat in VFP) **este** rezultatul unui apel intern la `cursor_preturi` —
lista de preturi completa, exact ca pe un document liber de tip 1/5/7/10. `V_CURSOR2` (= `crsarticole1`)
e interogarea proprie contractului, cu `id_c` deliberat decalat cu `-10000` fata de spatiul lui
`ROWNUM` simplu — dovada directa ca autorii codului au tratat coliziunea de `id_c` intre cele doua
cursoare ca pe un risc real de evitat, nu ca pe ceva neglijabil.
**Consecinta pentru UI**: pe un document de contract, `grd_articole` (RecordSource `crsarticole`)
afiseaza azi **lista de preturi**, nu articolele contractului — de aceea capul lui de coloana e deja
"Cantitate in stoc" si mesajul e deja "Acest articol nu este pe stoc!"
(`ofacturare.vc2:15129-15143`, citat integral):
```
Case Inlist(poDate.tip, 2, 6)
&& facturare pe baza de contract
This.lb_titlu_alb_b121.Caption = [FACTURA PE CTR. ] + Alltrim(poDate.descriere)
This.grd_articole.RemoveObject('cSerie')
&& articole din lista de preturi
This.grd_articole.cCantitate.header1.Caption = [Cantitate in stoc]
This.cmesaj_cantitate = [Acest articol nu este pe stoc!]
```
`grd_contracte` (RecordSource `crsarticole1`) e gridul secundar, cu articolele/ratele contractului.
Contractul "are deja lista de preturi" pentru ca **asa e populat de la inceput** — nu exista niciun
`APPEND`/merge, doar doua interogari separate aratate in doua griduri diferite.
**Confirmarea ca registrul nu se aplica pe contract**: `do_scrie_factura`'s `Do Case`
(`ofacturare.vc2:14282-14389`) are exact patru ramuri — `poDate.eProforma=1`, `poDate.Tip=4`,
`Inlist(poDate.Tip,3,21,25,28,42,47)`, `Otherwise`. Tipurile de contract (2,6,26,52) nu apar in
niciuna din primele trei, deci cad pe `Otherwise` — `pack_facturare.scrie_factura2` se cheama direct,
**fara** `Select crsarticole / Calculate Sum(cantitate) ...` si fara `pnParametruAditional` derivat
din vreo suma. Contractul nu are, azi, nicio decizie de "inchidere automata" bazata pe
`Sum(crsarticole.cantitate)` — deci amestecul de continut din `crsarticole` (lista de preturi) nu are
cum sa strice o logica ce nu exista pentru acest tip. **Pe comanda, aceeasi ramura a Do Case-ului
(`Inlist(poDate.Tip,3,21,25,28,42,47)`) exista si citeste exact `crsarticole`**
(`ofacturare.vc2:14332-14338`, citat in verdict) — de aici diferenta reala.
## 3. Proiectarea adaugarii
**Liniile libere NU intra in `crsarticole`.** Mecanismul recomandat:
1. **Sursa de date**: cursorul filtrat construit de S4 —
`pack_facturare.cursor_preturi` supraincarcat cu `V_FILTRU_COD`/`V_FILTRU_DEN`
(`docs\cercetare\s4_cautare_articole_server.md`, punctul 4/9, pasul 1). Nu se cere nimic nou
in Oracle pentru S4e insusi — se reutilizeaza mecanismul deja proiectat pentru S4 (S4e devine
inca un consumator al aceluiasi cursor filtrat, nu un cursor nou). "Alege din nomenclator…"
(S4g, decizia 27/34) e un cursor separat, in afara acestei povesti.
2. **Legarea in UI**: `APPEND BLANK` direct pe `crsfactura`, urmat de editare inline prin
`combosql` pe celula de cod/denumire — exact tiparul deja existent, azi, in productie (chiar
daca in celalalt formular) la `frm_facturare_articole2.But_nou1.do_adauga`
(`ofacturare.vc2:17118-17122`):
```
PROCEDURE do_adauga
Select crsFactura
APPEND BLANK
this.grd_factura.SetFocus()
ENDPROC
```
Meniul S4b ("Cauta in lista de preturi…") declanseaza acest gest, cu focus direct pe celula de
cautare — S4b insusi a documentat aceasta echivalenta (sectiunea 6 a raportului S4b): "aceste
doua optiuni de meniu ajung la acelasi gest UI".
3. **`crsfactura.id_c` ramane la valoarea implicita a campului** (`N(20)`, fara `Null`,
`creeaza_facturacrs`, `COMUN\programe\ofacturare_comun.prg:1775-1785`) — adica `0` la
`APPEND BLANK`, valoare pe care Oracle nu o produce niciodata pentru `id_c` (`ROWNUM` incepe de
la 1). Consecinta: `do_sterge`-ul de azi (`For id_c = poArticol.id_c`, vezi sectiunea 4) nu
gaseste niciodata un rand de potrivit in `crsarticole` pentru o linie libera — comportament
corect prin constructie, **fara nicio modificare de cod**.
*Precedent in acelasi fisier, deja in productie*: linia de discount adaugata la finalul
documentului (`ofacturare.vc2:14531-14537`, `Select crsfactura / Append Blank / Replace
denumire With Replicate('Z',20), ... , id_temp With 99999`) — nu seteaza `id_c` deloc, foloseste
deja acelasi tipar de "rand sintetic fara sursa in `crsarticole`".
4. **`crsfactura.opt_facturare`** (numeric, implicit `0`) ramane la valoarea implicita si pentru o
linie libera — coincide cu valoarea pe care o are deja o linie normala din lista de preturi
(`opt_facturare=0` inseamna azi "nu vine din contract", conventia citita de `do_sterge` la
`ofacturare.vc2:14615-14619`). Nu e nevoie de o valoare noua pe acest camp pentru distinctia
"libera vs. comanda" — distinctia se face deja prin `id_c` (punctul 3).
5. **Completarea campurilor de pret/TVA/valuta**: identic cu maparea deja proiectata de S4
(`s4_cautare_articole_server.md`, punctul 6 — tabelul camp-cu-camp) — S4e nu adauga camp nou,
preia mecanismul S4 ca atare pentru randul ales din cautarea filtrata.
**Ce ramane de verificat la implementare, nu de presupus**: daca `combosql`-ul legat pe `grd_factura`
(construit pentru S4) trebuie sa apara pe **toate** coloanele relevante (cod, denumire) ale unei
linii nou-adaugate prin acest gest, sau doar pe coloana pe care s-a facut click — comportamentul
exact al `LostFocus`-ului de completare in masa a celorlalte campuri (`s4_cautare_articole_server.md`,
punctul 5, pasul 4) ramane in perimetrul S4, S4e doar il consuma.
## 4. Stergerea
**(a) O linie venita din comanda** — comportament **neschimbat**, decizia 29 se aplica ca azi:
`do_sterge` (`ofacturare.vc2:14608-14693`) gaseste `poArticol.opt_facturare = 0` (linie din
`crsarticole`, comanda), potriveste pe `id_c` (`ofacturare.vc2:14652-14655`,
`Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip,3,4,21,25,28,42,47) => Select
crsarticole / Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c`),
reface cantitatea ramasa in `crsarticole`. La `do_scrie_factura`, `Calculate Sum(cantitate)` peste
`crsarticole` (neschimbat, fara linii libere in el) vede corect cantitatea marita — comanda ramane
"facturata partial", fara nicio interventie noua. Nicio confirmare, niciun marcaj "refuzat" —
exact decizia 29.
**(b) O linie libera adaugata** — `poArticol.opt_facturare = 0` (aceeasi valoare ca (a): `0` nu
distinge intre "din lista de preturi ca supliment pe comanda" si "din lista de preturi ca document
liber" — nu trebuie sa distinga, pentru ca in ambele cazuri `lcCursor = crsarticole`
la `ofacturare.vc2:14615-14619`). Executia lui `Replace cantitate With cantitate + poArticol.cantitate
For id_c = poArticol.id_c` gaseste **zero randuri** in `crsarticole` cu `id_c = 0` (Oracle nu produce
niciodata `id_c = 0`), deci `REPLACE ... FOR` e un no-op — nimic nu se schimba in registru. Corect: o
linie libera nu a fost niciodata scazuta din vreun "ramas de facturat", deci nu are ce sa refaca la
stergere. **Nicio schimbare de cod necesara in `do_sterge` pentru acest caz.**
**Verificare de facut la implementare, nu presupunere**: confirmarea exacta a valorii implicite
(`0` vs. posibil `.NULL.` daca proprietatile campului se schimba fata de ce arata azi
`creeaza_facturacrs`) — ambele valori produc acelasi rezultat (`REPLACE ... FOR` fara potrivire),
dar merita un test explicit inainte de a considera punctul inchis, nu doar citirea definitiei
campului.
## 5. Capul de coloana si mesajul de cantitate
**Corectie fata de ipoteza initiala a task-ului**: capul de coloana "Cantitate comandata" si mesajul
"A fost facturata intreaga cantitate comandata pentru acest articol!" (`ofacturare.vc2:15144-15150`)
apartin lui `grd_articole`/`crsarticole` — gridul care, cu proiectarea din sectiunea 3, **continua sa
arate exclusiv liniile comenzii** (liniile libere nu intra niciodata acolo). Deci acest cap de coloana
si acest mesaj **raman corecte, neschimbate**, pentru ca nu se mai aplica vreodata unei linii libere.
**Unde apare totusi problema semnalata de task**: nu in `grd_articole`, ci in **validarea de
cantitate/stoc a liniei libere insesi**, care trebuie sa arate un mesaj propriu, nu mostenit de la
`Thisform.cmesaj_cantitate` (proprietate unica per formular, setata o singura data in `Init` dupa
`poDate.tip` — daca `poDate.tip = 3`, ea contine azi tocmai textul de comanda). Daca validarea unei
linii libere ar reutiliza `Thisform.cmesaj_cantitate` fara sa o schimbe pe moment, un articol liber
epuizat din stoc ar afisa gresit "A fost facturata intreaga cantitate comandata" — mesajul cerut
de plan la punctul de atentie e real, doar localizat gresit initial (nu pe `grd_articole`, ci pe
mecanismul de validare a liniei libere, vezi sectiunea 6).
**Propunere concreta de text** (romana, de validat cu Marius la implementare):
- `grd_articole` (comanda), neschimbat: cap coloana **"Cantitate comandata"**, mesaj
**"A fost facturata intreaga cantitate comandata pentru acest articol!"**.
- Linie libera (validare separata, sectiunea 6), text propus, calchiat dupa formularea deja
folosita pe lista de preturi simpla (`ofacturare.vc2:15122-15127`, tipurile 1/5/7/10, unde
`cmesaj_cantitate` ramane la valoarea implicita a formularului): **"Nu mai exista stoc disponibil
pentru acest articol!"** — coerent cu formularea deja folosita pe contract
(`[Acest articol nu este pe stoc!]`, `ofacturare.vc2:15136`), fara sa foloseasca acelasi text
identic (ca sa nu para o dublare accidentala a mesajului de contract).
## 6. Validarea de cantitate
**Ce se pastreaza neschimbat**: pentru liniile din comanda, alegerea prin `grd_articole` ramane pe
calea de azi — `do_adauga_articol` -> `do_verifica_articol(tlContract=.F., tnCantitate=poArticol.cantitate)`
(`ofacturare.vc2:12813-12869`, `:14731-14787`). `poArticol.cantitate` provine din
`crsarticole.cantitate`, care e "ramas de facturat" (`A.CANTITATE - NVL(D.CANTITATE,0)`,
`PACK_FACTURARE:3030`) — cererea din plan ("liniile din comanda sa nu-si piarda validarea de
cantitate") e satisfacuta **prin constructie**: acest drum nu e atins de nimic din proiectarea S4e,
pentru ca liniile libere nu intra niciodata in `crsarticole` si nu modifica randurile lui.
**Ce nu se poate reutiliza ca atare pentru o linie libera**: `do_verifica_articol` cere un
`poArticol` scatter-uit dintr-un cursor sursa deja incarcat (`crsarticole`/`crsarticole1`) — o linie
libera adaugata prin `APPEND BLANK` + `combosql` (sectiunea 3) nu are un asemenea rand sursa. Capacul
de cantitate pentru un articol gestionabil ales liber trebuie sa vina din **stocul curent al
articolului**, nu din "ramas de facturat" — informatie pe care randul intors de cautarea filtrata
(`cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`) o are deja in coloana `cantitate` (aceeasi
semantica de stoc descrisa in verdict), fara sa fie nevoie de un apel Oracle suplimentar.
**Gol real de proiectare, necesar S4e, neacoperit de niciun raport anterior (S4/S4b)**: nici
`s4_cautare_articole_server.md`, nici `s4b_bara_butoane_meniu.md` nu descriu un mecanism de validare
a cantitatii/stocului pentru randul ales prin `combosql` pe `grd_factura` — ambele rapoarte se opresc
la "randul se completeaza cu valorile din cursorul filtrat" (mapare de campuri), fara sa acopere
"ce se intampla daca utilizatorul introduce o cantitate mai mare decat stocul". **De proiectat la
implementare**: fie (a) o verificare in `LostFocus`/`Valid` a celulei de cantitate din `grd_factura`,
similara ca forma cu `do_verifica_articol` dar alimentata din randul `combosql` (nu din
`crsarticole`), fie (b) reutilizarea `do_verifica_articol` insasi, cu un `poArticol` construit ad-hoc
din randul `combosql` in loc de `Scatter` dintr-un cursor — a doua varianta pastreaza un singur loc
de validare in cod, dar cere ca `do_verifica_articol` sa primeasca `poArticol` ca parametru explicit
in loc sa citeasca variabila `Private poArticol` deja populata de apelant (schimbare de contract a
metodei, nu doar de continut). Ambele variante trebuie sa aleaga mesajul din sectiunea 5, nu
`Thisform.cmesaj_cantitate` motenit din `Init`.
## 7. Aceeasi problema pe alte surse
| Sursa | `crsarticole` = registru citit de `do_scrie_factura`? | `do_sterge` ajusteaza `crsarticole` pe `id_c`? | Concluzie pentru "APPEND FROM literal" |
|---|---|---|---|
| **comanda** (3,21,25,28,42,47) | **Da** — `Inlist(poDate.Tip,3,21,25,28,42,47)`, `:14332-14338` | **Da** — aceleasi tipuri, `:14652-14655` | **Nesigur** — subiectul acestui raport; solutia e "linii libere in afara `crsarticole`" (sectiunea 3) |
| **avize** (4, "factura din avize") | **Da** — `poDate.Tip = 4`, `:14301-14311` | **Da** — `Inlist(poDate.tip,3,4,21,25,28,42,47)` include 4, `:14652-14655` | **Acelasi risc ca pe comanda** — `cursor_avize` are aceeasi semantica "ramas de facturat" (`docs\cercetare\s4_cautare_articole_server.md`, punctul 1.4); solutia din sectiunea 3 se aplica identic |
| **contract** (2,6,26,52) | **Nu** — tipurile nu apar in `Do Case`-ul din `do_scrie_factura`, cad pe `Otherwise` | Da, dar pe `crsarticole1` (`opt_facturare<>0`), niciodata pe `crsarticole` | **Sigur** — lista de preturi traieste deja intr-un cursor separat (`crsarticole`), fara sa fie citita ca registru; nimic de schimbat |
| **retur ca document** (8,9,24) | **Nu** — cad pe `Otherwise`, fara `Sum(cantitate)`/`pnParametruAditional` | **Da, partial** — `Inlist(poDate.tip,8,9,24) => Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c`, `:14658-14659` (semn opus, urmareste maximul returnabil, nu inchiderea) | **Risc de coliziune `id_c` ramane, dar fara poluarea `Sum`**: o linie libera adaugata literal prin `APPEND FROM` in `crsarticole` pe un document de retur ar putea, la stergere, atinge tacut cantitatea maxima returnabila a unei linii reale (aceeasi problema de `id_c` din verdict, punctul 1) — **de tratat cu aceeasi solutie (linii libere in afara `crsarticole`) si in S4f**, nu doar aici. Decizia 22 (retur fara factura originala e permis) nu schimba aceasta concluzie — priveste alt aspect (mostenirea gestiunii/pretului) |
| **transfer subunitati** (23, 41) | Nu (cad pe `Otherwise`) | Partial, doar tip 41 (`Case gnScadereStoc = 1 And poDate.tip = 41`, `:14647-14649`, cu semn opus) | In afara perimetrului S4e (surse via `cursor_gestiune`, nu `cursor_comanda`/`cursor_preturi`); acelasi principiu de precautie se aplica daca se cere vreodata "lista de preturi si pe transfer" |
**Concluzie de tabel**: comanda si avize sunt genuin identice ca risc si ca solutie — **orice
implementare a S4e trebuie sa acopere ambele tipuri de sursa cu acelasi mecanism** ("linii libere in
afara `crsarticole`"), nu doar comanda cum sugereaza titlul povestii. Contractul ramane cum e azi
(nimic de schimbat). Returul ca document (S4f) trebuie avertizat explicit sa nu foloseasca reteta
`APPEND FROM` literal, desi riscul lui e mai mic (fara poluarea sumei de inchidere).
## 8. Pasi de implementare
1. **Extinde meniul S4b cu "Cauta in lista de preturi…" pe comanda/aviz** (deja in tabelul propus de
S4b, sectiunea 3 a acelui raport) — leaga optiunea de gestul `APPEND BLANK` pe `crsfactura`
descris in sectiunea 3 de mai sus, nu de un al doilea cursor bulk.
*Verificabil*: pe un document de tip 3 (comanda) sau 4/21/25/28/42/47 (avize), alegerea optiunii
adauga un rand gol in `grd_factura` cu focus pe celula de cautare, fara niciun apel catre
`cursor_comanda`/`cursor_avize` in plus (verificabil in logul `goExecutor`).
2. **Confirma valoarea implicita a `crsfactura.id_c` la `APPEND BLANK`** (sectiunea 4) — test direct,
nu citire de definitie. *Verificabil*: dupa `APPEND BLANK` pe un `crsfactura` gol, `id_c` al
noului rand e o valoare pe care Oracle n-o produce niciodata pentru `id_c` (azi: `0`).
3. **Adauga validarea de cantitate/stoc pentru linia libera** (sectiunea 6, varianta aleasa la
implementare) — mesaj propriu, nu `Thisform.cmesaj_cantitate` mostenit.
*Verificabil*: pe un articol gestionabil cu stoc 0, adaugarea lui ca linie libera pe un document
de comanda arata mesajul de stoc propus in sectiunea 5, nu mesajul de "cantitate comandata".
4. **Verifica prin regresie ca `do_sterge`/`do_scrie_factura` raman neatinse** — niciun cod nou in
aceste doua metode pentru cazul liniei libere (confirmat teoretic in sectiunea 4, dar de validat
pe date reale). *Verificabil*: pe un document de comanda cu o linie a comenzii si o linie libera
adaugate, stergerea liniei libere nu modifica `Calculate Sum(cantitate)` peste `crsarticole`;
stergerea liniei din comanda reface cantitatea ei, neschimbat fata de azi.
5. **Extinde acelasi mecanism pe avize (tip 4/21/25/28/42/47)** — nu doar comanda (tabelul din
sectiunea 7). *Verificabil*: identic cu pasul 4, pe un document "factura din avize".
6. **Avertizeaza explicit S4f (retur ca document)** sa nu foloseasca `APPEND FROM` literal pentru
lista de preturi pe documentele de retur (8,9,24) — risc de coliziune `id_c` cu maximul returnabil,
chiar daca fara poluarea sumei de inchidere. Nu e un pas de implementat aici, e o nota de predare
catre acea poveste (deja referentiata la decizia 22/S4f in plan).
## 9. Ce nu se poate testa headless
- **Comportamentul `combosql`/`APPEND BLANK` in `grd_factura`** — capcana deja confirmata pe acest
proiect (`grid-coloane-nu-se-materializeaza-headless`): sub `-A -T`, `ColumnCount`/`RecordSource`
raman artefacte. Verificarea vizuala a mesajului/capul de coloana (sectiunea 5) si a gestului de
adaugare (sectiunea 3) cere harnessul UI vizibil, nu rulare headless.
- **`LostFocus`/`Valid` pe celula de cantitate** (sectiunea 6) — daca implementarea alege varianta
(a) din sectiunea 6 (verificare directa in eveniment de celula), comportamentul depinde de randare
de grid, deci nu e testabil direct headless; varianta (b) (reutilizare `do_verifica_articol` cu
`poArticol` construit ad-hoc) **este** apelabila programatic, fara UI, daca semnatura metodei
primeste parametrul explicit.
- **`AMESSAGEBOX` la mesajul de stoc** (sectiunea 5) — verificabil ca text literal in cod, nu ca
aparitie reala pe ecran, din acelasi motiv structural ca oriunde altundeva in suita.
- **Ce se poate verifica headless, direct**: continutul `crsfactura`/`crsarticole` dupa apeluri
directe (nu prin click) la `do_sterge`/`do_scrie_factura`/gestul de `APPEND BLANK`, cu date de
test pregatite manual (un `crsarticole` cu 2-3 randuri de comanda, un `crsfactura` cu o linie de
comanda si una libera inserate programatic) — confirma `id_c`, `Sum(cantitate)`, si comportamentul
`REPLACE ... FOR` fara sa deschida formularul.
## 10. Riscuri si decizii ramase lui Marius
1. **Metoda de validare a cantitatii pe linia libera** (sectiunea 6) — varianta (a) sau (b); (b)
cere schimbarea contractului lui `do_verifica_articol` (parametru nou `poArticol`), (a) dubleaza
logica de validare in doua locuri diferite ale codului. **Recomandare**: (b), pentru un singur
loc de intretinut — coerent cu principiul deja aplicat in S4b sectiunea 5 ("echivalenta tot vs.
alege" tine pe convergenta catre o singura rutina).
2. **Textul exact al mesajului de stoc pentru linia libera** (sectiunea 5) — propunerea e o
recomandare, nu o decizie finala; de confirmat impreuna cu textele deja stabilite la S3b
(punctul marunt `a` din plan, `:524`).
3. **S4f trebuie avertizat explicit sa nu repete reteta `APPEND FROM`** pentru retur (sectiunea 7,
pasul 6) — nu e o decizie de luat aici, dar cineva trebuie sa o duca mai departe cand S4f intra
in lucru, altfel riscul de coliziune `id_c` pe retur se reintroduce pe alta poveste, fara sa mai
fie legat de acest raport.
4. **Interactiunea cu S4 punctul 2, in lucru la momentul acestui raport** — proiectarea de aici
presupune ca liniile libere raman complet in afara oricarui mecanism de registru, indiferent cum
arata forma lui finala dupa decuplare. Daca punctul 2 decide sa scoata bookkeeping-ul din
`crsarticole` intr-un cursor nou (cea mai probabila directie, judecand dupa decizia 39), concluzia
sectiunilor 3-4 de aici ramane valabila fara nicio schimbare — liniile libere nu ating niciun
cursor de registru, indiferent de numele lui. Daca punctul 2 alege in schimb sa pastreze
`crsarticole` ca sursa unica dar sa filtreze `Sum(cantitate)`/`do_sterge` printr-un camp nou
(de exemplu un flag `liber`), acel camp ar trebui setat pe liniile libere **daca** ele ar intra
totusi in `crsarticole` — proiectare pe care acest raport o considera inutila (liniile libere nu
trebuie sa intre acolo deloc), dar merita re-confirmat cu punctul 2 cand acel raport se incheie,
ca sa nu ramana doua solutii partial suprapuse pentru aceeasi problema.
5. **Nu s-a masurat, doar dedus din forma interogarii** (decizia 31 a planului, aplicata si aici):
nimic din acest raport se sprijina pe volume de date masurate — toate concluziile despre `id_c`/
`Sum(cantitate)` vin din citirea directa a SQL-ului si a codului VFP, nu din executii de test.
## Handoff
Cercetare incheiata, fara nicio problema de context intalnita in aceasta runda — nu a fost necesara
predarea. Toate cele zece sectiuni cerute de brief sunt completate mai sus, cu `fisier:linie`
verificat direct pe fisierele text reale (nu `.bak`) si pe SQL-ul exportat curent
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`). Nicio editare
de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit, nicio scriere pe Oracle.
**Corectie fata de premisa initiala a task-ului**, de retinut explicit pentru cine citeste doar
rezumatul: reteta citata in decizia 16 (`APPEND FROM`, `ofacturare.prg:454-473`) **nu** trebuie
generalizata literal pe comanda — e sigura doar in contextul ei actual (copiere, fara registru activ
pe cursorul respectiv). Solutia corecta, verificata ca fezabila si ca deja partial sprijinita de
mecanisme existente (S4's `cursor_preturi` filtrat, `APPEND BLANK`-ul din `frm_facturare_articole2`,
valoarea implicita `0` a lui `id_c`), e ca liniile libere sa **nu intre niciodata in `crsarticole`**
— exact ipoteza pe care brief-ul o semnala ca posibila si cerea sa fie confirmata sau infirmata pe
cod, nu presupusa.

View File

@@ -1,727 +0,0 @@
# S4f — Returul in formularul unificat
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara
scriere Oracle — doar `SELECT`), pentru povestea **S4f** din
`docs\plan_13_unificare_formular_facturare.md:2287-2330` (decizia 17, proiectata in sectiunea N,
`:1574-1655`). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si
`COMUN\programe\ofacturare_editare.prg` — doar citite, cand au aparut in cautari.
Surse de adevar deja stabilite, citate nu reinvestigate: `docs\cercetare\legatura_linie_retur.md`,
`docs\cercetare\factura_retur_document.md`, `docs\cercetare\retur_si_lista_preturi.md`,
`docs\cercetare\s4b_bara_butoane_meniu.md`.
## Verdict (esenta, 10 randuri)
**Corectat dupa avertismentul S4e** (`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si
7): mecanismul `APPEND FROM` din decizia 16 (`ofacturare.prg:454-473`) **e respins pentru orice
cursor care serveste si drept registru citit de `do_sterge`**, `crsarticole` fiind exact acel cursor
pe documentele de retur (`Inlist(poDate.tip,8,9,24) => Replace cantitate With cantitate -
poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) — riscul e coliziune de
`id_c` intre `cursor_retur_document` si `cursor_preturi`, ambele numerotate `ROWNUM` independent.
Sectiunea 5 de mai jos e rescrisa pe solutia validata de S4e: **liniile libere nu intra deloc in
`crsarticole`**, se adauga direct in `crsfactura` prin `APPEND BLANK` + `combosql` (tiparul deja in
productie la `frm_facturare_articole2.But_nou1.do_adauga`), iar campul de distinctie e
**`crsfactura.id_c`** (`0` implicit pe o linie libera, valoare pe care Oracle n-o produce niciodata
pentru `id_c`), nu o coloana lipsa in `crsarticole`.
Partea grea nu e ce parea: N.1 (documentul de retur, tip 8/9/24) merge deja cap-coada — alegere
multipla de facturi, populare din `cursor_retur`, gestiune si pret de achizitie mostenite, stergere
si retur partial. Munca reala e in trei bucati inguste. **(1) Nu se muta dialogul de alegere a
facturilor** in bara de butoane a gridului — s-a verificat pe cod ca alegerea ramane la nivelul
antetului (sectiunea pliata), exact cum recomanda deja `s4b_bara_butoane_meniu.md` §9.3/tabelul
"puncte marunte", randul h; ce se muta in meniul "Adauga articole" e **rezultatul** alegerii (liniile
deja aduse), ca "Adauga tot din facturile alese" / "Alege liniile de returnat…", simetric cu
comanda/contract/avize. **(2)** Ridicarea lui `But_retur` la nivel de document e cod nou real, nu o
mutare de buton — azi alegerea e per-articol si niciodata scrisa ca legatura persistata.
**(3)** Liniile libere pe retur folosesc reteta S4e ("`APPEND BLANK` pe `crsfactura`, niciodata pe
`crsarticole`"), **plus** un gol nou de proiectat, gasit in aceasta sesiune si necunoscut de S4e (care
nu are dialog de gestiune pe comanda/avize): mecanismul de alegere a gestiunii
(`frm_articol_gest_factura`, `.lRetur=.T.`) **suprascrie pretul de vanzare cu pretul din stoc**
(`ofacturare.vc2:4094-4103`) — corect pentru o linie mostenita dintr-o factura sursa, dar gresit
pentru o linie libera, a carei pret de vanzare trebuie sa vina din lista de preturi (decizia 16), nu
din costul de stoc; in plus, acel dialog se intra azi doar dintr-un `poArticol` scatter-uit din
`crsarticole` (`do_adauga_articol`/`do_alege_stoc`), pe care o linie libera din `crsfactura` nu-l are
— e nevoie de un punct de intrare nou in dialog, nu doar de reparat pretul. Vezi sectiunea 5 si
riscurile R1/R7.
---
## 1. Inventarul celor doua cai de retur de azi
### 1.1 N.1 — factura de retur ca document (tip 8, 9, aviz 24)
| Pas | Ce face | `fisier:linie` |
|---|---|---|
| Intrare | Tile `Page2.Cw1` -> `politica.mpr` -> `factureaza(8)`/`factureaza(9)` din meniul shortcut | `COMUN\clase\ofundal_facturare.vc2:882-884`, `Meniuri\politica.mn2:14-15,45-46` |
| Antet | `nIdTipDoc=5`, formular `frm_date_factura`, aviz retur (24) foloseste `frm_date_aviz` cu `nIdTipDoc=6` | `COMUN\programe\ofacturare.prg:192-196,222-226` |
| Alegere facturi sursa | `frm_date_factura.do_cauta_facturi` -> `caut_facturi_multiple_client(id_client, in_valuta, id_valuta, .T.)` -> `cauta_alfa(..., tnTipReturn=1)`, selectie multipla, exclude tipurile de retur din sursa | `COMUN\clase\ofacturare.vc2:9173-9212`; `COMUN\programe\oproceduri_facturare.prg:2091-2121` |
| Validare obligatorie | Fara alegere, antetul nu se poate inchide (`inainte_de_do_termin`) | `ofacturare.vc2:9523-9526` |
| Populare linii | `pack_facturare.cursor_retur(V_IN_VALUTA,V_LISTAID,V_ID_UTIL)` -> `cursor_retur_document(...,V_COPIERE=0,...)`, `SELECT` direct din `VANZARI_DETALII`, filtrat pe `ID_VANZARE IN (V_LISTAID)` | `ofacturare.prg:306-307`; `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3943-3956,3958-4071` |
| Gestiune + pret achizitie | `ID_GESTIUNE`/`PRET_ACHIZITIE` copiate neschimbate din linia originala; `GESTIONABIL = NVL2(A1.ID_GESTIUNE,1,0)` | `ff_...sql:4034-4035,4055-4056,4048` |
| Stergere linie | Permisa, cantitatea reintra in cursorul sursa | `ofacturare.vc2:14608-14693`, ramura `:14658-14659` |
| Retur partial | Permis, validat fata de maximul = `CANTITATE` a liniei originale, fara agregare cu retururi anterioare | `ofacturare.vc2:14743-14754`, `ff_...sql:4036` |
| Adaugare libera | **Nu se poate azi** — `do_adauga_articol` ia articolul mereu din `crsarticole`, care pe tip 8/9/24 **este** rezultatul lui `cursor_retur` | `ofacturare.vc2:12843-12851` |
### 1.2 N.2 — `But_retur` per-articol, in factura normala (tip 1, 5, 7, 10)
| Pas | Ce face | `fisier:linie` |
|---|---|---|
| Vizibilitate | Doar pe tip 1/5/7/10; pe documentele de retur (8/9/24) butonul nu exista in UI | `ofacturare.vc2:15122-15127` |
| Declansare | `caction=do_retur` -> `do_adauga_articol(.F.,.F.,.T.)` | `cmd_butoane.vc2:324-338`; `ofacturare.vc2:13963-13965` |
| Alegere factura sursa | **Per articol**, la fiecare adaugare: `caut_facturi_multiple_client_articol(id_client,in_valuta,id_valuta,.F.,id_articol)` -> `cauta_alfa` cu titlu simplu "Alegeti factura" (nu selectie multipla) | `oproceduri_facturare.prg:2124-2159` |
| Acumulare | `thisform.cListaIdArticoleRetur` acumuleaza CSV de perechi `id_articol:id_vanzare`, cate una per articol adaugat; `poDate.listaid` e folosit **transient** (salvat in `lnListaIdOld` si restaurat imediat dupa) doar cat dureaza dialogul per-articol | `ofacturare.vc2:12882-12892` |
| Gestiune | **Se alege**, nu se mosteneste — `pack_facturare.cursor_gestiuni_articol_retur`, prin `do_alege_stoc(...,tlRetur=.T.)` -> `frm_articol_gest_factura` | `ofacturare.vc2:13230,13353-13385` |
| Pret | **Suprascris cu pretul din stoc** (`pretv`), nu cel din lista de preturi — vezi sectiunea 5 | `ofacturare.vc2:4094-4103` |
| Semn cantitate | Explicit `poArticol.cantitate = (-1) * cantitate` cand `Thisform.lRetur` | `ofacturare.vc2:4103` |
| Trimitere Oracle | `poDate.listaid = thisform.cListaIdArticoleRetur` la scriere finala | `ofacturare.vc2:13977-13979` |
| Legatura persistata | **Nicio legatura scrisa** — `listaid` e folosit doar ca filtru in calculul de disponibil pe rulaj (`RUL`), nu scrie nimic in `VANZARI_CORESP` sau pe linia noua | `ff_...sql:8142-8212` |
### 1.3 Ce au in comun si unde diverg
Comun: excluderea returului dintr-un retur (acelasi filtru `tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)`
in ambele functii de cautare), conventia de semn negativ pe cantitate, `frm_articol_gest_factura`
ca formular de alegere a gestiunii pentru linia gestionabila.
Diverg pe tot restul: N.1 alege facturile **o singura data, la nivel de document, inainte** ca
articolele sa existe; N.2 alege **per articol, in orice moment**, fara limita de cate ori. N.1
mosteneste gestiune+pret_achizitie fara alegere; N.2 le cere explicit. N.1 scrie
`VANZARI_CORESP(TIP=3)`; N.2 nu scrie nimic persistat. Zero cod comun intre cele doua cai —
`ofacturare.vc2:12813-12900` (N.2, `do_adauga_articol`) si `ofacturare.prg:306-311` (N.1,
`cursor_retur`) nu se ating.
---
## 2. `do_cauta_facturi` / `cauta_alfa(..., tnTipReturn=1)` — contractul exact
### 2.1 Cine il cheama si cand
Pe `frm_date_factura` exista un control generic de cautare, `Ct_clb_altele` (clasa
`ct_clb_cautare`), a carui iconita de lupa e legata de `cprocedura = thisform.do_cauta_altele`
(`ofacturare.vc2:8721-8729`). `do_cauta_altele` e un dispecer pe `nid_tip`:
```
Case Inlist(Thisform.nid_tip,8,9)
Thisform.do_cauta_facturi()
```
(`ofacturare.vc2:8953-8954`)
**Secventa confirmata pe cod** (`factureaza()`, `ofacturare.prg:187-311`): `frm_date_factura` se
creeaza si se afiseaza (`.Show()`, `:230-235`) **inainte** ca `Do Case tnTip` de la `:266-308` sa
ruleze `cursor_retur`, si mult inainte ca `lcObject = [frm_facturare_articole]` sa fie ales
(`:395`). Executia continua abia dupa ce `ofrmceredate` (antetul) se elibereaza (`:248`), iar
`pnButon` (setat la inchiderea antetului) e verificat imediat dupa. Deci **azi dialogul de alegere
a facturilor ruleaza pe formularul de antet, complet inainte ca formularul de articole sa existe** —
`crsarticole` se incarca o singura data, cu `poDate.listaid` deja fixat.
Control observat, semnificativ pentru sectiunea 3: `Ct_clb_altele` are
`cconditie = EMPTY(NVL(poDate.listaid,''))` (`ofacturare.vc2:8722`) — sugereaza ca iconita de
cautare e activa doar cat timp nimic nu a fost inca ales; **neverificat** insa exact ce proprietate
citeste `cconditie` (clasa `ct_clb_cautare` nu a fost gasita in text in acest repo, posibil intr-un
`.vcx` neconvertit sau in `COMUNROA`), deci nu se poate confirma daca astazi re-alegerea e blocata
explicit sau doar descurajata vizual. De tratat ca ipoteza, nu ca fapt confirmat.
### 2.2 Contractul functiei
`caut_facturi_multiple_client(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple)`
(`oproceduri_facturare.prg:2091-2121`):
- **Intrare**: client (obligatoriu — dialogul nu porneste fara el), valuta (doar daca `tnInValuta`),
`tlFacturiMultiple=.T.` la acest apel -> `lnTipReturn=1` in `cauta_alfa`.
- **Sursa**: `select serie_act,numar_act,data_act,dataora,id_vanzare from fact_vfacturi where
sters=0 and tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) [+ conditie sucursala] [+ client] [+ valuta]`
— fara filtru SQL pe perioada/serie (filtrarea pe acele coloane e comportament generic al
`cauta_alfa`, nu parametru dedicat).
- **Iesire**: XML cu randurile bifate (`ales=1`), convertit local prin `Xmltocursor(...,
"crsFacturiTemp")`; **nimic nu ajunge la Oracle in acest pas**.
- **Populare aditiva a rezultatului in VFP**: `poDate.listaid = cursor2lista("crsFacturiTemp",
"id_vanzare", ",")` — **inlocuieste** intreg continutul (`=`, nu `+=`); daca dialogul se apeleaza a
doua oara azi, a doua rulare suprascrie complet prima (dar azi asta nu se intampla niciodata pentru
ca dialogul ruleaza o singura data, inainte de orice alta actiune pe document — vezi 2.1).
- **`cauta_alfa(...,tnTipReturn=1)`** (`COMUN\programe\cauta_alfa.prg:16-260`, mecanismul generic
folosit peste tot in suita): adauga singur coloana `ales` (`cauta_alfa.prg:124`), titlu explicit
"Alegeti ... (mouse-click pe numar sau apasati SPACE)", filtrare interactiva pe coloanele afisate.
Nu are mecanism de dezactivare a unei optiuni individuale, nici submeniu — doar bifare/debifare.
### 2.3 Varianta per-articol
`caut_facturi_multiple_client_articol(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple, tnIdArticol)`
(`oproceduri_facturare.prg:2124-2159`) — **aceeasi functie de baza**, cu doua diferente: filtru
suplimentar `b.id_articol = ?pnIdArticol` (join pe `vanzari_detalii`) si `tlFacturiMultiple=.F.` la
apelul din `do_adauga_articol` (`:12884`), deci `lnTipReturn=0` -> selectie **simpla**, nu multipla.
Titlu simplu "Alegeti factura". Rezultatul e un obiect cu un singur `id_vanzare`, nu un cursor.
---
## 3. Proiectarea punctului 1 — alegerea facturilor sursa ca sursa in meniul de adaugare
**Ce NU se schimba**: alegerea propriu-zisa a facturilor sursa (dialogul cu bifare multipla) ramane
la nivelul antetului — in formularul unificat, sectiunea pliata (decizia 7). Verificat impotriva
tabelului "puncte marunte" din plan (`plan_13...md`, randul h): *„Alege facturile de returnat…"
ramane doar la antet in etapa I; mutarea (in bara de butoane) e o poveste separata* — si confirmat
independent de tabelul de meniu al lui `s4b_bara_butoane_meniu.md` §3: pe randul "retur (8,9,24)",
meniul "Adauga articole" propus contine **"Adauga tot din facturile alese" · "Alege liniile de
returnat…" · "Cauta in lista de preturi…" · "Alege din nomenclator…"** — nu si "Alege facturile de
returnat…". **Punctul 1 al lui S4f nu contrazice asta**: ce se muta in meniul de adaugare e
rezultatul alegerii deja facute la antet (liniile din facturile alese), simetric cu comanda/
contract/avize, nu dialogul de alegere a facturilor insusi.
**Ce SE schimba, si e proiectarea reala**: azi secventa e rigida — antet modal -> `cursor_retur` ->
formular de articole (sectiunea 2.1). In formularul unificat exista un singur formular, mereu
deschis; sectiunea de antet (pliata) si gridul de articole coexista. Trei consecinte de proiectat:
1. **Cand se declanseaza `cursor_retur_document`?** Nu mai poate fi legat de "inchiderea antetului"
(nu mai exista acel eveniment). Trebuie legat de **confirmarea dialogului de alegere** (butonul de
cautare din sectiunea pliata, echivalentul lui `Ct_clb_altele`/`do_cauta_facturi`): imediat ce
utilizatorul bifeaza facturile si confirma, `poDate.listaid` se seteaza si `cursor_retur_document`
ruleaza pe loc, populand/repopuland `crsarticole`. Documentul poate porni cu tipul deja pe 8/9/24
(ales din combo-ul de tip document) inainte ca vreo factura sa fie aleasa — grid-ul de articole
ramane gol pana la prima alegere, exact ca azi cand `Reccount(crsarticole)=0` da mesajul "Nu exista
articole pentru optiunile alese!" (`ofacturare.prg:325`).
2. **Poate opera la orice moment, nu doar o data.** Spre deosebire de azi (dialogul ruleaza o singura
data, inainte ca gridul sa existe), in formularul unificat butonul din sectiunea pliata e
apasabil oricand documentul e deschis. Trebuie decis explicit comportamentul la a doua apasare —
vezi punctul urmator.
3. **Alegere aditiva sau inlocuitoare, la a doua apasare.** Azi `poDate.listaid = ...` **inlocuieste**
(nu `+=`), dar azi nu conteaza pentru ca nu se poate apasa a doua oara dupa ce gridul exista. In
formularul unificat, daca utilizatorul a ales deja factura A, a adaugat cateva linii din ea in
`crsfactura`, si apoi vrea sa adauge si din factura B, doua variante:
- **(a) Inlocuire** — a doua alegere reseteaza `poDate.listaid`/`crsarticole` la noul set ales;
liniile deja mutate in `crsfactura` raman (crsfactura e independent de crsarticole dupa mutare),
dar orice linie inca needitata din factura A dispare din `crsarticole` fara avertisment. Simplu
de implementat (identic cu azi), dar surprinzator pentru operator.
- **(b) Aditiv** — a doua alegere **extinde** `poDate.listaid` cu facturile noi (evitand
duplicate) si ruleaza `cursor_retur_document` filtrat doar pe facturile noi, cu
`INSERT INTO crsarticole` (nu `SELECT INTO`), exact reteta deja propusa de
`s4b_bara_butoane_meniu.md` §9.3, optiunea 2 — cere cod VFP nou (bucla de filtrare +
`INSERT`), dar zero cod nou in `pack_facturare` (aceeasi procedura, apelata de mai multe ori).
Consistent cu filozofia deciziei 16 ("sursa umple documentul, nu il inchide").
**Recomandare**: varianta (b), aditiv — e coerenta cu tratamentul lista-de-preturi (S4e) si cu
comanda/contract (unde alegerea suplimentara nu sterge ce exista deja), si evita pierderea tacuta
de linii needitate. **Ramane decizie de confirmat de Marius** — nici plan-ul, nici
`s4b_bara_butoane_meniu.md` nu au tranzat-o explicit (§9.5, punctul 2 acolo o listeaza tot ca
deschisa).
**Consecinta pentru validarea de antet**: garda de azi (`inainte_de_do_termin`, `descriere` obligatoriu
pentru tip 8/9, `ofacturare.vc2:9523-9526`) nu mai are un moment natural "inainte de a inchide
antetul" — se muta la garda echivalenta din `Termina` (aceeasi verificare, alt declansator).
---
## 4. Proiectarea punctului 2 — ridicarea `But_retur` la nivel de document
**Ce se pastreaza din calea per-articol**: alegerea gestiunii ramane per-articol, prin
`cursor_gestiuni_articol_retur` si `frm_articol_gest_factura` — decizia 22/N.3 spune explicit ca
gestiunea si pretul de achizitie **se aleg**, nu se mostenesc, pentru orice linie fara factura
sursa unica precisa (si N.2 nu are niciodata o factura "sursa unica a documentului", are cel mult
factura aleasa pentru articolul curent). Punctul 2 nu schimba asta.
**Ce se schimba**: alegerea facturii sursa nu mai e per-articol (un dialog `cauta_alfa` cu selectie
simpla la fiecare `do_retur`), ci **la nivel de document**, dupa modelul N.1 — un singur dialog cu
selectie multipla (`caut_facturi_multiple_client`, acelasi ca la N.1, dar filtrat pe articolul
curent doar daca ramane necesar; de decis daca filtrul pe articol dispare complet odata cu ridicarea
la document). Consecinta directa: `poDate.listaid` (sau un camp nou, vezi mai jos) ar fi populat o
singura data pentru intreg documentul, nu reconstruit per articol prin `caut_facturi_multiple_client_articol`.
**Capcana de proiectare, gasita pe cod**: `poDate.listaid` e deja **partajat** intre N.1 si N.2 azi —
N.2 il suprascrie transient (`lnListaIdOld = poDate.listaid` ... `poDate.listaid = m.lnListaIdOld`,
`ofacturare.vc2:12882,12892`) doar cat dureaza alegerea per-articol, apoi il restaureaza. Daca N.2
ridicat la document incepe sa scrie **permanent** in `poDate.listaid` (ca N.1), cele doua mecanisme
intra in conflict pe acelasi camp — un document normal (tip 1/5/7/10) cu retur ridicat la nivel de
document ar avea `poDate.listaid` populat cu facturile de retur alese, camp care pe alte tipuri
(comanda/contract/avize) e deja folosit cu alt sens (id-uri de comanda/contract/aviz sursa, vezi
`ofacturare.prg:266-308`). **Nu e conflict pe tip 1/5/7/10** — acele tipuri nu folosesc azi
`poDate.listaid` pentru altceva (sunt populate cu `cursor_preturi`, care nu ia `V_LISTAID` ca
parametru) — deci reutilizarea e sigura tehnic, dar merita un camp cu nume propriu
(`poDate.listaid_retur` sau similar) ca sa nu se ambiguizeze semantic ce inseamna `listaid` pe un
document normal. **De decis la implementare**, nu blocant.
**Ce arata in bara de butoane / meniu (S4b)**: tabelul din `s4b_bara_butoane_meniu.md` §3 nu are
azi un rand pentru "retur intr-o factura normala" — doar pentru documentele de retur propriu-zise
(8,9,24). Ridicarea lui N.2 la nivel de document introduce o optiune noua in meniul "Adauga
articole" **pentru tipurile 1,5,7,10**: "Retur de articole…" (deja anticipata in tabelul §3 al
raportului S4b, coloana lista de preturi: "Cauta in lista de preturi… · Alege din nomenclator… ·
**Retur de articole…**"). Aceasta optiune deschide dialogul de alegere multipla la nivel de document
(prima data), apoi pe alegerile ulterioare acelasi comportament aditiv/inlocuitor discutat la
sectiunea 3 se aplica identic.
**Ce dispare**: `caut_facturi_multiple_client_articol` (filtrul per-articol) ramane cod mort daca
alegerea trece integral la nivel de document — sau se pastreaza ca alegere secundara optionala
("mai am nevoie de o factura sursa doar pentru articolul X", nefolosita azi in acest scenariu). Nu
e clar din decizia 17/N.3 daca trebuie eliminata explicit sau doar nu mai e calea principala — **de
decis de Marius** (risc R3).
---
## 5. Proiectarea punctului 3 — liniile libere pe document de retur
**Corectie fata de premisa initiala a brief-ului** ("prin acelasi `APPEND FROM` ca la S4e"): S4e a
gasit ca reteta literala a deciziei 16 (`ofacturare.prg:454-473`) **nu e sigura** pe niciun tip unde
`crsarticole` e citit ca registru de `do_sterge`/`do_scrie_factura`, si a documentat asta explicit
pentru retur, in tabelul sau de la §7:
> "**retur ca document (8,9,24)** | `crsarticole` = registru citit de `do_scrie_factura`? **Nu** —
> cad pe `Otherwise`, fara `Sum(cantitate)`/`pnParametruAditional` | `do_sterge` ajusteaza
> `crsarticole` pe `id_c`? **Da, partial** — `Inlist(poDate.tip,8,9,24) => Replace cantitate With
> cantitate - poArticol.cantitate For id_c = poArticol.id_c` (`:14658-14659`, semn opus, urmareste
> maximul returnabil, nu inchiderea) | Concluzie: **risc de coliziune `id_c` ramane, dar fara
> poluarea `Sum`**: o linie libera adaugata literal prin `APPEND FROM` in `crsarticole` pe un
> document de retur ar putea, la stergere, atinge tacut cantitatea maxima returnabila a unei linii
> reale — de tratat cu aceeasi solutie (linii libere in afara `crsarticole`) si in S4f."
> (`s4e_lista_preturi_pe_sursa.md`, §7, randul "retur ca document")
Solutia S4e (verificata acolo pe comanda/avize) se aplica identic aici — sectiunile de mai jos sunt
rescrise pe ea, nu pe `APPEND FROM`.
### 5.1 Mecanismul corect: `APPEND BLANK` pe `crsfactura`, niciodata pe `crsarticole`
**Liniile libere nu intra in `crsarticole`.** Se adauga direct in `crsfactura`, prin acelasi gest deja
in productie la `frm_facturare_articole2.But_nou1.do_adauga` (`ofacturare.vc2:17118-17122`):
```
PROCEDURE do_adauga
Select crsFactura
APPEND BLANK
this.grd_factura.SetFocus()
ENDPROC
```
urmat de editare inline prin `combosql` pe celula de cod/denumire (mecanismul construit de S4/S4e
pentru cautarea filtrata pe server, `pack_facturare.cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`).
Optiunea de meniu "Cauta in lista de preturi…" (deja in tabelul S4b §3, pe randul retur) declanseaza
exact acest gest — **nu** un `cursor_retur_document` suplimentar si **nu** un `APPEND FROM` peste
`crsarticole`.
### 5.2 Cum se distinge o linie libera de una din factura sursa
**Campul corect e `crsfactura.id_c`, nu o coloana pe `crsarticole`.** O linie mostenita ajunge in
`crsfactura` prin `do_adauga_articol`, care face `Select (lcCursor) / Scatter Name poArticol` pe
randul ales din `crsarticole` (populat de `cursor_retur_document`, cu `id_c = ROWNUM`, pornind de la
1) si copiaza acel `id_c` in randul nou din `crsfactura`. O linie libera, adaugata prin `APPEND BLANK`
direct pe `crsfactura` (5.1), **nu trece niciodata prin acest `Scatter`** — `id_c` ramane la valoarea
implicita a campului (`N(20)`, fara `Null`, deci `0`, `creeaza_facturacrs`,
`COMUN\programe\ofacturare_comun.prg:1775-1785`, citat si verificat de S4e §3 punctul 3). **Oracle nu
produce niciodata `id_c = 0`** (`ROWNUM` incepe la 1) — deci `crsfactura.id_c = 0` e semnalul curat,
fara camp nou, care distinge o linie libera de una mostenita. Acelasi tipar e deja in productie pentru
linia de discount (`ofacturare.vc2:14531-14537`, `Append Blank` fara `id_c` setat).
**Consecinta directa, fara cod suplimentar**: `do_sterge`, ramura de retur
(`Case Inlist(poDate.tip,8,9,24) => Select crsarticole / Replace cantitate With cantitate -
poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) cauta in `crsarticole`
un rand cu `id_c = poArticol.id_c`. Pentru o linie libera, `poArticol.id_c = 0`, iar `crsarticole` nu
are niciodata un rand cu acel `id_c` — `REPLACE ... FOR` devine **no-op prin constructie**. Stergerea
unei linii libere de pe un document de retur **nu atinge** maximul returnabil al nici unei linii
mostenite — exact ce cerea brief-ul la punctul 5, si exact acelasi mecanism (nu doar aceeasi idee) ca
solutia S4e pentru comanda.
### 5.3 Pretul de vanzare — riscul gasit (R1, ramane valabil)
**Nu e doar despre gestiune/pret de achizitie, si nu se rezolva prin mutarea in `crsfactura`.** Cand
o linie ajunge la `do_alege_stoc` cu `Inlist(poDate.tip,8,9,24)` adevarat (orice linie pe un document
de retur, indiferent de origine — conditia e pe `poDate.tip`, nu pe un flag de linie), se deschide
`frm_articol_gest_factura`. In acel formular, la schimbarea coloanei de gestiune
(`grd_gestiuni.RowColChange`), daca `Thisform.lRetur` e adevarat:
```
If Thisform.lRetur
poArticol.pretftva = pretv && pret DIN STOC, nu din lista de preturi
...
poArticol.cantitate = (-1) * cantitate
Endif
```
(`ofacturare.vc2:4094-4103`)
Pentru o linie **mostenita** (N.1), asta e corect prin constructie — returnezi marfa la pretul cu
care a fost vanduta. Pentru o linie **libera** (decizia 22 — articol niciodata vandut clientului, deci
fara "pret de retur" de mostenit), suprascrierea cu pretul de stoc e o **eroare de pret**: linia
libera ar iesi cu costul de stoc in loc de pretul din lista de preturi ales prin `combosql` la 5.1.
**Solutie propusa, actualizata**: `frm_articol_gest_factura` primeste un al doilea semnal, distinct de
`tlRetur` ("esti pe flux de retur"), care sa insemne "aceasta linie are factura sursa" — derivat acum
din `crsfactura.id_c <> 0` (5.2), nu dintr-o coloana lipsa in `crsarticole`. Ramura de suprascriere
pret (`:4094-4103`) se activeaza doar cand acest al doilea semnal e adevarat.
### 5.4 Golul nou, nu tratat de S4e: punctul de intrare in dialogul de gestiune
**S4e nu are acest gol pentru ca nu are dialog de gestiune pe comanda/avize** — liniile libere de
acolo se completeaza integral prin `combosql` (pret, TVA, valuta) fara sa mai treaca prin niciun
dialog de alegere a gestiunii. Pe retur, decizia 22 cere explicit ca gestiunea si pretul de achizitie
sa **se aleaga** pentru o linie libera — mecanismul care stie sa faca asta e
`cursor_gestiuni_articol_retur` + `frm_articol_gest_factura`, dar acel dialog se intra azi **doar**
din `do_alege_stoc`, apelat de `do_adauga_articol` pe baza unui `poArticol` scatter-uit dintr-un rand
**deja incarcat in `crsarticole`** (`ofacturare.vc2:12843-12851,12891`). O linie libera adaugata prin
`APPEND BLANK` pe `crsfactura` (5.1) **nu are** un asemenea rand sursa — exact acelasi gol pe care
S4e l-a semnalat, netratat, pentru validarea de cantitate pe comanda (`s4e_lista_preturi_pe_sursa.md`
§6: *"`do_verifica_articol` cere un `poArticol` scatter-uit dintr-un cursor sursa deja incarcat... o
linie libera... nu are un asemenea rand sursa"*).
**Consecinta pentru S4f, mai stricta decat pe comanda**: pe comanda/avize (S4e), lipsa unui `poArticol`
de sursa afecteaza doar validarea de cantitate (un gol de acoperit, dar linia se poate scrie si fara
gestiune aleasa — comanda/avize nu cer gestiune diferita de cea din lista de preturi). Pe retur, lipsa
aceluiasi `poArticol` blocheaza **si** alegerea gestiunii, care e obligatorie prin decizia 22. Trebuie
proiectat un punct de intrare nou in `frm_articol_gest_factura`/`do_alege_stoc`, care sa porneasca de
la randul **deja inserat in `crsfactura`** (dupa `combosql`, cu `id_articol` si cantitate deja
completate) in loc de la un rand din `crsarticole` — practic o a doua cale de apel, cu acelasi dialog
la capat, dar `poArticol` construit ad-hoc din `crsfactura` in loc de `Scatter` din `crsarticole`.
Aceeasi problema structurala, aceeasi solutie propusa (poArticol construit ad-hoc) ca varianta (b) de
la S4e §6 pentru `do_verifica_articol` — de unificat la implementare daca se poate, nu de rezolvat de
doua ori independent.
### 5.5 Maximul returnabil — nu se aplica, si acum e clar de ce
Confirmat structural: azi maximul returnabil pe N.1 **nu e un calcul de "cat s-a mai returnat"**, e
pur si simplu `CANTITATE` a liniei originale (`ff_...sql:4036`, `ofacturare.vc2:15236-15239`,
`do_verifica_articol:14743-14754`) — validarea respinge doar semnul gresit (`tnCantitate >= 0` in mod
retur), nu o depasire fata de un istoric. **Pentru o linie libera, intrebarea nici nu se pune in
termenii de azi**: `do_verifica_articol`, ca si dialogul de gestiune (5.4), asteapta un `poArticol`
din `crsarticole` — o linie libera nu are unul, deci nu poate trece prin comparatia de maxim existenta
nici macar din greseala. Validarea proprie a liniei libere (cantitate/stoc, construita ad-hoc, 5.4)
nu are nimic de comparat cu un "maxim returnabil" — verifica doar disponibilul de stoc curent, ca pe
o linie normala de lista de preturi (acelasi gol deschis de S4e §6 pentru comanda, aceeasi solutie).
---
## 6. Efectul asupra `VANZARI_CORESP`
**Scrierea nu depinde de continutul liniilor, ci de alegerea facuta la antet.**
`scrie_corespondente_vanzari(3)` (`ff_...sql:14834-14836`, apelata din `finalizeaza_factura`) scrie
direct din `pack_facturare.clistaid` (variabila de sesiune, = `poDate.listaid` trimis la
`initializeaza_date_factura`), **independent** de ce a ajuns efectiv in `VANZARI_DETALII` la
finalizare:
```sql
INSERT INTO VANZARI_CORESP (ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)
SELECT pack_facturare.nid_vanzare, ID_VANZARE, 3
FROM VANZARI WHERE ID_VANZARE IN (SELECT X FROM table(charn2collection(V_LISTAID,',')));
```
(`ff_...sql:15481-15516`, citat integral in `legatura_linie_retur.md:45-53`)
**Consecinta directa pentru liniile libere**: daca documentul de retur contine si linii libere (din
lista de preturi), corespondenta **tot se scrie**, si scrie exact aceleasi facturi alese la antet —
liniile libere nu adauga si nu scot randuri din `VANZARI_CORESP`, pentru ca sursa e `poDate.listaid`
(fixat la antet), nu continutul `crsfactura`/`VANZARI_DETALII`. Un document de retur cu 3 linii
mostenite din factura A si 2 linii libere scrie tot un singur rand in `VANZARI_CORESP`
(`ID_VANZARE_FACT`=documentul nou, `ID_VANZARE_AVIZ`=factura A, `TIP=3`) — corespondenta descrie
"acest document a folosit ca sursa factura A", nu "aceste linii vin din factura A".
**Ce se scrie cand nu s-a ales nicio factura sursa**: nu e un caz posibil pentru documentul de retur
insusi — validarea de antet (`inainte_de_do_termin`, `descriere` obligatoriu, sectiunea 3) impune cel
putin o factura aleasa inainte ca documentul sa poata continua. Deci pentru N.1/S4f (documente 8/9/24)
`clistaid` nu e niciodata gol la scriere. **Pentru N.2 ridicat la document (punctul 2)** raspunsul e
diferit: acolo documentul e de tip 1/5/7/10, iar `poDate.listaid`/campul nou echivalent (sectiunea 4)
nu e obligatoriu — un document normal fara nicio linie de retur nu are ce sa scrie. **Intrebare
deschisa, verificata partial**: daca `scrie_corespondente_vanzari(3)` e apelata azi doar cand
`ntip in (8,9)` (confirmat de `legatura_linie_retur.md:113`: *"TIP=3: factura de retur
(scrie_corespondente_vanzari(3), :14834-14836, la ntip in (8,9))"*), atunci **ridicarea lui N.2 la
nivel de document NU va scrie automat `VANZARI_CORESP`** — documentul ramane tip 1/5/7/10, gardat de
`ntip`, nu de continutul `listaid`. Linia exacta a acestui `IF`/`CASE` in exportul curent
(`ff_2026_08_09_01...sql`) **nu a fost re-verificata caracter cu caracter** in aceasta sesiune (citata
din raportul sursa, care a folosit un export anterior cu alta numerotare) — de reverificat la
implementare, dar concluzia structurala (gating pe `ntip`, nu pe `listaid`) e solida.
**Decizie de proiectare care rezulta**: ridicarea lui N.2 la nivel de document (punctul 2) **nu
capata**, prin simpla ridicare, o corespondenta persistata in `VANZARI_CORESP` — comportamentul
raman identic cu azi (nicio legatura scrisa), *cu exceptia* cazului in care Marius decide explicit ca
merita cod Oracle nou (extinderea gardei `ntip in (8,9)` la un al treilea semnal, ex. "are `listaid`
populat"). Decizia 35 (un singur cod de scriere contabila, cod Oracle nou doar cand strict necesar)
face ca varianta "fara schimbare in pack_facturare" sa fie implicita — **de confirmat cu Marius**
(risc R4), nu de presupus.
---
## 7. Semnul cantitatii / al valorii pe retur
**Doua mecanisme diferite, verificate separat pe cod:**
- **N.1 (documente 8/9/24)**: cantitatea vine deja cu semnul corect direct din
`cursor_retur_document` — `CANTITATE` a liniei originale (pozitiva ca valoare de referinta, dar
interpretata cu semn de retur prin `poDate.tip`); semnul efectiv de scriere in `VANZARI_DETALII`
e controlat de codul Oracle care insereaza documentul de retur (nu urmarit aici, in afara
perimetrului VFP), iar in UI validarea `do_verifica_articol` (`llRetur = Inlist(poDate.tip,8,9,24)`)
respinge `tnCantitate >= 0` in mod retur — deci utilizatorul introduce cantitatea ca valoare
pozitiva ("cat returnez"), iar semnul negativ e o conventie aplicata la nivel de tip de document,
nu per linie.
- **N.2 (`But_retur`)**: semnul negativ **e aplicat explicit in cod VFP**, in
`frm_articol_gest_factura` — `poArticol.cantitate = (-1) * cantitate` (`ofacturare.vc2:4103`),
declansat de `Thisform.lRetur` (adevarat cand `tlRetur` a fost trimis la `Init`, indiferent de
`poDate.tip`). Aici semnul **nu** vine din `poDate.tip` (documentul e tip 1/5/7/10, pozitiv prin
design), ci e o negatie explicita facuta pentru ca acea linie anume e un retur intr-un document
altfel normal.
**Implicatie pentru liniile libere pe document de retur (S4f punctul 3)**: pentru ca documentul
intreg e tip 8/9/24, orice linie — libera sau mostenita — trece prin acelasi tratament de tip de
document (semnul negativ e o proprietate a **documentului**, nu a liniei, in acest caz). Nu exista
risc de "linie libera cu semn pozitiv gresit" atata timp cat validarea ramane gatata pe `poDate.tip`
si nu pe originea liniei — de verificat explicit ca ramura noua a validarii (5.4, sarirea peste
maximul returnabil) **nu** atinge si sare peste verificarea de semn, care trebuie sa ramana activa
identic pe linii libere si mostenite.
**Implicatie pentru N.2 ridicat la document (punctul 2)**: aici semnul **ramane** o proprietate de
linie (nu de document, pentru ca documentul e tip normal 1/5/7/10) — mecanismul de la
`ofacturare.vc2:4103` se pastreaza neschimbat, declansat de acelasi `tlRetur`/`Thisform.lRetur`,
indiferent daca alegerea facturii sursa se face per-articol (ca azi) sau la nivel de document
(punctul 2). Nimic de schimbat aici — doar de confirmat ca noul flux (alegere la document) tot
seteaza `tlRetur=.T.` la apelul catre `do_alege_stoc`/`frm_articol_gest_factura` pentru fiecare linie
de retur adaugata.
---
## 8. Unde se aseaza informatia de provenienta in UI
**Inchis pe cod, cf. `legatura_linie_retur.md`**: legatura linie-la-linie nu se poate afisa in niciun
caz — nici `cursor_retur_document` nu aduce `ID_VANZARE`/`ID_VANZARE_DET` in `crsarticole`
(`ff_...sql:3965-4028`, coloanele lipsesc din SELECT), nici `INSERT`-ul final in `VANZARI_DETALII`
nu are coloana de sursa (`PACK_FACTURARE:13705-13757`, listata explicit, fara camp de provenienta).
**Ce se poate afisa, verificat**: la nivel de **document**, cand exact o singura factura sursa a fost
aleasa, `VANZARI_CORESP(TIP=3)` da fara ambiguitate factura sursa:
```sql
SELECT v.serie_act, v.numar_act
FROM VANZARI_CORESP c JOIN VANZARI v ON v.ID_VANZARE = c.ID_VANZARE_AVIZ
WHERE c.ID_VANZARE_FACT = :id_document_retur AND c.TIP = 3 AND c.STERS = 0;
```
(`legatura_linie_retur.md:174-178`, interogare formulata, neverificata pe date live in acel raport)
**Propunere concreta de text si conditie** (in romana, pentru sectiunea pliata / antetul
formularului unificat):
- **O singura factura sursa aleasa**: eticheta fixa de tip "Provine din factura: **{serie} {numar}**"
langa campul de descriere existent (`poDate.text_aditional`, deja populat azi cu
"RETUR FACTURA {numere}" — `factura_retur_document.md:65`), afisata read-only, imediat sub sau langa
campul de tip document.
- **Mai multe facturi sursa alese**: eticheta devine "Provine din facturile: **{lista serie+numar}**"
— aceeasi sursa de date (`poDate.descriere`, deja populata cu lista, nu doar `VANZARI_CORESP`),
**fara** sa se sugereze vreo distributie pe linii.
- **Niciodata pe linie** — nici coloana in grid, nici tooltip per rand care sa pretinda o sursa
exacta. Pentru liniile libere in particular, nu exista nimic de afisat (nu au sursa) — nu se
introduce o valoare "N/A" vizibila care ar sugera ca alte linii AU o sursa unica atunci cand de
fapt (cu selectie multipla) nici ele nu o au cu certitudine.
- **Conditie tehnica**: textul se construieste din `poDate.descriere` (deja in memorie, fara interogare
suplimentara) — nu necesita interogarea `VANZARI_CORESP` de mai sus decat daca se doreste
reafisarea la redeschiderea unui document deja emis (regenerare, etapa II) — caz in care
interogarea de mai sus e necesara pentru ca `poDate.descriere` nu mai exista in memorie.
---
## 9. Pasi de implementare ordonati
1. **Confirmarea comportamentului aditiv/inlocuitor la alegerea facturilor sursa** (sectiunea 3,
punctul 3) — decizie de produs, nu cod. *Gata cand:* Marius a ales (a) sau (b); fara asta pasii 2-3
nu se pot implementa fara risc de refacere.
*Verificabil:* niciunul (decizie, nu cod).
2. **Butonul/actiunea de alegere a facturilor sursa, mutat in sectiunea pliata a formularului
unificat**, pastrand `do_cauta_facturi`/`caut_facturi_multiple_client` neschimbate — doar
punctul de declansare se muta din antetul modal in sectiunea pliata a formularului unic, si
`cursor_retur_document` se declanseaza la confirmarea dialogului, nu la inchiderea unui formular
separat (sectiunea 3, punctele 1-2).
*Gata cand:* pe un document nou de tip 8/9/24, deschis in formularul unificat, alegerea facturilor
sursa din sectiunea pliata populeaza `crsarticole` cu exact aceleasi randuri ca azi (gestiune, pret
achizitie, pret vanzare identice), verificabil prin comparatie directa a cursorului.
3. **Meniul "Adauga articole" pe tipurile 8/9/24**, cu optiunile "Adauga tot din facturile alese" /
"Alege liniile de returnat…" peste `crsarticole` deja populat (reteta comuna S4b, sectiunile 4-5
ale acelui raport) — fara alegere de facturi in acest meniu (sectiunea 3).
*Gata cand:* meniul contine exact aceste doua optiuni pe tip 8/9/24, plus "Cauta in lista de
preturi…"/"Alege din nomenclator…" (punctul urmator).
4. **Liniile libere — `APPEND BLANK` pe `crsfactura`, semnalul de distinctie pe `id_c`** (sectiunile
5.1-5.2): optiunea de meniu "Cauta in lista de preturi…" pe tip 8/9/24 declanseaza
`Select crsFactura / APPEND BLANK` + `combosql`, exact tiparul S4e (`ofacturare.vc2:17118-17122`)
— **nu** `APPEND FROM` peste `crsarticole`. `id_c` ramane implicit (`0`) pe randul nou.
*Gata cand:* dupa adaugarea unei linii libere pe un document de test, `crsfactura.id_c = 0` pe acel
rand si `crsarticole` ramane neschimbat (acelasi `Reccount`/continut ca inainte de adaugare).
5. **Punct de intrare nou in dialogul de gestiune, pornind de la randul din `crsfactura`** (sectiunea
5.4) — `frm_articol_gest_factura`/`do_alege_stoc` primesc o a doua cale de apel, cu `poArticol`
construit ad-hoc din randul deja inserat in `crsfactura` (dupa `combosql`), nu din `Scatter` pe un
rand din `crsarticole`. Fara acest pas, o linie libera nu poate ajunge deloc la alegerea gestiunii
ceruta de decizia 22.
*Gata cand:* pe o linie libera adaugata pe un document de retur, dialogul de gestiune se deschide si
returneaza `ID_GESTIUNE`/`PRET_ACHIZITIE` alese de operator, fara sa fi trecut prin `crsarticole`.
6. **Fixarea pretului de vanzare pe liniile libere** (sectiunea 5.3, riscul R1) — ramura de
suprascriere din `frm_articol_gest_factura` (`ofacturare.vc2:4094-4103`) primeste al doilea semnal
("are sursa" vs. "e libera"), derivat din `crsfactura.id_c <> 0` (pasul 4), si nu suprascrie
`pretftva` cand linia e libera.
*Gata cand:* o linie libera adaugata pe un document de retur pastreaza pretul din lista de preturi
dupa alegerea gestiunii, verificabil comparand `poArticol.pretftva` inainte si dupa
`RowColChange` in dialogul de gestiune.
7. **Validarea de cantitate/stoc pe liniile libere** (sectiunea 5.5) — mecanism nou, nu
`do_verifica_articol` ca atare (care cere un `poArticol` din `crsarticole`); verifica disponibilul
de stoc curent, fara nicio comparatie cu un maxim returnabil. Unificabil cu golul echivalent
deschis de S4e §6 pentru comanda/avize, daca implementarea alege aceeasi varianta (`poArticol`
construit ad-hoc, parametru explicit).
*Gata cand:* pe o linie libera, orice cantitate <= stocul curent trece validarea, cu mesaj propriu
(nu "cantitate maxima de returnat"); pe o linie mostenita, comportamentul de azi ramane neschimbat.
8. **Ridicarea `But_retur` la nivel de document (punctul 2)** — dialog de alegere multipla la
deschiderea primei linii de retur pe un document normal (1/5/7/10), populare cu un camp nou sau
reutilizarea controlata a lui `poDate.listaid` (sectiunea 4), pastrand calea per-articol existenta
ca fallback sau eliminand-o explicit (decizie separata, risc R3).
*Gata cand:* pe o factura normala, alegerea facturilor sursa se face o singura data, iar liniile de
retur adaugate ulterior (mai multe articole) folosesc acel set fara sa redeschida dialogul de
cautare la fiecare articol.
9. **`VANZARI_CORESP` pentru N.2 ridicat la document** (sectiunea 6) — de decis daca se adauga cod
Oracle nou sau ramane fara corespondenta persistata (risc R4); implementat doar dupa decizia lui
Marius.
*Gata cand:* comportamentul ales (cu sau fara corespondenta) e implementat consistent si testat pe
un document cu retur ridicat la nivel de document si mai multe facturi sursa.
10. **Textul de provenienta la antet** (sectiunea 8) — afisare read-only din `poDate.descriere`,
condusa de numarul de facturi alese (0/1/multiple).
*Gata cand:* eticheta arata corect in toate trei cazurile (nicio factura inca, o factura, mai
multe), fara sa apara pe grid/linie.
*Depinde de:* S2 (formular unificat de baza), S4e (mecanismul `APPEND BLANK` + `combosql` pentru
liniile libere pe document cu sursa, NU `APPEND FROM`) — exact ca in plan (`plan_13...md:2330`),
corectat fata de reteta literala a deciziei 16.
---
## 10. Cum se verifica
**Proba din plan** (`plan_13...md:2327-2329`): *"un document de retur deschis in formularul unificat
aduce liniile facturilor alese cu aceleasi valori ca azi — inclusiv gestiunea si pretul de
achizitie —, permite stergere si cantitate partiala, iar pe o factura normala se poate face retur
alegand facturile o singura data."*
Pasi concreti:
1. **Paritate N.1**: pe date de test, emite un document de retur (tip 8) pe calea veche (formularul
actual) si retine `crsarticole` rezultat (gestiune, pret achizitie, pret vanzare, cantitate per
linie). Repeta aceeasi alegere de facturi sursa in formularul unificat si compara `crsarticole`
camp cu camp — trebuie sa fie identic, pentru ca sursa Oracle (`cursor_retur_document`) nu se
schimba, doar punctul VFP care o declanseaza.
2. **Stergere si retur partial**: pe formularul unificat, sterge o linie mostenita si verifica ca
cantitatea reintra in `crsarticole` (acelasi test ca azi, `do_sterge` neschimbat); introdu o
cantitate mai mica decat maximul pe o linie mostenita si verifica validarea.
3. **Linie libera**: adauga o linie din lista de preturi pe documentul de retur deschis (prin
`APPEND BLANK` + `combosql`, nu `APPEND FROM`); verifica (a) `crsfactura.id_c = 0` pe acel rand si
`crsarticole` neschimbat; (b) pretul de vanzare = pretul din lista de preturi, nu din stoc; (c)
gestiunea/pretul de achizitie sunt cerute prin dialogul de gestiune (intrat pe calea noua, 5.4), nu
completate automat; (d) cantitatea nu e limitata de niciun maxim istoric, doar de stocul curent;
(e) semnul cantitatii ramane negativ (consistent cu documentul); (f) stergerea liniei libere nu
modifica `crsarticole` (verifica direct: `Reccount`/continut identic inainte si dupa stergere).
4. **`VANZARI_CORESP` neschimbat de liniile libere**: dupa emiterea documentului din pasul 3,
interogheaza:
```sql
SELECT ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP FROM VANZARI_CORESP
WHERE ID_VANZARE_FACT = :id_document_nou AND TIP = 3;
```
— trebuie sa arate exact facturile alese la antet, indiferent de cate linii libere s-au adaugat.
5. **N.2 ridicat la document**: pe o factura normala, adauga doua linii de retur pentru articole
diferite folosind acelasi set de facturi sursa ales o singura data (nu redeschide dialogul la
fiecare articol); verifica semnul cantitatii (negativ) si absenta oricarei scrieri in
`VANZARI_CORESP` (daca decizia de la punctul 8/risc R4 pastreaza comportamentul de azi).
6. **Provenienta**: deschide un document de retur cu o singura factura sursa — verifica textul de
antet; alege doua facturi sursa — verifica ca textul listeaza ambele si nu implica o sursa unica.
## 11. Ce nu se poate testa headless
Reluat din capcanele deja confirmate pe acest proiect (`s4b_bara_butoane_meniu.md` §8), plus
specificul retur:
- **`cauta_alfa` cu `tnTipReturn=1`** — dialog modal (`.Show(1)`), blocheaza thread-ul UI; testarea
automata a bifarii multiple nu se poate face prin injectare de input pe aceasta masina (partajata,
memoria `masina-partajata-fara-input-real`). Se verifica indirect: apeland direct
`caut_facturi_multiple_client`/`cursor_retur_document` cu parametri de test si citind cursorul
rezultat, ocolind UI-ul.
- **`frm_articol_gest_factura`** — acelasi tip de dialog modal; verificarea suprascrierii de pret
(risc R1, sectiunea 5.3) si a noului punct de intrare pornit din `crsfactura` (sectiunea 5.4) se
poate face doar citind codul metodei `RowColChange`/noua metoda de intrare si, separat, apeland
direct logica de calcul cu date de test simulate — nu prin click real in grid.
- **Coloanele griduri** (`grd_articole`, gridul dialogului de alegere) — capcana deja cunoscuta:
`ColumnCount=0` sub `-A -T`; verificare vizuala necesita harness UI vizibil, nu headless.
(memoria `grid-coloane-nu-se-materializeaza-headless`)
- **Textul de provenienta din sectiunea pliata** (sectiunea 8) — verificabil headless ca **continut
al proprietatii** (`Caption`/`Value` a controlului), nu ca "arata bine" vizual.
- **Secventa reala de deschidere a dialogului din sectiunea pliata** (declansarea la click, nu doar
codul din spatele ei) — necesita harness UI vizibil pentru captura de interactiune.
**Ce se poate verifica headless, direct**: continutul `crsarticole`/`crsfactura` dupa apeluri directe
ale rutinelor (`cursor_retur_document`, `do_adauga_articol`, `do_verifica_articol`, `APPEND BLANK` pe
`crsfactura`) cu parametri de test; valoarea `crsfactura.id_c` pe randurile noi (semnalul de la 5.2,
`0` pe liniile libere, valoare reala pe cele mostenite); continutul real din
`VANZARI_CORESP`/`VANZARI_DETALII` dupa emiterea unui document de test pe schema Oracle (doar
`SELECT`, verificare post-factum).
---
## 12. Riscuri si ce ramane de decis de Marius
**R1 — Pretul de vanzare pe liniile libere se suprascrie cu pretul de stoc, daca nu se trateaza
explicit** (sectiunea 5.3). Cel mai concret risc gasit in aceasta cercetare, netratat in plan pana
acum. **Recomandare**: al doilea semnal ("linie cu sursa" vs. "linie libera") pe langa `tlRetur`,
derivat din `crsfactura.id_c <> 0` (5.2), care sa dezactiveze suprascrierea de pret doar pentru
liniile libere. Necesita o mica modificare in `frm_articol_gest_factura` (nu in `pack_facturare`).
**R7 — Punctul de intrare in dialogul de gestiune pentru o linie libera nu exista azi** (sectiunea
5.4). Gol nou, mai strict decat echivalentul lui pe comanda/avize (S4e §6, doar validare de
cantitate) — pe retur blocheaza si alegerea gestiunii/pretului de achizitie, obligatorie prin decizia
22. `do_alege_stoc`/`frm_articol_gest_factura` se intra azi doar dintr-un `poArticol` scatter-uit din
`crsarticole`; o linie libera (`APPEND BLANK` pe `crsfactura`, 5.1) nu are asa ceva. **Recomandare**:
a doua cale de apel, cu `poArticol` construit ad-hoc din randul `crsfactura` deja completat prin
`combosql`, unificata daca se poate cu solutia aleasa pentru golul echivalent de validare (R7 aici,
S4e §6/varianta b acolo) — un singur mecanism de "construieste `poArticol` fara sursa in `crsarticole`",
nu doua independente.
**R2 — Alegere aditiva sau inlocuitoare la a doua apasare a dialogului de facturi sursa** (sectiunea
3, punctul 3). Nu e tratat de nicio decizie existenta. **Recomandare**: aditiv, consistent cu
filozofia deciziei 16, dar cere cod VFP nou (bucla de filtrare pe facturi noi + `INSERT INTO
crsarticole`).
**R3 — Ce se intampla cu `caut_facturi_multiple_client_articol` (alegerea per-articol) dupa ridicarea
lui `But_retur` la nivel de document.** Ramane ca optiune secundara sau se elimina? Nefolosit oriunde
altundeva in cod (verificat implicit — apelul e doar din `do_adauga_articol`, ramura `tlRetur`).
**Recomandare**: se elimina, pentru ca pastrarea ambelor cai ar insemna doua UX-uri pentru aceeasi
actiune, exact riscul pe care unificarea vrea sa il elimine.
**R4 — `VANZARI_CORESP` pentru N.2 ridicat la document.** Gardat azi pe `ntip in (8,9)`, nu pe
continutul `listaid` — **reconfirmat independent** in aceasta sesiune, pe exact acelasi export
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:14834-14836`): `WHEN pack_facturare.ntip in (8, 9) THEN
pack_facturare.scrie_corespondente_vanzari(3);`, in interiorul unui `CASE` fara alta conditie pe
`listaid`. Daca ramane asa, un document normal
cu retur ridicat la document nu va avea nicio legatura persistata catre facturile sursa alese — exact
ca azi cu `But_retur` per-articol, deci nu e o regresie, dar nici un castig. **Recomandare**: se lasa
neschimbat (fara cod Oracle nou), consistent cu decizia 35 (economie de cod de intretinut) — de
confirmat explicit cu Marius, pentru ca punctul 2 al S4f ("ridicarea la nivel de document") ar putea
crea asteptarea implicita ca acum exista si o legatura persistata, cand de fapt nu exista.
**R5 — `Ct_clb_altele.cconditie` — semantica exacta neconfirmata** (sectiunea 2.1). Daca acest camp
chiar dezactiveaza azi re-alegerea dupa prima selectie, design-ul nou (sectiunea 3) trebuie sa decida
explicit sa **elimine** aceasta restrictie (pentru varianta aditiva) — altfel comportamentul nou ar
fi mai permisiv decat parea sa fie azi, fara sa fi fost o decizie constienta. **De verificat la
implementare**, cand clasa `ct_clb_cautare` devine accesibila (posibil in `COMUNROA`, in afara acestui
repo).
**R6 — Semnul cantitatii pe liniile libere, verificare explicita ceruta de brief** (sectiunea 7): nu
e un risc nou — analiza arata ca semnul e o proprietate a documentului (tip 8/9/24), nu a liniei,
deci liniile libere il mostenesc automat corect. Se listeaza aici doar ca sa fie explicit ca punctul
a fost verificat, nu omis.
---
## Handoff
Cercetare incheiata fara sa fie nevoie de predare de context — un subagent de verificare
(`ab0be8e27f0fb97f3`, Explore/general-purpose) a esuat de doua ori sa returneze rezultate, apoi a
raspuns complet la a treia incercare (reluat prin `SendMessage`), confirmand independent 4 puncte deja
scrise pe cod propriu in raport: (1) `do_cauta_facturi` e declansat explicit de user, prin
`Ct_clb_altele`/`caut_ora.vc2:787-798,839-844` -> `do_cauta_altele` -> `do_cauta_facturi`
(`ofacturare.vc2:8940-8961,9173`), inainte de orice deschidere a `frm_facturare_articole`; (2)
`scrie_corespondente_vanzari(3)` e gardat exact pe `ntip in (8,9)`
(`ff_...sql:14834-14836`) — folosit sa inchid definitiv R4; (3)
`thisform.cListaIdArticoleRetur` poate acumula `ID_VANZARE` diferit per articol
(`ofacturare.vc2:12882-12892`); (4) `poDate.listaid` e acelasi camp la N.1 si N.2, dar cu rol
tranzitoriu la N.2 (salvat/restaurat) si definitiv doar la `do_scrie_articole`
(`ofacturare.vc2:13977-13979`). Niciuna din aceste confirmari nu a schimbat vreo concluzie a
raportului, doar le-a intarit sursa.
**Corectie aplicata dupa livrare**, la avertismentul `team-lead` (bazat pe
`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si 7): sectiunea 5 (liniile libere) a
fost **rescrisa integral** — reteta `APPEND FROM` peste `crsarticole` (premisa initiala a brief-ului)
e inlocuita cu mecanismul validat de S4e (`APPEND BLANK` pe `crsfactura` + `combosql`, niciodata pe
`crsarticole`), campul de distinctie devine `crsfactura.id_c = 0` (nu absenta `PRET_ACHIZITIE` pe
`crsarticole`), si s-a adaugat un gol nou, negasit de S4e (care nu are dialog de gestiune pe
comanda/avize): punctul de intrare in `frm_articol_gest_factura` pentru o linie fara rand sursa in
`crsarticole` (risc R7, nou). Verdictul de la inceputul raportului, pasii de implementare (9),
verificarea (10) si sectiunile headless (11) au fost actualizate in consecinta. Restul raportului
(sectiunile 1-4, 6-8, 12 minus R1/R7) ramane neschimbat fata de livrarea initiala.
Fisierul SQL folosit: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(cel mai recent din director). Nicio editare de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun
commit, nicio scriere Oracle.

View File

@@ -1,437 +0,0 @@
# S4g — Adaugarea de articole la modificarea oricarui document (proiectare)
Proiectare, nu implementare. Zero fisiere de cod atinse, zero write-back, zero commit. Pe Oracle
doar `SELECT`. Continua planul `docs\plan_13_unificare_formular_facturare.md` liniile 2494-2530 si
cercetarile `canal_cont_venit_fara_politica.md` (sectiunea "Proiectarea parametrului de cont
contabil"), `parametru_cont_contabilizeaza_articol.md`, `coresp_cont_venchelt.md`,
`optiune_firma_cont_debit.md`.
**STARE: cercetare/proiectare incheiata.**
Sursa PL/SQL verificata direct in aceasta runda: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii, confirmat `wc -l`). Cod VFP citit: `COMUN\clase\ofacturare.vc2`,
`COMUN\programe\ofacturare_comun.prg` (ambele permise). Neatinse: `COMUN\clase\ofacturare_comun.vc2`,
`COMUN\programe\ofacturare_editare.prg` (perimetrul sesiunii #6).
---
## 0. Premisele deja decise (nu se redeschid)
- Povestea: a doua sursa de articole in meniu, "Alege din nomenclator...", disponibila si la
modificarea oricarui document deja emis, inclusiv auto (tip = -12, dar livrat separat, dupa).
- Decizia 20: din nomenclator se ofera si gestionabile, si negestionabile — nu se copiaza filtrul
`in_stoc = 0` al lui ROAAUTO.
- Decizia 21: contul de gestiune are fallback, nu NULL, nu refuz — mecanism separat de cel de aici
(priveste `id_gestiune`/`Cont` de gestiune al liniei, nu contul de venit), presupus deja rezolvat
cand derivarea din sectiunea 3 porneste.
- Decizia 27 (transportul inlocuit de decizia 34): contul de venit se **deriva** in VFP —
`CORESP_CONT_VENCHELT` pentru gestionabile (pe contul de gestiune al liniei), `NOM_ARTICOLE.CONT`
daca e 6xx/7xx pentru negestionabile, altfel `704`.
- Decizia 34: transportul e parametru nou, direct — nu prin `id_pol`/politica tehnica. Ocolul prin
`pack_preturi.adauga_politica_pret_art` e abandonat.
- Decizia 35: acelasi drum serveste si editarea prin regenerare — nu se proiecteaza a doua ruta de
contare.
- Decizia 36 (noua, 10.08.2026): `SCD` pe ramura fara politica nu e hardcodat `'4111'` literal —
vine dintr-o optiune de firma (`getoptiunefirma`), cu `4111` ca implicit dublu (rand in `OPTIUNI`
+ fallback hardcodat in PL/SQL), tiparul deja folosit de `RF_CONT_INCASARE_*` in acelasi pachet.
- "Alte servicii" ROAAUTO ocoleste complet `contabilizeaza_articol` — exceptie, nu jumatate de
mecanism.
- Regula deja adoptata in #13: liniile libere nu intra deloc in `crsarticole` — `APPEND BLANK` +
`combosql` direct in `crsfactura`, ca `id_c` sa ramana `0` si `do_sterge` sa fie no-op prin
constructie (verificat aici ca acelasi tipar se aplica si campului `id_pol`, sectiunea 4).
- Ordine: intai ROAFACTURARE, apoi tip = -12.
---
## 1. Punctul central: parametru vs politica, cine castiga
### 1.1 Ce face azi codul, verificat direct pe sursa (nu preluat din rapoarte)
`contabilizeaza_articol` (`ff_...:7173-7547`) primeste un singur parametru,
`detalii_articol VANZARI_DETALII_TEMP%ROWTYPE`. Primul lucru pe care il face (`:7275-7302`):
```sql
BEGIN
SELECT COMPUS, ID_POL_ART
INTO V_COMPUS, V_ID_POL_ART
FROM VCRM_POLITICI_PRET_ART
WHERE ID_ARTICOL = detalii_articol.id_articol
AND ID_POL = detalii_articol.id_pol;
EXCEPTION
WHEN NO_DATA_FOUND THEN
...
RAISE_APPLICATION_ERROR(-20000, 'Articolul ... nu este definit in politica de preturi ... (FACT-024)');
END;
```
Daca `detalii_articol.id_pol` e `NULL`, comparatia `ID_POL = NULL` nu se potriveste niciodata —
`NO_DATA_FOUND` garantat, FACT-024 garantat. Acelasi rezultat daca `id_pol` e populat dar articolul
nu e in acea politica. **`cursor_articol`** (`:7218-7271`), care face tot lucrul real (`scrie_nota`,
`descarca_gestiune`, discount), e filtrat **pe exact aceeasi cheie** (`A.ID_POL = detalii_articol.id_pol
AND A.ID_ARTICOL = detalii_articol.id_articol`, `:7270-7271`) — daca `SELECT INTO` de mai sus a picat
in `NO_DATA_FOUND`, cursorul ar gasi tot zero randuri (aceeasi cauza). Deci punctul de intrare in
politica e unic si dublu-verificat (o data prin exceptie explicita, o data structural prin cursor).
Design-ul deja facut (`canal_cont_venit_fara_politica.md:132-138`) propune sa infasoare **toata
functia** intr-un switch la nivel de metoda:
```
IF detalii_articol.cont_venit IS NOT NULL THEN
<ramura noua: SCC := cont_venit, SCD := optiune de firma (decizia 36), fara cursor, fara SELECT INTO de mai sus>
ELSE
<tot codul de azi, neschimbat, inclusiv SELECT INTO + FACT-024 + cursor_articol>
END IF;
```
**Aceasta e deja, prin constructie, varianta "parametrul castiga necondiionat cand e populat"** —
cand `cont_venit` nu e `NULL`, executia nu se mai uita deloc la `id_pol`/politica, indiferent daca
acestea ar fi rezolvat sau nu. Team-lead-ul cere explicit sa se decida asta ca punct de proiectare,
nu sa ramana o consecinta implicita a formei codului — mai jos analiza celor 3 variante realiste.
### 1.2 Cele 3 variante
**A. Parametrul castiga mereu cand e populat** (= design-ul existent, ca atare).
- *Ce se strica*: daca, dintr-un motiv oarecare (bug VFP, sau — mai realist — decizia 35: la
**regenerare**, daca logica de derivare a lui `cont_venit` ar rula necondiionat pe toate liniile
documentului, inclusiv cele scrise initial prin "Cauta in lista de preturi..." cu `id_pol` real),
o linie ajunge la Oracle cu **ambele** populate, `SCC`-ul politicii reale e inlocuit tacut cu
contul derivat generic, `SCD` cu optiunea de firma (posibil diferita de `NOTE_CONTABILE.SCD` al
notei reale), `ASCD`/`ASCC`/`EXPLICATIE`/`CU_TVA` cu variantele generice ale ramurii noi. **Fara
nicio eroare** — factura se emite, dar cu alta contare decat cea configurata prin politica. Cel
mai periculos tip de defect: tace si trece verificarea vizuala (suma corecta, cont plauzibil).
**B. Politica castiga mereu cand `id_pol` e populat** (indiferent daca rezolva sau nu).
- Ar cere schimbarea conditiei de switch din `cont_venit IS NOT NULL` in ceva de forma
`id_pol IS NULL AND cont_venit IS NOT NULL` — adica: daca exista `id_pol` pe linie, mergi pe
ramura veche necondiionat, chiar daca acel `id_pol` nu rezolva (caz FACT-024 de azi).
- *Ce se strica*: exact scenariul in care mecanismul nou ar fi cel mai util — o linie cu `id_pol`
populat gresit/stale (nu neaparat imposibil, doar improbabil in fluxul normal, vezi 1.3) —
ar continua sa primeasca FACT-024 desi VFP a trimis explicit un cont de rezerva. Varianta cea mai
fragila: defineste "castigatorul" pe **prezenta** unui camp, nu pe **rezolvarea** lui.
**C. Parametrul e folosit doar cand politica nu rezolva** (fallback real, nu switch pe camp).
- Cere ca `SELECT INTO ... FROM VCRM_POLITICI_PRET_ART` (1.1) sa ramana **necondiionat**, exact ca
azi (deja rulat pe fiecare linie, cost zero suplimentar), dar `EXCEPTION WHEN NO_DATA_FOUND` sa
verifice `cont_venit`: daca e populat, ramura noua; daca nu, `RAISE FACT-024` ca azi. Semantic
corect — politica, cand exista si rezolva, nu e niciodata inlocuita tacut; parametrul e strict ce
a fost gandit sa fie: o plasa pentru cazul in care politica lipseste.
- *Cost real*: restructurare interna a functiei, nu doar un `IF` la inceput. Codul de dupa blocul
`EXCEPTION` verifica azi `IF V_COMPUS = 1 THEN ... ELSE <deschide cursor_articol> END IF`
(`:7305,7391`) — `V_COMPUS` ramane `NULL` daca s-a intrat pe `EXCEPTION`, deci "cade" oricum pe
ramura `ELSE` care deschide `cursor_articol` (gaseste zero randuri, nu declanseaza nimic, dar nici
ramura noua). Trebuie introdus un flag explicit (`V_ARE_POLITICA`/similar) propagat din interiorul
`EXCEPTION` pana la punctul de decizie, marind suprafata modificata fata de varianta A (care
atinge doar granita functiei, cu restul intact).
### 1.3 Ar putea sa apara vreodata, in fluxul normal, ambele populate?
Verificat direct in aceasta runda (nu presupus): `crsfactura` (cursorul VFP din care se scrie orice
linie) are `id_pol N(20) Null` (`COMUN\programe\ofacturare_comun.prg:1775`) — camp nullable, deci la
`APPEND BLANK` (mecanismul liniilor libere, confirmat de S4e ca acelasi gest se foloseste si pentru
"Alege din nomenclator...") ramane `.NULL.` implicit, nu `0`. La scriere, `poArt.id_pol` (scatter
din randul respectiv) ajunge in apelul RPC ca `Nvl(Alltrim(Str(poArt.id_pol)),[NULL])`
(`ofacturare.vc2:14072`, identic la `:18107`) — literal SQL `NULL` cand campul e `.NULL.`. **O linie
"din nomenclator" trimite deci `id_pol = NULL` la Oracle prin constructie, nu doar prin conventie de
utilizare** — cursorul de cautare al nomenclatorului (`caut_articol`, `ocautare.prg`, deja confirmat
de `coresp_cont_venchelt.md` sectiunea 9d) nu are coloana `id_pol` in output, deci nu exista niciun
punct in care combosql-ul ar putea popula acest camp cu o valoare reala pentru o astfel de linie.
Ramane un singur scenariu real de ambiguitate: **regenerarea** (decizia 35). La regenerare, toate
liniile documentului (cele scrise initial prin "Cauta in lista de preturi...", cu `id_pol` real, **si**
cele adaugate prin "Alege din nomenclator...", fara `id_pol`) trec din nou prin acelasi drum de
scriere. Daca logica VFP care calculeaza `cont_venit` (sectiunea 3) ar rula **necondiionat** pe toate
liniile din grid, in loc sa fie conditionata explicit de "linia nu are `id_pol`", ar produce exact
combinatia periculoasa din varianta A. **Asta nu e un risc Oracle — e un risc de implementare VFP**,
dar exact tipul de risc pe care design-ul Oracle trebuie sa nu-l agraveze printr-un switch care il
face invizibil.
### 1.4 Recomandare
**Niciuna dintre cele 3 variante pure, ci varianta A (deja proiectata, cea mai simpla) plus o garda
explicita pe combinatia ambigua**, nu o rezolvare tacita in orice directie:
```sql
IF detalii_articol.cont_venit IS NOT NULL THEN
IF detalii_articol.id_pol IS NOT NULL THEN
RAISE_APPLICATION_ERROR(-20000,
'Articolul ' || detalii_articol.id_articol ||
' are simultan politica de pret (' || detalii_articol.id_pol ||
') si cont de venit calculat — conflict netratat (FACT-0xx)');
END IF;
<ramura noua, ca in design>
ELSE
<tot codul de azi, neschimbat>
END IF;
```
Motivare, in ordine:
1. **Combinatia nu trebuie sa apara niciodata in fluxul normal** (1.3) — `id_pol` ramane `NULL` prin
constructie pentru orice linie scrisa prin gestul "linie libera". Daca totusi apare, e un semnal
ca ceva e stricat in populate-ul liniei (VFP a trimis ambele campuri, posibil din cauza logicii
de regenerare descrisa la 1.3) — situatie de bug de investigat, nu de "rezolvat" silentios.
2. **Variantele B si C rezolva combinatia in cate o directie fixa** — ambele ascund o eroare de date
in loc sa o semnaleze: B ar putea reintroduce FACT-024 pe o linie unde VFP a oferit deja o
solutie; A fara garda ar inlocui tacut o politica reala. O garda explicita e singurul comportament
care nu presupune ca stie mai bine decat datele ce s-a intamplat.
3. **Cost de implementare minim**: un singur `IF` suplimentar, la intrarea in ramura deja proiectata
— nu restructureaza funcia (spre deosebire de varianta C), nu schimba conditia switch-ului
principal (spre deosebire de B).
4. **Compatibilitate/regresie zero pe apelantii de azi**: cand `cont_venit` e `NULL` (toti apelantii
existenti, care nu cunosc inca acest parametru), executia intra direct pe `ELSE` — garda nu se
evalueaza niciodata, comportamentul e identic bit-cu-bit cu azi. Cand `cont_venit` e populat si
`id_pol` e `NULL` (cazul intentionat, "din nomenclator"), garda trece nevazuta, ramura noua
ruleaza normal.
**Numele codului de eroare** (`FACT-0xx` in schita de mai sus) ramane de ales de Marius, distinct de
`FACT-024` (care ramane, neschimbat, pentru cazul "niciuna din cele doua" — linie fara politica si
fara cont).
---
## 2. Suprafata de schimbare pe Oracle
Deja proiectata si verificata adversarial in `canal_cont_venit_fara_politica.md` si
`parametru_cont_contabilizeaza_articol.md`; rezumat + completarea cu decizia 36 si garda de la
sectiunea 1.4:
1. **Coloana noua**: `VANZARI_DETALII_TEMP.CONT_VENIT VARCHAR2(4) NULL` (fara `NOT NULL`, fara
`CHECK` — acelasi tipar ca `ACT_TEMP.SCC`). Optional, recomandat pentru trasabilitate:
`VANZARI_DETALII.CONT_VENIT`, plus extinderea listei explicite de coloane din
`scrie_in_vanzari` (`PACK_FACTURARE:13705-13757`, confirmat ca listeaza 24 coloane explicit, nu
`SELECT *` — de extins cu o a 25-a daca se alege trasabilitatea).
2. **Parametru nou pe `adauga_articol_factura`** (`ff_...:4989-5015`), la coada, dupa `V_LOT`:
`V_CONT_VENIT IN VARCHAR2 DEFAULT NULL` — apelul VFP existent (pozitional, se opreste la
`V_LOT`, confirmat la doua locuri, `ofacturare.vc2:14069-14091` si `:18089-18114`, doua clase
distincte cu aceeasi metoda, nu o duplicare) ramane neschimbat, echivalent cu "trimite NULL".
Plumbing identic cu `V_CONT`/`V_CONT2`, o coloana in plus in `INSERT INTO VANZARI_DETALII_TEMP`
(`ff_...:5222-5282`).
3. **`contabilizeaza_articol` insasi NU primeste parametru nou** — ramane `VANZARI_DETALII_TEMP%ROWTYPE`;
coloana noua ajunge automat prin `SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP`
(`:6063`), fara nicio schimbare la cei 3 apelanti interni (`scrie_factura2`,
`scrie_factura_avize_retur`, `scrie_aviz_retur`).
4. **Ramura noua in `contabilizeaza_articol`**, cu garda de la sectiunea 1.4:
- `SCC := detalii_articol.cont_venit`.
- `SCD` (decizia 36): `PACK_SESIUNE.getoptiunefirma('FACT_SCD_ARTFPRET')`, cu fallback hardcodat
`'4111'` cand optiunea lipseste/e goala — optiune noua in tabelul `OPTIUNI`
(`VARTYPE='CHARACTER'`, script de migrare idempotent, tiparul exact al `RF_CONT_INCASARE_*`,
deja folosit in acelasi pachet, `optiune_firma_cont_debit.md` sectiunea 3.4). Ramurile de aviz
raman `'418'`/`'461'`, neschimbate.
- `ASCD`/`ASCC` din `GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD/V_SCC)` — acelasi
fallback deja folosit necondiionat pe ramurile de aviz azi.
- `EXPLICATIE` din `detalii_articol.explicatia` (parametru deja existent, `V_EXPLICATIE`).
- `ID_VENCHELT`/`ID_SECTIE` din `pack_facturare.nid_venchelt`/`nid_sectie_stoc` (fallback de
sesiune deja folosit ca prioritate azi).
- `IN_VALUTA` din `pack_facturare.nin_valuta` (parametru obligatoriu, fara `DEFAULT`, mereu
curent — nu o presupunere, sursa solida).
- `CU_TVA`: hardcodat `1`, **cu efect colateral real si masurat, nu "inofensiv"** —
`parametru_cont_contabilizeaza_articol.md` sectiunea 2b arata ca pe combinatia specifica "linie
fallback cu TVA 0% + discount global pe factura + acea linie e prima/singura vazuta" valoarea
forteaza `nproc_tva_max`/`nid_jtva_coloana` pentru toata linia de discount a facturii. Ramane
"de confirmat cu Marius", cu argumentul mai tare decat in propunerea initiala.
- Apeluri o singura data (nu in bucla): `scrie_nota(...)`, `descarca_gestiune(...)` (garda
identica, `nscadere_stoc=1 AND id_gestiune<>-1000 AND in_stoc=1`), discount (garda identica).
- Articole compuse (`V_COMPUS=1`) exclus structural — o linie fara `id_pol` nu poate avea
`ID_POL_ART`, deci intrebarea "e compus" nu se poate pune pe aceasta ramura (confirmat pe
schema view-ului, nu presupus).
5. **Ordinea de deploy**: DB inainte de EXE. Parametrul nou are `DEFAULT NULL`, deci EXE-ul vechi
ruland pe DB-ul nou functioneaza neschimbat (nu trimite parametrul). EXE-ul nou ruland pe DB-ul
vechi (fara coloana/parametru) ar esua la primul apel cu al 27-lea argument — deci EXE-ul nou
**nu** poate merge inaintea migrarii DB. Ordine standard, fara surpriza.
6. **Regenerarea (decizia 35)**: `initializeaza_date_factura` reseteaza starea de sesiune
(`DELETE FROM VANZARI_DETALII_TEMP`, `nid_act := 0`) la fiecare emitere — nimic din ramura noua
citeste vreo stare presupunand "documentul e nou". Calea Oracle e identica la regenerare, **cu o
exceptie separata, in alt pachet**: reemiterea cu acelasi `ID_FACT` ar da azi `ORA-00001` pe
`PK_DOCUMENTE` in `PACK_CONTAFIN.SET_IDFACT` — problema deja proiectata separat
(`idfact_refolosire_si_documente.md`), nu intersecteaza si nu invalideaza design-ul de aici, dar
ramane o bucata de lucru distincta, necesara pentru ca regenerarea sa fie completa.
7. **Gol neadresat inca**: ramura `ntip = 4` ("factura din avize", `:7520-7537`) apeleaza
`scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount — design-ul nu specifica ce se
intampla pe fallback aici. In practica improbabil (o linie de aviz sursa fara politica n-ar fi
ajuns la factura pe acest drum, `adauga_articol_factura:5096` cere deja `A.ID_POL = V_ID_POL` pe
avizul sursa), dar de exclus explicit inainte de implementare, nu de presupus tacit.
---
## 3. Derivarea contului in VFP
### 3.1 Sursele, in ordine (decizia 27)
1. **Articol gestionabil** (are `id_gestiune` valid, `in_stoc=1`): `CORESP_CONT_VENCHELT.CONT_VENIT`,
cautat pe **contul de gestiune al liniei** (`poArt.Cont`, camp deja gathered pe `poArticol` in
ambele metode `do_scrie_articole`, confirmat la `ofacturare.vc2:12945,13835` si echivalentele in
`frm_facturare_articole2`). Cheia de join e mereu `CONT` (nu `ID_GESTIUNE`, nu `TIP_DOC`) —
confirmat pe 4+ consumatori Oracle reali ai tabelului (`pack_vin`, `pack_devize`,
`gestiune_pack_gest_import`, un raport din 2026), niciodata pentru `CONT_VENIT` insa — aceasta
propunere ar fi **primul consumator real** al coloanei, desi populata din 2023 (20 conturi de
gestiune uzuale: 301-303/3021-3028, 331/332, 341, 345/346/348, 361, 371, 381). View-ul VFP-safe e
`VCORESP_CONT_VENCHELT` (`STERS=0` deja filtrat in view).
2. **Articol negestionabil**, sau gestionabil dar corespondenta nu rezolva (rand lipsa pentru acel
`CONT`): `NOM_ARTICOLE.CONT`, **doar daca** primul caracter e `6` sau `7` (`verific_cont` nu
restrange campul la o clasa, deci poate contine legitim orice cont valid, inclusiv 6xx/7xx pe un
articol negestionabil).
3. **Fallback final**: literal `'704'`. Fara reteta de cod deja scrisa in `pack_facturare` pentru
aceasta valoare, dar cu un precedent independent de "704 = cont implicit client" in alt subsistem
(`anaf_efactura.vc2:8376`, `cconte = 704`) — nu o inventie, dar cod nou.
### 3.2 Unde sta codul si cand ruleaza
Locul natural: chiar in `do_scrie_articole` (ambele clase, `frm_facturare_articole` si
`frm_facturare_articole2`, confirmat ca 2 locuri distincte de atins, nu 1), imediat inainte de
construirea textului RPC pentru fiecare linie, **conditionat explicit de `Empty(Nvl(poArt.id_pol,0))`**
— nu la momentul adaugarii liniei in grid. Doua motive:
- **Regenerarea (decizia 35)** re-parcurge toate liniile la fiecare scriere; derivarea la momentul
scrierii (nu la adaugare) garanteaza ca valoarea reflecta starea curenta a corespondentelor/
nomenclatorului, nu una inghetata la momentul in care linia a fost adaugata initial in grid.
- **Conditionarea explicita pe `id_pol` gol** (nu pe "linia vine din nomenclator" ca marcaj separat)
e chiar garda ceruta la sectiunea 1.3-1.4: o linie cu `id_pol` populat nu trebuie sa primeasca
niciodata o valoare pe `cont_venit`, indiferent de sursa ei — un singur punct de decizie, in
oglinda exacta cu conditia pe care Oracle o va verifica la randul lui (sectiunea 1.4).
### 3.3 Ce se intampla cand derivarea esueaza
- **Corespondenta nu are rand pentru acel `CONT`** (gestiune fara corespondenta configurata): cade pe
pasul 2 (`NOM_ARTICOLE.CONT`), nu pe eroare.
- **`NOM_ARTICOLE.CONT` nu incepe cu 6/7** (cont de gestiune sau alt tip pe un articol negestionabil,
configurare atipica): cade pe pasul 3 (`704`), nu pe eroare.
- **Niciodata `NULL`/gol trimis la Oracle pentru o linie "din nomenclator"** — ultimul pas e un
literal, nu o interogare care poate esua. Aceasta e proprietatea care face garda de la 1.4
inofensiva pe fluxul normal: `cont_venit` e *intotdeauna* populat cand `id_pol` e gol, deci
ramura noua din Oracle ruleaza mereu cand ar trebui, iar FACT-024 (ramura veche) nu mai poate fi
atinsa de o linie din nomenclator dupa implementare — dispare exact problema pe care povestea o
rezolva.
- **Nu e proiectata aici** (ramane de decis la implementare, in afara acestei povesti): daca vreo
validare suplimentara ar trebui sa verifice ca valorile derivate (`CONT_VENIT` din corespondenta,
`NOM_ARTICOLE.CONT`) sunt conturi valide in planul de conturi al anului curent — azi
`verific_cont` exista ca mecanism (`oproceduri_comune.prg:2389-2415`) dar nu e cablat automat pe
acest drum nou.
---
## 4. Fluxul in formularul unificat
1. Utilizatorul deschide un document deja emis la modificare (`frm_modific2024`/formularul unificat,
in afara perimetrului #6/omodificari.vc2, care nu se atinge aici) si alege din meniu "Alege din
nomenclator..." (a doua sursa, langa "Cauta in lista de preturi...", ambele reduse la acelasi
gest UI conform S4b/S4e).
2. Gestul UI: `Select crsfactura / APPEND BLANK` (tiparul deja in productie la
`frm_facturare_articole2.But_nou1.do_adauga`, `ofacturare.vc2:17118-17122`), focus pe celula de
cautare, `combosql` legat pe cursorul filtrat pe nomenclator (`caut_articol`/echivalent, fara
coloana `id_pol` in output — confirmat, sectiunea 1.3).
3. **`crsfactura.id_c` ramane `0`** (valoarea implicita a campului `N(20)` fara `Null`, la
`APPEND BLANK`) — valoare pe care Oracle nu o produce niciodata pentru `id_c`. `do_sterge`
(potriveste pe `id_c`) e no-op prin constructie pe aceasta linie, fara nicio modificare de cod —
exact regula deja adoptata in #13, reconfirmata aici pentru sursa "nomenclator" (S4e o stabilise
pentru sursa "lista de preturi pe comanda"; acelasi cursor de scriere `crsfactura`, acelasi
mecanism, doar alt cursor de cautare in fata).
4. **`crsfactura.id_pol` ramane `.NULL.`** (camp `N(20) Null`, `ofacturare_comun.prg:1775`), pentru
ca sursa de cautare (nomenclator) nu are aceasta coloana in output — confirmat direct in aceasta
runda (sectiunea 1.3), nu presupus.
5. Utilizatorul completeaza cantitate/pret (campuri editabile inline pe grid, tipar deja existent).
6. La `do_scrie_articole` (salvarea documentului), pentru fiecare linie cu `Empty(Nvl(poArt.id_pol,0))`,
se ruleaza derivarea din sectiunea 3 si se populeaza al 27-lea argument pozitional al apelului RPC
catre `adauga_articol_factura` cu valoarea calculata; pentru restul liniilor (cele cu `id_pol`
populat, "din lista de preturi"), argumentul ramane `NULL` — comportament identic cu azi.
7. Documentul se salveaza; pe Oracle, `contabilizeaza_articol` ruleaza ramura noua (sectiunea 1-2)
pentru liniile fara politica, ramura veche neschimbata pentru restul.
8. **Editarea** (regenerare, decizia 35): acelasi `do_scrie_articole`, aceeasi conditie pe `id_pol`,
acelasi rezultat — nu exista o a doua cale de contare pentru liniile deja existente pe document.
---
## 5. Ce ramane in afara (partea auto)
- `tip = -12` (facturare auto, ROAAUTO) — livrat separat, dupa ce mecanismul de mai sus e stabil pe
documentele ROAFACTURARE (ordinea deja decisa in plan).
- "Alte servicii" din ROAAUTO ramane exceptia ei — ocoleste complet `contabilizeaza_articol`, nu
foloseste si nu va fi migrata sa foloseasca acest mecanism ca parte a acestei povesti; daca se
decide vreodata unificarea, e o poveste separata.
- Gridul read-only din `frm_modific2024` (al #6) — S4g incepe dupa ce #6 se termina (decizia 30),
nicio proiectare de aici nu presupune sau modifica acel perimetru.
- Validarea de cantitate/stoc pentru o linie liberă la modificare (analogul sectiunii 6 din
`s4e_lista_preturi_pe_sursa.md`, dar pentru nomenclator, nu lista de preturi) — nu re-proiectata
aici, acelasi gol semnalat deja de S4e se aplica identic si pe sursa "nomenclator"; de rezolvat cu
acelasi mecanism (varianta (b), reutilizare `do_verifica_articol` cu `poArticol` explicit).
---
## 6. Teste minime
Pe langa cele deja listate in `canal_cont_venit_fara_politica.md` sectiunea 6 si
`nota_contabila_fara_politica.md`, specifice acestei povesti:
1. **Factura normala cu politica, parametru NULL** — verifica ACT identic cu azi (regresie de baza).
2. **Aviz cu articol adaugat din nomenclator (fara politica)** — `SCD` ramane `'461'`/`'418'`
(ramurile de aviz raman hardcodate independent de ramura noua/veche), nu `getoptiunefirma`.
3. **`ntip = 46`** (daca exista pe fluxul de modificare vizat) — `scrie_nota` nu se cheama pe acea
ramura; de confirmat ca ramura noua nu e atinsa deloc pentru acest tip.
4. **Articol gestionabil din nomenclator, cu corespondenta configurata** — un singur rand `ACT`,
`SCC` = `CORESP_CONT_VENCHELT.CONT_VENIT` pentru contul de gestiune al liniei, `SCD` = optiunea
de firma (sau `4111` daca optiunea lipseste), linie TVA scrisa separat.
5. **Articol negestionabil din nomenclator, fara corespondenta aplicabila** — `SCC` = `NOM_ARTICOLE.CONT`
daca incepe cu 6/7, altfel `'704'`.
6. **Articol negestionabil** (`id_gestiune=-1000`) cu `cont_venit` populat — `descarca_gestiune` NU
ruleaza.
7. **Discount pe o linie din nomenclator** — foloseste `V_ASCD`/`V_CU_TVA` calculate in ramura noua.
8. **Document in valuta** (`nin_valuta=1`) cu linie din nomenclator — `IN_VALUTA=1` pe nota,
`SUMA_VAL` completat.
9. **Linie fara politica si fara cont derivat** (nu ar trebui sa se poata construi prin UI, dar de
testat direct pe Oracle) — FACT-024 tot apare, regresie negativa: garda nu s-a slabit.
10. **Linie cu ambele populate** (construita direct la nivel de apel Oracle, nu prin UI — testeaza
garda de la sectiunea 1.4, nu fluxul normal) — noua eroare explicita apare, nu o rezolvare
tacita in nicio directie.
11. **Regenerare pe un document mixt** (o linie din lista de preturi cu `id_pol`, o linie din
nomenclator fara `id_pol`) — dupa regenerare, prima linie tot cu `SCC` din politica, a doua tot
cu `SCC` derivat; niciuna nu trece pe ramura celeilalte.
12. **`id_c` si `do_sterge`** (mostenit din tiparul S4e, de re-verificat pentru sursa nomenclator):
o linie din nomenclator adaugata la modificare, apoi stearsa inainte de salvare — no-op pe
`crsarticole`, fara efect asupra vreunei linii reale a documentului.
---
## 7. Riscuri si de decis de Marius
1. **Garda explicita pe combinatia `id_pol` + `cont_venit` ambele populate** (sectiunea 1.4) — e o
recomandare de proiectare a acestui raport, nu o decizie deja luata de Marius; codul exact al
erorii si numarul `FACT-0xx` raman de ales.
2. **`CU_TVA` hardcodat `1`** — are un efect colateral masurat (nu doar teoretic), prin
`nproc_tva_max`, pe combinatia specifica linie-scutita + discount global de factura. De confirmat
explicit, nu de presupus inofensiv.
3. **Decizia 36 (`SCD` prin optiune de firma)** — numele exact al cheii (`FACT_SCD_ARTFPRET` propus),
daca se adauga validare de cont la citire (niciun precedent existent nu valideaza), si
`PROGRAME` (restrans la ROAFACTURARE sau extins ca `RF_CONT_INCASARE_*`) raman decizii deschise
in `optiune_firma_cont_debit.md` sectiunea 4.
4. **Ramura `ntip=4`/"factura din avize" pe fallback** (sectiunea 2 punctul 7) — probabil imposibil
de atins prin acest drum (avizul sursa cere deja `id_pol`), dar nu exclus explicit prin cod sau
test — de confirmat inainte de implementare.
5. **Suprafata de regresie in restul suitei** (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB) pentru
`adauga_articol_factura` — pe baza cercetarilor existente, aceste produse folosesc proceduri
separate (`_deviz`/`_stoc`) sau alt pachet complet (`pack_acn`), deci risc asteptat zero, dar
cautarea directa in `COMUN`-urile lor pentru un apel simplu la `adauga_articol_factura(` nu s-a
terminat in nicio runda anterioara — de re-rulat inainte de implementare, nu de presupus incheiata.
6. **Validarea de cantitate/stoc pentru linia libera la modificare** (sectiunea 5) — gol mostenit
de la S4e, nu inchis aici, de rezolvat cu acelasi mecanism pe ambele surse (lista de preturi si
nomenclator).
7. **Trasabilitatea `CONT_VENIT` pe `VANZARI_DETALII`** (sectiunea 2 punctul 1) — optionala pentru
ca mecanismul sa functioneze, dar fara ea coloana nu se pastreaza dupa fapt; de decis daca merita
extinderea listei explicite de coloane din `scrie_in_vanzari`.
8. **`idfact_refolosire_si_documente.md`** — obstacol real pentru ca regenerarea (decizia 35) sa fie
completa (reemitere cu acelasi `ID_FACT`), in `PACK_CONTAFIN`, nu in `pack_facturare` — nu
blocheaza design-ul de aici, dar e o bucata de lucru separata, necesara inainte ca "editare =
regenerare" sa functioneze end-to-end.
---
## STARE / CE RAMANE
Cercetare/proiectare incheiata pentru toate cele 6 puncte cerute in brief, cu punctul central
(sectiunea 1) tratat explicit ca decizie de proiectare (nu doar consecinta implicita a codului
existent deja schitat in rundele anterioare). Nicio editare de cod, niciun `git_sync.ps1`/
`txt2vcx.ps1`, niciun commit, nicio scriere pe Oracle. Context consumat moderat in aceasta runda —
nu a fost necesara predarea de mijloc de sesiune.
**Ce nu s-a putut inchide complet, de reluat separat**:
- Cautarea exhaustiva a apelantilor `adauga_articol_factura` simplu in restul suitei (risc 5,
sectiunea 7) — de re-rulat, nu s-a terminat in nicio runda anterioara din lipsa de timp, nu din
cauza unei erori.
- Confirmarea explicita a lui Marius pe hardcodarile/optiunile ramase deschise (`CU_TVA=1`, numele
cheii de optiune, garda de la sectiunea 1.4) — sunt recomandari argumentate, nu decizii finale.

View File

@@ -1,502 +0,0 @@
# Cercetare + proiectare — S5: acoperirea tuturor tipurilor de document
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara
scriere pe Oracle — numai `SELECT`), pentru povestea **S5** din
`docs\plan_13_unificare_formular_facturare.md` (`#### S5`). Nu s-a atins
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul altei
sarcini in lucru) — doar citite cand au aparut in cautari (n-a fost cazul).
Sursa VFP: `COMUN\clase\ofacturare.vc2` (`frm_facturare_articole` = formularul de productie,
`:10968-15739`; `frm_facturare_articole2` = prototipul, `:15741-19355` — **nu e subclasa** a
primului, mostenesc separat din `_frmbase`, au `Init` propriu fiecare). Sursa de rutare:
`COMUN\programe\ofacturare.prg` (`factureaza` = standard, `:81-...`; `factureaza2` = prototip,
`:660-...`). Referinta de tipuri: `COMUN\docs\tipuri_documente_facturare.md`.
## Verdict (rezumat, citeste asta primul)
1. **`Do Case`-ul din `frm_facturare_articole.Init` (`ofacturare.vc2:15109-15248`) acopera 21 de
valori de `tip`** (grupate in 14 ramuri): `1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,28,29,41,42,47`.
2. **Patru tipuri sunt reale, reachable prin `factureaza()`, dar nu apar in niciun `Case`** —
pierd titlu, cap de coloana, mesaj de stoc, vizibilitate discount, eliminare `cSerie`: **`45`
(factura restaurant), `48`/`49` (custodie cu/fara descarcare K), `52` (contract, factura fiscala
valuta)**. Confirmat pe meniuri (`Meniuri\politica.mn2:18,26,29`, `Meniuri\contracte.mn2:18`) si
pe rutarea cursorului (`ofacturare.prg:271-282`), care **le recunoaste** — doar Init-ul
formularului de articole nu le-a "prins" niciodata. **Nu doar `52`**, cum semnalase raportul S4b —
sunt patru, nu unul.
3. **Cinci tipuri din referinta (`43,44,46,50,51`) nu sunt niciodata pasate lui `factureaza()`
in tot arborele `D:\ROA`** (cautare exhaustiva, zero potriviri) — nu ajung la acest formular deloc
azi. `50` e marcat explicit "in lucru" in sursa; `51` (ROAACNPRO) foloseste `id_set=50100`, un
interval separat de restul (`25000+`), semn ca provine dintr-un flux Oracle direct al altui produs,
nu din `factureaza()` local.
4. **Descoperire centrala, dincolo de ce cerea misiunea**: prototipul (`frm_facturare_articole2.Init`,
`:18988-19080`) **nu e o versiune partiala a Do Case-ului standard — e aproape gol**. Singurul
lucru pe care-l face pe tip e sa aleaga cuvantul `lcTipDoc` ("factura" vs "aviz"), pe o lista
**mai scurta** (lipseste `24`). Nu seteaza titlu (nu exista `lb_titlu_alb_b121` in tot fisierul
prototipului), nu schimba capul coloanei de cantitate, nu schimba mesajul de stoc, nu ascunde
discountul, **nu are deloc conceptul de coloana `cSerie`** (gridul prototipului, `grd_factura`,
n-are niciodata `RemoveObject('cSerie')` — cautare pe tot fisierul, zero potriviri in intervalul
`15741-19355`). Daca formularul unificat porneste de la prototip (cum indica decizia de baza a
planului), **toata diferentierea pe tip trebuie reconstruita de la zero**, nu doar completata.
5. **Rutarea cursorului diverge intre standard si prototip pe trei tipuri, nu doua**: `23`
(confirmat deja de S4/S4b), plus **`52` si `24`, gasite aici** — pe prototip, `Case Inlist(tnTip,
2, 26, 6)` (`ofacturare.prg:762`) **omite `52`** fata de standard (`Inlist(tnTip, 2, 26, 6, 52)`,
`:283`), si `Case Inlist(tnTip, 8, 9)` (`:819`) **omite `24`** fata de standard (`Inlist(tnTip, 8,
9, 24)`, `:307`). Daca cineva ar factura tip `52` sau `24` prin prototip azi (`gnFacturareNou=1`),
`lcSqlCursor` ar ramane nedefinit — eroare, nu doar diferenta de comportament.
6. **Tipurile `26` si `52` n-au niciun bookkeeping `crsarticole`** (nici Rol A, nici Rol B) —
inchis aici punctul lasat deschis de raportul S4 punctul 2: excluderea lor din toate cele patru
`Case`-uri de bookkeeping din `do_adauga_articol`/`do_sterge` e totala (Do Case exhaustiv, fara
ramura implicita), nu doar "neconfirmata".
7. **`27` si `30` raman pe calea lor** — confirmat pe cod, cu o nuanta importanta pentru `30`: nu
e un formular separat, ci **acelasi `frm_facturare_articole`, instantiat si trecut prin acelasi
`Init`/`Do Case`, dar niciodata aratat** (`ofacturare.prg:444-453`: calculeaza totalurile, apasa
programatic `but_termin1.Click()`, apoi `Release()`, fara `Show()`). Tip `30` **e afectat de
golurile din `Do Case`** exact ca oricare alt tip needitat — doar ca defectele (titlu, cap de
coloana) nu se vad niciodata pe ecran.
---
## 0. Metoda de verificare — lista de referinta
Lista completa de tipuri vine din `COMUN\docs\tipuri_documente_facturare.md` (sursa unica, deja
verificata pe cod de acea cercetare). Tipuri incluse in tabelul de mai jos: toate cele din sectiunile
"Facturi" si "Avize de expeditie" (documentele care intra prin `frm_facturare_articole`/`2`).
Sectiunea "Tipuri negative" (`-1..-13`) **nu intra in acest formular** — sunt scrise de alte produse
(ROAGEST, ROAAUTO) prin propriile lor fluxuri, niciodata prin `factureaza()` din ROAFACTURARE
(cautare exhaustiva `factureaza(-` in tot `D:\ROA`, zero potriviri) — declarate aici explicit **ramase
pe calea altui produs**, nu "neacoperite".
---
## 1. Tabelul complet, tip cu tip
Coloane: `tip` = `VANZARI.TIP` · **Case propriu** = are ramura proprie in `frm_facturare_articole.Init`
(`ofacturare.vc2:15109-15248`)? · **titlu** = ce seteaza pe `lb_titlu_alb_b121.Caption` · **cap
cantitate** = ce seteaza pe `grd_articole.cCantitate.header1.Caption` (implicit ramane cel din
design, `[Cantitate in stoc]`, daca nu e suprascris) · **mesaj stoc** = `This.cmesaj_cantitate` ·
**discount** = `clb_discount.Visible` · **`cSerie`** = coloana ramane (`Da`) sau se scoate (`Nu`) ·
**butoane** = ce se face vizibil (`but_urmator_tot1`/`but_retur`, ambele `.F.` la design) · **cursor
standard** = ramura din `factureaza` (`ofacturare.prg:266-308`) · **cursor prototip** = ramura din
`factureaza2` (`:748-822`, gol daca lipseste) · **Rol crsarticole** = A (cantitate ramasa de
facturat) / B (plafon de sesiune) / — (fara bookkeeping), din `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md`
· **stare S5** = acoperit azi / gol de completat / ramas pe calea veche / neatins.
### Facturi
| tip | denumire | Case propriu | titlu | cap cantitate | mesaj stoc | discount | `cSerie` | butoane vizibile | cursor standard | cursor prototip | Rol | stare S5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | lista de preturi (lei) | **Da** `:15122` (grup 1,5,7,10) | — (implicit) | "Cantitate in stoc" | "nu e pe stoc!" | vizibil (implicit) | **Nu** (scoasa) | `but_retur` | `cursor_preturi` (grup 1,22,5,29,7,10,23), `:279-282` | `cursor_preturi` (grup 1,22,5,29,7,10), `:758-761` | B (gestionabil, `1,22,29`) | acoperit azi, de portat |
| 2 | contract (lei) | **Da** `:15129` (grup 2,6) | "FACTURA PE CTR. …" | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | — | `cursor_contract` (grup 2,26,6,52), `:283-291` | `cursor_contract` (grup 2,26,6 — **fara 52**), `:762-769` | B doar pt. `opt_facturare=0`; — pe rest | acoperit azi, de portat |
| 3 | comanda | **Da** `:15144` | "FACTURA LA COMANDA …" | "Cantitate comandata" | "cantitate comandata facturata" | vizibil | **Nu** | `but_urmator_tot1` | `cursor_comanda` (grup 3,21,25,28,42,47), `:292-293` | `cursor_comanda` (acelasi grup), `:771-773` | **A** | acoperit azi, de portat |
| 4 | din avize | **Da** `:15151` | "FACTURA DIN AVIZE" | — (implicit) | "cantitate de pe aviz facturata" | **ascuns** (`.F.`, `:15154`) | Da (nu se scoate) | `but_urmator_tot1` | `cursor_avize`, `:294-295` | `cursor_avize`, `:774-775` | **A** | acoperit azi, de portat |
| 5 | lista de preturi valuta | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat |
| 6 | contract valuta | **Da** `:15129` (grup 2,6) | "FACTURA PE CTR. …" | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | — | `cursor_contract` | `cursor_contract` | B partial (ca 2) | acoperit azi, de portat |
| 7 | credit note | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat |
| 8 | retur factura lei | **Da** `:15236` (grup 8,9) | — | "Cant. max. de returnat" | "nu se mai poate returna" | vizibil | Da (nu se scoate) | `but_urmator_tot1` | `cursor_retur`, `:306-307` | `cursor_retur` (grup 8,9), `:819-820` | B (invers) | acoperit azi, de portat |
| 9 | retur factura valuta | **Da** `:15236` (grup 8,9) | — | "Cant. max. de returnat" | "nu se mai poate returna" | vizibil | Da | `but_urmator_tot1` | `cursor_retur` | `cursor_retur` | B (invers) | acoperit azi, de portat |
| 10 | factura fiscala valuta | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat |
| **43** | bon fiscal magazine (ROARETAIL) | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — niciodata pasat lui `factureaza()` (cautat in tot `D:\ROA`); colectat de la magazine prin alt flux |
| **44** | factura hotel | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — la fel, zero apeluri `factureaza(44` |
| **45** | factura restaurant | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** (implicit) | **nesetat** (ramane "nu e pe stoc!" default, `:15108`) | **nesetat** (ramane vizibil) | **Da, ramane** (nescoasa) | **nesetat** | `cursor_preturi`, `:275-278` | `cursor_preturi`, `:754-757` | — (exclus explicit din bookkeeping, `:12871,17178`) | **gol real de completat** — reachable din `Meniuri\politica.mn2:18` |
| **46** | nota de plata restaurant | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — zero apeluri `factureaza(46`; zero documente in date de test (`tipuri_documente_facturare.md`, capcana 2) |
| **48** | custodie cu descarcare K | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_articole_k`, `:271-273` | `cursor_articole_k`, `:750-752` | — | **gol real de completat** — reachable din `Meniuri\politica.mn2:29` (submeniu `Marfaincus`) |
| **49** | custodie fara descarcare K | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_articole_k` | `cursor_articole_k` | — | **gol real de completat** — reachable din `Meniuri\politica.mn2:26` |
| **50** | *(in lucru)* retur custodie | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins**, marcat explicit "in lucru" in `tipuri_documente_facturare.md` |
| **51** | factura ROAACNPRO | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — `id_set=50100`, interval separat; probabil scris direct de ROAACNPRO, nu prin `factureaza()` local |
| **52** | contract, factura fiscala valuta | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_contract` (grup 2,26,6,52) | ***lipseste*** din grupul contract (`:762`) — `lcSqlCursor` nedefinit pe prototip | — (confirmat, vezi §2) | **gol real de completat** — reachable din `Meniuri\contracte.mn2:18` |
### Avize de expeditie
| tip | denumire | Case propriu | titlu | cap cantitate | mesaj stoc | discount | `cSerie` | butoane vizibile | cursor standard | cursor prototip | Rol | stare S5 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 21 | catre clienti, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — (implicit) | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
| 22 | catre clienti, din lista | **Da** `:15178` (grup 22,29) | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_preturi` | `cursor_preturi` | B | acoperit azi, de portat |
| **23** | transfer subunitati, din lista | **Da** `:15187` | "TRANSFER INTRE SUBUNITATI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | **`cursor_preturi`** (grup 1,22,5,29,7,10,**23**), `:279` | **`cursor_gestiune`** (grup **23**,41), `:776-778` | **B** (cod comun, indiferent de sursa) | acoperit azi, **dar sursa de cursor diverge intre forme — vezi §3** |
| 24 | aviz retur | **Da** `:15242` | "RETUR AVIZ DE EXPEDITIE" | "Cant. max. de returnat" | "nu se mai poate returna" | (nemodificat aici) | **Nu** | `but_urmator_tot1` | `cursor_retur` (grup 8,9,**24**), `:306-307` | ***lipseste*** din grupul retur (`:819`, doar 8,9) — `lcSqlCursor` nedefinit pe prototip | B (invers) | acoperit azi, **dar prototipul n-are ramura de cursor — vezi §3** |
| 25 | transfer subunitati, din comanda | **Da** `:15206` | "TRANSFER INTRE SUBUNITATI PE BAZA DE COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
| 26 | catre clienti, din contract | **Da** `:15216` | "AVIZ DE EXPEDITIE DIN CONTRACTUL …" | "Cantitate in stoc" | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_contract` (grup 2,26,6,52) | `cursor_contract` (grup 2,26,6 — fara 52, dar 26 e prezent) | — (confirmat, §2) | acoperit azi, de portat |
| **27** | transfer subunitati, pe lucrare | n/a — **ramane pe calea lui** | n/a | n/a | n/a | n/a | n/a | n/a | `cursor_lucrare`, `:302-304` | `cursor_lucrare`, `:815-817` | n/a | **ramas pe calea veche** — `frm_avizare_lucrare`, confirmat §4 |
| 28 | catre clienti debitori, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
| 29 | catre clienti debitori, din lista | **Da** `:15178` (grup 22,29) | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_preturi` | `cursor_preturi` | B | acoperit azi, de portat |
| **30** | transfer subunitati, pe NIR | n/a — **ramane pe calea lui, dar prin acelasi formular** | (irelevant — formular niciodata aratat) | (irelevant) | (irelevant) | (irelevant) | (irelevant) | (irelevant) | `cursor_aviz_nir`, `:299-300` | `cursor_aviz_nir`, `:813` | n/a | **ramas pe calea veche, cu nuanta** — vezi §4 |
| 41 | retur transfer, lista pret | **Da** `:15197` | "RETUR TRANSFER" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_gestiune`, `:296-298` | `cursor_gestiune` (grup 23,41) | **B** | acoperit azi, de portat |
| 42 | catre clienti custodie, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
| 47 | catre clienti custodie K, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
| **50** | *(in lucru)* retur clienti custodie | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins**, "in lucru" |
**Tipuri negative** (`-1..-13`, ROAGEST/ROAAUTO): **ramase pe calea altui produs** — nu trec
niciodata prin `factureaza()`/`factureaza2` din ROAFACTURARE (cautare exhaustiva, zero potriviri),
deci nu intra in perimetrul Do Case-ului acestui formular. Declarate aici explicit, nu omise.
---
## 2. Tipurile care nu intra in niciun `Case` — inventar complet
Cerinta explicita a misiunii: "nu doar 52". Lista completa, verificata pe intreg `Do Case`-ul
(`ofacturare.vc2:15109-15248`, citit integral, nu esantion) fata de lista de referinta:
**Nu apar in niciun `Case` al `frm_facturare_articole.Init`:**
| tip | reachable prin `factureaza()`? | ce pierde concret |
|---|---|---|
| 43 | Nu (0 apeluri in tot `D:\ROA`) | irelevant — nu ajunge la acest formular |
| 44 | Nu | irelevant |
| **45** | **Da** (`Meniuri\politica.mn2:18`) | titlu, cap coloana cantitate, mesaj de stoc, `cSerie` nescoasa (ramane vizibila, desi tip 45 e explicit exclus din bookkeeping-ul de cantitate — cele doua lucruri nu sunt legate) |
| 46 | Nu | irelevant — zero documente si in datele de test |
| **48** | **Da** (`Meniuri\politica.mn2:29`, submeniu `Marfaincus`) | idem 45 |
| **49** | **Da** (`Meniuri\politica.mn2:26`) | idem 45 |
| 50 | Nu, "in lucru" | irelevant azi |
| 51 | Nu (interval `id_set` separat, alt produs) | irelevant pentru acest formular |
| **52** | **Da** (`Meniuri\contracte.mn2:18`) | titlu (ramane cel implicit al formularului), `cSerie` nescoasa, cap coloana cantitate implicit — **cel mai vizibil defect, pentru ca 2/6 (acelasi grup logic) au titlu corect** |
**Concluzie**: din cele noua tipuri fara `Case`, **patru sunt reale si vizibile utilizatorului azi**
(`45, 48, 49, 52`) — acestea sunt golul de completat cu valoare, nu doar `52`. Celelalte cinci
(`43,44,46,50,51`) nu ajung niciodata la acest formular in fluxul curent — nu au nevoie de ramura in
`Do Case` **pana cand** ceva le conecteaza la `factureaza()` (posibil, dar in afara perimetrului
observabil aici; de tratat ca risc, nu ca bug, la sectiunea 10).
**De ce raman "invizibile" azi cu `cSerie`/titlu implicit, nu cu eroare**: `Do Case ... Endcase` fara
ramura `Otherwise` in VFP nu genereaza nicio eroare cand nimic nu se potriveste — pur si simplu sare
peste tot blocul. De-asta tip 45/48/49/52 "merg" (formularul se deschide, factureaza cu succes), doar
cu UI-ul netratat pentru cazul lor specific — un defect tacut, nu un crash, motiv probabil pentru care
n-a fost observat/raportat pana acum.
---
## 3. Divergentele standard vs. prototip
| Aspect | Standard (`factureaza`) | Prototip (`factureaza2`) | Comportament corect de pastrat |
|---|---|---|---|
| **Cursor pe tip 23** | `cursor_preturi` (grup `1,22,5,29,7,10,23`, `:279`) — tratat ca lista de preturi | `cursor_gestiune` (grup `23,41`, `:776-778`) — tratat ca transfer | **`cursor_gestiune`**, impreuna cu 41 (deja stabilit de S4/S4b: transferul e o singura familie de tip, indiferent daca porneste "din lista" sau "retur"; tratarea ca lista de preturi pe standard e inconsistenta cu propriul titlu "TRANSFER INTRE SUBUNITATI" pe care tot standardul il afiseaza pentru tip 23) |
| **Cursor pe tip 52** | prezent, grupat cu `2,26,6` (`:283`) | **absent** din grupul contract (`:762`, doar `2,26,6`) — `lcSqlCursor` ramane nedefinit daca cineva factureaza tip 52 prin prototip | **prezent**, grupat cu `2,6,26` — lipsa lui pe prototip e o eroare de portare, nu o alegere deliberata (nimic in cod sugereaza ca 52 trebuia tratat diferit de 2/6/26 la nivel de cursor) |
| **Cursor pe tip 24** | prezent, grupat cu `8,9` (`:306-307`) | **absent** din grupul retur (`:819`, doar `8,9`) — `lcSqlCursor` nedefinit | **prezent**, grupat cu `8,9` — acelasi tip de omisiune ca la 52 |
| **Init: diferentiere pe tip** | 14 ramuri, seteaza titlu/cap coloana/mesaj/discount/`cSerie`/butoane | practic nimic — doar `lcTipDoc` ("factura"/"aviz"), pe o lista **fara tip 24** | **toata logica standardului**, portata — prototipul nu are nimic de pastrat aici in afara de pozitia `lcTipDoc` |
| **Coloana `cSerie`** | exista in grid prin design, se scoate condiționat (10 din 21 tipuri acoperite) | **nu exista deloc** ca si coloana in `grd_factura` (gridul unic al prototipului) | de decis explicit la proiectare (§5) — nu e o simpla portare, gridul insusi trebuie sa capete coloana |
| **`but_urmator_tot1` (sau echivalentul lui)** | vizibil pe 9 din 21 de tipuri (§1) | nu exista conceptul in Init — prototipul nu are nimic care sa corespunda azi | de portat lista completa de vizibilitate din standard |
| **`clb_discount.Visible`** | ascuns explicit pe toate tipurile de aviz (`21,28,42,47,22,29,23,41,25,26`) | niciodata atins in Init | de portat integral |
**De ce conteaza asta pentru S5**: planul spune ca formularul unificat se bazeaza pe prototip
(arhitectura lui: grid unic, editare inline, cautare pe server — deja deciziile S1-S4). Dar
**diferentierea pe tip nu vine "aproape gata" din prototip** — vine aproape in intregime din
standard, si trebuie portata, nu doar completata cu cele patru tipuri lipsa. Cele doua liste (tipuri
lipsa din standard: 45/48/49/52; tot ce lipseste din prototip: aproape totul) sunt probleme
**diferite**, care se rezolva **in aceeasi miscare** daca proiectarea de la §5 porneste de la o
sursa unica de configurare portata integral din standard, cu cele patru completari incluse de la
inceput (nu adaugate separat, dupa portare).
---
## 4. Tipurile speciale (27, 30) — confirmate pe cod
### Tip 27 — transfer pe baza de lucrare
Confirmat la trei niveluri, toate in `ofacturare.prg`:
- `Do Case tnTip = 27 -> poDate.nIdTipDoc = 6` (`:188-189`, tip document AVIZ);
- `Do Case tnTip = 27 -> lcObiect = [frm_date_aviz_lucrare]` (`:218-219`) — **formular de antet
diferit**, nu `frm_date_aviz`/`frm_date_factura`;
- `Do Case tnTip = 27 -> lcObject = [frm_avizare_lucrare]` (`:386-387`) — **formular de articole
diferit**, nu `frm_facturare_articole`. Cursorul sursa e si el propriu: `cursor_lucrare`
(`:302-304`), populat pe `poDate.id_lucrare`, un camp pe care restul tipurilor nu-l au.
**Ce il tine pe calea lui**: `id_lucrare` — o legatura pe care niciun alt tip de document n-o are
(lucrare de service/executie, nu comanda/aviz/contract). `frm_avizare_lucrare` grupeaza gestiunile
destinatie diferit (`crsgestiunidest`, `:388-393`, cu optiunea `<TOATE>`), o structura pe care
`frm_facturare_articole`/`2` n-o au. **Formularul unificat n-ar avea `id_lucrare` si n-ar avea
gruparea pe gestiuni destinatie** — motiv suficient sa ramana separat, confirmat pe cod, nu
presupus.
### Tip 30 — transfer pe baza de NIR
**Nuanta importanta, gasita aici**: tip 30 **nu ocoleste** `frm_facturare_articole` — il
instantiaza, exact ca orice alt tip din grupul "otherwise" (`ofacturare.prg:395`, `lcObject =
[frm_facturare_articole]`, ramura `Else` a lui `If tnTip = 27`). Trece prin acelasi `Init`, acelasi
`Do Case` de la `:15109-15248` (unde `30` nu are ramura proprie — ar avea aceleasi goluri ca 45/48/49
daca ar fi vreodata aratat). Diferenta reala: **formularul nu e niciodata aratat**
(`ofacturare.prg:444-453`):
```
IF tnTip = 30 && AVIZ DIN NIR
ofrmdetaliifactura.do_calculeaza_totaluri()
ofrmdetaliifactura.but_termin1.Click()
plVizibil = .F.
...
ELSE
...
If plVizibil
ofrmdetaliifactura.Show()
Else
ofrmdetaliifactura.Release()
pnButon = 2
Endif
ENDIF
```
**Ce il tine pe calea lui**: nu structura formularului (e acelasi obiect), ci **automatizarea
completa a fluxului** — cursorul sursa (`cursor_aviz_nir`, populat din `VRUL`/tranzactii de receptie,
nu din comanda/lista de preturi) vine deja complet, iar codul apeleaza direct metodele de finalizare
fara interactiune. **Pentru formularul unificat**: daca arhitectura noua pastreaza acelasi tipar
("creeaza obiectul, populeaza, cheama finalizarea, `Release()` fara `Show()`"), tip 30 continua sa
functioneze neschimbat — nu are nevoie de ramura in configurarea vizuala (§5), pentru ca vizualul nu
se vede niciodata. **Singurul risc real**: daca `do_calculeaza_totaluri()`/`but_termin1.Click()` ale
formularului unificat ajung sa citeasca vreo proprietate pe care doar `Do Case`-ul vizual o seteaza
azi (de exemplu, un cod care ar verifica `This.cmesaj_cantitate` sau `lcTipDoc` in logica de calcul,
nu doar in UI) — **de verificat explicit la implementare**, nu presupus ca "nu conteaza pentru ca nu
se vede".
---
## 5. Ce structura inlocuieste `Do Case`-ul de 140 de linii
### Optiunile comparate
**(a) Pastreaza `Do Case`, doar completeaza-l** (adauga ramuri pentru 45/48/49/52, porteaza restul in
prototip). Cost minim imediat, dar **nu rezolva problema de fond**: un `Do Case` fara `Otherwise` nu
semnaleaza niciodata un tip lipsa — exact mecanismul care a produs golul de azi (patru tipuri reale
pierdute, ani la rand, fara nicio eroare). Orice tip nou de document adaugat in viitor (suita are deja
`46,50` "in lucru", `43,44,51` din alte fluxuri) risca aceeasi soarta.
**(b) Metoda separata per grup de tipuri** (`configureaza_lista_preturi()`, `configureaza_comanda()`,
...). Mai clar decat un `Do Case` unic, dar tot **implicit** — un tip nou tot nu declanseaza nicio
eroare daca nimeni nu-l adauga in metoda corecta; doar muta problema din 140 de linii intr-un fisier
cu mai multe metode mici, fara sa adauge un mecanism de detectie.
**(c) Tabel de configurare per tip (RECOMANDAT)**. Un cursor/tabel cu **un rand per `tip`**, coloanele
fiind exact proprietatile pe care `Do Case`-ul le seteaza azi: `titlu`, `cap_cantitate`, `mesaj_stoc`,
`discount_vizibil` (`L`), `are_serie` (`L`), `tip_doc` (`factura`/`aviz`), `buton_tot_vizibil` (`L`),
`buton_retur_vizibil` (`L`), `grup_sursa` (pentru meniul S4b: `lista/comanda/aviz-comanda/transfer/
retur/contract-articole/contract-rate`). Populat printr-un singur bloc de `INSERT INTO` (sau un DBF
static, `configuratie_tip_document.dbf`, editabil fara compilare) — **un rand per tip din
`tipuri_documente_facturare.md`**, inclusiv cele patru azi lipsa.
`Init` devine:
```
SELECT * FROM configuratie_tip_document WHERE tip = poDate.tip INTO CURSOR crscfgtip
If Reccount('crscfgtip') = 0
* tip necunoscut -- eroare explicita, nu formular netratat tacut
AMESSAGEBOX("Tip de document necunoscut in configurare: " + Transform(poDate.tip), 16, "Eroare configurare")
Thisform.Release()
Return
Endif
This.lb_titlu_alb_b121.Caption = crscfgtip.titlu
This.grd_articole.cCantitate.header1.Caption = crscfgtip.cap_cantitate
This.cmesaj_cantitate = crscfgtip.mesaj_stoc
This.clb_discount.Visible = crscfgtip.discount_vizibil
If !crscfgtip.are_serie
This.grd_articole.RemoveObject('cSerie')
Endif
This.but_urmator_tot1.Visible = crscfgtip.buton_tot_vizibil
This.but_retur.Visible = crscfgtip.buton_retur_vizibil
```
**De ce e mai bun decat (a)/(b) pe cost de intretinere**:
- **Un tip lipsa devine o eroare vizibila la deschidere**, nu un formular netratat tacut — exact
defectul care a permis golul de azi sa treaca neobservat ani la rand.
- **"Cat de usor se vede un tip lipsa" e mecanic, nu vizual** — vezi §8, o interogare simpla compara
lista de tipuri din configurare cu lista de referinta din `tipuri_documente_facturare.md`, fara sa
ruleze formularul.
- **Grupurile identice raman explicite, nu implicite** — azi, "tipurile 21,28,42,47 au acelasi titlu"
se vede doar citind `Inlist(...)`; intr-un tabel, acelasi lucru se vede ca patru randuri cu aceeasi
valoare in coloana `titlu` — usor de generat cu un singur `INSERT` per grup (`FOR EACH tip IN
(21,28,42,47) ... INSERT ...`), nu mai putin explicit, dar auditabil cu `SELECT titlu, COUNT(*)
GROUP BY titlu`.
- **Coloana `are_serie` rezolva si divergenta standard/prototip de la §3** — prototipul nu are azi
conceptul deloc; cu configurarea noua, adaugarea coloanei `cSerie` in gridul unificat devine
conditionata de aceeasi sursa unica, indiferent de forma de baza.
**Cost**: portarea initiala a ~21 de randuri (14 ramuri distincte de azi + 4 completari + grupare
explicita), plus un nou tabel/cursor de intretinut. Nu e cod nou complex — e date, nu logica; riscul
de regresie e in acuratetea portarii (fiecare valoare trebuie sa corespunda exact cu ce face azi
`Do Case`-ul), verificabil linie cu linie fata de tabelul din §1.
**Recomandare finala**: **(c)**, cu tabelul de configurare implementat ca `DBF` static (nu cursor
generat in cod) — editabil de oricine fara sa recompileze, si direct verificabil cu `SELECT` fara sa
porneasca formularul (vezi §8).
---
## 6. Grupuri naturale de tipuri
Din tabelul §1, grupurile care au azi (sau ar trebui sa aiba) valori identice pe toate coloanele:
| Grup | Tipuri | Ce difera **in interiorul** grupului |
|---|---|---|
| **Lista de preturi, fara stoc special** | 1, 5, 7, 10 | Nimic in Init — difera doar valuta/tip document la nivel de antet (`poDate.in_valuta`, `nIdTipDoc`), nu in acest formular |
| **Contract, factura** | 2, 6 (+ **52** de adaugat) | Nimic in Init dupa completare — `52` e valuta, ca `6`, dar cu alt `id_set`; titlul/coloanele sunt identice |
| **Aviz din comanda (clienti/debitori/custodie)** | 21, 28, 42, 47 | Nimic in Init — difera doar destinatia comerciala (client normal/debitor/custodie), invizibila la acest nivel |
| **Aviz din lista de preturi** | 22, 29 | Nimic — difera doar client normal/debitor |
| **Retur facturi** | 8, 9 | Nimic — lei/valuta |
| **Transfer subunitati** | 23, 41 | Sens (din lista vs. retur) — titlu diferit ("TRANSFER..." vs "RETUR TRANSFER"), restul identic; **trebuie unificate pe cursor** (§3) inainte de unificare vizuala |
| **Comanda proprie** | 3 | Singur — cap de coloana propriu ("Cantitate comandata") |
| **Avize proprii** | 4 | Singur — discount vizibil (spre deosebire de toate celelalte avize) |
| **Aviz din contract** | 26 | Singur — titlu propriu, dar cursor comun cu grupul contract |
| **Aviz retur** | 24 | Singur — cursor comun cu 8/9, dar titlu si `but_urmator_tot1` proprii |
| **Custodie K** | 48, 49 | Identice ca structura vizuala (ambele lipsesc azi) — difera doar `cu`/`fara` descarcare K, invizibil la acest nivel |
| **Restaurant** | 45 | Singur, azi lipsa |
Observatie de proiectare: grupurile "aviz din comanda" (21/28/42/47) si "comanda" (3) au **acelasi**
cap de coloana si mesaj de stoc conceptual ("cantitate comandata"), dar text usor diferit
("facturata"/"avizata") — pastrate distincte in tabelul de configurare (nu fortate identice), pentru
ca diferenta e deliberata in codul de azi (`:15149` vs `:15176`, verb diferit).
---
## 7. Pasi de implementare, ordonati
1. **Extrage tabelul de configurare din `Do Case`-ul standard, exhaustiv** — un rand per tip din
`tipuri_documente_facturare.md` care intra prin acest formular (Facturi + Avize, exclus 27/30/
negative), valorile copiate exact din §1. *Gata cand*: `SELECT DISTINCT tip FROM
configuratie_tip_document` produce exact multimea `{1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,28,29,
41,42,45,47,48,49,52}` (23 de tipuri) — nici unul in minus, nici unul in plus fata de lista
calculata in §8.
2. **Completeaza cele patru randuri azi lipsa (45,48,49,52)** cu valori coerente cu grupul lor logic
(45 langa lista de preturi cu stoc dezactivat conceptual; 48/49 langa custodie K; 52 langa 2/6) —
decizie de continut, nu doar de structura; **de validat cu Marius inainte de a le considera
"gata"**, pentru ca azi nu exista niciun titlu/mesaj de referinta pentru ele (nimeni nu l-a vazut
pe ecran). *Gata cand*: cele patru randuri au valori nenule pe toate coloanele obligatorii
(`titlu`, `cap_cantitate`, `mesaj_stoc`).
3. **Corecteaza rutarea cursorului**: `23` trece pe `cursor_gestiune` (unificat cu `41`, nu mai divide
standard/prototip); `52` si `24` primesc ramura de cursor pe orice cale ramane vie din prototip
(daca formularul unificat pastreaza `factureaza2`-stil, sau devine parte din calea unica daca
`factureaza`/`factureaza2` se unesc — decizie separata, in afara acestei povesti). *Gata cand*:
deschiderea formularului pe tip `23`, `24` si `52` prin calea noua produce acelasi continut in
`crsarticole` ca varianta care functiona deja (`23`→prototip vechi pentru comparatie de continut,
`24`/`52`→standard).
4. **Adauga coloana `cSerie` in gridul unificat, condiționata pe `are_serie`** — azi absenta din
gridul prototipului; adaugata o singura data, aratata/ascunsa din configurare, nu prin
`RemoveObject` scris de mana pe fiecare tip. *Gata cand*: pe un tip cu `are_serie=.T.` (ex. 4)
coloana e vizibila; pe un tip cu `are_serie=.F.` (ex. 3) nu e.
5. **Inlocuieste `Do Case`-ul din `Init` cu citirea din configurare** (structura din §5), inclusiv
ramura de eroare explicita pe tip necunoscut. *Gata cand*: pentru fiecare din cele 23 de tipuri,
deschiderea formularului seteaza exact valorile din tabelul §1/pasul 2 (comparatie automata, nu
vizuala — proprietatile sunt citibile headless).
6. **Verifica tipurile speciale raman neatinse**: 27 (cale total separata, neschimbata), 30 (acelasi
formular, dar `do_calculeaza_totaluri`/`but_termin1.Click()` nu citesc nimic setat doar de vechiul
`Do Case` vizual — verificare explicita, §4). *Gata cand*: un document tip 30 de test se
finalizeaza cu acelasi rezultat in `VANZARI`/`VANZARI_DETALII` inainte si dupa migrare.
7. **Documenteaza tipurile neatinse (43,44,46,50,51) ca decizie explicita**, nu ca omisiune — un
comentariu in tabelul de configurare (`* 43,44,46,50,51: neconectate la factureaza() in
ROAFACTURARE, verificat <data>`) ca viitorii cititori sa nu presupuna ca lipsesc din greseala.
*Gata cand*: comentariul exista si linkeaza spre acest raport.
*Depinde de*: S4 (cautarea articolelor pe server) pentru arhitectura gridului unic — pasul 4 de aici
presupune ca gridul unificat exista deja in forma stabilita de S4; S4b (bara de butoane) pentru
`buton_tot_vizibil`/`buton_retur_vizibil`, care alimenteaza si meniul `xmenu()` de acolo — coloanele
`grup_sursa` din tabelul de configurare (§5) sunt exact ce cere S4b sectiunea 3.
---
## 8. Cum se verifica ca acoperirea e completa — proba mecanica
**Nu o citire — o interogare care compara doua liste.** Doua surse de adevar:
1. **Lista de referinta**: tipurile din `COMUN\docs\tipuri_documente_facturare.md`, sectiunile
"Facturi" si "Avize de expeditie", **minus** cele confirmate neatinse azi de acest formular (27, 30
raman — vezi nuanta §4 — dar 43,44,46,50,51 se exclud daca raman neconectate; de recalculat lista
la fiecare rulare, nu de la o constanta inghetata).
2. **Lista din configurare** (dupa implementarea §5): `SELECT DISTINCT tip FROM
configuratie_tip_document`.
**Script de verificare** (headless, fara UI, rulabil oricand):
```foxpro
* verifica_acoperire_tip.prg — proba mecanica pentru S5
LOCAL lnLipsa, lnInPlus
* 1. lista de referinta -- tinuta manual sincron cu tipuri_documente_facturare.md
* (facturi + avize, exclus negative; 27/30 raman in lista, tratate separat la pasul 3)
DIMENSION laReferinta[23]
laReferinta = [1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,27,28,29,30,41,42,47,52] && + 45,48,49 dupa Pasul 2
* 2. lista din configurare
SELECT DISTINCT tip FROM configuratie_tip_document INTO CURSOR crsCfg
* 3. tipuri de referinta fara configurare (exclus 27, 30 -- cale separata confirmata)
* 4. tipuri in configurare fara corespondent in referinta (config "orfana")
* -- ambele liste trebuie sa fie goale la "gata"
```
Alternativ, **fara sa astepte implementarea §5**: acelasi principiu se aplica azi direct pe
`Do Case`-ul din `ofacturare.vc2:15109-15248`, extragand tipurile din fiecare `Case ... Inlist/=` prin
`vfp_symbols.ps1 -Grep 'Case (poDate\.tip|Inlist\(poDate\.tip'` si comparand rezultatul cu lista de
referinta — exact tehnica folosita in aceasta cercetare pentru a produce tabelul din §1 (nu o
citire vizuala, o extractie sistematica).
**Pentru cursor (rutare, §3)**: acelasi principiu, pe `ofacturare.prg`, comparand tipurile din fiecare
`Do Case`/`Inlist` de la `:266-308` (standard) cu cele de la `:748-822` (prototip) — orice tip prezent
intr-una si absent in cealalta e o divergenta de raportat (asa au fost gasite `52` si `24` in aceasta
sesiune).
---
## 9. Ce nu se poate testa headless
- **Titlul, capul de coloana, mesajul de stoc ca text afisat efectiv pe ecran** — verificabile
headless doar ca *proprietati setate* (`This.lb_titlu_alb_b121.Caption` dupa `Init()`, apelat direct
cu un `poDate` de test), nu ca randare vizuala. Diferenta conteaza: o proprietate corect setata pe
un control care nu exista (`lb_titlu_alb_b121` lipsa din prototip azi) ar trece "headless" fara sa
arate nimic real — de confirmat mai intai ca ambele controale exista in formularul unificat.
- **Coloanele de grid (`grd_articole`/`grd_factura`, coloana `cSerie` noua)** — capcana deja cunoscuta
pe acest proiect: sub `-A -T` (rulare headless), `ColumnCount=0`/`RecordSource` raman artefacte,
coloanele nu se materializeaza. Exista un harness UI **vizibil** care le citeste corect — orice
verificare a `are_serie`/coloanelor trebuie sa treaca prin acela, nu prin `-A -T`.
- **Tip 30 — fluxul complet fara `Show()`** — se poate rula headless (nu deschide nicio fereastra prin
constructie), dar verificarea "rezultatul in Oracle e identic inainte/dupa" cere date de test reale
(un NIR cu articole), nu doar apelul metodei.
- **Popup-ul/dialogul viitor din S4b (`xmenu()`, `cauta_alfa`)**, daca `grup_sursa` din tabelul de
configurare (§5/§6) ajunge sa-l alimenteze direct — mostenit ca limitare de la raportul S4b (`§8`
de acolo): continutul `lcMeniu` verificabil ca text, popup-ul afisat nu.
- **Comportamentul real al meniurilor `politica.mn2`/`contracte.mn2`** pentru tipurile 45/48/49/52 —
se poate confirma ca exista intrarea de meniu (citire `.mn2`, facuta in aceasta cercetare), dar nu
ca apasarea ei in productie deschide exact formularul asteptat, fara o rulare UI vizibila.
---
## 10. Riscuri si ce ramane de decis de Marius
1. **Continutul exact (titlu/mesaj) pentru cele patru tipuri azi netratate (45,48,49,52)** — codul nu
ofera niciun precedent vizual (n-au fost vazute niciodata corect pe ecran), deci textele propuse in
Pasul 2 (§7) sunt o **propunere**, nu o recuperare a unui text existent. **Recomandare**: 45 langa
grupul "lista de preturi fara stoc" (e exclus explicit din bookkeeping de stoc, `:12871`); 48/49 cu
titlu care mentioneaza "custodie" (singurul lucru care le distinge conceptual azi, in afara de
coeficientul K, invizibil la acest nivel); 52 **identic cu 2/6** ("FACTURA PE CTR. ..."), pentru
coerenta cu gruparea deja facuta corect in `frm_date_factura.Init` (`:9530,9632,9670`, grupul
`2,6,52`).
2. **Daca 23 trece pe `cursor_gestiune` pe calea unificata, comportamentul vizibil pentru operator se
schimba** (sursa de articole devine stocul din gestiune, nu lista de preturi) — **decizie de produs,
nu doar tehnica**, deja semnalata de S4/S4b, dar repetata aici pentru ca afecteaza direct §5/§7
(tabelul de configurare trebuie sa reflecte alegerea finala, nu ambele variante). **Recomandare**:
`cursor_gestiune`, argumentat de coerenta cu titlul "TRANSFER INTRE SUBUNITATI" pe care insusi
standardul il afiseaza azi pentru 23 (titlul spune "transfer", cursorul azi spune "lista de
preturi" — inconsistenta interna de rezolvat, nu de pastrat).
3. **Daca 43,44,46,50,51 raman permanent neconectate la `factureaza()`, sau exista planuri sa fie
activate** — nu s-a gasit nicio dovada de cod ca ar fi in curs, dar nici o confirmare ca sunt
abandonate definitiv (in afara de `50`, marcat explicit "in lucru" in sursa). **De intrebat pe
Marius direct**, pentru ca raspunsul schimba daca tabelul de configurare (§5) trebuie sa le includa
preventiv sau poate ramane cu 23 de randuri.
4. **Coloana `cSerie` pe gridul unificat — cost de adaugare** — prototipul n-are azi conceptul deloc;
adaugarea ei presupune modificari de grid (nu doar de cod), posibil in afara perimetrului text-only
(`.vc2`/`.sc2` prin `txt2vcx.ps1`) daca structura de coloane a gridului cere editare in IDE — **de
confirmat la implementare**, nu presupus ca e o simpla proprietate.
5. **Tabel de configurare ca `DBF` static vs. `INSERT`-uri in cod** — recomandarea (§5) e DBF, pentru
editabilitate fara recompilare, dar suita foloseste azi predominant cod pentru acest fel de
configurare (niciun precedent de DBF static de configurare gasit in `frm_facturare_articole`/
`frm_date_factura`) — **de validat cu Marius daca abaterea de la conventia existenta merita
beneficiul**, sau daca un cursor generat in cod (mai aproape de stilul actual, dar mai putin
editabil) e preferat.
## Handoff
Cercetare + proiectare incheiata in aceasta sesiune, fara sa fie nevoie de predare de context.
Toate cele zece sectiuni cerute de misiune sunt completate, cu `fisier:linie` verificat direct pe
fisierele text reale (`ofacturare.vc2`, `ofacturare.prg`, nu `.bak`), plus meniurile `.mn2` si
raportul S4/S4b/S4_punct2 citate ca sursa pentru punctele deja stabilite (nereinvestigate).
Descoperiri dincolo de cerinta explicita a misiunii, semnalate clar in text: (1) patru tipuri lipsa
din `Do Case`, nu unul (45/48/49/52, sectiunea 2); (2) prototipul (`frm_facturare_articole2.Init`)
e aproape complet gol pe diferentiere de tip, nu doar incomplet (sectiunea 3) — cea mai mare
descoperire a acestei sesiuni, cu impact direct asupra efortului de implementare estimat pentru S5;
(3) doua divergente noi de rutare a cursorului (52, 24), pe langa cea deja cunoscuta (23) (sectiunea
3); (4) tipurile 26 si 52 confirmate fara niciun bookkeeping `crsarticole`, inchizand punctul lasat
deschis de raportul S4 punctul 2 (sectiunea 1, nota de subsol pe tabel).
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle (numai
`Read`/`Grep`/`vfp_symbols.ps1` pe fisiere de pe disc).

View File

@@ -1,321 +0,0 @@
# Cercetare — Verificarea 4: descarcare de gestiune pe proforma (Oracle)
Investigatie read-only, 10.08.2026. Nicio modificare de cod, niciun `git_sync.ps1`. Continua
`docs\cercetare\proforma_copiere_puncte_intrare.md` (nu il reia) si raspunde punctual la
"Necunoscute ramase" #1 de acolo.
**Nota sursa Oracle**: `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (mentionat in brief) nu
exista in `docs\`. Fisierul real e la
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (823 337 octeti,
confirma marimea asteptata). Am semnalat discrepanta catre team-lead prin mesaj si am continuat pe
acest fisier — **toate citatele `PACK:linie` de mai jos sunt pe fisierul din `DATABASE`, nu pe unul
din `docs\`**, pentru ca al doilea nu exista.
## Verdict (raspuns la intrebarea 2)
**NU — la emiterea unei proforme, gestiunea nu se descarca**, pentru niciun articol, indiferent daca
articolul e cu adevarat gestionabil in nomenclator sau nu. Blocajul e prin design: VFP marcheaza
toate liniile unei proforme "negestionabile" inainte de compunerea documentului, ceea ce le trimite
la server cu sentinela `id_gestiune = -1000` (dedus, nu confirmat linie-cu-linie — vezi sectiunea
"Ce nu s-a putut stabili"), iar pe Oracle `contabilizeaza_articol` sare apelul catre
`descarca_gestiune` exact pe acest sentinel. Concluzia se sprijina si pe intentia de business
explicita din changelog (12.03.2021 / 2.7.x): *"Proforma. Articolele din proforma sunt marcate
'negestionabile', astfel incat nu mai este necesara existenta articolelor in stoc pentru a genera
proforma."* — adica cerinta initiala a fost explicit "proforma trebuie sa mearga si fara stoc",
nu doar "nu arata plafonul".
---
## 1. Tipurile de document "proforma"
Deja stabilit in `proforma_copiere_puncte_intrare.md` §1 si reconfirmat aici pe partea Oracle:
proforma **nu** e o valoare in `TIP` (`pack_facturare.ntip`, 1-52) — e un atribut ortogonal.
- **VFP**: `poDate.nIdTipDoc = 23` (fata de `5` = FACTURA), setat din combo-ul "Tip document"
(`COMUN\clase\ofacturare.vc2:9396-9403`, `:8745-8754`). Setter-ul deriva boolean-ul
`poDate.eProforma`: `COMUN\programe\ofacturare_comun.prg:593-599` —
`This.eProforma = IIF(m.tnIdTipDoc = This.nIdTipDocProforma, 1, 0)`.
- **Oracle**: nu exista `nIdTipDoc` — documentul se scrie in `VANZARI` cu `TIP` normal (business
type, 1-52), iar `EPROFORMA` e o coloana separata pe `VANZARI`. Dovada directa:
`PACK:5637-5671` (`PROCEDURE scrie_proforma`) — scrie intai antetul cu `scrie_in_vanzari` (acelasi
helper ca la o factura normala), apoi:
```
PACK:5666-5669
-- completez vanzari.eproforma
update vanzari
set eproforma = 1
where id_vanzare = pack_facturare.nid_vanzare;
```
Deci pe Oracle, proforma **e** o factura normala in `VANZARI`/`VANZARI_DETALII`, cu un flag in
plus.
- Exista si un mecanism **legacy**, tabele separate `PROFORME`/`PROFORME_DETALII`
(`PACK:5674-5767`, `PROCEDURE scrie_proforma_old` / `sterge_proforma_old`, `:5413-5429`) — nefolosit
de fluxul curent (VFP apeleaza `scrie_proforma`, nu `scrie_proforma_old`; nu am gasit niciun apel
VFP catre varianta `_old` in `COMUN\programe\*.prg`). Tratati ca schela moarta, nu ca mecanism activ.
- `V_TIP = -102` in `citeste_setari_document` (`PACK:1960-1994`, ramura `:1985-1987`,
`V_VARNAME := 'ID_FDOC_PROFORMA'`) e un cod folosit **doar** pentru alocarea de serie/numar
(`id_fdoc`), apelat din VFP la `initializeaza_setari_document(-102)`
(`COMUN\clase\ofacturare.vc2:9415`, deja in cercetarea anterioara) — nu are legatura cu
`pack_facturare.ntip`, care ramane tipul de business real al documentului.
## 2. Descarcarea de gestiune la proforma — DA/NU si mecanismul exact
**NU.** Trasat pe trei straturi, VFP si Oracle:
### 2.1 VFP: articolele devin "negestionabile" inainte sa intre pe document
`COMUN\programe\ofacturare.prg:330-336` (in `factureaza`, imediat dupa incarcarea cursorului sursa
`crsarticole`, indiferent de sursa — lista de preturi, comanda, contract, aviz, retur):
```
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
* 12.03.2021
IF poDate.eProforma = 1
UPDATE (m.lcCursor) SET gestionabil = 0
GO TOP IN (m.lcCursor)
ENDIF
```
`gestionabil = 0` se propaga in `crsfactura` prin `prelucreaza_facturacrs`
(`COMUN\programe\ofacturare_comun.prg:1801-1810`, coloana `gestionabil` e in lista de INSERT).
La adaugarea/editarea unei linii pe formular, ramura pe `gestionabil` decide dialogul:
`COMUN\clase\ofacturare.vc2:13803-13809`:
```
Case (poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45) && 45 = ROARESTAURANT
ofrmadarticol = Createobject("frm_articol_factura", lnCantitateMax, .F.)
```
adica **acelasi dialog folosit pentru orice articol negestionabil in restul aplicatiei** (nu unul
proforma-specific), care ocoleste `do_alege_stoc` (dialogul de alegere lot din stoc). Pentru
articolele care intra pe `frm_articol_factura`, `id_gestiune` implicit e sentinela `-1000`
(`COMUN\clase\ofacturare.vc2:13618-13623`, `do_initializeaza_articol`:
`If Type('toArticol.id_gestiune') = "U" THEN AddProperty(toArticol,'id_gestiune',-1000)`; acelasi
tipar la `:17836-17837`). La scriere, `poArt.id_gestiune` se trimite direct ca parametru
`V_ID_GESTIUNE` catre `pack_facturare.adauga_articol_factura`
(`COMUN\clase\ofacturare.vc2:14069-14073`: `... + Nvl(Alltrim(Str(poArt.id_gestiune)),[NULL]) + ...`).
### 2.2 Oracle: `id_gestiune = -1000` e sentinela care blocheaza descarcarea
`adauga_articol_factura` (`PACK:4989-5284`), primeste `V_ID_GESTIUNE` si il traduce:
```
PACK:5032-5034
IF V_ID_GESTIUNE <> -1000 THEN
V_ID_GESTIUNE2 := V_ID_GESTIUNE;
END IF;
```
`V_ID_GESTIUNE2` (necompletat, deci `NULL`, cand `V_ID_GESTIUNE = -1000`) e cel scris efectiv in
`VANZARI_DETALII_TEMP.ID_GESTIUNE` (`PACK:5237,5267`).
La emitere, `contabilizeaza_articol` (`PACK:7173-7547`) parcurge liniile din
`VANZARI_DETALII_TEMP` si apeleaza `descarca_gestiune` doar aici:
```
PACK:7472-7476
IF pack_facturare.ntip <> 4 THEN
IF pack_facturare.nscadere_stoc = 1 AND
detalii_articol.id_gestiune <> -1000 AND
detalii_articol.in_stoc = 1 THEN
pack_facturare.descarca_gestiune(...)
```
`NULL <> -1000` evalueaza la `NULL` in PL/SQL (nu `TRUE`), deci conditia pica indiferent de
`in_stoc` — **liniile de pe o proforma nu ajung niciodata la `descarca_gestiune`**, cata vreme
`id_gestiune` a intrat ca `-1000`.
Exista si o a treia bariera, independenta, **in interiorul** lui `descarca_gestiune`
(`PACK:7648-7797`), dar aceasta priveste articolul insusi, nu documentul:
```
PACK:7789-7797
-- NU SE DESCARCA GESTIUNEA PENTRU ARTICOLELE NEGESTIONABILE
-- ASTFEL INCAT SA SE POATA FACE OPERATII GEN AVIZ DIN CUSTODIE INCLUSIV CU ARTICOLE NEGESTIONABILE
SELECT MAX(IN_STOC) INTO lnInStoc FROM NOM_ARTICOLE WHERE ID_ARTICOL = V_ID_ARTICOL;
IF lnInStoc = 0 THEN GOTO SFARSIT; END IF;
```
Aceasta verifica `NOM_ARTICOLE.IN_STOC` real (nomenclator), nu flagul de proforma — protejeaza un
articol cu adevarat negestionabil, indiferent de tipul documentului. **Nu** e mecanismul care
protejeaza proforma; mecanismul de proforma e strict gate-ul `id_gestiune <> -1000` de la 2.1-2.2.
### 2.3 `in_stoc` trimis de VFP: uneori ignorat de Oracle, dar nu e el gate-ul decisiv
`V_IN_STOC_TEMP` (parametrul care ajunge in `VANZARI_DETALII_TEMP.IN_STOC`) e **fie** preluat direct
de la VFP (`V_IN_STOC := V_IN_STOC_TEMP`, ramurile implicita si restaurant, `PACK:5200-5203,
5111-5112`), **fie recalculat de Oracle din nomenclator/contract**, ignorand ce a trimis VFP, pe
ramurile comenzi (`PACK:5061-5066`), avize (`:5086-5091`) si contract cu pret de contract
(`:5153-5158, 5184`). Asta inseamna ca pentru o proforma facuta din comanda/aviz/contract, campul
`IN_STOC` scris efectiv poate reveni la valoarea reala din nomenclator (`1` pentru un articol
gestionabil), **dar** asta nu conteaza — gate-ul din 2.2 cere `id_gestiune <> -1000 AND in_stoc = 1`
cu **AND**, iar `id_gestiune` ramane blocat la `-1000`/`NULL` indiferent de sursa documentului
(sentinela vine din UI, la nivelul liniei, nu din cursorul de incarcare). Deci recalcularea lui
`in_stoc` de catre Oracle pe aceste ramuri nu redeschide descarcarea.
## 3. De la proforma la factura
Nu exista o rutina de "transformare" dedicata — mecanismul e **copierea**, deja documentata complet
in `proforma_copiere_puncte_intrare.md` §2 (tooltip explicit `COMUN\clase\ofacturare_comun.vc2:1409`:
*"Se foloseste si pentru generarea unei facturi din proforma prin copiere"*).
- **Punct de intrare VFP**: `frm_facturi.But_copiaza1.Click -> do_copiaza` (degradeaza tipul de
business, `COMUN\clase\ofacturare_comun.vc2:3628-3713`) `-> copiere_factura`
(`COMUN\programe\oproceduri_facturare.prg:150-153`) `-> factureaza(tip_degradat, toFactura)`.
- **Punct de intrare Oracle**: acelasi `pack_facturare.adauga_articol_factura` / `scrie_in_vanzari`
ca la orice document nou — nu exista un `pack_facturare.transforma_proforma` sau echivalent.
Singurul apel Oracle specific copierii e cursorul de precompletare,
`pack_facturare.cursor_retur_document` (vezi §4), apelat cu `V_COPIERE = 1`
(`COMUN\programe\ofacturare.prg:267-268`).
- `completeaza_setari_document(toDateAnterior, .T.)` (`COMUN\programe\ofacturare_comun.prg:362-412`)
**nu propaga `nIdTipDoc`** (linia comentata, `:370`) — documentul nou porneste cu `nIdTipDoc`
implicit (`5`=FACTURA sau `6`=AVIZ, dupa `tnTip` degradat, `COMUN\programe\ofacturare.prg:187-196`),
deci **`poDate.eProforma = 0` pentru documentul nou**, indiferent ca sursa era proforma.
## 4. Ce se intampla cu stocul intre proforma si factura
**Nimic de reconciliat, pentru ca proforma nu a atins niciodata stocul** (§2). Nu exista concept de
"rezervare de stoc" pe proforma:
- tabelele legacy `PROFORME`/`PROFORME_DETALII` (§1) nu au coloane de rezervare si oricum nu sunt pe
calea activa;
- nu am gasit, in `PACK_FACTURARE`, niciun apel care sa insereze in `RUL` sau sa actualizeze
`STOC` la `scrie_proforma` — funcita se limiteaza la `scrie_in_vanzari` + `UPDATE vanzari SET
eproforma=1` (§1, `PACK:5637-5671`);
- cautare directa in fisierul PACK pentru orice mentiune de rezervare pe proforma (`rezerv`,
`blocheaza stoc`) nu a dat rezultate relevante (v. "Ce nu s-a putut stabili" pentru limitele
cautarii text simple pe un fisier de 823 KB).
**La copiere (transformarea efectiva in factura)**, gestionabilitatea reala se **restaureaza**:
cursorul de precompletare la copiere, `cursor_retur_document`
(`PACK:3949-4000`), calculeaza `GESTIONABIL` asa:
```
PACK:3993-4000
(case
when V_PROFORMA = 1 then 0
when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE.IN_STOC, articolul real
else A.GESTIONABIL
end) AS GESTIONABIL,
```
La copiere, `V_COPIERE = 1` e hardcodat (`COMUN\programe\ofacturare.prg:267-268`:
`cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)` — al treilea
parametru pozitional `1` e `V_COPIERE`), iar `V_PROFORMA` trimis e `poDate.eProforma` **al
documentului nou**, care e `0` (§3) — deci ramura `V_PROFORMA=1` nu se activeaza la copiere, si
`GESTIONABIL = B.IN_STOC` (flagul real din nomenclator). Rezultat: liniile copiate dintr-o proforma
redevin gestionabile normal daca articolul chiar e in stoc, trec prin `do_alege_stoc` la editare
(`ofacturare.vc2:13804`, ramura `Otherwise`), primesc un `id_gestiune` real, si **descarcarea de
gestiune se face normal la emiterea facturii rezultate din copiere** — exact ca la orice factura noua,
nu printr-un pas separat de "transformare".
## 5. Ce verifica serverul la descarcare, si cu ce eroare
Verificari observate direct in `PACK_FACTURARE`, toate independente de proforma (se aplica oricarei
descarcari reale de gestiune):
- **Gestiune inexistenta/stearsa**: `PACK:7847-7858` — `SELECT ... FROM NOM_GESTIUNI WHERE
ID_GESTIUNE = V_ID_GESTIUNE AND STERS = 0`; `NO_DATA_FOUND -> FACT-007`.
- **Stoc epuizat / lot inexistent**: `PACK:7860-8493` — construieste `tab_stoc` din `RUL`/`STOC` dupa
criterii (articol, gestiune, cont, serie, pret etc.) si, daca nu gaseste nimic
(`SQL%ROWCOUNT = 0`): `FACT-008` ("Articolul ... nu mai e in stoc") sau, daca nici macar
denumirea nu se gaseste, `FACT-009`.
- Ramuri similare mai jos in acelasi fisier pentru alte cazuri de gestiune/combinatii invalide:
`FACT-010` (`:10001`), `FACT-011` (`:10323`), `FACT-014` combinatie invalida de gestiuni
(`:12127`), `FACT-019` gestiune (`:10449`), `FACT-020`/`FACT-021` variante "nu mai e in stoc"
(`:10748,10752`).
- **Optiune globala de bypass**: `RF_FACTURARE_FARA_STOC` (`PACK:7783-7784`,
`lnFacturareFaraStoc`) — permite facturarea peste cantitatea disponibila pentru articole
gestionabile; **nu e specifica proformei**, e o optiune de firma generala.
- **Cota TVA lipsa / articol fara politica de pret** — nu sunt verificari de stoc, dar sunt cele mai
frecvente erori vecine (`FACT-024` la `contabilizeaza_articol`, `PACK:7278-7302`; `FACT-018`,
`FACT-012`, `FACT-013` la cautarea cotei TVA in `adauga_articol_factura`, deja semnalate in
`plan_13_unificare_formular_facturare.md` §G). O proforma cu `id_gestiune=-1000` **nu trece deloc**
prin verificarile de stoc de mai sus (FACT-007/008/009/010/011/014/019/020/021), pentru ca nu ajunge
la `descarca_gestiune` — dar tot trece prin verificarea de politica de pret / cota TVA, care nu are
legatura cu stocul.
## 6. Consecinte pentru S5b — constrangeri de proiectare
1. **Formularul unificat trebuie sa reproduca exact mecanismul `gestionabil=0` la incarcarea
liniilor cand `poDate.eProforma = 1`** — nu doar sa ascunda vizual plafonul. Fara acest pas,
articolele gestionabile ar intra pe document cu `id_gestiune` real si ar declansa
`descarca_gestiune` la emitere, contrazicand comportamentul de azi si cerinta de business
("proforma merge fara stoc").
2. **Nu se apeleaza `do_alege_stoc` (dialogul de alegere lot) pentru liniile unei proforme.** Traseul
corect e cel al articolului negestionabil (`frm_articol_factura`/`do_initializeaza_articol`), care
garanteaza `id_gestiune = -1000` pe linie.
3. **`id_gestiune = -1000` trebuie sa ajunga efectiv in parametrul `V_ID_GESTIUNE` trimis catre
`pack_facturare.adauga_articol_factura`** — verificarea de gate e strict pe aceasta valoare
(`<> -1000`), nu pe un flag de document. Daca formularul unificat schimba felul in care
construieste liniile (de ex. reutilizeaza un obiect de linie comun facturii si proformei), acest
`-1000` trebuie sa fie explicit setat pe ramura `eProforma=1`, nu mostenit implicit.
4. **`in_stoc`/`gestionabil` trimis de VFP nu e suficient de la sine** — pe unele surse (comanda,
aviz, contract cu pret de contract) Oracle il **rescrie** din nomenclator/contract (§2.3). Gate-ul
real e `id_gestiune`, deci formularul unificat nu poate conta pe faptul ca a trimis `in_stoc=0`;
trebuie sa garanteze `id_gestiune=-1000`.
5. **La copiere proforma -> factura, comportamentul trebuie sa fie opus**: liniile trebuie sa-si
recapete gestionabilitatea reala (`B.IN_STOC` din nomenclator), nu sa ramana blocate la
negestionabil. Decizia deja luata in plan ("degradarea de tip din `do_copiaza` ramane pentru
copiere si nu se aplica la regenerare") e consistenta cu asta, dar merita spus explicit: **regula
`eProforma=1 -> gestionabil=0` se aplica doar la incarcarea/compunerea unei proforme noi, nu si la
copierea din ea** — documentul nou pleaca cu `eProforma=0` (§3-4) si trebuie sa lase Oracle sa
recalculeze `GESTIONABIL` normal.
6. **Formularul unificat nu trebuie sa implementeze nicio logica de "eliberare stoc rezervat" la
trecerea proforma -> factura** — nu exista rezervare de stoc pe proforma (§4), deci nu exista
nimic de eliberat. Riscul tehnic real ramane cel deja semnalat in plan §G (ordinea
stergere-inaintea-reemiterii in aceeasi tranzactie la regenerare), care e independent de proforma.
7. **Verificarile de stoc (`FACT-007/008/009/010/011/014/019/020/021`) nu se vor manifesta niciodata
pentru o linie de proforma** cata vreme regula #1-#3 e respectata — formularul unificat nu are
nevoie de tratament special pentru aceste coduri de eroare pe ramura proforma (nu pot aparea
acolo), dar tot trebuie sa trateze erorile de politica de pret/TVA (`FACT-012/013/018/024`), care
raman valabile si pe proforma.
## 7. Copierea proformei in formularul unificat — ce lipseste
Plecand de la `proforma_copiere_puncte_intrare.md` §2 (traseul general de copiere, deja complet
documentat) si de la S5b din plan (`plan_13_unificare_formular_facturare.md:1674-1689`):
- **Ce exista deja si se reutilizeaza neschimbat**: `do_copiaza` (degradarea de tip),
`copiere_factura`, `factureaza(tip, toFactura)`, `completeaza_setari_document`,
`cursor_retur_document` cu `V_COPIERE=1`. Niciunul din aceste puncte de intrare nu are legatura
speciala cu proforma dincolo de parametrul `V_PROFORMA` deja tratat corect (§4) — nu trebuie
adaugat nimic nou aici pentru ca formularul unificat sa suporte copierea unei proforme.
- **Ce lipseste, specific formularului unificat, nu copierii in sine**: mecanismul `crsarticole`
intreg (incarcarea de masa + `UPDATE ... SET gestionabil=0`, §2.1) presupune un cursor complet
incarcat inainte de afisare. Planul (`plan_13_unificare_formular_facturare.md:1470`) stabileste deja
ca `crsarticole` **nu se mai incarca in masa** in formularul unificat — deci pasul "seteaza
gestionabil=0 pe toate liniile cand eProforma=1" **trebuie reimplementat linie-cu-linie**, la
momentul in care fiecare linie e adaugata pe formularul unificat (fie la copiere, fie la compunere
noua), nu ca un singur `UPDATE` de masa pe un cursor care nu mai exista. Constrangerea #1-#3 de mai
sus (sectiunea 6) e exact specificatia acestui pas lipsa.
- **Combo-ul de tip document si realocarea de serie** — deja acoperite de decizia S5b din plan
(`Ct_clb_fdoc` ramane, realocare la comutare); nu am gasit nimic suplimentar de adaugat aici fata
de ce e deja scris in plan.
## Ce nu s-a putut stabili
1. **Linia exacta unde `poArt.id_gestiune` devine efectiv `-1000` pentru o linie de proforma**, in
loc de a ramane la valoarea implicita de camp (`0`, cursorul `crsfactura` creat cu `id_gestiune
N(20)` fara `NULL`, iar `prelucreaza_facturacrs` nu include `id_gestiune` in lista sa de INSERT —
`COMUN\programe\ofacturare_comun.prg:1801-1810`). Am gasit sentinela `-1000` setata **conditionat**
("daca proprietatea lipseste") in `do_initializeaza_articol`
(`COMUN\clase\ofacturare.vc2:13618-13623`), dar nu am confirmat ca proprietatea chiar "lipseste"
(`Type = 'U'`) in momentul in care `frm_articol_factura` proceseaza o linie de proforma provenita
din `Scatter` pe `crsfactura`. **Argument indirect, nu dovada directa**: daca `id_gestiune` ar
ajunge `0` (nu `-1000`) la server, `descarca_gestiune` ar cauta `NOM_GESTIUNI WHERE ID_GESTIUNE=0`
si ar arunca `FACT-007` la fiecare emitere de proforma cu articol gestionabil din comanda/aviz —
eroare care ar fi vizibila si raportata de ani (proforma exista din 2014+ in changelog); absenta
oricarei asemenea raportari sustine indirect ca sentinela `-1000` chiar ajunge la server, dar nu e
o dovada pe cod.
2. **Nicio verificare pe date vii** — tot ce e mai sus e trasare de cod static (VFP text + PL/SQL
text), nu rulare/log real. Nu am rulat nimic, conform mandatului read-only.
3. **Cautarea de "rezervare de stoc pe proforma"** (§4) s-a facut prin grep text simplu pe
`PACK_FACTURARE` dupa cuvinte cheie (`rezerv`, `PROFORMA`) — nu e o dovada de completitudine
pentru intreaga baza de date Oracle (declanșatoare/triggere pe `VANZARI`/`VANZARI_DETALII`,
proceduri din alte pachete). Zero rezultate nu inseamna cu certitudine ca nu exista niciun
mecanism de rezervare in alta parte a schemei.
4. **Fisierul `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`** nu a fost creat de mine — am lucrat
pe originalul din `DATABASE\SCRIPTURI_CLAR`. Daca cineva copiaza ulterior fisierul in `docs\` cu
alta numerotare de linii, citatele `PACK:linie` din acest raport trebuie re-verificate pe copia
noua.
## Verificari recomandate pe date vii (daca raman intrebari)
- Emis o proforma de test cu un articol **cu adevarat gestionabil si cu stoc real**, sursa = comanda
(ramura unde Oracle rescrie `IN_STOC`, §2.3) — confirma ca `RUL`/`STOC` nu se modifica dupa emitere
si ca `VANZARI_DETALII.ID_GESTIUNE` a fost scris `NULL` pentru acea linie.
- Acelasi test, sursa = lista de preturi (ramura unde Oracle are incredere in `V_IN_STOC_TEMP`) —
pentru comparatie.
- Copiaza proforma de mai sus in factura si confirma ca `VANZARI_DETALII.ID_GESTIUNE` al facturii
rezultate e populat cu o gestiune reala si ca `RUL` inregistreaza descarcarea la emiterea facturii.
- Interogare directa pe schema pentru triggere/joburi legate de `EPROFORMA` sau de rezervare de stoc,
daca exista suspiciunea din punctul 3 de mai sus:
`SELECT trigger_name, table_name FROM user_triggers WHERE table_name IN ('VANZARI','VANZARI_DETALII','STOC','RUL');`

View File

@@ -1,610 +0,0 @@
# Proiectare S5b — proforma si copierea pe formularul unificat
Cercetare + proiectare READ-ONLY (fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara
commit, fara scriere Oracle — numai `SELECT`), pentru povestea **S5b** din
`docs\plan_13_unificare_formular_facturare.md` (sectiunea `#### S5b`, liniile 2507-2530), deciziile
10 si 11. Continua, fara sa reia, `docs\cercetare\s5b_proforma_descarcare_gestiune.md` (sursa de
adevar pentru mecanismul `gestionabil=0` / `id_gestiune=-1000`) si foloseste rezultatele din
`docs\cercetare\s4e_lista_preturi_pe_sursa.md` (coliziunea `id_c`) si
`docs\cercetare\s4_punct2_registru_cantitate_ramasa.md` (Rol A/B ale `crsarticole`).
**Status: cercetare + proiectare incheiate.**
Nu se ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`
(perimetrul altei sarcini). `COMUN\programe\ofacturare.prg` si `ofacturare_comun.prg` sunt citite,
nu editate — la fel `COMUN\clase\ofacturare.vc2` si `COMUN\clase\ofacturare_comun.vc2` (citire, nu
editare; scriere interzisa doar pe al doilea).
---
## Descoperire centrala, care rescrie premisa de risc a lui S5b
Sursa de adevar anterioara (`s5b_proforma_descarcare_gestiune.md`) a stabilit ca gate-ul pentru
proforma e sentinela `id_gestiune = -1000`, verificata **in interiorul** lui
`contabilizeaza_articol` (`PACK:7472-7476`). Cercetarea de fata a gasit un strat **mai devreme si
mai tare**: la `Termina`, `do_scrie_factura` **alege intre doua proceduri Oracle diferite** dupa
`poDate.eProforma`, inainte sa se uite la `poDate.tip` (`COMUN\clase\ofacturare.vc2:14282-14300`):
```
Do Case
Case poDate.eProforma = 1
* scrie_proforma are aceiasi parametri ca scrie_factura2
* salveaza doar in vanzari, nu si in contabilitate
lcSql = [{call pack_facturare.scrie_proforma(...)}]
Case poDate.Tip = 4
... lcSql = [{call pack_facturare.scrie_factura_avize(...)}]
Case Inlist(poDate.Tip,3,21,25,28,42,47)
... lcSql = [{call pack_facturare.scrie_factura2(...)}]
Otherwise
... lcSql = [{call pack_facturare.scrie_factura2(...)}]
Endcase
```
Comentariul din cod ("salveaza doar in vanzari, nu si in contabilitate") e literal adevarat, verificat
pe corpul Oracle: `pack_facturare.scrie_proforma` (`PACK:5637-5671`) cheama **doar**
`scrie_in_vanzari` (`PACK:13488-13760`) — care face `INSERT INTO VANZARI` (antet) **si**
`INSERT INTO VANZARI_DETALII ... SELECT FROM VANZARI_DETALII_TEMP` (liniile, cu `ID_GESTIUNE` asa
cum a ajuns in `TEMP`) — apoi marcheaza `VANZARI.EPROFORMA=1`. **`scrie_proforma` nu cheama
niciodata `contabilizeaza_articol`.** `scrie_factura2`/`scrie_factura_avize`/`scrie_aviz_retur` sunt
singurele trei proceduri care cheama `contabilizeaza_articol` (confirmat si in
`docs\plan_13_unificare_formular_facturare.md:298-310`, runda 11) — si niciuna din ele nu ruleaza
pentru `eProforma=1`.
**Consecinta:** pentru o proforma, `scrie_nota` (care scrie `NOTE_CONTABILE`) si `descarca_gestiune`
nu ruleaza **deloc** — nu pentru ca sentinela `-1000` le blocheaza pe fiecare linie (desi si asta
ramane adevarat, ca plasa suplimentara), ci pentru ca **intreaga functie care le cheama nu se
executa**. Sentinela `-1000` din `contabilizeaza_articol` conteaza doar cand `contabilizeaza_articol`
chiar ruleaza — adica exact pe drumul invers (vezi sectiunea 5), nu pe proforma insasi.
**De ce conteaza pentru S5b**: alegerea `scrie_proforma` vs. `scrie_factura2` se face **o singura
data, la Termina**, citind `poDate.eProforma` **in acel moment** — nu depinde de cand/cum a fost
comutat combo-ul, nici de valorile `gestionabil`/`id_gestiune` de pe liniile individuale. Asta e o
veste buna pentru un sens al comutarii (proforma -> ramane proforma la salvare: corect, indiferent ce
au liniile), dar **nu acopera** sensul opus (liniile marcate negestionabil sub proforma, apoi
documentul comutat inapoi la factura inainte de Termina) — acolo `scrie_factura2` **chiar** ruleaza,
si atunci sentinela `-1000` de pe liniile ramase de la proforma **chiar blocheaza** `descarca_gestiune`
pe un document care ar trebui sa descarce. Vezi sectiunea 5.
---
## 1. Fluxul de azi al proformei, cap-coada
**1.1 Alegerea tipului.** Proforma nu e o valoare in `pack_facturare.ntip` (tipul de business,
1-52) — e un atribut ortogonal, `poDate.nIdTipDoc` (5=FACTURA, 23=PROFORMA, 3=BON FISCAL,
6=AVIZ), setat din combo-ul "Tip document" (`Ct_clb_fdoc._combobox1`,
`RowSource = "FACTURA,PROFORMA,BON FISCAL"`, `ofacturare.vc2:8745-8754`). Setter-ul
`nIdTipDoc_Assign` (`COMUN\programe\ofacturare_comun.prg:593-600`) deriva boolean-ul in acelasi
moment: `This.eProforma = IIF(m.tnIdTipDoc = This.nIdTipDocProforma, 1, 0)`.
**1.2 Marcarea in masa la incarcarea cursorului.** In `factureaza()` (`COMUN\programe\ofacturare.prg`),
dupa ce cursorul sursa (indiferent care — lista de preturi, comanda, contract, aviz, retur) e adus in
`crsarticole` (Do Case pe `tnTip`, `:266-311`), **daca** `poDate.eProforma = 1`, se face un `UPDATE`
de masa peste tot cursorul (`:330-334`):
```
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
IF poDate.eProforma = 1
UPDATE (m.lcCursor) SET gestionabil = 0
GO TOP IN (m.lcCursor)
ENDIF
```
Acest pas ruleaza **o singura data**, la incarcare, **inainte** ca formularul de linii sa se
deschida — pentru ca la momentul lui `poDate.eProforma` e deja finala: combo-ul traieste in
`frm_date_factura`/`frm_date_aviz`, un dialog modal care se **inchide** (`ofrmceredate.Show()` la
`:235`, urmat de `Release ofrmceredate` la `:248`) **inainte** ca `Do Case`-ul de incarcare a
cursorului sa ruleze (`:266` e dupa `:248`). Tipul nu se mai poate schimba dupa acest punct, azi.
**1.3 Ce face `gestionabil=0` la nivel de linie.** Cand operatorul adauga o linie, ramura care alege
dialogul citeste exact acest camp (`ofacturare.vc2:13803-13809`):
```
Do Case
Case (poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45)
ofrmadarticol = Createobject("frm_articol_factura", lnCantitateMax, .F.)
Otherwise
Thisform.do_alege_stoc(...)
Endcase
```
`frm_articol_factura` e dialogul generic pentru orice articol negestionabil din toata aplicatia
(nu unul specific proformei) — ocoleste `do_alege_stoc` (alegerea lotului din stoc).
`do_initializeaza_articol` (`ofacturare.vc2:13618-13623`) seteaza `id_gestiune=-1000` **doar daca
proprietatea lipseste** pe obiectul articol (`Type(...) = "U"`):
```
If Type('toArticol.id_gestiune') = "U"
AddProperty(toArticol,'id_gestiune',-1000)
Endif
```
La scriere, `poArt.id_gestiune` merge direct ca `V_ID_GESTIUNE` catre
`pack_facturare.adauga_articol_factura` (`ofacturare.vc2:14069-14073`), care il traduce
(`PACK:5032-5034`: `IF V_ID_GESTIUNE <> -1000 THEN V_ID_GESTIUNE2 := V_ID_GESTIUNE; END IF;` —
altfel ramane `NULL`), scris in `VANZARI_DETALII_TEMP.ID_GESTIUNE`.
**Nota de incertitudine, mostenita din raportul-sursa**: linia exacta unde proprietatea
`id_gestiune` "lipseste" (`Type='U'`) pentru o linie de proforma scatter-uita din `crsfactura` nu a
fost confirmata direct pe date vii — `crsfactura` are `id_gestiune` ca si camp cu valoare implicita,
nu absent. Ramane argument indirect (fara `FACT-007` raportat in productie de ani), nu dovada de
executie. **De verificat cu prioritate la implementare**, pentru ca proiectarea de mai jos (sectiunea
4) muta acest mecanism dintr-un `UPDATE` de masa intr-o marcare per-linie, si trebuie sa stie exact
ce camp seteaza.
**1.4 Routingul la Termina.** Vezi "Descoperirea centrala" de mai sus: `scrie_proforma`, nu
`scrie_factura2`/`scrie_factura_avize` — fara nota contabila, fara descarcare de gestiune, indiferent
de tipul de business original.
**1.5 Raportul propriu.** `listeaza_ofacturare` alege raportul dupa `poDate.eProforma`
(`ofacturare.prg:1638-1643`):
```
Case (Between(poDate.tip, 1, 20) Or Inlist(poDate.tip, -1,-2,-3,-4,-8,-11,44,45,48,49,50,51,52)) And poDate.eProforma = 1
lcRaport = [PROFORMA]
lcRaportVal = [PROFORMA_VAL]
lcSetare = [PROFORMA]
```
Independent de forma formularului (unificat sau nu) — declansat de `poDate.eProforma` la momentul
listarii, care e populata corect din `nIdTipDoc_Assign` daca antetul reflecta starea finala.
**1.6 Garzile `eProforma = 0` pe atasamente (nu pe nota contabila).** Cinci guarde separate in
`listeaza_ofacturare`, toate de forma `poDate.nRelistare = 0 And poDate.eProforma = 0 ...`, controland
salvarea PDF-ului ca atasament arhivat (`export2pdf(..., poDate.cDocAtasate)` si
`poDate.scrieAtasamente()`), **nu** scrierea notei contabile (care e blocata la alt nivel, sectiunea
precedenta): `ofacturare.prg:1960` (factura in lei), `:1996` (aviz retur), `:2052` (factura in
valuta), `:2063` (recapitulatie), `:2103` (`scrieAtasamente()`, arhivarea propriu-zisa). **Corectie
fata de formularea din plan** (`plan_13_unificare_formular_facturare.md:548-549`, "fara nota
contabila si fara atasamente, prin garzile `eProforma=0`"): cele doua efecte sunt reale amandoua, dar
prin **mecanisme diferite** — nota contabila prin routing-ul `scrie_proforma`/`scrie_factura2`
(sectiunea "Descoperire centrala"), atasamentele prin aceste cinci guarde explicite. Nu schimba nimic
pentru proiectare, dar conteaza pentru cine cauta "garda de nota contabila" in cod si nu o gaseste la
liniile citate de plan.
**1.7 Relistarea pe cale separata.** `frm_facturi.do_listare` (`COMUN\clase\ofacturare_comun.vc2:7233-`)
reconstruieste `poDate` de la zero pentru un document deja emis, cu `poDate.eProforma = 1` setat
explicit (`:7262`) cand se relisteaza o proforma din grid — nu refoloseste obiectul `poDate` din
sesiunea de facturare curenta.
---
## 2. Fluxul de azi al copierii, cap-coada
**2.1 Degradarea de tip in `do_copiaza`.** `frm_facturi.do_copiaza`
(`COMUN\clase\ofacturare_comun.vc2:3628-3713`) mapeaza **orice** tip de business catre unul din
cinci "simple" — `T1` (lista de preturi), `T10` (lista de preturi valuta), `T5` (invoice),
`T22` (aviz din lista de preturi), sau `T1`/`T10` pentru transfer/altele — pe un `Do Case` explicit
peste `loFactura.tip` (`:3693-3708`). Factura/avizul din contract sau din comanda **nu se copiaza ca
atare** — devine o factura simpla din lista de preturi. Apoi `DO copiere_factura WITH loFactura IN
oproceduri_facturare.prg` (`:3710`).
**2.2 `copiere_factura` -> `factureaza(tip, toFactura)`.** `copiere_factura`
(`COMUN\programe\oproceduri_facturare.prg:150-153`) cheama `factureaza(loFactura.tip, loFactura)` —
`toFactura` (obiectul scatter-uit din `crsFacturi`) devine parametrul opțional al lui `factureaza`
(`COMUN\programe\ofacturare.prg:81-82`), care seteaza `llCopiere = (Type('toFactura') = 'O')`
(`:111`).
**2.3 Antetul precompletat, dar `nIdTipDoc` NU se propaga.**
`poDate.completeaza_setari_document(toFactura, .T.)` (`ofacturare.prg:204`, apelat cand `m.llCopiere`)
cheama `oDateFactura::completeaza_setari_document`
(`COMUN\programe\ofacturare_comun.prg:362-412`), ramura `tlFactura=.T.` (`:368-390`): copiaza delegat,
masina, sectie, agent, client, referinta la documentul sursa (`listaid = toDateAnterior.id_vanzare`) —
dar **linia `.nIdTipDoc = toDateAnterior.nIdTipDoc` e comentata** (`:370`,
`*.nIdTipDoc = toDateAnterior.nIdTipDoc`), deliberat. Documentul nou primeste `nIdTipDoc` implicit
dupa tipul degradat (`Do Case` la `ofacturare.prg:187-196`: `tnTip < 21` sau in
`{45,48,49,51,52}` => `5` FACTURA, altfel `6` AVIZ) — pentru tipurile degradate ale copierii (`T1=1`,
`T5=5`, `T10=10`, `T22=22`), toate `<21`, deci **`nIdTipDoc=5` (FACTURA) intotdeauna**, ceea ce prin
`nIdTipDoc_Assign` face **`poDate.eProforma = 0` pe documentul nou, indiferent daca sursa era
proforma**. Confirma direct pe cod ce raportul-sursa dedusese din trasare (`s5b_proforma_descarcare_gestiune.md` §3).
**2.4 Numar nou, intotdeauna.** `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` (`:211`)
ruleaza neconditionat, indiferent de copiere — copierea nu mosteneste numarul documentului sursa,
alege intotdeauna un numar nou din seria tipului nou (`FACTURA`).
**2.5 Cursorul de linii candidate — intotdeauna `cursor_retur_document`.** `Do Case`-ul care alege
cursorul Oracle de incarcat in `crsarticole` (`ofacturare.prg:266-308`) verifica **`Case m.llCopiere`
primul**, inaintea oricarei ramuri pe `tnTip`:
```
Case m.llCopiere
lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}]
```
al treilea parametru pozitional `1` e `V_COPIERE`. Documentul sursa (proforma sau nu) intra prin
`?poDate.listaid` (setat la `toDateAnterior.id_vanzare` in §2.3). `poDate.eProforma` trimis e cel al
**documentului nou** (`0`, per §2.3), nu al sursei.
**2.6 Gestionabilitatea se restaureaza la copiere.** `cursor_retur_document`
(`PACK_FACTURARE:3949-4000`) calculeaza `GESTIONABIL` cu exact aceasta expresie (`:3993-4000`):
```sql
(case
when V_PROFORMA = 1 then 0
when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE.IN_STOC, articolul real
else A.GESTIONABIL
end) AS GESTIONABIL,
```
Cu `V_PROFORMA=0` (documentul nou nu e proforma) si `V_COPIERE=1`, ramura activa e
`GESTIONABIL = B.IN_STOC` — valoarea reala din nomenclator, **indiferent daca documentul sursa era o
proforma cu toate liniile fortate `gestionabil=0`**. Liniile copiate dintr-o proforma redevin
gestionabile normal (daca articolul chiar e in stoc), trec prin `do_alege_stoc` la adaugare, primesc
`id_gestiune` real, si descarca gestiune normal la emiterea facturii rezultate.
**2.7 Nimic auto-adaugat.** Comentariu explicit in cod (`ofacturare.prg:97-99`, in ramura
`m.llCopiere` de mai jos in aceeasi functie):
```
* Nu adaug automat articolele din factura originala, ca sa dau posibilitate de modificare a cantitatilor, pretului, explicatiei
* ofrmdetaliifactura.do_adauga_tot()
```
Liniile documentului sursa raman doar **candidate** in `crsarticole` (populat de `cursor_retur_document`
la §2.5) — operatorul le adauga manual (sau cu "adauga tot"), la fel ca la orice document nou.
**2.8 Lista de preturi se adauga peste, doar la copiere.** Imediat dupa, tot in `factureaza()`
(`ofacturare.prg:454-473`, citat integral in `docs\cercetare\s4e_lista_preturi_pe_sursa.md` §1):
`pack_facturare.cursor_preturi(...)` intr-un cursor separat `crsArticoleTemp`, apoi
`SELECT crsArticole / APPEND FROM DBF(lcCursorTemp)` — lipeste lista de preturi completa peste
candidatii din documentul sursa, ca operatorul sa poata adauga si articole noi la copiere/modificare.
Vezi sectiunea 6 pentru siguranta acestui pas.
---
## 3. Combo-ul `Ct_clb_fdoc` si realocarea de serie/numar la comutare
**Azi, combo-ul traieste exclusiv in dialogul separat `frm_date_factura`/`frm_date_aviz`**
(clasa continuta in acelasi fisier text `ofacturare.vc2`, dar formular distinct de
`frm_facturare_articole`/`frm_facturare_articole2`, care contin gridul de linii). Handler-ul e legat
pe `LostFocus`, nu pe `InteractiveChange`:
```
PROCEDURE Ct_clb_fdoc._combobox1.LostFocus && ofacturare.vc2:9857-9859
thisform.do_schimba_tipdoc()
ENDPROC
```
`do_schimba_tipdoc` (`ofacturare.vc2:9396-9438`), pas cu pas:
1. Determina `lnIdTipDoc` din textul combo-ului (`FACTURA`/`PROFORMA`/`BON FISCAL`/altfel `FACTURA`).
2. Cheama neconditionat `poDate.initializeaza_setari_document(...)` cu un cod special (`-101` bon
fiscal, `-102` proforma, sau `poDate.Tip` altfel) — realoca setarile de document (serie/numar)
pentru noul tip, inainte sa stie daca tipul chiar s-a schimbat.
3. **Iesire timpurie daca tipul nu s-a schimbat**: `IF poDate.nIdTipDoc = m.lnIdTipDoc THEN RETURN`.
4. Daca s-a schimbat: `poDate.nract = 0`, `poDate.serie_act = ""` (curata selectia locala, nesalvata
nicaieri inca), `poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc)` (**elibereaza in pool numarul
vechi**, rezervat pe tipul vechi — nu se pierde, devine disponibil pentru alt document/alta sesiune;
documentul nu are inca niciun numar scris in baza, fiind pre-Termina), apoi
`poDate.nIdTipDoc = m.lnIdTipDoc` (declanseaza `nIdTipDoc_Assign`, care actualizeaza
`eProforma`/`eBonFiscal`), si `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` reincarca
seriile disponibile pentru noul tip, populand `clb_serie_act`.
5. **Numarul vechi nu se "reia" automat** — utilizatorul trebuie sa aleaga din nou serie/numar pentru
noul tip din controlul `clb_serie_act`, care s-a reincarcat la pasul 4.
**Ce NU atinge `do_schimba_tipdoc` azi**: `crsarticole`, `crsfactura`, `gestionabil`, `id_gestiune` —
nimic legat de linii. **Motivul e structural, nu o omisiune**: azi comutarea e posibila **doar**
inaintea oricarei linii, pentru ca `frm_date_factura` (unde traieste combo-ul) e un dialog modal
care se inchide (`Release ofrmceredate`, `ofacturare.prg:248`) **inainte** ca Do Case-ul de incarcare
a cursorului de linii sa ruleze (`:266`) — comutarea si incarcarea liniilor nu pot fi simultane azi.
**Ce se schimba cand combo-ul traieste in formularul unificat, cu gridul deja prezent**: exact
premisa care dispare — combo-ul devine comutabil **si dupa** ce linii exista deja in `crsfactura`.
`do_schimba_tipdoc` ramane corect pentru partea de serie/numar (pasii 1-5 de mai sus se aplica
identic, indiferent daca gridul are linii sau nu — realocarea de serie e independenta de continutul
documentului). **Ce lipseste e pasul 1.2/1.3 de mai sus (marcarea `gestionabil=0`/`id_gestiune=-1000`),
care azi nu are nevoie sa fie legat de comutare pentru ca nu poate fi comutare cu linii deja
prezente.** Vezi sectiunea 4.
---
## 4. Ce se rupe la unificare
**4.1 Marcarea negestionabil nu mai are un singur moment de executie.** Azi exista un singur loc
(`ofacturare.prg:330-334`, dupa incarcarea cursorului, inaintea deschiderii gridului) unde
`eProforma=1` implica `gestionabil=0` in masa. In formularul unificat, cu combo-ul viu in acelasi
ecran cu gridul (decizia 10), sunt **trei momente distincte** care trebuie sa garanteze acelasi
invariant, nu unul:
a. **La deschiderea initiala**, daca formularul porneste direct pe tip Proforma (echivalent cu azi
— se poate pastra `UPDATE` de masa pe cursorul candidat, daca inca exista un cursor de masa
pentru sursa respectiva dupa S4/S4e; pe sursele deja mutate pe cautare filtrata/`APPEND BLANK`
— S4e — nu mai exista cursor de masa de actualizat, marcarea trebuie facuta la construirea
fiecarui rand).
b. **La adaugarea unei linii noi cat timp `poDate.eProforma=1`** — deja identificat ca gol de
proiectare in `s5b_proforma_descarcare_gestiune.md` §7 ("trebuie reimplementat linie-cu-linie,
la momentul in care fiecare linie e adaugata"), confirmat aici ca valabil si pentru cazul in
care operatorul a **pornit** documentul ca proforma inainte de a adauga prima linie (nu doar
dupa un switch).
c. **La comutarea combo-ului DUPA ce linii exista deja in `crsfactura`** (cazul explicit cerut de
brief) — scenariu care azi nu poate exista, deci nu are cod de reutilizat. `do_schimba_tipdoc`
trebuie extins (sau o metoda noua chemata din el) sa parcurga liniile deja prezente in
`crsfactura` si sa aplice acelasi tratament ca la 1.2-1.3: `gestionabil=0`,
`id_gestiune=-1000`, pe fiecare linie existenta, **cand comutarea intra pe Proforma**.
*Corolarul necesar, netratat de nicio decizie/raport anterior*: pentru fiecare linie deja
adaugata prin `do_alege_stoc` (gestionabila, cu `id_gestiune` real ales de operator), acea
alegere de gestiune/lot devine irelevanta odata marcata negestionabil — de decis daca se
**pastreaza tacit** valoarea veche (inofensiv, pentru ca `id_gestiune=-1000` o inlocuieste
oricum in parametrul trimis la scriere) sau se **sterge explicit** din obiectul liniei, ca sa nu
induca in eroare un ecran care ar afisa gestiunea aleasa pe o linie acum negestionabila.
**4.2 Combo-ul viu tot timpul inseamna ca `eProforma` poate flutura de mai multe ori inainte de
Termina.** Azi tipul se alege o singura data, ireversibil (dialogul se inchide). In formularul
unificat, operatorul poate comuta Factura -> Proforma -> Factura de mai multe ori inainte de a apasa
`Termina`. Routing-ul de la Termina (sectiunea "Descoperire centrala") citeste `poDate.eProforma`
**doar la momentul apasarii** — corect pentru starea finala — dar **starea liniilor** (marcate sau nu
negestionabil, dupa istoricul comutarilor) nu se "reseteaza" singura la fiecare comutare, daca 4.1.c
nu implementeaza si drumul invers. Vezi sectiunea 5.
**4.3 Realocarea de serie/numar (sectiunea 3) ramane corecta ca atare**, dar UX-ul ei (curatarea
`clb_serie_act`, cererea catre operator sa aleaga din nou seria) trebuie sa functioneze si cu gridul
de linii vizibil pe acelasi ecran — nu identificat niciun cod care sa presupuna ca gridul e ascuns in
timpul realocarii; e o verificare vizuala, nu o problema de proiectare gasita in cod.
---
## 5. Drumul invers: proforma comutata inapoi in factura
**Acesta e riscul cel mai serios gasit in aceasta cercetare, nesemnalat explicit in rapoartele
anterioare.**
Scenariu: operatorul porneste documentul ca Proforma (sau comuta pe Proforma la un moment dat),
adauga linii — care, daca 4.1 e implementat corect, ajung marcate `gestionabil=0`/`id_gestiune=-1000`
pe `crsfactura`. Apoi, **inainte de Termina**, comuta combo-ul inapoi pe Factura.
- **`poDate.eProforma` devine `0`** prin `nIdTipDoc_Assign`, corect.
- **La Termina, routing-ul alege `scrie_factura2`/`scrie_factura_avize`** (sectiunea "Descoperire
centrala") — care **chiar cheama** `contabilizeaza_articol` pentru fiecare linie.
- **`contabilizeaza_articol` verifica exact sentinela `id_gestiune <> -1000`** (`PACK:7472-7476`)
inainte de a chema `descarca_gestiune`. Liniile ramase marcate `-1000` de cand documentul era
proforma **nu vor descarca gestiune**, desi documentul final e o factura reala, nu o proforma.
- **Nu exista azi niciun mecanism care sa refaca `gestionabil`/`id_gestiune`** cand tipul revine la
Factura in aceeasi sesiune. Singurul loc din tot codul citit unde gestionabilitatea se
"restaureaza" e `cursor_retur_document` la **copiere** (`GESTIONABIL = B.IN_STOC` cand
`V_COPIERE=1`, sectiunea 2.6) — mecanism legat de incarcarea unui cursor **nou**, la deschiderea
unui document **nou**, nu de o comutare in acelasi document, in aceeasi sesiune, pe linii deja
existente in `crsfactura`.
**Consecinta pe date**: o factura reala, emisa din formularul unificat dupa un du-te-vino prin
Proforma, poate iesi din sesiune cu stocul nedescarcat pentru articole care ar fi trebuit sa-l
descarce — silentios, fara eroare Oracle (sentinela `-1000` e o valoare valida pentru
`contabilizeaza_articol`, nu declanseaza nicio exceptie).
**Ce trebuie proiectat, obligatoriu, pentru ca decizia 10 sa fie sigura**: la comutarea **dinspre**
Proforma **catre** orice alt tip, liniile care au fost marcate negestionabil **exclusiv din cauza
proformei** (nu articole real negestionabile in nomenclator) trebuie sa-si recapete
`gestionabil`/`id_gestiune` reale, inainte de a ajunge la `Termina`. Doua variante, niciuna aleasa
inca:
a. **Re-interogare per linie** la comutare (analog cu `cursor_retur_document.GESTIONABIL`, dar
aplicat pe liniile deja din `crsfactura`, nu la incarcarea unui cursor nou) — cere un apel
Oracle (sau citire din nomenclator local, daca exista deja incarcat) per linie afectata, si
re-deschiderea alegerii de gestiune/lot pentru cele gestionabile (echivalentul lui
`do_alege_stoc`, care nu a rulat cand linia a fost adaugata sub Proforma).
b. **Blocarea comutarii inapoi** cand exista deja linii adaugate sub Proforma — cere confirmare
sau refuza tranzitia, obligand operatorul sa stearga liniile si sa le re-adauge sub noul tip.
Mai simplu de implementat, cu cost UX (utilizatorul pierde munca de introducere).
**Nu e o decizie de proiectare pe care raportul o ia in locul lui Marius** — vezi sectiunea 11,
punctul 1.
---
## 6. `id_c` la copiere — raspuns cu dovada
**Nu exista coliziune cu efect observabil pe drumul de copiere, azi.** Motivul, verificat direct pe
cod (nu presupus, extras din `s4e_lista_preturi_pe_sursa.md` §1, reconfirmat aici pentru S5b):
`ofacturare.prg:454-473` face `APPEND FROM` peste `crsArticole`, lipind lista de preturi
(`crsArticoleTemp`, numerotata `id_c` independent, cu `ROWNUM` propriu Oracle) peste continutul deja
prezent (documentul copiat, incarcat la §2.5 din `cursor_retur_document`, cu propriul `id_c`
independent). Cele doua seturi de `id_c` **se pot suprapune la nivel de valoare** — asta e adevarat,
tehnic. Dar coliziunea **nu are efect**, pentru doua motive independente, ambele verificate:
1. **`Case m.llCopiere` e prima ramura verificata** in Do Case-ul de incarcare a cursorului
(`ofacturare.prg:266-268`) — pentru orice document copiat, cursorul de baza e **intotdeauna**
`cursor_retur_document`, indiferent de tipul original. Un document copiat nu (mai) e niciodata de
tip `3` (comanda) dupa degradarea din `do_copiaza` (sectiunea 2.1: degradare catre
`1,5,7,10,22,23` — **CORECTIE runda 13**, cifra veche `1,5,10,22` era gresita; setul real e citit
din primul `CASE`, `ofacturare_comun.vc2:3693-3694`) —
deci **nu poate ajunge** in ramura `Inlist(poDate.Tip,3,21,25,28,42,47)` a lui `do_scrie_factura`
(`ofacturare.vc2:14332-14338`), care e singurul loc unde `id_c` conteaza pentru "cantitate
ramasa" (Rol A, per `s4_punct2_registru_cantitate_ramasa.md`).
2. **`do_sterge` potriveste pe `id_c`** (`ofacturare.vc2:14652-14655`), dar aceasta potrivire conteaza
doar daca ceva citeste `crsarticole` ca registru dupa aceea — ceea ce nu se intampla pe tipurile
rezultate din copiere (`1,5,7,10,22,23`, toate in ramura `Otherwise` a Do Case-ului de scriere, fara
`Calculate Sum(cantitate)`).
**Concluzie, cu aceeasi rezerva ca in raportul-sursa**: coliziunea de `id_c` exista **tehnic** in
datele din `crsArticole` dupa copiere, dar **nu are cale prin care sa produca un efect gresit**, atata
timp cat tipul rezultat din copiere ramane in setul degradat (`1,5,7,10,22,23` — niciodata
`3,21,25,28,42,47`).
**Aceasta concluzie ramane valabila neschimbata si in formularul unificat**, pentru ca nu depinde de
forma formularului — depinde doar de faptul ca `do_copiaza` degradeaza tipul inaintea oricarei
incarcari de cursor, mecanism care nu se propune sa fie schimbat de S5b (sectiunea 7).
**O singura conditie de pastrat, explicit**: daca vreo poveste viitoare schimba `do_copiaza` sa NU
mai degradeze tipul catre grupul "simplu" (de exemplu, sa permita copierea unei comenzi ca o comanda
noua, nu ca factura din lista de preturi), concluzia de mai sus **trebuie re-verificata** — riscul de
coliziune `id_c` descris in `s4e_lista_preturi_pe_sursa.md` verdict/punctul 1 ar deveni real.
---
## 7. Ce NU se atinge — lista explicita
Confirmate ca deja corecte si suficiente, fara nicio schimbare necesara pentru S5b:
1. **Sentinela `id_gestiune = -1000`** in `contabilizeaza_articol` (`PACK:7472-7476`) — ramane gate-ul
de aparare pentru orice linie care ajunge totusi la contabilizare cu aceasta valoare (drumul
invers, sectiunea 5, o foloseste ca sa NU descarce gestiune — ceea ce e exact problema de acolo,
nu ceva de "reparat" in sentinela insasi).
2. **Routing-ul `scrie_proforma` vs. `scrie_factura2`/`scrie_factura_avize`**
(`ofacturare.vc2:14282-14300`) — corect prin constructie, citeste `poDate.eProforma` la momentul
potrivit (Termina), nu are nevoie de nicio schimbare.
3. **Cele cinci garzi `eProforma=0` pe atasamente** (`ofacturare.prg:1960,1996,2052,2063,2103`) —
raman neschimbate, nu au legatura cu formularul (grid vs. dialog separat), doar cu momentul
listarii/salvarii PDF, care ramane acelasi apel indiferent de forma UI.
4. **Raportul propriu** (`PROFORMA`/`PROFORMA_VAL`, `ofacturare.prg:1638-1643`) — alegerea ramane pe
`poDate.eProforma`, neschimbata.
5. **`do_copiaza` (degradarea de tip)** — ramane exact cum e, decizia S5b (D) o cere explicit
("degradarea de tip din `do_copiaza` ramane pentru copiere si nu se aplica la regenerare").
6. **`copiere_factura` -> `factureaza(tip, toFactura)`** — punctul de intrare ramane neschimbat.
7. **`completeaza_setari_document`** — comentariul de la `:370` (`nIdTipDoc` necopiat) e deliberat si
ramane corect: documentul nou pleaca mereu ca Factura, indiferent de sursa, exact ce cere decizia
S5b (copierea deschide "document nou", cu antetul editabil, nu proforma mostenita).
8. **`cursor_retur_document` cu `V_COPIERE=1`** (`PACK:3949-4000`) — restaurarea `GESTIONABIL = B.IN_STOC`
la copiere ramane corecta si suficienta pentru cazul "copiere", fara nicio schimbare (nu e acelasi
mecanism cu comutarea in-sesiune de la sectiunea 5, care ramane un gol real).
9. **Numarul alocat la copiere e intotdeauna nou** (`ofacturare.prg:208,211`, neconditionat de
`llCopiere`) — nimic de schimbat.
10. **Alocarea de serie/numar a lui `do_schimba_tipdoc`** (sectiunea 3) — mecanismul de dealocare +
realocare ramane corect si reutilizabil ca atare in formularul unificat; doar absenta lui legata
de linii (sectiunile 4-5) e golul de acoperit.
---
## 8. Pasi de implementare, ordonati
1. **Confirma pe date reale linia exacta unde `id_gestiune` devine `-1000`** pentru o linie de
proforma (incertitudinea de la sectiunea 1.3) — headless, cu un `crsfactura` construit manual din
`Scatter` pe o linie de proforma reala, inainte de orice alta schimbare de cod.
*Gata cand:* se confirma cu certitudine (nu argument indirect) fie linia din
`do_initializeaza_articol`, fie alt punct, unde proprietatea `id_gestiune` a liniei devine
efectiv `-1000`.
2. **Extrage marcarea "linie negestionabila pentru proforma" intr-o metoda proprie**, reutilizabila
din trei locuri (sectiunea 4.1: incarcare initiala, adaugare linie noua, comutare combo pe linii
existente) — un singur loc de intretinut pentru regula `eProforma=1 => gestionabil=0,
id_gestiune=-1000`, nu trei copii.
*Gata cand:* toate cele trei puncte de intrare cheama aceeasi metoda, verificat prin grep pe
codul nou.
3. **Leaga metoda de la pasul 2 de `do_schimba_tipdoc`**, pe ramura care duce spre Proforma, aplicata
pe toate liniile deja prezente in `crsfactura` la momentul comutarii (sectiunea 4.1.c).
*Gata cand:* pe un document cu linii deja adaugate ca Factura, comutarea combo-ului pe Proforma
marcheaza `gestionabil=0`/`id_gestiune=-1000` pe fiecare linie existenta, verificabil prin citirea
directa a `crsfactura` dupa comutare.
4. **Proiecteaza si implementeaza drumul invers** (sectiunea 5) — varianta (a) sau (b), decizie a lui
Marius (sectiunea 11, punctul 1). *Gata cand:* pe un document cu linii adaugate sub Proforma,
comutat inapoi pe Factura inainte de Termina, fie liniile isi recapata `gestionabil`/`id_gestiune`
reale (varianta a, verificabil prin `descarca_gestiune` apelat corect la emitere — vezi sectiunea
9), fie comutarea inapoi e refuzata/necesita confirmare explicita (varianta b).
5. **Verifica riscul de la sectiunea 4.1, corolarul**: decide daca `id_gestiune`/lotul ales anterior pe
o linie acum negestionabila se pastreaza tacit sau se sterge din obiectul liniei — implementeaza
alegerea si documenteaz-o in cod (un rand de comentariu, per conventia proiectului).
6. **Regresie pe copiere**, fara nicio schimbare de cod asteptata (sectiunile 2, 6, 7) — pas de
verificare, nu de implementare: confirma ca formularul unificat, rulat pe drumul de copiere, produce
acelasi rezultat ca azi (sectiunea 9).
7. **Regresie pe proforma simpla** (fara comutare, document pornit direct ca Proforma) — confirma ca
pasii 2-3 nu au schimbat comportamentul cazului deja corect azi.
*Depinde de:* S5 (acoperirea tipurilor), S4/S4e (forma finala a incarcarii liniilor — pasul 2 trebuie
sa stie daca opereaza pe un cursor de masa sau pe randuri individuale, dupa ce alte povesti decid
asta pentru fiecare sursa).
---
## 9. Cum se verifica
**Proba ceruta de plan**: o proforma emisa din formularul unificat nu produce nota contabila si se
listeaza pe raportul ei; o copie produce un document nou cu numar nou si acelasi continut.
**Interogari SQL concrete** (numai `SELECT`, rulate cu unealta PowerShell + `sqlplus.exe`, per
mediul de proiect):
```sql
-- 1. Proforma emisa din formularul unificat: fara nota contabila, fara descarcare de gestiune
SELECT id_vanzare, tip, eproforma, sters FROM vanzari WHERE id_vanzare = :ID_TEST;
-- eproforma trebuie sa fie 1
SELECT COUNT(*) FROM vanzari_detalii WHERE id_vanzare = :ID_TEST;
-- trebuie sa coincida cu numarul de linii adaugate (liniile SE scriu, doar nu se contabilizeaza)
SELECT id_vanzare_det, id_articol, id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST;
-- id_gestiune trebuie sa fie NULL pe fiecare linie (sentinela -1000 tradusa de adauga_articol_factura)
SELECT COUNT(*) FROM note_contabile nc
JOIN act a ON a.id_fact = nc.id_fact -- sau echivalentul legaturii act/nota folosite in schema
WHERE a.cod = (SELECT cod FROM vanzari WHERE id_vanzare = :ID_TEST);
-- trebuie sa fie 0 -- nicio nota contabila generata
-- 2. Fara descarcare de gestiune: RUL nu are miscare noua dupa emiterea proformei
SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE AND id_articol IN (:articolele_testate);
-- trebuie sa fie identic cu inainte de emitere (0 randuri noi legate de acest document)
-- 3. Copie: document nou, numar nou, acelasi continut
SELECT id_vanzare, numar_act, serie_act, eproforma FROM vanzari WHERE id_vanzare = :ID_COPIE;
-- eproforma = 0; numar_act/serie_act diferite de documentul sursa
SELECT vd.id_articol, vd.cantitate, vd.pret, vd.id_gestiune
FROM vanzari_detalii vd WHERE vd.id_vanzare = :ID_COPIE
ORDER BY vd.id_articol;
-- comparat linie cu linie cu vanzari_detalii al documentului sursa (:ID_TEST) -- acelasi articol/cantitate/pret;
-- id_gestiune insa REAL (nu NULL), daca articolul e gestionabil -- confirma restaurarea de la sectiunea 2.6
-- 4. Drumul invers (sectiunea 5, dupa ce varianta e implementata): factura emisa dupa un du-te-vino prin Proforma
-- descarca gestiune normal, ca orice factura
SELECT id_vanzare, eproforma FROM vanzari WHERE id_vanzare = :ID_TEST_INVERS;
-- eproforma = 0
SELECT id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST_INVERS;
-- NU mai e NULL pe liniile gestionabile -- confirma ca varianta aleasa la pasul 4 (sectiunea 8) functioneaza
SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE_INVERS AND id_articol IN (:articolele_testate);
-- trebuie sa arate miscare noua -- gestiunea s-a descarcat
```
**Protocol**: fiecare interogare rulata **inainte si dupa** implementare, pe date de test resetate
identic, per memoria de proiect "zero cazuri in date nu e dovada" — minim un caz per scenariu descris
mai sus (proforma simpla, proforma cu switch la comutare, copiere din proforma, drumul invers),
nu doar "a mers o data".
---
## 10. Ce nu se poate testa headless
- **Comutarea combo-ului `Ct_clb_fdoc` cu gridul de linii vizibil in acelasi ecran** — capcana deja
confirmata pe acest proiect (`grid-coloane-nu-se-materializeaza-headless`, memorie de proiect):
sub `-A -T`, `ColumnCount`/`RecordSource` raman artefacte, comportamentul real al `LostFocus`
pe combo si al reincarcarii `clb_serie_act` cere harnessul UI vizibil.
- **Dialogul `frm_articol_factura` vs. `do_alege_stoc`** (sectiunea 1.3, decizia de dialog dupa
`gestionabil`) — depinde de randare de formular modal, nu apelabil direct fara UI.
- **Verificarea vizuala ca liniile deja adaugate isi schimba starea la comutare** (sectiunea 4.1.c) —
daca implementarea alege sa arate un indicator vizual (de exemplu, o coloana/culoare care marcheaza
"negestionabil"), acel indicator nu se verifica headless; **continutul** `crsfactura` (valorile
`gestionabil`/`id_gestiune`) **se poate** verifica headless, cu apeluri directe la metoda noua din
pasul 2 (sectiunea 8), fara sa deschida formularul.
- **Realocarea vizuala a seriei/numarului in `clb_serie_act`** — control de UI, comportamentul lui de
reincarcare (`do_initializeaza`) nu se verifica fara randare.
- **Ce se poate verifica headless, direct**: continutul `crsfactura`/`poDate` dupa apeluri directe
(nu prin click) la metoda de marcare (pasul 2), la `do_schimba_tipdoc`, si la rutina drumului
invers (pasul 4) — cu date de test pregatite manual (un `crsfactura` cu 2-3 linii, `poDate.eProforma`
comutat programatic inainte si dupa) — confirma `gestionabil`, `id_gestiune`, `poDate.eProforma`,
fara sa deschida formularul. La fel, toate interogarile SQL din sectiunea 9 sunt verificabile
headless (SQL direct, fara UI).
---
## 11. Riscuri si decizii ramase lui Marius
1. **Drumul invers (sectiunea 5) — varianta (a) sau (b).** Cel mai important punct deschis al acestui
raport: fara o decizie explicita aici, decizia 10 din plan ("proforma foloseste acelasi formular")
ramane incompleta — un document care trece prin Proforma si revine la Factura in aceeasi sesiune
poate iesi cu stocul nedescarcat, silentios. **Recomandare**: varianta (a) (re-interogare si
re-deschidere a alegerii de gestiune la comutarea inapoi), pentru ca varianta (b) (blocarea
comutarii) contrazice explicit decizia 10 ("combo-ul ramane in antetul unificat", implicit
liber de folosit in ambele sensuri) si ar surprinde operatorul cu o pierdere de munca fara
avertisment prealabil la momentul comutarii spre Proforma.
2. **Ce se intampla cu `id_gestiune`/lotul ales anterior pe o linie marcata ulterior negestionabil**
(sectiunea 4.1, corolarul) — pastrare tacita (mai simplu, fara cod nou) sau stergere explicita
(mai clar pentru un ecran viitor care ar afisa gestiunea). **Recomandare**: pastrare tacita — nu
ajunge niciodata la server oricum (sentinela `-1000` trimisa explicit ignora orice valoare veche),
iar stergerea ar cere cod suplimentar fara beneficiu functional, doar cosmetic.
3. **Incertitudinea ramasa din raportul-sursa** (sectiunea 1.3: linia exacta unde `id_gestiune` devine
`-1000`) — necesara inainte de a scrie codul pasului 2 (sectiunea 8), nu doar o curiozitate.
Nu e o decizie de produs, e o verificare tehnica obligatorie inaintea implementarii.
4. **Indicator vizual pentru liniile marcate negestionabil la comutare** (mentionat la sectiunea 10) —
optional, nu cerut explicit de plan; de decis daca merita un semnal in grid (culoare/iconita) sau
ramane invizibil pana la emitere. Nu blocheaza nimic, dar afecteaza UX-ul comutarii repetate.
5. **Testarea pe date reale a `FACT-007`** (mentionata in raportul-sursa ca argument indirect,
nu dovada) — daca Marius vrea certitudine completa inainte de implementare, un test manual pe
mediu de dezvoltare (proforma cu articol gestionabil din comanda, verificare directa
`VANZARI_DETALII.ID_GESTIUNE IS NULL`) inchide definitiv acest gol, in afara perimetrului
read-only al acestei cercetari.
---
## Handoff
Cercetare + proiectare incheiate intr-o singura sesiune, fara sa fie nevoie de predare de context.
Toate cele 11 sectiuni cerute de brief sunt complete, cu `fisier:linie` verificat direct pe fisierele
text reale (`ofacturare.vc2`, `ofacturare_comun.vc2`, `ofacturare.prg`, `ofacturare_comun.prg` — nu
`.bak`) si pe corpul `PACK_FACTURARE` de pe `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`.
**Descoperirea centrala** (sectiunea "Descoperire centrala"): mecanismul care blocheaza nota
contabila pe proforma nu e (doar) sentinela `-1000` din `contabilizeaza_articol`, ci alegerea
`scrie_proforma` (fara contabilizare deloc) vs. `scrie_factura2`/`scrie_factura_avize` (cu
contabilizare) in `do_scrie_factura`, facuta dupa `poDate.eProforma` la Termina. Aceasta descoperire
schimba unde trebuie sa se concentreze grija de proiectare: nu pe "cum ajunge `-1000` la server"
(deja acoperit corect de codul existent), ci pe **ce se intampla cu liniile deja marcate `-1000` cand
documentul nu mai e proforma la Termina** — exact riscul din sectiunea 5, singurul loc unde sentinela
chiar conteaza si azi nu exista nicio plasa.
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe
Oracle (doar `SELECT`/`Read`/`Grep` pe fisiere de pe disc).

View File

@@ -1,374 +0,0 @@
# S5C — Factura din proforma (proiectare)
Status: INCHEIAT
## Sarcina
Cerinta noua (decizia 43, runda 12): dintr-o proforma emisa sa se poata genera o
FACTURA ca document NOU (nu prin comutarea tipului pe acelasi document — comutarea
PROFORMA -> FACTURA cu linii e deja blocata cu mesaj). Mecanismul pare sa existe deja
pe calea de copiere (`do_copiaza`).
## (a) Poate fi azi o proforma aleasa ca sursa de copiere?
**Da, fara nicio excludere.** Trei dovezi convergente:
1. `COMUN\clase\ofacturare_comun.vc2:4964-4974` — `frm_facturi.IsCopy(tnTip)`:
```
RETURN .T. && POT SA COPIEZ ORICE, TRATEZ TIPURILE IN DO_COPIAZA
*!* RETURN INLIST(m.lnTip, 1,5,7,10,22,23,43) && pot sa copii doar avize si facturi pret de lista, ...
```
Varianta veche (restrictiva, comentata) verifica doar `tip` (1-52) — niciodata `eproforma`. Varianta
activa returneaza necondiționat `.T.`. Nu exista, si n-a existat vreodata in acest cod, o excludere
pe `eproforma`.
2. `COMUN\clase\ofacturare_comun.vc2:5110` — vizibilitatea butonului de copiere pe randul selectat:
`Thisform.but_copiaza1.Visible = Thisform.IsCopy(crsFacturi.tip)` — acelasi apel, acelasi rezultat
necondiționat.
3. `COMUN\clase\ofacturare_comun.vc2:5082-5083` — gridul de facturi are un filtru dedicat pe
`eproforma`, cu proforma tratata ca subset normal, nu ascuns:
```
"Facturi&Avize\nofiled\E\(eproforma = 0)\" + crlf + ;
"Proforme\nofiled\E\(eproforma=1)\" + crlf + ;
```
Chiar filtrul "Proforme" arata explicit ca proformele sunt navigabile si selectabile normal in
acelasi grid — butonul de copiere ramane vizibil identic pe orice rand selectat din acest filtru.
**Concluzie**: azi orice utilizator poate selecta o proforma in grid si apasa "Copiere (CTRL+K)"
(tooltip-ul insusi anunta explicit acest caz de folosire — vezi citatul din
`s5b_proiectare_proforma_copiere.md` §"1.4"/`proforma_copiere_puncte_intrare.md:41-47`). Nu exista
nicio garda de blocat, la niciun nivel (`IsCopy`, vizibilitate buton, filtru grid).
## (b) Ce tip rezulta din copierea unei proforme?
**Rezultatul e intotdeauna `nIdTipDoc=5` (FACTURA)** — corect pentru o factura fiscala — dar tipul de
business (`VANZARI.TIP`, 1-52) degradeaza dupa tabelul deja existent din `do_copiaza`, **nemodificat
pentru cazul proforma**: nu exista nicio ramura speciala pe `eproforma` in `do_copiaza`
(`COMUN\clase\ofacturare_comun.vc2:3628-3713`) — degradarea se face **strict dupa `loFactura.tip`**,
indiferent daca documentul sursa era proforma sau nu.
**Pasul 1 — degradarea de tip business** (`:3693-3708`, tabel deja confirmat in
`s5b_proiectare_proforma_copiere.md` §2.1):
- Proforma poate exista doar pe tipuri "de factura" (combo-ul `Ct_clb_fdoc` traieste in
`frm_date_factura`, nu si in `frm_date_aviz` — cf. `proforma_copiere_puncte_intrare.md` §1). Setul
posibil de `loFactura.tip` pentru o proforma e deci printre `{1,2,3,4,5,6,7,8,9,10,43,44,45,47,48,49,51}`
(grupul "Facturi" din `COMUN\docs\tipuri_documente_facturare.md`).
- Din acest set: `{1,5,7,10}` raman neschimbate (`CASE INLIST(loFactura.tip,T1,T5,T7,T10,T22,T23)`,
`:3694`); `{2,3,4,8,43,44,45,47,48,49,51}` degradeaza la `T1` (`:3696-3697`); `{6}` la `T10`
(`:3698-3699`); `{9}` la `T5` (`:3700-3701`).
- Deci, pentru orice proforma emisa astazi (indiferent de tipul de business original), documentul
copiat va avea `TIP` in `{1,5,7,10}` — nucleul "lista de preturi" (lei/valuta) sau credit note.
**Pasul 2 — `nIdTipDoc` NU se propaga** (confirmat deja in
`s5b_proiectare_proforma_copiere.md` §2.3, `COMUN\programe\ofacturare_comun.prg:370`:
`*.nIdTipDoc = toDateAnterior.nIdTipDoc` — comentata). Documentul nou primeste `nIdTipDoc` implicit
dupa tipul degradat: `Do Case` la `COMUN\programe\ofacturare.prg:187-196`, toate valorile din
`{1,5,7,10}` cad sub `5` (FACTURA) → `poDate.eProforma = 0` pe documentul nou, **indiferent ca sursa
era proforma**.
**Concluzie pentru (b)**: mecanismul de copiere transforma azi orice proforma intr-o **FACTURA reala
(`nIdTipDoc=5`, `eproforma=0`)**, de tip business `1` (lista de preturi, cel mai frecvent caz — orice
tip din `{2,3,4,8,43,44,45,47,48,49,51}` cade tot pe `T1`), `5`/`10` (daca sursa era deja pe lista de
preturi in valuta/factura valuta), sau `7` (credit note, ramas neschimbat). Tipul rezultat **e cel bun
pentru o factura fiscala** din perspectiva `nIdTipDoc`/`eproforma` (nu ramane proforma), dar tipul de
business e intotdeauna **degradat catre "lista de preturi"** — proforma emisa dintr-o comanda (`tip=3`)
sau dintr-un aviz (`tip=4`) devine, dupa copiere, o factura "banala" `tip=1`, **la fel ca la orice alta
copiere non-proforma** (comportament deja documentat si acceptat in S5b §7 punctul 5: "degradarea de
tip din `do_copiaza` ramane pentru copiere si nu se aplica la regenerare" — nicio schimbare ceruta aici
fata de acel raport).
## (c) Legatura proforma -> factura (VANZARI_CORESP.TIP)
### Structura tabelei (confirmata pe schema vie, `MARIUSM_AUTO`)
```
ID_VANZARE_CORESP NUMBER NOT NULL (PK, populat de trigger BEFORE INSERT din SEQ_VANZARI_CORESP)
ID_VANZARE_FACT NUMBER NOT NULL
ID_VANZARE_AVIZ NUMBER NOT NULL (nume generic mostenit — nu neaparat un aviz, vezi mai jos)
STERS NUMBER NULL
TIP NUMBER NOT NULL
```
Singurul trigger gasit pe tabela (`TRG_VANZARI_CORESP_BEFOINS`, prezent identic in schemele `ACN` si
`MARIUSM_AUTO`) doar genereaza PK-ul din secventa — nu atinge/valideaza `TIP`.
### Cine scrie in tabela — un singur punct, in tot codul Oracle
`grep` pe `all_source` (schema vie) dupa `VANZARI_CORESP` gaseste **un singur writer**:
`pack_facturare.scrie_corespondente_vanzari` (`PACK_FACTURARE:15481-15516`, singurul `INSERT INTO
VANZARI_CORESP` din toata baza). Nu exista alt pachet, trigger sau produs ROA (ROAGEST, ROAIMOB,
ROACONTRACTE, ROAACNPRO etc.) care sa scrie in aceasta tabela — `PACK_FACTURARE` traieste in `COMUN`,
partajat de toata suita, deci **orice** consumator ar trece prin acelasi punct.
### Valorile de `TIP` deja folosite — confirmate cod + date
**Cod** (singurele 3 apeluri la `scrie_corespondente_vanzari` din tot `PACK_FACTURARE`,
`:14818-14839`, in `finalizeaza_factura`):
- `TIP=1`: `WHEN pack_facturare.ntip = 4 THEN ... scrie_corespondente_vanzari(1)` — factura scrisa
dintr-un aviz (`ntip=4` = "FACT. DIN AVIZ"). Foloseste `pack_facturare.clistaid_avize` (lista
separata, populata pentru facturare-din-aviz cu selectie multipla).
- `TIP=2`: `WHEN pack_facturare.ntip = 24 THEN ... scrie_corespondente_vanzari(2)` — aviz de retur
(`ntip=24`).
- `TIP=3`: `WHEN pack_facturare.ntip IN (8, 9) THEN ... scrie_corespondente_vanzari(3)` — factura de
retur.
Toate trei folosesc `pack_facturare.nid_vanzare` (documentul curent, tocmai scris) ca
`ID_VANZARE_FACT`; pentru `TIP=1` sursa vine din `clistaid_avize`, pentru `TIP=2`/`TIP=3` (ramura
`ELSE` din `scrie_corespondente_vanzari`, `:15490-15491`) din `pack_facturare.clistaid` generic.
**Date** (schema `MARIUSM_AUTO`, interogare directa `SELECT tip, COUNT(*) FROM vanzari_coresp GROUP BY
tip`): `TIP=1` → 6 randuri, `TIP=2` → 1, `TIP=3` → 1. **Zero randuri cu alt `TIP`.** Coerent cu codul
(nu exista alt loc care sa scrie alte valori) — dovada dubla (cod + date), nu doar una din ele (per
memoria de proiect "zero cazuri in date nu e dovada": aici avem *si* absenta din date, *si* absenta
structurala in cod, ceea ce e o dovada tare, nu doar un data point izolat).
### Valoare noua libera
**`TIP=4` e liber**, confirmat pe ambele fronturi (cod: niciun apel existent la
`scrie_corespondente_vanzari(4)`; date: zero randuri `TIP=4` in schema testata). Recomandare: **`TIP=4`
= "factura scrisa dintr-o proforma"**, urmatorul numar disponibil in secventa deja folosita (1,2,3),
fara conflict cu niciun consumator existent (nu exista alt produs ROA care sa scrie sau sa citeasca
`VANZARI_CORESP` in afara de `PACK_FACTURARE`). Structura tabelei **nu se schimba** — doar o noua
valoare de enum in coloana `TIP`, exact cum a cerut misiunea.
### Unde s-ar scrie corespondenta — punct de intrare, cu rezerva importanta
Tiparul de azi (`TIP=1/2/3`) scrie corespondenta **din interiorul** `finalizeaza_factura`, intr-un
`CASE` cheie **`pack_facturare.ntip`** — semnalul de business-type al documentului nou scris. Aceasta
cheie **nu poate distinge** "factura rezultata dintr-o copiere de proforma" de "orice alta factura
obisnuita cu acelasi tip degradat" (vezi (b): rezultatul copierii unei proforme cade intotdeauna in
`{1,5,7,10}`, niciodata `4`/`24`/`8`/`9` — deci nu exista azi nicio ramura `WHEN` pe care s-o extinda,
si niciuna nu s-ar putea scrie corect doar din `ntip`, pentru ca acelasi `ntip=1` rezulta si dintr-o
copiere obisnuita de factura, nelegata de nicio proforma). Vezi Capcana (i) pentru raspunsul complet la
"de unde stie Oracle ca sursa a fost o proforma" si propunerea de proiectare pentru rezolvare.
## Capcana (i): supravietuieste id_fact/id_vanzare al sursei pana la scrierea corespondentei?
**Da, `id_vanzare` al proformei supravietuieste — dovedit pas cu pas — dar faptul ca sursa "era o
proforma" (nu doar "era un document oarecare") NU supravietuieste nicaieri azi. Asta e golul real.**
**Partea care functioneaza deja, neschimbata:**
1. `do_copiaza` (`COMUN\clase\ofacturare_comun.vc2:3690-3691`): `SELECT crsFacturi / SCATTER NAME
loFactura MEMO` — `loFactura` e o copie completa a randului sursa din grid, **inclusiv
`loFactura.id_vanzare`** (campul cheie) si `loFactura.eproforma` (coloana de grid confirmata la
`ofacturare_comun.vc2:2494`, `Column37.ControlSource = "eproforma"`) — **ambele disponibile in
acest moment**, inainte ca degradarea de tip (`:3693-3708`) sa ruleze.
2. `copiere_factura` (`COMUN\programe\oproceduri_facturare.prg:150-153`) → `factureaza(loFactura.tip,
loFactura)` — `loFactura` intreg (cu `id_vanzare` si `eproforma` inca pe el) devine `toFactura`,
parametrul opțional.
3. `completeaza_setari_document(toDateAnterior, .T.)`
(`COMUN\programe\ofacturare_comun.prg:362-412`), ramura de copiere: **`.listaid =
toDateAnterior.id_vanzare`** (`:387`) — **aici** `id_vanzare` al proformei ajunge in `poDate`,
proprietate care **nu e atinsa de degradarea de tip** (aceea opereaza doar pe `loFactura.tip`,
variabila locala din `do_copiaza`, complet separata de `poDate`).
4. `poDate.listaid` calatoreste neschimbat prin toata sesiunea (folosit si la §2.5 din
`s5b_proiectare_proforma_copiere.md` pentru `cursor_retur_document`), pana la scriere:
`do_scrie_articole` (`COMUN\clase\ofacturare.vc2:6104`): `Alltrim(Nvl(poDate.listaid,''))` e trimis
ca parametru `V_LISTAID` catre `pack_facturare.initializeaza_date_factura`, care il pune in
`pack_facturare.clistaid := V_LISTAID` (`PACK_FACTURARE:1883`) — **inainte** ca `do_scrie_factura`
sa aleaga `scrie_factura2`/`finalizeaza_factura`. La momentul in care `scrie_corespondente_vanzari`
ar rula, `pack_facturare.clistaid` **este deja** `id_vanzare`-ul proformei (ca text), exact valoarea
pe care ramura `ELSE` a lui `scrie_corespondente_vanzari` (`:15490-15491`, folosita azi de `TIP=2` si
`TIP=3`) o citeste.
**Partea care NU exista azi — golul real:** `completeaza_setari_document` (pasul 3 de mai sus) copiaza
explicit `id_lucrare`, `nrord`, `id_sectie`, `sectie`, `id_agent`, `nume_agent`, `id_delegat`,
`nume_delegat`, `BIdelegat`, `CNPdelegat`, `nrinmat`, `id_masina`, `listaid`, `descriere`, `id_client`,
`nume_client` de pe `toDateAnterior` — **dar niciodata `.eproforma`**. Documentul nou stie "din ce
`id_vanzare` a fost copiat" (`.listaid`), dar **nu stie daca acel document sursa era o proforma sau o
factura obisnuita** — informatia se pierde exact la acest pas, inainte sa ajunga la Oracle.
**De ce conteaza**: `finalizeaza_factura` (Oracle) decide azi ce `TIP` de corespondenta sa scrie
uitandu-se **doar** la `pack_facturare.ntip` (business-type-ul documentului nou). Cum am stabilit la
(b), rezultatul unei copieri de proforma cade intotdeauna in `{1,5,7,10}` — **acelasi interval** in
care cade si o copiere obisnuita (proforma sau nu). Oracle **nu poate reconstitui** din `ntip` singur
daca documentul curent a fost copiat dintr-o proforma sau dintr-o factura normala — nu exista niciun
semnal in `pack_facturare` care sa poarte aceasta distinctie.
**Ce trebuie adaugat, minimal** (propunere, nu implementare):
1. **VFP**: la `completeaza_setari_document:387`, langa `.listaid = toDateAnterior.id_vanzare`, adauga
o proprietate noua, de exemplu `.lProformaSursa = (toDateAnterior.eproforma = 1)` — un simplu boolean
client-side, capturat in acelasi moment si din acelasi obiect sursa unde `listaid` e deja capturat
(zero risc suplimentar de "se pierde intre timp", pentru ca foloseste exact acelasi tipar dovedit la
pasul 3-4 de mai sus).
2. **Scrierea corespondentei, NU prin `finalizeaza_factura`/`ntip`**: pentru ca `ntip` nu poate purta
distinctia (vezi mai sus), cea mai sigura ruta e un apel Oracle **explicit, separat**, facut din VFP
imediat dupa ce `do_scrie_factura` confirma succesul scrierii documentului nou, gardat de
`poDate.lProformaSursa`:
```
IF poDate.lCopiere AND poDate.lProformaSursa
lcSql = [{call pack_facturare.scrie_corespondente_vanzari(4)}]
* ... goExecutor.oExecute(lcSql) ...
ENDIF
```
Aceasta reutilizeaza **exact** procedura Oracle existenta, neschimbata (ramura `ELSE`,
`pack_facturare.clistaid`/`pack_facturare.nid_vanzare` inca valide in sesiune la acel moment, per
pasul 4 de mai sus) — **zero cod PL/SQL nou**, doar un nou punct de apel VFP si o noua valoare de
`TIP`. Alternativa (adaugarea unei ramuri noi in `CASE`-ul din `finalizeaza_factura`, cheie pe un
parametru nou trimis prin `V_PARAMETRU_ADITIONAL` sau similar) ar fi mai fragila: acel `CASE` e
punctul comun al **oricarei** facturi/aviz/retur scrise in tot codul, folosit de toate produsele ROA
prin `PACK_FACTURARE` partajat — o ramura noua acolo, keyed pe o combinatie de `ntip` + un flag nou,
ar creste suprafata unui switch deja incarcat, pentru un caz care oricum nu poate fi exprimat corect
doar din `ntip`. Apelul explicit izolat e mai simplu si nu atinge deloc `finalizeaza_factura`.
3. **Ordinea conteaza**: apelul explicit trebuie sa ruleze **inainte** ca orice alt cod Oracle din
aceeasi sesiune sa resetteze `pack_facturare.nid_vanzare`/`clistaid` (de exemplu, o alta scriere de
document in acelasi batch) — de plasat imediat dupa `do_scrie_factura`, inainte de listare/atasamente
(care oricum nu ating Oracle pentru partea de scriere). Daca se prefera robustete completa (fara
dependenta de stare de sesiune Oracle), varianta alternativa e sa se paseze explicit
`V_ID_VANZARE_FACT` (= `poDate.id_vanzare`, cunoscut client-side dupa scriere) si
`V_ID_VANZARE_PROFORMA` (= `poDate.listaid`, deja cunoscut) direct ca parametri, printr-o mica
procedura Oracle noua care ocoleste `clistaid`/`nid_vanzare` cu totul — cost: un nou obiect PL/SQL in
loc de zero, beneficiu: independenta de ordinea altor apeluri din sesiune. **Alegere ramasa lui
Marius** (vezi propunerea finala).
## Capcana (ii): `marcheaza_facturat` pe proforma — corect sau nu?
**Nu trebuie chemat pe proforma. Confirmat cu dovada, pe patru argumente convergente.**
**Ce face, exact** (`PACK_FACTURARE:15381-15418`):
```sql
PROCEDURE marcheaza_facturat(V_VERIFICARE IN NUMBER) IS
BEGIN
IF V_VERIFICARE = 0 THEN
UPDATE VANZARI SET FACTURAT = 1, ID_UTILFACT = pack_facturare.nid_util
WHERE ID_VANZARE IN (SELECT id_vanzare_aviz FROM vanzari_coresp
WHERE id_vanzare_Fact = pack_facturare.nid_vanzare
AND sters = 0 AND tip <> 3)
AND FACTURAT = 0;
ELSE
-- varianta "verificare cantitate ramasa": marcheaza FACTURAT=1 doar daca toata cantitatea
-- din VANZARI_DETALII a documentului sursa a fost deja consumata in VANZARI_CANTITATI
...
END IF;
END;
```
Flipeaza `VANZARI.FACTURAT=1` (+`ID_UTILFACT`) pe **documentul(ele) sursa** legate prin
`VANZARI_CORESP` de documentul tocmai scris — fie neconditionat (`V_VERIFICARE=0`), fie doar cand
cantitatea documentului sursa a fost epuizata (`V_VERIFICARE=1`, mecanismul de **facturare partiala** a
avizelor, bazat pe `VANZARI_CANTITATI`).
**Argumentul 1 — nu exista un `TIP` cu care sa se cupleze corect azi.** `marcheaza_facturat` se cheama
azi **doar** din `finalizeaza_factura`, in aceleasi doua ramuri `WHEN pack_facturare.ntip = 4` si
`WHEN pack_facturare.ntip = 24` care scriu si corespondenta (`:14823-14831`) — cuplate mereu impreuna cu
`scrie_corespondente_vanzari(1)`/`(2)`, niciodata separat. Cum am stabilit la Capcana (i), calea propusa
pentru `TIP=4` (factura din proforma) e un **apel explicit separat**, in afara acestui `CASE` — deci
n-ar exista niciun loc "natural" unde `marcheaza_facturat` sa se agate fara sa introduca exact acelasi
tip de cod nou-scris ca la corespondenta insasi.
**Argumentul 2 — mecanismul de "cantitate ramasa" (`V_VERIFICARE=1`) nu are pe ce sa opereze pentru o
proforma.** Verificarea citeste `VANZARI_CANTITATI` (populat **doar** de
`scrie_cantitati_vanzari_avize`, apelata **doar** in ramura `ntip=4` a lui `finalizeaza_factura`).
Proforma nu trece niciodata prin `finalizeaza_factura` (confirmat in
`s5b_proiectare_proforma_copiere.md`, "Descoperire centrala": `scrie_proforma` cheama doar
`scrie_in_vanzari`) — deci **nu exista niciodata randuri `VANZARI_CANTITATI` pentru o proforma**.
Varianta `V_VERIFICARE=1` a lui `marcheaza_facturat` ar gasi mereu "cantitate ramasa = cantitate totala"
(nimic consumat), fie nu ar marca niciodata `FACTURAT=1` (comportament inutil), fie (daca s-ar folosi
gresit `V_VERIFICARE=0`) ar marca neconditionat, ca la punctul urmator.
**Argumentul 3 — asimetrie la stergere: proforma ar ramane blocata `FACTURAT=1` definitiv.**
`sterge_factura` (`PACK_FACTURARE:5501-5533`) reface `FACTURAT=0` pe documentele sursa **doar** pentru
`V_TIP=24` (aviz retur, `:5502-5510`) si `V_TIP=4` (factura din aviz, `:5525-5533`) — tipurile care azi
chiar cheama `marcheaza_facturat`. Rezultatul copierii unei proforme cade intotdeauna in `{1,5,7,10}`
(per (b)) — **niciuna din aceste valori nu are ramura in `CASE`-ul de stergere**. Daca s-ar chema
`marcheaza_facturat` la scrierea facturii din proforma, iar utilizatorul ar sterge ulterior acea
factura, **proforma sursa ar ramane cu `FACTURAT=1` pentru totdeauna** — o stare orfana, fara niciun
cod care s-o repare, introdusa exact de acest apel. Corespondenta `VANZARI_CORESP` insasi nu are aceeasi
problema (nimic n-o citeste ca sa se strice daca ramane "orfana" dupa stergerea facturii — cel mult
devine o legatura catre un document sters, inofensiv).
**Argumentul 4 — nimic nu filtreaza azi proformele dupa `FACTURAT`, deci n-ar exista niciun beneficiu
de blocat.** Spre deosebire de avize (`cursor_avize`/candidatii de facturat, filtrati implicit prin
fluxul dedicat "Factura din aviz") si comenzi (`VCOMENZI.FACTURAT=0`, folosit explicit in
`cauta_date_comanda`/`cauta_date_comanda_gest`, `PACK_FACTURARE:15659,15685`, ca sa nu ofere din nou o
comanda deja facturata), **nimic in codul citit** filtreaza dupa `FACTURAT` cand se alege o proforma ca
sursa de copiere — confirmat la (a): `IsCopy`/vizibilitatea butonului/filtrul de grid nu se uita
niciodata la `facturat`. Marcarea n-ar preveni nicio re-copiere accidentala a aceleiasi proforme (care
oricum ramane posibila, vezi propunerea finala, punctul de decizie 2).
**Concluzie**: `marcheaza_facturat` **nu trebuie chemat** pentru "factura din proforma". Se scrie
**doar** corespondenta (`VANZARI_CORESP`, `TIP=4`), fara actualizare de `VANZARI.FACTURAT` pe proforma.
Confirma explicit suspiciunea din misiune ("proforma nu e document de livrare") cu evidenta concreta:
proforma n-are urma in `VANZARI_CANTITATI` (Argumentul 2), n-are ramura de reversare la stergere
(Argumentul 3), si n-are niciun consumator care sa citeasca `FACTURAT` pe ea (Argumentul 4).
## Propunere de proiectare
Mecanismul de baza **ramane copierea existenta** (`do_copiaza`/`copiere_factura`/`factureaza`,
neschimbata in structura ei) — cerinta "document nou, nu comutare pe acelasi document" e deja
satisfacuta de calea de azi (confirmat la (a)/(b)). Ce lipseste e strict **legatura persistata**
proforma → factura si excluderea explicita a lui `marcheaza_facturat`. Pasi, in ordine:
1. **Captureaza `eproforma` al sursei la copiere.** In `completeaza_setari_document`
(`COMUN\programe\ofacturare_comun.prg:387`, ramura `tlFactura=.T.`), langa
`.listaid = toDateAnterior.id_vanzare`, adauga o proprietate noua pe `poDate`
(ex. `.lProformaSursa`), citind `toDateAnterior.eproforma` — singurul loc unde informatia mai e
disponibila, inainte sa se piarda (Capcana (i)). *Gata cand:* dupa copierea unei proforme,
`poDate.lProformaSursa = .T.`; dupa copierea oricarui alt document, `.F.`.
2. **Rezerva `TIP=4`** in `VANZARI_CORESP` pentru "factura scrisa dintr-o proforma" — doar o conventie
documentata (ca la 1/2/3), zero schimbare de schema (confirmat la (c): tabela ramane
`ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP, STERS`).
3. **Scrie corespondenta cu un apel Oracle explicit, separat de `finalizeaza_factura`.** Imediat dupa
ce `do_scrie_factura` confirma succesul (acelasi punct unde azi se decid listarea/atasamentele),
daca `poDate.lCopiere AND poDate.lProformaSursa`: `{call
pack_facturare.scrie_corespondente_vanzari(4)}` — **reutilizeaza procedura Oracle existenta,
neschimbata** (ramura `ELSE`, deja scrisa pentru `TIP=2`/`3`). Zero cod PL/SQL nou pentru scriere.
*Alternativa mai robusta, cu cost:* o mica procedura Oracle noua care primeste explicit
`V_ID_VANZARE_FACT`/`V_ID_VANZARE_PROFORMA` ca parametri (nu se bazeaza pe
`pack_facturare.clistaid`/`nid_vanzare` inca valide in sesiune) — de ales intre simplitate (varianta
de mai sus) si robustete fata de ordinea apelurilor (varianta cu parametri expliciti); vezi punctul
de decizie 1 mai jos.
4. **NU cheama `marcheaza_facturat`.** Confirmat cu 4 argumente independente la Capcana (ii) — proforma
nu are `VANZARI_CANTITATI`, nu are ramura de reversare la stergere, nimic n-o filtreaza dupa
`FACTURAT`, si oricum n-ar exista un `WHEN pack_facturare.ntip=...` natural de unde s-o cheme (calea
aleasa la pasul 3 e explicit separata de `finalizeaza_factura`).
5. **Afisare optionala "provine din proforma X"** pe factura noua — se poate construi din
`VANZARI_CORESP` (`TIP=4`) la fel ca tiparul deja folosit pentru retur (`legatura_linie_retur.md`
§5): la nivel de **document**, nu de linie (nicio schimbare fata de tiparul deja acceptat pentru
retur — nu exista niciun tabel de legatura la nivel de linie in tot codul citit, per acelasi raport).
Nu e cerut explicit de misiune, dar e disponibil "gratis" din legatura scrisa la pasul 3.
**Ce NU se schimba** (confirmat, fara nevoie de atingere):
- `IsCopy`, vizibilitatea `But_copiaza1`, filtrul de grid "Facturi&Avize / Proforme" — proforma ramane
selectabila ca sursa exact ca azi (a).
- Degradarea de tip din `do_copiaza` (`{1,5,7,10}` pentru orice proforma) si alocarea de numar nou —
raman neschimbate, deja produc `nIdTipDoc=5`/`eproforma=0` corect pentru documentul nou (b).
- `cursor_retur_document`, restaurarea `GESTIONABIL=B.IN_STOC` la copiere — neschimbat, mecanism deja
corect pentru orice copiere (mostenit din `s5b_proiectare_proforma_copiere.md` §7).
- `scrie_corespondente_vanzari` insasi — zero cod PL/SQL nou (varianta recomandata la pasul 3).
### Riscuri si ce ramane de decis de Marius
1. **Apel explicit pe stare de sesiune (`clistaid`/`nid_vanzare`) vs. procedura noua cu parametri
expliciti** (pasul 3) — simplitate (zero cod Oracle nou, dar depinde ca nimic altceva sa nu resetteze
starea pachetului intre scrierea facturii si apelul de corespondenta) vs. robustete (un obiect
PL/SQL nou, insensibil la ordine). Recomandare: varianta simpla, **daca** apelul se plaseaza imediat
dupa `do_scrie_factura`, inainte de orice alt apel Oracle din acelasi flux (listare/atasamente nu
ating Oracle pentru scriere) — de verificat pe cod exact la implementare ca nu exista un apel Oracle
intercalat care ar reseta `pack_facturare.nid_vanzare`.
2. **Poate fi copiata de mai multe ori aceeasi proforma?** Azi nu exista nicio garda (FACTURAT nu se
marcheaza — decizia de la Capcana (ii)), deci un utilizator poate genera N facturi din aceeasi
proforma, fiecare cu propriul rand `TIP=4` in `VANZARI_CORESP` catre aceeasi proforma sursa. E
comportamentul implicit al oricarei copieri azi (nimic n-o limiteaza nici pentru facturi/avize
normale) — de confirmat daca e acceptabil sau daca se doreste un avertisment (nu o blocare, pentru
ca ar contrazice tiparul existent de copiere liber-repetabila).
3. **Guard simetric la stergere?** `sterge_factura` blocheaza azi stergerea unui aviz/unei facturi care
are deja o factura de retur legata (`TIP=3`) sau facturi/avize-retur legate (`TIP IN (1,2)`,
`PACK_FACTURARE:5450-5476`). Nu exista cerinta explicita in misiune pentru un guard simetric pe
`TIP=4` (ex. "nu poti sterge o proforma care are deja o factura generata din ea") — de decis daca se
doreste, sau daca proforma ramane liber stersa oricand (comportament actual, neschimbat daca nu se
adauga nimic).
4. **Afisarea "provine din proforma X"** (pasul 5) — optionala, nu ceruta explicit; de decis daca
merita implementata acum sau ramane pentru o poveste ulterioara (costul e mic, data fiind legatura
deja scrisa la pasul 3).
5. **`TIP=4` ca valoare aleasa** — libera si fara conflict azi (confirmat cod+date), dar e o alocare
ireversibila din punct de vedere al datelor odata folosita in productie; de confirmat explicit de
Marius inainte de implementare (nu doar acceptata tacit).
## STARE / CE RAMANE
**Cercetare + proiectare incheiate.** Toate cele trei intrebari (a/b/c) si ambele capcane (i/ii) au
raspuns cu dovada `fisier:linie`, verificat pe fisierele text reale (`ofacturare_comun.vc2`,
`ofacturare.vc2`, `ofacturare_comun.prg`, `ofacturare.prg` — nu `.bak`) si pe `PACK_FACTURARE`
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`), plus verificare directa pe schema Oracle vie
(`MARIUSM_AUTO`: structura `VANZARI_CORESP`, distributia `TIP` din date, singurul trigger existent).
Descoperirea centrala a acestei cercetari: mecanismul de copiere de azi rezolva deja "document nou" si
"tip corect" (a, b) fara nicio schimbare, dar **pierde tacit** informatia "sursa era o proforma" chiar
in metoda care ar trebui s-o pastreze (`completeaza_setari_document:387`, care copiaza `listaid` dar nu
si `eproforma`) — motiv pentru care legatura `VANZARI_CORESP` nu se poate agata de `CASE`-ul existent
din `finalizeaza_factura` (cheie doar pe `ntip`, care nu poarta aceasta distinctie) si are nevoie de un
semnal nou, client-side, plus un apel Oracle explicit separat (pasii 1 si 3 din propunere).
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe
Oracle (doar `SELECT`, rulat cu `sqlplus.exe` pe schema `MARIUSM_AUTO`).

View File

@@ -1,720 +0,0 @@
# S8 — Proiectare: incarcarea documentului existent in formularul unificat
Livrabilul povestii **S8** din `docs\plan_13_unificare_formular_facturare.md:2944-2962` (etapa II,
deciziile 5, 8, 9, 19, 35, 50). Cercetare **READ-ONLY** — zero editari de cod, zero write-back, zero
`git_sync.ps1` / `txt2vcx.ps1`, zero commit. Pe Oracle **numai `SELECT`** pe dictionar
(`all_tab_columns` pentru `VANZARI`, `VANZARI_DETALII`, `FACT_VFACTURI`, `FACT_VFACTURI_DETALII`).
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar
citite, neatinse.
**Conventie de marcare**, folosita peste tot in raport:
- **[V]** = verificat direct pe cod / pe dictionarul Oracle, cu `fisier:linie` sau nume de coloana;
- **[D]** = dedus din cod verificat, dar concluzia insasi n-a fost executata / observata;
- **[N]** = nestabilit, cu motivul scris.
---
## 0. Verdict (rezumat)
Schita din plan — *„`completeaza_setari_document` pentru antet + `cursor_retur_document(V_COPIERE = 1)`
pentru linii, fara degradarea de tip din `do_copiaza`"* — e corecta ca directie, dar **niciuna dintre
cele doua piese nu incarca documentul**, si asta nu e o nuanta de implementare:
1. **`completeaza_setari_document(toFactura, .T.)` copiaza 16 proprietati din ~120 [V]**
(`COMUN\programe\ofacturare_comun.prg:361-411`). Nu copiaza **niciunul** dintre cei 14 parametri de
identitate/antet ai lui `modifica_date_factura`, in afara de `id_delegat` / `id_masina` / `id_agent`.
Serie, numar, data, scadenta, `text_aditional`, `id_facturare`, `id_ruta`, `tip_saft`, `efactura`,
`listare_detaliata`, `dataora_exp`, discountul de document, `discount_evidentiat`, `eproforma`,
valuta, cursul — **toate lipsesc**. Sectiunea 1.2 le da pe toate, cu sursa.
2. **`cursor_retur_document(V_COPIERE = 1)` nu umple gridul documentului, ci gridul-sursa.** Pe calea de
copiere, rezultatul lui intra in `crsarticole` (selectorul din stanga), iar `crsfactura` (documentul)
se creeaza **gol** si ramane gol — comentariul e explicit: *„Nu adaug automat articolele din factura
originala, ca sa dau posibilitate de modificare"* (`COMUN\programe\ofacturare.prg:455-457`,
`:338` `creeaza_facturacrs([crsfactura])`) **[V]**. Transferul in document se face **numai** prin
`do_adauga_tot` → `do_adauga_articol`, care pentru articolele gestionabile trece prin
**dialogul de alegere din stoc** (`do_alege_stoc`). Sectiunea 4.3.
3. **`cursor_retur_document` nu intoarce `ID_VANZARE_DET` si nu intoarce `TAXCODE` [V]**
(lista completa de coloane: `PACK_FACTURARE:3949-4054`). Fara `ID_VANZARE_DET`:
- **cheia de linie presupusa de S8b (§1.2 al raportului S8b) nu exista** pe calea propusa;
- **ruta ieftina `modifica_explicatie_articol` devine neapelabila** — primul ei parametru **este**
`V_ID_VANZARE_DET` (`PACK_FACTURARE:14511-14519`) **[V]**.
Fara `TAXCODE`, cerinta explicita a planului („explicatia si `taxcode` pe fiecare linie") nu e
acoperita.
**Recomandarea centrala a acestei proiectari:** S8 **nu** se construieste pe perechea din schita, ci pe
**precedentul care exista deja si face exact acest lucru** — `relisteaza_ofacturare_stoc`
(`COMUN\programe\ofacturare_stoc.prg:456-742`) **[V]**, care reconstituie `poDate` + cursorul de linii
dintr-un document salvat, prin `FACT_VFACTURI` (antet) + `FACT_VFACTURI_DETALII` (linii), pentru
relistare. `FACT_VFACTURI_DETALII` **are** `ID_VANZARE_DET` si `TAXCODE` **[V]**. Detaliile,
compromisurile si ce ramane totusi de completat — sectiunea 1.3 si 4.
---
## 1. Inventarul complet a ce se incarca, camp cu camp
### 1.1 Cele doua canale de citire, comparate pe coloane [V]
Sursa: `PACK_FACTURARE:3949-4054` (`cursor_retur_document`) si `all_tab_columns` pentru
`FACT_VFACTURI_DETALII` / `VANZARI_DETALII`.
| Ce trebuie pe linie | `cursor_retur_document(V_COPIERE=1)` | `FACT_VFACTURI_DETALII` | Exista pe `VANZARI_DETALII`? |
|---|---|---|---|
| `ID_VANZARE_DET` (cheia liniei) | **NU** | **DA** | DA |
| `TAXCODE` | **NU** | **DA** | DA |
| `EXPLICATIE` | DA | DA | DA |
| `ID_POL` (politica de pret pe linie) | DA | **NU** (doar `NUME_LISTA_PRETURI`) | DA |
| `PRETD` / `ID_VALUTAD` | DA (`PRETD`) | **NU** | DA |
| `GESTIONABIL` (flag calculat) | DA (calculat, vezi 1.4) | **NU** | — (derivat din `ID_GESTIUNE`) |
| `DIFERENTA` | pliata in `PRET` (vezi 1.4) | **NU** | DA |
| `ID_GESTIUNE`, `LOT`, `SERIE`, `CANTITATE`, `PRET`, `PRET_CU_TVA`, `PROC_TVAV`, `ID_JTVA_COLOANA`, `ID_JTVA_COLOANA_EX`, `PRET_ACHIZITIE`, `DISCOUNT_UNITAR`, `ID_VALUTA`, `CURS`, `MULTIPLICATOR` | DA | DA | DA |
| `ID_CTR` / `NUMAR_CONTRACT` | **NU** | **DA** | DA (`ID_CTR`) |
**Concluzia operationala: niciunul dintre cele doua canale nu e suficient singur [V].** Cele doua
coloane care lipsesc din `cursor_retur_document` (`ID_VANZARE_DET`, `TAXCODE`) exista **amandoua pe
`VANZARI_DETALII`**, deci sunt disponibile fara nicio schimbare de structura — lipseste doar din lista
`SELECT`.
**Trei variante, cu recomandare:**
| Varianta | Ce cere | Risc |
|---|---|---|
| **(A)** adaugarea celor doua coloane in `SELECT`-ul lui `cursor_retur_document` | doua randuri in PL/SQL, plus `A1.ID_VANZARE_DET, A1.TAXCODE` in subselectul intern | Procedura e folosita si de **copiere** si (prin `cursor_retur`) de **retur**. Coloanele in plus ajung in `crsarticole` → `Scatter Name poArticol` → si `taxcode` **s-ar gathera** in `crsfactura` (campul exista deja acolo, `creeaza_facturacrs`, `ofacturare_comun.prg:1785`) **[V]**. Adica **copierea ar incepe sa transporte `taxcode`** — probabil o corectie, dar e o **schimbare de comportament pe o cale existenta**, nu una inerta. |
| **(B) procedura noua**, `cursor_editare_document`, clona cu cele doua coloane in plus — **RECOMANDAT** | o procedura noua in `pack_facturare`, zero atingere pe cele existente | Duplicare de SQL (~100 randuri). Costul e intretinerea, nu regresia. Garantie de regresie zero **prin constructie**, in acelasi spirit ca solutia `SET_IDFACT` din S9. |
| **(C)** citire din `FACT_VFACTURI_DETALII` (calea `relisteaza_ofacturare_stoc`) | zero cod Oracle nou | Pierde `ID_POL`, `PRETD`/`ID_VALUTAD`, `GESTIONABIL` si tratamentul `DIFERENTA`/valuta din `cursor_retur_document` — adica exact partea grea. Ar cere refacerea ei in VFP. |
*Recomandare:* **(B)**, cu `SELECT`-ul copiat identic din `cursor_retur_document` plus
`A1.ID_VANZARE_DET` si `A1.TAXCODE`, si cu ramura `GESTIONABIL` decisa explicit (1.4).
### 1.2 Antetul — ce se incarca, si de unde [V]
`poDate` e `oDateFactura` (`COMUN\programe\ofacturare_comun.prg:99-320`). Coloana „sursa" e coloana din
`FACT_VFACTURI` (view-ul din spatele `crsFacturi`, folosit deja de `do_modifica`
`ofacturare_comun.vc2:4560-4568` si de `relisteaza_ofacturare_stoc` `ofacturare_stoc.prg:504-509`),
sau `VANZARI` direct.
Legenda coloanei „azi": **CSD** = acoperit de `completeaza_setari_document(…, .T.)`; **GOL** = nimeni
nu-l incarca azi pe calea de copiere, deci e **gol de umplut in S8**.
#### (a) Identitatea documentului — antetul vizibil
| `poDate.` | Sursa | Azi | Nota |
|---|---|---|---|
| `nIdTipDoc` (FACTURA/PROFORMA/BON) | derivat din `tip` + `eproforma` | **GOL** — linia e **comentata explicit** in CSD (`ofacturare_comun.prg:366`) | vezi 3.2 |
| `tip` | `FACT_VFACTURI.TIP` | **GOL** (si azi **degradat** de `do_copiaza`) | S8 il pastreaza, per plan |
| `eProforma` | `FACT_VFACTURI.EPROFORMA` | **GOL** — `eproforma` nu apare deloc in `ofacturare_comun.prg` (rezultatul 1 al rundei 13) | |
| `serie_act` | `FACT_VFACTURI.SERIE_ACT` | **GOL** (CSD il pune doar in `.descriere`, ca text) | vezi 4.2 — conflict cu `poGeneratorNumere` |
| `nract` | `FACT_VFACTURI.NUMAR_ACT` | **GOL** (idem) | idem |
| `dataact` | `FACT_VFACTURI.DATA_ACT` | **GOL** (`Init` pune **azi**) | |
| `datascad` | `FACT_VFACTURI.DATA_SCAD` | **GOL** (`Init` calculeaza din `gnZileScadentaFact`) | |
| `zi_curs` | **NU EXISTA COLOANA** — vezi 1.5 | **GOL, si nerecuperabil** | raspuns la intrebarea deschisa din S8b §5.2 |
| `in_valuta` / `id_valuta` / `nume_valuta` | `FACT_VFACTURI.IN_VALUTA` / `ID_VALUTA` / `NUME_VAL` | **GOL** (`Init` deriva `in_valuta` din `tip`) | |
| `Curs` / `multiplicator` | `FACT_VFACTURI.CURS` / `MULTIPLICATOR` | **GOL** | pe linii, din `VANZARI_CURSURI` |
#### (b) Partener si sursa
| `poDate.` | Sursa | Azi |
|---|---|---|
| `id_client` / `nume_client` | `ID_PART` / `CLIENT` | **CSD** |
| `cod_fiscal` | `FACT_VFACTURI.COD_FISCAL` | **GOL** |
| `sold_lei` / `sold_valuta` | recalculate la deschidere (derivate, read-only) | **GOL** — se recalculeaza, nu se restaureaza |
| `listaid` (id comanda / contract / lista avize) | vezi 1.6 | **CSD, dar cu valoare GRESITA pentru editare** — CSD scrie `.listaid = toDateAnterior.id_vanzare` (`ofacturare_comun.prg:383`) **[V]** |
| `descriere` (nr. comanda/contract/avize) | `FACT_VFACTURI.ALTELE` / `COMANDA` / `CONTRACT` / `AVIZE` | **CSD, dar cu serie+numarul documentului insusi**, nu al sursei (`:384`) |
| `id_gestiune_init` | `FACT_VFACTURI.ID_GESTIUNE` | **GOL** |
| `id_pol` / `nume_politica` | **NU EXISTA pe `VANZARI`** — doar `VANZARI_DETALII.ID_POL` | **GOL** — vezi 1.5 |
| `id_ctr` / `contract` | `VANZARI.ID_CTR` / `VANZARI.CONTRACT` | **GOL** |
#### (c) Pliat — analitice (grupul A, read-only in #13, decizia 26)
| `poDate.` | Sursa | Azi |
|---|---|---|
| `id_sectie` / `sectie` | `FACT_VFACTURI.ID_SECTIE` / `SECTIE` | **CSD** |
| `id_lucrare` / `nrord` | `ID_LUCRARE` / `LUCRARE` | **CSD** |
| `id_responsabil` / `responsabil` | **NU EXISTA pe `VANZARI`** | **GOL, si nerecuperabil din `VANZARI`** — liniile sunt **comentate** in CSD (`:369-370`) **[V]** |
| `id_venchelt` / `venchelt` | **NU EXISTA pe `VANZARI`** | **GOL** — idem, comentate (`:373-374`) **[V]** |
> Faptul ca `ID_RESPONSABIL` si `ID_VENCHELT` **nu sunt coloane pe `VANZARI`** (verificat pe
> `all_tab_columns`) e **dovada structurala** ca grupul A traieste la nivel de **linie de nota**, nu de
> antet — exact ce spunea `rute_scriere_antet.md`, dar acum cu argument de schema, nu de cautare. **[V]**
#### (d) Pliat — alte date (`frm_alte_date`) si cei 14 parametri
| `poDate.` | Sursa | Azi |
|---|---|---|
| `id_delegat` / `nume_delegat` / `BIdelegat` / `CNPdelegat` | `ID_DELEGAT` / `DELEGAT` / `BIDELEGAT` / `CNPDELEGAT` | **CSD** |
| `id_masina` / `nrinmat` | `ID_MASINA` / `NRINMAT` | **CSD** |
| `id_agent` / `nume_agent` | `ID_AGENT` / `NUME_AGENT` | **CSD** |
| `id_facturare` / `adresa_facturare` | `ID_FACTURARE` / `ADRESA_FACTURARE` | **GOL** |
| `dataora_exp` | `DATAORA_EXP` | **GOL** |
| `text_aditional` | `TEXT_ADITIONAL` | **GOL** — plus capcana de la 1.7 |
| `nListareDetaliata` | `LISTARE_DETALIATA` | **GOL** |
| `tip_saft` | `TIP_SAFT` | **GOL** (`Init` pune `380`) |
| `eFactura` | `EFACTURA` | **GOL** (`Init` pune `0`) |
| *`id_ruta`* | `ID_RUTA` | **GOL, si nu exista proprietate pe `oDateFactura`** — vezi 1.5 |
| `afisare_scadenta` | `AFISARE_SCADENTA` | **GOL** (`Init` pune `1`; ROACONTRACTE il recalculeaza) |
| `tva_incasare` | `TVA_INCASARE` | **GOL** (`Init` ia `goCalendar.tva_incasare`) |
| `institutie_publica` | `FACT_VFACTURI.INSTITUTIE_PUBLICA` | **GOL** |
| `nTipFactura` | `TIP_FACTURA` | **GOL** |
| `nIdBeneficiar` | `ID_BENEFICIAR` | **GOL** |
| `id_ordl` | `ID_ORDL` | **GOL** |
| `coeficient_k` | `VANZARI.COEFICIENT_K` | **GOL** |
| grupul C (`ntip_incasare`, `serie_chit`, `nr_incasare`, `incasat`, `id_casa`) | `TIP_INCASAT`, `SERIE_INCASAT`, `NR_INCASAT`, `SUMA_INCASAT` | **GOL** — blocat in etapa I, dar vezi 2.3 |
#### (e) Discountul de document — nu e in `poDate`
**[V]** Discountul de document **nu are proprietate pe `oDateFactura`**: traieste in proprietatile
formularului `frm_facturare_articole.ndiscfactron` / `.ndiscfactval`
(`ofacturare.vc2:5286-5287` declaratie `*p:`, `:5317-5318` initializare la `0`,
`:11325`/`:11337` `ControlSource` pe cele doua textbox-uri, `:14272-14274` citirea la scriere).
| Ce | Sursa | Cum se incarca |
|---|---|---|
| discount de document, lei | `VANZARI.DISCOUNT` (in lei cand `IN_VALUTA=0`) | `Thisform.ndiscfactron` |
| discount de document, valuta | `VANZARI.DISCOUNT` (in valuta cand `IN_VALUTA=1`) | `Thisform.ndiscfactval`, plus `ndiscfactron = Round(ndiscfactval * Curs / multiplicator, …)` |
| `discount_evidentiat` | `VANZARI.DISCOUNT_EVIDENTIAT` | `poDate.discount_evidentiat` (`ControlSource` la `ofacturare.vc2:11305` si `:15968`) |
**Precedentul exact exista** si trateaza deja bifurcatia lei/valuta:
`ofacturare_stoc.prg:553-561` **[V]** — `lnDiscountVal = discount` cand `in_valuta = 1`, altfel
`lnDiscount = discount`. S8 copiaza acest tipar.
**Atentie [V]:** `oDateFactura.Init` pune `.discount_evidentiat = IIF(TYPE('gnDiscountEvidentiat')='N',
gnDiscountEvidentiat, 1)` (`ofacturare_comun.prg:238`) — **optiunea de firma, nu valoarea documentului**.
Daca S8 nu suprascrie, un document emis pe alta setare apare cu discountul evidentiat altfel decat a
fost emis, si — pentru ca `discount_evidentiat` **atinge sumele** (e parametru al lui `scrie_factura2`,
`ofacturare.vc2:14358`) — ar declansa **regenerare la simpla deschidere**. Fals pozitiv de acelasi tip
cu lookup-ul delegatului, dar cu efect mai scump.
### 1.3 Liniile — inventar
Coloanele intoarse de `cursor_retur_document(V_COPIERE=1)`, in ordinea din `SELECT`
(`PACK_FACTURARE:3963-4022`) **[V]**: `ID_C` (=`ROWNUM`), `ID_ARTICOL`, `LOT`, `SERIE`, `ID_POL`,
`ID_VALUTA`, `DISCOUNT_UNITAR`, `DISCOUNT_UNITAR_VAL`, `CODMAT`, `CODBARE`, `DENUMIRE`, `UM`,
`GESTIONABIL`, `CANTITATE`, `PROC_TVAV`, `ID_JTVA_COLOANA`, `PRETURI_CU_TVA`, `CURS`, `MULTIPLICATOR`,
`PRET`, `PRET_VAL`, `TIP_VALUTA`, `NUME_VAL`, `EXPLICATIE`, `ID_GESTIUNE`, `PRET_ACHIZITIE`, `PRETD`,
`ID_JTVA_COLOANA_EX`.
**Lipsesc, si sunt necesare pentru S8/S8b:** `ID_VANZARE_DET`, `TAXCODE`, `ID_CTR`, `CODMATC`,
`COD_UM_ISO`, `CONT`. Ultimele trei sunt in `crsfactura` si vin azi pe alte cai
(`do_adauga_articol` cere `codmatf` cu un `SELECT` separat, `ofacturare.vc2:12932-12934` **[V]**).
### 1.4 Doua capcane in `cursor_retur_document`, ambele verificate direct
**(i) `GESTIONABIL` cu `V_COPIERE = 1` vine din nomenclatorul CURENT, nu din document [V]**
(`PACK_FACTURARE:3990-3997`):
```
(case when V_PROFORMA = 1 then 0
when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE, starea de AZI
else A.GESTIONABIL -- NVL2(A1.ID_GESTIUNE,1,0), starea DOCUMENTULUI
end) AS GESTIONABIL
```
Pentru **copiere** e corect (documentul nou se emite cu regulile de azi). Pentru **editare** e o
divergenta tacuta: un articol care a fost facturat gestionabil si intre timp a fost trecut pe
`IN_STOC = 0` (sau invers) se incarca cu **alt regim** decat cel cu care e scris in `VANZARI_DETALII`.
Efectul practic: articolul trece pe alta ramura in `do_adauga_articol` (dialog de stoc vs. fara),
deci se schimba si ce se descarca la reemitere. **Recomandare: pe calea de editare se foloseste
`A.GESTIONABIL` (ramura `else`), adica adevarul documentului** — argument suplimentar pentru varianta
(B) din 1.1. **Aceasta e a cincea valoare din rezultatul 6 al handoff-ului (`IN_STOC` din nomenclator),
regasita pe calea de CITIRE, nu doar pe cea de scriere.**
**(ii) `PRET` vine rotunjit si cu `DIFERENTA` pliata inauntru [V]** (`PACK_FACTURARE:4008-4014`):
`ROUND(...) + A.DIFERENTA`. Confirma rezultatul 6 din handoff. Consecinta directa pentru **S8b**:
comparatia de pret la confirmare trebuie facuta pe **aceasta forma** (rotunjita + `DIFERENTA`), nu pe
`VANZARI_DETALII.PRET` brut — altfel orice document cu `DIFERENTA <> 0` apare modificat la deschidere.
### 1.5 Trei campuri de antet care nu au unde sa fie salvate — deci nu se pot restaura [V]
Verificat pe `all_tab_columns` pentru `VANZARI`:
| Camp | Constatare | Ce inseamna pentru S8 |
|---|---|---|
| **`zi_curs`** | **Nu exista coloana `ZI_CURS` pe `VANZARI`.** Se stocheaza rezultatul (`CURS`, `MULTIPLICATOR`, si `VANZARI_CURSURI` pe linie), nu ziua de la care s-a luat cursul. | **Inchide intrebarea deschisa din S8b §5.2.** Nu e „S8 trebuie sa suprascrie implicitul cu valoarea salvata" — **nu exista valoare salvata**. Se incarca `Curs`/`multiplicator` din document, iar `zi_curs` **se exclude din comparatie** si (recomandat) **se ascunde pe calea de editare**, ca sa nu sugereze o valoare pe care documentul n-o are. Ascunderea are precedent in acelasi `Do Case` (`ofacturare.vc2:9720-9725`, tipurile 8 si 9). |
| **`id_pol`** (politica de preturi, antet) | Nu exista pe `VANZARI`; exista **pe linie** (`VANZARI_DETALII.ID_POL`). | Antetul nu are ce sa afiseze ca „politica documentului". Recomandat: pe editare se afiseaza politica **doar daca e unica pe toate liniile**, altfel gol — si e oricum **blocat** (grupul B, 3.1). |
| **`id_ruta`** | Exista pe `VANZARI` (`ID_RUTA`) si e **unul din cei 14**, dar **`oDateFactura` nu are proprietate `id_ruta`** (verificat pe lista completa de proprietati, `ofacturare_comun.prg:99-218`). Azi valoarea circula prin `poRec` (`Scatter` din `crsfacturi`), nu prin `poDate` (`ofacturare_comun.vc2:4601`). | **Proprietate noua pe `oDateFactura`**, altfel `but_modifica` (S8c) n-are de unde sa citeasca ruta. Gol de umplut, semnalat aici pentru ca S8c il presupune existent. |
### 1.6 Legatura cu sursa — `id_comanda`, `id_ctr`, lista de avize
Ce exista, verificat:
| Sursa | Unde e salvata | Citibila? |
|---|---|---|
| comanda | `VANZARI.ID_COMANDA` (+ `VANZARI.COMANDA`, text) **[V]** | **DA**, direct din `FACT_VFACTURI.ID_COMANDA` |
| contract | `VANZARI.ID_CTR` (+ `VANZARI.CONTRACT`, text) **[V]** | **DA**, direct din `FACT_VFACTURI.ID_CTR` |
| avize | `VANZARI_CORESP(ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)` + `VANZARI.AVIZE` (text redundant) **[V]** (`PACK_FACTURARE:15494-15516`) | **DA ca date, NU ca procedura** — vezi mai jos |
**[V] Nu exista in `pack_facturare` nicio procedura care sa CITEASCA lista sursa a unui document.**
Cele 9 aparitii ale lui `VANZARI_CORESP` in export sunt: 6 in garzile lui `sterge_factura`
(`:5454-5530`), 1 `UPDATE ... STERS = 1` tot acolo (`:5582`), 1 subselect in
`scrie_cantitati_vanzari_avize` (`:6319`), 1 `INSERT` in `scrie_corespondente_vanzari` (`:15494`).
**Citirea e cod nou** — un `SELECT` simplu, dar de scris:
```sql
SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP
WHERE ID_VANZARE_FACT = :id AND STERS = 0 AND TIP = 1
```
**Semantica lui `TIP` nu e stabilita in acest raport [N]** — `scrie_corespondente_vanzari(V_TIP)`
comuta doar sursa listei (`clistaid_avize` pentru `TIP = 1`, `clistaid` altfel, `:15485-15493`), iar
valorile efective (`1/2/3`) vin din `CASE`-ul lui `finalizeaza_factura`, necitit aici. Handoff-ul
rundei 13 le da ca `1/2/3` folosite, `4` liber. **De confirmat ramura cu ramura la implementare**, nu
de preluat din raport — e exact tiparul care a produs cifra gresita a rundei 13.
**Nota de proiectare:** `poDate.listaid` e **suprasolicitat**. Pe calea de copiere/editare el trebuie sa
fie `id_vanzare`-ul documentului (ca `cursor_retur_document` sa-i citeasca liniile), dar pentru
reemitere `pack_facturare.clistaid` / `clistaid_avize` trebuie sa contina **lista sursei originale**.
Sunt **doua valori diferite in acelasi camp**, la momente diferite. Precedentul din
`relisteaza_ofacturare_stoc` arata ca problema e veche si **nerezolvata**: acolo
`poDate.listaid = Iif(poDate.tip = 3, Alltrim(Str(id_comanda)), [0])` **[V]**
(`ofacturare_stoc.prg:531`) — cu ramura de **contract comentata** la `:529`, deci nici acel precedent
nu restaureaza sursa pentru contracte sau avize. **S8 are nevoie de un camp separat**
(propunere: `poDate.cListaSursa` + `poDate.cListaSursaAvize`), nu de o a doua semnificatie a lui
`listaid`. Vezi si sectiunea 6.
### 1.7 `text_aditional` — trei transformari intre ce se vede si ce se salveaza [V]
1. **Truncat la 100 de caractere** la scriere: `LEFT(Nvl(poDate.text_aditional,[]),100)`
(`ofacturare.vc2:14356`, identic pe toate ramurile `scrie_*`).
2. **`CR+LF` inlocuit cu `Chr(170)`** imediat inainte:
`poDate.text_aditional = Strtran(poDate.text_aditional, Chr(13)+Chr(10), Chr(170), 1, 100, 1)`
(`:14281`) — **si atribuirea e pe `poDate` insusi**, deci obiectul ramane modificat dupa scriere.
3. Pe calea `modifica_date_factura` **nu exista** nici truncare, nici substitutie
(`ofacturare_comun.vc2:4608` trimite `?poRec.text_aditional` ca parametru legat) **[V]** —
deci **cele doua rute salveaza forme diferite ale aceluiasi camp**.
**Consecinte pentru S8:** la incarcare, `Chr(170)` trebuie convertit inapoi in `CR+LF` (altfel textul
apare pe un rand cu caractere ciudate), iar comparatia din S8b trebuie facuta pe forma **normalizata**
(vezi 5.2). Fara asta, orice document emis cu text pe mai multe randuri apare „modificat" la
deschidere. **Nesemnalat pana acum in niciun raport.**
---
## 2. Initializarile „pentru document nou" care trebuie sarite la editare
Cerinta obligatorie din S8b, rezultatul 5, plus inventarul cerut de briefing.
### 2.1 Lookup-ul „ultimul delegat / ultima masina" — **garda exista deja** [V]
`frm_alte_date.Init`, `COMUN\clase\ferestre_cere_date.vc2:3105-3208`. Codul real
(`:3110-3136`) — si aici raportul S8b si planul descriu situatia **incomplet**:
```
If poDate.eProforma = 0
If Empty(Nvl(poDate.id_delegat,0)) And Empty(Nvl(poDate.id_masina,0)) && :3111
poDate.id_delegat = 0
poDate.id_masina = 0
... cauta_date_ultima_factura[_tip](...) && :3118-3124
```
**Lookup-ul e deja conditionat de „ambele goale".** Deci:
- pentru un document care **are** delegat sau masina salvate, e suficient ca **S8 sa populeze
`poDate.id_delegat` / `id_masina` INAINTE de construirea lui `frm_alte_date`** — lookup-ul nu mai
ruleaza, fara niciun cod nou. `completeaza_setari_document` le pune deja (1.2.d), deci pe calea de
editare conditia e indeplinita **din ordinea de apel**, nu dintr-un semnal;
- **riscul real ramane, dar e mai ingust decat il descrie S8b:** el se manifesta **exact pe documentele
care n-au nici delegat, nici masina salvate** — un caz frecvent (multe facturi se emit fara delegat).
Pentru acelea, lookup-ul umple `poDate` cu ultimul delegat al clientului, si atunci: ori documentul
apare modificat, ori `modifica_date_factura` scrie **un delegat pe care documentul nu l-a avut
niciodata**. Sub `modifica_date_factura` campul se scrie **neconditionat** (`ID_DELEGAT = V_ID_DELEGAT`,
`PACK_FACTURARE:14462`) **[V]**, deci scrierea gresita e certa, nu probabila.
**Mecanismul propus (minim, si in acord cu tiparul existent):** parametru nou pe `frm_alte_date.Init`
sau — mai ieftin si mai greu de ratat — **o proprietate pe `poDate`**, `poDate.lEditare`, citita in
conditia de la `:3111`:
```
If !poDate.lEditare And Empty(Nvl(poDate.id_delegat,0)) And Empty(Nvl(poDate.id_masina,0))
```
Argumentul pentru proprietate pe `poDate` si nu parametru: `frm_alte_date` **nu e singurul** formular
care are initializari de acest tip (2.2), `poDate` e deja obiectul citit de toate, si e acelasi tipar
cu `lCopiere`, care exista deja pe `oDateFactura` (`ofacturare_comun.prg:212`) **[V]**.
**„Ce se intampla cand documentul chiar n-are delegat salvat":** cu garda de mai sus, `poDate.id_delegat`
ramane `.NULL.` (valoarea implicita din declaratia de clasa), campul se afiseaza gol, iar la salvare
`modifica_date_factura` primeste `NULL` si scrie `NULL` — **identic cu starea din baza**. Adica exact
comportamentul dorit: documentul ramane cum a fost. Fara garda, valoarea devine `0` (atribuirea de la
`:3112-3113`) **si apoi** rezultatul lookup-ului.
> **Diferenta fata de S8b, spusa explicit:** S8b cerea „sarirea explicita a lookup-ului"; codul real
> arata ca sarirea e **deja implicita** pentru documentele cu delegat, si e **necesara explicit** doar
> pentru cele fara. Concluzia lui S8b (S8 trebuie sa faca ceva) ramane valida; **motivul si perimetrul
> se schimba**, iar asta reduce riscul de la „primul document editat al unui client cu activitate
> recenta" la „primul document **fara delegat** al unui client cu activitate recenta".
### 2.2 Alte initializari de acelasi tip — inventar cu `fisier:linie` [V]
Cautate in `oDateFactura.Init`, `frm_alte_date.Init`, `frm_date_factura.Init`, `frm_date_aviz.Init`.
| # | Loc | Ce face | Efect pe calea de editare | Gravitate |
|---|---|---|---|---|
| 1 | `ofacturare_comun.prg:232-236` | `.Data`, `.dataireg`, `.dataact` = azi (sau ultima zi a lunii de lucru); `.datascad` din `gnZileScadentaFact`; `.zi_curs` = azi | Documentul apare cu **data de azi** si scadenta recalculata | **Mare** — atinge 2 din cei 4 campuri de identitate |
| 2 | `ofacturare_comun.prg:238` | `.discount_evidentiat` = `gnDiscountEvidentiat` (optiune de firma) | Vezi 1.2.e — poate declansa **regenerare** la simpla deschidere | **Mare** |
| 3 | `ofacturare_comun.prg:233` | `.tva_incasare` = `goCalendar.tva_incasare` (starea de azi a firmei) | Un document emis in alt regim se incarca cu regimul curent | Medie |
| 4 | `ofacturare_comun.prg:237` | `.in_valuta = 1` pentru `tnIdSet` / `tnTip` din lista | Derivat din tip, nu din document — coincide de obicei, dar nu prin constructie | Mica |
| 5 | `ofacturare_comun.prg:241-243` | `.initializeaza_politica_pret()` pentru tipurile 23, 30, 41 — apel `actualizeaza_politica_pret` | Politica **curenta**, nu cea a documentului | Medie (tipuri de transfer) |
| 6 | `ofacturare_comun.prg:240` | `.initializeaza_setari_document()` → `actualizeaza_document(tip)` → `id_fdoc` / `fdoc` | Reconfigureaza tipul de document din setarile curente | Medie |
| 7 | `ofacturare_comun.prg:245-283` | ramura **ROACONTRACTE** (`goContract`): suprascrie `id_client`, `nume_client`, `cod_fiscal`, `listaid`, `descriere`, `id_sectie`, `id_responsabil`, `id_valuta`, **si `.datascad = .dataact + lnScadentaIncasare`**, **si `.afisare_scadenta`** | Daca `goContract` exista in sesiune, **suprascrie antetul documentului editat cu datele contractului** | **Mare**, conditionata de context |
| 8 | `ofacturare_comun.prg:288-316` | ramura **ROACOMENZI** (`goComanda`, `tnTip = 3`): idem, `id_client`, `listaid`, `descriere`, `id_sectie`, `sectie` | Idem | **Mare**, conditionata |
| 9 | `ferestre_cere_date.vc2:3177-3179` | `If This.opt_incasat.Value = 2 → poDate.incasat = poDate.totalctva` | Suprascrie suma incasata cu totalul documentului | Medie (grup C, blocat in etapa I) |
| 10 | `ferestre_cere_date.vc2:3166-3171` | `poDate.id_casa = gnid_part_casa` (casa implicita a firmei) | Suprascrie casa documentului | Medie (grup C) |
| 11 | **`ferestre_cere_date.vc2:3183-3185`** | `IF poDate.tip = 2 AND poDate.afisare_scadenta = 0 AND AT([Scadenta la ], poDate.text_aditional) = 0` → **prefixeaza** `text_aditional` cu `„Scadenta la N zile."` | **A doua instanta a exact aceluiasi defect ca lookup-ul delegatului**, si pe un camp din cei 14: modifica `text_aditional` la `Init`, fara actiune a utilizatorului | **Mare** |
| 12 | `ferestre_cere_date.vc2:3150` | `If poDate.incasat <> 0 → Thisform.opt_incasat.Value = 2` — atribuire programatica pe `opt_incasat` | Declanseaza `ProgrammaticChange` → `actualizeaza_tipincasare()` → alocare/dezalocare de numere de chitanta (riscul deja documentat in `s3b_alte_date_analitice.md` §4.3) | Medie |
| 13 | `ofacturare.prg:207-209` | `poGeneratorNumere.ResetNumere()` + `creeaza_cursor_serii(nIdTipDoc)`, la fiecare trecere prin `factureaza` | **Aloca un numar nou** pe calea de emitere | **Mare** — vezi 4.2 |
| 14 | `ofacturare.prg:184-193` | `Do Case` pe `tnTip` care forteaza `poDate.nIdTipDoc` (5 = FACTURA / 6 = AVIZ) **inainte** de `completeaza_setari_document` | Pe editare, `nIdTipDoc` trebuie sa vina din document (proforma = 23, bon = 3), nu din tip | Medie |
**Nr. 11 merita subliniat**: garda lui (`AT([Scadenta la ], text_aditional) = 0`) il face inert pe un
document de contract emis **cu acelasi numar de zile de scadenta**. Devine activ exact cand
scadenta s-a schimbat de la emitere — adica precis cazul in care textul e cel vechi si trebuie
pastrat. **Metoda `Destroy`/`Unload` face si operatia inversa** (`:3037-3038`: `Strtran(...,[])`),
ceea ce inseamna ca pe calea normala prefixul e adaugat si scos in aceeasi sesiune; pe o cale de
editare care **nu** trece prin acelasi `Unload`, prefixul ar ramane. **[D]** — n-am urmarit toate
caile de iesire ale formularului.
### 2.3 Regula generala propusa
> **Orice camp al lui `poDate` populat prin apel Oracle sau dintr-o variabila globala/`go*` in
> `Init`/`Load` e o sugestie pentru document nou, nu o valoare de document.** Pe calea de editare,
> ordinea trebuie sa garanteze ca aceste sugestii **nu ajung sa se execute** (garda), nu doar ca sunt
> **suprascrise dupa** (ceea ce ar merge pentru valori, dar **nu** pentru efectele secundare — nr. 12
> si nr. 13 aloca numere, nu doar seteaza campuri).
---
## 3. Ce se blocheaza la editare, si de ce
### 3.1 Blocate — schimbarea lor ar insemna alt document
| Camp / control | De ce | Ruta care ar lipsi oricum |
|---|---|---|
| **Tipul documentului** (`Ct_clb_fdoc`, `poDate.tip` / `nIdTipDoc`) | Tipul decide ce sursa se consuma, ce nota se scrie si ce garzi se aplica. `do_copiaza` il **degradeaza** tocmai pentru ca un document copiat nu mai poate reconsuma sursa (`ofacturare_comun.vc2:3694-3711`) **[V]**; la editare degradarea e interzisa prin plan, deci tipul e fix. | grupul B — nicio ruta de scriere pe loc |
| **Sursa** (`Ct_clb_altele`) | Vezi 3.2 | grupul B |
| **Clientul** (`Ct_clb_nume_client`) | Schimbarea partenerului rescrie `IREG_PARTENERI`, `ACT`, soldurile — alt document | grupul B |
| **Valuta, `zi_curs`, gestiunea sursa, politica de preturi** | grupul B, decizia 25; in plus `zi_curs` si `id_pol` **n-au valoare salvata** (1.5) | grupul B |
| **Grupul C** (incasare) | decizia 25; in plus `opt_incasat` are efect secundar de alocare (2.2 nr. 12) | grupul C |
| **Grupul A** (venit/cheltuiala, sectie, responsabil, lucrare) | decizia 26 — read-only permanent in #13; in plus doua din patru **nu exista pe `VANZARI`** (1.2.c) | editarea e a lui #6 |
### 3.2 `Ct_clb_altele` — verificarea ceruta explicit, cu doua corectii la plan [V]
Planul (`:2952-2955`) spune: *„eticheta schimbata pe tip de `do_schimba_explicatia` („Nr. contract" /
„Nr. comanda" / „Nr. factura" / „Nr. facturi" / „Locatie", `ofacturare.vc2:9633-9643`), eliminat azi
din formular cand `gnScadereStoc = 0` si tipul e 1/5/10 fara copiere."*
Citit ramura cu ramura din `frm_date_factura.Init` (`ofacturare.vc2:9563-9796`), `Do Case`-ul de la
`:9615-9722`:
**Corectia 1 — lista de etichete e incompleta.** Intervalul `9633-9643` din plan e corect ca interval,
dar contine **sase** apeluri, nu cinci: `Nr. contract` (`:9633`, tip 2/6/52), `Nr. comanda` (`:9635`,
tip 3), **`Nr. aviz / avize` (`:9637`, tip 4)**, `Nr. factura` (`:9639`, tip 7), `Nr. facturi`
(`:9641`, tip 8/9), `Locatie` (`:9643`, tip 45). **Planul omite exact tipul 4** — cel pentru care
`Ct_clb_altele` tine **lista de avize**, adica singura sursa care trebuie refurnizata lui S9
(sectiunea 6). Omisiunea nu e cosmetica.
**Corectia 2 — conditia de eliminare e mai larga decat cea din plan.** `ct_clb_altele` e scos
(`RemoveObject`) in **trei** situatii, nu una:
| Ramura | Conditie | Ce face |
|---|---|---|
| `:9616-9628` | `gnScadereStoc = 0 And Inlist(poDate.tip, 1, 5, 10, **48, 49**)` | daca `llCopiere` → eticheta `Nr. factura` (`:9623`); altfel **`RemoveObject('ct_clb_altele')`** (`:9627`) |
| `:9651-9660` | **`gnScadereStoc = 1`** `And Inlist(poDate.tip, 1, 5, 10)` | idem (`:9653` / `:9658`) |
| `:9705-9712` (in `Otherwise`) | `Inlist(poDate.tip, 48, 49)` | **`RemoveObject('ct_clb_altele')`** neconditionat |
Deci: tipurile sunt **1, 5, 10, 48, 49** (nu doar 1/5/10), si eliminarea are loc si cand
**`gnScadereStoc = 1`**, nu doar cand e `0`. Formularea din plan („`gnScadereStoc = 0` si tipul e
1/5/10") descrie **una** din trei ramuri.
**Consecinta pentru S8, si e in favoarea noastra:** pe ramurile 1 si 2, `llCopiere = .T.` **pastreaza**
controlul si ii pune eticheta `Nr. factura`. Calea de editare, care va avea acelasi semnal ridicat
(`lCopiere` sau `lEditare`), **mosteneste automat pastrarea controlului** — nu e nimic de adaugat, doar
de **blocat**. Pe ramura 3 (tipurile 48/49) controlul e scos neconditionat; **[N]** n-am stabilit daca
tipurile 48/49 intra in perimetrul etapei II.
### 3.3 Ce ramane editabil
Cei 14 parametri, prin `but_modifica` (S8c), **plus** liniile (cantitati, preturi, discounturi,
explicatii, adaugari/stergeri) si discountul de document — care declanseaza regenerarea. Nimic din
sectiunea 3.1.
---
## 4. Ordinea reala de incarcare, si de ce conteaza
### 4.1 Secventa propusa
```
0. S7 a validat deja documentul (garzi) si a stabilit id_vanzare.
1. Citeste ANTETUL: SELECT ... FROM FACT_VFACTURI WHERE id_vanzare = :id
2. Citeste LISTA SURSA (avize) inainte de orice altceva: SELECT ... FROM VANZARI_CORESP (§6)
3. poDate = CreateObject("oDateFactura", 0, 0) <-- tnIdSet = 0 => Init NU ruleaza (§4.2)
4. poDate.lEditare = .T. <-- semnalul, inainte de orice formular
5. Populeaza poDate din antet: cei 14 + tip + eproforma + valuta/curs +
discount_evidentiat + client + gestiune + sursa (cListaSursa / cListaSursaAvize)
6. Populeaza thisform.ndiscfactron / ndiscfactval din VANZARI.DISCOUNT
7. Citeste LINIILE (canalul din §1.1(B)) -> crsarticole
8. GARDA DE RECALCUL: thisform.lIncarcare = .T.
9. Umple crsfactura din crsarticole, fara dialoguri (§4.3)
10. Adauga lista de preturi peste crsarticole (pentru S4g: adaugarea de articole noi)
11. thisform.lIncarcare = .F.
12. Un singur do_calculeaza_totaluri()
13. SNAPSHOT pentru S8b (§5)
14. Blocheaza antetul (S8c) si campurile din §3.1; arata formularul
```
### 4.2 De ce pasul 3 arata asa — `tnIdSet = 0` [V]
Tot corpul lui `oDateFactura.Init` e inchis in `If !Empty(m.tnIdSet)` (`ofacturare_comun.prg:225`).
Cu `tnIdSet = 0`, **niciuna dintre cele opt initializari de la 2.2 (nr. 1-8) nu se executa**, iar
constructorul devine inert. **Precedentul exista si e chiar cel de reincarcare a unui document:**
`relisteaza_ofacturare_stoc` face `poDate = Createobject("oDateFactura", 0, 0)`
(`ofacturare_stoc.prg:497`) **[V]** exact din acest motiv.
Asta rezolva **printr-o singura decizie de constructie** opt din cele paisprezece initializari
periculoase, fara nicio modificare in `oDateFactura` — si e mult mai robust decat „suprascriem dupa",
pentru ca nu depinde de completitudinea listei de suprascrieri.
**Ce ramane de tratat separat, pentru ca nu trece prin `Init`:**
- nr. 9-12 (`frm_alte_date.Init`) → garda `poDate.lEditare` de la 2.1;
- **nr. 13, `poGeneratorNumere`** — e apelat din `factureaza` (`ofacturare.prg:207-209`), nu din
`Init`. Pe calea de editare **nu trebuie sa se aloce numar**: seria si numarul vin din document
(decizia F / plan §F). `oGeneratorNumere` are deja `dezaloca_numar` si `verifica_numar`
(`ofacturare.prg:227`, `:250`), dar calea corecta e **sa nu se aloce deloc**. **[N]** —
`COMUN\programe\oserii_numere.prg` n-a fost citit in aceasta sesiune; planul citeaza `:227-233`
pentru afirmatia „la modificare nu se aloca numar nou". **De verificat la implementare** ca ocolirea
alocarii nu lasa `poDate.rezultat_serii` intr-o stare pe care `frm_date_factura.Init` o citeste
(`ofacturare.vc2:9736`: `If Inlist(poDate.rezultat_serii, 1, 2, 3) → clb_serie_act.do_initializeaza(...)`).
### 4.3 Pasul 9 e piesa care lipseste azi — si e cea mai mare
**[V]** Pe calea de copiere, `crsfactura` ramane gol; documentul se compune manual sau prin
`do_adauga_tot`. `do_adauga_tot` (`ofacturare.vc2:13169-13198`) face `SCAN` peste `crsarticole` si
cheama `do_adauga_articol(.T.)`, care (`:12871-12894`):
- pe ramura `poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45` — creeaza
`frm_articol_factura` **fara sa-l arate** (cu `tlImplicit = .T.`) si calculeaza totalurile. Acceptabil.
- pe ramura `Otherwise` — cheama **`do_alege_stoc(...)`**, adica **dialogul de alegere din stocul de
azi**. Pe calea de editare asta e gresit de doua ori: (a) ar putea cere interventia utilizatorului
pentru fiecare linie gestionabila la simpla deschidere a documentului; (b) ar alege **loturi/serii
din stocul curent**, desi documentul are deja `LOT`, `SERIE` si `ID_GESTIUNE` proprii, intoarse de
cursor.
In plus, `do_adauga_tot` are un `Do While` cu `aMessageBox("Nu ati selectat toata cantitatea …")`
(`:13188`) — **un modal per linie neacoperita**. Inacceptabil la incarcare.
Si `do_adauga_articol` scrie `Replace id_temp With Recno()` (`:12951` si `:13000`) **[V]** — deci chiar
daca sursa ar avea `ID_VANZARE_DET`, **nu ar ajunge in `crsfactura` pe aceasta cale**.
**Concluzie:** S8 nu poate refolosi `do_adauga_tot`. Ii trebuie o **populare directa**
`crsarticole → crsfactura` (un `INSERT INTO crsfactura ... SELECT ...` plus recalculul valorilor pe
linie prin `calculeaza_totaluri`), care:
1. **nu deschide niciun dialog**;
2. pastreaza `lot`, `serie`, `id_gestiune`, `id_pol`, `pretd` **din document**;
3. duce `id_vanzare_det` intr-o coloana proprie noua pe `crsfactura` (**nu** in `id_temp`, care e
suprasolicitat: `Recno()` pe o cale, `id_vanzare_det` pe alta — `prelucreaza_facturacrs` face
`id_vanzare_det As id_temp`, `ofacturare_comun.prg:1811` **[V]**);
4. duce `taxcode`.
Precedentul de structura exista: blocul `tnTip = 30` din `factureaza`
(`ofacturare.prg:340-378`) face exact o populare directa `crsarticole → crsfactura` prin
`INSERT INTO crsfactura (...) Values (...)`, fara niciun dialog **[V]**. Se cloneaza forma lui.
### 4.4 Garda de „nu recalcula acum" — unde, si de ce
`crsfactura` e `RecordSource`-ul gridului, iar coloanele au `Valid`/`InteractiveChange` care
recalculeaza. La populare programatica, `Valid` **nu** se declanseaza in VFP (se declanseaza doar la
iesirea din control, pe interactiune) — deci riscul principal **nu** e in grid, ci in:
- **`ControlSource` legate direct de `poDate`** (ex. `poDate.discount_evidentiat`,
`ofacturare.vc2:11305`) — atribuirea programatica declanseaza `ProgrammaticChange`, nu `Valid`
**[D]** (comportament VFP standard; nu l-am observat rulat aici);
- **`opt_incasat.Value =`** — 2.2 nr. 12, efectul secundar documentat;
- **`clb_discount.procent.Value =`** (`ofacturare.vc2:13475`) — daca S8 populeaza procentul de discount
in loc de suma, `InteractiveChange` ar recalcula discountul pe baza curenta, care in timpul popularii
e incompleta.
**Recomandare concreta:** un singur flag pe formular, `Thisform.lIncarcare`, testat la intrarea in
`do_calculeaza_totaluri` si in `actualizeaza_discount` (`ofacturare.vc2:13408-13415`), plus **regula ca
discountul de document se incarca ca SUMA** (`ndiscfactron`/`ndiscfactval`, valorile pe care le citeste
si scrierea, `:14272-14274`), **nu ca procent** — procentul se recalculeaza din suma la pasul 12
(`:13475`: `procent.Value = Round(ndiscfactron * 100 / nbazafdiscount, 2)`), nu invers.
### 4.5 Asezarea — decizia 5
Nimic din cele de mai sus nu schimba aspectul: nu se adauga banda de avertizare, coloane cu valori
initiale sau panou de diferente. Se schimba **titlul ferestrei** si **butonul principal** (decizia 9 /
S8c). Diferentele vizibile fata de introducere sunt **doar** cele care rezulta din blocarea campurilor
(3.1) si din ascunderea lui `zi_curs` (1.5) — ultima e o abatere minora de la „identic", propusa
motivat, **de confirmat de Marius** (intrebarea 4, sectiunea 8).
---
## 5. Interfata cu S8b — ce se retine ca „stare initiala"
### 5.1 Momentul
Snapshot-ul se ia la **pasul 13**: dupa incarcarea completa si dupa singurul recalcul, dar **inainte de
afisarea formularului**. Nu mai devreme (valorile derivate n-ar fi calculate) si nu mai tarziu (orice
`Init` de subformular ar putea polua — 2.2).
### 5.2 Ce se retine, si in ce forma
| Grup | Forma | Normalizare obligatorie inainte de comparatie |
|---|---|---|
| **Cei 14 parametri** | copie a valorilor din `poDate` (+ `id_ruta`, 1.5) intr-un obiect `poDateInitial` | `.NULL.` vs `0` vs `''` — functie unica aplicata **simetric**; `text_aditional`: **`LEFT(...,100)` + `Chr(170)`→`CR+LF`** pe ambele parti (1.7) |
| **Discountul de document** | `ndiscfactron` si `ndiscfactval`, **ca sume** | `Round(..., gnPc)` / `Round(..., gnPVal)` |
| `discount_evidentiat` | scalar | — |
| **Liniile** | copie a lui `crsfactura`, cheia = **`id_vanzare_det`** (coloana noua, 4.3) | `Round` la `gnPc` / `gnPPretV` / `gnPCant`; pretul comparat in forma **rotunjita + `DIFERENTA`** (1.4.ii) |
| **Explicatie / taxcode pe linie** | in aceeasi copie | `Alltrim`, `Nvl(...,'')` |
**Trei precizari care corecteaza sau completeaza S8b:**
1. **Cheia `id_vanzare_det` nu vine gratuit** — S8b o presupunea incarcata („coloana deja incarcata la
S8"). Nu e (1.1). Devine **livrabil al lui S8**, nu premisa.
2. **Liniile fara `id_vanzare_det`** (adaugate in sesiune) se marcheaza cu `0`/`.NULL.` si inseamna
direct „regenerare", ca in S8b §1.3.
3. **`zi_curs` se scoate din comparatie** (1.5) — altfel orice document ar aparea modificat.
### 5.3 Cele 7 campuri comune `scrie_factura2` / `modifica_date_factura`
Nimic de adaugat la S8b §3.1 — lantul cu prioritate ramane. S8 doar garanteaza premisa lui: **`poDate`
contine, la momentul confirmarii, valorile documentului plus editarile utilizatorului si nimic altceva**
— ceea ce e exact ce asigura sectiunile 2 si 4.
---
## 6. Interfata cu S9 — ce trebuie sa incarce S8 ca S9 sa poata refurniza lista sursa
Pasul 1 nou al lui S9 (handoff, rezultatul 7) cere **citirea listei sursa inainte de stergere**. S8 e
locul unde se citeste, pentru ca **dupa stergere `VANZARI_CORESP` are `STERS = 1`** (`:5582` **[V]**) si
`VANZARI.ID_COMANDA` / `ID_CTR` raman pe randul soft-sters.
**Ce incarca S8, si de unde:**
| Ce | Camp propus pe `poDate` | Sursa | Consumator la reemitere |
|---|---|---|---|
| lista de avize | `cListaSursaAvize` | `SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP WHERE ID_VANZARE_FACT = :id AND STERS = 0 AND TIP = 1` | `pack_facturare.clistaid_avize` → `scrie_corespondente_vanzari(1)` si `marcheaza_facturat` |
| comanda | `cListaSursa` | `FACT_VFACTURI.ID_COMANDA` | `pack_facturare.clistaid` |
| contract | `cListaSursa` (+ `id_ctr`) | `FACT_VFACTURI.ID_CTR` | idem, plus `CTR_RATE_FACTURI` |
| tipul original | `poDate.tip`, nedegradat | `FACT_VFACTURI.TIP` | `CASE`-ul pe `ntip` din `finalizeaza_factura` |
**Doua avertismente:**
1. **`poDate.listaid` NU poate purta aceasta informatie** — la incarcare el trebuie sa fie
`id_vanzare`-ul documentului insusi, ca sa se citeasca liniile (1.6). Doua campuri distincte,
nu unul reinterpretat.
2. **Semantica valorilor lui `TIP`** din `VANZARI_CORESP` **nu e stabilita aici [N]** (1.6) — se
citeste din `CASE`-ul real al lui `finalizeaza_factura` la implementare.
**Nu tine de S8**, dar se semnaleaza pentru ca S8 decide continutul lui `crsarticole`:
`do_scrie_factura` face `Select crsarticole` + `Calculate Sum(cantitate) To lnCantitateRamasa` pe
ramurile `poDate.Tip = 4` (`ofacturare.vc2:14305-14307`) si `Inlist(poDate.Tip,3,21,25,28,42,47)`
(`:14336-14338`) **[V]**, si pe baza sumei intreaba *„Doriti sa se inregistreze si avizul de retur?"* /
*„Doriti sa se inchida comanda?"* si seteaza `pnParametruAditional`. Pe calea de editare `crsarticole`
contine liniile documentului **plus lista de preturi adaugata peste ele** (`ofacturare.prg:465-473`
**[V]**), deci **suma e lipsita de sens** si intrebarea ar aparea gresit, cu efect real pe
`pnParametruAditional`. **De rezolvat in S9** — fie prin cursor separat pentru lista de preturi, fie
prin calcularea sumei doar peste randurile provenite din document.
---
## 7. Criteriul „gata cand", in forma testabila
**Comun tuturor tipurilor.** Pe un document existent, dupa deschidere si **inainte** de orice
interactiune:
| # | Ce se verifica | Cum |
|---|---|---|
| C1 | serie, numar, data, scadenta afisate = cele din `VANZARI` | comparatie ecran ↔ `SELECT serie_act, numar_act, data_act, data_scad FROM vanzari WHERE id_vanzare = :id` |
| C2 | **niciun numar nou alocat** | inainte/dupa: ultimul numar din generatorul de serii pentru `nIdTipDoc` e neschimbat |
| C3 | numarul de linii din grid = `SELECT count(*) FROM vanzari_detalii WHERE id_vanzare = :id AND sters = 0` | |
| C4 | pe fiecare linie: cantitate, pret, discount, gestiune, lot, serie, cota TVA, explicatie, `taxcode` = valorile din `VANZARI_DETALII` (pret in forma rotunjita + `DIFERENTA`, 1.4.ii) | |
| C5 | totalurile afisate = `VANZARI.TOTAL_FARA_TVA` / `TOTAL_TVA` / `TOTAL_CU_TVA` | |
| C6 | discountul de document si `discount_evidentiat` = `VANZARI.DISCOUNT` / `DISCOUNT_EVIDENTIAT` | |
| C7 | delegat, masina, agent, adresa de facturare, `dataora_exp`, `text_aditional`, `listare_detaliata`, `tip_saft`, `efactura`, `id_ruta` = `VANZARI` | inclusiv **cazul „documentul n-are delegat"** → campul ramane gol (2.1) |
| C8 | **nicio scriere in baza** — criteriul de baza al lui S8b | `SELECT dataoras, id_utils FROM vanzari WHERE id_vanzare = :id` neschimbat dupa deschidere+inchidere; idem pe `VANZARI_DETALII` |
| C9 | **niciun dialog modal** la deschidere (nici alegere de stoc, nici „nu ati selectat toata cantitatea", nici „doriti sa inchideti comanda") | observatie |
| C10 | aspectul e cel de introducere (decizia 5): fara banda, fara coloane de valori initiale, fara panou de diferente; difera doar titlul si butonul principal | observatie |
**Pe fiecare tip de sursa, in plus:**
| Tip | Ce se verifica specific |
|---|---|
| **comanda** (3, 21, 25, 28, 42, 47) | `Ct_clb_altele` afiseaza numarul comenzii, eticheta „Nr. comanda", **blocat**; `poDate.cListaSursa` = `VANZARI.ID_COMANDA` |
| **contract** (2, 6, 26, 52) | eticheta „Nr. contract", blocat; `id_ctr` incarcat; **`goContract` din sesiune NU suprascrie antetul** (2.2 nr. 7); daca S10 se implementeaza, **niciun avertisment de re-derivare la simpla deschidere** (`cursor_retur_document` citeste pretul stocat, rezultatul 6 al handoff-ului) |
| **aviz** (tip 4 = factura din avize) | eticheta **„Nr. aviz / avize"** (3.2), blocat; `poDate.cListaSursaAvize` = randurile `VANZARI_CORESP` ale documentului; **nicio intrebare „doriti sa se inregistreze si avizul de retur?"** la deschidere (§6) |
| **factura simpla** (1, 5, 10) | `Ct_clb_altele` **pastrat si vizibil** cu eticheta „Nr. factura" (3.2, ramurile 1 si 2 cu `llCopiere`), spre deosebire de emiterea normala unde e scos |
| **retur** (8, 9, 24) | `zi_curs` e oricum scos azi pentru 8/9 (`ofacturare.vc2:9720-9725`) — comportament neschimbat; liniile se incarca cu cantitatile documentului, nu cu cele disponibile la retur |
| **ROAAUTO** | `poDate.id_ordl` incarcat din `VANZARI.ID_ORDL`; **[N]** nu s-a verificat in aceasta sesiune ce alte campuri specifice ROAAUTO exista pe antet — `roaauto_facturi.md` nu a fost citit |
| **proforma** (`eproforma = 1`) | `poDate.eProforma` incarcat (1.2.a) → `frm_alte_date.Init` sare din start pe ramura de proforma (`:3110`), iar `gestionabil` vine `0` din cursor (1.4) |
---
## 8. Riscuri, goluri ramase, si intrebarile pentru Marius
### 8.1 Ce n-am putut stabili, si de ce
| # | Ce | De ce |
|---|---|---|
| N1 | Semantica exacta a valorilor `VANZARI_CORESP.TIP` (`1/2/3`, `4` liber) | Cere citirea `CASE`-ului pe `ntip` din `finalizeaza_factura`, in afara perimetrului parcurs. **Nu se preia din raportul precedent** — e exact tiparul cifrei gresite din runda 13. |
| N2 | Daca ocolirea alocarii de numar lasa `poDate.rezultat_serii` intr-o stare pe care `frm_date_factura.Init` o citeste gresit (`ofacturare.vc2:9736`) | `COMUN\programe\oserii_numere.prg` necitit in aceasta sesiune |
| N3 | Daca prefixul „Scadenta la N zile." adaugat la `Init` (2.2 nr. 11) e scos pe **toate** caile de iesire | Am vazut operatia inversa la `ferestre_cere_date.vc2:3037-3038`, dar n-am urmarit toate caile de `Unload`/`Destroy` |
| N4 | Campurile de antet specifice **ROAAUTO** | `roaauto_facturi.md` necitit |
| N5 | Daca tipurile **48/49** (custodie) intra in perimetrul etapei II | Conteaza pentru 3.2, ramura 3 |
| N6 | Daca `frm_facturare_articole2` (varianta paralela) trebuie tratata identic | Are `do_adauga_tot` / `do_adauga_articol` proprii (`ofacturare.vc2:17476`, `:17124`), cu logica usor diferita (fara testul `llGestionabil`) — **de decis daca S8 tinteste ambele forme sau doar `frm_facturare_articole`** |
### 8.2 Intrebari pentru Marius, cu recomandare
1. **Canalul de citire a liniilor: (A) extindem `cursor_retur_document`, (B) procedura noua
`cursor_editare_document`, sau (C) `FACT_VFACTURI_DETALII`?** (1.1)
*Recomandare:* **(B)**. Argumentul nu e estetica, ci ca (A) schimba **comportamentul copierii**
(`taxcode` ar incepe sa se propage), iar (C) pierde `ID_POL`, `PRETD` si tratamentul valutar —
partea grea. (B) da si libertatea de a alege corect `GESTIONABIL` (1.4.i).
2. **`GESTIONABIL` la editare: din nomenclatorul de azi (`IN_STOC`) sau din document (`ID_GESTIUNE`)?**
(1.4.i)
*Recomandare:* **din document**. Un document editat trebuie sa arate cum a fost emis; regimul de
stoc al articolului s-a putut schimba intre timp fara nicio legatura cu documentul.
3. **`text_aditional`: se normalizeaza la incarcare (`Chr(170)` → `CR+LF`) sau se afiseaza ca atare?**
(1.7)
*Recomandare:* **se normalizeaza la incarcare si se re-normalizeaza la comparatie**, altfel orice
document cu text pe mai multe randuri apare modificat la deschidere. Nota: cele doua rute de scriere
salveaza **forme diferite** ale campului (truncat/substituit vs. brut) — merita semnalat separat,
e un defect preexistent, nu al lui #13.
4. **`zi_curs` pe calea de editare: ascuns, sau afisat gol?** (1.5)
*Recomandare:* **ascuns**, cu precedent in acelasi `Do Case` (tipurile 8/9). E o abatere mica de la
decizia 5, si o semnalez ca atare — un camp afisat gol pe un document care are curs ar fi mai
derutant decat absenta lui.
5. **`poDate.lEditare` ca proprietate pe `oDateFactura`, sau parametru pe `frm_alte_date.Init`?** (2.1)
*Recomandare:* **proprietate**, pentru ca sunt **cel putin patru** locuri care au nevoie de semnal
(2.2 nr. 9-13), nu unul, si pentru ca `lCopiere` e deja acolo, cu exact acelasi rol.
6. **`id_ruta` — proprietate noua pe `oDateFactura`?** (1.5)
*Recomandare:* **da**. Fara ea S8c nu poate implementa unul din cei 14 parametri, iar azi valoarea
circula doar prin `poRec`, care nu exista in formularul unificat.
7. **Tipurile 48/49 si `frm_facturare_articole2`** (N5, N6) — intra in perimetrul etapei II?
*Recomandare:* **nu acum**; se declara explicit ca neacoperite, ca sa nu se descopere la testare.
8. **Defectul de la 2.2 nr. 11** (prefixarea `text_aditional` la `Init` pentru contracte) — se repara
in #13, se semnaleaza separat, sau se lasa?
*Recomandare:* **se ocoleste in #13** (prin `lEditare`) si **se semnaleaza separat** ca defect
preexistent — repararea lui pe calea de emitere nu tine de aceasta poveste.
### 8.3 Ce a fost corectat fata de materialele existente
| Afirmatie anterioara | Stare |
|---|---|
| S8b: „lookup-ul delegatului trebuie sarit explicit, altfel pica primul criteriu" | **Nuantat** — garda `Empty(id_delegat) And Empty(id_masina)` exista deja (`ferestre_cere_date.vc2:3111`); riscul e real **doar** pe documentele fara delegat si fara masina |
| S8b §1.2 / §7.4: „cheia de linie `id_vanzare_det`, coloana deja incarcata la S8" | **Infirmat** — `cursor_retur_document` nu o intoarce; devine livrabil al lui S8 |
| S8b §5.2: „S8 trebuie sa suprascrie implicitul `zi_curs` cu data reala salvata" | **Infirmat structural** — nu exista coloana `ZI_CURS` pe `VANZARI` |
| Plan S8: etichetele lui `Ct_clb_altele` (cinci) | **Incomplet** — sunt sase; lipseste „Nr. aviz / avize" (tip 4), exact cazul relevant pentru S9 |
| Plan S8: „eliminat cand `gnScadereStoc = 0` si tipul e 1/5/10 fara copiere" | **Incomplet** — trei ramuri, tipurile `1,5,10,48,49`, si pentru `gnScadereStoc = 1`, nu doar `0` |
| Plan S8: „`cursor_retur_document(V_COPIERE=1)` pentru linii" | **Insuficient** — umple selectorul, nu documentul; lipsesc `ID_VANZARE_DET` si `TAXCODE` |
| Plan S8: „`completeaza_setari_document` pentru antet" | **Insuficient** — 16 proprietati din ~120, niciuna de identitate |
---
*Cercetare incheiata pe toate cele 8 puncte cerute in briefing. Afirmatiile portante au `fisier:linie`
sau nume de coloana din dictionar. Nu s-a modificat niciun fisier de cod; pe Oracle numai `SELECT` pe
`all_tab_columns`.*

View File

@@ -1,311 +0,0 @@
# S8b — Proiectare: rutarea scrierii dupa ce utilizatorul a schimbat ceva
Livrabilul povestii **S8b** din `docs\plan_13_unificare_formular_facturare.md:2890-2898` (G-bis,
deciziile 6/9/25/26). Cercetare READ-ONLY — zero editari de cod, zero `git_sync.ps1`/`txt2vcx.ps1`,
zero commit, zero scriere Oracle (doar SELECT, nefolosit efectiv — tot ce trebuia era in codul VFP/
PL-SQL deja exportat sau in rapoartele de referinta citate in briefing).
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar
citite, neatinse.
## Verdict (rezumat, 8 randuri)
Reteta din G-bis e corecta ca directie, dar are un gol nedocumentat pana acum: **`modifica_date_factura`
nu e singura ruta care scrie cei 14 parametri de antet** — 7 din 14 (`id_delegat`, `id_masina`,
`id_facturare`, `listare_detaliata`, `dataora_exp`, `id_agent`, `text_aditional`) sunt scrisi **si**
de calea normala de emitere (`scrie_factura2`, apelata direct de regenerare, decizia 35), pentru ca
ambele cai citesc din **acelasi obiect `poDate`**, nu din doua reprezentari separate (sectiunea 3,
dovada pe cod). Consecinta directa pentru punctul cel mai periculos al sarcinii (schimbare simultana
antet+sume): **cand regenerarea porneste, NU se mai cheama `modifica_date_factura` in plus** — ar fi
fie redundant (pe cele 7 campuri comune), fie periculos (ar scrie pe un `ID_VANZARE` care tocmai a
fost soft-sters sau inca nu exista). Regenerarea *este* deja calea de scriere a antetului cand sumele
se schimba, nu o cale separata care trebuie compusa cu ea. Explicatia de linie e acoperita direct de
`adauga_articol_factura` (parametru `V_EXPLICATIE`, plus `V_TAXCODE`), deci regenerarea o transporta
fara cod suplimentar — dar **doar daca cursorul de linii incarcat la S8 e re-citit din formular, nu
din snapshot-ul initial**. Cel mai probabil loc de fals-pozitiv pentru „deschid si inchid fara sa
modific nimic” e lookup-ul „ultimul delegat/masina al clientului” din `frm_alte_date.Init`
(sectiunea 5) — cod construit pentru emiterea unui document nou, periculos daca ruleaza neschimbat pe
calea de editare.
---
## 1. Mecanismul de detectare a schimbarii
### 1.1 Optiuni si ce se strica la fiecare
| Optiune | Ce se strica |
|---|---|
| **Hash/checksum pe randuri** | Ascunde exact tipul de fals-pozitiv cel mai periculos aici: doua reprezentari numeric-egale dar text-diferite (`Str(12.5,18,2)` vs `Str(12.50,18,2)`, `.NULL.` vs `0` vs `""`) produc hash-uri diferite desi valoarea „reala" e identica. Orice normalizare facuta *inainte* de hash trebuie sa fie deja perfecta — hash-ul nu adauga nimic, doar ascunde bug-urile de normalizare in loc sa le arate. |
| **Flag-uri `lModificat` in evenimentele de editare** | E robust doar daca *fiecare* eveniment care poate schimba o valoare seteaza flag-ul — un `ControlSource` legat direct (binding automat VFP, cazul majoritatii campurilor de antet aici, vezi `modifica_date_factura_parametri.md` §4) nu trece printr-un eveniment scriptat, deci flag-ul ramane `.F.` desi valoarea s-a schimbat. Whitelist fragil: la fiecare control nou adaugat, cineva trebuie sa-si aminteasca sa cablasje flag-ul. Cel mai riscant pentru campurile din `frm_alte_date` (S3b), unde `ControlSource=poDate.xxx` e tiparul dominant. |
| **Snapshot la incarcare + comparatie camp cu camp la confirmare** (recomandat) | Cere disciplina la normalizare (precizie, `.NULL.`), dar e singura optiune care **nu poate rata o schimbare structurala** — compara starea finala, nu istoricul de evenimente, deci un binding automat care a schimbat o valoare fara eveniment scriptat tot apare in diff. E si optiunea deja folosita implicit in codebase pentru un caz inrudit: `chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))` (`ofacturare_comun.vc2:5736`) testeaza starea curenta, nu un istoric de evenimente. |
**Recomandare: snapshot + comparatie la confirmare**, din motivul de mai sus (robustete la binding
automat) — argumentat, nu doar preferat.
### 1.2 Ce se snapshoteaza, concret
- **Antet**: o copie profunda a lui `poDate` (sau a subsetului de proprietati relevante) luata
imediat dupa S8 (incarcare), **inainte** de orice lookup auto-completat (vezi sectiunea 5 — ordinea
conteaza: daca lookup-ul „ultimul delegat" ruleaza inainte de snapshot, snapshot-ul insusi e deja
poluat, si orice comparatie ulterioara devine inutila).
- **Linii**: o copie a cursorului `crsfactura` (nume alias de confirmat la implementare — S4d il
citeaza ca atare) imediat dupa populare la S8, cu cheia de linie **`id_vanzare_det`** (coloana deja
incarcata la S8 din `VANZARI_DETALII`, cf. plan S8: „explicatia si taxcode pe fiecare linie" — nu
exista alt candidat de cheie stabila in materialele citite; **de confirmat exact numele coloanei in
cursor la implementare**, nu presupus mai departe aici).
- **Discountul de document** (`VANZARI.DISCOUNT`, `discount_evidentiat`) — campuri de antet cu efect
de suma, tratate separat de cele 14 (vezi matricea, sectiunea 2).
### 1.3 Capcanele reale de VFP, cu tratament explicit
| Capcana | Tratament recomandat |
|---|---|
| `.NULL.` vs `0` vs `""` | Comparatie prin functie de normalizare unica (`NVL`-style) aplicata **simetric** pe ambele parti (snapshot si curent) inainte de `=`, nu doar pe una — o comparatie `Nvl(nou,0) == vechi` fara acelasi tratament pe `vechi` e asimetrica si poate rata cazul `vechi=.NULL., nou=0` ca "neschimbat" cand de fapt utilizatorul a introdus explicit un zero peste un camp gol (relevant pt. campuri ca `id_ruta`/`id_agent`, unde `.NULL.` si `0` pot avea semnatura diferita in Oracle — `modifica_date_factura` scrie orice i se da, inclusiv `NULL` neconditionat, deci diferenta chiar conteaza acolo). |
| Precizie numerica (`gnPc`, `gnPPretV`, `gnPCant`) | Comparatia nu se face pe valoarea `Double` bruta, ci pe reprezentarea **rotunjita la aceeasi precizie folosita la scriere** — acelasi `Round(x, gnPc)`/`Round(x, gnPPretV)`/`Round(x, gnPCant)` aplicat pe ambele parti inainte de `=`. Motiv concret: `adauga_articol_factura` primeste pretul ca `Str(..., 18, gnPc)` (text), deci orice zgomot de reprezentare in binar (ex. `12.4999999999` vs `12.5`) care nu apare si in textul trimis Oracle-ului nu trebuie sa declanseze regenerare — regenerarea trebuie sa porneasca de la o diferenta **care ar produce efectiv un text diferit trimis la Oracle**, nu de la zgomot de virgula mobila intern VFP. |
| Randuri sterse/adaugate, nu doar modificate | Comparatie pe **multimea cheilor** `id_vanzare_det`, nu pe pozitie: chei prezente in snapshot dar absente in curent = sters; chei prezente in curent dar absente in snapshot (sau `id_vanzare_det` gol/`0`, sentinela de linie noua) = adaugat; chei prezente in ambele = potential modificat, comparat camp cu camp. **Orice** rand adaugat sau sters, indiferent de continutul lui, marcheaza direct pentru regenerare (tabelul din G-bis: „linii adaugate/sterse” → regenerare) — nu are sens sa se compare campuri pe un rand care oricum nu exista pe ambele parti. |
| Ordinea randurilor | **Nu conteaza pentru detectie** — comparatia e pe chei (set), nu pe pozitie in grid. Ordinea ar conta doar daca reordonarea insasi ar fi o schimbare semnificativa pentru Oracle (nu e cazul — `adauga_articol_factura` nu are parametru de ordine vizibil in semnatura citata in `s10_pret_rederivat.md` §1). **Rezerva**: daca formularul unificat permite reordonare manuala a liniilor si aceasta conteaza pentru vreun raport (necercetat aici), ar trebui un camp explicit de ordine comparat separat — nu presupus din pozitia in cursor. |
---
## 2. Matricea „ce s-a schimbat → ce ruta", exhaustiva
Sursa de adevar: G-bis (`plan:812-889`) + `modifica_date_factura_parametri.md` + `rute_scriere_antet.md`.
| Camp / grup | Ruta | De ce nu una mai ieftina |
|---|---|---|
| **Cei 14 parametri** (serie, numar, data, scadenta, ruta, delegat, masina, agent, `dataora_exp`, adresa facturare, text aditional, `listare_detaliata`, `tip_saft`, `efactura`) | `modifica_date_factura`, **daca nimic altceva nu s-a schimbat in aceeasi sesiune** | E singura cale directa pe `VANZARI`+`DOCUMENTE`+`ACT`+`IREG_PARTENERI`+`JV2007`+`RUL` care nu atinge sumele — mai ieftina decat regenerarea (fara stergere+reemitere, fara stoc, fara nota noua). Regenerarea ar face acelasi lucru, dar cu cost mult mai mare (tranzactie stergere+reemitere, stoc, ID_VANZARE nou) pentru un camp care nu atinge nicio suma — nu se justifica. |
| **Explicatia si `taxcode` pe o linie**, **fara nicio alta schimbare pe linii** | `modifica_explicatie_articol` | Update pe doua coloane, fara sume — regenerarea ar fi disproportionata (stergere+reemitere completa pentru un text). |
| **Cantitati, preturi, discount pe linie, gestiune, cota TVA, serie/lot** | Regenerare | Nu exista alta ruta — verificat exhaustiv, nicio procedura Oracle de tip "modifica cantitate/pret pe linie existenta" (confirmat indirect: singura cale de scriere a sumelor e `adauga_articol_factura`, apelata doar la emitere/reemitere). |
| **Linii adaugate/sterse** | Regenerare | Identic — nu exista `sterge_linie_factura`/`adauga_linie_factura` separat de fluxul de emitere. |
| **Discountul de document** (`VANZARI.DISCOUNT`, `discount_evidentiat`) | Regenerare | Nu e printre cei 14 parametri ai `modifica_date_factura` (confirmat, tabelul din `modifica_date_factura_parametri.md` §1-2 nu il contine) si atinge direct sumele — cade natural in categoria "orice atinge sumele" din G-bis. |
| **Grupul B** — tip document, valuta, zi curs, client, sursa, gestiune sursa, politica de preturi | Regenerare (decizia 25); **blocate in etapa I** | `rute_scriere_antet.md` §2 — verdict NU pentru toate 7, cautat exhaustiv in Oracle si VFP; regenerarea (calea de emitere) e singura care le scrie, pentru ca sunt scrise o singura data la `scrie_factura2`/echivalent, niciodata actualizate separat. |
| **Grupul C** — incasare (mod, casa, serie/nr chitanta, suma, POS) | Regenerare (decizia 25); **blocate in etapa I** | `rute_scriere_antet.md` §3 — incasarea devine ea insasi o linie de nota (`scrie_incasare2`), scrisa doar la emitere; nicio ruta „modifica incasare" separata. |
| **Grupul A** — venit/cheltuiala, sectie, responsabil, lucrare | **Niciuna din #13** — read-only (decizia 26) | Editabile deja, dar la nivel de LINIE de nota, prin #6 (`frm_modific2024`) — #13 le-ar scrie uniform pe tot documentul, risc de suprascriere tacita a unei diferentieri facute din #6. Zero suprapunere intre fire, per decizia lui Marius din 09.08.2026. |
| **Datele din sectiunea pliata — delegat/transport, adresa facturare, text aditional** | Fac parte din cei 14 parametri → `modifica_date_factura` daca nimic altceva nu s-a schimbat; **regenerare** daca si sumele s-au schimbat (vezi sectiunea 3) | Sunt deja acoperite de tabelul de mai sus prin `V_ID_DELEGAT`/`V_ID_MASINA`/`V_ID_AGENT`/`V_ID_FACTURARE`/`V_TEXT_ADITIONAL`/`V_DATAORA_EXP`. |
| **Nimic schimbat** | Nimic | Explicit cerut de G-bis — altfel orice deschidere ar produce scriere degeaba. |
---
## 3. Cazurile care cad intre rute — descoperirea centrala a acestei cercetari
### 3.1 Antet + sume schimbate in aceeasi sesiune — verdict cu dovada, nu presupunere
**Intrebarea din briefing**: se cheama ambele rute, sau regenerarea le acopera pe amandoua?
**Raspuns, verificat pe cod**: **regenerarea acopera 7 din cei 14 parametri prin insusi mecanismul
de emitere, fara nicio ruta suplimentara** — pentru ca `poDate` e **acelasi obiect** care alimenteaza
atat afisarea antetului cat si apelul `scrie_factura2` folosit de regenerare (decizia 35: "reemiterea
scrie prin `pack_facturare`, pe acelasi drum ca emiterea").
Dovada directa, apelul `scrie_factura2` (`COMUN\clase\ofacturare.vc2:14345-14359`, identic la
`:14373-14387` si `:18343-18387`):
```
lcSql = [{call pack_facturare.scrie_factura2(] + ;
... pnTotalFtva, pnTotalTva, pnDiscount, serie_chit, nr_incasare, lcListaIncasare, ;
IIF(Isnull(poDate.id_delegat),[NULL],Alltrim(Str(poDate.id_delegat))) + [,] + ;
IIF(Isnull(poDate.id_masina),[NULL],Alltrim(Str(poDate.id_masina))) + [,] + ;
IIF(Isnull(poDate.id_facturare),[NULL],Alltrim(Str(poDate.id_facturare))) + [,] + ;
IIF(Isnull(poDate.nListareDetaliata),[0],Alltrim(Str(poDate.nListareDetaliata))) + [,] + ;
[to_date('] + Ttoc(poDate.dataora_exp,1) + [','YYYYMMDDHH24MISS'),] + ;
IIF(Isnull(poDate.id_agent),[NULL],Alltrim(Str(poDate.id_agent))) + [,] + ;
['] + OracleSpecialCharacters(...(poDate.text_aditional...)) + [',] + ;
Alltrim(Str(poDate.discount_evidentiat)) + [,] + ;
ALLTRIM(Str(pnParametruAditional)) + [,?@poDate.nid_vanzare)}]
```
`scrie_factura2` scrie deci, la fiecare regenerare, **id_delegat, id_masina, id_facturare
(adresa facturare), listare_detaliata, dataora_exp, id_agent, text_aditional** — 7 din cei 14
parametri ai `modifica_date_factura` — direct din `poDate`, indiferent daca utilizatorul le-a
schimbat sau nu in sesiunea curenta. **Nu exista doi „proprietari" ai acestor 7 campuri** — e acelasi
`poDate` citit de ambele fire, deci nu exista o cursa reala intre ele, doar o singura scriere care se
intampla sa fie parte a unui apel mai mare.
**Verdict, cu ordinea explicita**:
1. **Cand regenerarea porneste, NU se mai cheama `modifica_date_factura`.** Ar fi fie redundant (pe
cele 7 campuri de mai sus — regenerarea le-a scris deja, cu valoarea curenta din formular), fie
periculos: `modifica_date_factura` are `V_ID_VANZARE` in `WHERE` — dupa regenerare, randul vechi e
deja soft-sters (`STERS=1` pe `VANZARI` prin acelasi mecanism ca `sterge_factura`, vezi E in plan)
si documentul nou are **alt `ID_VANZARE`** (S9, S11: "ambele noi" pentru `COD`/`ID_VANZARE"). Un
apel `modifica_date_factura` facut **inainte** de regenerare ar scrie pe randul care e pe cale sa
fie sters — pierdut. Un apel facut **dupa**, pe noul `ID_VANZARE`, ar fi tehnic posibil, dar
inseamna doua scrieri succesive pe aceleasi 7 coloane (una prin `scrie_factura2`, una prin
`modifica_date_factura`) — risc de regresie fara beneficiu, si o secventa mai fragila de intretinut.
2. **Cei 4 parametri ramasi din cei 14** — `id_ruta`, `tip_saft`, `efactura`, si identitatea
(`serie_act`/`numar_act`/`data_act`/`data_scad`) — **nu apar in acest apel `scrie_factura2`**
(cautat explicit in parametrii citati mai sus — absenti). Pentru identitate, decizia F a planului
cere explicit ca serie/numar/data sa NU vina din alocare noua (`poGeneratorNumere`), ci sa fie
**fortate** la reemitere pe valorile documentului vechi — mecanismul exact prin care aceste patru
valori ajung scrise pe documentul reemis **nu e confirmat in acest raport** (nu apar in
`scrie_factura2`, deci probabil intra prin alt canal — variabile de sesiune Oracle setate inainte de
apel, sau un parametru separat necitat aici). **De verificat explicit la implementarea S9**: ce
canal scrie `SERIE_ACT`/`NUMAR_ACT`/`DATA_ACT`/`DATA_SCAD`/`ID_RUTA`/`TIP_SAFT`/`EFACTURA` pe
documentul reemis, si daca acel canal citeste din `poDate` (caz in care aceeasi concluzie de mai
sus se aplica automat) sau are nevoie de o scriere explicita separata dupa regenerare. **Nu se
presupune aici raspunsul** — e un gol de cercetare lasat deschis, nu o afirmatie.
3. **Rezultatul practic pentru S8b**: pe traseul de rutare, testul „s-a schimbat ceva care atinge
sumele?" trebuie evaluat **inaintea** testului „s-a schimbat antetul?" — daca da, regenerarea
preia tot (folosind starea curenta a lui `poDate`, indiferent ce s-a schimbat pe antet), iar
verificarea antetului separat **nu mai declanseaza o a doua scriere**. Ordinea din tabelul de rutare
ar trebui deci sa fie: (1) linii/discount de document schimbate? → regenerare, gata; (2) altfel,
antet schimbat (14 parametri)? → `modifica_date_factura`; (3) altfel, doar explicatie de linie? →
`modifica_explicatie_articol`; (4) altfel → nimic. Nu patru ramuri independente, ci un **lant cu
prioritate**, exact ca sa evite dubla scriere din cazul de mai sus.
### 3.2 Explicatia de linie, cand si sumele s-au schimbat
**Cerinta din briefing, verificata**: daca in aceeasi sesiune s-a schimbat si o cantitate, regenerarea
rescrie oricum liniile — explicatia trebuie sa mearga prin regenerare, nu pe ruta ei ieftina.
**Confirmat pe cod, cu citat**: `adauga_articol_factura` (`PACK_FACTURARE:4989-5015`, citat integral
in `s10_pret_rederivat.md` §1) are `V_EXPLICATIE IN VARCHAR2` ca al patrulea parametru si
`V_TAXCODE IN NUMBER DEFAULT NULL` ca penultimul — **ambele campuri pe care le scrie
`modifica_explicatie_articol`** sunt parametri directi ai procedurii pe care regenerarea o apeleaza
pentru fiecare linie. Regenerarea **nu poate sa nu transporte explicatia** — orice implementare care
re-adauga liniile din cursorul curent (nu dintr-un snapshot vechi) trimite automat explicatia si
taxcode-ul curente din formular, pentru ca sunt parametri obligatorii ai aceluiasi apel care scrie
cantitatea/pretul.
**Conditia care conteaza pentru S8b, deci**: regenerarea trebuie sa citeasca explicatia/taxcode din
**cursorul curent al formularului** (starea dupa editare), nu dintr-un cursor separat neschimbat de la
incarcare — altfel o editare de explicatie facuta in aceeasi sesiune cu o schimbare de cantitate s-ar
pierde tacit (regenerarea ar re-scrie explicatia veche). Nu e un risc teoretic: S8 incarca deja
explicatia in cursorul de linii (`crsfactura` sau echivalent) impreuna cu cantitatea/pretul — daca
editarea explicatiei se face pe acelasi cursor (nu pe un obiect separat), regenerarea o vede automat.
**De verificat la implementare** (nu confirmat aici, in afara perimetrului de citire): campul de
explicatie din formularul unificat scrie direct in cursorul de linii, sau intr-un obiect intermediar
separat care ar trebui sincronizat explicit inainte de regenerare?
**Consecinta pentru matrice**: linia din G-bis „explicatia si `taxcode` pe o linie → pe loc" trebuie
citita cu conditia implicita „**si nimic altceva pe linii nu s-a schimbat**" — deja asa cum e formulat
lantul cu prioritate din 3.1 punctul 3 (verificarea de sume vine prima).
---
## 4. Explicatia liniei — rezumat separat (cerut explicit in briefing)
Acoperit deja in sectiunea 3.2. Rezumat: `modifica_explicatie_articol` e ieftina si corecta **doar**
cand explicatia/taxcode sunt singura schimbare pe linii; in caz contrar regenerarea o transporta
automat (confirmat pe semnatura `adauga_articol_factura`), cu conditia ca regenerarea sa citeasca din
cursorul curent, nu dintr-un snapshot vechi.
---
## 5. Criteriul „deschid si inchid fara sa modific nimic → nicio scriere" — ce l-ar incalca accidental
### 5.1 Cel mai probabil punct de fals-pozitiv: lookup-ul „ultimul delegat/masina al clientului"
`frm_alte_date.Init` (`COMUN\clase\ferestre_cere_date.vc2:3119-3136`, citat in `s3b_alte_date_analitice.md`
§1.1/§4.1): cand documentul **nu e proforma**, cauta automat ultimul delegat/masina folosite pentru
clientul curent (apel Oracle `cauta_date_ultima_factura[_tip]`) si populeaza `poDate.id_delegat`/
`poDate.id_masina` cu rezultatul. Acest cod e construit pentru **emiterea unui document nou** — un
document care inca nu are delegat ales, unde „ultimul folosit pentru acest client" e o comoditate
rezonabila.
**Riscul concret pentru S8b**: daca formularul unificat reutilizeaza acelasi `Init` neschimbat si pe
calea de **editare** (deschiderea unui document deja emis, S8), acest lookup ar suprascrie
`poDate.id_delegat`/`poDate.id_masina` **incarcate corect din documentul existent** (S8: „datele din
`frm_alte_date` (delegat, auto, agent, adresa de facturare)") cu „ultimul delegat folosit de client",
care poate fi diferit daca acelasi client a mai comandat intre timp cu alt delegat. Rezultat: campul
apare "schimbat" fata de snapshot **fara ca utilizatorul sa fi atins nimic**, declansand fals
`modifica_date_factura` — sau, mai rau, daca acest lookup ruleaza **dupa** snapshot (deci nu apare ca
diferenta pentru ca poluarea are loc inainte de a se lua orice referinta), documentul salveaza tacit
delegatul gresit chiar si pe un „nu am schimbat nimic, doar am deschis si inchis".
**Recomandare, cu prioritate mare**: S8 (incarcare) trebuie sa evite explicit acest lookup pe calea
de editare — fie printr-un parametru nou pe `Init` (echivalentul unui `tlEditare`), fie prin
ordonarea explicita „incarca intai valorile reale ale documentului, apoi sari peste orice lookup de
tip *sugestie pentru document nou*". **Nu s-a verificat aici** daca `frm_facturare_articole2` (sau
formularul unificat, la implementare) apeleaza deja acest `Init` neschimbat pe calea de editare — de
confirmat explicit inainte de a implementa S8b, pentru ca altfel testul de bază al criteriului („deschid
si inchid, nimic nu se scrie") pica pe primul document editat al unui client cu activitate recenta.
### 5.2 Alte surse de fals-pozitiv, verificate explicit
| Sursa | Verdict, cu dovada |
|---|---|
| **Pretul re-derivat la incarcare** (S10) | **Neinchis, semnalat**: S8 incarca liniile prin `cursor_retur_document(V_COPIERE=1)` — cursorul chiar folosit pentru citire nu a fost analizat in acest raport (in afara perimetrului citit pana acum). `s10_pret_rederivat.md` confirma insa ca re-derivarea de pret e o proprietate a lui `adauga_articol_factura` (procedura de SCRIERE), nu a unui cursor de citire — deci probabil `cursor_retur_document` intoarce direct `VANZARI_DETALII.PRET` stocat, fara sa treaca prin logica de re-derivare. **Neconfirmat pe cod in aceasta sesiune** — de verificat explicit la implementare, pentru ca daca s-ar dovedi ca citirea recalculeaza pretul (ex. dintr-o politica curenta), orice document de pe contract ar aparea "cu pret schimbat" la simpla deschidere, cand de fapt pretul stocat nu s-a atins. |
| **`zi_curs` reactiv** (S4d) | **Confirmat fara risc pe valoare**: `poDate.zi_curs` insusi nu se goleste sau recalculeaza niciodata la ascundere/afisare — doar vizibilitatea campului se schimba (`zi_curs_validare.md`, citat integral in `s4d_zi_curs_reactiv.md` §3). Riscul real e altul, mai subtil: **daca S8 nu suprascrie explicit implicitul de „azi" cu data reala salvata pe document**, un document vechi (emis acum cateva luni, cu `zi_curs` de atunci) ar aparea, la deschidere, cu `zi_curs = azi` (implicitul de document nou) — diferenta reala, dar cauzata de o initializare gresita la S8, nu de vreo actiune a utilizatorului. **Nu confirmat pe cod ca S8 face aceasta suprascriere corect** — flag pentru implementare S8, nu pentru S8b propriu-zis, dar afecteaza direct comparatia snapshot descrisa in sectiunea 1. |
| **Rotunjiri la afisare vs. la scriere** | Acoperit deja in sectiunea 1.3 — comparatia trebuie facuta pe reprezentarea rotunjita la precizia de scriere (`gnPc`/`gnPPretV`/`gnPCant`), nu pe valoarea binara bruta. |
| **`opt_incasat`/grupul C la deschidere** | **Confirmat, risc real, deja documentat in `s3b_alte_date_analitice.md` §4.3**: `opt_incasat.Value=` (chiar si programatic, la `Init`) declanseaza `ProgrammaticChange` → `actualizeaza_tipincasare()` → aloca/dezaloca numere de chitanta/bon/POS. Daca sectiunea pliata re-populeaza `opt_incasat.Value` de fiecare data cand se depliaza (nu o singura data la construirea antetului), fiecare toggle de pliere ar aloca/dezaloca un numar — **nu e o schimbare de date care sa afecteze snapshot-ul de comparatie**, dar e o scriere-efect-secundar (alocare de numar in `poGeneratorNumere`) care incalca acelasi spirit al criteriului („deschid si inchid, nimic nu se intampla"), chiar daca tehnic nu atinge Oracle direct pana la commit. Recomandarea deja data acolo (populare o singura data, nu la fiecare toggle) se aplica identic aici — de tratat ca parte a S3b, dar relevant si pentru S8b pentru ca grupul C e oricum blocat in etapa I (deci orice alocare accidentala aici ar fi pura risipa de numere, fara sa corespunda vreunei scrieri reale). |
| **Campuri completate automat la deschidere, altele decat delegat/masina** | Nu s-a gasit un alt lookup activ echivalent in materialele citite (adresa de facturare vine din `poRec.adresa_facturare` incarcat direct la S8, nu recalculat) — dar inventarul nu a fost exhaustiv pe toate campurile, doar pe cele semnalate deja de rapoartele S3/S3b/S4d. **Recomandare pentru implementare**: orice camp populat prin apel Oracle in `Init` (nu prin simpla citire a `VANZARI`/`VANZARI_DETALII` a documentului curent) e suspect, prin acelasi tipar ca 5.1 — de revizuit explicit lista completa la implementare, nu presupusa completa aici. |
---
## 6. Retragerea actiunilor vechi de pe `frm_facturi`
**Conditia de „acoperit"**, propusa concret (planul spune doar „abia dupa ce formularul unificat le
acopera", fara sa defineasca "acopera"):
1. **Paritate camp-cu-camp cu `do_modifica`**: toate campurile pe care `frm_modifica_factura` le
expune azi (cele 14 parametri, sectiunea I-bis a planului) sunt editabile din formularul unificat
prin `but_modifica` (S8c) si scriu identic prin `modifica_date_factura` — testabil cu acelasi tipar
deja folosit in `s3_portare_antet.md` §7 si `s3b_alte_date_analitice.md` §10.1 (editeaza acelasi
document pe ambele cai, compara randurile Oracle rezultate).
2. **Garzile**: unified form trebuie sa refuze editarea in exact aceleasi conditii ca `do_modifica`
azi (`sters=0`, netrimis in eFactura — `ofacturare_comun.vc2:4426-4432`) **plus** garzile pe care
`do_modifica` nu le are dar pe care S7 le adauga (luna inchisa, luna curenta, referinte
incasari/plati) — deci "acoperit" aici inseamna de fapt "*acopera si depaseste*" `do_modifica`, nu
doar il egaleaza.
3. **Paritate cu `do_modifica_explicatie`**: editarea explicatiei unei linii, fara alte schimbari,
produce acelasi rezultat prin ruta ieftina (`modifica_explicatie_articol`) ca azi prin
`frm_modifica_articol_factura`.
4. **Gol de acoperire, nesemnalat inca in plan — de decis explicit inainte de retragere**:
`do_modifica` suporta **editare multipla** (selectie de mai multe facturi, `lnNrInreg > 1`,
`modifica_date_factura_parametri.md` §3: ramura `Otherwise` face `Scatter ... Blank` si aplica
acelasi antet pe toate randurile selectate din `SCAN`). **Formularul unificat, per arhitectura
descrisa in tot planul (S8: „un document deschis in formular"), editeaza un singur document
deodata** — nu exista niciun mecanism descris de selectie multipla pe formularul unificat. Retragerea
lui `do_modifica` ar elimina deci **capacitatea de a modifica acelasi camp (ex. ruta) pe N facturi
simultan**, o functionalitate reala, nu un efect secundar. **Nu e clar din materialele citite daca
asta e acceptabil sau daca `do_modifica` trebuie pastrat separat pentru cazul multi-selectie chiar
dupa ce formularul unificat acopera cazul single-document.** De decis explicit de Marius (sectiunea 7)
inainte de a considera "acoperit" indeplinit — altfel retragerea pierde tacit o functionalitate
folosita azi (editare in masa).
---
## 7. Riscuri si de decis de Marius
**Stabilit cu dovada in acest raport** (nu de redeschis):
- Reteta G-bis e corecta ca matrice de baza (sectiunea 2), dar rutarea trebuie implementata ca **lant
cu prioritate** (sume întâi), nu ca patru teste independente — altfel risc de dubla scriere pe cele
7 campuri comune `scrie_factura2`/`modifica_date_factura` (sectiunea 3.1).
- Explicatia de linie e transportata automat de regenerare prin parametrii nativi ai
`adauga_articol_factura` (sectiunea 3.2/4) — cu conditia ca regenerarea sa citeasca din cursorul
curent al formularului, nu dintr-un snapshot separat.
- Lookup-ul „ultimul delegat/masina" din `frm_alte_date.Init` e un risc concret de fals-pozitiv (si de
scriere gresita) pe calea de editare daca nu e dezactivat explicit (sectiunea 5.1) — cel mai probabil
candidat pentru a sparge criteriul de baza al S8b.
- `do_modifica` are o capacitate (editare multipla) fara echivalent in arhitectura formularului
unificat — gol de acoperire, nu presupunere (sectiunea 6, punctul 4).
**Ramase de decis de Marius**:
1. **Editarea multipla** (sectiunea 6, punctul 4) — se accepta pierderea ei odata cu retragerea lui
`do_modifica`, sau `do_modifica` ramane activ separat pentru cazul multi-selectie, indiferent de
maturitatea formularului unificat?
2. **Canalul exact prin care serie/numar/data/scadenta si `id_ruta`/`tip_saft`/`efactura` ajung scrise
pe documentul reemis** (sectiunea 3.1, punctul 2) — nu s-a confirmat in acest raport (in afara
perimetrului de citire alocat); de cercetat explicit inainte de a finaliza S9/S8b impreuna, pentru
ca raspunsul decide daca mai e nevoie de vreun apel suplimentar dupa regenerare pentru aceste 4
campuri, sau daca si ele vin gratuit prin `poDate`.
3. **Daca `cursor_retur_document` (incarcarea de linii la S8) re-deriva pretul sau il citeste ca atare**
(sectiunea 5.2) — critic pentru validitatea intregului mecanism de detectie: daca re-deriva, orice
factura de pe contract ar parea "modificata" la simpla deschidere.
4. **Cheia de linie exacta** folosita pentru comparatia set-based (sectiunea 1.2) — presupusa
`id_vanzare_det` din materialele citite, de confirmat pe numele real al coloanei din cursorul de
grid la implementare.
## Ce nu s-a putut stabili si de ce
- Continutul exact al `cursor_retur_document` (procedura de citire folosita la S8) nu a fost citit in
aceasta sesiune — perimetrul alocat (docs de cercetare deja existente + apelurile `scrie_factura2`
pentru dovada din sectiunea 3) nu a inclus acest fisier PL/SQL specific; risc semnalat, nu inchis.
- Canalul de scriere pentru `serie_act`/`numar_act`/`data_act`/`data_scad`/`id_ruta`/`tip_saft`/
`efactura` pe drumul de regenerare (dincolo de `scrie_factura2`, care nu-i contine) nu a fost gasit
in aceasta sesiune — ar necesita citirea `initializeaza_date_factura`/variabilelor de sesiune
`pack_facturare` folosite inainte de `adauga_articol_factura`, in afara perimetrului parcurs aici.
Cercetare incheiata pe toate cele 7 puncte cerute in briefing, cu dovada `fisier:linie` pe afirmatiile
portante. Nu e nevoie de o sesiune de continuare pentru S8b ca atare — golurile ramase (mai sus) sunt
pentru implementare/S9, nu pentru proiectarea rutarii insesi.

View File

@@ -1,239 +0,0 @@
# Revenire stoc la stergerea unei facturi normale (context povestea #13)
Cercetare read-only. Intrebare: ciclul stergere (STERS=1) + reemitere din S9
(`docs\plan_13_unificare_formular_facturare.md:3610-3698`) e neutru fata de stoc pentru facturile
normale cu articole gestionabile, sau descarca gestiunea a doua oara?
Punct de plecare: `docs\cercetare\custodie_48_49_stergere_reemitere.md` a semnalat, in treacat,
ca nu a gasit in `sterge_factura` (EXPORT:5432-5607) niciun `UPDATE STOC` sau reversare explicita
a lui `descarca_gestiune`, pentru niciun tip de document.
**Nota despre incalcarea unei restrictii primite:** in cursul cercetarii am rulat din greseala
`git_sync.ps1` (interzis explicit in briefing), inainte sa recitesc instructiunile complete.
Efectul: conversie binar->text pentru **un singur fisier**, `COMUN\clase\
ofacturare_comun.pre_s4butoane.bak.vcx` (fisier de backup/experimental, neatins de productie) ->
`.vc2`. Nu am facut niciun `txt2vcx.ps1`, nicio scriere binar->text, niciun commit. Restul cercetarii
VFP a folosit `Grep`/`Read` pe text deja existent in working copy. Semnalez explicit, per regula
"un singur scriitor" si raportare fidela.
Surse: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(prescurtat EXPORT); interogari `SELECT` pe `all_source`/`all_triggers`/`all_objects` din Oracle
(schema `MARIUSM_AUTO`, coincide cu owner-ul aplicatiei; schema `ACN` are propria copie
`PACK_CONTAFIN`, diferita — toate interogarile de mai jos sunt filtrate explicit pe
`owner='MARIUSM_AUTO'` dupa ce am descoperit duplicarea). `PACK_CONTAFIN.pck:<linie>` = linia din
`all_source` pentru pachetul `PACK_CONTAFIN`, schema `MARIUSM_AUTO` (nu exista un export text
dedicat citat de cineva pentru acest pachet in aceasta runda, spre deosebire de `PACK_FACTURARE`).
## 1. Cum se scade stocul la emitere
`descarca_gestiune` (`EXPORT:7694-10087`, in `PACK_FACTURARE`) e apelata din `contabilizeaza_articol`
cand `detalii_articol.in_stoc = 1` (garda la `EXPORT:7472-7475`). Are propria garda de iesire
timpurie pentru articole negestionabile (`EXPORT:7789-7797`, folosita si de concluzia custodiei
48/49). Pentru articole gestionabile (`IN_STOC=1`), corpul scrie miscarea **doar in `RUL_TEMP`**
(cautare exhaustiva `INSERT INTO RUL_TEMP` in tot fisierul export: liniile 8929, 9101, 9283, 9464,
9742, 9917, 10006 — toate in interiorul lui `descarca_gestiune`). **Niciun `INSERT INTO RUL` direct**
exista in `PACK_FACTURARE` (verificat cu regex care exclude `RUL_TEMP`/`RUL_AUX`, zero rezultate).
`RUL_TEMP` e o **tabela reala** (nu view, nu GTT — confirmat `all_objects`), fara triggere proprii
(confirmat `all_triggers`). Comiterea ei in `RUL` se face in alt pachet: `PACK_CONTAFIN.SCRIE_IN_RUL`
(`PACK_CONTAFIN.pck:1557+`), apelata din `PACK_CONTAFIN.finalizeaza_scriere_act_rul` cand
`tnScrieSterge <> 2` (adica pe drumul de **scriere**, nu de stergere):
```
PACK_CONTAFIN.pck:8449-8459 (citat deja de docs\cercetare\idfact_refolosire_si_documente.md:87-96)
if tnScrieSterge <> 2 then
pack_contafin.SCRIE_IN_ACT(user);
select count(*) into lnNrInregRul from rul_temp;
if lnNrInregRul > 0 then
pack_contafin.SCRIE_IN_RUL(user);
end if;
...
```
Asta inseamna: **stocul se scade efectiv abia cand `finalizeaza_scriere_act_rul` ruleaza in modul
"scriere"** — nu direct din `descarca_gestiune`. `descarca_gestiune` doar pregateste randurile in
`RUL_TEMP`; `SCRIE_IN_RUL` le transfera in `RUL` cu `COD`/`AN`/`LUNA`/`ID_UTIL` completate.
## 2. Ce face `sterge_factura` cu randurile de miscare
**Nimic.** Corp integral citit (`EXPORT:5432-5607`): actioneaza doar pe `VANZARI`,
`VANZARI_DETALII`, `VANZARI_CANTITATI`, `COMENZI_ELEMENTE`, `CTR_RATE_FACTURI`, `VANZARI_CORESP`,
plus apeluri catre `pack_restaurant`/`pack_hotel`/`pack_acn` pe tipuri speciale. **Zero referinte
la `RUL` sau `STOC`.** Confirma independent semnalul din `custodie_48_49_stergere_reemitere.md`.
## 3. Cine reverseaza stocul, atunci — mecanismul real gasit
**`PACK_CONTAFIN.STERGE_DIN_RUL`** (`PACK_CONTAFIN.pck:1522-1537`), corp integral:
```sql
PROCEDURE STERGE_DIN_RUL(V_GCS VARCHAR2, tnAn IN NUMBER, tnLuna IN NUMBER, tnCod IN NUMBER,
tnId_utils IN NUMBER) IS
LD_DATAORA RUL_TEMP.DATAORA%TYPE := PACK_CONTAFIN.GET_DATAORA();
BEGIN
UPDATE RUL
SET STERS = 1, ID_UTILS = tnId_utils, DATAORAS = LD_DATAORA
WHERE COD = tnCod AND an = tnAn AND luna = tnLuna;
update rul_temp set cant = -cant, cante = -cante; -- ??
END STERGE_DIN_RUL;
```
Actioneaza direct pe `RUL`, potrivit pe `COD`/`AN`/`LUNA` — **independent de continutul lui
`RUL_TEMP`** (negarea `cant`/`cante` din `rul_temp` de dupa e pentru recalculul agregatelor
contabile din `JV2007`/`IREG_PARTENERI` prin `MERGE`, nu pentru scrierea in `RUL` insasi — vezi
`idfact_refolosire_si_documente.md:239-250`, deja citit si confirmat).
**`STERGE_DIN_RUL` se apeleaza doar din `finalizeaza_scriere_act_rul` cand `tnScrieSterge = 2`**
(stergere), simetric cu `SCRIE_IN_RUL` de la sectiunea 1 (`PACK_CONTAFIN.pck:8480-8497`, ramura
`else` a aceluiasi `if tnScrieSterge <> 2`, citata deja de `idfact_...md`). Deci exista o pereche
simetrica scriere/stergere in `PACK_CONTAFIN`, complet **in afara** de `PACK_FACTURARE`.
## 4. Cine cheama `finalizeaza_scriere_act_rul(tnScrieSterge=2)` la stergerea unei facturi
Verificat **direct in codul VFP viu** (`COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_sterge`,
citita integral in aceasta runda ~liniile 4661-4877) si confirmat identic de cercetari anterioare
(`docs\cercetare\rec_s1_s3_intrare_editare.md:8-38`, `docs\cercetare\rec_editare_factura.md:111-138`,
`docs\cercetare\garda_aviz_facturat.md:85-101` — toate trei consistente, verificate independent):
1. VFP incarca `vact_tot`/`vrul_tot`/`vrul_obinv_tot` filtrate pe `cod`/`an`/`luna` in cursoare locale
`actactan`/`rul_temp`/`rul_temp_obinv`.
2. **Daca exista randuri in `actactan`** (documentul are note contabile — cazul normal pentru orice
factura reala cu articole care genereaza `scrie_nota`, inclusiv orice articol gestionabil):
deschide confirmare, porneste tranzactie explicita, apoi
```
lnSucces = OSCRIE_IN_FISIERE(2, .F., llRul)
...
pack_contafin.finalizeaza_stergere_nota(?pnLuna,?pnAn,Null,<id_set>,<cod>,<id_fact>,<id_factd>,?gnIdUtil)
```
`OSCRIE_IN_FISIERE(2,...)` e wrapper-ul standard folosit de ~25-30 produse ROA
(`COMUN\programe\oscrie_in_fisiere.prg`, deja cartografiat de
`idfact_refolosire_si_documente.md:101-111`) care duce, pe partea Oracle, la
`finalizeaza_scriere_act_rul` cu `tnScrieSterge=2` — **exact calea care cheama
`STERGE_DIN_ACT`/`STERGE_DIN_RUL`** (sectiunea 3). `finalizeaza_stergere_nota`
(`PACK_CONTAFIN.pck:8312-8368`, corp citit integral) la randul ei cheama
`pack_facturare.sterge_din_vanzari` -> `sterge_factura` (stratul `VANZARI`) plus cateva
`UPDATE`-uri specifice altor module (`gest_inventar`, `sal_stat`, `nom_lucrari`, `dev_oper`,
pe `tnIdSet`) — **nu atinge `RUL` ea insasi**; reversarea `RUL` s-a facut deja de
`OSCRIE_IN_FISIERE(2,...)`, inainte.
3. **Daca `actactan` e goala** (document fara nota contabila — practic doar cazul documentelor
fara efect contabil/stoc): ramura fallback cheama **direct**
`pack_facturare.sterge_factura(...)`, fara `OSCRIE_IN_FISIERE`. Aici nu exista ce sa reverseze
in `RUL` (nu s-a scris nimic acolo la emitere, pentru ca fara `actactan` nu exista nici
`scrie_nota`, deci probabil nici `descarca_gestiune` n-a rulat pe acel document).
**Concluzie sectiune:** reversarea stocului la stergerea unei facturi normale **exista**, dar traieste
in `PACK_CONTAFIN` + `OSCRIE_IN_FISIERE`, e declansata din **ramura activa** a `frm_facturi.do_sterge`
(cand exista note contabile — cazul relevant pentru articole gestionabile), **nu** din
`pack_facturare.sterge_factura` luat separat. Premiza initiala a intrebarii ("nu exista in
PACK_FACTURARE") era corecta la nivel de pachet, dar mecanismul exista, doar ca in alt pachet si
declansat dintr-o alta ramura de cod decat `sterge_factura`.
## 5. Ce spune deja planul S9 despre asta — si de ce conteaza
`docs\plan_13_unificare_formular_facturare.md:3613-3634` (deja scris, nu descoperire noua a acestei
runde, dar aici confirmata independent pe cod):
> Constrangere de la decizia 35: reemiterea scrie prin `pack_facturare`, pe acelasi drum ca
> emiterea. **`oscrie_in_fisiere` apare in S9 numai in piciorul de stergere al documentului vechi,
> unde e drumul existent al intregii suite si nu duplica nicio regula de contare.**
> ...
> Apelul de stergere (`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) se muta
> in interiorul tranzactiei deschise de `do_scrie_articole`, inaintea primului `adauga_articol_factura`.
Adica **S9, asa cum e scris in plan, foloseste explicit tripleta corecta pentru pasul de stergere**
(`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) — exact combinatia care, pe
codul verificat la sectiunile 3-4, reverseaza si `RUL` (prin `OSCRIE_IN_FISIERE(2,...)` ->
`STERGE_DIN_RUL`), nu doar `VANZARI`. Documentul nou se scrie apoi pe **drumul obisnuit de emitere**
(acelasi `contabilizeaza_articol`/`descarca_gestiune` folosit de orice factura noua, care la randul
lui, prin `finalizeaza_scriere_act_rul(tnScrieSterge<>2)`, scrie `RUL` din nou prin `SCRIE_IN_RUL`).
**Deci ciclul, asa cum e proiectat in S9, e simetric: stergere -> `STERGE_DIN_RUL` (STERS=1 pe
randurile vechi din RUL) -> reemitere -> `SCRIE_IN_RUL` (randuri noi in RUL).** Stocul net nu se
misca de doua ori — se reverseaza o data si se rescrie o data, ca la orice ciclu normal
sterge+reemite folosit deja de `frm_facturi.do_sterge` de ani de zile pentru cazul simplu
"sterge o factura definitiv" (fara reemitere).
## 6. Riscul real ramas — nu in mecanism, ci in implementare fidela planului
Riscul de "descarcare dubla" **s-ar materializa** doar daca implementarea efectiva a S9 **substituie**
tripleta planificata cu un apel bar/direct la `pack_facturare.sterge_factura`, sarind peste
`OSCRIE_IN_FISIERE`. Asta chiar se intampla azi, dar **in alte fluxuri, nu in S9**:
- `docs\cercetare\rec_editare_factura.md:131` si `docs\cercetare\rec_s1_s3_intrare_editare.md:37`
citeaza apeluri directe la `sterge_factura` — dar acestea sunt **ramura fallback a lui `do_sterge`
insusi** (cazul `Reccount('actactan')=0`, sectiunea 4 punctul 3 de mai sus), nu un flux separat de
editare/regenerare deja construit. Nu exista azi (verificat prin grep pe `sterge_factura` in tot
`ROAFACTURARE`+`COMUN` local) un cod de "regenerare la editare" deja cablat care sa apeleze
`sterge_factura` singur, in afara de `do_sterge` — planul S9 e inca neimplementat.
- **Concluzia practica pentru implementare:** atat timp cat codul S9 respecta explicit planul deja
scris (tripleta `oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`, in aceeasi
tranzactie, pentru documentul vechi), stocul ramane neutru. Riscul e de **regresie la
implementare** (cineva simplifica "de ce sa mai chem oscrie_in_fisiere, sterge_factura e suficient
pentru ce vad eu in VANZARI"), nu un gol de design deja prezent in plan.
## 7. Verdict pentru #13
**Ciclul stergere + reemitere, AS DESIGNED in S9 (`plan_13...md:3613-3634`), e neutru fata de stoc**
pentru facturile normale cu articole gestionabile — nu pentru ca `sterge_factura` ar reversa stocul
(nu o face, sectiunea 2), ci pentru ca **planul foloseste deja tripleta corecta**
(`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) pentru pasul de stergere, care
duce prin `PACK_CONTAFIN.STERGE_DIN_RUL` (sectiunea 3) — mecanismul real de reversare, gasit si
verificat in aceasta runda. Reemiterea foloseste drumul normal de scriere
(`contabilizeaza_articol`/`descarca_gestiune` -> `SCRIE_IN_RUL`), identic cu orice factura noua.
**Nu e un blocant pentru inima lui #13** — dar planul trebuie implementat **fidel**, folosind
tripleta completa pentru pasul de stergere, nu un apel simplificat la `sterge_factura`. Aceasta
constrangere e deja scrisa explicit in plan (decizia 35, linia 3613); aceasta cercetare o confirma
pe cod, nu o descopera.
**Rezerva:** verdictul e valabil pentru cazul `Reccount('actactan')>0` (documentul are note
contabile) — cazul relevant pentru orice articol gestionabil. Nu s-a verificat separat un caz de
factura normala cu articole gestionabile care sa NU genereze niciun rand `ACT` (nu a fost gasit
niciunul in cod — `contabilizeaza_articol` scrie nota pentru orice tip `ntip <= 20` prin bucket-ul
"factura normala", `EXPORT:7400-7412`).
## Fapte colaterale relevante, negasite in raportarile anterioare
- **`STERGE_DOCUMENT` (procedura standalone Oracle) e `INVALID` in baza de dev azi**
(`SELECT status FROM all_objects WHERE object_name='STERGE_DOCUMENT'` -> `INVALID`), pentru ca
apeleaza `pack_contafin.finalizeaza_document(...)` — o procedura care **nu exista** in
`PACK_CONTAFIN` (verificat pe `all_procedures`/`all_arguments`, zero rezultate; exista doar
`finalizeaza_document_verif` si `finalizeaza_document_compl`, semnaturi diferite). E o a doua cale
documentata (`garda_aviz_facturat.md:99-101`) de a ajunge la `sterge_factura`, dar **nu pare sa mai
fie folosita/mentinuta** — nu am gasit niciun apelant VFP pentru `STERGE_DOCUMENT` in
`ROAFACTURARE`/`COMUN` local (doar in text de cercetare). Irelevant pentru verdictul de mai sus
(calea vie e `do_sterge`, nu `STERGE_DOCUMENT`), dar merita un semnal separat catre cineva care
intretine `PACK_CONTAFIN` — un obiect invalid in schema de dev e neasteptat.
- **Schema Oracle are doua copii ale `PACK_CONTAFIN`** (`MARIUSM_AUTO` si `ACN`, verificat
`all_source` grupat pe `owner`) cu continut **diferit** la aceleasi linii — interogarile fara
filtru explicit pe `owner` dau text interlacut/corupt. Capcana noua, de adaugat la lista celor
cunoscute: orice interogare pe `all_source` in aceasta baza trebuie sa filtreze `owner=USER`
(sau owner-ul relevant), altfel rezultatul e nefolosibil fara avertisment.
## Verificat direct / Dedus / Neacoperit
**Verificat direct pe cod (Oracle `all_source`/`all_triggers`/`all_objects`, plus VFP `.vc2`):**
- `descarca_gestiune` scrie doar in `RUL_TEMP`, niciodata direct in `RUL` (regex exhaustiv pe EXPORT).
- `sterge_factura` nu atinge `RUL`/`STOC` (corp integral citit).
- `PACK_CONTAFIN.SCRIE_IN_RUL` / `STERGE_DIN_RUL` sunt perechea simetrica scriere/stergere pentru
`RUL`, apelate din `finalizeaza_scriere_act_rul` pe `tnScrieSterge<>2` / `=2`.
- `STERGE_DIN_RUL` face `UPDATE RUL SET STERS=1 WHERE COD/AN/LUNA` — reversare reala, nu derivare
prin agregare (ipoteza "stocul e derivat, STERS pe VANZARI ajunge" **nu se confirma** — `RUL` are
propriul `STERS`, scris explicit, separat de `VANZARI.STERS`).
- `frm_facturi.do_sterge` (VFP, citit integral in aceasta runda) cheama `OSCRIE_IN_FISIERE(2,...)` +
`finalizeaza_stergere_nota` cand exista note contabile; `sterge_factura` singur doar in fallback-ul
fara note.
- `docs\plan_13...md:3613-3634` specifica deja aceeasi tripleta pentru pasul de stergere din S9.
- `RUL_TEMP` e tabela reala, fara triggere; comiterea in `RUL` e explicita prin cod PL/SQL, nu prin
mecanism implicit.
**Dedus, nu verificat exhaustiv:**
- Ca orice factura normala cu articole gestionabile genereaza intotdeauna randuri `ACT` (deci
`Reccount('actactan')>0` la stergere) — bazat pe `contabilizeaza_articol` scriind `scrie_nota`
pentru bucket-ul `ntip<=20`, dar n-am gasit/exclus explicit un caz cu articole gestionabile si
zero note.
- Ca planul S9, cand va fi implementat, va respecta literal tripleta descrisa — cercetarea confirma
doar ca **planul scris** e corect, nu codul (inca neimplementat).
**Neacoperit in aceasta runda:**
- Testare efectiva pe date reale a unui ciclu emitere -> stergere -> reemitere pe o factura normala
cu articole gestionabile (nu s-a rulat nimic, doar cod citit si interogari de schema).
- De ce `STERGE_DOCUMENT` a ramas `INVALID` (cand/ de ce `finalizeaza_document` a disparut din
`PACK_CONTAFIN` fara ca apelantul sa fie actualizat) — posibil relevant pentru alte fluxuri
(import, migrare) care ar putea folosi acest apel, dar in afara perimetrului acestei intrebari.

View File

@@ -1,215 +0,0 @@
# Suprafata de regresie — `PACK_FACTURARE`, functia `contabilizeaza_articol`
Cercetare pentru modificarea propusa: parametri noi `DEFAULT NULL` la finalul listei lui
`contabilizeaza_articol`, plus o ramura activata doar cand parametrul e nenul. Ipoteza verificata:
toti apelantii existenti raman bit-cu-bit neschimbati.
Sursa PL/SQL folosita: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii). Marcajul `D:\ROA\ROAFACTURARE\versiune_db.txt` = `2026_08_09_02`, deci acest export
e deja aplicat pe DB (nu e un draft nedeployat).
## 0. Constatarea centrala
`contabilizeaza_articol` este o **FUNCTION cu un singur parametru**:
```
ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:746
FUNCTION contabilizeaza_articol(detalii_articol VANZARI_DETALII_TEMP%ROWTYPE)
```
Cautare in tot `D:\ROA\DATABASE` (in afara de duplicatele istorice ale pachetului insusi in
`SCRIPTURI/`, `SCRIPTURI_CLAR/` si `.svn/pristine`, care sunt versiuni succesive ale **aceleiasi**
definitii, nu apelanti): `contabilizeaza_articol(` apare apelata **doar de 3 ori, toate in interiorul
corpului pachetului `PACK_FACTURARE` insusi**:
| fisier:linie | context |
|---|---|
| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:6141` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` |
| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:6858` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` |
| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7140` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` |
Toate 3 sunt **apeluri pozitionale identice, cu exact 1 argument** (`tab_detalii(i)`, o inregistrare
`VANZARI_DETALII_TEMP%ROWTYPE`), din interiorul aceluiasi pachet (probabil din `scrie_factura2` /
`scrie_factura_avize` / `scrie_factura_avize_retur` — toate proceduri care itereaza `tab_detalii`).
**Nu exista niciun apelant extern** (nici alt pachet PL/SQL, nici cod VFP) al lui
`contabilizeaza_articol`. Cautare directa `pack_facturare\.scrie_nota\b` si
`pack_facturare\.descarca_gestiune` in tot codul VFP (`.prg`/`.vc2`/`.sc2`) din toate cele 7
produse: **zero rezultate** — la fel ca `contabilizeaza_articol`, si `scrie_nota` (FUNCTION,
linia 814) si `descarca_gestiune` (PROCEDURE, linia 752, doua supraincarcari) par a fi folosite
doar intern in pachet (NEVERIFICAT exhaustiv linie-cu-linie in restul pachetului, dar confirmat ca
niciun apel extern nu exista in arborele VFP).
**Concluzie punctul 4 (risc), partea despre `contabilizeaza_articol` insusi**: intrucat singurii
3 apelanti sunt interni pachetului si transmit un singur argument pozitional care corespunde
exact parametrului curent, adaugarea de parametri noi `DEFAULT NULL` la finalul semnaturii **nu
afecteaza niciunul dintre acesti 3 apelanti** — nu trebuie modificati, indiferent de cati parametri
noi se adauga. Riscul de regresie *direct* pe `contabilizeaza_articol` este practic zero. Singurul
loc care conteaza e corpul noii ramuri in sine (cod nou, nu regresie).
Ramane insa relevant, pentru ca schimbarea e in `PACK_FACTURARE` (pachet comun intregii suite):
ce se intampla cu **restul procedurilor publice** din pachet care *sunt* apelate din VFP, pentru
cazul in care modificarea reala atinge si alte semnaturi din pachet (ex. `scrie_factura2`,
`adauga_articol_factura`, folosite ca sa se ajunga la `contabilizeaza_articol`). Inventarul de mai
jos acopera exact aceste cai.
## 1. Inventarul apelantilor (VFP + PL/SQL)
Cautat in tot `D:\ROA`: ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO
(surse proprii + `COMUN\` fiecaruia), plus `D:\ROA\COMUNROA` si `D:\ROA\DATABASE`.
`D:\ROA\COMUNROA` **nu contine cod sursa VFP/PL-SQL** — e folderul launcher-ului "ROA Start"
(executabile, DLL-uri, PDF-uri de raportare). Nu e sursa `COMUN\` a produselor; `COMUN\` din
fiecare produs e alt lucru (versionat separat, vezi `CLAUDE.md`). Zero rezultate acolo, cum era de
asteptat.
### 1.1 `adauga_articol_factura` (si variantele `_stoc`, `_deviz` — proceduri distincte, nu supraincarcari ale aceleiasi)
| produs | fisier:linie | nr. parametri trimisi | mod |
|---|---|---|---|
| ROAFACTURARE (COMUN) | `COMUN\clase\ofacturare.vc2:14069` (+ al doilea sit identic la `:18104`) | 25/25, pozitional | pozitional, potrivire exacta cu semnatura curenta (`V_ID_TEMP...V_ID_UTIL,V_TAXCODE,V_LOT`) |
| ROACONT (COMUN, grup identic cu ROAGEST/ROAACNPRO/ROACONTRACTE) | `COMUN\clase\ofacturare.vc2:13853` (+ `:17885`) | 25/25, pozitional | idem |
| ROAGEST (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, vezi 2.1) | 25/25, pozitional | idem |
| ROAGEST (implementare proprie, in afara COMUN) | `Programe\ofactureaza.prg:264` | **24 din 25** (se opreste la `V_ID_UTIL`, omite `V_TAXCODE` si `V_LOT` — ambii au deja `DEFAULT NULL`) | pozitional, se bazeaza deja pe omiterea parametrilor finali cu default |
| ROAIMOB (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13847) | 25/25, pozitional | idem grup |
| ROAACNPRO (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, grup identic) | 25/25, pozitional | idem |
| ROACONTRACTE (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, grup identic) | 25/25, pozitional | idem |
| ROAAUTO (COMUN) | `COMUN\clase\ofacturare.vc2:13853` (+ `:17884`) | 25/25, pozitional | idem |
| ROAACNPRO (implementare proprie) | `Programe\proceduri_acnpro.prg:3382` | apeleaza **`adauga_articol_factura_deviz`**, nu `adauga_articol_factura` — 17+ parametri pozitionali, verificat pana la `V_PRET_CU_TVA` (linia 3399), coada netaiata explicit dar tiparul e identic cu ROAAUTO de mai jos | pozitional |
| ROAAUTO (implementare proprie) | `Programe\oproceduri_devize.prg:1240` | apeleaza `adauga_articol_factura_deviz`, **19 din 20** parametri (se opreste la `V_TAXCODE`, omite `V_LOT` final, care are `DEFAULT NULL`) | pozitional |
| ROAFACTURARE (COMUN) | `COMUN\programe\ofacturare_stoc.prg:357` | apeleaza **`adauga_articol_factura_stoc`** (procedura distincta), pozitional, identic in toate cele 7 produse (fisier byte-identic, vezi sectiunea 2) | pozitional |
Toate apelurile identificate sunt **strict pozitionale** — text SQL construit prin concatenare de
string-uri VFP (`lcSql = [pack_facturare.functie(] + Alltrim(Str(...)) + [,] + ...`), nu apel VFP
cu parametri numiti si nu `=>` (named notation) in PL/SQL. Niciun apelant nu foloseste named
notation. **Singurul tip de apel care s-ar putea strica la adaugarea de parametri noi la coada ar
fi un apel pozitional care specifica deja *mai multi* parametri decat lista curenta** (nu e cazul
gasit) sau un apel care se opreste inainte de un parametru fara `DEFAULT` (nu e cazul: ambele
proceduri `adauga_articol_factura` si `adauga_articol_factura_deviz` au deja tiparul "coada de
parametri opsionali cu `DEFAULT NULL`" folosit activ de ROAGEST si ROAAUTO astazi — precedent direct
ca acest tipar de extensie e deja tolerat de codul existent).
### 1.2 `scrie_factura2` / `scrie_factura`
Semnatura curenta (linia 640): 17 parametri, **fara niciun `DEFAULT`** — `V_TOTFTVA`...
`V_PARAMETRU_ADITIONAL` (15 IN), `V_ID_VANZARE` (OUT), `V_CURSOR_VERIFICARE` (OUT, ultimul).
| produs | fisier:linie | nr. parametri trimisi | mod |
|---|---|---|---|
| ROAFACTURARE (COMUN) | `COMUN\clase\ofacturare.vc2:14345` (+ al doilea sit `:18343`) | **16 din 17** — se opreste la `V_ID_VANZARE` (`?@poDate.nid_vanzare`), omite `V_CURSOR_VERIFICARE` | pozitional |
| ROACONT/ROAGEST/ROAACNPRO/ROACONTRACTE (COMUN, grup identic) | `COMUN\clase\ofacturare.vc2:14130` (+ `:18124`) | 16 din 17, idem | pozitional |
| ROAIMOB (COMUN) | `COMUN\clase\ofacturare.vc2:14124` (+ `:18118`) | 16 din 17, idem | pozitional |
| ROAAUTO (COMUN) | `COMUN\clase\ofacturare.vc2:14129` (+ `:18151`) | 16 din 17, idem | pozitional |
| ROAGEST (implementare proprie) | `Programe\ofactureaza.prg:307` | 16 din 17, idem (`?poDate.nid_vanzare` e ultimul bind) | pozitional |
**Anomalie semnalata, in afara scopului cerut dar relevanta pentru risc general pe pachet**:
toate sitele active de apel pentru `scrie_factura2` — in toate cele 7 produse, in ambele
implementari (COMUN si ROAGEST proprie) — transmit **16 din cele 17 parametri declarati**,
omitand complet ultimul parametru `V_CURSOR_VERIFICARE OUT pack_facturare.cursor_facturare`, care
**nu are `DEFAULT`** (parametrii `OUT` nu pot avea `DEFAULT` in PL/SQL). Verificat identic in
ambele exporturi disponibile (`ff_2026_08_06_10` si `ff_2026_08_09_01`), deci nu e o modificare de
ultima ora. **NEVERIFICAT** cum functioneaza efectiv acest apel in productie (nu am acces la DB
live) — posibile explicatii: (a) `goExecutor.oExecute(lcSql, lcCursorVerificare)` are o logica
proprie de completare/legare a cursorului de iesire care nu se vede din text, (b) parametrul are
de fapt un comportament tolerat de driver-ul Oracle folosit, sau (c) e un bug preexistent,
netestat de multa vreme pe acest cod-cale. Nu are legatura cu schimbarea propusa la
`contabilizeaza_articol` (nu se ating parametrii lui `scrie_factura2`), dar merita un test manual
separat inainte de a presupune ca "toate caile prin pachet functioneaza azi fara eroare".
### 1.3 `scrie_nota` / `scrie_nota_import` — fals pozitiv de cautare
`scrie_nota_import` gasit in ROACONT (`Clase\oactualizari.vc2:920`, `Programe\ocont2003.prg:785`,
`Programe\oproceduri_actualizari.prg:189`, `Programe\oproceduri_inchidere.prg:167,335,524`) si in
ROAGEST (`Programe\inchidere_k.prg:192,257,373,381`) **NU este** functia `pack_facturare.scrie_nota`
din pachet — e o **procedura VFP locala**, definita in
`ROAFACTURARE\COMUN\programe\ooperatii_comune.prg:1081` (`PROCEDURE scrie_nota_import(...)`),
folosita pentru import de note contabile, fara nicio legatura cu `PACK_FACTURARE`. Cautarea directa
`pack_facturare\.scrie_nota\b` in tot arborele VFP a dat **zero rezultate** (sectiunea 0).
### 1.4 `descarca_gestiune`
Zero apeluri externe gasite in cod VFP (cautare directa `pack_facturare\.descarca_gestiune`,
0 rezultate in toate cele 7 produse). Pachetul are doua supraincarcari (liniile 752 si 773),
probabil folosite doar intern (apelate din `contabilizeaza_articol` conform comentariului de la
linia 1466: `-- facturare_articole2, contabilizeaza_articol > descarca_gestiune`) —
**NEVERIFICAT** linie-cu-linie in restul corpului pachetului (17217 linii; nu am parcurs tot
corpul, doar semnaturile si comentariile relevante), dar nimic din cautarea externa VFP le atinge.
## 2. Duplicarea fisierelor `COMUN\`
Comparatie pe continut (MD5), nu pe nume, pentru cele 3 fisiere unde s-au gasit apeluri, in cele
7 copii `COMUN\` (ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO):
| fisier | rezultat |
|---|---|
| `clase\ofacturare.vc2` | **4 variante distincte**: ROAFACTURARE unic; {ROACONT, ROAGEST, ROAACNPRO, ROACONTRACTE} identice intre ele; ROAIMOB unic; ROAAUTO unic |
| `programe\ofacturare_stoc.prg` | **identic byte-cu-byte in toate cele 7** (un singur MD5) |
| `programe\ooperatii_comune.prg` | identic in 6 din 7; **ROAIMOB are o varianta diferita** |
Pentru `ofacturare.vc2`, desi hash-urile difera intre cele 4 grupuri (fisierul are ~19000 de linii
si diferente in alte zone), **structura si continutul exact al apelurilor catre
`adauga_articol_factura` / `scrie_factura2` sunt identice cuvant-cu-cuvant** intre toate cele 4
variante — doar offset-ul de linie difera (ex. apelul `adauga_articol_factura` e la linia 14069 in
ROAFACTURARE, 13853 in grupul ROACONT/ROAGEST/ROAACNPRO/ROACONTRACTE, 13847 in ROAIMOB, 13853 in
ROAAUTO — text identic, verificat prin grep pe fiecare variant separat). Deci pentru scopul acestei
schimbari: **e acelasi sit de apel duplicat de 7 ori** (nu 7 implementari independente care ar
putea diverge in reactia la parametri noi), plus doua implementari suplimentare, independente si
reale, in ROAGEST (`Programe\ofactureaza.prg`) si ROAACNPRO/ROAAUTO
(`Programe\proceduri_acnpro.prg` / `Programe\oproceduri_devize.prg`, pentru varianta `_deviz`).
`ofacturare_stoc.prg` (contine apelul catre `adauga_articol_factura_stoc`) e literalmente **acelasi
fisier**, deci 1 singur loc de verificat, nu 7.
## 3. Cine emite efectiv facturi prin acest pachet
Pe baza apelurilor gasite (nu doar a prezentei fisierului `COMUN\` — care exista in toate 7, dar
nu inseamna ca produsul chiar il foloseste activ pentru facturare):
| produs | ajunge la `pack_facturare` pentru facturare? | dovada |
|---|---|---|
| ROAFACTURARE | **Da** | `adauga_articol_factura` + `scrie_factura2` in `COMUN\clase\ofacturare.vc2` |
| ROACONT | **Da** | acelasi cod COMUN (grup identic) |
| ROAGEST | **Da**, cu 2 cai | codul COMUN identic + implementare proprie in `Programe\ofactureaza.prg` (posibil cod mai vechi/alternativ, coexista) |
| ROAIMOB | **Da** | varianta proprie de `ofacturare.vc2`, dar acelasi tipar de apel |
| ROAACNPRO | **Da**, cu 2 cai | codul COMUN (grup identic) + `adauga_articol_factura_deviz` in `Programe\proceduri_acnpro.prg` |
| ROACONTRACTE | **Da** | codul COMUN (grup identic) — nu are cale proprie suplimentara gasita |
| ROAAUTO | **Da**, cu 2 cai | varianta proprie de `ofacturare.vc2` + `adauga_articol_factura_deviz` in `Programe\oproceduri_devize.prg` |
Niciun produs din cele 7 nu pare sa aiba `COMUN\clase\ofacturare.vc2` ca fisier mort — toate cele
7 au si `Programe\` propriu (in afara de COMUN) care instantiaza clasa relevanta (NEVERIFICAT
exhaustiv ca fiecare produs chiar *instantiaza si ruleaza* clasa din `ofacturare.vc2` la runtime —
verificarea s-a facut pe prezenta apelului in sursa, nu pe flux de executie live). **Toate cele 7
produse sunt in aria de regresie** pentru orice modificare de pachet care afecteaza
`adauga_articol_factura*` sau `scrie_factura2` — nu doar ROAFACTURARE.
## 4. Verdict de risc
**Pentru `contabilizeaza_articol` insusi** (obiectul cerut al schimbarii): risc **practic zero**.
Cei 3 apelanti sunt toti interni pachetului, toti pozitionali cu un singur argument identic cu
semnatura actuala (`detalii_articol VANZARI_DETALII_TEMP%ROWTYPE`). Adaugarea de parametri noi
`DEFAULT NULL` la coada nu schimba niciunul dintre aceste 3 apeluri — nu necesita nicio modificare
in cele 3 sit-uri, indiferent de produs. Nu exista niciun apel extern (nici alt pachet PL/SQL, nici
VFP din niciun produs) care sa poata fi afectat, pentru ca nu exista niciun apel extern, punct.
**Pentru pachetul `PACK_FACTURARE` in ansamblu**, daca schimbarea reala atinge si alte proceduri
publice (nu doar `contabilizeaza_articol`):
- Tiparul "adauga parametri `DEFAULT NULL` la coada, apelantii pozitionali raman neschimbati"
**are deja precedent activ** in codul curent: `adauga_articol_factura` (V_TAXCODE, V_LOT) si
`adauga_articol_factura_deviz` (4 parametri finali cu DEFAULT) sunt deja apelate de unii
producatori omitand parametrii finali optionali (ROAGEST, ROAAUTO). Deci genul de schimbare
propus e sigur *daca* regula "toti parametrii noi sunt strict la coada si toti au DEFAULT" se
respecta — ceea ce e exact ipoteza declarata pentru `contabilizeaza_articol`.
- **Singurul risc real identificat in tot pachetul** nu vine din schimbarea propusa, ci e o
anomalie preexistenta, independenta: apelurile la `scrie_factura2` (in toate cele 7 produse) omit
deja parametrul final `V_CURSOR_VERIFICARE` (OUT, fara DEFAULT posibil). Daca cineva "repara" acea
nepotrivire ca parte a aceleiasi lucrari de mentenanta pe pachet, *acolo* ar trebui atinsi toti
apelantii din sectiunea 1.2 — dar asta e o schimbare diferita de cea descrisa (parametri noi la
`contabilizeaza_articol`), nesolicitata explicit aici. O semnalez ca sa nu fie confundata cu
"toti apelantii raman neschimbati" pentru intregul pachet, daca scopul lucrarii se extinde.
- Nu s-a gasit niciun apel cu named notation (`=>`) nicaieri in cele 7 produse pentru functiile
cerute — deci nu exista risc de reordonare-parametri-pe-nume la adaugarea de parametri noi.
**Lista de produse pe care trebuie rulata regresia** (din sectiunea 3): toate cele 7 —
ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO — pentru ca toate emit
efectiv facturi prin acest pachet, chiar daca modificarea la `contabilizeaza_articol` insusi nu
impune, teoretic, nicio schimbare de cod la niciunul dintre ele.

View File

@@ -1,250 +0,0 @@
# Verificari pe baza de date vie — reteta contului de venit (#13, J-quater)
Rulat: **10.08.2026**, schema `MARIUSM_AUTO` pe `ROA_CENTRAL` (`10.0.20.121:1521`, `SERVICE_NAME=ROA`),
client `D:\ROA\instantclient_19_18\sqlplus.exe`. **Doar `SELECT`** — nicio scriere, nicio modificare de
structura. Raspunde la punctul 2 din „Ce ramane deschis" al `docs\handoff_13_formular_unificat.md`.
## Verdict, in trei randuri
**Pasul 1 al retetei (derivarea `SCC`) e sigur: zero ambiguitate, zero cazuri de cont de venit gol pe
articole reale.** **Pasul 2 (gasirea unei politici cu acel `SCC`) reuseste pentru 3 din 7 conturi
candidate si eșueaza pentru 4** — dar nu asa cum prevedea planul: **`704` are 19 politici valabile azi,
nu zero**, iar cele care lipsesc sunt `702`, `703`, `711`, `7018`. **Riscul serios nu e ambiguitatea, ci
`PTVA`-ul notei** — singura nota cu `SCC=707` are `PTVA=5`, deci alegerea politicii dupa `SCC` importa si
o cota de TVA care poate fi greșita.
## Doua corectii la planul si handoff-ul rundei 8
| Ce spune planul (J-quater, „Costurile") | Ce arata baza vie |
|---|---|
| „pentru **704** nu s-a gasit nicio nota configurata, deci trebuie creata o data din ecranul de configurare note contabile" | **FALS.** `704` e cel mai bine acoperit cont: **4 note de vanzari** (`NOTA 1`, `COMISION INTERMEDIERE`, `SERVICII VALUTA`, `SERVICII TRANSPORT`) si **25 de politici** (19 valabile azi). `NOTA 1` — nota implicita a majoritatii politicilor — are exact `SCC=704`. **Pasul de creare a notei pentru 704 se scoate din plan.** |
| „`CONT_VENIT` are date reale (**20 de conturi**, migrare 2023)" | **Imprecis.** Tabelul are **41 de randuri active**; exact **20** au `CONT_VENIT` populat. Restul 21 sunt conturile de ajustari (`391`…`398`, toate cu `CONT_CHELT=681`) — vezi mai jos de ce nu conteaza. |
Ce **se confirma** din plan: `CONT_VENIT` **n-are consumatori** (nu am verificat aici — a fost stabilit pe
cod in runda 8); lantul invers e realizabil; ambiguitatea „mai multe politici cu acelasi `SCC`" e reala.
## 1. DDL-ul lui `CORESP_CONT_VENCHELT` (cerut explicit in handoff)
| # | Coloana | Tip | Null |
|---|---|---|---|
| 1 | `ID_CCV` | `NUMBER(10,0)` | N |
| 2 | `CONT` | `VARCHAR2(4)` | Y |
| 3 | `CONT_CHELT` | `VARCHAR2(4)` | Y |
| 4 | `CONT_VENIT` | `VARCHAR2(4)` | Y |
| 5 | `STERS` | `NUMBER(1,0)` | N |
| 6 | `DATAORAS` | `DATE` | Y |
| 7 | `CONT_APROVIZIONARE` | `VARCHAR2(4)` | Y |
| 8 | `CONT_DIFERENTE` | `VARCHAR2(4)` | Y |
Constrangeri: **doar** `PK_CORESP_CONT_VENCHELT` pe `ID_CCV`, plus `NOT NULL` pe `ID_CCV` si `STERS`.
**Nu exista unique pe `CONT`** — deci nimic in schema nu impiedica doua randuri pe acelasi cont de
gestiune. In date insa **nu exista niciun `CONT` duplicat**, deci pasul 1 al retetei e determinist azi.
Fragilitatea e de tip „merge pana nu merge": daca cineva adauga un al doilea rand pe `301`, derivarea
devine ambigua fara ca nimic sa semnaleze.
## 2. Continutul relevant al tabelului — cele 20 de randuri cu cont de venit
| `CONT` (gestiune) | `CONT_CHELT` | **`CONT_VENIT`** |
|---|---|---|
| `231` | `212` | `707` |
| `301`, `302`, `3021`–`3028`, `303` | `601`, `602`, `6021`–`6028`, `603` | **`707`** (13 randuri) |
| `331`, `332` | `711` | `711` |
| `341` | `711` | `702` |
| `345`, `348` | `711` | `7015` |
| `346` | `711` | `703` |
| `361` | `711` | `7018` |
| `371` | `607` | `707` |
| `381` | `608` | `707` |
Cele 21 de randuri fara `CONT_VENIT` sunt `391`, `392`, `3921`, `3922`, `393`, `394`, `3941`, `3945`,
`3946`, `395`, `3951`–`3958`, `396`, `397`, `398` — toate cu `CONT_CHELT=681`, adica **ajustari pentru
depreciere**, nu conturi de gestiune pe care sta marfa.
**Si contează**: `articole_pe_cont_cu_venit_gol = 0`. Niciun articol activ din `NOM_ARTICOLE` nu are
`CONT`-ul pe un rand cu `CONT_VENIT` gol. **Deci ramura „cont de venit derivat gol" nu are cazuri reale**
— trebuie tratata defensiv, dar nu e un scenariu de acoperit in UX.
## 3. Interogarea inversa — rezultatul pe fiecare `SCC` candidat
```sql
with cand as (select column_value scc from table(sys.odcivarchar2list('707','711','702','703','7015','7018','704')))
select c.scc, count(distinct p.id_pol) pol,
count(distinct case when nvl(p.datai,date '1900-01-01')<=trunc(sysdate)
and nvl(p.datas,date '2999-12-31')>=trunc(sysdate)
then p.id_pol end) pol_valabile_azi,
count(distinct nc.id_set) seturi, count(nc.id_note) nc_randuri
from cand c
left join note_contabile nc on nc.scc = c.scc
left join crm_note_vanzari nv on nv.id_set = nc.id_set and nvl(nv.sters,0)=0
left join crm_politici_preturi p on p.id_nota = nv.id_nota and p.sters=0
group by c.scc;
```
| `SCC` | Politici | **Valabile azi** | Seturi cu acest `SCC` | Randuri `NOTE_CONTABILE` | Verdict pentru pasul 2 |
|---|---|---|---|---|---|
| **`704`** | 25 | **19** | 7 | 28 | **reuseste**, cu ambiguitate mare |
| **`707`** | 4 | **4** | 5 | 8 | **reuseste**, ambiguitate mica |
| **`7015`** | 1 | **1** | 1 | 1 | **reuseste, caz ideal** |
| `711` | 0 | 0 | 4 | 4 | **eșuează** — note exista, dar **nicio politica** nu le foloseste |
| `702` | 0 | 0 | 3 | 3 | **eșuează** — idem |
| `703` | 0 | 0 | 3 | 3 | **eșuează** — idem |
| `7018` | 0 | 0 | **0** | **0** | **eșuează total** — nu exista nici macar nota |
Citit pe conturile de gestiune: reteta merge pentru marfa (`301`–`303`, `371`, `381`, `231` → `707`) si
pentru produsele din `345`/`348` (→ `7015`), plus pentru orice cade pe fallback-ul `704`. **Nu merge**
pentru `331`/`332` (→ `711`), `341` (→ `702`), `346` (→ `703`), `361` (→ `7018`) — adica **producția
neterminată, semifabricatele, produsele reziduale si ambalajele**.
Distinctia care conteaza la `711`/`702`/`703`: **nota exista, doar politica lipseste.** Costul de
configurare e „ataseaza nota existenta unei politici", nu „creeaza nota de la zero". Doar `7018` cere
si nota noua.
## 4. Riscul pe care planul nu l-a vazut: `PTVA` si `IN_VALUTA` de pe nota
Cele 7 note de vanzari active, cu nota contabila atasata:
| `ID_NOTA` | Denumire | `ID_SET` | `SCD` | **`SCC`** | **`PTVA`** | `CU_TVA` | **`IN_VALUTA`** |
|---|---|---|---|---|---|---|---|
| 1 | `NOTA 1` | 251023 | `4111` | **704** | **21** | 1 | 0 |
| 2 | `DISCOUNT` | 251024 | `667` | 4111 | 21 | 1 | 0 |
| 3 | `COMISION INTERMEDIERE` | 251025 | `4111` | **704** | **0** | 1 | **1** |
| 4 | `SERVICII VALUTA` | 251026 | `4111` | **704** | **0** | 1 | **1** |
| 5 | `VANZARE MARFA` | 251027 | `4111` | **707** | **5** | 1 | 0 |
| 6 | `PRODUCTIE` | 251028 | `4111` | **7015** | 21 | 1 | 0 |
| 7 | `SERVICII TRANSPORT` | 251029 | `4111` | **704** | 21 | 1 | 0 |
> **RETRAS — verificat pe cod si infirmat.** Ingrijorarea de mai jos, formulata la prima citire a acestor
> date („`PTVA=5` pe singura nota cu `SCC=707` produce TVA greșit"), **nu se susține**.
> `docs\cercetare\s10_pret_rederivat.md`, „Completare: nota contabila a politicii" p. 2: `cursor_articol`
> (`PACK_FACTURARE:7218-7271`) **nu selecteaza `D.PTVA`**; cota folosita e
> `detalii_articol.proc_tvav * 100 - 100` (`:7464`), din articol / document. Coloana e moarta pentru
> `contabilizeaza_articol`. Iar `IN_VALUTA=1` pe un document in lei nu da eroare si nu strica suma —
> doar populeaza redundant `ACT_TEMP.SUMA_VAL` la curs 1. **Deci criteriul de selectie NU trebuie extins
> cu `PTVA` / `IN_VALUTA`.** Se pastreaza textul de mai jos doar ca urma a raționamentului; nu se citeaza
> ca risc.
Ce **rămâne** ca risc real din aceste date, si e mai grav: `cursor_articol` face `LEFT JOIN
NOTE_CONTABILE ON C.ID_SET = D.ID_SET` fara `ROWNUM` si fara agregare, iar bucla care il consuma executa
`scrie_nota` **si** `descarca_gestiune` o data **pentru fiecare rand al setului**, cu pretul si cantitatea
intregi de fiecare data. Deci un set cu N randuri = **venit inregistrat de N ori si gestiune descarcata de
N ori**. Cele 7 note de vanzari active au exact un rand fiecare — dar **30 din cele 40 de seturi din baza
au mai multe, cu maxim 30**. Vezi punctul 6.1 mai jos.
Ce planul enunta drept **avantaj** al retetei — „politica reala aduce si `CU_TVA`, `IN_VALUTA`,
`EXPLICATIE`, `ASCD`, `ASCC`" — se confirma ca argument, cu nuanta ca `ASCD`/`ASCC` au oricum fallback
automat din grupul de utilizatori, deci nu ele justifica alegerea.
Textul original al ingrijorarii, pastrat pentru trasabilitate:
- **`707` are o singura nota, si aceea cu `PTVA=5`.** Pentru marfa (majoritatea articolelor de gestiune)
singurul rezultat posibil al pasului 2 e nota cu TVA 5%.
- **Doua din cele patru note cu `SCC=704` au `IN_VALUTA=1`** (`SERVICII VALUTA`, `COMISION INTERMEDIERE`),
deci o potrivire pe `SCC` poate ateriza pe ele pe un document in lei.
Colateral util pentru **S4c**: exista deja o nota de discount configurata — `id_nota=2`, `SCD=667` /
`SCC=4111`. Nu e o eroare de configurare (explica randul `SCC=4111` din agregat), e nota folosita pentru
discount. De verificat la S4c daca discountul pe linie trece prin ea.
## 5. Efectul colateral al pasului 3 — politica **este** o lista de preturi
Pasul 3 al retetei insereaza articolul in politica alesa, prin `pack_preturi.adauga_politica_pret_art`.
Politicile candidate, cu numarul de articole pe care le contin azi:
| `SCC` | `ID_POL` | Nume | Articole |
|---|---|---|---|
| `7015` | 41 | `STOC PRODUSE` | **6310** |
| `707` | 39 | `LISTA PRETURI LEI` | **909** |
| `707` | 40 | `LISTA PRETURI CU TVA EURO` | 2 |
| `707` | 47 / 62 | `DEPOZIT ELI` / `DEPOZIT ELI_31/07/2025 11:07:25` | 3 / 3 |
| `704` | 2 | `LISTA 2` | 19 |
| `704` | 34 | `LISTA 2 - EURO` | 12 |
| `704` | 36 | `SERVICE AUTO` | 9 |
| `704` | 35 | `LISTA STANDARD EURO` | 8 |
| `704` | 9 | `CHIOSC` | 5 |
| `704` | 6, 12, 13, 14, 15, 16 | `LISTA DE PROBA`, `S5 ZILNIC`, `S6 WEEKEND SEZON`, `S7 CRACIUN`, `S3 PASTE`, `S3 1MAI` | 4 fiecare |
| `704` | 65, 66 | `TRANSPORT`, `SERVICII INFORMATICE` | 1 fiecare |
| `704` | 26, 27, 28, 29, 30, 31 | `S6 ZILE IARNA`, `S8 REVELION`, `S1 IARNA2`, `S1 IARNA2 WEEKEND`, `S2 PRIMAVARA`, `S7 CRACIUN` | **0** |
**Astea sunt liste de preturi reale, cu nume de client si de sezon.** Daca reteta alege `LISTA PRETURI
LEI` sau `STOC PRODUSE` pentru un articol adaugat ad-hoc pe factura, articolul **apare de acum in lista
de preturi a acelui context**, cu pretul cu care a fost facturat o data. Asta e efectul pe care planul il
descrie drept „politica tehnica populata automat" — dar **numai daca politica e chiar tehnica**.
Alegerea unei politici comerciale existente **poluează o lista de preturi de producție**.
Consecinta de proiectare, de dus in J-quater: pasul 2 nu trebuie sa caute *orice* politica cu `SCC`-ul
potrivit, ci **o politica tehnica dedicata, una per `SCC`**, creata anume — modelul
`gnId_pol_pret_stoc` extins de la o politica la sapte. Cele sase politici cu **0 articole** arata ca o
politica goala e o stare acceptata de produs.
Detaliu care ajuta: **majoritatea politicilor cu `SCC=704` trimit la aceeasi `id_nota=1`**. Deci
ambiguitatea celor 19 politici e ambiguitate de **lista de preturi**, nu de rezultat contabil — toate dau
acelasi `704` / `PTVA=21` / lei. Cu atat mai mult, criteriul de alegere trebuie sa fie „care lista de
preturi vreau sa murdaresc", si raspunsul corect e „niciuna dintre cele existente".
## 6. Fragilitati masurate, de trecut in Riscuri
1. **`ID_SET` cu mai multe randuri dubleaza venitul si descarcarea de gestiune.** Pe cele 7 seturi
folosite de politici, fiecare are exact **1** rand `NOTE_CONTABILE`. Dar in tabel sunt **40 de seturi,
din care 30 au mai multe randuri**, cu maxim **30 de randuri pe set**. Confirmat pe cod
(`s10_pret_rederivat.md`, Completare p. 1): `cursor_articol` face `LEFT JOIN ... ON C.ID_SET =
D.ID_SET` **fara `ROWNUM`, fara agregare, fara filtru**, iar bucla `:7393-7542` executa `scrie_nota`
**si** `descarca_gestiune` o data per rand, **cu aceeasi cantitate si acelasi pret intreg**, fara
distributie (`ORDINE` nu apare in tot pachetul). Deci pasul 2 al retetei trebuie sa garanteze **un
singur rand pe setul ales**, nu doar un rand cu `SCC`-ul potrivit. Conditia care face reteta sigura
azi e o coincidenta a datelor, nu o garanție a codului sau a schemei.
2. **Doua politici active fara `ID_NOTA`**: `32 HOTEL TAXE` si `33 HOTEL CAZARE` (ambele valabile
01.01.2010–31.12.2099, `id_valuta=3`). Un articol pe una din ele are `id_pol` valid dar **fara nota** —
comportament neverificat, intrebare trimisa agentului pe `PACK_FACTURARE`. E o gaura care **exista
deja azi**, independenta de #13.
3. **Fara unique pe `CORESP_CONT_VENCHELT.CONT`** — vezi punctul 1.
## 7. Distributia articolelor — cat de mult conteaza fiecare ramura a deciziei 27
`NOM_ARTICOLE`, 6430 articole active:
| Prima cifra din `CONT` | Articole | Conturi distincte | Ramura deciziei 27 |
|---|---|---|---|
| `3xx` | **6125** | 9 | gestionabil → `CORESP_CONT_VENCHELT` |
| `8xx` (toate `8035`) | 26 | 1 | nu e 6xx/7xx, nu e in `CORESP` → **`704`** |
| `6xx` | 22 | 5 | `NOM_ARTICOLE.CONT` ca atare |
| `7xx` | 20 | 6 | `NOM_ARTICOLE.CONT` ca atare |
| `1xx` | 4 | 2 | → `704` |
| `4xx` | 3 | 2 | → `704` |
| `0xx` | 1 | 1 | → `704` |
| `2xx` | 1 | 1 | → `704` |
| **fara `CONT`** | **228** | — | → `704` |
Conturi de pe articole care **nu** sunt 6xx/7xx si **nu** apar in `CORESP_CONT_VENCHELT.CONT`, deci cad pe
`704`: `8035` (26 articole), `123` (3), `446` (2), `111`, `409`, `0`, `212`, `37` (1 fiecare).
**Ramura dominanta e de departe cea gestionabila** — 6125 din 6430. Iar ea trimite in majoritate spre
`707`, adica spre cazul cu `PTVA=5`. Cele 228 de articole fara cont si cele 26 pe `8035` fac fallback-ul
`704` un caz real, nu teoretic — si acolo reteta functioneaza.
Nota: `212` apare pe un articol, dar in `CORESP_CONT_VENCHELT` figureaza ca `CONT_CHELT` (pe randul
`231`), nu ca `CONT`. Deci cade corect pe `704` prin regula deciziei 27.
## Ce nu s-a putut stabili aici, si de ce
- **Ce face `contabilizeaza_articol` cu `PTVA` / `CU_TVA` / `IN_VALUTA` ale notei** — e intrebare de cod,
nu de date. Trimisa agentului deja incarcat in `PACK_FACTURARE`; raspunsul intra in
`docs\cercetare\s10_pret_rederivat.md`, sectiunea „Completare: nota contabila a politicii". **Pana
atunci pasul 2 al retetei nu se considera validat.**
- **Daca `CONT_VENIT` chiar n-are consumatori** — stabilit pe cod in runda 8, nu reverificat pe date.
- **Comportamentul politicii fara nota** — idem, intrebare de cod, trimisa.
- Verificarile sunt pe **schema de dezvoltare** `MARIUSM_AUTO`. Datele sunt reprezentative (liste de
preturi cu nume reale, 6430 articole), dar **o baza de client poate avea alta configurare de note** —
in special poate avea deja politici pe `711` / `702` / `703`. Concluziile pe **structura** (lantul,
absenta unique-ului, `ID_SET` multi-rand) sunt insa independente de date.
## Reproducere
Cele trei scripturi rulate, in ordine: descoperire DDL/coloane · interogarea inversa pe `SCC` ·
efecte colaterale si distributii. Interogarea-cheie e citata integral la punctul 3. Rulare:
```powershell
& 'D:\ROA\instantclient_19_18\sqlplus.exe' -L -S 'MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL' '@fisier.sql'
```
SQL-ul se scrie in fisier **ASCII**, nu prin pipe din PowerShell (BOM-ul UTF-16 sparge parserul), si cu
`linesize 32767` — vezi `COMUN\docs\oracle_export.md`.

View File

@@ -1,325 +0,0 @@
# Verificare adversariala — golul ntip=4 si "ramurile de aviz" (contabilizeaza_articol)
Verifica raportul `docs/cercetare/gol_ntip4_factura_din_avize.md`. Rol: incercare de infirmare,
nu confirmare. Toate citatele de mai jos sunt re-verificate direct in aceasta runda pe
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii) si
pe `COMUN\clase\ofacturare.vc2` (ambele copii, `frm_facturare_articole` ~13967-14500 si
`frm_facturare_articole2` ~18003-18500). Status: **TERMINAT**.
## Corectie structurala prealabila — cei "3 apelanti" ai `contabilizeaza_articol` sunt gresit numiti
Toate rapoartele anterioare (`canal_cont_venit_fara_politica.md:67`, `nota_contabila_fara_politica.md:103`,
si implicit raportul verificat) numesc cei 3 apelanti interni `scrie_factura2`,
**`scrie_factura_avize_retur`**, `scrie_aviz_retur`. **Gresit pentru al doilea.** Verificare directa
pe granitele reale (`PROCEDURE ...` / `END ...`):
```
ff_...:6264 PROCEDURE scrie_factura_avize_retur(... ff_...:6657 END scrie_factura_avize_retur;
ff_...:6692 PROCEDURE scrie_factura_avize(... ff_...:7058 END scrie_factura_avize;
ff_...:7085 PROCEDURE scrie_aviz_retur(... ff_...:7171 END scrie_aviz_retur;
```
Apelul de la `ff_...:6858` (`pack_facturare.contabilizeaza_articol(tab_detalii(i));`, in interiorul
`IF articole_aviz(j).custodie = 1 THEN`) cade **intre 6692 si 7058** — deci apartine lui
**`scrie_factura_avize`** (fara `_retur`), nu lui `scrie_factura_avize_retur` (care se termina la
6657, cu 200+ linii inainte). `scrie_factura_avize_retur` **nu cheama deloc**
`contabilizeaza_articol` — corpul ei (6264-6657) insereaza direct in `VANZARI_DETALII_TEMP` din
`VANZARI_DETALII` (randurile avizului sursa deja existente), fara sa treaca prin
`adauga_articol_factura` sau `contabilizeaza_articol`.
**De ce conteaza**: `scrie_factura_avize` e exact handler-ul Oracle pentru `ntip=4` ("facturare din
aviz") — apelat din VFP la `ofacturare.vc2:14318` (`Case poDate.Tip = 4`). Deci al doilea apel real
catre `contabilizeaza_articol` **e in interiorul propriului handler `ntip=4`** (pentru randurile
`custodie=1`), nu intr-un flux separat "avize+retur". Cei 3 apelanti reali sunt:
| Apel | Procedura reala | Cand se ajunge la ea din VFP |
|---|---|---|
| `ff_...:6141` | `scrie_factura2` | `Case Inlist(poDate.Tip,3,21,25,28,42,47)` sau `Otherwise` (`ofacturare.vc2:14332,14361`, identic la `:18330,18361`) — calea principala pentru aproape toate tipurile, inclusiv avizele |
| `ff_...:6858` | `scrie_factura_avize` | `Case poDate.Tip = 4` (`:14301,14318`) — doar pentru randurile `custodie=1` ale unei facturi din aviz |
| `ff_...:7140` | `scrie_aviz_retur` | **Niciun apel gasit in `COMUN\` din VFP** (`Grep` pe tot `COMUN` si pe tot proiectul — zero rezultate in cod, doar in rapoarte). Posibil cod mort/apelat din alt produs; nu afecteaza verdictele de mai jos, pentru ca `scrie_factura2` singura e suficienta sa dovedeasca A1a. |
Corectia nu schimba verdictul general al A1a (avizele *ajung* la `contabilizeaza_articol`), dar
schimba **prin ce drum** — relevant pentru oricine ar urma sa scrie cod pe baza acestor rapoarte.
## A1. Ramurile de AVIZ raman atinse si ramura noua le-ar scrie SCD gresit
**Verdict: CONFIRMAT, dar mai ingust decat sustine raportul.**
### A1a. Ajunge `contabilizeaza_articol` sa fie apelata pe un AVIZ?
**DA, confirmat**, prin `scrie_factura2`. Ruta VFP (`ofacturare.vc2:14282-14389`, identic la
`:18enable...`, verificata in ambele copii ale formularului):
```
Case poDate.eProforma = 1 -> scrie_proforma
Case poDate.Tip = 4 -> scrie_factura_avize && facturare din aviz
Case Inlist(poDate.Tip,3,21,25,28,42,47) -> scrie_factura2 && din comenzi / avize din comenzi
Otherwise -> scrie_factura2 && tot restul (incl. 29, 22, 24, 27...)
```
In interiorul `scrie_factura2` (`ff_...:6072-6142`), CASE-ul propriu **intercepteaza unele `ntip`
inainte** de a ajunge la `contabilizeaza_articol`:
```
WHEN ntip IN (23,25,30,41) THEN transfera_articol(...) -- NU contabilizeaza_articol
WHEN ntip IN (42,47) THEN descarca_gestiune(...) direct -- NU contabilizeaza_articol
WHEN ntip IN (2,6,52) AND id_rata<>0 THEN contabilizeaza_rata(...)
ELSE contabilizeaza_articol(...) -- aici ajung 28, 29, 21, 22, 24, 27, 3, ...
```
Deci `ntip=28` si `ntip=29` **chiar ajung** la `contabilizeaza_articol` (prin `scrie_factura2`, ramura
`ELSE`) — asta confirma miezul A1a. Dar `ntip IN (23,25,30,41,42,47)`, desi trec prin `scrie_factura2`
(sunt in lista VFP `Inlist(3,21,25,28,42,47)`), **nu ajung niciodata** la `contabilizeaza_articol` —
sunt deviate mai devreme, in CASE-ul propriu al lui `scrie_factura2`, spre `transfera_articol`/
`descarca_gestiune`. Vezi corectia la sectiunea 3 (tabelul per-`ntip`).
### A1b. Poate un articol fara politica sa fie adaugat pe un aviz?
**DA, confirmat structural**, pe doua cai distincte in `adauga_articol_factura` (`ff_...:5052-5203`):
- `WHEN ntip IN (3,21,28,42,47)` (`:5053-5078`): cauta in `COMENZI_ELEMENTE` dupa
`ID_COMANDA + ID_ARTICOL + PRET` — **fara nicio referinta la `V_ID_POL`** in `WHERE`. Un articol
fara politica poate gasi potrivire aici daca exista elementul de comanda corespunzator (comanda +
articol + pret identice), indiferent de politica. **Fara handler `EXCEPTION`** — la fel ca ramura
`ntip=4`, dar aici absenta handler-ului nu conteaza pentru `id_pol`, pentru ca filtrul nu-l
foloseste.
- `ELSE` (`:5187-5203`, bucket-ul implicit pentru orice `ntip` neacoperit de ramurile speciale —
include `29` si restul avizelor "simple"): **nicio interogare legata de politica** — ia direct
`V_PRET_TEMP`/`V_ID_VALUTA_TEMP`/`V_PRETURI_CU_TVA_TEMP`/`V_IN_STOC_TEMP` (valorile deja calculate
de VFP), plus o singura `SELECT` pe `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` (nimic legat de
`id_pol`). **Zero obstacol** pentru un articol fara politica pe aceasta ramura.
Nu am gasit (si nu am cautat exhaustiv, la fel ca raportul original) formularul VFP concret care ar
adauga azi un articol ad-hoc fara politica pe un aviz — premisa ramane, ca si in raportul original,
mostenita din proiectul #13 (`canal_cont_venit_fara_politica.md`), nu re-derivata aici. Ce am putut
confirma exhaustiv e partea Oracle: **daca** VFP trimite `V_ID_POL=NULL` pe aceste `ntip`, Oracle nu
pune nicio piedica.
### A1c. `SCD` in blocul `:7398-7423` — hardcodat sau derivat?
Citit integral (`ff_...:7398-7423`), confirmat identic cu citatul raportului, **linie cu linie**:
```
ff_...:7400-7406 WHEN ntip<=20 OR ntip IN (nTipFacturaHotel,nTipFacturaRestaurant,
nTipNotaPlata,nTipVanzareRetail,48,49,51,52) THEN
V_SCD := crs_rand_articol.scd; -- din politica (NOTE_CONTABILE.SCD)
ff_...:7413-7417 WHEN ntip IN (28,29) THEN
V_SCD := '461'; -- HARDCODAT, aviz catre clienti debitori
ff_...:7418-7422 ELSE
V_SCD := '418'; -- HARDCODAT, aviz generic
```
`V_SCC` (linia 7425) vine mereu din `crs_rand_articol.scc` (coloana notei de vanzari), **indiferent
de ramura** — CASE-ul de mai sus decide doar `V_SCD`. Confirmat: hardcodarea exista exact cum spune
raportul, fara offset de linie.
**Nuanta importanta, omisa de raport**: proiectarea `canal_cont_venit_fara_politica.md:146` (sursa
ramurii noi) **flagheaza deja, printr-o paranteza**, ca "ramurile aviz raman `'418'`/`'461'`, deja
hardcodate azi la `ff_...:7415,7420`, independent de politica" — deci designul **e constient** de
existenta acestor hardcodari si de faptul ca ar trebui pastrate, dar **nu da structura de cod
concreta** (niciun `IF`/`CASE` pe `ntip` in tabelul "Continutul ramurii noi", sectiunea 3) care sa
implementeze efectiv acea pastrare. Raportul verificat prezinta acest lucru ca pe o **descoperire
noua** ("Gol sora... gasit in aceasta cercetare") — de fapt designul deja stia de riscul general, dar
nu-l rezolvase in cod. Concluzia practica ramane aceeasi (ramura noua, asa cum e descrisa azi in
proiectare, chiar ar hardcoda `SCD='4111'` fara conditionare pe `ntip` daca ar fi implementata
literal dupa tabelul din sectiunea 3), dar caracterizarea "gol nou, neconsemnat" e inexacta.
### Corectie la tabelul per-`ntip` din raport (sectiunea 3)
Raportul marcheaza `IN (28,29)` **si** "toate celelalte (`21,22,23,24,25,27,30,41,42,47,...`)" ca
"Atinsa: DA". Pe baza rutarii reale prin `scrie_factura2` (A1a de mai sus):
| `ntip` | Ajunge la `contabilizeaza_articol`? | Motiv |
|---|---|---|
| `28`, `29` | **DA** | `scrie_factura2` -> `ELSE` -> CASE intern `ntip IN(28,29)` -> `SCD='461'` |
| `21`, `22`, `24`, `27`, `3` si alte "otherwise" nespeciale | **DA** (probabil) | cad in `ELSE` al lui `scrie_factura2` -> `ELSE` intern -> `SCD='418'` |
| `23`, `25`, `30`, `41` | **NU** | interceptate in `scrie_factura2` de `WHEN ntip IN(23,25,30,41) THEN transfera_articol(...)` — nu ajung niciodata la `contabilizeaza_articol` prin acest apelant |
| `42`, `47` | **NU** | interceptate de `WHEN ntip IN(42,47) THEN descarca_gestiune(...) direct` — la fel, nu ajung |
Golul real e deci mai ingust decat "orice aviz": se limiteaza la avizele care cad in ramura `ELSE` a
lui `scrie_factura2` (28, 29, si restul avizelor "simple" neexcluse), nu la `23,25,30,41,42,47`, care
sunt structural neatinse de `contabilizeaza_articol` prin singurul apelant confirmat. (Nu exclud ca
`scrie_aviz_retur`, al carei apelant VFP nu l-am gasit, sa trateze si aceste `ntip` — dar cum nu exista
dovada ca e apelata deloc, nu pot confirma nici infirma pentru ea.)
## A2. Golul `ntip=46` (`nTipNotaPlata`) — `scrie_nota` sarita azi
**Verdict: CONFIRMAT.**
- Constanta: `ff_...:84 nTipNotaPlata VANZARI.TIP%TYPE := 46;` — **exact 46**, verificat direct, nu
presupus.
- Garda: `ff_...:7441 IF pack_facturare.nTip <> pack_facturare.nTipNotaPlata THEN` — inainte de
apelul `scrie_nota` (`:7443-7467`) — citat exact, linie identica cu raportul.
- Poate un `ntip=46` sa primeasca un articol fara politica? **DA**, prin acelasi bucket `ELSE`
(`ff_...:5187-5203`) al `adauga_articol_factura` verificat la A1b — `nTipNotaPlata` nu e in niciuna
din ramurile speciale (`3,21,28,42,47`, `4`, `45`, `V_OPT_FACTURARE=3`), deci cade in `ELSE`, care
nu cere `id_pol` deloc.
- Ajunge la `contabilizeaza_articol`? **DA** — `ntip=46` nu e proforma, nu e `Tip=4`, nu e in
`Inlist(3,21,25,28,42,47)` -> `Otherwise` -> `scrie_factura2` -> nu e in `(23,25,30,41)`/`(42,47)`/
`(2,6,52)` -> `ELSE` -> `contabilizeaza_articol`.
- Ramura noua (per `canal_cont_venit_fara_politica.md` sectiunea 3, pasul 1 "Apeluri, o singura data
fiecare") descrie apelul `scrie_nota` **fara** sa mentioneze garda `ntip<>nTipNotaPlata` — deci, asa
cum e scrisa azi proiectarea, ar apela necondiționat `scrie_nota` pentru un document `NotaPlata` cu
linie `cont_venit`. Gol real, bine argumentat de raport.
## A3. `ntip=4` exclus prin `NO_DATA_FOUND` neprins la `adauga_articol_factura`
**Verdict: CONFIRMAT**, cu toate liniile citate verificate exact, fara offset:
- `ff_...:5080-5103` — ramura `WHEN ntip=4`, `WHERE A.ID_POL = V_ID_POL` (linia 5096 exact), fara
`EXCEPTION`. Confirmat caracter cu caracter identic cu citatul raportului.
- Semantica SQL `NULL = NULL` -> `UNKNOWN`, niciodata `TRUE`: standard, corect.
- **Ordinea de apel confirmata direct pe VFP**: `do_scrie_articole` (`ofacturare.vc2:13967`) cheama
`initializeaza_date_factura` (seteaza `ntip`), apoi in bucla `Scan` peste `crsfactura` cheama
`adauga_articol_factura` (`:14069-14091`) pentru fiecare rand — **toate acestea ruleaza inainte** de
`Do Case` (`:14282`) care alege `scrie_factura_avize`/`scrie_factura2`/etc. Deci un articol cu
`V_ID_POL=NULL` pe `ntip=4` cade la `adauga_articol_factura` **inainte** ca `scrie_factura_avize` sa
fie chemata — linia nu ajunge niciodata la `contabilizeaza_articol`. Confirma independent
concluzia raportului.
- **Semantica VFP `Nvl(Alltrim(Str(poArt.id_pol)),[NULL])`** (`ofacturare.vc2:14072`, identic
`:18107`): in Visual FoxPro, `Str()` si `Alltrim()` **propaga `.NULL.`** cand primesc `.NULL.` ca
argument (comportament standard, documentat al limbajului — orice functie "null-aware" aplicata pe
`.NULL.` intoarce `.NULL.`, nu eroare si nu `"0"`). `Nvl(.NULL., [NULL])` inlocuieste explicit cu
literalul caracter `NULL` (4 caractere), care ajunge **necotat** in textul SQL generat (concatenare
directa, fara ghilimele in jur) — deci Oracle il interpreteaza ca si cuvantul-cheie `NULL`, nu ca
string. Rezultat: `V_ID_POL` ajunge `NULL` in Oracle exact cand `poArt.id_pol` e `.NULL.` in VFP.
Verificare bazata pe semantica standard VFP (nu am putut rula VFP live — masina partajata, conform
interdictiei) — consistenta si cu tiparul deja folosit identic pentru alti parametri opționali pe
acelasi apel (`id_gestiune`, `id_valuta_d`, `id_part_rez` etc., toate cu acelasi `Nvl(Alltrim(Str(...`).
- **Neverificat** (nici in raportul original, nici aici): cum ajunge efectiv `poArt.id_pol` sa fie
`.NULL.` pentru un articol fara politica — ramane premisa mostenita din proiectul #13, nu
re-derivata in aceasta runda.
## A4. Numerele de linie — offset +17 sau identice?
**Verdict: NICIUNA din cele doua afirmatii e o regula generala — depinde de setul de citate comparat.**
- **Pentru citatele proprii ale raportului verificat** (re-testate direct aici): `adauga_articol_factura`
header `:4989-5015`, ramura `ntip=4` `:5080-5103`, `contabilizeaza_articol` header `:7173`, CASE
`:7398-7423`, hardcode `:7413-7422`, garda `:7441`, `nTipNotaPlata:=46` la `:84` — **toate identice**,
zero offset. Raportul are dreptate pentru propriile sale citate.
- **Dar exista un offset real, doar ca nu e +17 ci +9**, intre citatele mai vechi din
`nota_contabila_fara_politica.md:100-104` (`ff_...:6150`, `:6867`, `:7149` pentru cele 3 apeluri
`contabilizeaza_articol`) si pozitiile reale confirmate in aceasta runda pentru **aceleasi** apeluri
(`6141`, `6858`, `7140`) — diferenta consecventa de **9 linii**, nu 17, si in directia opusa
(citatul vechi e mai mare, nu mai mic).
- Concluzia corecta: fisierul `PACK_FACTURARE.sql` a fost re-exportat de mai multe ori pe parcursul
proiectului (cod adaugat/sters intre versiuni), asa ca **fiecare set de citate vechi are propriul
offset fata de exportul curent — nu exista o constanta universala (nici 0, nici +17, nici +9)**.
`handoff_13_formular_unificat.md:190` ("+17") si acest raport ("identic, fara offset") sunt ambele
adevarate **doar pentru seturile de citate pe care le-au verificat fiecare** — nu se pot generaliza
una peste alta. Disciplina corecta, pe care ambele rapoarte au respectat-o, e re-verificarea directa
pe fisierul curent inainte de orice citare — nu presupunerea unui offset fix.
## Text de plan corectat (sectiunea 4 din raportul verificat, rescris)
```
**Ramura `ntip=4` (facturare din avize, handler real `scrie_factura_avize:6692-7058`, nu
`scrie_factura_avize_retur` cum apare gresit in rapoartele anterioare) — exclusa structural, nu
necesita cod suplimentar.** `adauga_articol_factura`, ramura `WHEN ntip=4` (`:5080-5103`), cauta
randul sursa din `VANZARI_DETALII` prin `A.ID_POL = V_ID_POL`, fara handler de exceptie — cu
`V_ID_POL=NULL` (exact cazul liniei fara politica), comparatia nu se poate potrivi niciodata, deci
apelul cade cu `ORA-01403` la adaugarea articolului, in bucla `do_scrie_articole` (`ofacturare.vc2:
13967-14106`), care ruleaza intotdeauna inaintea `Do Case`-ului (`:14282`) ce ar chema
`scrie_factura_avize`. Un articol fara politica nu poate fi, structural, adaugat pe un document
`ntip=4`; ramura noua `cont_venit` nu are nimic de tratat aici.
**Gol confirmat, de acoperit inainte de implementare — mai ingust decat s-a crezut initial**:
ramurile de aviz care **efectiv** ajung la `contabilizeaza_articol` prin singurul apelant confirmat
functional (`scrie_factura2`, apelat pentru `Inlist(3,21,25,28,42,47)` si pentru toate celelalte
tipuri prin `Otherwise`) sunt cele care cad in `ELSE`-ul CASE-ului propriu al `scrie_factura2`:
`ntip IN (28,29)` -> `SCD='461'`, restul (`21,22,24,27` si alte "otherwise" avize) -> `SCD='418'`
(`:7413-7422`). **`ntip IN (23,25,30,41,42,47)` NU ajung la `contabilizeaza_articol`** prin acest
apelant — sunt deviate mai devreme, in `scrie_factura2` insasi, spre `transfera_articol`/
`descarca_gestiune`, deci golul nu li se aplica (contabilizeaza_articol si ramura ei noua nu se
executa niciodata pentru ele). `adauga_articol_factura` nu cere `id_pol` pentru niciunul din aceste
`ntip` (foloseste calea "din comenzi", fara filtru pe `id_pol`, sau calea implicita `ELSE`, fara nicio
interogare legata de politica) — deci un articol fara politica poate ajunge pe oricare din avizele
`28,29,21,22,24,27` cu `cont_venit` populat. Designul deja semnalase acest risc printr-o paranteza
(`canal_cont_venit_fara_politica.md:146`, "ramurile aviz raman 418/461"), dar nu-l implementase: ramura
noua trebuie sa aleaga `SCD` in functie de `ntip` (pastrand `'461'` pentru `IN(28,29)`, `'418'` pentru
restul avizelor atinse), nu sa hardcodeze `'4111'` necondiționat.
**Gol confirmat separat**: ramura noua trebuie sa pastreze garda `IF ntip <> nTipNotaPlata` (`ntip=46`,
`ff_...:7441`) inainte de a apela `scrie_nota` — azi sarita intentionat pentru documentele de tip
"nota de plata", iar un articol fara politica poate fi adaugat si pe acest tip de document
(`adauga_articol_factura`, bucket `ELSE`, fara cerinta de `id_pol`).
**Defensiv (opțional)**: se poate adauga o garda explicita `IF cont_venit IS NOT NULL AND ntip = 4
THEN RAISE_APPLICATION_ERROR(...)`, dar e demonstrat redundanta — cazul e deja imposibil pe caile VFP
cunoscute, blocat cu doua straturi independente inainte de a ajunge la `contabilizeaza_articol`.
```
## Ce nu s-a putut stabili in aceasta runda (mostenit din raportul original, nu re-rezolvat)
- Cum ajunge `poArt.id_pol` sa fie `.NULL.` in VFP pentru un articol fara politica pe un aviz —
premisa proiectului #13, nu re-derivata.
- Cine cheama `scrie_aviz_retur` (daca cineva) — cautare exhaustiva in `COMUN\` si in tot proiectul,
zero rezultate; posibil cod mort sau apelat din alt produs al suitei ROA.
- Comportamentul exact `goExecutor`/`oPrelucrareEroare()` la o exceptie Oracle neprinsa — nu urmarit
in aceasta runda (mostenit ca gol si in raportul original).
## Handoff
Verificare incheiata, toate cele 4 afirmatii + corectia structurala preliminara sunt in sectiunile de
mai sus. Zero cod atins, zero write-back, doar `SELECT`/`Read`/`Grep` pe SQL si VFP text. Context
consumat substantial (citire extinsa pe `ff_...sql` si `ofacturare.vc2`), dar verificarea s-a incheiat
inainte de pragul de predare.
---
## Propagarea `CONT_VENIT` prin cei trei apelanti — verificare
Intrebare noua de la team-lead: argumentul central al proiectarii (`canal_cont_venit_fara_politica.md`,
sectiunea 1, `:63-69`) — "orice coloana noua pe `VANZARI_DETALII_TEMP` ajunge automat in
`detalii_articol`, fara sa atingi cei 3 apelanti, pentru ca fac `SELECT * BULK COLLECT` intr-un
`TABLE OF ...%ROWTYPE`" — se bazeaza pe lista de nume deja corectata mai sus. Verificat acum pe
numele **reale** ale celor 3 apelanti (`scrie_factura2`, `scrie_factura_avize`, `scrie_aviz_retur`),
fiecare citit direct, linie cu linie, in aceasta runda.
### 1-2. `scrie_factura2` (`:6020-6223`)
```
ff_...:6039-6040 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
tab_detalii tab_detalii_type;
ff_...:6063 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
ff_...:6141 pack_facturare.contabilizeaza_articol(tab_detalii(i));
```
**`SELECT *` intr-un `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`, confirmat.** `CONT_VENIT` curge automat.
### 1-2. `scrie_factura_avize` (`:6692-7058`, handler-ul real `ntip=4`)
```
ff_...:6708-6709 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
tab_detalii tab_detalii_type;
ff_...:6749 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
ff_...:6858 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- doar pt. custodie=1
```
**Acelasi tipar: `SELECT *` in `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`, confirmat.** Intre populare
(`:6749`) si apelul catre `contabilizeaza_articol` (`:6858`), randul `tab_detalii(i)` e modificat doar
pe doua campuri (`.cantitate`, `.id_rata`, `:6855-6856`) — `CONT_VENIT` ramane neatins, curge automat.
Chiar daca numele apelantului real difera de ce credea proiectarea, **concluzia ei ("nu se ating deloc")
ramane corecta pentru acest apelant**, doar numele era gresit.
### 3. `scrie_aviz_retur` (`:7085-7171`)
```
ff_...:7097-7098 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
tab_detalii tab_detalii_type;
ff_...:7106 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
ff_...:7140 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- doar pt. custodie=0 (ELSE la :7115)
```
**Acelasi tipar, confirmat.** Nu conteaza ca nu i-am gasit apelant VFP (ramane nedovedit ca se
executa vreodata) — *daca* s-ar executa, `CONT_VENIT` ar curge automat si aici, deci nu schimba
suprafata diff-ului indiferent de raspuns.
### Verdict pe argumentul proiectarii
**Se salveaza.** Toti cei 3 apelanti reali (nu cei numiti gresit in rapoartele anterioare) folosesc
identic `SELECT * BULK COLLECT INTO <TABLE OF VANZARI_DETALII_TEMP%ROWTYPE> FROM VANZARI_DETALII_TEMP`
— zero lista explicita de coloane, zero `%ROWTYPE` pe alta tabela, zero constructie camp-cu-camp.
Concluzia proiectarii ("adaugi coloana pe `VANZARI_DETALII_TEMP`, `contabilizeaza_articol` o vede
automat prin toti apelantii, fara sa atingi `scrie_factura2`/`scrie_factura_avize`/`scrie_aviz_retur`")
**e corecta ca mecanism**, chiar daca doua din cele trei nume citate pentru ea erau gresite. Corectia
de nume nu schimba nimic la suprafata de regresie a schemei — schimba doar la ce trebuie sa te uiti
daca vreodata unul dintre cei trei apelanti isi schimba modul de populare a lui `tab_detalii`.

View File

@@ -1,366 +0,0 @@
# Verificare — alegerea stocului pe proforma (runda de verificare)
Investigatie read-only, 10.08.2026. Raspunde la 4 intrebari punctuale cerute de team-lead, pentru
decizia de proiectare S5b (comutare FACTURA <-> PROFORMA cu linii deja adaugate).
Continua fara sa reia: `s5b_proforma_descarcare_gestiune.md` (mecanismul `gestionabil=0` /
`id_gestiune=-1000`) si `s5b_proiectare_proforma_copiere.md` (proiectarea de runda 12, care a
descoperit deja ca `scrie_proforma` nu cheama `contabilizeaza_articol` deloc).
Nicio modificare de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit, nicio scriere Oracle
(numai `SELECT`/`Read`/`Grep` pe fisiere de pe disc). Sursa Oracle folosita:
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (citat mai jos
`PACK:linie`).
**Status: COMPLET.**
---
## Rezumat executiv
1. **Da, pe calea principala si pe toate celelalte surse (cu exceptia copierii), stocul NU se
alege la adaugarea unei linii pe proforma azi** — pentru ca `gestionabil` a fost deja fortat pe
`0` in VFP, in masa, **inainte** ca gridul de linii sa se deschida. Cand ruleaza `Do Case`-ul de
la `ofacturare.vc2:13803`, `poArticol.gestionabil` e deja `0`, nu valoarea reala din nomenclator.
2. **Marcarea negestionabila are un singur mecanism activ azi, exclusiv in VFP**:
`UPDATE (m.lcCursor) SET gestionabil = 0` (`ofacturare.prg:333-336`), rulat in interiorul
functiei `factureaza()`, imediat dupa incarcarea cursorului candidat (`crsarticole`) si
**inainte** de construirea `crsfactura` (`creeaza_facturacrs`, linia 338) — adica la
**deschiderea documentului / incarcarea liniilor candidate**, nu la Termina/salvare. **Exista si
un al doilea mecanism, in Oracle**, in `cursor_retur_document` (`PACK:3993-4000`,
`CASE WHEN V_PROFORMA=1 THEN 0 ...`), dar acesta e **mort in fluxul curent**: `cursor_retur_document`
se cheama dintr-un singur loc (`ofacturare.prg:268`, ramura de copiere), cu
`V_PROFORMA = poDate.eProforma` **al documentului nou**, care e **intotdeauna 0** dupa copiere
(`nIdTipDoc` nu se propaga de la sursa, `ofacturare_comun.prg:370`) — deci ramura `V_PROFORMA=1`
nu se activeaza niciodata azi. **Nu e o contradictie intre cele doua rapoarte anterioare — sunt
doua mecanisme reale in cod, dar doar unul (cel VFP) e activ pe traseul de creare a unei
proforme; cel Oracle exista doar pentru copiere si acolo nu se declanseaza cu valoarea curenta a
parametrului.**
3. **`pret_achizitie` depinde de sursa**: pe calea principala (`cursor_preturi`, lista de preturi)
NU se completeaza deloc din cursor (coloana nici nu exista in `SELECT`-ul lui `cursor_preturi`)
— ramane `0`, prin acelasi tipar de valoare implicita ca la `id_gestiune` (`do_initializeaza_articol`).
Pe sursele care incarca dintr-un document existent (`cursor_avize`, `cursor_retur_document` —
avize si copiere), `PRET_ACHIZITIE` **e** selectat direct din `VANZARI_DETALII`, deci ajunge real.
4. **Daca proforma ar pastra `id_gestiune` real pana la salvare, nimic din contabilizare/stoc nu
s-ar strica** — pentru ca blocajul real nu e sentinela `-1000`, ci faptul ca `scrie_proforma`
(calea Oracle aleasa la Termina cand `eProforma=1`) **nu cheama niciodata**
`contabilizeaza_articol`/`descarca_gestiune` (confirmat deja in raportul-sursa de runda 12).
`adauga_articol_factura` (care ar primi `V_ID_GESTIUNE` real) **se cheama oricum**, neconditionat
de `eProforma`, pentru orice linie — deci un `id_gestiune` real ar ajunge fara probleme in
`VANZARI_DETALII_TEMP`/`VANZARI_DETALII`, fara sa declanseze nimic in plus. **`do_alege_stoc` nu
rezerva stoc real** — face doar un `SELECT` (cursoare `cursor_gestiuni_articol*`) si scade local,
in memorie, cantitatile deja puse pe documentul curent (`crsfactura`), ca operatorul sa nu aleaga
de doua ori acelasi lot; nu scrie nimic in Oracle. Deci lasarea lui `do_alege_stoc` sa ruleze pe
proforma nu ar bloca stoc pentru nimeni. Singurul risc real gasit e cel deja semnalat in
`s5b_proiectare_proforma_copiere.md` §5 (drumul invers, proforma -> factura in aceeasi sesiune),
care ramane valabil indiferent de aceasta decizie.
---
## Intrebarea 1 — se alege stocul la adaugarea unui articol pe proforma azi?
**Raspuns: NU, pe niciuna dintre caile de creare directa a unei proforme** (nu prin copiere).
### Calea principala — `cursor_preturi`
`cursor_preturi` (`PACK:2138-2646`) e apelat pentru `tnTip` in `{45, 1, 22, 5, 29, 7, 10, 23}`
(`ofacturare.prg:275-282` — lista de preturi, inclusiv `restaurant`), adica cea mai comuna sursa
pentru o proforma noua. SELECT-ul lui `cursor_preturi` intoarce `GESTIONABIL` ca
`C.IN_STOC AS GESTIONABIL` (`PACK:2192`, `:2297` — valoarea reala din `NOM_ARTICOLE`, fara nicio
constienta de proforma; `cursor_preturi` **nu are parametru** `V_PROFORMA`, confirmat prin grep pe
tot fisierul: `V_PROFORMA` apare doar la declaratia si corpul lui `cursor_retur_document`,
`PACK:408, 3939, 3944, 3952, 3957, 3994`).
Insa, imediat dupa ce acest cursor e adus in `crsarticole` in VFP, **inainte** ca operatorul sa
apuce sa vada gridul de linii:
```
COMUN\programe\ofacturare.prg:330-336
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
* 12.03.2021
IF poDate.eProforma = 1
UPDATE (m.lcCursor) SET gestionabil = 0
GO TOP IN (m.lcCursor)
ENDIF
```
`m.lcCursor` = `crsarticole` (setat la `:310`). Acest bloc ruleaza **dupa** `Do Case`-ul de
incarcare (`:266-308`) si **inainte** de `creeaza_facturacrs([crsfactura])` (`:338`) — adica la
compunerea listei de articole candidate, nu la salvare. Cand operatorul adauga o linie mai tarziu,
`Do Case`-ul care alege dialogul (`ofacturare.vc2:13803-13809`) citeste `poArticol.gestionabil`,
care e deja `0` pentru **toate** liniile candidate (setat in masa mai sus), deci intra pe ramura
`frm_articol_factura`, **niciodata** pe `Thisform.do_alege_stoc`.
### Celelalte cursoare (fara copiere)
Verificat direct in `PACK_FACTURARE`, niciunul din urmatoarele cursoare, folosite pentru celelalte
surse ale unei proforme noi, nu are parametru `V_PROFORMA` si nici nu calculeaza `GESTIONABIL` in
functie de proforma — toate intorc valoarea reala din nomenclator/contract:
| Cursor | Apelat pentru (`tnTip`) | `GESTIONABIL` in SELECT | Linie |
|---|---|---|---|
| `cursor_preturi` | 45,1,22,5,29,7,10,23 | `C.IN_STOC` | `PACK:2192,2297` |
| `cursor_contract` | 2,26,6,52 | `A.GESTIONABIL` (coloana reala) | `PACK:2411,2487,2572` |
| `cursor_comanda` | 3,21,25,28,42,47 | `E.IN_STOC` | `PACK:2789` |
| `cursor_lucrare` | 27 | `C.IN_STOC` | `PACK:3029` |
| `cursor_articole_k` | 48,49 | `C.IN_STOC` | `PACK:3117` |
| `cursor_avize` | 4 | `C.IN_STOC` | `PACK:3634` |
| `cursor_aviz_nir` | 30 | `0` (hardcodat) | `PACK:3745` |
| `cursor_gestiune` | 41 | `A.GESTIONABIL` | `PACK:4104` |
| `cursor_retur` | 8,9,24 | (nu are coloana `GESTIONABIL` separata — vezi nota) | `PACK:3934-3948` |
Pentru **toate** aceste surse, aceeasi bucla `ofacturare.prg:330-336` e singurul loc care forteaza
`gestionabil=0` cand `poDate.eProforma=1` — mecanismul e identic indiferent de sursa, pentru ca
`UPDATE (m.lcCursor) SET gestionabil = 0` ruleaza pe `crsarticole` dupa orice ramura a `Do Case`-ului
de la `:266-308`, nu doar pe ramura lista-de-preturi.
### Ramura de copiere — singura cu parametru `V_PROFORMA` in Oracle
`Case m.llCopiere` (`ofacturare.prg:267-268`) e **singura** ramura care apeleaza
`cursor_retur_document`, singurul cursor cu parametru `V_PROFORMA`. Insa la copiere, documentul nou
pleaca intotdeauna cu `eProforma=0` (`nIdTipDoc` explicit necopiat, `ofacturare_comun.prg:370` —
deja stabilit in `s5b_proiectare_proforma_copiere.md` §2.3), deci `V_PROFORMA` trimis e `0`, iar
ramura `GESTIONABIL = B.IN_STOC` (valoarea reala) se activeaza, nu `WHEN V_PROFORMA=1 THEN 0`. Asta
e comportamentul **corect si dorit** la copiere (liniile redevin gestionabile) — dar confirma ca
`V_PROFORMA=1` nu apare niciodata cu valoarea `1` in vreun apel real azi (vezi Intrebarea 2).
**Concluzie Intrebarea 1**: pe orice cale de creare directa a unei proforme (nu copiere), la
momentul `Do Case`-ului de adaugare a liniei (`ofacturare.vc2:13803`), `poArticol.gestionabil` e
deja `0` — fortat de VFP la incarcare, nu valoarea reala din nomenclator. `do_alege_stoc` nu ruleaza
niciodata pentru o linie noua adaugata sub `eProforma=1`, indiferent de sursa.
---
## Intrebarea 2 — unde exact se face marcarea negestionabila si CAND?
**Doua locuri in cod, dar un singur loc activ azi:**
### 2.1 VFP — activ, la incarcarea documentului (nu la salvare)
`COMUN\programe\ofacturare.prg:333-336`, in interiorul procedurii `factureaza()`. Secventa completa
in `factureaza()`:
1. `:266-308` — `Do Case` pe `tnTip`/`llCopiere`, alege cursorul Oracle si il aduce in `crsarticole`.
2. `:311` — `goExecutor.oExecute(lcSqlCursor, lcCursor)` — executa efectiv apelul Oracle.
3. `:324-336` — daca sunt randuri, **si daca `poDate.eProforma = 1`**: `UPDATE crsarticole SET gestionabil = 0`.
4. `:338` — abia acum `creeaza_facturacrs([crsfactura])` construieste cursorul gol de linii ale
documentului; liniile candidate (`crsarticole`, deja marcate) raman disponibile pentru ca
operatorul sa aleaga din ele.
Acest pas ruleaza **la deschiderea ecranului de adaugare articole**, mult inainte de `Termina`
(`do_scrie_factura`, apelat doar la click pe butonul de finalizare — vezi 2.3 mai jos). Nu exista
niciun `UPDATE`/`REPLACE gestionabil With 0` la salvare in `do_scrie_factura` sau `do_scrie_articole`
— am cautat explicit `gestionabil` in ambele metode (`ofacturare.vc2:14197-14380`,`:13967-14150`) si
singura referinta la `gestionabil` in acea zona e citirea lui la `Do Case`-ul din `:13803` (decizia
de dialog), nu o scriere.
### 2.2 Oracle — exista in cod, dar mort in fluxul curent
`cursor_retur_document` (`PACK:3949-4064`), branch la `PACK:3993-4000`:
```sql
(case
when V_PROFORMA = 1 then 0
when V_COPIERE = 1 then B.IN_STOC
else A.GESTIONABIL
end) AS GESTIONABIL,
```
cu comentariu explicit in cod: `-- V_PROFORMA: Daca este proforma (1), fac articolele negestionabile
sa pot alege orice cantitate` (`PACK:3957`). Acest cod **exista si e corect scris**, dar
`cursor_retur_document` are un singur punct de apel in tot codul VFP (confirmat prin cautare in
`ofacturare.prg`, singura potrivire e la `:268`; potrivirile suplimentare gasite in
`COMUN\.svn\pristine\*` sunt copii istorice ale **aceleiasi** linii, nu apeluri suplimentare), si
acolo `V_PROFORMA` trimis e `poDate.eProforma` **al documentului nou** din copiere, care e
intotdeauna `0` (Intrebarea 1). **Deci acest branch Oracle nu se activeaza niciodata cu valoarea 1
in fluxul curent** — e cod mort din perspectiva efectului observabil, desi sintactic corect si
prezent.
### 2.3 Clarificare pe "inainte de compunerea documentului" vs. "la salvare"
Formularea raportului de runda 9 ("VFP marcheaza toate liniile proformei negestionabile inainte de
compunerea documentului") e **corecta** — "compunerea documentului" inseamna acolo constructia
listei de articole candidate/`crsfactura` (pasul 4 de mai sus), care se intampla la
**deschiderea** ecranului de facturare, nu la apasarea `Termina`. Nu exista o contradictie reala cu
mentiunea `V_PROFORMA` din `cursor_retur_document` din brief — acel `CASE` Oracle e un mecanism
separat, pentru un cursor diferit (candidati la copiere), care azi nu se declanseaza niciodata cu
`V_PROFORMA=1`. Ambele afirmatii din surse sunt adevarate simultan, pentru ca descriu lucruri
diferite.
**Concluzie Intrebarea 2**: singurul mecanism activ e `UPDATE` de masa in VFP
(`ofacturare.prg:333-336`), care ruleaza in `factureaza()`, la incarcarea/compunerea listei de
articole candidate — **cu mult inainte** de `Termina`/`do_scrie_factura`. Mecanismul Oracle din
`cursor_retur_document` exista in cod dar nu se activeaza cu valoarea curenta a parametrilor.
---
## Intrebarea 3 — se completeaza `pret_achizitie` pe liniile de proforma?
**Depinde strict de sursa cursorului, la fel ca la `gestionabil` — dar cu rezultat opus pentru
calea principala.**
### Ce cursoare selecteaza `PRET_ACHIZITIE`
Cautare directa in `PACK_FACTURARE` (`grep PRET_ACHIZITIE`): coloana **nu apare deloc** in
`cursor_preturi` (`PACK:2138-2646`), `cursor_contract` (`:2646-2952`), `cursor_comanda`
(`:2952-3173`), `cursor_lucrare` (`:3173-3595`), `cursor_articole_k` (`:3595-3703`),
`cursor_gestiune` (`:4158-4349`). Apare doar in:
- `cursor_avize` (in jurul liniilor `PACK:3754-3864` — sursa `VANZARI_DETALII`/`RUL`, articol
provenit dintr-un aviz existent);
- `cursor_retur_document` (`PACK:4026`, `A.PRET_ACHIZITIE` direct din `VANZARI_DETALII`, folosit la
copiere).
### Ce se intampla in VFP cand lipseste
`crsfactura` (structura din `creeaza_facturacrs`, `ofacturare_comun.prg:1777`) are un camp
`pret_achizitie` in schema — dar o linie noua nu se construieste prin copiere in masa din
`crsarticole`, ci prin `Scatter`/`Gather` pe obiectul `poArticol`, populat cand utilizatorul alege un
articol din grid. `do_initializeaza_articol` (`ofacturare.vc2:13715-13719`), apelat pentru orice
linie care trece prin `frm_articol_factura` (ramura negestionabila, deci si orice linie de
proforma):
```
If Type('toArticol.pret_achizitie')="U"
AddProperty(toArticol,'pret_achizitie',0)
Endif
If Isnull(toArticol.pret_achizitie)
toArticol.pret_achizitie = 0
Endif
```
Deci: **pe calea principala (lista de preturi), `pret_achizitie` ramane `0`** pe liniile de proforma
— campul nici nu exista in `crsarticole` sursa (cursorul Oracle nu-l selecteaza), asa ca proprietatea
lipseste pe `poArticol` si `do_initializeaza_articol` o creeaza cu valoare implicita `0`, exact
acelasi tipar defensiv folosit pentru `id_gestiune=-1000` (aceeasi metoda, linii apropiate,
`:13618-13623` vs `:13715-13719`).
**Pe sursele care carata un document existent** (`cursor_avize`, si `cursor_retur_document` la
copiere), `pret_achizitie` **e** populat real din `VANZARI_DETALII.PRET_ACHIZITIE` a documentului
sursa, deci proprietatea exista pe `poArticol` si `do_initializeaza_articol` nu o suprascrie.
### Unde ajunge
Indiferent de valoare (`0` sau reala), `poArt.pret_achizitie` merge neschimbat catre Oracle ca
parametru pozitional in apelul catre `adauga_articol_factura`:
```
ofacturare.vc2:14073
... + Alltrim(Str(poArt.pret_achizitie,18,gnPPret)) + [,] + ...
```
primit ca `V_PRET_ACHIZITIE_TEMP` (`PACK:4995`), copiat direct `V_PRET_ACHIZITIE := V_PRET_ACHIZITIE_TEMP`
(`PACK:5031`, fara sentinela, spre deosebire de `id_gestiune`) si scris ca atare in
`VANZARI_DETALII_TEMP.PRET_ACHIZITIE` (`PACK:5229/5259`), apoi in `VANZARI_DETALII` la
`scrie_in_vanzari`.
**Concluzie Intrebarea 3**: pe calea principala (lista de preturi), `pret_achizitie` ramane `0` pe
liniile de proforma — nu vine din `do_alege_stoc` (care nu ruleaza) si nici din cursorul Oracle (care
nu-l selecteaza pe aceasta ramura). Pe sursele avize/copiere, valoarea reala din documentul sursa se
pastreaza.
---
## Intrebarea 4 — ce s-ar strica daca proforma ar pastra gestiunea reala pana la salvare?
### 4a. `adauga_articol_factura` se cheama si pentru liniile de proforma?
**Da, neconditionat.** `do_scrie_factura` (`ofacturare.vc2:14197+`) cheama
`Thisform.do_scrie_articole()` la linia **14264**, **inainte** de `Do Case poDate.eProforma = 1`
(care alege intre `scrie_proforma` si `scrie_factura2`, la `:14282`):
```
ofacturare.vc2:14264
llReturn = Thisform.do_scrie_articole() && se deschide tranzactie manuala
...
ofacturare.vc2:14282
Do Case
Case poDate.eProforma = 1
* scrie_proforma are aceiasi parametri ca scrie_factura2
* salveaza doar in vanzari, nu si in contabilitate
lcSql = [{call pack_facturare.scrie_proforma(...)}]
```
`do_scrie_articole` parcurge liniile din `crsfactura` si cheama `adauga_articol_factura` pentru
fiecare (`ofacturare.vc2:14069-14073`, deja citat in raportul-sursa) — **acelasi cod, indiferent de
`eProforma`**. Daca `poArt.id_gestiune` ar fi real (nu `-1000`), `adauga_articol_factura` l-ar
traduce direct in `V_ID_GESTIUNE2 := V_ID_GESTIUNE` (`PACK:5032-5034`, gate-ul `<> -1000`) si l-ar
scrie ca atare in `VANZARI_DETALII_TEMP.ID_GESTIUNE` (`PACK:5237,5267`), apoi in
`VANZARI_DETALII.ID_GESTIUNE` la `scrie_in_vanzari` (`INSERT INTO VANZARI_DETALII SELECT FROM
VANZARI_DETALII_TEMP`, fara nicio conditie suplimentara pe `id_gestiune`).
### 4b. Are efect ca linia sa ramana cu `ID_GESTIUNE` completat, cata vreme `scrie_proforma` nu cheama `contabilizeaza_articol`?
**Niciunul, pe partea de contabilizare/descarcare.** Confirmat deja (runda 12,
`s5b_proiectare_proforma_copiere.md`, sectiunea "Descoperire centrala"): `scrie_proforma`
(`PACK:5637-5671`) cheama **doar** `scrie_in_vanzari` — insereaza antetul si liniile ca atare, apoi
seteaza `VANZARI.EPROFORMA=1`. **Nu cheama** `contabilizeaza_articol` in niciun caz. Sentinela
`-1000` conteaza doar **in interiorul** lui `contabilizeaza_articol` (`PACK:7472-7476`), care nu
ruleaza deloc pentru `scrie_proforma`. Deci `ID_GESTIUNE` real pe o linie de proforma nu ar declansa
`descarca_gestiune`, nu ar genera `NOTE_CONTABILE`, nu ar atinge `RUL`/`STOC` — pentru ca intreaga
functie care ar face asta nu se executa pe traseul `scrie_proforma`.
### 4c. Rapoarte/view-uri care citesc `ID_GESTIUNE` pe documente `eProforma=1` si ar arata altceva?
Cautare `eproforma` (case-insensitive) in tot `PACK_FACTURARE`: doar 3 potriviri, toate in
`scrie_proforma`/comentarii (`PACK:1408, 5666, 5668`) — nicio alta procedura/vedere din pachet nu
filtreaza sau calculeaza dupa `EPROFORMA`.
Pe partea VFP, `crsDetaliiListare` (folosit la **relistarea** unui document deja emis, inclusiv
proforma — `oproceduri_facturare.prg:1111-1126`, sursa `fact_vfacturi2`) **include** coloana
`id_gestiune` in schema si in `SELECT`, indiferent de `eproforma` (nu exista filtru pe
`eproforma` in acest `SELECT`). Deci daca `ID_GESTIUNE` ar fi real pe o proforma, acest cursor l-ar
citi ca atare la relistare — azi citeste `NULL`. **Nu am putut confirma daca vreun raport `.frx`
tiparit efectiv afiseaza `id_gestiune`/`nume_gestiune` pe documentul proforma insusi**
(rapoartele `PROFORMA`/`PROFORMA_VAL` nu au versiune text `.fr2` generata in `Rapoarte\`, deci nu
s-au putut grep-ui direct) — de regula gestiunea e un camp intern, nu unul tiparit pe un document
comercial, dar afirmatia ramane neconfirmata pe cod, nu doar presupusa corecta. Vezi "Ce nu s-a
putut stabili".
### 4d. `do_alege_stoc` rezerva stoc sau doar alege?
**Doar alege — nu scrie nimic in Oracle.** Corpul complet al `do_alege_stoc`
(`ofacturare.vc2:13200-13400+`) face exclusiv: (1) un `SELECT` prin
`cursor_gestiuni_articol`/`cursor_gestiuni_articol_retur`/`cursor_gestiuni_articol_stoc0` (toate
proceduri read-only, populeaza un cursor local `crsgestarticoltemp`), (2) o scadere **locala, in
memorie**, a cantitatilor deja adaugate pe **acelasi document, in aceeasi sesiune**
(`ofacturare.vc2:13273-13311`: `SELECT ... FROM crsfactura ... INTO CURSOR crsFacturaArticoleTemp`,
apoi `a.cantitate - Nvl(b.cantitate,0)` la afisare), ca sa nu i se ofere operatorului sa aleaga de
doua ori acelasi lot pe acelasi document. **Nu exista niciun `INSERT`/`UPDATE` catre Oracle in
aceasta metoda** — `goExecutor.oExecute` apare o singura data, pentru `SELECT`-ul de la pasul (1).
Blocarea/decrementarea reala a stocului se intampla abia la `descarca_gestiune`
(`PACK:7648-7797`), apelata doar din `contabilizeaza_articol`, care (per 4b) nu ruleaza niciodata
pentru `scrie_proforma`. **Deci chiar daca `do_alege_stoc` ar rula pe liniile unei proforme, nu ar
"tine" stoc blocat pentru nimeni** — nu exista mecanism de rezervare reala in aplicatie pe acest
traseu, doar o verificare locala de neduplicare in sesiunea curenta.
**Concluzie Intrebarea 4**: nimic din contabilizare, descarcare de gestiune sau stoc real nu s-ar
strica daca proforma ar pastra `id_gestiune` real pana la salvare — blocajul functional e la nivelul
routing-ului `scrie_proforma` (care nu cheama `contabilizeaza_articol`), nu la nivelul sentinelei
`-1000`. Singurul loc unde s-ar vedea o diferenta reala e la **relistarea** documentului
(`crsDetaliiListare`/`fact_vfacturi2`), unde `id_gestiune` ar aparea completat in loc de `NULL` —
efect cosmetic/informational, nu unul care sa afecteze stocul sau contabilitatea, cu rezerva ca nu
s-a confirmat daca vreun `.frx` chiar il tipareste. Riscul real ramas e cel deja identificat in
`s5b_proiectare_proforma_copiere.md` §5 (drumul invers, proforma comutata inapoi in factura in
aceeasi sesiune, inainte de Termina) — acela nu se schimba, indiferent de aceasta decizie, pentru ca
depinde de routing-ul de la Termina, nu de momentul in care se alege gestiunea.
---
## Ce nu s-a putut stabili
1. **Daca vreun raport `.frx` tiparit (PROFORMA/PROFORMA_VAL) afiseaza efectiv `id_gestiune` sau
`nume_gestiune` pe documentul insusi** — rapoartele nu au versiune text `.fr2` generata in
`Rapoarte\`, asa ca nu s-a putut grep pe continutul lor; ar necesita fie generarea textului cu
`git_sync.ps1`/`foxbin2prg` (in afara mandatului read-only), fie deschidere in IDE VFP.
2. **Nu s-a rulat nimic pe date vii** — toata analiza e trasare de cod static (VFP text + PL/SQL
text), conform mandatului read-only.
3. **Cautarea "eproforma" in Oracle** a acoperit doar `PACK_FACTURARE` — nu s-au verificat alte
pachete/view-uri/rapoarte Oracle care ar putea referi `VANZARI.EPROFORMA` impreuna cu
`ID_GESTIUNE` (de exemplu rapoarte de gestiune/stoc din alte module ROA). Zero rezultate in
`PACK_FACTURARE` nu e dovada de completitudine pentru intreaga schema.
4. **`cursor_retur`** (`PACK:3934-3948`, folosit pentru `tnTip` 8,9,24 — retururi) nu a fost citit
integral pentru coloana `GESTIONABIL` (tabelul de la Intrebarea 1 il listeaza fara valoare
confirmata) — nu are parametru `V_PROFORMA` (confirmat prin grep), deci concluzia generala
(marcarea vine doar din VFP) ramane valabila, dar valoarea exacta intoarsa de coloana nu a fost
verificata linie-cu-linie.
---
## Handoff
Cercetare incheiata intr-o singura sesiune, fara sa fie nevoie de predare de context. Toate cele 4
intrebari din brief au raspuns cu dovada `fisier:linie`, plus rezumatul executiv de mai sus. Niciun
fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe
Oracle.

View File

@@ -1,262 +0,0 @@
# Cercetare: validarea zi_curs la ofacturare.vc2:8076 si impactul asupra S4d
## Verdict (5-10 randuri)
Ascunderea selectorului de zi curs NU va lasa un document fara curs si NU va cadea la salvare,
CU CONDITIA sa se respecte precedentul deja existent in cod: `poDate.zi_curs` primeste un implicit
necondiionat (data documentului) chiar in `oDateFactura.Init`/`Reset`
(`COMUN\programe\ofacturare_comun.prg:247` si `:496`), INAINTE ca formularul sa decida ce ascunde.
Nicaieri codul nu goleste `poDate.zi_curs` cand controlul e ascuns/eliminat. Riscul real de eroare
Oracle (-20005, "Nu este setat cursul...") vine NU din camp gol, ci din faptul ca verificarea
`pack_facturare.verifica_cursuri_valute` (in `cursor_preturi`) ruleaza NECONDITIONAT de `in_valuta`
al documentului curent si exclude doar moneda nationala - deci un `zi_curs` implicit (azi) care nu
are curs setat in tabela CURS pentru o valuta folosita in listele de preturi ale utilizatorului
poate pica oricum, INDIFERENT daca selectorul e vizibil sau nu. Linia :8076 NU apartine formularului
de factura, ci unui formular separat, restrans, pentru AVIZ PE LUCRARE / AVIZ PE NIR
(`frm_date_aviz_lucrare`, tipuri 27 si 30) - fara control de valuta pe el - si valideaza `zi_curs`
strict pentru ca tipul 27 are nevoie de curs pentru articolele din comanda (posibil in valuta),
INDEPENDENT de `poDate.in_valuta`. Precedentul cerut la punctul 6 exista deja, dar in alt formular:
`frm_date_factura`, tipurile 8/9 (retur), unde `clb_zi_curs` e eliminat NECONDITIONAT si documentul
se salveaza corect - motivul principal e ca SQL-ul pentru retur (`cursor_retur`) nici nu foloseste
`poDate.zi_curs`.
## 1. Ce valideaza linia :8076
Index de simboluri: `frm_date_aviz_lucrare.inainte_de_do_termin` = `ofacturare.vc2:8054-8119`
(fisier real: `COMUN\clase\ofacturare.vc2`).
Blocul complet (validare secventiala pe formular, fiecare `Case` opreste salvarea la primul fail):
```
COMUN\clase\ofacturare.vc2:8063-8079
Do Case
Case Empty(poDate.dataireg)
amessagebox("Nu ati completat data inregistrarii!",48,"Atentie")
...
Case Empty(poDate.dataact)
amessagebox("Nu ati completat data documentului!",48,"Atentie")
...
Case Empty(Nvl(poDate.id_fdoc,0))
amessagebox("Nu ati ales felul documentului!",48,"Atentie")
...
Case Empty(Nvl(poDate.zi_curs,{}))
amessagebox("Nu ati completat ziua cursului valutar!",48,"Atentie")
This.clb_zi_curs.SetFocus()
plReturn = .F.
Case Empty(poDate.nract)
...
```
Valideaza STRICT prezenta unei date in `poDate.zi_curs` (`Empty(Nvl(...,{}))`), nu existenta unui
curs in baza pentru acea data - acel test se face abia in Oracle, la momentul in care se cere
cursorul de articole (vezi punctul 4).
## 2. Cand ruleaza si pe ce tipuri de document
`inainte_de_do_termin` e apelat la evenimentul butonului "Termina" al formularului (`BUT_TERMIN1`,
vezi lista de obiecte a clasei, `ofacturare.vc2:7674-7679`) - deci la incercarea de a incheia
completarea datelor de antet, inainte de a trece la ecranul de articole.
`frm_date_aviz_lucrare` NU e formularul de factura. E instantiat DOAR pentru doua tipuri de
document, ambele AVIZ (nu FACTURA):
```
COMUN\programe\ofacturare.prg:187-226 (identic in factureaza2, :697-699)
Do Case
Case tnTip = 27
poDate.nIdTipDoc = 6 && AVIZ
Case tnTip = 30
poDate.nIdTipDoc = 6 && AVIZ
Case tnTip < 21 Or Inlist(tnTip, 45, 48, 49, 51, 52)
poDate.nIdTipDoc = 5 && FACTURA
...
Do Case
Case tnTip = 27
lcObiect = [frm_date_aviz_lucrare]
Case tnTip = 30
lcObiect = [frm_date_aviz_lucrare]
Case tnTip < 21 Or Inlist(tnTip, 45, 48, 49, 51, 52)
lcObiect = [frm_date_factura]
Otherwise
lcObiect = [frm_date_aviz]
Endcase
```
Titlul formularului confirma: `Lb_titlu_alb_b121.Caption = "AVIZ PE BAZ­ DE LUCRARE"`
(`ofacturare.vc2:7670`), schimbat in `Init` la "AVIZ PE BAZ­ DE NIR" cand `nid_tip = 30`
(`ofacturare.vc2:8154-8161`).
Nu exista o garda mai sus in lant care sa dezactiveze validarea :8076 pe vreun tip - ruleaza
identic pentru tip 27 si tip 30, necondiionat de `poDate.in_valuta`. IMPORTANT: aceasta clasa
NU are deloc control de valuta pe formular - lista de obiecte a clasei (`ofacturare.vc2:7623-7637`)
nu contine niciun `ct_clb_valuta`. Deci validarea de aici nu e legata de "documentul e in valuta",
ci de nevoia formularului AVIZ-LUCRARE de a avea o zi de curs pentru articolele comenzii care pot
fi preturite in valuta (vezi punctul 4, `cursor_lucrare`).
Concluzie: linia :8076 NU intra deloc in fluxul de facturare (FACTURA) vizat de decizia 15 - e un
formular separat, pentru un subset ingust de avize (27 = aviz pe lucrare, 30 = aviz pe NIR).
## 3. Ce se intampla daca zi_curs e gol la :8076
Mesaj de eroare blocant (nu doar avertisment): `amessagebox("Nu ati completat ziua cursului
valutar!",48,"Atentie")`, apoi `This.clb_zi_curs.SetFocus()` si `plReturn = .F.` - `Case`-ul
opreste executia `Do Case` (Otherwise nu se mai atinge), iar `inainte_de_do_termin` returneaza
`.F.`, ceea ce (conform conventiei din restul clasei) blocheaza inchiderea formularului / trecerea
la pasul urmator.
## 4. Cine mai citeste poDate.zi_curs
Cautare `zi_curs` in `.vc2`/`.prg`/`.sc2` si in sursele Oracle
(`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`):
**a) Validari de formular (camp obligatoriu):**
- `frm_date_aviz_lucrare.inainte_de_do_termin` :8076 - necondiionat (vezi punctele 1-2).
- `frm_date_factura.inainte_de_do_termin`, `ofacturare.vc2:9484`:
```
Case poDate.in_valuta = 1 And Empty(Nvl(poDate.zi_curs,{})) And Type('thisform.clb_zi_curs.visible')<>'U'
```
Aici validarea E DEJA dublu conditionata: pe `in_valuta = 1` SI pe existenta controlului
(`Type(...)<>'U'` - devine 'U' daca controlul a fost eliminat cu `RemoveObject`). Deci pe factura
in lei, sau pe orice tip unde controlul a fost eliminat, validarea nu ruleaza deloc. Comentariul
`*!* modificare v 2.0.56` de langa arata ca exact acest lucru a fost REZOLVAT anterior pentru
formularul de factura.
**b) Populare implicita / sincronizare (fara conditie de in_valuta):**
- `oDateFactura.Init`, `COMUN\programe\ofacturare_comun.prg:247`: `.zi_curs = ldData` -
necondiionat, seteaza mereu data documentului curent (ldData = azi, ajustat la luna/anul
curent de facturare) INAINTE de blocul care seteaza `.in_valuta` (linia 248-250).
- `oDateFactura.Reset`, `ofacturare_comun.prg:496`: `.zi_curs = .Data` - la fel, necondiionat.
- `frm_date_aviz.Clb_dataact.Text_simplu1.LostFocus` (:7603-7604) si
`frm_date_aviz.Clb_dataireg...LostFocus` (:7610-7611): `poDate.zi_curs = poDate.dataact`,
necondiionat (formularul aviz general nu are guard, dar si nu are RemoveObject pe zi_curs).
- `frm_date_aviz_lucrare.Clb_dataact...LostFocus` (:8186-8187) si
`...Clb_dataireg...LostFocus` (:8193-8194): idem, necondiionat - zi_curs NU e niciodata
eliminat in aceasta clasa, deci sincronizarea merge mereu.
- `frm_date_factura.Clb_dataact...LostFocus` (:9805-9808) si `...Clb_dataireg...` (:9824-9827):
ACESTEA SUNT deja conditionate: `If Type('thisform.clb_zi_curs.visible')<>'U' ... zi_curs =
dataact ... Endif` (comentariu `*!* modificare v 2.0.56`). Cand controlul e eliminat, sincronizarea
se opreste - dar valoarea RAMASA de la Init/Reset nu se sterge, ramane cea de la creare.
**c) Consum efectiv in SQL (trimis catre Oracle, cursoare de articole):**
`COMUN\programe\ofacturare.prg:266-308` (identic in `factureaza2`, :751-816) - alegerea SQL-ului de
populare a articolelor se face pe `tnTip`, NU pe `in_valuta`:
```
Case Inlist(tnTip, 48, 49) -> cursor_articole_k(?poDate.zi_curs, ...)
Case tnTip = 45 -> cursor_preturi(?poDate.zi_curs, ...)
Case Inlist(tnTip, 1,22,5,29,7,10,23) -> cursor_preturi(?poDate.zi_curs, ...)
Case Inlist(tnTip, 2,26,6,52) -> cursor_contract(?poDate.zi_curs, ...)
Case Inlist(tnTip, 3,21,25,28,42,47) -> cursor_comanda(?poDate.zi_curs, ...)
Case tnTip = 4 -> cursor_avize(...) [FARA zi_curs]
Case Inlist(tnTip, 41) -> cursor_gestiune(?poDate.zi_curs, ...)
Case tnTip = 30 -> cursor_aviz_nir(...) [FARA zi_curs]
Case tnTip = 27 -> cursor_lucrare(?poDate.zi_curs, ...)
Case Inlist(tnTip, 8,9,24) -> cursor_retur(?poDate.in_valuta, ...) [FARA zi_curs]
```
Descoperire cheie: pentru tipurile 8, 9, 24 (retur) SI 30 (aviz pe NIR), SQL-ul NU trimite deloc
`poDate.zi_curs` catre Oracle - `zi_curs` gol sau completat nu are niciun efect pentru aceste
tipuri. Acesta e motivul real pentru care eliminarea controlului la tip 8/9 e sigura (mai puternic
decat simpla existenta a unei valori implicite).
Pentru tipurile care TRIMIT zi_curs, comportamentul in Oracle e diferit:
- `cursor_preturi` (`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2138+`) apeleaza NECONDITIONAT
`pack_facturare.verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)` (linia 2153). Aceasta procedura
(`:16247-16274`) verifica cursul pentru TOATE valutele distincte din `FACT_VPRETURI_UTILIZATOR`
ale utilizatorului curent (nu doar valuta documentului!) si arunca
`RAISE_APPLICATION_ERROR(-20005, 'Nu este setat cursul din data de ... !')` daca oricare dintre
ele nu are curs care sa acopere `V_DATA_CURS` - EXCLUDE explicit moneda nationala
(`AND A.ID_VALUTA <> pack_facturare.nid_moneda_nationala`). Deci: chiar pe un document in LEI
(`in_valuta=0`), daca utilizatorul are liste de preturi in valuta configurate si data trimisa
(implicita sau nu) nu are curs setat, apelul PICA cu -20005 - INDIFERENT de vizibilitatea
selectorului pe formular. Riscul nu vine din camp gol, ci din "camp cu o data pentru care nu
exista curs in tabela CURS".
- `cursor_articole_k` (`:3595-3701`) si `cursor_lucrare` (`:3173-3593`, foloseste `V_DATA_CURS`
pentru comenzi_elemente) - `cursor_articole_k` NU apeleaza `verifica_cursuri_valute`, doar face
`LEFT JOIN CURS ... WHERE DATA <= V_DATA_CURS AND DATA2 >= V_DATA_CURS` - daca nu gaseste, cade
silentios pe `NVL(D.CURS,0)` (pret gresit, nu eroare). `cursor_lucrare` (:3186-3218) INSA face
o verificare proprie, similara: daca articolele comenzii au valute fara curs pe `V_DATA_CURS`,
arunca acelasi `-20005`.
- Exista deja o rutina de recuperare la acest cod de eroare: `ofacturare.prg:313-317`
```
If lnSucces < 0
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
If goExecutor.nEroare = 20005
vizualizeaza_curs(poDate.zi_curs)
ENDIF
```
Deci sistemul ANTICIPEAZA deja cazul "curs lipsa la data respectiva" si deschide un ecran de
gestiune a cursurilor - independent de validarea din formularul de date.
**d) Afisare / etichetare (fara risc):**
- `frm_facturare_articole.Init` (:15097-15098) si `frm_facturare_articole2.Init` (:19004-19005):
`If !Empty(Nvl(poDate.zi_curs,{})) Then Thisform.lb_cursuri.Caption = "Curs valutar (" +
Dtoc(poDate.zi_curs) + ")"` - deja tolereaza gol (nu afiseaza nimic), fara eroare.
- `frm_date_factura.do_cauta_valuta` (:9344-9359) - dupa alegerea valutei, muta focusul pe
`clb_zi_curs` DACA exista (`Type(...)<>'U'`), altfel pe `clb_serie_act`. Deja conditionat.
- `onom_curs.vc2` (`ck_zi_curs`, `tx_zi_curs`) - ecran DIFERIT, de administrare a cursurilor
valutare in sine (nu are legatura cu `poDate.zi_curs`; e o cautare "dupa ziua cursului" generica).
## 5. Valoarea implicita azi si de unde vine
Vine din `oDateFactura.Init`/`Reset`, necondiionat de tip sau de `in_valuta`:
- `Init` (`ofacturare_comun.prg:235-247`): `ldData = Ttod(get_ora())`, ajustat la luna/anul curent
de facturare (`gnAn`/`gnLuna`) daca `get_ora()` cade in alta luna; apoi `.zi_curs = ldData`.
- `Reset` (`ofacturare_comun.prg:486-496`): `.zi_curs = .Data` (unde `.Data` a fost deja setat tot
din `ldData`-ul curent).
Deci implicit `zi_curs` = data curenta (get_ora, ajustata la perioada de facturare deschisa), NU
`Date()` brut si nu neaparat `dataact`/`dataireg` (desi acestea pornesc de la aceeasi `ldData`).
Ulterior, cat timp controlul `clb_zi_curs` exista pe formular, orice editare a `dataact`/`dataireg`
resincronizeaza `zi_curs = dataact` prin evenimentele `LostFocus` (vezi punctul 4b). Daca formularul
ar ascunde controlul FARA sa elimine obiectul si fara sa goleasca proprietatea, campul ar ramane
la valoarea implicita de la Init/Reset (sau la ultima valoare sincronizata inainte de ascundere).
## 6. Precedent: tip cu campul ascuns care se salveaza corect
DA, exista deja, dar in `frm_date_factura` (formularul de FACTURA), nu in `frm_date_aviz_lucrare`:
```
COMUN\clase\ofacturare.vc2:9717-9722 [frm_date_factura.Init]
*!* modificare v 2.0.56
If Inlist(poDate.tip, 8, 9)
lnHeight = lnHeight - .clb_zi_curs.Height
laPozitii(.clb_zi_curs.TabIndex, 2) = 1
.RemoveObject('clb_zi_curs')
Endif
*!* modificare v 2.0.56 ^
```
Pentru tip 8 si 9 (facturi de retur - care pot fi chiar in valuta, vezi `ofacturare_comun.prg:248`:
`INLIST(m.tnTip, 5,6,7,9,10,52) -> .in_valuta = 1`), controlul `clb_zi_curs` e eliminat COMPLET de
pe formular, necondiionat de `in_valuta`, si documentul se salveaza corect. Motivele, in ordine de
robustete:
1. Validarea din `inainte_de_do_termin` (:9484) e deja garda cu `Type(...)<>'U'`, deci se
auto-dezactiveaza cand controlul nu mai exista.
2. SQL-ul de populare articole pentru tip 8/9 e `cursor_retur(?poDate.in_valuta,...)`
(`ofacturare.prg:306-307`) - NU trimite deloc `poDate.zi_curs`, deci nu poate cauza -20005 din
cauza acestui camp.
3. Chiar daca ar fi trimis, `poDate.zi_curs` tot ar avea valoarea implicita de la Init/Reset
(punctul 5) - nimic nu-l goleste la `RemoveObject`.
Aceasta e "reteta" cerinta de punctul 6: eliminarea vizuala e sigura pentru ca (a) validarea are
deja garda pe existenta controlului, si (b) proprietatea `poDate.zi_curs` nu e niciodata golita -
ramane pe implicitul din Init/Reset.
Pentru `frm_date_aviz_lucrare` (linia :8076) NU exista un tip cu campul ascuns - `clb_zi_curs` nu e
eliminat pentru nici tip 27, nici tip 30. Motivul plauzibil: tip 27 (aviz pe lucrare) chiar
foloseste `zi_curs` in `cursor_lucrare` pentru articolele comenzii (posibil in valuta), independent
de `poDate.in_valuta` al documentului-aviz insusi - deci acolo campul NU e un candidat sigur pentru
ascundere pe baza lui `in_valuta`. Pentru tip 30, SQL-ul (`cursor_aviz_nir`) nu foloseste `zi_curs`
deloc, deci validarea de acolo e superflua dar inofensiva (campul e mereu populat implicit).
## Ramas de verificat
- Nu am gasit inca daca decizia 15 / S4d intentioneaza sa includa si `frm_date_aviz_lucrare` in
formularul unificat, sau doar `frm_date_factura`/`frm_date_aviz`. Din cod, `frm_date_aviz_lucrare`
e un formular de sine statator, fara control de valuta, folosit doar pentru tnTip 27 si 30 - daca
planul S4d nu-l tinteste explicit, linia :8076 e in afara scopului imediat.
- Nu am verificat ce se intampla in `cursor_articole_k` (tip 48/49) si `cursor_gestiune` (tip 41)
fata de `verifica_cursuri_valute` - din citire, `cursor_articole_k` nu apeleaza acea procedura
(cade silentios pe curs 0), dar nu am verificat `cursor_gestiune`.
- Nu am verificat cum decide `frm_date_factura.Init` ce alte tipuri (in afara de 8,9) ar putea fi
candidate pentru ascunderea lui `clb_zi_curs` conform deciziei 15 - doar am confirmat mecanismul
existent si conditia dubla deja implementata la validare (in_valuta + Type<>'U').

12
docs/erori_deschise.md Normal file
View File

@@ -0,0 +1,12 @@
# Erori deschise — formular facturare unificat
Erori observate la rulare, neinvestigate inca. Se sterge intrarea cand e reparata.
## 1. `Alias 'CRSGESTIUNE' is not found.` la parasirea combo-ului de gestiune
- **Unde:** `FRM_FACTURARE_ARTICOLE2.GRD_FACTURA.CGESTIUNE.CCBOGESTIUNE.LOSTFOCUS`, linia 5
- **Linia:** `REPLACE nume_gestiune WITH crsGestiune.nume_gestiune, id_gestiune WITH crsGestiune.id_gestiune IN crsFactura`
- **Mediu:** MARIUSM_AUTO, 09.09.2026
- **De verificat:** cine creeaza `crsGestiune` si pe ce cale de cod ajunge `LostFocus` sa ruleze
inainte (sau dupa inchiderea) cursorului — probabil combo-ul isi pierde focusul intr-un context
in care cursorul nu a fost creat sau a fost deja inchis.

View File

@@ -1,798 +0,0 @@
# Handoff — #13 formular unificat de facturare + editare prin regenerare
Sesiune: 11.08.2026, **runda 17** (istoricul de mai jos e cumulativ; blocul rundei 14 e pastrat ca
atare). Runda 13 predase dupa cinci proiectari si deciziile 44-50.
> ## RUNDA 17 — „Urmatorul bloc de lucru" S-A GOLIT. Proiectarea lui #13 e INCHEIATA.
>
> Runda 17 a executat **exact cele doua sarcini** care mai ramasesera (punctele 4 si 5 din lista de
> jos) si **nu a deschis niciuna noua**.
>
> > ### DECIZIA 66 (runda 17) RASTOARNA DECIZIA 64 — citeste asta inainte de orice paragraf despre asezare
> > **Motivul discountului sta LANGA DISCOUNT, in banda de totaluri, nu ca a treia sectiune jos.**
> > Formularea lui Marius: *„motiv discount vreau sa fie langa discount, nu a treia coloana"*.
> > **Randul de jos revine la DOUA sectiuni** (incasare, alte date), fiecare pe jumatate de latime —
> > adica exact decizia 57, punctul 2, neatinsa. **Argumentul „~440 px fiecare, strans" cade odata cu
> > decizia 64.** Mai jos, in istoricul cumulativ al rundei 16, decizia 64 apare inca in vigoare in
> > cateva paragrafe: **sunt istorie, nu stare curenta.** Locurile decisive sunt corectate; daca
> > gasesti unul necorectat, **decizia 66 castiga**. Enuntul complet, cu regula de activare adaugata de
> > proiectare (campul e activ **doar cand discountul nu e zero**): **in plan**, la „Decizia 66".
>
> **O singura decizie noua, 66**, deci numerotarea se opreste la **66**.
> ### AUDIT DE INCHIDERE (18.08.2026) — planul e curatat de marcajele „deschis" ramase in urma
> Verificare ceruta de Marius inainte de a inchide sesiunea: **e planul finalizat?** Raspuns: **da ca
> proiectare**, dar avea **noua intrebari fantoma** — blocuri „De decis de Marius" si „ramane deschis"
> care supravietuisera deciziilor care le inchisesera. Un agent proaspat le-ar fi citit ca sarcini si
> ar fi redeschis lucruri transate. **Toate au fost stinse**, fiecare cu decizia care o inchide, in
> **8 linii** din plan (verificat prin diff fata de o copie de siguranta: zero modificari colaterale):
> - trei blocuri **„De decis de Marius"** (S5c, S8b, S11) → inchise de deciziile **51**, **52**, **53**
> si **56**; textul ramane ca **inventar al recomandarilor acceptate**, nu ca intrebari;
> - doua afirmatii „ramane deschisa asezarea campului de motiv" → inchise de **decizia 66**;
> - „din intrebarea 7 ramane deschisa partea de tipuri 48/49" → inchisa de **decizia 60**.
>
> **Ce a ramas deschis, si e corect asa — trei puncte, toate de implementare, niciunul blocant:**
> 1. **Relistarea unei facturi vechi deja trimise** (decizia 62, rest semnalat): dupa decizia 59 ar
> produce N randuri de discount in loc de unul, deci hartie diferita de originalul din eFactura.
> Relistarea nu e modificare, deci `EsteInEFactura` n-o acopera. **Nedecis, semnalat lui Marius.**
> 2. **`PROC_TVAV` ca parametru** (consecinta 3 a deciziei 54): doar daca se cere reproducere exacta
> peste o modificare legala de cota. **De decis separat.**
> 3. **De unde se reconstituie `IN_STOC`** (cerinta rundei 17): preconditie de proiectare a lui S8,
> trei variante scrise, niciuna aleasa.
> **Cod: neatins** — nici un `.vc2` / `.sc2` / `.prg` deschis macar. **Zero Oracle** in aceasta runda,
> nici macar `SELECT`. **Niciun commit dat de aceasta sesiune.**
>
> **Punctul 4 — golul `IN_STOC` din S8 — INCHIS**, prins acolo unde cerea handoff-ul precedent (in nota
> de executie a lui S8, nu la S12). Trei editari in plan, toate verificate pe disc dupa ce sesiunea de
> la #6 a rescris `docs\` in paralel:
> - `plan_13:3520-3546` — caseta „CERINTA DE EXECUTIE ADAUGATA IN RUNDA 17";
> - `plan_13:3567-3569` — criteriul intra in „gata cand" al lui S8;
> - `plan_13` la S10, consecinta 1 — marcata **PRELUAT**, ca sa nu se redescopere.
>
> **Punctul dur, si e mai greu decat suna cerinta:** valoarea istorica a lui `IN_STOC` **nu e stocata
> nicaieri** — nu e coloana pe `VANZARI_DETALII` (verificat pe DB inca de la S10), traieste doar in
> temp. Deci „S8 incarca `IN_STOC` din document" **nu e implementabil ca atare azi**. De unde se
> reconstituie e o **preconditie de proiectare a lui S8** — trei variante scrise in plan (din urma
> lasata in rulaje/gestiune; nomenclatorul curent ca aproximatie **declarata**; coloana noua, deci
> migrare DB inainte de EXE). **Nu s-a ales niciuna** si nu se presupune niciuna.
>
> **Punctul 5 — mockup v9 — LIVRAT.** `docs\mockup_13_formular_unificat.html`, 71 336 octeti (era
> 67 075 la v8). Asezarea e acum **varianta D** cu **randul de jos in doua** si motivul discountului in
> banda de totaluri (deciziile 57 + 66):
> antet strans pe doua randuri fara titluri de grup; panoul „Discount pe document" **disparut**; banda
> de totaluri **in interiorul panoului Articole, dupa `.gridbar`**, deci lipita de grid, cu procentul
> de discount editabil pe loc, **motivul discountului imediat dupa suma pe care o explica** (decizia
> 66) si totalul mare singur la dreapta; sub ea, **doua** sectiuni colapsabile egale
> (incasare · alte date), cu **rezumatul continutului pe randul inchis**.
> Legenda are acum 5 callout-uri (2 si 4 repurpozate, 5 nou). In text au intrat deciziile
> **52, 53, 54, 59, 60, 61, 62, 63+65**. Raport: `docs\cercetare\mockup_v9_modificari.md`.
> **Verificat de sesiunea principala, nu preluat din raport:** 0 taguri nechise, 0 inchideri
> neasteptate, `<style>` unic si inchis.
>
> **Mockup-ul E REPUBLICAT** (Marius a cerut-o in aceeasi runda), **pe URL-ul existent, nu pe unul nou**:
> **https://claude.ai/code/artifact/e9d73e86-7518-4f50-a998-43c49e083142**. Cu asta decizia 33 e
> consumata — URL-ul nu mai e in urma, e la v9. **URL-ul e notat acum si in plan**, la S1; pana in runda
> 17 nu exista nicaieri in `docs\` si a trebuit scos cu `Artifact action: "list"`.
> Inainte de publicare s-a facut **WebFetch pe URL** (obligatoriu, altfel publicarea e refuzata) si s-a
> confirmat ca versiunea online era **v8**, deci v9 e superset si nu s-a suprascris nimic.
>
> **Ce ramane dupa runda 17:** *nimic de proiectat*. Raman **testele (S6, S12)** si **inchiderea
> (S13)**, si amandoua cer cod, care nu poate incepe inainte de #6 (decizia 30). Prima sarcina la
> pornire: **spargerea planului pe stories** + nota de executie a primeia (decizia 55).
Stare: **ETAPA I E PROIECTATA INTEGRAL. ETAPA II E PROIECTATA INTEGRAL — S8 a fost ultima poveste, si
e gata. Raman testele (S6, S12), diff/review (S13), si O SINGURA VERIFICARE PE COD, in curs. Cod:
neatins.** Runda 14 a livrat **doua rapoarte** (S8 in detaliu, garda pe aviz), a luat **deciziile
51-54**, si a **rasturnat doua premise**: una despre garda existenta (era inteleasa pe dos), alta
despre S10 (Marius a respins intrebarea, nu a ales dintre variante).
**Din partea rundei 14, nimic nu e intr-o stare periculoasa.** Niciun `.vc2` / `.sc2` editat, **niciun
write-back**, niciun `git_sync.ps1` / `txt2vcx.ps1` rulat, nicio tranzactie deschisa, niciun commit.
Pe Oracle **numai `SELECT`-uri** (`all_source`, `all_tab_columns`, distributia `VANZARI_CORESP.TIP`),
pe `MARIUSM_AUTO`. Singurele fisiere scrise sunt in `docs\` — planul, acest handoff, doua rapoarte noi.
**Verificarea S10 s-a terminat** (`docs\cercetare\s10_rederivare_pe_calea_reemiterii.md`, 245 randuri,
toate cele 6 intrebari) si e integrata in plan.
**Runda 15 (11.08.2026) a inchis asezarea zonei de jos: varianta D, decizia 57, aprobata de Marius.**
Cu ea, **punctul 17 din lista de deschise cade**. Deciziile 57 si 58 sunt in plan, la „Deciziile lui
Marius, runda 15"; S1 e actualizat. **Cod: tot neatins.** Singurele scrieri ale rundei: acest handoff,
plan-ul, si artifactul de asezare. **`docs\mockup_13_variante_asezare_jos.html` a fost scos din
`docs\`** la cererea lui Marius („doar online, ca sa nu mai intretii 2 variante") — daca il cauti si
nu-l gasesti, nu s-a pierdut, e la URL-ul din punctul 3.
**Runda 16 (11.08.2026) a inchis punctul 1 din „Urmatorul bloc de lucru", integral.** Cele doua
rapoarte de discount au fost **citite**, cele trei afirmatii portante ale lor **verificate la sursa**
(toate rezista), rapoartele **imbinate** intr-unul singur, rezultatul **integrat in plan** (sectiune
noua **K-bis**, plus S1, decizia 56 si decizia 57), si raspunsul **dat lui Marius**. **Marius a
raspuns in aceeasi runda: decizia 59 — repartizare proportionala pe cote.** Cu asta **punctul 18
iese si din cercetare, si din decizie** pe partea principala; cele doua fire mici (campul de motiv,
retroactivitatea la relistare) **s-au inchis si ele, tot in runda 16 — decizia 61 (camp: da) si
decizia 62 (restrictia e strict `EsteInEFactura`, fara legatura cu data)**. Runda 16 a mai luat si
decizia 60 (tipurile 48/49 editabile) si decizia 63 (cerinta noua de audit). **Cod: tot neatins.**
**Tot runda 16 a mai inchis TREI cercetari, toate cu rezultatul integrat in plan si verificat la
sursa de sesiunea principala** (nu preluat din rapoarte):
1. **Custodia (48/49)** — `custodie_48_49_stergere_reemitere.md`. Blocantul de la decizia 60 **cade**:
nu e nimic de reversat, fiindca emiterea lor nu atinge stocul. **Premisa era gresita** —
`scrie_fact_aviz_custodie` serveste `ntip = 4`, nu custodia. Ramane **cerinta** ca S4/S4g/S5 sa
pastreze restrictia `IN_STOC = 0`.
2. **Auditul** — `audit_vanzari_creare_modificare_stergere.md`, proiectat ca **S14**. Patru din sase
informatii exista deja; lipseste perechea de modificare; regenerarea ar suprascrie tacit crearea.
3. **Stocul la stergere** — `stoc_la_stergere_si_reemitere.md`. **Nu e blocant**, dar din alt motiv
decat parea: `sterge_factura` nu reverseaza rulajele, o face `PACK_CONTAFIN.STERGE_DIN_RUL` prin
`oscrie_in_fisiere`. Deci **tripleta din S9 nu se simplifica** — vezi S9 si sectiunea „Teste".
**Cu asta „ce ramane de proiectat" e din nou gol**, in afara de S14 care tocmai a fost scris. Raman
testele (S6, S12), diff/review (S13). **Cele trei intrebari mici lasate lui Marius — asezarea
campului de motiv (decizia 61) si cele doua de la S14 (audit numai pe antet sau si pe linii; se
afiseaza in interfata sau e doar pentru interogare) — au primit toate raspuns, tot in runda 16:
decizia 64 (randul se imparte in trei) si decizia 65 (audit in gridul din `frm_facturi`, deci pe
antet). Nu mai ramane nicio intrebare deschisa pentru Marius.**
> ### REZOLVAT IN RUNDA 16 — rapoartele de discount sunt citite, verificate, imbinate si integrate
> Caseta ramane ca inventar al rezultatului, **nu mai e o sarcina**. Nu relua nimic din ea.
>
> | fisier | stare |
> |---|---|
> | `docs\cercetare\discount_document_cota_tva.md` | **raportul unic**, 44 KB — rezultatul imbinarii; aici se citeste |
> | `docs\cercetare\discount_document_cota_tva_b.md` | **ramane pe disc ca sursa** a partii adaugate (3.1-3.3, 2.3, 4.1); nu se sterge, nu se mai citeste separat |
>
> **Imbinarea E FACUTA.** Structura noua: sectiunile **3.1** (masuratoarea `IIF`/NULL), **3.2**
> (grupul orfan pe factura scutita), **3.3** (validarea offline cu control negativ), plus completari
> in 2.3, 4.1 si 5. Cele trei casete de avertizare vechi („sectiunea 3 nu e inchisa", blockquote-ul
> din capul sectiunii 4, mentiunea din „Raspunsul scurt") au fost **inlocuite cu rezultatul**, nu
> lasate alaturi de el.
>
> **Cele trei afirmatii portante au fost verificate la sursa de sesiunea principala. Toate rezista:**
> - **masuratoarea `IIF`/NULL** — sonda `probe_null.prg` citita si confruntata cu
> `xmlefactura.prg:226-248`: reproduce fidel acelasi `LEFT JOIN` si aceeasi cheie de grupare;
> iesirea reala arata **1 grup** (contopire), `IIF()` da ramura falsa, nu `.NULL.`;
> - **controlul negativ din validare exista si chiar iese cu erori** — `control_stricat.txt` are exact
> `[BR-CO-13]` + `[BR-CO-15]`, in timp ce restul au `ok`. Deci validatorul discrimineaza;
> - **identitatea SHA256** — confirmata cu `Get-FileHash`:
> `3303AFE34BC9E3E84DA9B3527C435DD9B8CB0BC7881D7D2F660A250237E23C64`, XML-ul de pe disc = cel din
> `TRIMISE`. Iar continutul din `ERORI` e un mesaj de 446 octeti pentru o incarcare **anterioara**,
> cu alt index, pe `BR-CO-15` + `BR-CL-04`.
>
> **In plus, o observatie noua a sesiunii principale, care nu exista in niciun raport** (acum in
> raport, §4.1): fisierul `...2885...` e o factura **integral scutita** cu alocare de document, si are
> **un singur `TaxSubtotal`, net** (`4410.00 − 539.82 = 3870.18`), fara grup orfan. Cum `expltva` face
> parte din cheia de grupare, contopirea **dovedeste** ca pseudo-linia purta un `id_jtva_coloana` real
> — deci era discount **pe linie**. E un **al treilea indiciu independent** ca cele 4 XML-uri de
> productie sunt discounturi pe linie, si arata **pozitiv forma corecta** pe o factura scutita — exact
> forma pe care o recomanda reparatia. Rezerva: fisierul e din 02.2024, inferenta presupune traseul de
> cod neschimbat.
>
> **Ce raporta al doilea agent, acum verificat si integrat:**
> - **Ipoteza bazei negative e GRESITA pe factura obisnuita** — masurat headless, nu dedus: `IIF()` din
> VFP intoarce **ramura falsa, nu `.NULL.`**, cand conditia e nula. Deci pseudo-linia de discount se
> contopeste corect in grupul cotei maxime, si **sectiunile 3-4 ale raportului principal raman
> valabile**.
> - **Se confirma insa pe facturile scutite / taxare inversa / intracomunitare**, unde liniile reale au
> `scutit = 1` si `expltva` completat iar pseudo-linia are `0` si gol: apare un **`TaxSubtotal` in
> plus, cu baza negativa si categoria `Z`**, si `agettipcota(1)` devine ambiguu.
> - **Nu blocheaza factura:** validat cu **DUKIntegrator offline**, 6 scenarii, **inclusiv un control
> negativ care chiar iese cu erori** (BR-CO-13/BR-CO-15) — fara el cele cinci „ok" n-ar fi dovedit
> nimic. Trece inclusiv un caz cu `TaxableAmount = -400.00`. Regulile pe categorii (BR-S/E/Z-08) se
> satisfac trivial, deci **aritmetica inchide si niciun schematron nu prinde greseala de modelare**.
> Avertisment propriu: jar-ul local e din 2022, validatorul online curent poate fi mai strict.
> - **`currencyID="RON"` hardcodat e neconformitate reala si activa**, dovedita pe fisier: XML-ul EUR
> de la VENDING_MASTER e identic pe SHA256 cu cel din `TRIMISE` (deci exact ce a plecat la ANAF) si
> **a fost acceptat**. Respingerea din `ERORI` e a unei incarcari anterioare, pentru alte reguli.
> Deci: neconform, dar **fara dovada ca ar cauza respingere**.
> - **Nu s-a putut proba pe date reale, si o spune explicit:** in baza accesibila sunt **3** facturi cu
> discount de document (toate cu o singura cota, anterioare eFacturii) si **34** cu cote mixte fara
> discount — **intersectia e goala**. Cele 4 XML-uri de productie cu alocare de document par a fi
> discounturi **pe linie** (unul are alocarea pe cota minima, altul are doua alocari — niciuna
> obtenabila din `Max()` pe o singura pseudo-linie). **Absenta din date nu e dovada.**
> - **Completare pentru #13, importanta la implementare:** daca se merge pe repartizare proportionala,
> bucatile de discount trebuie sa primeasca **`id_jtva_coloana` al grupului pe care il reduc**, nu
> doar cota — cheia de grupare are cinci campuri si `expltva` se completeaza tot prin
> `id_jtva_coloana`. O implementare care seteaza doar `proc_tva` **lasa grupul orfan exact unde e azi**.
>
> **Igiena confirmata de agent pe disc, nu din memorie:** `svn status` gol pe `xmlefactura.prg`,
> `ofacturare_comun.prg`, `oproceduri_facturare.prg` — doar citite; **zero write-back, deci niciun
> fisier text editat fara conversie in binar**; niciun proces `vfp9.exe` / `sqlplus.exe` / `java.exe`
> viu; nicio tranzactie deschisa (numai `SELECT` cu `exit`); **datele de test neconsumate** (scenariile
> au fost XML-uri sintetice in scratchpad); **niciun apel catre ANAF** — validare offline.
>
> **SINGURUL PUNCT RAMAS NEDOVEDIT PRIN RULARE, si dupa runda 16:** reproducerea **end-to-end prin
> program** a scenariului *factura scutita / taxare inversa + discount de document*. Tot ce sustine
> concluzia de acolo e analiza statica plus XML-uri sintetice plus masuratoarea `IIF`/NULL. Nu e
> blocant pentru nimic acum — **se dovedeste la implementare**, cand exista cod de testat. Artefactele
> sunt inca pe disc si sunt refolosibile: sondele si XML-urile de scenariu in scratchpad-ul sesiunii
> `d2dbc0e9-...`, cu apelul exact al validatorului in raport, §3.3 si §4.3.
>
> **Nu relansa cei doi agenti** — si-au terminat treaba, iar rapoartele lor sunt deja imbinate. Daca
> apare o intrebare noua pe discount, porneste un agent proaspat **pe raportul unic**, nu pe `_b.md`.
>
> **Cauza coliziunii, ca sa nu se repete:** `discount-tva-efactura` raportase `idleReason: interrupted`
> avand pe disc doar scheletul (624 octeti), a fost presupus mort si relansat — **era viu**. Vezi
> „Capcane de mediu".
> ### ATENTIE — copia de lucru are modificari necomise care NU sunt ale rundei 14
> **Actualizat in runda 17, masurat, nu copiat.** Ce e necomis **din partea lui #13**: exact trei
> fisiere, toate in `docs\` — `handoff_13_formular_unificat.md`, `mockup_13_formular_unificat.html`
> (v9) si `cercetare\mockup_v9_modificari.md` (netracked). Nimic altceva. Editarile rundei 17 in
> `plan_13` si `plan_index` **apar deja comise**, prinse in commit-ul de curatenie al sesiunii de la
> #6 — vezi „Capcane de mediu", prima intrare.
>
> Restul de mai jos e al lui **#6** si/sau zgomot de compilare VFP, si e **inca acolo** (verificat cu
> `svn status` in runda 17: `changelog_roafacturare.txt`, `roafacturare.PJX` / `.PJT`, `versiune_db.txt`,
> plus in `COMUN\`: `anaf_efactura`, `comun`, `ofacturare_comun`, `omodificari`, doua ferestre de import
> si `ofacturare_editare.prg`). Lista originala, pastrata ca atare:
> ```
> M changelog_roafacturare.txt · M roafacturare.PJX / .PJT · M versiune_db.txt
> M COMUN\clase\ofacturare_comun.vcx / .vct · M COMUN\clase\omodificari.vcx / .VCT
> M COMUN\clase\anaf_efactura · M COMUN\clase\comun · M COMUN\docs\...
> ? COMUN\clase\ofacturare_comun.pre_s4butoane.bak.vcx / .vct · ? bash.exe.stackdump
> ```
> Astea sunt **ale sesiunii de la #6** (editarea facturii emise) si/sau zgomot de compilare VFP.
> **Nu le comite, nu le reverta, nu le da `svn revert` in bloc si nu rula `git stash` in `COMUN\`**
> (`COMUN` e dublu-versionat SVN+git — un `stash` acolo pierde modificari necomise din SVN).
> `bash.exe.stackdump` din radacina, din `docs\` si din `docs\cercetare\` se pot sterge.
> **`{06D747B8-0824-488C-8832-DEB9AE661974}.png` din radacina NU e gunoi** — e captura Saga pusa de
> Marius ca referinta vizuala pentru asezarea formularului (runda 14), citata in plan la S1. Nu o sterge.
## Ce a livrat runda 14
| Livrabil | Ce a stabilit |
|---|---|
| `garda_aviz_facturat.md` | **premisa era pe dos** — garda blocheaza deja avizul facturat; golul e pe **proforma** |
| `s8_incarcare_document.md` | **S8 proiectat integral** — si ambele piese din schita planului sunt gresite |
| deciziile **51-54** in plan | `TIP = 4`, `do_modifica`, atasamentele, si **rasturnarea lui S10** |
| S7 rescris in plan | garda nu se reimplementeaza — se **muta momentul** (pre-flight read-only) |
| S10 rescris in plan | nu garda, ci **eliminarea** re-derivarii; cele 3 intrebari vechi cad |
| S9 / S11 corectate | pasul de atasamente devine **stergere**, nu `UPDATE` |
## Cele patru rezultate care conteaza (nu se reiau, nu se reargumenteaza)
### 1. Garda pe aviz EXISTA deja — premisa care circula in plan era gresita
Se credea ca `sterge_factura` blocheaza documentul doar cand are **retururi** peste el. **Nu:** a doua
garda testeaza `TIP IN (1, 2)`, iar **`TIP = 1` e corespondenta aviz → factura normala, nu retur**
(`EXPORT:5464-5476`; semantica citita ramura cu ramura din `CASE`-ul lui `finalizeaza_factura`,
`EXPORT:14818-14839`). Formularea gresita venea din citirea **comentariului**
`-- verific daca exista facturi sau avize de retur`, in care „de retur" se distribuia si peste
„facturi". Codul zice altceva. **Deci decizia 50 nu cere nicio garda noua pe aviz.**
**Sunt trei garzi, nu doua, si a treia schimba enuntul deciziei 50:**
| garda | `EXPORT` | ce blocheaza |
|---|---|---|
| 1 | 5452-5462 | **factura** cu facturi de retur peste ea (`TIP = 3`) |
| 2 | 5466-5476 | **aviz** cu factura (`TIP = 1`) sau aviz de retur (`TIP = 2`) peste el |
| 3 | 5480-5494 | **factura din aviz** ale carei avize-sursa au primit intre timp aviz de retur |
**„Factura din aviz ESTE editabila" nu e neconditionat** — e editabila doar cat timp niciunul dintre
avizele ei n-a primit aviz de retur.
**Ce lipseste nu e regula, e MOMENTUL:** garda traieste in `sterge_factura`, deci se manifesta ca
`ORA-20000` **in mijlocul** stergerii din regenerare, dupa completarea formularului si dupa deschiderea
tranzactiei. S7 are nevoie de **pre-flight read-only** pe exact aceeasi conditie, **fara** reimplementarea
regulii. Se interogheaza `VANZARI_CORESP`, **nu `VANZARI.FACTURAT`** — `FACTURAT` e derivat, scris si
resetat, dar **necitit de nicio garda**.
**GOLUL REAL E PE PROFORMA, si a devenit relevant chiar acum, prin decizia 51.**
`sterge_proforma` (`EXPORT:5610-5635`) **nu are nicio garda** — corpul ei e doua `UPDATE ... SET
STERS = 1`. E procedura **complet separata**, nu cheama `sterge_factura`, deci garda existenta **nu se
extinde automat** la `TIP = 4`. O proforma din care s-a emis factura se poate sterge azi fara niciun
avertisment (calea VFP iese devreme: `ofacturare_comun.vc2:4707-4719`). **Asta e continutul concret al
punctului 4 din S5c** — nu mai e intrebare de principiu, e garda de scris intr-o procedura fara niciuna.
**Comanda si contractul nu trec prin `VANZARI_CORESP`.** Comanda: `VANZARI.ID_COMANDA` +
`inchide_comanda`, garda e **in VFP si pe comanda** (`COMUN\clase\ocomenzi.vc2:1806-1807` la modificare,
`:2065-2066` la stergere). Contract: `VANZARI.ID_CTR` + `CTR_RATE_FACTURI`, nicio garda „are urmasi".
### 2. S8: ambele piese din schita planului sunt gresite, si nu marunt
1. **`completeaza_setari_document(toFactura, .T.)` copiaza 16 proprietati din ~120**
(`COMUN\programe\ofacturare_comun.prg:361-411`) si **niciuna de identitate**. Lipsesc serie, numar,
data, scadenta, `text_aditional`, `id_facturare`, `id_ruta`, `tip_saft`, `efactura`,
`listare_detaliata`, `dataora_exp`, discountul de document, `discount_evidentiat`, `eproforma`,
valuta, cursul.
2. **`cursor_retur_document(V_COPIERE = 1)` nu umple documentul, ci selectorul-sursa.** Rezultatul intra
in `crsarticole` (gridul din stanga); `crsfactura` se creeaza **gol si ramane gol** — comentariul din
cod e explicit (`COMUN\programe\ofacturare.prg:338`, `:455-457`). Transferul se face doar prin
`do_adauga_tot` → `do_adauga_articol`, care pentru articole gestionabile trece **prin dialogul de
alegere din stoc**.
3. **Nu intoarce `ID_VANZARE_DET` si nici `TAXCODE`** (`PACK_FACTURARE:3949-4054`). Fara
`ID_VANZARE_DET`, cheia de linie presupusa de S8b **nu exista**, iar `modifica_explicatie_articol`
**devine neapelabila** — primul ei parametru *este* `V_ID_VANZARE_DET` (`PACK_FACTURARE:14511-14519`).
**Recomandarea centrala:** S8 se construieste pe precedentul care face deja exact asta —
`relisteaza_ofacturare_stoc` (`COMUN\programe\ofacturare_stoc.prg:456-742`), care reconstituie `poDate` +
cursorul de linii dintr-un document salvat, prin `FACT_VFACTURI` + `FACT_VFACTURI_DETALII` (ambele au
`ID_VANZARE_DET` si `TAXCODE`).
**Cerinta din S8b e mai blanda decat se credea:** lookup-ul „ultimul delegat / ultima masina" **are deja
garda** — `Empty(id_delegat) And Empty(id_masina)` (`ferestre_cere_date.vc2:3111`). Riscul e real
**numai** pe documentele fara delegat si fara masina. Raportul da inventarul complet al celorlalte
initializari „pentru document nou" de sarit (§2.2), si **sapte corectii** la materialele existente
(§8.3) — printre care: nu exista coloana `ZI_CURS` pe `VANZARI`; etichetele lui `Ct_clb_altele` sunt
**sase**, nu cinci (lipsea „Nr. aviz / avize", tip 4 — exact cazul relevant pentru S9).
### 3. S10 nu se decide, se rastoarna — Marius a respins intrebarea
Cele trei intrebari deschise (blocare vs. avertizare; ramura de aviz; masurarea frecventei) **sunt
inchise prin respingerea premisei lor**. Formularea lui: *„nu inteleg de ce se schimba valorile fata de
ce se incarca din factura sursa care se modifica? asta este comportamentul pe care il doresc — modific
sursa"*. **La reemitere se scriu valorile din formular.** Nu se cere utilizatorului sa confirme o
schimbare pe care n-a cerut-o.
**VERIFICAT integral** — `s10_rederivare_pe_calea_reemiterii.md`, confirmat pe fisier **si** pe DB.
Rezultatul e in plan, la S10, cu tabelul complet. Ce conteaza:
- **Ramurile care re-deriva sunt TREI, nu una — raportul rundei 9 gresea.** Contract, **aviz** si
comenzi, toate in acelasi `CASE` din `adauga_articol_factura` (`PF:5052-5220`), **inainte** de
insertul in temp. Copierea temp → `VANZARI_DETALII` e 1:1, deci nu protejeaza nimic — dauna e amonte.
- **Ramura AVIZ e cea mai agresiva din tot `CASE`-ul:** `PRET` inlocuit **neconditionat**, fara `DECODE`,
fara filtru pe pretul din formular, **fara bloc `EXCEPTION`**, si **fara `A.STERS = 0`** desi coloana
exista. Ramura comenzi, in schimb, **cade cu `ORA-01403`** — vizibila, nu tacita.
- **Comportamentul cerut de decizia 54 exista deja in cod**, pe ramura de exceptie a contractului
(`PF:5167-5185`), care pune toate cele cinci pe valorile din formular. Nu e nimic de inventat.
- **Curat VFP nu se poate, si acum e dovada pozitiva, nu absenta:** semnatura n-are comutator
(`PF:4989-5015`), globalele n-au (`PF:126-212`), poarta se decide pe `ntip` (ajunge in `VANZARI.TIP`)
si pe `CONTRACTE.OPT_FACTURARE`. `V_ID_POL = NULL` **are exact acelasi defect ca `V_ID_CTR = NULL`**,
deja respinsa — notat in plan **tocmai ca sa nu fie redescoperit ca „solutie"**.
- **Modificarea de pachet e mica:** un `WHEN` nou, primul in `CASE`, cu **acelasi corp ca `ELSE`-ul de
azi**, comandat de o variabila noua de pachet („regenerare in curs", implicit `0`). **Acopera toate
trei ramurile dintr-o data.** Punctul de resetare exista deja (`initializeaza_date_factura`,
`PF:1808-1917`). Inert prin constructie. **DB inainte de EXE.**
**Trei consecinte de acceptat explicit, nu de descoperit la S12** (detaliate in plan): `IN_STOC` ar veni
din formular — si **S8 nu-l incarca azi** (`ofacturare_editare.prg:302-303` il ia din nomenclator), deci
flag-ul singur nu ajunge; se **pierde o validare** pe ramura comenzi; `PROC_TVAV` **ramane derivat** pe
toate ramurile (nu e parametru), deci o modificare legala de cota schimba TVA-ul la reemitere chiar si
pe `ELSE`.
**Capcana NULL, dedusa nu rulata:** `CTR_ARTICOLE.PRET_UNITAR` NULL (nu `0`) → `DECODE` da **NULL**,
pretul din formular se pierde complet. In dev nu apare (27 randuri, 0 NULL) — ceea ce nu spune nimic.
**Obstacol pentru S9, gasit in treacat:** **`VVANZARI_ARTICOLE` nu expune `ID_POL` si nici `ID_CTR`** —
exact cei doi ceruti de `adauga_articol_factura`. Reemiterea **nu poate folosi view-ul ca atare**. E un
argument in plus pentru varianta (B) de la intrebarea 1 din S8.
### 4. Doi agenti ucisi de limita de sesiune — si amandoi livrasera
`s8-incarcare` (52 KB, toate cele 8 puncte, cu propria concluzie de final) si `s10-rederivare` (**zero
octeti — nu apucase sa scrie nimic**). `garda-aviz` a trecut in `idle` fara mesaj final, desi livrase
integral 13 KB. **A cincea, a sasea si a saptea oara** cand se intampla. **Livrabilul se verifica pe
disc**, nu se reia munca reflex. Instructiunea „scrie devreme si incremental" e ce a salvat 52 KB.
## Ce asteapta raspunsul lui Marius
> ### CITESTE ASTA INAINTE DE LISTA DE MAI JOS
> **Marius a raspuns „DA LA TOATE" (decizia 56) — lista lunga care urmeaza NU mai e deschisa.**
> A acceptat in bloc toate recomandarile, cu doua exceptii pe care le-a scos el explicit. Lista e
> pastrata mai jos **doar ca inventar al recomandarilor acceptate**, ca sa se stie ce s-a decis si de
> ce; fiecare punct e scris si la locul lui, in povestea careia ii apartine. **Nu se reintreaba
> niciunul.** Textul deciziei 56, cu toate punctele: in plan, la deciziile rundei 14.
>
> **Runda 16 a raspuns la tot ce mai astepta pe Marius — inclusiv ultima alegere ramasa (48/49),
> cele doua puncte mici de la decizia 59, asezarea campului de motiv (decizia 64) si cele doua
> intrebari de la S14 (decizia 65). NU MAI RAMANE NICIO INTREBARE DESCHISA PENTRU MARIUS:**
> 1. **Tipurile 48/49** (facturi de marfa in custodie) — **INCHIS, decizia 60 (runda 16): DA, sunt
> editabile prin #13.** Formularea lui: *„vreau sa fie posibila editarea si a facturilor in
> custodie"*. **Verificarea care conditiona executia s-a facut, si blocantul CADE** — regenerarea
> e sigura pe 48/49, dar din alt motiv decat se banuia: **nu e nimic de reversat**, fiindca
> emiterea lor nu atinge deloc stocul (articole `IN_STOC = 0` prin constructie). Premisa despre
> `scrie_fact_aviz_custodie` **era gresita** — vezi „Capcane de mediu". Ramane o **cerinta** pentru
> S4/S4g/S5: restrictia `IN_STOC = 0` pe 48/49 se pastreaza, altfel invariantul se rupe.
> Raport: `docs\cercetare\custodie_48_49_stergere_reemitere.md`; enunt complet in plan, decizia 60.
> *Precizare pastrata din runda 14:* partea despre `frm_facturare_articole2` era o eroare de
> raport — nu e o varianta paralela de exclus, e **prototipul pe care se construieste formularul
> unificat** (S1).
> 2. **Cota si explicatia de TVA a discountului de document** — **INCHIS INTEGRAL.** Repartizarea:
> decizia 59, runda 16 (proportional pe cote). Campul text optional de motiv: **decizia 61, runda
> 16 — DA, se adauga.** Retroactivitatea la relistare/retrimitere: **decizia 62, runda 16 —
> restrictia de modificare e strict `EsteInEFactura`, fara legatura cu nicio data.** Ramane deschis
> doar cum se aseaza campul de motiv pe rand (vezi punctul 3) si un rest separat semnalat la
> decizia 62 (relistarea unei facturi vechi deja trimise). Vezi deciziile 61-62 in plan.
> 3. **Alegerea variantei de asezare** A / B / C — **INCHISA in runda 15**, varianta D, decizia 57.
> **Redeschisa partial de decizia 61, INCHISA la loc de decizia 64 (runda 16): randul de jos se
> imparte in trei** (incasare, alte date, motivul discountului), ~440 px fiecare la 1366 px, strans.
> D ramane intr-un etaj.
> 4. **Cerinta noua de audit** (decizia 63, runda 16) — **PROIECTATA, S14**. Cercetarea s-a terminat
> (`docs\cercetare\audit_vanzari_creare_modificare_stergere.md`); cele doua puncte ramase (antet
> vs. si linii; afisare in UI vs. doar interogare) **INCHISE de decizia 65 (runda 16)**: afisarea e
> in **gridul din `frm_facturi`**, deci pe **antet** — S14 nu mai cere controale noi in formularul
> unificat, cere coloane in acel grid, si incepe cu inventarul lui (posibil sa existe deja).
**Blocanta: NICIUNA.**
**Din S8 (opt puncte, toate cu recomandare — `s8_incarcare_document.md` §8.2):**
1. **Canalul de citire a liniilor:** (A) extinderea lui `cursor_retur_document`, (B) procedura noua
`cursor_editare_document`, (C) `FACT_VFACTURI_DETALII`. *Recomandare:* **(B)** — (A) schimba
comportamentul copierii (`taxcode` ar incepe sa se propage), (C) pierde `ID_POL`, `PRETD` si
tratamentul valutar.
2. **`GESTIONABIL` la editare: din nomenclatorul de azi sau din document?** *Recomandare:* **din
document** — un document editat trebuie sa arate cum a fost emis.
3. **`text_aditional`: se normalizeaza la incarcare (`Chr(170)` → `CR+LF`)?** *Recomandare:* **da**,
altfel orice document cu text pe mai multe randuri apare modificat la deschidere. Nota: cele doua
rute de scriere salveaza **forme diferite** ale campului — defect preexistent, de semnalat separat.
4. **`zi_curs` la editare: ascuns sau afisat gol?** *Recomandare:* **ascuns** (precedent: tipurile 8/9).
Mica abatere de la decizia 5, semnalata ca atare.
5. **`poDate.lEditare`: proprietate pe `oDateFactura` sau parametru?** *Recomandare:* **proprietate** —
sunt cel putin patru locuri care au nevoie de semnal, si `lCopiere` e deja acolo cu acelasi rol.
6. **`id_ruta` — proprietate noua pe `oDateFactura`?** *Recomandare:* **da**; fara ea S8c nu poate
implementa unul din cei 14 parametri.
7. **Tipurile 48/49 si `frm_facturare_articole2` intra in etapa II?** *Recomandare:* **nu acum**, dar se
declara explicit ca neacoperite.
8. **Defectul de prefixare `text_aditional` la `Init` pentru contracte** — se repara in #13 sau separat?
*Recomandare:* **se ocoleste** prin `lEditare` si **se semnaleaza separat**.
**Din S5c (patru puncte ramase — punctul 1, `TIP = 4`, e INCHIS prin decizia 51):**
9. **Apel pe starea de sesiune a pachetului vs. procedura noua cu parametri expliciti.** *Recomandare:*
varianta simpla (zero cod Oracle nou), **cu conditia** verificata la implementare ca niciun apel
Oracle intercalat nu reseteaza starea.
10. **Aceeasi proforma poate fi copiata de N ori.** *Recomandare:* daca deranjeaza — avertisment, **nu**
blocare.
11. **Garda simetrica la stergerea proformei** — **nu mai e intrebare de principiu**: `sterge_proforma`
n-are nicio garda, deci e **cod nou**. Vezi rezultatul 1.
12. **Afisarea „provine din proforma X"** — gratis din legatura scrisa, la nivel de document.
**Din S4g (patru puncte):**
13. **Numarul codului de eroare nou** (`FACT-0xx`), distinct de `FACT-024`.
14. **`CU_TVA = 1` hardcodat** — are efect **masurat** prin `nproc_tva_max`. **De confirmat.**
15. **Decizia 36** — numele cheii de optiune de firma pentru `SCD` (`FACT_SCD_ARTFPRET` propus).
16. **Trasabilitatea `CONT_VENIT` pe `VANZARI_DETALII`** — optionala pentru functionare.
**Deschise la sfarsitul rundei 14:**
17. ~~**Asezarea zonei de jos**~~ — **INCHIS. Marius a ales varianta D (decizia 57), runda 15.**
Enuntul complet, cele patru consecinte de executie si singurul punct lasat deschis sunt **in plan**,
la „Deciziile lui Marius, runda 15", si rezumate la S1. Pe scurt: banda de totaluri din C;
**incasarea si alte date sunt sectiuni colapsabile in formular, una langa alta pe acelasi rand**,
nu dialoguri; **nu exista bara de comenzi jos** — `but_renunt` / `but_termin` raman in banda de
titlu. **Nu se reintreaba, nu se reargumenteaza, A / B / C au cazut.**
Doua lucruri de dus mai departe la implementare, ambele deja scrise in plan: **validarea incasarii
se muta in `Termina`** (de prins in S9), si **„renunt doar la incasare" dispare** — acceptat
explicit de Marius dupa ce i s-a spus.
18. **Cota si explicatia de TVA a discountului de DOCUMENT** — **cercetarea E TERMINATA (runda 16)**,
raportul unic e `docs\cercetare\discount_document_cota_tva.md`, rezultatul e in plan la
**K-bis**. **INCHIS INTEGRAL, tot in runda 16:** repartizarea — **decizia 59**, proportional pe
cote, fara sa se ceara cota de la utilizator; campul text optional de motiv — **decizia 61**, da,
se adauga (`AllowanceChargeReason`, `ReasonCode` ramane `95`, stocare noua pe `VANZARI`, control
nou in formular); retroactivitatea la relistare/retrimitere — **decizia 62**, restrictia de
modificare e strict `EsteInEFactura`, fara legatura cu nicio data (ramane insa deschis, separat,
cazul relistarii unei facturi vechi deja trimise — vezi decizia 62 in plan). **Singurul rest
deschis: asezarea campului de motiv pe rand** (decizia 61 redeschide punctul de la decizia 57).
Ce s-a stabilit prin cercetare, integral valabil:
- **Cota discountului de document = cota MAXIMA de pe factura**, si e o regula implicita pe care
n-o alege nimeni. `Calculate Max(proc_tvav)` (`oproceduri_facturare.prg:1387`); discountul intra
ca **pseudo-linie** printr-un rand-sentinela `Replicate('Z',20)`
(`ofacturare_comun.prg:1891-1896`). In XML, `AllowanceChargeReason="Discount"` si
`ReasonCode=95` sunt **hardcodate** (`xmlefactura.prg:774-776`), la fel `currencyID="RON"`
(`:778`).
- **Regula e implementata de DOUA ori**: si in VFP (mai sus), si in PL/SQL —
`recalculeaza_totaluri_vanzari`, `MAX(ROUND(discount*(a1.proc_tvav-1),...)) as DISC_TVA_RON`
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16081-16092`), scris in `VANZARI.DISCOUNT_TVA`
(`:16214-16227`). **Pentru #13 inseamna doua locuri de schimbat, nu unul.**
- **Ipoteza „TaxSubtotal cu baza negativa" — TRANSATA, cu verdict impartit.** Se temea ca
pseudo-linia, avand `id_jtva_coloana = 0`, iese din LEFT JOIN cu `coloana_jv` NULL si isi
formeaza grup propriu. **Infirmata pe factura obisnuita** — masurat, nu dedus: `IIF()` din VFP
intoarce **ramura falsa, nu `.NULL.`**, deci pseudo-linia se contopeste corect si sectiunile
3-4 ale raportului raman valabile. **Confirmata pe facturile scutite / taxare inversa /
intracomunitare**, unde liniile reale au `scutit = 1` si `expltva` completat iar pseudo-linia
are `0` si gol: iese un `TaxSubtotal` orfan cu baza negativa si categoria `Z`, iar
`agettipcota(1)` devine ambiguu intre `E` si `Z`.
- **Corectie la o presupunere care circula in handoff-ul precedent: ANAF NU respinge forma asta.**
Validat offline, 6 scenarii, cu control negativ care chiar iese cu erori. Aritmetica inchide
(`BR-S/E/Z-08` se satisfac trivial), deci **niciun schematron nu prinde greseala de modelare**.
Ramane eroare **tacuta**, ca si defectul de atribuire fiscala. Rezerva: validatorul local e din
2022, cel online al ANAF **nu a fost apelat**.
- **`currencyID="RON"` hardcodat e neconformitate reala si activa**, dar dovedit pe fisier ca
**a fost acceptat** de ANAF (identitate SHA256 cu XML-ul din `TRIMISE`). Neconform, fara dovada
ca ar cauza respingere.
- **Nu s-a putut proba pe date reale:** 3 facturi cu discount de document (toate cu o singura
cota) si 34 cu cote mixte fara discount — **intersectia e goala**. Absenta din date nu e dovada.
- **Completare importanta la implementare:** bucatile de discount trebuie sa primeasca
**`id_jtva_coloana` al grupului pe care il reduc**, nu doar cota — o implementare care seteaza
doar `proc_tva` **lasa grupul orfan exact unde e azi**. **eFactura nu cere nicio modificare** —
`xmlefactura.prg:758-792` grupeaza deja pe cota.
19. Cele **trei consecinte** ale deciziei 54 (`IN_STOC`, validarea pierduta pe comenzi, `PROC_TVAV`
derivat) — ultima poate cere decizie separata.
**Si defectul `lnTip` din `do_copiaza`** (runda 13) — de decis daca se repara in #6 (fisierul e al lui)
sau separat, dupa.
## Ce ramane de proiectat
**Nu mai e „nimic" — runda 16 a adaugat doua povesti noi, ambele cu cercetare in curs, nu inchise:**
**Actualizare runda 17: punctele 1 si 4 de mai jos sunt FACUTE. Lista e goala pe partea de proiectare
— raman doar 3 (testele si inchiderea), care cer cod.**
1. ~~**Golul `IN_STOC` din S8**~~ — **FACUT in runda 17**, in nota de executie a lui S8. A lasat in
urma o **alegere de proiectare** (de unde se reconstituie valoarea istorica), nu o sarcina.
2. **Canalul care scrie `serie_act` / `numar_act` / `data_act` / `data_scad` / `id_ruta` / `tip_saft` /
`efactura`** pe documentul reemis — **nu apar in `scrie_factura2`**. Se inchide in S9, la implementare.
3. `S6` / `S12` (teste pe flux real) si `S13` (diff, review, changelog) raman la final.
4. ~~**Mockup-ul e la v8**~~ — **FACUT in runda 17: e la v9.1**, cu varianta D, randul de jos in doua
si motivul discountului in banda de totaluri (deciziile 57 + 66), plus deciziile
52/53/54/59/60/61/62/63+65 in text. **Republicat** pe URL-ul existent (vezi punctul 5 din
„Urmatorul bloc de lucru") — decizia 33 e consumata, URL-ul e la zi.
5. **Verificarea custodiei (decizia 60, runda 16) — TERMINATA.** Verdict:
`scrie_fact_aviz_custodie` nu are legatura cu 48/49 (serveste `ntip = 4`); emiterea 48/49 nu
atinge stocul (`cursor_articole_k`, `IN_STOC = 0`); `sterge_factura` n-are ce reversa. **Blocantul
CADE — regenerarea e sigura pe 48/49.** Ramane o rezerva neverificata exhaustiv (invariantul
`IN_STOC = 0` pe 48/49) si o cerinta noua pentru S4/S4g/S5 sa nu-l rupa. Detaliu si citari: plan,
decizia 60; raport `docs\cercetare\custodie_48_49_stergere_reemitere.md`.
6. **Cerinta noua de audit (decizia 63, runda 16) — PROIECTATA, S14.** Cercetarea s-a terminat
(`docs\cercetare\audit_vanzari_creare_modificare_stergere.md`): patru din sase informatii exista
deja pe `VANZARI` (`ID_UTIL`/`DATAORA` creare, `ID_UTILS`/`DATAORAS` stergere, scrise consecvent);
lipseste complet perechea de modificare; regenerarea din S9 ar suprascrie tacit perechea de creare
daca nu se transporta explicit, la fel ca `ID_FACT`. S14 are verdictul complet, recomandarea
(pereche noua `ID_UTILM`/`DATAORAM`, transport la S9, migrare DB inainte de EXE). **Decizia 65
(runda 16) a inchis cele doua puncte ramase** (antet vs. si linii; afisare in UI sau doar
interogare): afisarea e in **gridul din `frm_facturi`**, deci pe **antet** — S14 incepe cu
inventarul acelui grid (posibil sa existe deja coloane de audit), inainte de a adauga ce lipseste.
**Nu mai intreba** — inchise in rundele 6-15: tot ce listeaza handoff-ul rundei 13, plus deciziile 51-54,
cele patru intrebari ale rundei 14 puse lui Marius direct, **si asezarea zonei de jos (decizia 57)**.
### Deciziile 51-54 (Marius, runda 14) — luate, nu de reluat
51. **`TIP = 4` in `VANZARI_CORESP` = „factura scrisa dintr-o proforma".** Confirmat explicit dupa ce i
s-a explicat ce inseamna `1/2/3`: `TIP` e **natura legaturii parinte-copil**, nu tipul documentului
(`1` = aviz → factura, `2` = aviz → aviz de retur, `3` = factura → factura de retur). `4` e in
aceeasi familie cu `1`. **Alocarea e ireversibila** odata cu primele date de productie — acceptat.
52. **`do_modifica` ramane activ pentru multi-selectie.** Formularul unificat preia cazul cu un singur
document. Consecinta: **S2 nu poate desfiinta nici aceasta ruta**, nu doar alegerea de la decizia 49.
53. **Atasamentul PDF se sterge la reemitere** — nici remigrare, nici marcaj „versiune inlocuita".
Pasul din S9 devine **stergere** (`STERS = 1`), nu `UPDATE`. **Consecinta i-a fost spusa explicit si
a acceptat-o:** urma PDF-ului efectiv trimis clientului dispare din sistem.
54. **La reemitere se scriu valorile din formular, nu se reciteste sursa.** Rastoarna S10 — vezi
rezultatul 3. Avertizarea + confirmarea sunt **respinse**.
55. **Metoda de executie e obligatorie, si e scrisa in plan** (sectiunea **„Metoda de executie"**,
imediat inainte de Etapa I). Trei cerinte: (a) la implementare planul **se sparge pe stories**,
fiecare story fiind o livrare de sine statatoare, cu nota de executie proprie scrisa **inainte** de
prima linie de cod; (b) **se testeaza la fiecare pas**, nu doar la S6 / S12 — headless pentru
logica, UI vizibil unde sunt griduri, cu rezultatul asteptat declarat **inainte** de rulare, si
testele se pastreaza pentru suita finala; (c) **fiecare story trece prin code review dupa
implementare si dupa teste, inainte de commit**, facut de **un agent care nu a scris codul**.
Ordinea fixa: implementare → teste → diff in `docs\` → review → aprobarea lui Marius → commit.
**S13 nu mai e momentul review-ului**, ci al inchiderii (review de ansamblu, suita completa,
changelog, documentatie).
### Deciziile 57-58 (Marius, runda 15) — luate, nu de reluat
57. **Asezarea zonei de jos = varianta D.** Banda de totaluri din C; **incasare si alte date ca
sectiuni colapsabile in formular, una langa alta pe acelasi rand**, nu dialoguri; **fara bara de
comenzi jos** — `but_renunt` / `but_termin` raman in banda de titlu. A / B / C cad. Enuntul
complet, cele patru consecinte si punctul lasat deschis: **in plan**, la deciziile rundei 15.
58. **Mockup-ul asezarii ramane doar online**, fara copie in `docs\`. Vezi „Urmatorul bloc de lucru",
punctul 3, pentru procedura de modificare.
### Decizia 59 (Marius, runda 16) — luata, nu de reluat
59. **Discountul de DOCUMENT se repartizeaza proportional pe cote**, nu pe cota maxima. Doua locuri
de schimbat impreuna (`ofacturare_comun.prg` si `recalculeaza_totaluri_vanzari`), plus
`id_jtva_coloana` pe fiecare bucata si diferenta de rotunjire pe ultima cota. Raman deschise
campul de motiv si retroactivitatea. Enuntul complet: **in plan**, la „Decizia 59".
### Deciziile 60-65 (Marius, runda 16) — luate, nu de reluat
60. **Tipurile 48/49 (custodie) SUNT editabile prin #13.** Inchide punctul (a) ramas deschis din
decizia 56. **Verificarea s-a facut: blocantul cade**, regenerarea e sigura pe 48/49 fiindca
emiterea lor nu atinge stocul (`cursor_articole_k` filtreaza `IN_STOC = 0`), deci n-are ce reversa.
Ramane **cerinta** ca S4/S4g/S5 sa pastreze acea restrictie. Enuntul complet, cu ramificatia pentru
`ct_clb_altele` in S8: **in plan**, la „Decizia 60".
61. **Discountul de document primeste un camp text optional de motiv** (`AllowanceChargeReason`,
`ReasonCode` ramane `95`) — cere stocare noua pe `VANZARI` si un control nou in formular.
**Redeschide intrebarea de asezare de la decizia 57**: randul se imparte in trei sau campul
coboara pe rand propriu — **inchisa la loc de decizia 64**. Enuntul complet: **in plan**, la
„Decizia 61".
62. **Restrictia de modificare e strict „nu a fost trimisa in eFactura"** (garda `EsteInEFactura`,
deja in S7) — **retroactivitatea nu mai e o intrebare**, nu se leaga de nicio data. Ramane
semnalat, nedecis, un rest separat: relistarea (nu editarea) unei facturi vechi deja trimise ar
produce N randuri de discount dupa decizia 59, deci hartie diferita de originalul trimis. Enuntul
complet: **in plan**, la „Decizia 62".
63. **Cerinta noua de audit**: data + utilizator pentru creare, modificare si stergere pe documente.
**Cercetare TERMINATA, proiectata ca S14.** Patru din sase informatii exista deja pe `VANZARI`
(creare, stergere); lipseste perechea de modificare; regenerarea (S9) ar rescrie tacut perechea
de creare fara un transport explicit, la fel cum era riscul pentru `ID_FACT` inainte de decizia
care l-a pastrat. Locul de afisare, ramas deschis aici, se inchide la decizia 65. Enuntul
complet: **in plan**, la „Decizia 63" si la S14.
64. ~~**Asezarea campului de motiv: randul se imparte in trei.**~~ **RASTURNATA DE DECIZIA 66
(runda 17). Nu mai e in vigoare** — se pastreaza doar ca istorie, ca sa se stie ca varianta „a
treia sectiune jos" a fost incercata si respinsa dupa ce Marius a vazut-o in mockup. Ce e in
vigoare: **motivul sta in banda de totaluri, langa discount; randul de jos are doua sectiuni.**
65. **Auditul (decizia 63) se afiseaza in gridul din `frm_facturi`, nu in formularul facturii.**
Confirma continutul de la decizia 63 (trei perechi data + utilizator: adaugat, modificat,
sters). Inchide ambele puncte ramase de la S14: e pe **antet** (gridul listeaza documente) si
**se afiseaza**, in acel grid — nu cere controale noi in formularul unificat. **Prim pas al
implementarii S14: inventarul gridului din `frm_facturi`**, posibil sa existe deja coloane de
audit. Enuntul complet: **in plan**, la „Decizia 65" si la S14.
### Decizia 66 (Marius, runda 17) — luata, nu de reluat. RASTOARNA DECIZIA 64.
66. **Motivul discountului sta LANGA DISCOUNT, in banda de totaluri** — *„motiv discount vreau sa fie
langa discount, nu a treia coloana"*. **Rastoarna decizia 64**: randul de jos ramane cu **doua**
sectiuni (incasare, alte date), fiecare pe jumatate de latime, adica exact decizia 57 punctul 2.
Proiectarea a adaugat o regula care nu i-a fost ceruta explicit si care se poate schimba dintr-o
linie: **campul e activ doar cand discountul de document nu e zero**. Consecinta de asezare,
acceptata: banda de totaluri devine plina si la latimi mici se rupe pe doua randuri. Enuntul
complet: **in plan**, la „Decizia 66".
## Interzis
- **Nu se atinge `COMUN\clase\ofacturare_comun.vc2` si nici `COMUN\programe\ofacturare_editare.prg`** —
perimetrul lui #6 cat timp #6 e in lucru. **Include si defectul `lnTip`.**
**`COMUN\programe\ofacturare.prg` NU e in acest perimetru.**
- **Nu se ruleaza `git_sync.ps1`** cat timp sesiunea de la #6 lucreaza.
- **Nu se propune „avertizare + confirmare" la S10** — respinsa de decizia 54.
- **Nu se foloseste `oscrie_in_fisiere` ca ruta de contare in #13** (decizia 35). Exceptie: piciorul de
**stergere** din S9.
- **Nu se proiecteaza retragerea fluxului lui #6** (decizia 38).
- **Reteta in 4 pasi din J-quater e ABANDONATA** (decizia 34).
- **Nu se proiecteaza pe numere masurate in Dev** (decizia 31).
- **Nu se reargumenteaza deciziile 22, 29, 39, 43, 49, 50, 57** — perimetrul lor e inchis.
- **Nu se mai propune dialog modal pentru incasare / alte date** si **nu se readuce bara de comenzi
jos** — respinse de decizia 57. Nici A / B / C nu se mai propun.
- **Nu se mai propune motivul discountului ca sectiune separata jos** — incercat in v9 si **respins de
decizia 66**. Sta in banda de totaluri, langa discount.
- Fara commit din proprie initiativa: diff-ul se livreaza intai ca fisier in `docs\`.
- **Nu se incepe implementarea pe cod** — #13 porneste dupa terminarea lui #6 (decizia 30).
## Capcane de mediu
- **NOU (runda 17), si e cea care conteaza pentru oricine lucreaza acum: SESIUNEA DE LA #6 COMITE IN
ACEEASI COPIE DE LUCRU, IN PARALEL, INCLUSIV IN `docs\`.** In timpul rundei 17 a dat commit-ul
`b5a7108` („cercetarile comune trec in COMUN, reziduul #6 dispare") — o curatenie care a **rescris 39
de fisiere din `docs\`**, a mutat 14 cercetari in `COMUN\docs\cercetare\` si a **sters 20 de rapoarte**.
Doua consecinte de stiut:
1. **A sters si doua fisiere ale lui #13**, incadrate gresit drept reziduu de #6:
`mockup_v7_modificari.md` si `mockup_v8_modificari.md`. **Nu sunt pierdute** —
`git show d9f5ca4:docs/cercetare/mockup_v8_modificari.md` le scoate. Nu s-au restaurat: erau
nereferite si stergerea a fost o curatenie aprobata a altei sesiuni; **daca le vrei inapoi, e o
comanda, dar se cere lui Marius intai**.
2. **Editarile rundei 17 in plan au fost prinse in acel commit**, nu de mine — `git status` arata
`plan_13` si `plan_index` **curate**, desi le-am scris eu. Nu e o dovada ca n-am scris nimic.
**Verifica pe continut (`grep`), nu pe `git status`.** Toate trei editarile au supravietuit,
confirmat.
**Regula practica:** cat timp #6 lucreaza, orice fisier din `docs\` poate fi rescris sau sters sub
tine intre doua apeluri. Editarile se **reverifica pe continut dupa** ce le-ai facut, iar fisierele
proprii nu se presupun stabile.
- **Ruda punctului de mai sus, platita tot in runda 17: un `grep` care nu gaseste nu dovedeste ca
lipseste.** Am „constatat" ca decizia 62 lipseste din mockup si eram gata s-o adaug — era acolo,
rupta pe doua randuri (`decizia\n62`), iar eu cautasem si forma feminina („trimisa"), nu pe cea din
text („trimis"). **Inainte sa declari ceva lipsa dintr-un fisier, cauta termenul cel mai scurt si
neflexionat**, si abia apoi forma completa.
- **NOU (runda 16): `PACK_CONTAFIN` are DOUA copii pe schema** — `owner = ACN` si
`owner = MARIUSM_AUTO`. O interogare pe `all_source` **fara filtru pe `owner`** intoarce cele doua
corpuri intercalate, adica **text corupt** care pare cod real. Filtreaza mereu pe owner, sau
citeste din exportul de pe disc (`D:\ROA\DATABASE\SCRIPTURI_CLAR\...`).
- **NOU (runda 16): `git_sync.ps1` a fost rulat din greseala de un subagent**, desi e interzis cat
timp sesiunea de la #6 lucreaza. **Paguba: zero** — verificat pe mtime, singurul fisier atins e
`COMUN\clase\ofacturare_comun.pre_s4butoane.bak.vc2`, text generat pentru un **binar de backup
netracked**; niciun `.vc2` / `.sc2` de lucru n-a fost rescris, niciun write-back, niciun commit.
**Lectia pentru briefing:** interdictiile trebuie puse **la inceputul** promptului, nu la mijloc —
agentul a inceput sa lucreze inainte sa le citeasca pe toate.
- **NOU, si e cea mai scumpa a rundei: o garda intreaga a fost inteleasa pe dos pentru ca s-a citit
COMENTARIUL, nu codul.** `-- verific daca exista facturi sau avize de retur` a fost citit ca „facturi
de retur sau avize de retur"; codul testa `TIP IN (1,2)`, adica si facturarea normala. Premisa a
circulat prin plan mai multe runde. **Comentariul nu e sursa de adevar; `IF`-ul e.**
- **Ruda buna a punctului de mai sus: numele unei proceduri nu e sursa de adevar.**
`scrie_fact_aviz_custodie` suna ca si cum ar servi facturile de custodie (48/49) — de fapt are un
singur apel in tot pachetul, pe ramura `ntip <> 4` (`PACK:7472`, `:7521`), deci serveste `ntip = 4`
(factura din avize). Premisa gresita a circulat pana in decizia 60. Se verifica **apelantul si
garda**, nu numele.
- **NOU: numerotarea difera intre exportul `PACK_FACTURARE` si corpul din `all_source`.**
`sterge_factura` incepe la `EXPORT:5432` si la `DB PACK_FACTURARE:4192`. **Nu exista offset
constant** — nici intre export si rapoarte, nici intre export si DB. Textul insa e identic.
- **Cifrele si multimile de valori se citesc din `Do Case`-ul real, nu din raportul precedent.** A doua
oara consecutiv cand asta salveaza ceva.
- **Un agent poate trece in `idle` fara mesaj final, desi a livrat integral** — si poate fi **ucis de
limita de sesiune** dupa ce a livrat. S-a intamplat de **opt** ori pana acum, ultima oara chiar la
`s10-rederivare-2`, care a raportat `idle` la o ora dupa ce terminase raportul. **Livrabilul se
verifica pe disc** (dimensiune, sectiuni, ultimele randuri, marcaje `(in lucru)` ramase), nu se reia
munca reflex.
- **NOU, si a costat o coliziune: `idleReason: interrupted` NU dovedeste ca agentul a murit.**
`discount-tva-efactura` a raportat `interrupted` (de doua ori) avand pe disc doar scheletul de 624
octeti. A fost presupus mort si relansat — **era viu**, si a ajuns la 13,5 KB. Al doilea agent era la
un pas de un `Write` peste raportul viu; a scapat doar pentru ca harness-ul a prins ca fisierul se
schimbase de la citire. **Inainte de relansare se compara mtime-ul livrabilului cu ora curenta**, nu
doar dimensiunea — un fisier atins acum cateva minute inseamna scriitor viu. **Daca s-a produs deja
coliziunea:** al doilea agent scrie in fisier separat (`<nume>_b.md`) si se imbina manual — nu `Edit`
tintit pe sectiunile ramase (primul le scrie oricum), si nu predare, care arunca firul deja inceput.
- **Cere din promptul initial: raport scris pe disc devreme si incremental.** In runda 14 asta a salvat
52 KB de la un agent ucis; celalalt, care n-apucase sa scrie, a pierdut tot.
- **Limita de sesiune poate ucide agentii instant, la pornire.** **Sonda ieftina**: relanseaza intai
agentul cel mai mic.
- **`vfp_symbols.ps1` se cheama din unealta PowerShell, nu din Bash** — caile cu `\` sunt mancate de
Bash. Indexeaza si `.bak`-urile; liniile se confirma pe fisierul real.
- **Pentru sqlplus foloseste unealta PowerShell**, nu Bash:
`& 'D:\ROA\instantclient_19_18\sqlplus.exe' -L -S 'MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL' '@C:\cale\x.sql'`
SQL-ul **in fisier ASCII**, cu `set linesize 32767` / `set pagesize 0` / `set feedback off` si `exit`.
- **`cd` intr-un apel de shell persista intre apeluri.** Verifica fisierele pe **cale absoluta**.
- **Grep de la radacina nu vede `COMUN\`** (gitignore); se da `path` explicit.
- **`grep -o -E '.{N}X.{N}'` rateaza tacut potrivirile de la capete de rand.** Intai termenul simplu.
- **`ID_UTILS` / `DATAORAS` pe `VANZARI_DETALII` nu inseamna „sters de/la".** Editarea de linie a lui
#6 le scrie si pe randuri **nesterse** — `ofacturare_editare.prg:501-505` (pe rand viu, `sters = 0`)
si `:522-525` (pe rand proaspat inserat). Un raport de audit trebuie sa puna si `STERS` in conditie,
altfel liniile doar atinse la editare apar gresit drept sterse.
- **Cautarea unui literal SQL trebuie sa tina cont de spatii** (`-10000` nu gaseste `rownum - 10000`).
- **Un singur scriitor pe fisier.** Integrarea in plan a facut-o sesiunea principala, secvential.
- **Verifica afirmatiile portante ale rapoartelor, nu le lua pe incredere.** Runda 14 a infirmat doua
premise portante din plan (garda pe aviz; ambele piese ale schitei S8) si a corectat sapte afirmatii
din materialele existente.
- **Export pachet:** `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii). Marcajul `versiune_db.txt` = `2026_08_09_02`, deci exportul e aplicat pe DB.
## Teste
**Caz adaugat de runda 16, si e cel mai important dintre toate:** **reemiterea unei facturi cu
articole gestionabile lasa stocul NESCHIMBAT.** E proba directa ca piciorul de stergere din S9 a
trecut chiar prin `oscrie_in_fisiere` — daca cineva „simplifica" tripleta la un apel direct de
`sterge_factura`, rulajele raman in picioare si gestiunea se descarca **de doua ori**, fara niciun
mesaj. Testul se face pe stocul agregat inainte / dupa, nu pe inspectia codului.
**Caz adaugat de runda 17, pereche cu cel de mai sus:** un document emis cu un articol caruia i s-a
schimbat intre timp `IN_STOC` in nomenclator, deschis in formular, **poarta valoarea de la emitere**,
nu pe cea de azi; iar reemiterea lui lasa **stocul agregat neschimbat**. Masurat inainte / dupa, nu
prin inspectia codului. E criteriul care demonstreaza ca golul `IN_STOC` din S8 chiar s-a inchis.
**Nu s-a rulat nimic.** Nu exista inca cod de testat (decizia 30). Testele minime raman in
`canal_cont_venit_fara_politica.md` §6, plus cazurile rundei 11 (aviz cu articol fara politica → `SCD`
ramane `461` / `418`; `ntip = 46` → `scrie_nota` nu se cheama), probele de paritate ale rundei 12
(acelasi rezultat de inchidere cu si fara linii libere; factura emisa dupa du-te-vino prin proforma
descarca stocul) si cele ale rundei 13 (factura din proforma descarca gestiunea si are rand `TIP = 4`,
iar proforma **nu** e marcata `FACTURAT`; linie cu `id_pol` si `cont_venit` simultan → eroare).
**Runda 14 adauga:** reemiterea unui document de contract **dupa** ce pretul din contract s-a schimbat
reproduce **pretul din formular**, nu pe cel din contract (decizia 54); si stergerea unei proforme din
care s-a emis factura **e refuzata** (garda noua, punctul 11).
**Runda 16 adauga (decizia 60, verificare terminata):** pe un document de tip 48/49 (custodie) **nu
se poate adauga un articol gestionabil** (`IN_STOC <> 0`), nici prin cautarea pe server in linie
(S4), nici prin „Alege din nomenclator…" (S4g) — criteriu de test, nu presupunere; raport
`docs\cercetare\custodie_48_49_stergere_reemitere.md`.
`s8_incarcare_document.md` §7 da criteriul „gata cand" al lui S8 in forma testabila, pe fiecare tip de
sursa.
## Urmatorul bloc de lucru
1. ~~Citeste cele doua rapoarte de discount si imbina-le~~ — **FACUT INTEGRAL in runda 16**, toti cei
cinci pasi (a)-(e): citite, cele trei afirmatii portante verificate la sursa, imbinate in raportul
unic, integrate in plan (**K-bis** + S1 + deciziile 56 si 57), si raspunsul dat lui Marius. **Nu se
reia nimic din el.** Ce a ramas in urma lui e o **decizie a lui Marius**, nu o sarcina de executie:
se implementeaza sau nu repartizarea proportionala pe cote, si vrea sau nu un camp de motiv.
*Cand vine vorba de asta, atentie la formulare:* concluzia s-a intors de doua ori pe drum — cota
maxima e regula reala, dar defectul e **tacut**, nu blocheaza factura, si nu s-a putut proba pe
date reale.
2. **NU mai cere raspunsuri pe lista de 19 puncte** — sunt inchise prin decizia 56 („da la toate").
Tipurile 48/49 s-au raspuns si ele — **decizia 60, runda 16: da, editabile**. Nimic de reintrebat
din lista veche.
3. ~~Confirmarea variantei D~~ — **DATA (decizia 57).** Cercetarea a confirmat ca discountul **NU
cere cota de la utilizator**, deci **a treia sectiune „grea" (cota + explicatie proprii) nu mai e
in discutie**. **Varianta minimala s-a materializat — decizia 61, runda 16: Marius vrea campul
text optional de motiv.** Intrebarea de asezare e deci **activa, nu mai e ipotetica**: campul fie
imparte randul in trei (~440 px fiecare la 1366 px, strans), fie coboara pe rand propriu si D
redevine doua etaje. **De decis de Marius**, nu la implementare. Pagina:
https://claude.ai/code/artifact/dfc70d38-856f-48fd-b6af-3da78e9da4f7
> **ATENTIE — mockup-ul asezarii NU MAI ARE COPIE PE DISC.** Cerinta lui Marius, runda 15: „doar
> online, ca sa nu mai intretii 2 variante". `docs\mockup_13_variante_asezare_jos.html` **a fost
> mutat afara din `docs\`**; **artifactul e singura sursa de adevar**. Ca sa-l modifici:
> **intai `WebFetch` pe URL** ca sa recuperezi HTML-ul, scrie-l intr-un fisier de lucru **in
> scratchpad, nu in `docs\`**, editeaza, apoi `Artifact` cu **`url` = link-ul de mai sus** (fara
> `url` se creeaza link nou). `WebFetch` cere si el o citire prealabila daca artifactul a fost
> publicat din alta conversatie — asa a fost si in runda 15, si a mers.
> **`docs\mockup_13_formular_unificat.html` (v8) nu e atins de regula asta** — Marius a cerut-o
> numai pentru mockup-ul asezarii.
4. ~~**Golul de la `IN_STOC` in S8**~~ — **FACUT in runda 17.** E in plan, in nota de executie a lui S8
(caseta „CERINTA DE EXECUTIE ADAUGATA IN RUNDA 17"), cu criteriul de test in „gata cand" si cu
consecinta 1 de la S10 marcata **PRELUAT**. **Nu se reia.** Ce a ramas in urma lui **nu e o sarcina
de executie, e o alegere de facut la proiectarea lui S8**: valoarea istorica a lui `IN_STOC` nu e
stocata nicaieri, deci trebuie ales **de unde se reconstituie** (una din cele trei variante scrise
in plan). Nu se presupune niciuna.
5. ~~**Mockup v9**~~ — **FACUT in runda 17.** `docs\mockup_13_formular_unificat.html` e la **v9.1**, cu
varianta D, randul de jos in **doua** sectiuni si motivul discountului in banda de totaluri
(decizia 66, care rastoarna decizia 64); raport de modificari:
`docs\cercetare\mockup_v9_modificari.md`. **Republicat**, la cererea lui Marius, pe URL-ul existent:
**https://claude.ai/code/artifact/e9d73e86-7518-4f50-a998-43c49e083142** (notat de acum si in plan,
la S1). Ca sa-l modifici: `WebFetch` pe URL intai — fara asta publicarea e refuzata — apoi `Artifact`
cu `url` = link-ul de mai sus.
6. Dupa aceea **nu mai e proiectare de facut**: #13 asteapta terminarea lui #6 (decizia 30). Prima
sarcina la pornirea implementarii e **spargerea planului pe stories** si scrierea notei de executie
pentru prima dintre ele (decizia 55).
**FACUT in runda 14, nu se reia:** integrarea in plan a rezultatelor 1-3 (decizia 50 rescrisa cu cele
trei garzi; S7 rescris cu pre-flight; S8 cu blocul de avertizare peste schita gresita; S10 rescris
integral; S9 si S11 corectate pentru atasamente; deciziile 51-55 adaugate), **plus sectiunea „Metoda de
executie"** (spargerea pe stories, testele la fiecare pas, code review per story inainte de commit) si
rescrierea lui S13 ca inchidere, nu ca moment al review-ului. **Plus, tot in runda 14:** corectia S8
(`frm_facturare_articole2` e **prototipul**, nu o varianta de exclus — S1); inchiderea punctului
`REST_NOTE_PLATA` / `IPS_VOYAGES_VANZARI` prin raspunsul lui Marius, cu cerinta ca **S7 sa le refuze
explicit**; si cele trei variante de asezare a zonei de jos.
**Nu se incepe implementarea pe cod** (decizia 30).

View File

@@ -1,73 +0,0 @@
# Handoff — stergerea lui progres.md (ultimul pas al curateniei)
Scris 20.08.2026, dupa r18027. Sesiunea principala a trecut de pragul de context (254k).
**Numai stare, fara analize noi.**
## Nimic nu e intr-o stare periculoasa
- **Totul e comis si pushed.** SVN `r18026` (lucrarea) si `r18027` (curatenia). Git: proiect
`3ddcbc0`, COMUN `58d4c4a`, ambele la zi cu origin. `git status` curat in ambele repo-uri.
- Niciun fisier editat fara write-back. `vfp9.exe` nu ruleaza. Nicio tranzactie deschisa.
- `roafacturare.PJT`/`.PJX`/`.exe` raman modificate din sesiunea de VFP a lui Marius: **nu se comit**.
## Ce a cerut Marius, si unde s-a oprit
> "cred ca poti sa stergi si progres.md daca nu mai indica catre modificari active"
**Premisa e corecta**, verificat: `progres.md` nu mai descrie lucrari active. #6, #7 si #8 sunt
inchise; starea lui #13 sta in `handoff_13_formular_unificat.md`, care **nu** il refera
(0 trimiteri; la fel `plan_13` si `plan_10`).
**Ce lipseste ca sa se poata sterge** — sase trimiteri vii de rescris:
| fisier | trimiteri | ce spune |
|---|---|---|
| `docs\plan_index.md` | **3** | "Planul a fost sters la curatenie; istoricul e in `progres.md`" — randurile #8, #6, #7 |
| `docs\plan_11_integrare_politici_preturi.md` | 1 | |
| `docs\plan_12_nomenclator_ca_lista_preturi.md` | 1 | |
| **`COMUN\docs\reguli_lucru.md`** | 1 | **cross-proiect** — fisier partajat de toata suita ROA |
Ultima e motivul pentru care lucrarea nu e banala: `COMUN` e dublu-versionat (SVN + git propriu,
`gitea.romfast.ro:romfast/comun.git`) si orice modificare acolo atinge toate produsele ROA.
## Ce are de facut sesiunea urmatoare
1. Decide cu Marius **unde se muta rolul de arhiva**: fie se renunta la promisiune (istoricul
ramane doar in git, `3ddcbc0` si mai vechi), fie trimiterile arata catre revizia SVN/commit-ul
git in loc de fisier.
2. Rescrie cele 6 trimiteri de mai sus in acord cu decizia.
3. Sterge `docs\progres.md` (e sub SVN — `svn delete`, nu doar `rm`).
4. Commit in **doua repo-uri**: SVN pe `docs\` + `COMUN\docs\reguli_lucru.md`, apoi `roa_sync.bat`.
Verifica separat repo-ul git al lui `COMUN`.
5. Sterge si acest handoff, odata ce pasul e incheiat.
## Starea curenta a documentatiei
`docs\` are **10 fisiere** + `docs\cercetare\` cu **63**:
- planurile deschise/amanate: `plan_13` (**urmatorul la rand**, locul 1 in index), `plan_12`,
`plan_11`, `plan_10`, `plan_index.md`;
- materialul lui #13: `handoff_13_formular_unificat.md`, `mockup_13_formular_unificat.html`,
`S1_inventar_campuri_formular_unificat.md`;
- `conventii_mediu_oracle.md` (netrackat in SVN, doar git);
- `progres.md` — subiectul acestui handoff.
**Capcana de nume, deja platita**: `cercetare\s8_incarcare_document.md`, `s8b_rutarea_scrierii.md`
si `audit_vanzari_creare_modificare_stergere.md` NU tin de planul #8 — sunt story-uri ale lui
**#13**. Criteriul de stergere in `cercetare\` a fost referinta, nu numele: **#13 se sprijina pe
folder cu 86 de trimiteri**, deci nu se poate sterge in bloc.
## Reziduu acceptat, nu defect
`progres.md` pastreaza 39 de trimiteri catre fisiere sterse, plus 5 mentiuni in proza in
`plan_13`, `cercetare\handoff_s4_runda1.md`, `cercetare\rec_s4_runda1.md` si
`cercetare\rec_cale_vanzari_detalii.md`. Sunt istorice, se rezolva din git — aceeasi situatie ca la
curatenia planului #8. Daca `progres.md` dispare, dispar si cele 39.
## Defect deschis, raportat si nereparat (decizia lui Marius)
Sincronizarea articole-rulaje nu recalculeaza `valoarev`/`valtvav` pe randul `trul`; ele ajung
invechite in Oracle (`RUL`). `VALOAREVCTVA` nu e coloana in `RUL` — ramane invechit doar pe ecran,
pana la reincarcarea notei. Back-fill-ul de la reincarcare are garda `WHERE EMPTY(NVL(...,0))`,
deci prinde doar zerourile. Lantul complet, cu `fisier:linie`, era in `docs\raport_r7_sincronizare.md`,
sters la curatenie — se recupereaza din git (`5b52cb3`), iar rezumatul a ramas in `progres.md`.

268
docs/livrare_13.md Normal file
View File

@@ -0,0 +1,268 @@
# Livrare #13 — inventar
Nu exista un inventar de livrare gata facut ("fisiere modificate + scripturi") ca document unic —
verificat la scriere in `docs\raport_curatenie_finala.md`, `docs\handoff_reluare_13_b13.md`,
`docs\handoff_plan13_executie_continua.md`, `docs\plan_13_executie.md`, `docs\scripturi_oracle_13.md`.
Ce exista sunt piese separate refolosite ca baza aici:
- **Registrul scripturilor Oracle** — `docs\scripturi_oracle_13.md` (26 randuri, tabel complet) —
sursa pentru sectiunea „Scripturi Oracle" de mai jos.
- **Tabelul de stories** cu fisierele atinse per poveste — `docs\plan_13_executie.md:35-62`.
- **Datoriile deschise** — extrase integral (35+4 intrari) in sectiunea „Datorii deschise" de mai jos.
- Inventarul de fisiere sterse/pastrate din curatenia de documentatie de la inchiderea planului
(454 fisiere, 08.09.2026) — era in `docs\raport_curatenie_finala.md`; continutul e comis in
`04ce426` (ROAFACTURARE), runda 2 (rapoartele citate din registru, pointerii rescrisi) in
`79cfd72`.
**Curatenia de azi (09.09.2026)** a sters, dupa ce continutul lor a fost mutat mai sus/mai jos in
acest fisier sau verificat ca e deja in cod comis: `docs\raport_curatenie_finala.md`,
`docs\raport_curatenie_comun.md`, `docs\handoff_reluare_13_b13.md`,
`docs\handoff_plan13_executie_continua.md`, `docs\raport_valtva_null.md`,
`docs\raport_valtva_verificare_oracle.md`, `docs\raport_sursa_cota_tva.md`,
`docs\raport_fallback_cota_tva.md`, `docs\raport_meniu_plat_facturare.md`,
`docs\raport_acces_factura_noua.md`, `docs\raport_combo_lista_preturi.md`,
`docs\propunere_combo_lista_preturi.md` — plus backup-uri Oracle `pack_*_inainte_*`, snapshot-uri
`wip13_*` (starea finala ramane in `docs\scripturi_finale_13\`) si diff-uri/fixturi temporare.
Toate recuperabile din git.
Nimic de mai jos nu e analiza noua — doar `git log`/`git diff`/`git status` pe cele doua repo-uri,
fisierele de pe disc citate mai sus, si fisierele `docs\wip13_*.sql.txt` de pe disc (inainte de
stergerea lor de azi).
## Commit-uri
| Repo | Branch | Commit-uri fata de `main` | HEAD |
|---|---|---|---|
| `D:\ROA\ROAFACTURARE` (git) | `plan13-s2` | **415** | `7943659` |
| `D:\ROA\ROAFACTURARE\COMUN` (git, repo separat) | `plan13-s2` | **189** | `ed43b39` |
Ultimele 3 commit-uri pe fiecare (cele mai recente, dupa blocul b13 inchis 08.09.2026 seara):
**ROAFACTURARE**: `7943659` docs combo lista preturi · `4a77400` docs investigatia VALTVA null ·
`e371a08` cod — un singur meniu plat la „Facturare (nou)" (`Clase/ofundal_facturare.vc2`).
**COMUN**: `ed43b39` cod — lista de preturi vizibila in combo-urile de cautare · `14586e3` cod —
cota TVA standard cand articolul cautat nu are cota pe politica · `755f19a` docs — procedura de
instalare ponytail.
Istoricul complet, cu mesajele fiecarui commit: `git log --oneline main..plan13-s2` (fiecare repo,
din radacina lui). Lantul de decizii si predari care a produs aceste commit-uri era comprimat in
`docs\handoff_plan13_executie_continua.md` §4.0-istoric (61 de predari, un rand fiecare, fiecare cu
trimitere la rapoartele arhivate in `docs\arhiva\13\`) — fisierul a fost sters in curatenia de
inchidere a etapei (09.09.2026); textul integral ramane recuperabil din git
(`git log -p -- docs/handoff_plan13_executie_continua.md`, ultima versiune necomprimata `36325ec`).
## Decizii Marius (valabile pana la revocare explicita)
Sursa: `docs\handoff_plan13_executie_continua.md` §2, §3ter-§3octies (fisier sters, recuperabil din
git). Cele trei decizii blocante originale sunt documentate si in `docs\plan_13_executie.md:83-92`
(fisier pastrat); tabelul de mai jos e mai complet.
| # | Decizie | Stare |
|---|---|---|
| 1 | `IN_STOC` la incarcarea documentului | **INCHISA 31.08.2026** — varianta (2), valoarea curenta din nomenclator, fara migrare DB. Sustinuta de decizia 62 (`EsteInEFactura` blocheaza modificarea dupa trimitere) |
| 2 | Relistarea unei facturi vechi deja trimise | **INCHISA 31.08.2026** — toate facturile se tiparesc dupa regula noua, fara versionare pe data. Facturile trimise la ANAF nu se mai retrimit. Consecinta acceptata: retiparirea unei facturi vechi cu cote mixte arata N randuri de discount in loc de unul (doar afisare, totalurile nu se schimba) |
| 3 | `PROC_TVAV` ca parametru (decizia 54) | **INCHISA 31.08.2026 — DA.** Copierea/modificarea pastreaza cota documentului original; emiterea noua lasa utilizatorul sa aleaga. Cost: Oracle (pachet PL/SQL reutilizabil) + VFP care transmite cota salvata + S8 trebuie s-o incarce/pastreze |
| 4 | Comportament la 1366x768 cu ambele acordeoane deschise (deficit 98px) | nedecis la predare — recomandare: acordeoane mutual exclusive sub un prag de inaltime |
| 5 | `TabIndex` containere 62-67 | **INCHISA 06.09.2026 — varianta (b)**, ramane cum e azi, fara cod scris |
| 6 | S4-4 ramura aviz (`exista_ramas_avize`, ID facturii inexistent la momentul calcului) | **INCHISA** — varianta (C), functie Oracle noua (`exista_ramas_avize_lista`), test paritate 18/0 |
| 7 | Plafonul Rol B pe `1,22,29,23` (S4-5) | **acceptata, de executat** (06.09.2026) — recomandare: poveste separata, validare la editarea celulei de cantitate, nu in `do_adauga_articol_cautat` |
| 8 | Tipul `41` in `llCautareInLinie` (S4-5) | **acceptata, de executat** (06.09.2026) — recomandare: fix pe `combosql_cautare.cursor_preturi_call`, ca poveste separata |
| 9 | S4b — meniul `xmenu()` „Adauga tot / Alege..." vs mecanismul v2 existent | **INCHISA 31.08.2026 — varianta (3)**: `do_incarca_articole` neatins, alegerea selectiva devine poveste separata (datoria 19) |
Permisiuni operationale date de Marius in cursul planului (arse/valabile doar in fereastra de
executie #13, documentate aici doar ca sa nu se piarda motivatia deciziilor de mai sus si a
scripturilor `wip13_*`/rapoartelor citate): acord Oracle liber pe `MARIUSM_AUTO` (3ter, 03.09.2026),
masina de lucru fara aprobare prealabila (3quater, 03.09.2026), documente de test libere pe
`MARIUSM_AUTO` (3quinquies, 04.09.2026), kill pe sesiuni Oracle orfane INACTIVE (3septies,
07.09.2026), rulari intermediare scurte / suita completa doar la finalizare (3octies, 07.09.2026).
Detaliu complet: `git log -p -- docs/handoff_plan13_executie_continua.md`.
## Fisiere de cod modificate
### `D:\ROA\ROAFACTURARE` (repo radacina)
Fata de `main`: 96 fisiere schimbate (328592 insertii / 6324 stergeri, `git diff --stat main...plan13-s2`).
Cod propriu-zis (5 fisiere) — restul din cele 96 sunt `docs\` (rapoarte, cercetare, scripturi `.sql`,
`.sql.txt`, sabloane):
| Fisier | Ce |
|---|---|
| `Clase\ofacturare_util.vc2` | clasa noua, intrata in versionare (S8-geometrie-p) |
| `Clase\ofundal_facturare.vc2` | meniul principal — S8-geometrie + meniul plat „Facturare (nou)" (`e371a08`) + **modificare uncomisa curenta**, vezi „Necomis pe disc acum" |
| `Programe\roafacturare.prg` | `SET CLASSLIB` pentru `ofacturare_util.vc2` (S8-geometrie-p) |
| `changelog_roafacturare.txt` | intrari noi pe seria 2.11.x |
| `CLAUDE.md` | reguli de proiect actualizate in cursul planului |
### `D:\ROA\ROAFACTURARE\COMUN` (repo separat)
Fata de `main`: 549 fisiere schimbate (69124 insertii / 1589 stergeri, `git diff --stat main...plan13-s2`,
rulat din `COMUN`). **526 sunt in `utile\Teste\`** (vezi sectiunea „Doar test"); **23 sunt cod/docs
propriu-zise**:
**Clase `.vc2` (6):**
| Fisier | Domeniu |
|---|---|
| `clase\ofacturare.vc2` | nucleul formularului unificat — 4305 linii diff, cea mai atinsa clasa din tot planul |
| `clase\ofacturare_comun.vc2` | 174 linii |
| `clase\ferestre_cere_date.vc2` | 16 linii — incarcare/rutare document |
| `clase\ocomenzi.vc2` | 6 linii — S3c-4 tinta 1 (`do_factura`) |
| `clase\caut_ora.vc2` | 14 linii — combo cautare (lista de preturi, `ed43b39`) |
| `clase\omodificari.vc2` | 2 linii |
**Programe `.prg` (7):**
| Fisier | Domeniu |
|---|---|
| `programe\ofacturare.prg` | 1141 linii (in mare parte scoatere de logica spre fisierele noi de mai jos) |
| `programe\ofacturare_antet.prg` | **nou**, 925 linii — antet, extras din S3-2 |
| `programe\ofacturare_rutare_scriere.prg` | **nou**, 568 linii — rutarea scrierii la editare (S8b) |
| `programe\ofacturare_editare.prg` | 269 linii |
| `programe\oproceduri_facturare.prg` | 69 linii — discount pe linie (S4c) + fallback cota TVA (VALTVA) |
| `programe\ofacturare_comun.prg` | 134 linii |
| `programe\ogrid_latimi.prg` | **nou**, 104 linii — geometrie grid |
**Docs (9)** + `.gitignore`: `docs\reguli_lucru.md`, `docs\todos.txt`, `docs\.ultima_revizie`,
`docs\capcana_sqlexec_no_data_found.md`, `docs\conventie_encoding_cp1252.md`,
`docs\depanare_testare_vfp.md`, `docs\instalare-ponytail.md` (netracked, vezi §b13 — continutul
nu a fost verificat, e cod tert), `docs\prompt_recreare_statusline.md`, `docs\tipuri_documente_facturare.md`.
Fisierele atinse per story, cu detaliu: `docs\plan_13_executie.md`, coloana „Fisiere atinse".
## Scripturi Oracle
Sursa: `docs\scripturi_oracle_13.md` (26 scripturi inregistrate), incrucisat cu cele 17 fisiere
`docs\wip13_*.sql.txt` prezente pe disc (restul, #1-#6/#8-#10, sunt sub
`D:\ROA\DATABASE\SCRIPTURI_CLAR\...`, in afara acestui repo). Toate scripturile din registru
raporteaza **APLICAT** (verificat pe `USER_OBJECTS`/`VERSIUNE`/probe la momentul aplicarii) — **nu
exista niciun rand marcat "NERULAT"** in registru, deci nu a fost nevoie de reincadrare.
| # | Obiect Oracle | Tip | Fisier wip cel mai recent | Aplicat pe MARIUSM_AUTO |
|---|---|---|---|---|
| — | `PACK_FACTURARE` | package (spec+body) | `wip13_2026_09_08_03_COMUN_PACK_FACTURARE_fix_venchelt.sql.txt` (ultimul din 9 recompilari cumulative: #1,2,4,5,10,11,12,15,16,20,25,26) | DA — probat 08.09.2026 (`probe_s13b_batchmode_direct.prg`) |
| — | `PACK_CONTAFIN` | package (spec+body) | `wip13_2026_09_03_02_COMUN_PACK_CONTAFIN.sql.txt` (S9-7, dupa `..._01_...` S9-6) | DA — probat 03.09.2026, 24/0 tintire + 20/4 control negativ (a picat cum trebuia) |
| — | `PACK_SERII_NUMERE` | package (spec+body) | `wip13_2026_08_24_02_COMUN_PACK_SERII_NUMERE.sql.txt` (`D:\ROA\DATABASE\...`) | **APLICAT SI DAT INAPOI** 24.08.2026 — nu functiona (`ORA-24338`), rollback `docs\pack_serii_numere_inainte_fix.sql`; **nu se livreaza**, in afara perimetrului #13 |
| — | `VVANZARI_ARTICOLE` (view) | view | `docs\wip13_2026_09_02_01_COMUN_VVANZARI_ARTICOLE_EXT.sql.txt` | DA — aplicat si revizuit 02.09.2026 |
| — | `FACT_VFACTURI` (view) | view | `docs\wip13_2026_09_04_04_COMUN_FACT_VFACTURI.sql.txt` | DA — 04.09.2026 |
| — | `VANZARI` (coloane audit modificare) | DDL (`ALTER TABLE`) | `docs\wip13_2026_09_04_03_COMUN_VANZARI_AUDIT_MODIF.sql.txt` | DA — 04.09.2026, idempotent |
| — | `VANZARI_DETALII_TEMP` + optiune `RF_CONT_ART_FARA_POL` | DDL + config (`OPTIUNI`) | `docs\wip13_2026_09_08_01_COMUN_VANZARI_DETALII_TEMP_CONT_VENIT.sql.txt` | DA — 08.09.2026, idempotent |
| — | `CRM_POLITICI_PRET_ART.PROC_TVAV` (curatenie cote) | date (curatenie productie) | `docs\wip13_2026_09_05_03_COMUN_POLITICI_COTE_TVA.sql.txt` | **DOAR pe `MARIUSM_AUTO`** — "PREGATIT PENTRU PRODUCTIE, NEAPLICAT acolo"; scriptul e idempotent (`MERGE`), il aplica Marius cand decide |
| — | `CONFIG_GRUPUTIL_ANALITICE` (simboluri analitice) | date (nomenclator) | `docs\wip13_2026_09_07_03_COMUN_CONFIG_GRUPUTIL_ANALITICE.sql.txt` | DA pe `MARIUSM_AUTO` — **alegerea simbolului (12/1) NU e confirmata de Marius ca decizie contabila**, doar suficienta sa deblocheze scrierea in test |
Rollback-uri disponibile pe disc (export „inainte", `CREATE OR REPLACE`): pentru `PACK_FACTURARE`
seria completa `docs\pack_facturare_inainte_*.sql` (8 exporturi succesive, cate unul per hunk major),
`PACK_CONTAFIN`: `docs\pack_contafin_inainte_s9_6.sql` / `_s9_7.sql`, `FACT_VFACTURI`:
`docs\fact_vfacturi_inainte_s14.sql`, `PACK_SERII_NUMERE`: `docs\pack_serii_numere_inainte_fix.sql`.
## Doar test — nu se livreaza
**COMUN — 526 fisiere** sub `COMUN\utile\Teste\` (teste `.prg`/`.ps1` noi sau modificate + loguri
de rulare `*_log_*.txt`/`out\*.log`, artefacte de rulare repetata a suitei). Lista completa:
`git diff --name-only main...plan13-s2 -- . | grep '/Teste/'`, din `COMUN`.
**Scripturi Oracle doar de test/fixturi** (nu intra in livrarea catre clienti — insereaza date de
proba sau nomenclator de dezvoltare):
| Fisier | Ce insereaza |
|---|---|
| `wip13_2026_09_01_01_COMUN_CURSURI_TEST.sql.txt` | curs valutar EUR lipsa, doar ca sa deblocheze un test de contract pe `MARIUSM_AUTO` |
| `docs\wip13_2026_09_05_02_COMUN_AVIZE_TEST.sql.txt` | 30 avize de proba, seria `AVZ13` |
| `docs\wip13_2026_09_05_04_COMUN_FIXTURI_TEST.sql.txt` | reinviere documente-martor + resetare consum comenzi de test |
| `docs\wip13_2026_09_07_01_COMUN_AVIZE_TEST_v2.sql.txt` | regenerare rezervor `AVZ13` (v2, dupa epuizare) |
| `docs\wip13_2026_09_07_02_COMUN_FIXTURA_1130_S8B.sql.txt` | reinviere martor 1130 + document nou de test `S8B/1` |
## Datorii deschise
Sursa: `docs\handoff_plan13_executie_continua.md` §7 (35 de intrari, linia 730) + §3 din
`docs\handoff_reluare_13_b13.md` (4 intrari, blocul cel mai recent). Ambele fisiere sterse in
curatenia de inchidere a etapei (09.09.2026) — textul integral, cu detaliu pe fiecare intrare, ramane
recuperabil din `git log -p -- docs/handoff_plan13_executie_continua.md` si
`git log -p -- docs/handoff_reluare_13_b13.md`. Verificate pe `git log` la momentul scrierii — **nu
s-a gasit nicio datorie din aceste doua liste deja inchisa printr-un commit ulterior nemarcat**;
toate raman asa cum au fost lasate la predare. Lista completa de mai jos pastreaza titlu + stare
pentru fiecare intrare (numerotarea urmeaza sursa originala):
**Etapa 6 (35 intrari, `handoff_plan13_executie_continua.md` §7):**
1. `probe_totaluri.prg` — 33 fail preexistente (acces direct pe controale in container, fara `ObjExists`). Deschisa, alt story.
2. `test_s3_init_antet` — 44 fail identice (aviz, controale client/sold). Deschisa, posibil defect real de produs, neinvestigat.
3. `TabIndex` containere (decizia 5) — la data scrierii, nedecis. **INCHISA ulterior 06.09.2026**, vezi tabelul de decizii de mai sus.
4. Log emitere (`test_s3b_emitere_*`) arata MODUL de incasare, nu `.Value`-ul radio-group-ului. Deschisa, retus cosmetic.
5. 3 numere de incasare (Chitanta/Bon fiscal/POS) posibil ramase alocate fara document, dupa eroare `nid_vanzare`. Deschisa, neverificat direct in Oracle.
6. `nota_executie_s4e.md` §2.2 continea o caracterizare gresita despre referintele `Thisform.txtCodmat` etc. — **CORECTATA** la S4e-0, nu mai e datorie (doar avertisment pentru cine citea varianta veche).
7. S4-4 Rol A, proba manuala UI a emiterii reale. Deschisa — Marius nu poate testa manual; paritate 18/0 e singura dovada automata.
8. S4-4 cazul multi-aviz real. Deschisa — in `MARIUSM_AUTO` nu exista factura reala din mai multe avize simultan, doar combinatie construita pentru test.
9. S4-4 concurenta (doi operatori pe acelasi document). Deschisa — netestabil headless cu harnessul existent, risc acceptat si consemnat.
10. S4-4 `V_LISTAID` gol/NULL la `exista_ramas_avize_lista`. Deschisa, comportament mostenit, nu introdus la S4-4.
11. `do_sterge` (`frm_facturare_articole2`) eroare de alias la stergere pe tipurile `llCautareInLinie` — **INCHISA (S4-5, 31.08.2026)**, garda `If Used(lcCursor)`, test 10/10.
12. S4e-3 proba manuala UI (linie libera adaugata prin `combosql_cautare`). Deschisa — headless 62/0/0 e singura dovada automata.
13. S4e-3 descoperirea ca S4e-1 pare deja satisfacut (`cursor_preturi_call` neconditionat de tip) — confirmata doar pe cod, nu prin proba manuala UI.
14. S4e-3 acoperire partiala pe grupul de avize (testat doar tipul `21` din `21,25,28,42,47`). Deschisa, risc mic (aceeasi ramura in `do_verifica_articol`) dar netestat exhaustiv.
15. S4e-4 cursoare (`crsfactura`/`crsarticole`) construite programatic in test, nu incarcate prin fluxul real de UI. Deschisa.
16. S4e-5 tip 4 neexercitat pe date — 0 avize cu `ramas>0` in `MARIUSM_AUTO`, testul trece „verde" fara sa fi declansat dialogul (pass vacuu). Dovada ramane structurala pe cod.
17. S4e-5 dialogul real — doar `mock_amessagebox` testat, nu comportamentul dialogului de productie. Deschisa.
18. `nota_executie_s4e.md` §S4e-5 continea o caracterizare gresita a ramurii tip 4 (`Calculate Sum` local vs. apel Oracle real) — **CORECTATA** 31.08.2026.
19. „Alege selectiv din sursa" — poveste separata, neinceputa (decizia 9/13, varianta 3 aleasa la S4b). Vezi `docs\propunere_s4b_bara_butoane.md` §3.
20. Coloana `cSerie` ramane vizibila pe v2 la tipurile de contract (2/6/52) — pe v1 era eliminata. Deschisa, in afara perimetrului S4b (Marius a ales varianta 3 fara extindere).
21. `do_adauga_tot` e cod mort pe v2 (nicio referinta in afara de `PROCEDURE` si `*m:`). De stabilit candva daca se sterge sau se reconecteaza.
22. S4c „procent editabil" — s-a livrat procentul CALCULAT/needitabil (decizia Marius 31.08.2026), nu editabil cum cerea planul initial. Reluare ieftina: campul `procdisc` exista deja, lipseste doar cablajul `LostFocus` cu `tip=1`.
23. S4c `Column14.ReadOnly` confirmat doar STATIC (diff pe binar) — gridul nu se materializeaza headless, deci nu s-a verificat la rulare.
24. S4c proba manuala UI a editarii discountului in grid. Deschisa — test 26/0/0 e singura dovada automata.
25. S8 invariantul 48/49 (custodie): `IN_STOC` trebuie sa fie `0` prin constructie pe aceste tipuri, indiferent de nomenclator. Constrangere separata de tratat explicit la implementarea S8 (daca se redeschide).
26. S8 relistarea NU ameninta premisa „interval scurt" a deciziei 1 (corectie Marius 31.08.2026) — relistarea nu reemite si nu atinge stocul. Grija reala e alta: retiparirea unei facturi cu cote mixte dupa decizia 59 arata N randuri de discount in loc de unul (doar prezentare, nu stoc).
27. S8 varianta (2) (decizia 1) trebuie DECLARATA explicit ca aproximatie asumata, nu doar tacuta in cod.
28. Regula de discount NU se versioneaza (decizia 2, **INCHISA 31.08.2026**) — tiparire uniforma dupa regula noua pe toate documentele, inclusiv cele vechi; nu se scrie ramura „daca documentul e mai vechi de X".
29. Decizia 3 (`PROC_TVAV`) putea fi FARA OBIECT — intrebare a lui Marius ramasa fara raspuns explicit la predare: ce inseamna „reemitere" si daca utilizatorul alege el cotele TVA pe acea cale. Vezi #31 (partial consumata ulterior).
30. S8 are doua surse diferite intentionat: `IN_STOC` din nomenclatorul curent (fara istoric stocat), `PROC_TVAV` din documentul original (istoric stocat pe `VANZARI_DETALII`). Nu se „armonizeaza" — regula: unde istoricul exista se foloseste, unde nu, se accepta aproximatia explicit.
31. Datoria 29 **CONSUMATA partial** — Marius a raspuns ce inseamna „reemitere" (copiere sau modificare). Ramane doar verificarea la S10: exista vreo cale unde cota se deriva tacit, fara utilizator si fara copiere/modificare?
32. S3c-4 tinta 2 (`ferestre_contracte.vc2`, ROACONTRACTE) **NEIMPLEMENTATA deliberat** — produs separat, cu semnaturi vechi (`facturare_contracte`, `factureaza`, `oDateFactura.Init`); se face dupa sincronizarea S3c-1/2/3 in acel produs, ca parte a S3c-5. Diff deja scris: `docs\propunere_s3c_4_apelanti_reali.md` §3.2.
33. S3c-4 `do_factura` nu e exercitata pe calea reala de UI (doar mecanismul, testat 18/0, nu clicul din formularul de comenzi). Deschisa, risc mic prin constructie (acelasi cursor `crsComenzi`).
34. Comportamentul VFP la un argument in plus fata de `LPARAMETERS` — neverificat empiric, a contat pentru decizia de a amana tinta 2 (datoria 32). Cine reia are nevoie de un `.prg` de proba mic care sa stabileasca daca da eroare sau ignora tacit.
35. `oDateFactura.Init` (`ofacturare_comun.prg:234`) nu executa nimic din initializare cand e apelat `(0, 0)` (`If !Empty(tnIdSet)`) — capcana pentru cine scrie teste pe `Init`: un `CREATEOBJECT` cu `tnIdSet=0` trece „verde" fara sa testeze nimic. A costat un fals-negativ la S3c-4.
**Bloc b13 (4 intrari, `handoff_reluare_13_b13.md` §3, renumerotate dupa blocul b13)**:
1. Ramura S4g pentru articole fara politica de pret (contract cu ARTICOLE, `CTR_ARTICOLE.ID_POL_ART` gol) — cale de productie gasita in cod (`PACK_FACTURARE.cursor_contract` ramura 1 + `do_urmator2` + `do_adauga_articol(.T.,.T.)` -> `deriva_cont_venit_fara_pol`), **zero cazuri in date** `MARIUSM_AUTO` (un singur rand `CTR_ARTICOLE` ar produce combinatia, si nu a fost niciodata facturat). Proba propusa (caz (d) RPC sau harness end-to-end), neexecutata.
2. `CONTRACT "Too many columns."` — cauza nedovedita, masurat 83 coloane (sub pragul 254 al VFP). Proba scrisa (`probe_s13b_batchmode_direct.prg`), nerulata pe acest caz specific.
3. `diag01_env_serii` atarna 722s — preexistent, in afara perimetrului #13.
4. Documentele 1851-1857 raman scrise pe `MARIUSM_AUTO` (by-design al harnessurilor de proba, nu curatare de facut).
**Defect neinvestigat, aparut in afara celor doua liste**: `docs\erori_deschise.md` —
**`Alias 'CRSGESTIUNE' is not found.`** la parasirea combo-ului de gestiune, neinvestigat.
**Datorii noi, gasite dupa cele doua liste (09.09.2026, mutate aici din rapoartele sterse azi)**:
- Combo-urile de cautare articol (`raport_combo_lista_preturi.md`) pot afisa **duplicate false**
— acelasi articol, aceeasi lista de preturi, acelasi pret — multiplicate de fan-out-ul join-ului
`utilizatori_rol_intern` -> `politici_grupuri`. Fara `DISTINCT` in query (`rownum as id_c` il
blocheaza pe unul simplu, ar cere un wrapper exterior). Netratat, poveste separata.
- Acelasi loc: `codbare` nu e legat la cautare (`pSecondField` gol pe ambele combo-uri), desi
cursorul il contine si `combosql.KeyPress` are deja carligul. Sugestie, nu datorie.
**Amanate deliberat, poveste separata** (nu sunt „de facut", sunt inchise ca decizie): plafonul Rol B
tip `23` (decizia 7), tipul `41` in `llCautareInLinie` (decizia 8), „alege selectiv din sursa" pe
v2 (datoria 19), S3c-4 tinta 2 + S3c-5 (ROACONTRACTE, alt produs — cross-sync amanata motivat),
`do_adauga_tot` cod mort pe v2 (de stabilit candva daca se sterge).
**S13 (inchidere de etapa: diff, review, changelog, documentatie finala)** — marcata **NEINCEPUT**
in `docs\plan_13_executie.md:62`. E ultima poveste din lant si nu are cod propriu — pare sa fie
tocmai livrarea pe care acest fisier o pregateste.
## Necomis pe disc acum
**ROAFACTURARE** (`git status --short`, verificat la ora scrierii acestui inventar):
- `M Clase\ofundal_facturare.vc2` — modificare **necomisa**, diferita de commit-ul `e371a08` (deja
in istoric). Diff-ul curent contine: **reparatia** sirului `xmenu()` din `Cw10.do_actiune`: literalul
avea 295 de caractere, peste limita VFP de 255, deci metoda nu compila si dadea la rulare
„Command contains unrecognized phrase/keyword." la linia 3. Sirul e spart in doua atribuiri;
optiunile si numerele de bara raman identice. Write-back facut, fidelity check OK, metoda
compileaza curat. **Plus** obiectele aduse in text de `git_sync.ps1` din binarul salvat anterior
din IDE (`Page2.Pict_liniuta1`, `Cw10.Top` 233->250, coloanele de grid `cCodFiscal`, `cIdPart`,
`cNumeListaPreturi`, `cPTVA`, `But_attach1`) — nu sunt scrise de mana si nu sunt o schimbare
neintentionata.
- `?? docs\erori_deschise.md` — fisier nou, netracked, 1 eroare inregistrata (`CRSGESTIUNE`,
neinvestigata, vezi „Datorii deschise").
**COMUN**: `git status --short` **curat** — nimic necomis.
Acest fisier de inventar (`docs\livrare_13.md`) e el insusi nescris in git la momentul livrarii lui.

File diff suppressed because it is too large Load Diff

146
docs/plan_13_executie.md Normal file
View File

@@ -0,0 +1,146 @@
*!* 03.09.2026
*!* agent documentatie (orchestrat de team-lead, sesiune plan13-s2)
*!* Coloana "Stare" era stale din 27.08.2026 (marca NEINCEPUT povesti de mult inchise si probate).
*!* Adusa la zi pe baza `docs\handoff_plan13_executie_continua.md` (fisierul de stare viu — ramane
*!* sursa de adevar, se citeste ACOLO intai). Rezolvata si capcana de numerotare de mai jos, prin
*!* sectiunea separata "Firul geometrie (designer editabil)".
# Plan #13 - executie pe stories
Tabel de urmarire pentru executia planului `docs\plan_13_unificare_formular_facturare.md`,
conform metodei de executie (liniile 1954-2015 din plan, decizia 55). Nu e reproiectare - fiecare
story are proiectarea proprie in plan si/sau in `docs\cercetare\`. Actualizat dupa fiecare story
inchisa. **Sursa de adevar pentru stare e `docs\handoff_plan13_executie_continua.md`** - acest
tabel e o rasfrangere a lui, tinuta pentru titluri scurte, fisiere atinse si dependente.
**Capcana de numerotare — REZOLVATA (03.09.2026).** Numele `S6`/`S7`/`S8`/`S8b`/`S8c` din acest
tabel se refera strict la povestile din acest plan ("Test pe flux real", "Actiunea si garzile",
"Incarcarea documentului in formular" etc.). Un lant separat, comis tot pe `plan13-s2`, a folosit
ACELEASI nume pentru firul "designer editabil" (cererea separata a lui Marius,
`docs\sinteza_designer_editabil.md`). Cele doua nu au nicio legatura. Firul de geometrie are acum
randuri proprii, cu prefixul `S8-geometrie-*`, in sectiunea **„Firul geometrie (designer
editabil)"** de sub tabelul principal — nu se mai confunda cu povestile de aici.
## Vocabular de stare
- **INCHISA SI PROBATA** — cod comis + proba automata verde.
- **INCHISA FARA COD** — inchisa prin decizia lui Marius, fara implementare (cu decizia citata).
- **LIVRATA, PROBA MANUALA DATORIE** — codul e comis si probat automat, dar criteriul oficial
„gata" e o proba manuala UI pe care Marius n-o poate face acum (mandatul din §1 al handoff-ului).
- **PARTIALA** — ce s-a livrat si ce a ramas, explicit.
- **NEINCEPUT** — chiar neinceput.
## Tabel stories
| Story | Nume scurt | Fisiere atinse (din plan) | Depinde de | Stare | Nota de executie |
|---|---|---|---|---|---|
| S1 | Inventar campuri + alegerea bazei | (inventar, fara cod) | - | **INCHISA FARA COD** — aprobata de Marius, 09.08.2026 | `docs\S1_inventar_campuri_formular_unificat.md` |
| S2 | O singura procedura `factureaza` | `COMUN\programe\ofacturare.prg`, `Clase\ofundal_facturare.vc2` (decizia 67 + completare + corectie review) | S1 | **LIVRATA, PROBA MANUALA DATORIE** — comisa pe `plan13-s2`, 21.08.2026; netestata manual la momentul comiterii (decizia lui Marius). De atunci exercitata cu succes indirect, prin documente reale emise care trec prin `factureaza`, la S6 (`docs\raport_s6_flux_real.md`) si S9 (`docs\raport_s9_7_oracle.md`, `docs\raport_s9_7_vfp_proba.md`) — proba manuala UI dedicata ramane datorie deschisa | `docs\nota_executie_s2.md`, `docs\raport_s2_implementare.md`, `docs\cercetare\s2_factureaza_unificare.md`, `docs\review_s2.md`, `docs\handoff_13_s2.md` |
| S3 | Portare logica antet in formularul unificat (spart in S3-1..S3-4, decizia 68) | `ofacturare.vc2`, `ofacturare_antet.prg` (nou, S3-2), `ofacturare.prg` (S3-4) | S2 | **LIVRATA, PROBA MANUALA DATORIE** — S3-1..S3-4d COMISE pe `plan13-s2` (COMUN `89dd30a`, `27bdf58`, `c269fdf`, `4f6af10`, `359f478`, `02df236`, `e538590`, `409ce90`, `e96fd3e`); Q8 (numarul ars) COMISA, doar pe calea unificata. Implementarea e completa; proba manuala pe tip 1 a fost refuzata de Marius (22.08.2026), iar `factureaza` nu e apelabila headless | `docs\nota_executie_s3.md`, rapoartele si review-urile S3-1/S3-2/S3-3, `docs\raport_b1_rerulare_s3_3.md`, `docs\raport_q8_numar_ars.md`, `docs\review_q8_numar_ars.md`, `docs\raport_adaptare_paritate_q8.md`, `docs\handoff_13_s3.md` |
| S3b | Sectiune pliata: alte date + analitice | `caut_ora.vc2`, `ferestre_cere_date.vc2`, `ofacturare.vc2`, `omodificari.vc2` | S3 | **PARTIALA** — implementata aproape integral, comisa pe `plan13-s2`. Criteriul „gata" din 5 puncte (`docs\cercetare\s3b_alte_date_analitice.md` §10): **(1) paritate emitere pe 4 tipuri de incasare — TRECUT**, `test_s3b_emitere_paritate_2.prg` PASS 20/0 (`docs\raport_s3b_criteriu1_pasat.md`); **(2) non-alocare la toggle de pliere — TRECUT**, `test_s3b_incasare_nealocare.prg` PASS 14/0 (`docs\handoff_13_s3b_asezare.md`); **(3) blocarea grupului de incasare pe document emis — CADUC pentru S3b, amanat la S8c** (decizia 25); **(4) analitice read-only — CADUC, rasturnat de decizia 72** (raman editabile); **(5) paritate delegat/transport/adresa/text — FACUT**, cu o inconsecventa vizuala minora ramasa (`Ct_clb_delegat`/`Ct_clb_masina` editabile pe proforma, unde calea veche le scotea) [^1] | `docs\handoff_13_s3b_varianta_b.md`, `docs\raport_s3b_varianta_b.md`, `docs\handoff_13_s3b_reasezare_grila.md`, `docs\handoff_13_s3b_asezare.md`, `docs\cercetare\s3b_alte_date_analitice.md` §10, `docs\cercetare\s3b_bloc5_ce_ramane.md` |
| S3c | Sursa ca parametru, nu ca global | `ferestre_contracte.vc2`, `ocomenzi.vc2`, `ofacturare.prg`, `ofacturare_comun.prg`, `ofundal_facturare.vc2`, `oproceduri_facturare.prg` | S2. Atinge cod comun intregii suite - nu se face impreuna cu alta modificare | **INCHISA SI PROBATA (S3c-1/2/3/4-tinta1)** + **INCHISA FARA COD (S3c-4-tinta2 si S3c-5)**, 31.08.2026 — S3c-1/S3c-2/S3c-3: `toSursa` la coada in `Init`/`factureaza`/`facturare_contracte`/`facturare_comenzi`, test 55/0, comis 27.08.2026. S3c-4 tinta 1 (`ocomenzi.vc2:do_factura`): LIVRATA, test 18/0. S3c-4 tinta 2 (ROACONTRACTE) si S3c-5 (sincronizare cross-produs, 6 produse): mutate intr-o **poveste separata prin decizia lui Marius**, nu se mai ating la planul #13 (detaliu in §9 al handoff-ului) | `docs\nota_executie_s3c.md`, `docs\raport_s3c_1_implementare.md`, `docs\handoff_13_s4_s3c.md`, `docs\propunere_s3c_4_apelanti_reali.md`, `docs\propunere_s3c_5_sincronizare.md` |
| S4 | Cautarea articolelor pe server, in linie | `ofacturare.prg`, `ofacturare.vc2` | S3 | **PARTIALA** — S4-1 (Oracle articole server) INCHISA SI PROBATA, 23.08.2026 (identic byte-cu-byte pe filtru NULL fata de baseline); S4-2 (functii Oracle remainder/plafon) INCHISA SI PROBATA, 24.08.2026 (114 randuri numai adaugate in `PACK_FACTURARE`, zero stergeri); S4-3 (cautare in linie, `ofacturare_cautare.prg` nou) INCHISA SI PROBATA, 27.08.2026, teste 41/0, 50/0, 39/0; S4-4 (decuplare Rol A, decizia 6 varianta C) LIVRATA, PROBA MANUALA DATORIE, test paritate 18/0; **S4-5 (Rol B) PARTIALA** — garda `If Used(lcCursor)` in `do_sterge` LIVRATA (repara bug LIVE preexistent, test 10/10), dar plafonul Rol B pe tipul `23` a fost implementat, testat si **REVENIT** in aceeasi sesiune (garda structural moarta); ramane deschis prin deciziile 7 si 8 din handoff §2 (nedecise) [^2] | `docs\raport_s4_1_aplicare.md`, `docs\scripturi_oracle_13.md`, `docs\cercetare\s4_cautare_articole_server.md`, `docs\raport_s4_3_implementare.md`, `docs\raport_s4_4_vfp_implementare.md`, `docs\raport_s4_4_oracle_varianta_c.md`, `docs\propunere_s4_5_rol_b.md`, `docs\raport_s4_5_implementare.md` |
| S4b | Bara de butoane si meniul de adaugare | `ofacturare.vc2` | S4 | **INCHISA FARA COD** — decizia 9 varianta 3 (31.08.2026): mecanismul v2 (`but_incarca_articole` -> `do_incarca_articole`) e pastrat neatins; alegerea selectiva devine poveste separata (datoria 19) | `docs\propunere_s4b_bara_butoane.md` |
| S4c | Discount pe linie, mutat din dialog in grid | `ofacturare.vc2`, `ofacturare_comun.prg`, `oproceduri_facturare.prg`, `xmlefactura.prg` | S4 | **LIVRATA, PROBA MANUALA DATORIE** — handlere `LostFocus` pe cele doua campuri de discount + `recalc_discount_linie` nou, camp `procdisc` nou; repara si un defect preexistent (`cProcentDiscount` editabila dar legata la camp inexistent); test 26/0/0, write-back verificat independent | `docs\propunere_s4c_discount_grid.md` |
| S4d | Data cursului valutar, numai cand are sens | `ofacturare.prg`, `ofacturare.vc2`, `ofacturare_comun.prg` | S4 | **LIVRATA, PROBA MANUALA DATORIE** — metoda noua `do_actualizeaza_vizibilitate_curs`, apelata din 3 puncte (sink comun `do_calculeaza_totaluri`, `Init`, `do_incarca_articole`); test 32/0/0, inclusiv traseul complet al erorii Oracle `-20005`, write-back verificat independent | - |
| S4e | Lista de preturi si pe factura din comanda | `ofacturare.prg`, `ofacturare.vc2` | S4-1, S4-3 | **INCHISA SI PROBATA** — S4e-1 acoperit de S4-3 (prin `do_adauga_articol_cautat`, acceptat de Marius ca solutie finala, nu se redeschide); S4e-2 GATA (decizia 44); S4e-0 GATA 31.08.2026 (deblocarea `do_adauga_articol`); S4e-3 GATA 31.08.2026 (validare cantitate/stoc, tipurile `3,4,21,25,28,42,47`, test 62/0/0); S4e-4 GATA (proba de neregresie a registrului, fara cod nou, test 15/0/0); S4e-5 GATA (probe repetate pe tip 4/21 + captura dialog, test 37/0/0) | `docs\nota_executie_s4e.md`, `docs\recon_s4e_stare_reala.md`, `docs\raport_s4e_2_implementare.md`, `docs\raport_s4e_0_implementare.md`, `docs\raport_s4e_3_implementare.md`, `docs\raport_s4e_4_implementare.md` |
| S4f | Returul in formularul unificat | `ofacturare.vc2` | S2, S4e | **INCHISA** (08.09.2026) — stare veche din acest tabel depasita, vezi `docs\handoff_plan13_executie_continua.md` §4.0; `do_adauga_articol_cautat` deleaga returul (tip 8/9/41) la `do_adauga_articol` | `docs\raport_s4f_retur.md` |
| S4g | Adaugare articole la modificarea documentului | `ofacturare.vc2`, `ofacturare_comun.prg` | S4e + deciziile de mai sus. Nu blocheaza etapa I | **INCHISA** (08.09.2026) — stare veche din acest tabel depasita, vezi `docs\handoff_plan13_executie_continua.md` §4.0; decizia 34/optiunea B aplicata (Oracle `FACT-032` + VFP `deriva_cont_venit_fara_pol`) | `docs\raport_s4g_oracle.md`, `docs\raport_s4g_vfp.md` |
| S5 | Acoperirea tuturor tipurilor de document | `ofacturare.prg` | S4 | **INCHISA SI PROBATA**, 01.09.2026 — cod COMUN `1f89c18` (v2), teste conform §6 al handoff-ului | `docs\propunere_s5_acoperire_tipuri.md`, `docs\matrice_goluri_s5_form2.md`, `docs\verificare_s5_tabel_sursa.md` |
| S5b | Proforma si copierea pe formularul unificat | `ofacturare.prg`, `ofacturare.vc2`, `ofacturare_comun.vc2`, `oproceduri_facturare.prg` | S5 | **INCHISA SI PROBATA**, 01.09.2026 — COMUN `76e7398` (emiterea proformei pe formularul unificat, v2) + `d6cae36` (v1, calea de copiere); teste `080321f` (UI, 20/0/2) + testul de rutare (22/0/0). Ramane o singura datorie neblocanta: clauza de gestiune probata prin paritate, nu prin `id_gestiune > 0` (M3) | `docs\propunere_s5b_proforma_copiere.md`, `docs\raport_s5b_implementare.md`, `docs\raport_verificare_s5b_disc.md` |
| S5c | Factura din proforma (decizia 43) | `cmd_butoane.vc2`, `ocomenzi.vc2`, `ofacturare.prg`, `ofacturare.vc2`, `ofacturare_comun.prg`, `ofacturare_comun.vc2`, `ofacturare_editare.prg`, `oinit_optiuni.prg`, `xmlefactura.prg` | S5b | **INCHISA SI PROBATA**, 01.09.2026 — proba capat-la-capat prin UI RULATA, 20/0/2; vezi si golul de produs semnalat separat (nu al lui S5c) in handoff §4.0a | `docs\raport_s5c_test_ui.md` |
| S6 | Test pe flux real - formularul unificat | (test, nu cod nou) | S5 | **PARTIALA** — INCHIS 02.09.2026 cu proba partiala: **4 din 7** surse au acum comparatie v1/v2 pe document real emis (0/7 la pornire), **7 defecte de productie gasite, 6 reparate si comise** (3 cu proba doar partiala) | `docs\raport_s6_flux_real.md`, `docs\propunere_s6_test_flux_real.md`, `docs\raport_s6_1_lista_preturi.md`, `docs\raport_s6_3_aviz_retur.md`, `docs\raport_s6_4_transfer.md`, `docs\raport_s6_5_valuta.md`, `docs\raport_s6_6_listare_intrare.md` |
| S7 | Actiunea si garzile | `ofacturare_comun.vc2`, `ofacturare_editare.prg` | S6, si inchiderea perimetrului cu #6 | **INCHISA SI PROBATA**, 02.09.2026 — cod revizuit integral si comis, COMUN `70eddfa`; cele 5 cerinte de review inchise (cens octeti 13=13, metoda cu toate cele 3 inregistrari). Metoda ramane schela, fara apelant, pana cand S8 cableaza a treia optiune de meniu | `docs\propunere_s7_actiune_garzi.md`, `docs\raport_s7_actiune_garzi.md`, `docs\verif_s7_doua_fapte.md` |
| S8 | Incarcarea documentului in formular | `ferestre_cere_date.vc2`, `ofacturare.prg`, `ofacturare.vc2`, `ofacturare_comun.prg`, `ofacturare_editare.prg`, `ofacturare_stoc.prg` | S7 | **INCHISA SI PROBATA**, 02.09.2026 — pasii 1-8 GATA, proba finala `test_s8_editare_incarcare.prg` 307/0, CINCI rulari consecutive identice verificate pe md5/diff. Decizia 1 (`IN_STOC`) INCHISA de Marius (handoff §2). Defect nou, neblocant, in afara perimetrului: **G10 incalcat pentru avize** | `docs\propunere_s8_incarcare.md`, `docs\raport_s8_pas7b_proba.md`, `docs\raport_s8_defect5_incasat.md`, `docs\nota_s8q_aplicare_etapa6.md` |
| S8b | Rutarea scrierii dupa schimbarea tipului | `ferestre_cere_date.vc2`, `ofacturare.vc2` | S8 | **INCHISA SI PROBATA**, 03.09.2026 — rutarea scrierii la editare, COMUN `0a41327`, ROAF `e5530fe`; proba 54/0, trei rulari consecutive identice, toate cele patru rute exercitate pe traseul real | `docs\raport_s8b_implementare.md` |
| S8c | Buton comutator antet + salvare separata | `lb_tx.vc2`, `rulaje.vc2` | S8 | **NEINCEPUT** — mentionata in handoff doar ca alternativa paralela posibila cu S9 (mai mica, poate rula in paralel), fara cod scris pana acum | - |
| S9 | Stergere + reemitere intr-o tranzactie, `ID_FACT` pastrat | `oscrie_in_fisiere.prg` | S8b | **PARTIALA — IN LUCRU (03.09.2026)** — S9-1..S9-7 INCHISE SI PROBATE (S9-6 `PACK_CONTAFIN.nIdFactFortat` aplicat/probat pe `MARIUSM_AUTO`; S9-7 cablaj VFP aplicat si probat, sweep P1-P6/CN1-CN4 verde, COMUN `658acc6`/`cc79734`, ROAF `5886b3f`/`119474e`). **S9-8** (testul de flux: reemitere identica + cu modificari, pe aviz/comanda/contract/ELSE) **IN LUCRU** in aceasta sesiune. Datorii neinchise: proba vizuala UI dupa scoaterea garzii, neregresia pe ROACONTRACTE pentru scriptul #9 | `docs\propunere_s9_stergere_reemitere.md`, `docs\propunere_s9_6_pack_contafin.md`, `docs\propunere_s9_7_cablare_idfact.md`, `docs\raport_s9_7_oracle.md`, `docs\raport_s9_7_vfp_aplicare.md`, `docs\raport_s9_7_vfp_proba.md` |
| S10 | Pretul care nu trebuie re-derivat | `ofacturare.prg`, `ofacturare.vc2`, `ofacturare_editare.prg` | S9 | **NEINCEPUT** — decizia 3 (`PROC_TVAV` ca parametru) care il blocheaza e INCHISA (handoff §2), deci pornirea nu mai e conditionata de o decizie in asteptare | - |
| S11 | Legaturile ramase pe `ID_VANZARE` | (verificare + cod punctual, vezi plan) | S9 | **NEINCEPUT** | - |
| S12 | Test pe flux real - regenerarea | (test, nu cod nou) | S10, S11 | **INCHISA** - criteriul (toate cele patru familii trec scenariul B) atins prin sweep 4/4 pe `test_s9_8_flux.prg`, 04.09.2026 | `docs\raport_s12_inchidere.md` |
| S14 | Audit: creare, modificare, stergere document | `ofacturare_editare.prg` | S9 | **INCHISA**, 04.09.2026 — criteriul din plan bifat pe date reale (documentele reemise 1195/1196); vezi datoriile deschise in raport | `docs\raport_s14_oracle.md`, `docs\raport_s14_vfp_transport.md`, `docs\raport_s14_vfp_grid.md`, `docs\raport_s14_inchidere.md` |
| S13 | Diff, review, changelog, documentatie | toate cele de mai sus (inchidere de etapa) | toate stories anterioare - livrare finala | **NEINCEPUT** | - |
[^1]: In plus, nedus in niciun raport separat pana acum: `lb_total_mod` e cod viu dar inert
(`docs\raport_s3b_lb_total_mod.md`, recomandat pentru alta poveste); euristica „205/210 dezalinieri
de coloana" din `verifica_grila.ps1` ramane neinvestigata.
[^2]: Tipul `41` (S4-5) ramane definitiv in afara perimetrului curent — fix-ul necesar e proiectat
in `docs\propunere_s4_5_rol_b.md` §7, dar nefacut, ca sa nu arate tacit stoc din gestiuni gresite.
## Firul geometrie (designer editabil)
Firul separat, comis tot pe `plan13-s2`, prin care Marius poate aranja controalele formularului cu
mana in designerul VFP (`docs\sinteza_designer_editabil.md`). A folosit intern aceleasi litere
`S6`/`S7`/`S8b`/`S8c` in documentele lui proprii (`docs\handoff_s6_designer_editabil.md`,
`docs\handoff_s7_containere.md`, `docs\handoff_s8b_transa_a_si_ghid.md`,
`docs\handoff_s8c_transa_c.md`) si, intr-o etapa ulterioara, etichetele `S8h`-`S8p`/Etapa 6 — **fara
nicio legatura** cu povestile din tabelul de mai sus. Randurile de aici sunt rasfrangerea acelei
etape ulterioare (sursa: `docs\handoff_plan13_executie_continua.md` §3); tot firul e **INCHIS SI
COMIS**.
| Bloc | Ce | Stare | Commit |
|---|---|---|---|
| S8-geometrie-h..m | Ancorare nativa vs. geometrie din cod; bitul `Bottom(4)` scos de pe controalele asezate din cod | **INCHISA** | COMUN `e2eca46` |
| S8-geometrie-n | Test de geometrie mutat din scratchpad in arborele de teste | **INCHISA SI PROBATA**, 8/8 pasi | COMUN `4063277`, ROAF `20eb547` |
| S8-geometrie-p | Dependenta rupta reparata: `Clase\ofacturare_util.vc2` intrat in versionare + `SET CLASSLIB` in `roafacturare.prg` | **INCHISA** — fara ea, `plan13-s2` era rupt la checkout curat | ROAF `fe3eef8` |
| S8-geometrie-o | Proba de rezolutie 1366x768 + diagnosticul benzii de totaluri | **INCHISA SI PROBATA** — la 1366x768 maximizat, deficit 98px, preexistent, nu regresie | COMUN `261f547`, ROAF `6257df6`, `3b312a0` |
| Etapa 6 | 5 fisiere de test reparate (`ObjExists` facut recursiv in 3; substitutia `opt_incasat.Value` -> `seteaza_mod_incasare` aplicata doar pe siturile formei NOI) | **INCHISA SI PROBATA** — `test_s3b_emitere_paritate_2` 20/0, `test_s3b_emitere_reala` 1/0, `test_s3_controale` 20/0, zero aparitii ale erorii 1734 | COMUN `34a1f30` |
## Ordinea de executie rezultata din dependente
```
S1 (GATA)
-> S2 (GATA implementare, in asteptare review/aprobare)
-> S3c (paralel cu S3, atinge alt fisier comun - nu impreuna cu alta modificare)
-> S4e (STALE: aici in arbore ca "porneste dupa S2"; depinde de fapt de S4-1 si S4-3 -
vezi randul S4e din tabel si `docs\nota_executie_s4e.md` §6)
-> S4f (dupa S4e)
-> S3
-> S3b
-> S4 (proiecteaza intai punctul 2, neproiectat)
-> S4b
-> S4c
-> S4d
-> S4g (dupa S4e + deciziile anterioare; nu blocheaza etapa I)
-> S5
-> S5b
-> S5c
-> S6 (test pe flux real)
-> S7 (dupa inchiderea perimetrului cu #6)
-> S8 (varianta IN_STOC fixata in nota de executie S8, runda 17)
-> S8b
-> S9
-> S10
-> S11
-> S14
-> S12 (dupa S10 si S11)
-> S8c
-> S13 (inchidere finala, dupa toate)
```
Observatii pe ordine:
- S3c si S4e nu depind de S3/S4 - pot fi luate in paralel cu ramura S3, dar **un singur scriitor pe
fisier**: S3c atinge `ofacturare.prg`/`oproceduri_facturare.prg`, S2 le-a atins deja si le-a
eliberat (S2 e inchisa la nivel de implementare), deci nu se suprapun.
- S4g explicit "nu blocheaza etapa I" - poate aluneca dupa S6/S7 fara sa opreasca lantul principal.
- S8c e paralela cu S9 (ambele depind doar de S8), dar scrie in fisiere diferite (`lb_tx.vc2`,
`rulaje.vc2` vs `oscrie_in_fisiere.prg`) - fara conflict de scriitor.
- **Starea reala la 03.09.2026** (handoff §6): lantul e dus pana la S9 (S9-1..S9-7 inchise,
S9-8 in lucru); ordinea ramasa e **S10 -> S11 -> S12 -> S14 -> S13**, cu **S8c** paralela.
Actualizare 04.09.2026: **S12 inchisa** (`docs\raport_s12_inchidere.md`) - vezi §4.0 al
handoff-ului pentru starea curenta. **S14 inchisa** (`docs\raport_s14_inchidere.md`) - ramane
doar **S13** (inchiderea de etapa), cu **S8c** in continuare paralela.
## Cele 3 puncte de implementare ramase deschise — TOATE INCHISE (handoff §2, 31.08.2026)
Sursa initiala: `docs\handoff_13_formular_unificat.md`. Cele trei decizii ale lui Marius care le
inchid sunt in `docs\handoff_plan13_executie_continua.md` §2.
1. **Relistarea unei facturi vechi deja trimise** (decizia 62) — **INCHISA**: toate facturile se
tiparesc dupa regula noua, fara versionare dupa data. Facturile trimise la ANAF nu se mai pot
retrimite.
2. **`PROC_TVAV` ca parametru** (consecinta 3 a deciziei 54) — **INCHISA, DA devine parametru**:
caile de copiere si modificare pastreaza cota documentului original; blocheaza S10.
3. **De unde se reconstituie `IN_STOC`** — **INCHISA, varianta (2)**: valoarea curenta din
nomenclator, fara migrare DB si fara reconstituire din rulaje; blocase S8, acum livrat si probat.

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ si verificari proprii, ca sa nu se amestece.
| — | #8 — denormalizare VANZARI | **mediu** (scop extins 06.08) | `PACK_FACTURARE` + ambele view-uri (COMUN) + reparare date | **TERMINAT si COMIS** 07.08.2026 (r17990-r17993). Planul a fost sters la curatenie; istoricul e in `progres.md` |
| — | #7 — pret cu TVA pe linie | mic-mediu | doar VFP, gridul de articole din `frm_facturare_articole` | **TERMINAT** 08.08.2026 (changelog 2.11.14). Editarea flagului pe o factura **deja salvata** a fost mutata in #6. Planul a fost sters la curatenie; istoricul e in `progres.md` |
| — | #6 — editare factura emisa | mediu | `omodificari.vc2` (COMUN) + `PACK_FACTURARE` | **INCHIS** 20.08.2026 (r18026, changelog 2.11.15). Planul a fost sters la curatenie; istoricul — inclusiv cele 2 esecuri ramase pe „factura din aviz" din S8 — e in `progres.md` |
| 1 | [#13 — formular unificat + editare prin regenerare](plan_13_unificare_formular_facturare.md) | **mare** | `ofacturare.vc2` + `ofacturare.prg` + `PACK_FACTURARE` (COMUN) | **PROIECTAT INTEGRAL, cod neatins** (17 runde de proiectare, 11.08.2026). Etapele I si II sunt proiectate story cu story (S1-S14); raman testele (S6, S12) si inchiderea (S13). **65 de decizii luate**, niciuna deschisa. Perimetrul cu #6 e transat: **#13 incepe dupa terminarea lui #6** (decizia 30). Mockup: [`mockup_13_formular_unificat.html`](mockup_13_formular_unificat.html). Stare curenta: `handoff_13_formular_unificat.md` |
| 1 | [#13 — formular unificat + editare prin regenerare](plan_13_unificare_formular_facturare.md) | **mare** | `ofacturare.vc2` + `ofacturare.prg` + `PACK_FACTURARE` (COMUN) | **INCHIS ca plan (08.09.2026)**, pe branch `plan13-s2` (ROAFACTURARE + COMUN), nemerge-uit si netestat manual; ce ramane sunt datorii. Stare curenta: `handoff_reluare_13_b13.md`, `handoff_plan13_executie_continua.md` |
| — | [#12 — nomenclator ca sursa de pret](plan_12_nomenclator_ca_lista_preturi.md) | mediu | depinde de varianta | valabil, **amanat** |
| — | [#11 — integrare politici de preturi](plan_11_integrare_politici_preturi.md) | mare | COMUN + ROAPRETURI | valabil, **amanat** |
| — | [#10 — integrare contracte](plan_10_integrare_contracte.md) | mare | COMUN + ROACONTRACTE | valabil, **amanat** |

View File

@@ -1,10 +1,37 @@
# Progres implementare — ROAFACTURARE, punctele 6, 7, 8 (10, 11, 12 amanate)
# Progres implementare — ROAFACTURARE, punctele 6, 7, 8, 13 (10, 11, 12 amanate)
**Fisierul curent de stare.** Orice sesiune il citeste primul si il actualizeaza inainte sa se
incheie. Planurile (`plan_0*.md`) spun *ce* e de facut; acesta spune *unde s-a ajuns*. Istoricul
sesiunilor nu se pastreaza aici — doar starea la zi, deciziile, faptele stabilite si datoriile.
Ultima actualizare: **19.08.2026**.
Ultima actualizare: **21.08.2026**.
> **RUNDA 21.08.2026 — #13 S2 implementata si comisa pe branch.**
> Cerere Marius, aprobata azi: **fara teste manuale, commit pe branch, continuare in alta sesiune.**
>
> **Ce s-a schimbat**: `COMUN\programe\ofacturare.prg` — procedura duplicata `factureaza2` (fork mort
> din 2017, zero utilizatori reali) a fost stearsa; alegerea formularului se face acum in
> `factureaza`, dupa flagul `Private plFacturareNoua`. `Clase\ofundal_facturare.vc2` — butoane noi
> `Page2.Cw10` „Facturare (nou)" (4 optiuni: lista de preturi, contract, comanda, aviz) si
> `Page3.Cw5` „Avize (nou)" (4 optiuni), ambele seteaza `plFacturareNoua` si deschid
> `frm_facturare_articole2`.
>
> **Decizia 67** (Marius, 20.08.2026): intrare separata in meniu, nu comutator/dialog; ambele cai
> facturare vechi si noua raman accesibile simultan. Perimetru: vanzarile din stoc (`Cw5..Cw9` ->
> `initializeaza_vanzare_din_stoc`) **nu** trec prin `factureaza` — raman in afara #13.
>
> **NETESTAT MANUAL** — decizia explicita a lui Marius, 21.08.2026. Ramane datorie de verificat cel
> tarziu la S6 (test pe flux real, planificat dupa S5).
>
> **Commis pe branch `plan13-s2`** in ambele repo-uri (ROAFACTURARE + COMUN), **fara SVN** — SVN
> ramane neatins pana la aprobarea finala si testele manuale. Detalii si hash-uri:
> `docs\handoff_13_s2.md`.
>
> **Urmatoarea story: S3** — portarea logicii de antet in formularul unificat.
>
> **Capcana platita**: FoxBin2Prg ordoneaza obiectele si metodele **ASCII-lexicografic**, nu numeric —
> `Cw10` cade intre `Cw1` si `Cw2` in textul `.vc2`, nu dupa `Cw9`. De verificat cu `vfp_symbols.ps1`
> inainte de orice cautare pe pozitie in fisier.
> **RUNDA 19.08.2026 — butoanele de linie trec pe butoanele laterale ale grid-ului de articole.**
> Cerere Marius, executata si testata; **fara commit**.
@@ -2326,26 +2353,26 @@ Ramase: partea VFP a punctului 6 si intrarea de changelog pentru runda 6 (nu exi
livrare nu sunt surse de stare pentru ce e in Oracle. Starea se citeste din `VERSIUNE` +
`ALL_SOURCE`, prin diff, inainte de a re-aplica ceva.
- Ramane valabila interdictia: **nu se re-aplica `ff_2026_08_20_01`** — ar sterge punctul 6.
## Runda 6 punctul 6 + runda 7 (sincronizare) - 20.08.2026, seara
Starea completa, cu inventar pe `fisier:linie`, ce s-a stabilit si ce e in lucru:
**`docs\handoff_r6_r7_sincronizare.md`**. Pe scurt:
- **Punctul 6 (explicatie TVA in `frm_facturi`): TERMINAT**, write-back verificat prin
reconversie (0 linii diferenta, md5 identic), probat pe ecran de Marius de doua ori.
Filtrul final al listei: `id_jtva_coloana > 0 AND afisat > 0 AND jv = 1 AND cota_tva = <cota
liniei>`. Pe date (`vjtva_coloane`, `MARIUSM_AUTO`): `afisat = 0` sunt liniile de TVA,
`afisat = 1` bazele, `afisat = 2` neimpozabilele; `jv = 1` tine afara explicatiile de
achizitie, care altfel treceau de garda `FACT-029` fiindca au aceeasi cota.
- **Runda 7, in lucru**: sincronizarea articole-rulaje porneste doar din buton (se scoate
declansarea de la salvare, `omodificari.vc2:14484-14494`), si `AplicaModificareTrul`
(`ofacturare_editare.prg:924`) scrie si `pretv`/`tvav`, nu doar `pretvtva`.
**Perimetru fixat de Marius: doar preturi, nu si `valoarev`/`valtvav`/`valoarevcTVA`** -
sincronizarea e intre cantitate si preturi. Ramane de raspuns, ca fapt, cine recalculeaza
acele valori dupa sincronizare.
- **Nimic nu e comis** - decizia lui Marius e un singur commit, dupa ce sunt gata toate trei.
- `roafacturare.PJT`/`.PJX`/`.exe` apar modificate: zgomot din sesiunea lui de VFP, se lasa asa.
## Runda 6 punctul 6 + runda 7 (sincronizare) - 20.08.2026, seara
Starea completa, cu inventar pe `fisier:linie`, ce s-a stabilit si ce e in lucru:
**`docs\handoff_r6_r7_sincronizare.md`**. Pe scurt:
- **Punctul 6 (explicatie TVA in `frm_facturi`): TERMINAT**, write-back verificat prin
reconversie (0 linii diferenta, md5 identic), probat pe ecran de Marius de doua ori.
Filtrul final al listei: `id_jtva_coloana > 0 AND afisat > 0 AND jv = 1 AND cota_tva = <cota
liniei>`. Pe date (`vjtva_coloane`, `MARIUSM_AUTO`): `afisat = 0` sunt liniile de TVA,
`afisat = 1` bazele, `afisat = 2` neimpozabilele; `jv = 1` tine afara explicatiile de
achizitie, care altfel treceau de garda `FACT-029` fiindca au aceeasi cota.
- **Runda 7, in lucru**: sincronizarea articole-rulaje porneste doar din buton (se scoate
declansarea de la salvare, `omodificari.vc2:14484-14494`), si `AplicaModificareTrul`
(`ofacturare_editare.prg:924`) scrie si `pretv`/`tvav`, nu doar `pretvtva`.
**Perimetru fixat de Marius: doar preturi, nu si `valoarev`/`valtvav`/`valoarevcTVA`** -
sincronizarea e intre cantitate si preturi. Ramane de raspuns, ca fapt, cine recalculeaza
acele valori dupa sincronizare.
- **Nimic nu e comis** - decizia lui Marius e un singur commit, dupa ce sunt gata toate trei.
- `roafacturare.PJT`/`.PJX`/`.exe` apar modificate: zgomot din sesiunea lui de VFP, se lasa asa.
### Runda 7 - terminata, 20.08.2026, 22:40

View File

@@ -0,0 +1,150 @@
# Sablon `.vc2` gol — pornire pentru o biblioteca de clase noua
`sablon_vcx_gol.vc2` e punctul de plecare cand trebuie creata o biblioteca `.vcx` noua in
proiect. Exista pentru ca `txt2vcx.ps1` **refuza permanent** sa creeze un binar `.vcx`
inexistent (`txt2vcx.ps1:203-206`, garda nu verifica provenienta binarului, doar existenta lui) —
deci fluxul text->binar nu poate produce singur un `.vcx` nou de la zero. Solutia e sa pornesti
de la un binar-purtator **copiat**, peste care aplici acest text.
Continut: antetul FoxBin2Prg (6 linii, obligatoriu identic) + **o singura clasa-exemplu
minimala** (`AS custom`, fara obiecte-copil). Am ales varianta cu o clasa, nu biblioteca goala
de-a dreptul, pentru ca n-am gasit nicaieri in proiect un `.vcx` cu 0 clase de folosit ca
referinta si n-am vrut sa presupun formatul text al unei biblioteci goale fara sa-l vad — o clasa
`custom` fara copii insa exista deja ca tipar verificat (`COMUN\clase\_baza.vc2:114-121`, clasa
`_custom`) si a fost **confirmata printr-un test complet text->binar->text in izolare** (vezi mai
jos), deci e alegerea provata, nu doar plauzibila.
## Procedura, pas cu pas
### 1. Alege binarul-purtator
Trebuie sa fie o pereche `.vcx` + `.vct` **existenta** (ambele — `.vct` e memo-ul cu proprietatile
lungi/codul; fara el binarul nu se deschide). Nu presupune care e cea mai mica — verifica:
```
ls -la Clase/*.vcx Clase/*.vct
```
Alege perechea cu cel mai mic `.vcx`. Nu conteaza ce clase contine acum — le stergi la pasul 3.
### 2. Copiaza si redenumeste
```
cp Clase/<sursa>.vcx Clase/<nume_nou>.vcx
cp Clase/<sursa>.vct Clase/<nume_nou>.vct
```
Ambele comenzi, nu doar `.vcx` — un `.vcx` fara `.vct` pereche e un binar corupt pentru VFP.
### 3. Aplica sablonul si inlocuieste
```
cp docs/sabloane/sablon_vcx_gol.vc2 Clase/<nume_nou>.vc2
```
In fisierul copiat, inlocuieste **toate** aparitiile:
- `SourceFile="__NUME_BIBLIOTECA__.vcx"` -> `SourceFile="<nume_nou>.vcx"` (trebuie sa fie
exact numele fisierului `.vcx` de la pasul 2, cu tot cu extensie).
- `DEFINE CLASS __NumeClasaExemplu__ AS custom` -> numele si clasa-parinte reala dorita. Poti
schimba si baseclass-ul (`AS custom` -> ex. `AS _frmbase OF "_frm_base.vcx"` pentru un formular)
si adauga `ADD OBJECT` / metode dupa nevoie — respecta constrangerile de mai jos.
- `Name = "__NumeClasaExemplu__"` (in blocul `*<PropValue>`) -> **acelasi** nume ca la
`DEFINE CLASS`, cele doua trebuie sa coincida.
Spre deosebire de fluxul folosit la crearea lui `Clase\ofacturare_util.vcx`
(`docs\nota_s8b_vcx_util.md`), care a luat antetul dintr-o conversie reala ca sa nu-l inventeze,
**antetul acestui sablon e deja verificat printr-un test text->binar->text complet** (mai jos) —
nu mai trebuie reconfirmat printr-o conversie separata inainte de folosire, doar `SourceFile`
trebuie sa corespunda numelui ales la pasul 2.
### 4. Write-back
```
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\txt2vcx.ps1 `
-TextFile 'D:\ROA\ROAFACTURARE\Clase\<nume_nou>.vc2' `
-ProjectRoot D:\ROA\ROAFACTURARE -CacheRoot D:\ROA\ROAFACTURARE
```
Fara `-Force`, fara `-NoVerify` (fidelity check-ul trebuie sa treaca real). Adauga
`-AllowComun` **doar** daca `<nume_nou>.vcx` ajunge sub `COMUN\`.
### 5. Dovada de sincronizare — pe continut, nu pe mtime
`txt2vcx.ps1` rescrie mtime-ul textului la refresh de cache, deci mtime-ul egal nu dovedeste
nimic. Reconverteste binarul proaspat scris intr-un cache **izolat** (diferit de `ProjectRoot`,
altfel `vcx2txt.ps1` isi poate sterge singur binarul sursa — vezi capcana documentata in
`docs\nota_s8b_vcx_util.md`, sectiunea "Conversie text") si compara pe octeti:
```
powershell -ExecutionPolicy Bypass -File D:\ROA\UTIL\foxbin2prg\vcx2txt.ps1 `
-Source 'D:\ROA\ROAFACTURARE\Clase\<nume_nou>.vcx' `
-ProjectRoot D:\ROA\ROAFACTURARE -CacheRoot '<cale_temporara_izolata>'
diff Clase/<nume_nou>.vc2 <cale_temporara_izolata>/Clase/<nume_nou>.vc2
```
Zero diferente = dovada reala. Orice diferenta inseamna ca binarul din proiect nu reflecta
textul din arbore.
### 6. Verificari de integritate
```
grep -c 'ADD OBJECT' Clase/<nume_nou>.vc2
grep -c 'OBJECTDATA:' Clase/<nume_nou>.vc2
```
Trebuie sa fie **egale**. Pentru CRLF si diacritice, vezi `COMUN\docs\flux-editare-vfp-text.md`
si memoria de proiect despre editarea `.vc2` in cp1250 (`Edit`/`sed -i` corup octetii non-ASCII —
scrie orice modificare text cu un script Python pe disc, `latin-1`/`cp1250` passthrough, CRLF
explicit, niciodata `sed -i` sau `python -c`).
### 7. `.pjx` ramane manual
`.pjx` nu e editabil din text (cf. `CLAUDE.md` proiect). `Clase\<nume_nou>.vcx` trebuie adaugat
manual in `roafacturare.pjx` (Project Manager > Classes > Add) — dar **doar** cand ai nevoie de
Build/Rebuild pentru EXE. Nu e necesar pentru o proba cu `vfp9.exe` + `DO roafacturare.prg`, unde
`SET CLASSLIB` (pasul 8) rezolva clasa prin `SET PATH`.
### 8. Inregistrare in `roafacturare.prg`
Biblioteca trebuie inregistrata ca sa fie vazuta de aplicatie. Exemplu deja existent in fisier,
langa care se adauga o linie similara pentru biblioteca noua (`Programe\roafacturare.prg:137-139`):
```
SET CLASSLIB TO ocriterii.vcx ADDITIVE
SET CLASSLIB TO ofacturare_util.vcx ADDITIVE
Set Classlib To ctl32_statusbar.vcx Additive
```
`Clase\` e deja pe `SET PATH` (`Programe\roafacturare.prg:89`), deci fara cale completa. Aceasta
editare **nu e facuta automat de acest sablon** — se face separat, cu aprobare, cand biblioteca
noua chiar trebuie incarcata de aplicatie.
## Constrangeri FoxBin2Prg de retinut (au picat fidelity check-ul in trecut)
- `ADD OBJECT` la nivelul unei clase sunt ordonate **ASCII/colationat case-insensitive** pe
`ObjPath` (ex. `Cw10` intre `Cw1` si `Cw2`), inclusiv obiectele de top-nivel.
- Proprietatile in interiorul unui bloc `ADD OBJECT` / `*<PropValue>` sunt in **ordine
alfabetica**.
- **Ultima proprietate dintr-un bloc nu are `, ;`** la final (vezi ultima linie din blocul
`ADD OBJECT` din sablon — n-are virgula).
- Numarul de `ADD OBJECT` == numarul de `*< OBJECTDATA:` (0 = 0 in sablonul de baza).
- Liniile goale din corpul unui `PROCEDURE`/inainte de un `PROCEDURE` la nivel de clasa se
indenteaza cu un TAB; liniile goale **dintre blocuri `ADD OBJECT` consecutive** raman
neindentate (verificat direct pe text regenerat, `docs\nota_s8b_vcx_util.md` punctul 3).
- Encoding **cp1250** pentru diacritice, CRLF (`\r\n`) pe fiecare linie, inclusiv ultima
(`ENDDEFINE\r\n`, fara linie goala dupa la finalul fisierului).
## Ce s-a probat si cum
Test complet text -> binar -> text, **izolat in scratchpad**, fara sa atinga arborele
`D:\ROA\ROAFACTURARE`: copiat un binar-purtator existent intr-un proiect-fals in scratchpad,
aplicat sablonul (cu `SourceFile`/nume de clasa concrete), rulat `txt2vcx.ps1` cu
`-ProjectRoot`/`-CacheRoot` catre acel proiect-fals — **fidelity check trecut din prima
incercare** (spre deosebire de `frm_cursuri_valutare`, care a avut nevoie de 3 corectii). Apoi
reconvertit binarul proaspat cu `vcx2txt.ps1` intr-un cache izolat separat si comparat cu
`diff` — **zero diferente**. Detalii complete si comenzile exacte: `docs\nota_s8b_sablon_vc2.md`.
**Neprobat**: adaugarea in `.pjx` si deschiderea reala in designer/rulare (pasii 7-8 raman
descrisi, nu executati — n-ar fi trebuit sa atinga arborele real fara aprobare).

View File

@@ -0,0 +1,14 @@
*--------------------------------------------------------------------------------------------------------------------------------------------------------
* (EN) AUTOGENERATED - ATTENTION!! - NOT INTENDED FOR EXECUTION!! USE ONLY FOR MERGING CHANGES AND STORING WITH SCM TOOLS!!
*--------------------------------------------------------------------------------------------------------------------------------------------------------
*< FOXBIN2PRG: Version="1.21" SourceFile="__NUME_BIBLIOTECA__.vcx" CPID="1252" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
*
*
DEFINE CLASS __NumeClasaExemplu__ AS custom
*< CLASSDATA: Baseclass="custom" Timestamp="" Scale="Pixels" Uniqueid="" />
*<PropValue>
Name = "__NumeClasaExemplu__"
*</PropValue>
ENDDEFINE

View File

@@ -1 +1 @@
2026_08_09_02
2026_09_09_07