Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git, nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de cercetare pe care se sprijina modificarile din cod. O stergere acolo era definitiva. Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele fisierelor de test la care se refereau. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
15 KiB
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:Aici validarea E DEJA dublu conditionata: peCase poDate.in_valuta = 1 And Empty(Nvl(poDate.zi_curs,{})) And Type('thisform.clb_zi_curs.visible')<>'U'in_valuta = 1SI pe existenta controlului (Type(...)<>'U'- devine 'U' daca controlul a fost eliminat cuRemoveObject). Deci pe factura in lei, sau pe orice tip unde controlul a fost eliminat, validarea nu ruleaza deloc. Comentariul*!* modificare v 2.0.56de 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) sifrm_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 NECONDITIONATpack_facturare.verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)(linia 2153). Aceasta procedura (:16247-16274) verifica cursul pentru TOATE valutele distincte dinFACT_VPRETURI_UTILIZATORale utilizatorului curent (nu doar valuta documentului!) si aruncaRAISE_APPLICATION_ERROR(-20005, 'Nu este setat cursul din data de ... !')daca oricare dintre ele nu are curs care sa acopereV_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) sicursor_lucrare(:3173-3593, folosesteV_DATA_CURSpentru comenzi_elemente) -cursor_articole_kNU apeleazaverifica_cursuri_valute, doar faceLEFT JOIN CURS ... WHERE DATA <= V_DATA_CURS AND DATA2 >= V_DATA_CURS- daca nu gaseste, cade silentios peNVL(D.CURS,0)(pret gresit, nu eroare).cursor_lucrare(:3186-3218) INSA face o verificare proprie, similara: daca articolele comenzii au valute fara curs peV_DATA_CURS, arunca acelasi-20005.- Exista deja o rutina de recuperare la acest cod de eroare:
ofacturare.prg:313-317Deci sistemul ANTICIPEAZA deja cazul "curs lipsa la data respectiva" si deschide un ecran de gestiune a cursurilor - independent de validarea din formularul de date.If lnSucces < 0 AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare") If goExecutor.nEroare = 20005 vizualizeaza_curs(poDate.zi_curs) ENDIF
d) Afisare / etichetare (fara risc):
frm_facturare_articole.Init(:15097-15098) sifrm_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 peclb_zi_cursDACA exista (Type(...)<>'U'), altfel peclb_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 cupoDate.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) dacaget_ora()cade in alta luna; apoi.zi_curs = ldData.Reset(ofacturare_comun.prg:486-496):.zi_curs = .Data(unde.Dataa fost deja setat tot dinldData-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:
- Validarea din
inainte_de_do_termin(:9484) e deja garda cuType(...)<>'U', deci se auto-dezactiveaza cand controlul nu mai exista. - SQL-ul de populare articole pentru tip 8/9 e
cursor_retur(?poDate.in_valuta,...)(ofacturare.prg:306-307) - NU trimite delocpoDate.zi_curs, deci nu poate cauza -20005 din cauza acestui camp. - Chiar daca ar fi trimis,
poDate.zi_curstot ar avea valoarea implicita de la Init/Reset (punctul 5) - nimic nu-l goleste laRemoveObject.
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_lucrarein formularul unificat, sau doarfrm_date_factura/frm_date_aviz. Din cod,frm_date_aviz_lucraree 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) sicursor_gestiune(tip 41) fata deverifica_cursuri_valute- din citire,cursor_articole_knu apeleaza acea procedura (cade silentios pe curs 0), dar nu am verificatcursor_gestiune. - Nu am verificat cum decide
frm_date_factura.Initce alte tipuri (in afara de 8,9) ar putea fi candidate pentru ascunderea luiclb_zi_cursconform deciziei 15 - doar am confirmat mecanismul existent si conditia dubla deja implementata la validare (in_valuta + Type<>'U').