#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:
46
CLAUDE.md
46
CLAUDE.md
@@ -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
136
Clase/ofacturare_util.vc2
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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`.
|
||||
@@ -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\`.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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").
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 negestionabil |
|
||||
| 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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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');`
|
||||
@@ -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).
|
||||
@@ -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`).
|
||||
@@ -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`.*
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
@@ -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
12
docs/erori_deschise.md
Normal 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.
|
||||
@@ -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).
|
||||
@@ -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
268
docs/livrare_13.md
Normal 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
146
docs/plan_13_executie.md
Normal 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
@@ -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** |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
150
docs/sabloane/README_sablon_vcx.md
Normal file
150
docs/sabloane/README_sablon_vcx.md
Normal 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).
|
||||
14
docs/sabloane/sablon_vcx_gol.vc2
Normal file
14
docs/sabloane/sablon_vcx_gol.vc2
Normal 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
|
||||
@@ -1 +1 @@
|
||||
2026_08_09_02
|
||||
2026_09_09_07
|
||||
Reference in New Issue
Block a user