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
263 lines
15 KiB
Markdown
263 lines
15 KiB
Markdown
# 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').
|