# Cercetare: `poDate.in_valuta`, `Clb_zi_curs` si cele doua concepte de valuta

Investigatie read-only (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit). Sursa: cache
text `.vc2`/`.prg` din `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare.prg`,
`COMUN\programe\ofacturare_comun.prg`, `COMUN\programe\ofacturare_stoc.prg`,
`COMUN\programe\oproceduri_curs.prg`, plus pachetul Oracle
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql` (copie pe disc,
alta numerotare decat baza). Reutilizeaza si confirma cercetari anterioare din
`docs\cercetare\rec_cale_vanzari_detalii.md` si `docs\cercetare\rec_consumatori_vanzari.md`.

---

## 1. `poDate.in_valuta` — unde se seteaza si din ce

**Concluzie**: `in_valuta` e determinat **o singura data**, la `Init`, exclusiv din **tipul
documentului** (`tnTip`) plus o lista fixa de `id_set` — niciodata din alegerea manuala a unei
valute in antet si niciodata resetat ulterior. E o proprietate a *tipului de factura* (Invoice /
credit note / retur valuta / factura fiscala valuta pe contract), nu a faptului ca articolele au
preturi in valuta.

**Dovezi**:
- Valoare implicita `0`: `COMUN\programe\ofacturare_comun.prg:165` (`in_valuta = 0`, in blocul de
  proprietati al obiectului `poDate`).
- Singurul loc care il seteaza pe `1`, in `Procedure Init`:
  ```
  ofacturare_comun.prg:248-250
  If Inlist(tnIdSet,223,225,226,227,271) OR INLIST(m.tnTip, 5,6,7,9,10,52)
      .in_valuta = 1
  Endif
  ```
- Legenda tipurilor (comentariu din `frm_date_factura.Init`), `ofacturare.vc2:9571-9582`:
  `1`=lista preturi, `2`=contract, `3`=comanda, `4`=aviz, `5`=Invoice lista preturi, `6`=Invoice
  contract, `7`=credit note, `8`=retur lei, `9`=retur valuta, `48`=avize valoric, `52`=Factura
  fiscala valuta contract. Tipul `10` nu are comentariu explicit in acest bloc (adaugat separat,
  v2.0.56, ca variatie de tip 1/5).
- Nicio alta cale de scriere pe `in_valuta` in `.vc2`/`.prg` din COMUN — verificat cu grep
  (`\.in_valuta\s*=\s*[01]`) pe intreg `ofacturare.vc2` si `ofacturare.prg`: restul potrivirilor
  sunt toate **citiri** (`If poDate.in_valuta = 1 ...`), nu atribuiri. `ofacturare_stoc.prg:549`
  (`poDate.in_valuta = in_valuta`) e o cale alternativa (facturare "din stoc") care primeste
  parametrul `in_valuta` deja calculat in amonte, cu aceeasi semantica.
- Consecinta directa, confirmata de cod: alegerea manuala a unei valute pentru document (control
  `ct_clb_valuta`) nu poate schimba `in_valuta` — controlul insusi e eliminat din formular cand
  `in_valuta = 0` (`ofacturare.vc2:9725-9728`), deci userul nu are cum sa-l "aleaga" pe o factura
  care nu e deja de tip valuta.

---

## 2. Campul „Data curs valutar" (`Clb_zi_curs`) — unde e definit si cand e eliminat azi

**Concluzie**: campul e eliminat **doar** pentru facturi de retur (`tip` 8 sau 9), niciodata pe
baza de `in_valuta`. **Da, azi campul apare si pe facturile in lei** (`in_valuta=0`) de orice alt
tip — comportament confirmat explicit prin simetrie de cod: imediat dupa blocul de retur, exista un
bloc separat care elimina `ct_clb_valuta` cand `in_valuta=0`, dar echivalentul lipseste pentru
`clb_zi_curs`.

**Dovezi** (identificarea claselor facuta cu `vfp_symbols.ps1 -Where`):

| Clasa | Interval | `Clb_zi_curs` definit | Eliminat azi? |
|---|---|---|---|
| `frm_date_aviz` | `ofacturare.vc2:6566-7618` | `:6727-6740` (caption "Data cursului valutar") | Niciodata — zero `RemoveObject`/conditionare pe `in_valuta` in toata clasa (verificat exhaustiv) |
| `frm_date_aviz_lucrare` | `:7620-8201` | `:7787-7801` | Niciodata; in plus, validare **necondiționata**: `:8076` `Case Empty(Nvl(poDate.zi_curs,{}))` fara nicio garda pe `in_valuta` (spre deosebire de `frm_date_factura`, vezi mai jos) |
| `frm_date_factura` | `:8482-9869` | `:8701-8714` (caption "Data curs valutar") | **Doar** pentru `tip` 8/9, in `Init`: |

```
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	^

ofacturare.vc2:9725-9728  (imediat dupa, in ACEEASI metoda)
If poDate.in_valuta = 0
    lnHeight = lnHeight - .ct_clb_valuta.Height
    laPozitii(.ct_clb_valuta.TabIndex, 2) = 1
    .RemoveObject('ct_clb_valuta')
```

Cele doua blocuri sunt adiacente si scrise dupa acelasi tipar (inaltime, `laPozitii`,
`RemoveObject`) — dovada ca autorul original a tratat `ct_clb_valuta` (selectorul de valuta) ca
dependent de `in_valuta`, dar **nu** a facut acelasi lucru pentru `clb_zi_curs`. Validarea de
completare e de asemenea asimetrica: `ofacturare.vc2:9484`
(`Case poDate.in_valuta = 1 And Empty(Nvl(poDate.zi_curs,{})) And ...`) cere data curs **doar** cand
`in_valuta=1`, in timp ce `frm_date_aviz_lucrare` (`:8076`) o cere mereu — inconsistenta intre cele
doua forme, de retinut daca regula noua trebuie aplicata uniform.

Nota: `frm_facturare_articole2` (clasa separata, `:15741-19355`) are propriul label "Curs valutar"
(control diferit, `:16285-16299`), tratat la punctul 3/5 — nu e acelasi camp cu `Clb_zi_curs` din
antet, dar citeste aceeasi `poDate.zi_curs`.

---

## 3. Cele doua concepte, in cod — confirmate distinct

**Concluzie**: da, distinctia exista clar in cod, pe doua axe independente:
- **(a) valuta de document**: `poDate.in_valuta` / `poDate.Curs` / `poDate.multiplicator` /
  `poDate.id_valuta` — un singur curs, al documentului intreg, folosit cand tot documentul e emis
  in valuta.
- **(b) articol cu pret in valuta pe document in lei**: `poArticol.tip_valuta = 1` (proprietate a
  politicii de pret a articolului, independenta de `poDate.in_valuta`), cu preturile brute in
  valuta pastrate in `poArticol.pretftva_val`/`pretctva_val`/`pretd` + `id_valuta_d`, convertite in
  lei **client-side, in VFP**, folosind cursul zilei `poDate.zi_curs`.

**Dovezi — conversia in lei pentru cazul (b)**:
```
ofacturare.vc2:1989   poArticol.pretftva = Round(poArticol.pretftva_val * poArticol.Curs / poArticol.multiplicator, gnPPretV)
ofacturare.vc2:2953   poArticol.pretftva = Round(poArticol.pretftva_val * poArticol.Curs / poArticol.multiplicator, gnPPretV)
ofacturare.vc2:2017   poArticol.pretctva = Round(poArticol.pretctva_val * poArticol.Curs / poArticol.multiplicator, gnPPretV)
```
`poArticol.Curs`/`multiplicator` (nu e alt curs decat cel al zilei documentului) provin din cursorul
`crscursuri`, incarcat explicit **cand documentul e in lei** — exact pentru articolele cu preturi in
valuta:
```
ofacturare.prg:407-418  (identic si la :941-947)
If poDate.in_valuta = 1
    Select Distinct Curs, nume_val, id_valuta, multiplicator From (lcCursor) ... Where tip_valuta = 1 ... Into Cursor crscursuri
    Select crscursuri
    poDate.Curs = Curs
    poDate.multiplicator = multiplicator
Else
    citeste_cursuri_zi(poDate.zi_curs)
    If Reccount('crscursuri') = 0
        Use In crscursuri
    Endif
Endif
```
`citeste_cursuri_zi` (`COMUN\programe\oproceduri_curs.prg:113-124`) construieste `crscursuri` cu
**toate** cursurile valabile la `tdDataCurs` (`= poDate.zi_curs`):
```
oproceduri_curs.prg:117-118
lcSql = [select nume_val,curs,id_valuta,multiplicator from ] + gcS + [.vcurs where data <= ... and data2 >= ...]
```
Cursorul e apoi afisat direct pe grila de selectie a articolelor, cu eticheta construita din
`poDate.zi_curs`, **indiferent de `in_valuta`**:
```
ofacturare.vc2:15097-15098 si 19004-19005 (frm_facturare_articole2)
If !Empty(Nvl(poDate.zi_curs, {}))
    Thisform.lb_cursuri.Caption = "Curs valutar " + Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "")
```
Grila/eticheta apar doar daca `crscursuri` are randuri (`ofacturare.vc2:15092`/`19000`), ceea ce se
intampla si pe un document in lei, daca exista macar un articol cu politica in valuta.

**`VANZARI` vs `VANZARI_DETALII` — ce se stocheaza**:
- `VANZARI` (document): coloane proprii `CURS`/`ID_VALUTA`/`MULTIPLICATOR` — cursul/valuta **doar
  ale documentului**, populate din `poDate.Curs`/`id_valuta`/`multiplicator` (relevante cand
  `in_valuta=1`).
- `VANZARI_DETALII` (linie): **nu are coloana `CURS`** — confirmat din `all_tab_columns`
  (`docs\cercetare\rec_cale_vanzari_detalii.md:171-175`) si din SQL-ul live al pachetului, care
  reface cursul liniei prin JOIN, nu dintr-o coloana proprie:
  ```
  PACK_FACTURARE.sql:3773-3778 (identic la :4034-4036, :6393-6396, :6571-6573)
  LEFT JOIN VANZARI_CURSURI B1 ON A1.ID_VANZARE = B1.ID_VANZARE AND A1.ID_VALUTA = B1.ID_VALUTA
  ```
  Linia stocheaza insa `PRET` (pretul efectiv, in lei, folosit pe factura) **si** `PRETD` +
  `ID_VALUTAD` (pretul brut in valuta si valuta originii, pastrate ca referinta/audit) — vezi
  INSERT-ul din `adauga_articol_factura`
  (`docs\cercetare\rec_cale_vanzari_detalii.md:59-65`, coloanele `PRET`, `PRETD`, `ID_VALUTAD`,
  `ID_VALUTA` alaturi). Deci **da**, se pastreaza si urma valutei originale pe linie, dar valoarea
  de facturare efectiva e mereu in lei pe `VANZARI_DETALII.PRET`.

**La ce se foloseste `VANZARI_CURSURI`**: e un instantaneu al cursurilor pentru **orice valuta
straina aparuta printre liniile documentului** (nu doar valuta proprie a documentului), scris o
singura data la emitere, indiferent de `in_valuta`:
```
PACK_FACTURARE.sql:14491-14501
PROCEDURE scrie_cursuri(V_ID_VANZARE IN NUMBER) IS
BEGIN
  INSERT INTO VANZARI_CURSURI (ID_VANZARE, ID_VALUTA, CURS, MULTIPLICATOR)
    SELECT DISTINCT V_ID_VANZARE, ID_VALUTA, CURS, MULTIPLICATOR
      FROM VANZARI_DETALII_TEMP
     WHERE ID_VALUTA <> pack_facturare.nid_moneda_nationala;
END scrie_cursuri;
```
Rulat necondiționat la fiecare emitere (apelat din `scrie_in_vanzari`, vezi
`docs\cercetare\rec_cale_vanzari_detalii.md:98-102`) — deci **si pe o factura in lei** cu articole
in valuta se scrie cate un rand `VANZARI_CURSURI` pentru fiecare valuta straina aparuta pe linii,
cu cursul citit la `poDate.zi_curs`. Tabela e folosita ulterior la reafisare/reprint/retur, ca sursa
a cursului per-linie (JOIN-urile de mai sus).

---

## 4. Ce se strica daca ascundem campul cand "nu are sens"

**Concluzie**: nu exista risc de "factura fara curs deloc" — `poDate.zi_curs` primeste mereu o
valoare implicita la `Init` (data documentului) si nu poate ramane `NULL`. Riscul real e altul:
**userul pierde controlul asupra datei de curs pentru cazul (b)** — o factura in lei cu articole in
valuta ar folosi mereu cursul zilei curente/implicite, fara sa poata fi corectat manual, exact in
situatia in care lipseste cursul pentru acea data (eroarea Oracle 20005, punctul 5) sau cand userul
vrea sa aliniaze cursul cu data unui document sursa (aviz, contract).

**Consumatori confirmati ai `poDate.zi_curs`** (grep exhaustiv `poDate\.zi_curs`, fisiere
`ofacturare.vc2`, `ofacturare.prg`, `ofacturare_stoc.prg` — singurele 3 din COMUN):

| Loc | Ce primeste | Conditionat de `in_valuta`? |
|---|---|---|
| `ofacturare.vc2:6105-6107`, `:13994-13996`, `:18030-18032` | parametru `to_date(...)` catre `pack_facturare.initializeaza_date_factura(...)` la fiecare finalizare de antet | **Nu** — trimis pentru orice tip |
| `ofacturare.prg:272,276,281,297,303` | primul parametru al `pack_facturare.cursor_articole_k` / `cursor_preturi` / `cursor_gestiune` / `cursor_lucrare` — SQL-ul care aduce lista de articole/preturi afisata userului | **Nu** — inclusiv pentru `tip=1` (lista de preturi, lei) |
| `ofacturare_stoc.prg:79,335` | acelasi rol, pe calea alternativa "facturare din stoc" | Nu |
| `ofacturare.vc2:15097-15098`, `:19004-19005` | eticheta grilei "Curs valutar (data)" din `frm_facturare_articole2` | Nu — apare oricand `crscursuri` are randuri |
| `ofacturare.vc2:9484` | validare "Nu ati completat ziua cursului valutar" | **Da**, doar `in_valuta=1` (`frm_date_factura`) |
| `ofacturare.vc2:8076` | aceeasi validare | **Nu**, necondiționata (`frm_date_aviz_lucrare`) |

**Riscul concret**: daca regula noua ascunde/dezactiveaza campul strict cand `poDate.in_valuta=0`,
pe `frm_date_factura` (tip 1, lista de preturi) userul nu va mai putea edita `zi_curs` inainte de a
deschide grila de articole — dar `ofacturare.prg:279-282` tot va trimite acel `poDate.zi_curs`
(neschimbat, ramas pe data initializata la `Init`) catre `cursor_preturi`, iar daca acolo Oracle
ridica eroarea 20005 (curs lipsa pentru acea data/valuta), fluxul de recuperare
(`vizualizeaza_curs`, punctul 5) va porni oricum, dar userul nu va (mai) avea camp pe antet ca sa
retina/corecteze data dupa ce inchide formularul de curs. Pe `frm_date_aviz_lucrare`, ascunderea ar
intra chiar in conflict cu validarea necondiționata de la `:8076`, care ar bloca finalizarea
antetului cerand un camp care nu mai exista pe ecran — de reparat impreuna, nu doar ascuns.

---

## 5. Formularul de curs valutar din antet — localizare si traseu

**Concluzie**: nu exista niciun buton/dblclick pe `Clb_zi_curs` care sa deschida formularul de curs
(verificat exhaustiv — zero hit pentru `Clb_zi_curs.DblClick/RightClick/Click` sau `but_curs` in
`ofacturare.vc2`). Formularul se deschide **automat**, ca recuperare de eroare, dupa ce antetul
(`frm_date_factura`/`frm_date_aviz`) s-a inchis deja si codul incearca sa incarce lista de articole.

**Traseu**:
1. `ofacturare.prg:228-235` — `ofrmceredate = Createobject(lcObiect, toFactura)` (`lcObiect` =
   `frm_date_factura` sau `frm_date_aviz`), `.Show()` modal; la inchidere, `Release ofrmceredate`
   (`:248`).
2. `ofacturare.prg:266-308` — pe baza tipului, se construieste apelul catre
   `pack_facturare.cursor_preturi(?poDate.zi_curs, ...)` (sau `cursor_articole_k`/`cursor_gestiune`/
   `cursor_lucrare`) si se executa (`:311` `goExecutor.oExecute(lcSqlCursor, lcCursor)`).
3. Daca esueaza cu eroarea Oracle **20005** (curs lipsa pentru data/valuta ceruta):
   ```
   ofacturare.prg:313-317 (identic la :828-832)
   If lnSucces < 0
       AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
       If goExecutor.nEroare = 20005
           vizualizeaza_curs(poDate.zi_curs)
       ENDIF
   ```
4. `vizualizeaza_curs(tdDataCurs)` — `COMUN\programe\oproceduri_curs.prg:8-42` — construieste
   cursorul `crscurs` si deschide `loFrmCurs = Createobject("frm_curs", tdDataCurs)` /
   `.Show(1)` (modal).
5. Clasa `frm_curs` e definita in **`COMUN\clase\onom_curs.vc2:537`** (`DEFINE CLASS frm_curs AS
   _frmbase ...`), cu formulare-satelit pentru adaugare curs: `frm_curs_nou`
   (`onom_curs.vc2:1066`) si `frm_curs_nou_multiplu` (`onom_curs.vc2:1330`, apelat de la
   `onom_curs.vc2:941`).
6. A doua cale catre acelasi formular: `citeste_cursuri_stoc` (`oproceduri_curs.prg:45-110`, folosit
   pe fluxul de facturare din stoc) cheama `vizualizeaza_curs()` **fara parametru de data**
   (`oproceduri_curs.prg:94`) cand gaseste valute fara curs pentru ziua ceruta, in bucla `Do While
   llVerificare` (retry pana userul completeaza sau renunta).

Aceasta e exact situatia din `COMUN\docs\todos.txt:45` (punctul 16): "la finalizare se verifica
cursul valutar necesar pentru politicile de preturi care se factureaza [...] la revenire din
formularul de curs valutar, focusul revine [...] pe TIP DOCUMENT, si la iesire din serie se
regenereaza numar act" — confirmat ca fenomenul are loc **dupa** ce antetul (`frm_date_factura`) e
deja inchis si eliberat (`Release ofrmceredate`, pasul 1), deci orice regenerare de numar/focus
vizibila dupa inchiderea `frm_curs` tine de ecranul/starea care ramane activa in spate (nu s-a
localizat mai departe — cere depanare separata, in afara scopului "doar localizare" cerut aici).

---

## Cand are sens campul „Data curs valutar" — propunere de regula

Campul are sens de aratat/editabil pe antet exact cand exista **vreo** sansa ca cursul zilei sa fie
folosit la facturare — nu doar cand documentul insusi e in valuta:

> Arata (si activeaza) `Clb_zi_curs` cand `poDate.tip` NU e retur (`!Inlist(poDate.tip,8,9)`) **SI**
> (`poDate.in_valuta = 1` **SAU** exista macar o politica de pret in valuta accesibila tipului
> curent de document — adica exact conditia care astazi populeaza `crscursuri` cu randuri, verificata
> deja empiric la `ofacturare.vc2:15092`/`:19000`: `Used('crscursuri') And Reccount('crscursuri') > 0`
> dupa incarcarea listei de articole). Pe retur (8/9) ramane ascuns ca azi.

Pe `frm_date_aviz`/`frm_date_aviz_lucrare`, unde `crscursuri` nu e inca disponibil la momentul
antetului (se incarca dupa, la fel ca la factura), regula echivalenta practicabila **pe antet** (nu
dupa) ar fi: arata campul daca tipul documentului admite politici de pret in valuta pentru
sucursala/gestiunea curenta (verificare care azi nu exista pe antet, doar mai tarziu pe grila de
articole) — necesita fie mutarea verificarii mai devreme, fie acceptarea ca antetul nu poate sti cu
certitudine inainte de a incarca articolele, caz in care campul ramane vizibil implicit si doar
eticheta/relevanta lui se clarifica ulterior (ca azi, in `frm_facturare_articole2`).

---

## Necunoscute ramase

- Nu s-a confirmat daca exista si alte politici (`CRM_POLITICI_PRET*`) sau reguli de sucursala care
  determina *inainte* de deschiderea grilei de articole daca vor exista linii `tip_valuta=1` — utile
  pentru a decide vizibilitatea campului chiar pe antet, nu doar reactiv dupa incarcarea articolelor.
- Nu s-a urmarit complet "focusul sare pe tip document / regenerare numar act" (punctul 5/todos #16)
  dupa inchiderea `frm_curs` — s-a localizat doar traseul de deschidere, nu si ecranul/handler-ul
  care ramane activ in spate si cauzeaza simptomul; cere sesiune separata de depanare (posibil in
  `poGeneratorNumere`/`clb_serie_act`, cf. `ofacturare.vc2:9735-9737`, needatate aici).
- Layout-urile `.frx` (neconvertite) care afiseaza `poDate.Curs`/`cValuta` pe hartie nu au fost
  verificate — gap deja semnalat in `rec_consumatori_vanzari.md` (risc posibil #3), ramane valabil.
- Nu s-a confirmat empiric (interogare Oracle) daca exista azi in productie facturi `tip=1` (lei) cu
  linii `ID_VALUTA <> moneda_nationala` in `VANZARI_DETALII` — dovada ar intari direct concluzia
  punctului 3, dar cercetarea a fost strict pe cod (fara conexiuni DB, conform mandatului).
