#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:
2026-09-09 21:30:51 +03:00
parent 87063b06a8
commit 104c24ec20
80 changed files with 5585 additions and 27852 deletions

View File

@@ -1,246 +0,0 @@
# Cercetare — doua puncte deschise inainte de implementarea S4
Investigatie READ-ONLY, doua intrebari inchise din `docs\plan_13_unificare_formular_facturare.md`,
`#### S4`, sectiunea "De inchis inainte de implementare" (`:2092-2095`). Fara editari de cod, fara
`git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara scriere pe Oracle (numai `SELECT`).
Status: **GATA**. Ambele intrebari au raspuns confirmat pe cod, cu `fisier:linie`. Amandoua
raspunsurile de baza confirma "nu schimba nimic", dar fiecare are o rezerva concreta, nu triviala,
de pus in proiectarea S4 (nu doar de bifat).
Sursa Oracle: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
(17217 linii — liniile citate mai jos sunt verificate direct pe acest fisier, corpul pachetului).
Sursa VFP: `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare.prg` (perimetrul `frm_facturare_articole`
/ `factureaza`; `frm_facturare_articole2` / `factureaza2` citite doar pentru comparatie, nu atinse).
---
## Intrebarea 1 — `id_jtva_coloana` lipseste din `cursor_preturi`?
### 1.1 Ce e si cine o consuma
`id_jtva_coloana` identifica randul din `JTVA_COLOANE` (explicatia/coloana de TVA folosita la
raportare si SAFT) asociat unei linii de factura. E scris pe fiecare linie in `VANZARI_DETALII_TEMP`
(Oracle, prin `adauga_articol_factura`) si citit inapoi de UI-ul VFP pentru dialogul de "explicatie
TVA" (`Cb_explicatie_Tva`, `frm_articol_factura`).
### 1.2 Chiar lipseste din `cursor_preturi`? — confirmat pe SQL
Toate cele **cinci ramuri** ale `cursor_preturi` (`WHEN V_TIP = 45`, `WHEN V_TIP IN (1,2)`,
`WHEN V_TIP IN (5,6,10,52)`, `WHEN V_TIP = 7`, `ELSE` aviz — corpul la
`ff_...PACK_FACTURARE.sql:2138-2644`) au liste de coloane complete verificate direct — **niciuna nu
returneaza `id_jtva_coloana`** (`:2163-2221`, `:2268-2329`, `:2394-2427`, `:2470-2504`, `:2545-2603`).
`cursor_facturare` e un `REF CURSOR` slab tipat (`:28`), deci lista de coloane a fiecarei ramuri e
literal ce se vede in `SELECT`, fara camp implicit.
Acelasi lucru e adevarat si pentru **`cursor_gestiune`** (`:4158-4347`, coloanele la `:4207-4343`) —
relevant pentru intrebarea 2, tipul 41. `cursor_contract` (`:2646-2950`) delega `V_CURSOR` la
`cursor_preturi` (deci mosteneste lipsa), iar `V_CURSOR2` (rate + articole `OPT_FACTURARE=3`,
`:2718-2938`) **de asemenea nu are `id_jtva_coloana`** in niciuna din cele doua ramuri `UNION ALL`.
Prin contrast, **`cursor_avize`** (`:3703-3874`) **are** `A.ID_JTVA_COLOANA` explicit (`:3748`,
plus `GROUP BY`-urile de la `:3781`, `:3804`, `:3822`, `:3843`) si `cursor_comanda` (`:2952-3171`)
**nu are**, pe niciuna din cele doua ramuri (factura/aviz, `:2994-3170`). Deci impartirea reala e:
*are* — doar `cursor_avize`; *nu are* — `cursor_preturi`, `cursor_gestiune`, `cursor_contract`
(ambele cursoare), `cursor_comanda`.
### 1.3 E completat in alt pas, sau ramane gol? — DA, e completat, printr-un mecanism in doi pasi
Pasul 1 — **valoare implicita, nu `NULL`**. `do_initializeaza_articol`
(`ofacturare.vc2:13681-13683`, identic la `:17896-17897` pe prototip):
```
If Type('toArticol.id_jtva_coloana') = 'U'
AddProperty(toArticol,'id_jtva_coloana',0)
Endif
```
Daca `Scatter Name poArticol` dintr-un cursor fara coloana `id_jtva_coloana` produce un obiect fara
acea proprietate (`Type(...) = 'U'`), se adauga cu valoarea **`0`** (numeric, nu `NULL`).
Pasul 2 — **derivare reala din `proc_tvav`, neconditionata**. `frm_articol_factura.Init`
(`ofacturare.vc2:2344-2371`) suprascrie *intotdeauna* `id_jtva_coloana`, indiferent de ce a venit din
cursor:
```
Select jtva_coloane
If !Isnull(poArticol.proc_tvav)
Set Filter To cota_tva = poArticol.proc_tvav * 100 - 100
...
poArticol.id_jtva_coloana = id_jtva_coloana
Else
...
poArticol.proc_tvav = (cota_tva + 100)/100
poArticol.id_jtva_coloana = id_jtva_coloana
EndIf
```
`proc_tvav` **este** returnat de toate cele cinci ramuri ale `cursor_preturi` (coloana comuna,
confirmata la 1.2), deci lookup-ul are mereu ce derivea, indiferent daca `id_jtva_coloana` a lipsit
din cursorul sursa.
Acest `Init` ruleaza pentru **orice** linie adaugata prin `do_adauga_articol`
(`ofacturare.vc2:12813-13167`), pe ambele ramuri ale `Do Case` de la `:12870-12896`:
- articol negestionabil / `gnScadereStoc=0` / restaurant (`tip=45`) →
`Createobject("frm_articol_factura", ...)` direct (`:12873`);
- articol gestionabil (`Otherwise`, `:12881-12895`) → `do_alege_stoc` →
`Createobject("frm_articol_gest_factura", ...)` (`:13353`/`:13361`/`:13375`) — clasa care
**mosteneste** `frm_articol_factura` (`vfp_symbols -Class`: `frm_articol_gest_factura ->
frm_articol_factura -> _frmbase -> _form`) si al carei `Init` (`:3986-3989`) cheama
`DoDefault(tnCantitate,tlAscunde)` — deci ruleaza acelasi `Init` de la 2315-2371, cu aceeasi
derivare.
**Confirmare ca derivarea chiar functioneaza si nu doar exista in cod:** `do_alege_stoc`
(`:13330-13340`) avea, intr-o versiune anterioara (comentata, `v 2.2.18`), un scurtcircuit care
seta direct `id_jtva_coloana` fara sa mai arate formularul cand exista o singura optiune de TVA;
azi acel scurtcircuit e dezactivat si se creeaza intotdeauna obiectul `frm_articol_gest_factura`
(deci `Init` ruleaza mereu, nu doar in cazuri rare).
Safety-net-ul `Case Empty(poArticol.id_jtva_coloana)` din `inainte_de_do_termin`
(`:2205-2208`, mostenit si de `frm_articol_gest_factura`) e o verificare reziduala pentru cazul in
care nici `proc_tvav` n-are match in `jtva_coloane` — nu dovada ca `id_jtva_coloana` ramane gol azi
pe fluxul normal.
### 1.4 Concluzia care conteaza — cu o rezerva reala, nu ipotetica
**Pe fluxul de azi (`do_adauga_articol`), lipsa lui `id_jtva_coloana` din `cursor_preturi`/
`cursor_gestiune` NU e un bug** — e completat corect, de doua ori (default 0, apoi derivat real din
`proc_tvav`), pe ambele ramuri (gestionabil/negestionabil), inainte ca linia sa ajunga in
`crsfactura` (`Gather ... id_jtva_coloana ...`, `:12945-12950`/`:12992-12995`) si, de acolo, in
Oracle (`do_scrie_articole` → `adauga_articol_factura(...)`, `Alltrim(Str(poArt.id_jtva_coloana))`,
`:14085`).
**Rezerva: raspunsul "filtrarea nu schimba nimic" e adevarat DOAR daca implementarea S4 pastreaza
trecerea prin `do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa.** Cercetarea
anterioara (`docs\cercetare\s4_cautare_articole_server.md`, sectiunea 5) arata ca **singurul exemplu
real existent de cautare-pe-server** (`combosql`, `grd_factura.cCodMat.cboCodmat.LostFocus`,
`ofacturare.vc2:19288-19306`) **nu trece prin `do_adauga_articol` deloc** — face `REPLACE` direct in
`crsFactura`:
```
REPLACE codmat WITH crsCodMat.codmat, denumire WITH crsCodmat.denumire, id_articol WITH crsCodmat.id_articol IN crsFactura
```
Acelasi tipar identic pe coloana `cDenumire` (`:19312-19317`). Daca S4 extinde acest `REPLACE` cu
restul campurilor din cursorul filtrat — asa cum recomanda raportul anterior la sectiunea 5, punctul
4 ("sa extinda REPLACE-ul... cu restul campurilor") — **`id_jtva_coloana` nu mai trece prin niciun
`do_initializeaza_articol`/`frm_articol_factura.Init`**, pentru ca acel `REPLACE` nu creeaza obiectul
`poArticol` si nu instantiaza formularul. Cursorul filtrat tot n-are `id_jtva_coloana` (varianta
filtrata a `cursor_preturi` pastreaza aceeasi lista de coloane — vezi
`s4_cautare_articole_server.md:220`), deci randul din `crsFactura` ar ramane cu valoarea implicita
de camp (`Append Blank`, nu exista un `REPLACE` explicit pe acea coloana) — **fara nicio derivare din
`proc_tvav`**.
La scriere, `adauga_articol_factura` (Oracle) cauta `ID_JTVA_COLOANA = V_ID_JTVA_COLOANA` in
`JTVA_COLOANE` pe ramura `ELSE` a `CASE`-ului sau (`ff_...PACK_FACTURARE.sql:5187-5198` — ramura care
se aplica exact tipurilor de lista de preturi 1/5/7/10/22/29, tipurile de transfer 23/45/48/49 si
altele care nu intra in ramurile speciale comanda/aviz/restaurant/contract-cu-pret-fix) si, daca nu
gaseste o potrivire, **arunca `RAISE_APPLICATION_ERROR(-20000, 'Nu a fost gasita cota de TVA!
(FACT-013 : ...)')`**. O valoare implicita de camp (tipic `0`, posibil `NULL` dupa `Append Blank`,
de verificat pe structura reala a `crsfactura`) ar produce fie acest -20000, fie (daca exista un rand
`JTVA_COLOANE.ID_JTVA_COLOANA=0`) o cota de TVA gresita scrisa tacut — amandoua ar fi un **bug nou,
introdus de S4**, nu unul preexistent.
**Recomandare concreta pentru proiectarea S4 (nu doar constatare):** varianta filtrata a cautarii
(oricare ar fi mecanismul concret — `combosql` extins sau altceva) trebuie sa continue sa treaca prin
`do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa (sau sa reproduca explicit
derivarea din `proc_tvav` daca se alege un `REPLACE` direct), nu doar sa extinda `REPLACE`-ul de azi
cu campurile brute din cursor. De pus explicit ca cerinta in proiectarea S4, nu ca detaliu care se
rezolva de la sine.
---
## Intrebarea 2 — `cursor_gestiune` (tipurile 23/41) fara buton "adauga tot"?
### 2.1 Vizibilitatea butonului pe cod — confirmata
`but_urmator_tot1` are **`Visible = .F.` la design-time**
(`ofacturare.vc2:11257-11266`, `ADD OBJECT 'But_urmator_tot1' AS but_urmator_tot WITH ... Visible =
.F. ...`). Devine vizibil doar prin `This.but_urmator_tot1.Visible = .T.` explicit, in 8 locuri din
`Do Case poDate.tip` din `Init` (`:15108-15248`):
| Tip | Vizibil? | Linia care il seteaza |
|---|---|---|
| `eProforma=1` | da | `:15113` |
| `lCopiere` | da | `:15120` |
| `1,5,7,10` (lista de preturi) | **nu** (fara linie) | — |
| `2,6` (contract) | **nu** | — |
| `3` (comanda) | da | `:15150` |
| `4` (avize) | da | `:15166` |
| `21,28,42,47` (aviz din comanda) | da | `:15177` |
| `22,29` (aviz din lista de preturi) | **nu** | — |
| **`23`** (transfer subunitati) | **nu** — are propriul `Case` (`:15187-15195`), dar niciun `Visible=.T.` in el | — |
| **`41`** (retur transfer) | **nu** — are propriul `Case` (`:15197-15205`), dar niciun `Visible=.T.` in el | — |
| `25` (transfer din comanda) | da | `:15215` |
| `26` (aviz din contract) | **nu** | — |
| `8,9` (retur) | da | `:15240` |
| `24` (aviz retur) | da | `:15245` |
**Confirmat: 23 si 41 au fiecare `Case` propriu in `Do Case`** (nu lipsesc din el, spre deosebire de
tipul 52, verificat separat intr-o cercetare anterioara ca "nu intra in niciun `Case`") — dar niciunul
din cele doua `Case`-uri nu seteaza `Visible = .T.`, deci butonul ramane la valoarea implicita
ascunsa. Concluzie identica cu lista de preturi (1/5/7/10): "adauga tot" e ascuns azi, nu doar
"neconfirmat".
### 2.2 Exista alt mecanism de adaugare in masa pe 23/41?
**Nu unul dedicat — dar exista mecanismul de baza, "adauga rand cu rand", disponibil pe orice tip.**
`But_urmator1` (singular, nu "tot") e `ADD OBJECT`-at neconditionat in definitia clasei
(`ofacturare.vc2:11237`), fara vreun `Visible=.F.` la design-time si fara sa fie atins de `Do Case`
de la 15108-15248 (acolo se atinge doar `but_urmator2`, aferent `grd_contracte`/contract, scos prin
`RemoveObject` pentru tipurile non-contract, `:15322-15323` — nu are legatura cu 23/41). Lantul e
`But_urmator1.Click` → `do_urmator()` → `Thisform.do_adauga_articol()` (`:14715`) — exact metoda cu
derivarea de TVA confirmata la intrebarea 1. Deci pe 23/41, ca si pe lista de preturi, operatorul
adauga **o linie o data**, prin acelasi buton/metoda folosit si azi pe tipurile de lista de preturi —
nu exista un "adauga tot" separat de recreat, pentru ca nu exista nici azi.
### 2.3 Cine mai foloseste `cursor_gestiune` — CORECTIE fata de premisa din plan
Rutarea reala e in `ofacturare.prg`, procedura `factureaza` (formularul **standard**,
`frm_facturare_articole` — cel lansat implicit), `Do Case` de la `:266-308`:
```
Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi -> cursor_preturi (:279-282)
Case Inlist(tnTip, 2, 26, 6, 52) && contract -> cursor_contract (:283-290)
Case Inlist(tnTip, 3, 21, 25, 28, 42, 47) && comenzi -> cursor_comanda (:292-293)
Case tnTip = 4 && din avize -> cursor_avize (:294-295)
Case Inlist(tnTip, 41) && transfer/retur -> cursor_gestiune (:296-298)
Case tnTip = 30 -> cursor_aviz_nir (:299-300)
Case tnTip = 27 -> cursor_lucrare (:302-304)
Case Inlist(tnTip, 8, 9, 24) -> cursor_retur (:306-307)
```
plus, mai sus in acelasi `Do Case` (`:271-278`): `Case Inlist(tnTip, 48, 49)` → **`cursor_articole_k`**
(procedura complet diferita, nu una din cele cinci) si `Case tnTip = 45` → **`cursor_preturi`**.
**Pe formularul standard: doar tipul 41 foloseste `cursor_gestiune`.** Tipul **23 foloseste de fapt
`cursor_preturi`**, grupat direct cu tipurile de lista de preturi (1,22,5,29,7,10) — asta explica de
ce, la 2.1, tipul 23 se comporta identic cu lista de preturi (acelasi cursor sursa). Tipurile **45 si
48/49 nu ating deloc `cursor_gestiune`**: 45 → `cursor_preturi`, 48/49 → `cursor_articole_k` (iese
din perimetrul celor cinci cursoare din S4). Premisa din plan ("`cursor_gestiune` — tipurile 23/41")
si mentiunea "45, 48, 49" (`s4_cautare_articole_server.md:344`, tabelul de vizibilitate) **descriu
corect vizibilitatea butonului** (toate aceste tipuri au "adauga tot" ascuns), dar **nu si sursa lor
de cursor** — nu toate trec prin `cursor_gestiune`.
**Pe formularul prototip** (`frm_facturare_articole2`/`factureaza2`, accesibil live doar cu optiunea
`gnFacturareNou=1` **si** alegerea explicita a operatorului la un dialog de confirmare,
`ofacturare.prg:88-93` — deci reachable in productie, nu cod mort, dar opt-in), rutarea **difera**:
`Case Inlist(tnTip, 23, 41)` (`ofacturare.prg:776-778`) cheama **impreuna** `cursor_gestiune` pentru
**ambele** tipuri. Pe acest formular, premisa din plan e exacta. Divergenta intre cele doua formulare
(23 pe `cursor_preturi` in cel standard, pe `cursor_gestiune` in prototip) nu pare intentionata — nu
exista niciun comentariu care s-o justifice — dar e in afara perimetrului acestei cercetari (read-only,
fara editare) si nu afecteaza concluzia de mai jos, valabila pe oricare din cei doi cursori (niciunul
nu are `id_jtva_coloana`, ambii sunt tratati identic de `do_adauga_articol`).
### 2.4 Concluzia care conteaza
**Dupa S4, pe tipurile 23/41 nu se pierde nimic functional.** "Adauga tot" e deja absent azi pe
ambele (confirmat pe cod, 2.1), iar adaugarea se face deja rand-cu-rand prin acelasi mecanism folosit
si pe lista de preturi (`but_urmator1`/`do_urmator`/`do_adauga_articol`, 2.2) — mecanism pe care S4 nu
il atinge (S4 schimba doar sursa cursorului incarcat la deschidere, nu calea de adaugare a unei linii
individuale). Aceeasi rezerva de la intrebarea 1 se aplica identic aici: valabil doar daca varianta
filtrata a cautarii trece prin `do_adauga_articol`, nu daca reproduce `REPLACE`-ul direct din
`combosql`.
---
## Handoff
Nu e necesara predare — ambele intrebari sunt inchise, cu dovada pe cod. Rezervele de la 1.4 si 2.4
(derivarea `id_jtva_coloana` trebuie sa treaca prin `do_adauga_articol`/`frm_articol_factura.Init`,
nu prin `REPLACE` direct tip `combosql`) sunt de dus mai departe in proiectarea S4, nu doar de
arhivat aici. Corectia de la 2.3 (doar tipul 41, nu si 23, foloseste `cursor_gestiune` pe formularul
standard) merita reflectata daca planul S4 mai citeaza undeva "cursor_gestiune (23/41)" ca sursa unica.