#13: formular de facturare unificat - etapa curenta
Squash al branch-ului de lucru plan13-s2. Cod: clasa noua ofacturare_util (geometrie si utilitare de formular), inrolata in roafacturare.prg; meniul "Facturare (nou)" plat, cu reparatia literalului peste limita VFP de 255 de caractere care impiedica compilarea metodei. Livrare: changelog 2.11.19, marcajul de versiune DB pentru scripturile finale din SCRIPTURI_CLAR\2026\09, inventarul livrarii in docs/livrare_13.md si registrul de erori deschise. Documentatia de lucru a etapei (cercetare, rapoarte, handoff-uri, snapshot-uri de scripturi si backup-uri de rollback) a fost scoasa din arbore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PkyjGyrV2S7932om4kfSiK
This commit is contained in:
@@ -1,262 +0,0 @@
|
||||
# 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').
|
||||
Reference in New Issue
Block a user