ROAFACTURARE 2.11.15 (r18015): programe/ofacturare_editare.prg, programe/ofacturare_stoc.prg. docs: conventia de encoding .sc2/.vc2 corectata la cp1250; cercetarile trans-proiect mutate din ROAFACTURARE in docs/cercetare (r18014); actualizari reguli_lucru, oracle_export, flux-editare-vfp-text, scripturi-migrare-db, flux-modificare-stergere-nota-jurnal, todos. .gitignore: watchdog_out si PNG-urile din rularile headless (r18008). Text FoxBin2Prg regenerat: clase/comun.vc2, ferestre/frm_import_note_facturi_clienti.sc2, ferestre/frm_initializare_facturi_balanta.sc2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AV7dQbTLAqvrtQbH7zxXxw
18 KiB
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 obiectuluipoDate). - Singurul loc care il seteaza pe
1, inProcedure 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. Tipul10nu are comentariu explicit in acest bloc (adaugat separat, v2.0.56, ca variatie de tip 1/5). - Nicio alta cale de scriere pe
in_valutain.vc2/.prgdin COMUN — verificat cu grep (\.in_valuta\s*=\s*[01]) pe intregofacturare.vc2siofacturare.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 parametrulin_valutadeja calculat in amonte, cu aceeasi semantica. - Consecinta directa, confirmata de cod: alegerea manuala a unei valute pentru document (control
ct_clb_valuta) nu poate schimbain_valuta— controlul insusi e eliminat din formular candin_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 depoDate.in_valuta), cu preturile brute in valuta pastrate inpoArticol.pretftva_val/pretctva_val/pretd+id_valuta_d, convertite in lei client-side, in VFP, folosind cursul zileipoDate.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 propriiCURS/ID_VALUTA/MULTIPLICATOR— cursul/valuta doar ale documentului, populate dinpoDate.Curs/id_valuta/multiplicator(relevante candin_valuta=1).VANZARI_DETALII(linie): nu are coloanaCURS— confirmat dinall_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:Linia stocheaza insaPACK_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_VALUTAPRET(pretul efectiv, in lei, folosit pe factura) siPRETD+ID_VALUTAD(pretul brut in valuta si valuta originii, pastrate ca referinta/audit) — vezi INSERT-ul dinadauga_articol_factura(docs\cercetare\rec_cale_vanzari_detalii.md:59-65, coloanelePRET,PRETD,ID_VALUTAD,ID_VALUTAalaturi). Deci da, se pastreaza si urma valutei originale pe linie, dar valoarea de facturare efectiva e mereu in lei peVANZARI_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:
ofacturare.prg:228-235—ofrmceredate = Createobject(lcObiect, toFactura)(lcObiect=frm_date_facturasaufrm_date_aviz),.Show()modal; la inchidere,Release ofrmceredate(:248).ofacturare.prg:266-308— pe baza tipului, se construieste apelul catrepack_facturare.cursor_preturi(?poDate.zi_curs, ...)(saucursor_articole_k/cursor_gestiune/cursor_lucrare) si se executa (:311goExecutor.oExecute(lcSqlCursor, lcCursor)).- 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 vizualizeaza_curs(tdDataCurs)—COMUN\programe\oproceduri_curs.prg:8-42— construieste cursorulcrscurssi deschideloFrmCurs = Createobject("frm_curs", tdDataCurs)/.Show(1)(modal).- Clasa
frm_curse definita inCOMUN\clase\onom_curs.vc2:537(DEFINE CLASS frm_curs AS _frmbase ...), cu formulare-satelit pentru adaugare curs:frm_curs_nou(onom_curs.vc2:1066) sifrm_curs_nou_multiplu(onom_curs.vc2:1330, apelat de laonom_curs.vc2:941). - A doua cale catre acelasi formular:
citeste_cursuri_stoc(oproceduri_curs.prg:45-110, folosit pe fluxul de facturare din stoc) cheamavizualizeaza_curs()fara parametru de data (oproceduri_curs.prg:94) cand gaseste valute fara curs pentru ziua ceruta, in buclaDo 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_curscandpoDate.tipNU e retur (!Inlist(poDate.tip,8,9)) SI (poDate.in_valuta = 1SAU exista macar o politica de pret in valuta accesibila tipului curent de document — adica exact conditia care astazi populeazacrscursuricu randuri, verificata deja empiric laofacturare.vc2:15092/:19000:Used('crscursuri') And Reccount('crscursuri') > 0dupa 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 liniitip_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 inpoGeneratorNumere/clb_serie_act, cf.ofacturare.vc2:9735-9737, needatate aici). - Layout-urile
.frx(neconvertite) care afiseazapoDate.Curs/cValutape hartie nu au fost verificate — gap deja semnalat inrec_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 liniiID_VALUTA <> moneda_nationalainVANZARI_DETALII— dovada ar intari direct concluzia punctului 3, dar cercetarea a fost strict pe cod (fara conexiuni DB, conform mandatului).