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

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

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

263 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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').