#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,610 +0,0 @@
# Proiectare S5b — proforma si copierea pe formularul unificat
Cercetare + proiectare READ-ONLY (fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara
commit, fara scriere Oracle — numai `SELECT`), pentru povestea **S5b** din
`docs\plan_13_unificare_formular_facturare.md` (sectiunea `#### S5b`, liniile 2507-2530), deciziile
10 si 11. Continua, fara sa reia, `docs\cercetare\s5b_proforma_descarcare_gestiune.md` (sursa de
adevar pentru mecanismul `gestionabil=0` / `id_gestiune=-1000`) si foloseste rezultatele din
`docs\cercetare\s4e_lista_preturi_pe_sursa.md` (coliziunea `id_c`) si
`docs\cercetare\s4_punct2_registru_cantitate_ramasa.md` (Rol A/B ale `crsarticole`).
**Status: cercetare + proiectare incheiate.**
Nu se ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`
(perimetrul altei sarcini). `COMUN\programe\ofacturare.prg` si `ofacturare_comun.prg` sunt citite,
nu editate — la fel `COMUN\clase\ofacturare.vc2` si `COMUN\clase\ofacturare_comun.vc2` (citire, nu
editare; scriere interzisa doar pe al doilea).
---
## Descoperire centrala, care rescrie premisa de risc a lui S5b
Sursa de adevar anterioara (`s5b_proforma_descarcare_gestiune.md`) a stabilit ca gate-ul pentru
proforma e sentinela `id_gestiune = -1000`, verificata **in interiorul** lui
`contabilizeaza_articol` (`PACK:7472-7476`). Cercetarea de fata a gasit un strat **mai devreme si
mai tare**: la `Termina`, `do_scrie_factura` **alege intre doua proceduri Oracle diferite** dupa
`poDate.eProforma`, inainte sa se uite la `poDate.tip` (`COMUN\clase\ofacturare.vc2:14282-14300`):
```
Do Case
Case poDate.eProforma = 1
* scrie_proforma are aceiasi parametri ca scrie_factura2
* salveaza doar in vanzari, nu si in contabilitate
lcSql = [{call pack_facturare.scrie_proforma(...)}]
Case poDate.Tip = 4
... lcSql = [{call pack_facturare.scrie_factura_avize(...)}]
Case Inlist(poDate.Tip,3,21,25,28,42,47)
... lcSql = [{call pack_facturare.scrie_factura2(...)}]
Otherwise
... lcSql = [{call pack_facturare.scrie_factura2(...)}]
Endcase
```
Comentariul din cod ("salveaza doar in vanzari, nu si in contabilitate") e literal adevarat, verificat
pe corpul Oracle: `pack_facturare.scrie_proforma` (`PACK:5637-5671`) cheama **doar**
`scrie_in_vanzari` (`PACK:13488-13760`) — care face `INSERT INTO VANZARI` (antet) **si**
`INSERT INTO VANZARI_DETALII ... SELECT FROM VANZARI_DETALII_TEMP` (liniile, cu `ID_GESTIUNE` asa
cum a ajuns in `TEMP`) — apoi marcheaza `VANZARI.EPROFORMA=1`. **`scrie_proforma` nu cheama
niciodata `contabilizeaza_articol`.** `scrie_factura2`/`scrie_factura_avize`/`scrie_aviz_retur` sunt
singurele trei proceduri care cheama `contabilizeaza_articol` (confirmat si in
`docs\plan_13_unificare_formular_facturare.md:298-310`, runda 11) — si niciuna din ele nu ruleaza
pentru `eProforma=1`.
**Consecinta:** pentru o proforma, `scrie_nota` (care scrie `NOTE_CONTABILE`) si `descarca_gestiune`
nu ruleaza **deloc** — nu pentru ca sentinela `-1000` le blocheaza pe fiecare linie (desi si asta
ramane adevarat, ca plasa suplimentara), ci pentru ca **intreaga functie care le cheama nu se
executa**. Sentinela `-1000` din `contabilizeaza_articol` conteaza doar cand `contabilizeaza_articol`
chiar ruleaza — adica exact pe drumul invers (vezi sectiunea 5), nu pe proforma insasi.
**De ce conteaza pentru S5b**: alegerea `scrie_proforma` vs. `scrie_factura2` se face **o singura
data, la Termina**, citind `poDate.eProforma` **in acel moment** — nu depinde de cand/cum a fost
comutat combo-ul, nici de valorile `gestionabil`/`id_gestiune` de pe liniile individuale. Asta e o
veste buna pentru un sens al comutarii (proforma -> ramane proforma la salvare: corect, indiferent ce
au liniile), dar **nu acopera** sensul opus (liniile marcate negestionabil sub proforma, apoi
documentul comutat inapoi la factura inainte de Termina) — acolo `scrie_factura2` **chiar** ruleaza,
si atunci sentinela `-1000` de pe liniile ramase de la proforma **chiar blocheaza** `descarca_gestiune`
pe un document care ar trebui sa descarce. Vezi sectiunea 5.
---
## 1. Fluxul de azi al proformei, cap-coada
**1.1 Alegerea tipului.** Proforma nu e o valoare in `pack_facturare.ntip` (tipul de business,
1-52) — e un atribut ortogonal, `poDate.nIdTipDoc` (5=FACTURA, 23=PROFORMA, 3=BON FISCAL,
6=AVIZ), setat din combo-ul "Tip document" (`Ct_clb_fdoc._combobox1`,
`RowSource = "FACTURA,PROFORMA,BON FISCAL"`, `ofacturare.vc2:8745-8754`). Setter-ul
`nIdTipDoc_Assign` (`COMUN\programe\ofacturare_comun.prg:593-600`) deriva boolean-ul in acelasi
moment: `This.eProforma = IIF(m.tnIdTipDoc = This.nIdTipDocProforma, 1, 0)`.
**1.2 Marcarea in masa la incarcarea cursorului.** In `factureaza()` (`COMUN\programe\ofacturare.prg`),
dupa ce cursorul sursa (indiferent care — lista de preturi, comanda, contract, aviz, retur) e adus in
`crsarticole` (Do Case pe `tnTip`, `:266-311`), **daca** `poDate.eProforma = 1`, se face un `UPDATE`
de masa peste tot cursorul (`:330-334`):
```
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
IF poDate.eProforma = 1
UPDATE (m.lcCursor) SET gestionabil = 0
GO TOP IN (m.lcCursor)
ENDIF
```
Acest pas ruleaza **o singura data**, la incarcare, **inainte** ca formularul de linii sa se
deschida — pentru ca la momentul lui `poDate.eProforma` e deja finala: combo-ul traieste in
`frm_date_factura`/`frm_date_aviz`, un dialog modal care se **inchide** (`ofrmceredate.Show()` la
`:235`, urmat de `Release ofrmceredate` la `:248`) **inainte** ca `Do Case`-ul de incarcare a
cursorului sa ruleze (`:266` e dupa `:248`). Tipul nu se mai poate schimba dupa acest punct, azi.
**1.3 Ce face `gestionabil=0` la nivel de linie.** Cand operatorul adauga o linie, ramura care alege
dialogul citeste exact acest camp (`ofacturare.vc2:13803-13809`):
```
Do Case
Case (poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45)
ofrmadarticol = Createobject("frm_articol_factura", lnCantitateMax, .F.)
Otherwise
Thisform.do_alege_stoc(...)
Endcase
```
`frm_articol_factura` e dialogul generic pentru orice articol negestionabil din toata aplicatia
(nu unul specific proformei) — ocoleste `do_alege_stoc` (alegerea lotului din stoc).
`do_initializeaza_articol` (`ofacturare.vc2:13618-13623`) seteaza `id_gestiune=-1000` **doar daca
proprietatea lipseste** pe obiectul articol (`Type(...) = "U"`):
```
If Type('toArticol.id_gestiune') = "U"
AddProperty(toArticol,'id_gestiune',-1000)
Endif
```
La scriere, `poArt.id_gestiune` merge direct ca `V_ID_GESTIUNE` catre
`pack_facturare.adauga_articol_factura` (`ofacturare.vc2:14069-14073`), care il traduce
(`PACK:5032-5034`: `IF V_ID_GESTIUNE <> -1000 THEN V_ID_GESTIUNE2 := V_ID_GESTIUNE; END IF;` —
altfel ramane `NULL`), scris in `VANZARI_DETALII_TEMP.ID_GESTIUNE`.
**Nota de incertitudine, mostenita din raportul-sursa**: linia exacta unde proprietatea
`id_gestiune` "lipseste" (`Type='U'`) pentru o linie de proforma scatter-uita din `crsfactura` nu a
fost confirmata direct pe date vii — `crsfactura` are `id_gestiune` ca si camp cu valoare implicita,
nu absent. Ramane argument indirect (fara `FACT-007` raportat in productie de ani), nu dovada de
executie. **De verificat cu prioritate la implementare**, pentru ca proiectarea de mai jos (sectiunea
4) muta acest mecanism dintr-un `UPDATE` de masa intr-o marcare per-linie, si trebuie sa stie exact
ce camp seteaza.
**1.4 Routingul la Termina.** Vezi "Descoperirea centrala" de mai sus: `scrie_proforma`, nu
`scrie_factura2`/`scrie_factura_avize` — fara nota contabila, fara descarcare de gestiune, indiferent
de tipul de business original.
**1.5 Raportul propriu.** `listeaza_ofacturare` alege raportul dupa `poDate.eProforma`
(`ofacturare.prg:1638-1643`):
```
Case (Between(poDate.tip, 1, 20) Or Inlist(poDate.tip, -1,-2,-3,-4,-8,-11,44,45,48,49,50,51,52)) And poDate.eProforma = 1
lcRaport = [PROFORMA]
lcRaportVal = [PROFORMA_VAL]
lcSetare = [PROFORMA]
```
Independent de forma formularului (unificat sau nu) — declansat de `poDate.eProforma` la momentul
listarii, care e populata corect din `nIdTipDoc_Assign` daca antetul reflecta starea finala.
**1.6 Garzile `eProforma = 0` pe atasamente (nu pe nota contabila).** Cinci guarde separate in
`listeaza_ofacturare`, toate de forma `poDate.nRelistare = 0 And poDate.eProforma = 0 ...`, controland
salvarea PDF-ului ca atasament arhivat (`export2pdf(..., poDate.cDocAtasate)` si
`poDate.scrieAtasamente()`), **nu** scrierea notei contabile (care e blocata la alt nivel, sectiunea
precedenta): `ofacturare.prg:1960` (factura in lei), `:1996` (aviz retur), `:2052` (factura in
valuta), `:2063` (recapitulatie), `:2103` (`scrieAtasamente()`, arhivarea propriu-zisa). **Corectie
fata de formularea din plan** (`plan_13_unificare_formular_facturare.md:548-549`, "fara nota
contabila si fara atasamente, prin garzile `eProforma=0`"): cele doua efecte sunt reale amandoua, dar
prin **mecanisme diferite** — nota contabila prin routing-ul `scrie_proforma`/`scrie_factura2`
(sectiunea "Descoperire centrala"), atasamentele prin aceste cinci guarde explicite. Nu schimba nimic
pentru proiectare, dar conteaza pentru cine cauta "garda de nota contabila" in cod si nu o gaseste la
liniile citate de plan.
**1.7 Relistarea pe cale separata.** `frm_facturi.do_listare` (`COMUN\clase\ofacturare_comun.vc2:7233-`)
reconstruieste `poDate` de la zero pentru un document deja emis, cu `poDate.eProforma = 1` setat
explicit (`:7262`) cand se relisteaza o proforma din grid — nu refoloseste obiectul `poDate` din
sesiunea de facturare curenta.
---
## 2. Fluxul de azi al copierii, cap-coada
**2.1 Degradarea de tip in `do_copiaza`.** `frm_facturi.do_copiaza`
(`COMUN\clase\ofacturare_comun.vc2:3628-3713`) mapeaza **orice** tip de business catre unul din
cinci "simple" — `T1` (lista de preturi), `T10` (lista de preturi valuta), `T5` (invoice),
`T22` (aviz din lista de preturi), sau `T1`/`T10` pentru transfer/altele — pe un `Do Case` explicit
peste `loFactura.tip` (`:3693-3708`). Factura/avizul din contract sau din comanda **nu se copiaza ca
atare** — devine o factura simpla din lista de preturi. Apoi `DO copiere_factura WITH loFactura IN
oproceduri_facturare.prg` (`:3710`).
**2.2 `copiere_factura` -> `factureaza(tip, toFactura)`.** `copiere_factura`
(`COMUN\programe\oproceduri_facturare.prg:150-153`) cheama `factureaza(loFactura.tip, loFactura)` —
`toFactura` (obiectul scatter-uit din `crsFacturi`) devine parametrul opțional al lui `factureaza`
(`COMUN\programe\ofacturare.prg:81-82`), care seteaza `llCopiere = (Type('toFactura') = 'O')`
(`:111`).
**2.3 Antetul precompletat, dar `nIdTipDoc` NU se propaga.**
`poDate.completeaza_setari_document(toFactura, .T.)` (`ofacturare.prg:204`, apelat cand `m.llCopiere`)
cheama `oDateFactura::completeaza_setari_document`
(`COMUN\programe\ofacturare_comun.prg:362-412`), ramura `tlFactura=.T.` (`:368-390`): copiaza delegat,
masina, sectie, agent, client, referinta la documentul sursa (`listaid = toDateAnterior.id_vanzare`) —
dar **linia `.nIdTipDoc = toDateAnterior.nIdTipDoc` e comentata** (`:370`,
`*.nIdTipDoc = toDateAnterior.nIdTipDoc`), deliberat. Documentul nou primeste `nIdTipDoc` implicit
dupa tipul degradat (`Do Case` la `ofacturare.prg:187-196`: `tnTip < 21` sau in
`{45,48,49,51,52}` => `5` FACTURA, altfel `6` AVIZ) — pentru tipurile degradate ale copierii (`T1=1`,
`T5=5`, `T10=10`, `T22=22`), toate `<21`, deci **`nIdTipDoc=5` (FACTURA) intotdeauna**, ceea ce prin
`nIdTipDoc_Assign` face **`poDate.eProforma = 0` pe documentul nou, indiferent daca sursa era
proforma**. Confirma direct pe cod ce raportul-sursa dedusese din trasare (`s5b_proforma_descarcare_gestiune.md` §3).
**2.4 Numar nou, intotdeauna.** `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` (`:211`)
ruleaza neconditionat, indiferent de copiere — copierea nu mosteneste numarul documentului sursa,
alege intotdeauna un numar nou din seria tipului nou (`FACTURA`).
**2.5 Cursorul de linii candidate — intotdeauna `cursor_retur_document`.** `Do Case`-ul care alege
cursorul Oracle de incarcat in `crsarticole` (`ofacturare.prg:266-308`) verifica **`Case m.llCopiere`
primul**, inaintea oricarei ramuri pe `tnTip`:
```
Case m.llCopiere
lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}]
```
al treilea parametru pozitional `1` e `V_COPIERE`. Documentul sursa (proforma sau nu) intra prin
`?poDate.listaid` (setat la `toDateAnterior.id_vanzare` in §2.3). `poDate.eProforma` trimis e cel al
**documentului nou** (`0`, per §2.3), nu al sursei.
**2.6 Gestionabilitatea se restaureaza la copiere.** `cursor_retur_document`
(`PACK_FACTURARE:3949-4000`) calculeaza `GESTIONABIL` cu exact aceasta expresie (`:3993-4000`):
```sql
(case
when V_PROFORMA = 1 then 0
when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE.IN_STOC, articolul real
else A.GESTIONABIL
end) AS GESTIONABIL,
```
Cu `V_PROFORMA=0` (documentul nou nu e proforma) si `V_COPIERE=1`, ramura activa e
`GESTIONABIL = B.IN_STOC` — valoarea reala din nomenclator, **indiferent daca documentul sursa era o
proforma cu toate liniile fortate `gestionabil=0`**. Liniile copiate dintr-o proforma redevin
gestionabile normal (daca articolul chiar e in stoc), trec prin `do_alege_stoc` la adaugare, primesc
`id_gestiune` real, si descarca gestiune normal la emiterea facturii rezultate.
**2.7 Nimic auto-adaugat.** Comentariu explicit in cod (`ofacturare.prg:97-99`, in ramura
`m.llCopiere` de mai jos in aceeasi functie):
```
* Nu adaug automat articolele din factura originala, ca sa dau posibilitate de modificare a cantitatilor, pretului, explicatiei
* ofrmdetaliifactura.do_adauga_tot()
```
Liniile documentului sursa raman doar **candidate** in `crsarticole` (populat de `cursor_retur_document`
la §2.5) — operatorul le adauga manual (sau cu "adauga tot"), la fel ca la orice document nou.
**2.8 Lista de preturi se adauga peste, doar la copiere.** Imediat dupa, tot in `factureaza()`
(`ofacturare.prg:454-473`, citat integral in `docs\cercetare\s4e_lista_preturi_pe_sursa.md` §1):
`pack_facturare.cursor_preturi(...)` intr-un cursor separat `crsArticoleTemp`, apoi
`SELECT crsArticole / APPEND FROM DBF(lcCursorTemp)` — lipeste lista de preturi completa peste
candidatii din documentul sursa, ca operatorul sa poata adauga si articole noi la copiere/modificare.
Vezi sectiunea 6 pentru siguranta acestui pas.
---
## 3. Combo-ul `Ct_clb_fdoc` si realocarea de serie/numar la comutare
**Azi, combo-ul traieste exclusiv in dialogul separat `frm_date_factura`/`frm_date_aviz`**
(clasa continuta in acelasi fisier text `ofacturare.vc2`, dar formular distinct de
`frm_facturare_articole`/`frm_facturare_articole2`, care contin gridul de linii). Handler-ul e legat
pe `LostFocus`, nu pe `InteractiveChange`:
```
PROCEDURE Ct_clb_fdoc._combobox1.LostFocus && ofacturare.vc2:9857-9859
thisform.do_schimba_tipdoc()
ENDPROC
```
`do_schimba_tipdoc` (`ofacturare.vc2:9396-9438`), pas cu pas:
1. Determina `lnIdTipDoc` din textul combo-ului (`FACTURA`/`PROFORMA`/`BON FISCAL`/altfel `FACTURA`).
2. Cheama neconditionat `poDate.initializeaza_setari_document(...)` cu un cod special (`-101` bon
fiscal, `-102` proforma, sau `poDate.Tip` altfel) — realoca setarile de document (serie/numar)
pentru noul tip, inainte sa stie daca tipul chiar s-a schimbat.
3. **Iesire timpurie daca tipul nu s-a schimbat**: `IF poDate.nIdTipDoc = m.lnIdTipDoc THEN RETURN`.
4. Daca s-a schimbat: `poDate.nract = 0`, `poDate.serie_act = ""` (curata selectia locala, nesalvata
nicaieri inca), `poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc)` (**elibereaza in pool numarul
vechi**, rezervat pe tipul vechi — nu se pierde, devine disponibil pentru alt document/alta sesiune;
documentul nu are inca niciun numar scris in baza, fiind pre-Termina), apoi
`poDate.nIdTipDoc = m.lnIdTipDoc` (declanseaza `nIdTipDoc_Assign`, care actualizeaza
`eProforma`/`eBonFiscal`), si `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` reincarca
seriile disponibile pentru noul tip, populand `clb_serie_act`.
5. **Numarul vechi nu se "reia" automat** — utilizatorul trebuie sa aleaga din nou serie/numar pentru
noul tip din controlul `clb_serie_act`, care s-a reincarcat la pasul 4.
**Ce NU atinge `do_schimba_tipdoc` azi**: `crsarticole`, `crsfactura`, `gestionabil`, `id_gestiune` —
nimic legat de linii. **Motivul e structural, nu o omisiune**: azi comutarea e posibila **doar**
inaintea oricarei linii, pentru ca `frm_date_factura` (unde traieste combo-ul) e un dialog modal
care se inchide (`Release ofrmceredate`, `ofacturare.prg:248`) **inainte** ca Do Case-ul de incarcare
a cursorului de linii sa ruleze (`:266`) — comutarea si incarcarea liniilor nu pot fi simultane azi.
**Ce se schimba cand combo-ul traieste in formularul unificat, cu gridul deja prezent**: exact
premisa care dispare — combo-ul devine comutabil **si dupa** ce linii exista deja in `crsfactura`.
`do_schimba_tipdoc` ramane corect pentru partea de serie/numar (pasii 1-5 de mai sus se aplica
identic, indiferent daca gridul are linii sau nu — realocarea de serie e independenta de continutul
documentului). **Ce lipseste e pasul 1.2/1.3 de mai sus (marcarea `gestionabil=0`/`id_gestiune=-1000`),
care azi nu are nevoie sa fie legat de comutare pentru ca nu poate fi comutare cu linii deja
prezente.** Vezi sectiunea 4.
---
## 4. Ce se rupe la unificare
**4.1 Marcarea negestionabil nu mai are un singur moment de executie.** Azi exista un singur loc
(`ofacturare.prg:330-334`, dupa incarcarea cursorului, inaintea deschiderii gridului) unde
`eProforma=1` implica `gestionabil=0` in masa. In formularul unificat, cu combo-ul viu in acelasi
ecran cu gridul (decizia 10), sunt **trei momente distincte** care trebuie sa garanteze acelasi
invariant, nu unul:
a. **La deschiderea initiala**, daca formularul porneste direct pe tip Proforma (echivalent cu azi
— se poate pastra `UPDATE` de masa pe cursorul candidat, daca inca exista un cursor de masa
pentru sursa respectiva dupa S4/S4e; pe sursele deja mutate pe cautare filtrata/`APPEND BLANK`
— S4e — nu mai exista cursor de masa de actualizat, marcarea trebuie facuta la construirea
fiecarui rand).
b. **La adaugarea unei linii noi cat timp `poDate.eProforma=1`** — deja identificat ca gol de
proiectare in `s5b_proforma_descarcare_gestiune.md` §7 ("trebuie reimplementat linie-cu-linie,
la momentul in care fiecare linie e adaugata"), confirmat aici ca valabil si pentru cazul in
care operatorul a **pornit** documentul ca proforma inainte de a adauga prima linie (nu doar
dupa un switch).
c. **La comutarea combo-ului DUPA ce linii exista deja in `crsfactura`** (cazul explicit cerut de
brief) — scenariu care azi nu poate exista, deci nu are cod de reutilizat. `do_schimba_tipdoc`
trebuie extins (sau o metoda noua chemata din el) sa parcurga liniile deja prezente in
`crsfactura` si sa aplice acelasi tratament ca la 1.2-1.3: `gestionabil=0`,
`id_gestiune=-1000`, pe fiecare linie existenta, **cand comutarea intra pe Proforma**.
*Corolarul necesar, netratat de nicio decizie/raport anterior*: pentru fiecare linie deja
adaugata prin `do_alege_stoc` (gestionabila, cu `id_gestiune` real ales de operator), acea
alegere de gestiune/lot devine irelevanta odata marcata negestionabil — de decis daca se
**pastreaza tacit** valoarea veche (inofensiv, pentru ca `id_gestiune=-1000` o inlocuieste
oricum in parametrul trimis la scriere) sau se **sterge explicit** din obiectul liniei, ca sa nu
induca in eroare un ecran care ar afisa gestiunea aleasa pe o linie acum negestionabila.
**4.2 Combo-ul viu tot timpul inseamna ca `eProforma` poate flutura de mai multe ori inainte de
Termina.** Azi tipul se alege o singura data, ireversibil (dialogul se inchide). In formularul
unificat, operatorul poate comuta Factura -> Proforma -> Factura de mai multe ori inainte de a apasa
`Termina`. Routing-ul de la Termina (sectiunea "Descoperire centrala") citeste `poDate.eProforma`
**doar la momentul apasarii** — corect pentru starea finala — dar **starea liniilor** (marcate sau nu
negestionabil, dupa istoricul comutarilor) nu se "reseteaza" singura la fiecare comutare, daca 4.1.c
nu implementeaza si drumul invers. Vezi sectiunea 5.
**4.3 Realocarea de serie/numar (sectiunea 3) ramane corecta ca atare**, dar UX-ul ei (curatarea
`clb_serie_act`, cererea catre operator sa aleaga din nou seria) trebuie sa functioneze si cu gridul
de linii vizibil pe acelasi ecran — nu identificat niciun cod care sa presupuna ca gridul e ascuns in
timpul realocarii; e o verificare vizuala, nu o problema de proiectare gasita in cod.
---
## 5. Drumul invers: proforma comutata inapoi in factura
**Acesta e riscul cel mai serios gasit in aceasta cercetare, nesemnalat explicit in rapoartele
anterioare.**
Scenariu: operatorul porneste documentul ca Proforma (sau comuta pe Proforma la un moment dat),
adauga linii — care, daca 4.1 e implementat corect, ajung marcate `gestionabil=0`/`id_gestiune=-1000`
pe `crsfactura`. Apoi, **inainte de Termina**, comuta combo-ul inapoi pe Factura.
- **`poDate.eProforma` devine `0`** prin `nIdTipDoc_Assign`, corect.
- **La Termina, routing-ul alege `scrie_factura2`/`scrie_factura_avize`** (sectiunea "Descoperire
centrala") — care **chiar cheama** `contabilizeaza_articol` pentru fiecare linie.
- **`contabilizeaza_articol` verifica exact sentinela `id_gestiune <> -1000`** (`PACK:7472-7476`)
inainte de a chema `descarca_gestiune`. Liniile ramase marcate `-1000` de cand documentul era
proforma **nu vor descarca gestiune**, desi documentul final e o factura reala, nu o proforma.
- **Nu exista azi niciun mecanism care sa refaca `gestionabil`/`id_gestiune`** cand tipul revine la
Factura in aceeasi sesiune. Singurul loc din tot codul citit unde gestionabilitatea se
"restaureaza" e `cursor_retur_document` la **copiere** (`GESTIONABIL = B.IN_STOC` cand
`V_COPIERE=1`, sectiunea 2.6) — mecanism legat de incarcarea unui cursor **nou**, la deschiderea
unui document **nou**, nu de o comutare in acelasi document, in aceeasi sesiune, pe linii deja
existente in `crsfactura`.
**Consecinta pe date**: o factura reala, emisa din formularul unificat dupa un du-te-vino prin
Proforma, poate iesi din sesiune cu stocul nedescarcat pentru articole care ar fi trebuit sa-l
descarce — silentios, fara eroare Oracle (sentinela `-1000` e o valoare valida pentru
`contabilizeaza_articol`, nu declanseaza nicio exceptie).
**Ce trebuie proiectat, obligatoriu, pentru ca decizia 10 sa fie sigura**: la comutarea **dinspre**
Proforma **catre** orice alt tip, liniile care au fost marcate negestionabil **exclusiv din cauza
proformei** (nu articole real negestionabile in nomenclator) trebuie sa-si recapete
`gestionabil`/`id_gestiune` reale, inainte de a ajunge la `Termina`. Doua variante, niciuna aleasa
inca:
a. **Re-interogare per linie** la comutare (analog cu `cursor_retur_document.GESTIONABIL`, dar
aplicat pe liniile deja din `crsfactura`, nu la incarcarea unui cursor nou) — cere un apel
Oracle (sau citire din nomenclator local, daca exista deja incarcat) per linie afectata, si
re-deschiderea alegerii de gestiune/lot pentru cele gestionabile (echivalentul lui
`do_alege_stoc`, care nu a rulat cand linia a fost adaugata sub Proforma).
b. **Blocarea comutarii inapoi** cand exista deja linii adaugate sub Proforma — cere confirmare
sau refuza tranzitia, obligand operatorul sa stearga liniile si sa le re-adauge sub noul tip.
Mai simplu de implementat, cu cost UX (utilizatorul pierde munca de introducere).
**Nu e o decizie de proiectare pe care raportul o ia in locul lui Marius** — vezi sectiunea 11,
punctul 1.
---
## 6. `id_c` la copiere — raspuns cu dovada
**Nu exista coliziune cu efect observabil pe drumul de copiere, azi.** Motivul, verificat direct pe
cod (nu presupus, extras din `s4e_lista_preturi_pe_sursa.md` §1, reconfirmat aici pentru S5b):
`ofacturare.prg:454-473` face `APPEND FROM` peste `crsArticole`, lipind lista de preturi
(`crsArticoleTemp`, numerotata `id_c` independent, cu `ROWNUM` propriu Oracle) peste continutul deja
prezent (documentul copiat, incarcat la §2.5 din `cursor_retur_document`, cu propriul `id_c`
independent). Cele doua seturi de `id_c` **se pot suprapune la nivel de valoare** — asta e adevarat,
tehnic. Dar coliziunea **nu are efect**, pentru doua motive independente, ambele verificate:
1. **`Case m.llCopiere` e prima ramura verificata** in Do Case-ul de incarcare a cursorului
(`ofacturare.prg:266-268`) — pentru orice document copiat, cursorul de baza e **intotdeauna**
`cursor_retur_document`, indiferent de tipul original. Un document copiat nu (mai) e niciodata de
tip `3` (comanda) dupa degradarea din `do_copiaza` (sectiunea 2.1: degradare catre
`1,5,7,10,22,23` — **CORECTIE runda 13**, cifra veche `1,5,10,22` era gresita; setul real e citit
din primul `CASE`, `ofacturare_comun.vc2:3693-3694`) —
deci **nu poate ajunge** in ramura `Inlist(poDate.Tip,3,21,25,28,42,47)` a lui `do_scrie_factura`
(`ofacturare.vc2:14332-14338`), care e singurul loc unde `id_c` conteaza pentru "cantitate
ramasa" (Rol A, per `s4_punct2_registru_cantitate_ramasa.md`).
2. **`do_sterge` potriveste pe `id_c`** (`ofacturare.vc2:14652-14655`), dar aceasta potrivire conteaza
doar daca ceva citeste `crsarticole` ca registru dupa aceea — ceea ce nu se intampla pe tipurile
rezultate din copiere (`1,5,7,10,22,23`, toate in ramura `Otherwise` a Do Case-ului de scriere, fara
`Calculate Sum(cantitate)`).
**Concluzie, cu aceeasi rezerva ca in raportul-sursa**: coliziunea de `id_c` exista **tehnic** in
datele din `crsArticole` dupa copiere, dar **nu are cale prin care sa produca un efect gresit**, atata
timp cat tipul rezultat din copiere ramane in setul degradat (`1,5,7,10,22,23` — niciodata
`3,21,25,28,42,47`).
**Aceasta concluzie ramane valabila neschimbata si in formularul unificat**, pentru ca nu depinde de
forma formularului — depinde doar de faptul ca `do_copiaza` degradeaza tipul inaintea oricarei
incarcari de cursor, mecanism care nu se propune sa fie schimbat de S5b (sectiunea 7).
**O singura conditie de pastrat, explicit**: daca vreo poveste viitoare schimba `do_copiaza` sa NU
mai degradeze tipul catre grupul "simplu" (de exemplu, sa permita copierea unei comenzi ca o comanda
noua, nu ca factura din lista de preturi), concluzia de mai sus **trebuie re-verificata** — riscul de
coliziune `id_c` descris in `s4e_lista_preturi_pe_sursa.md` verdict/punctul 1 ar deveni real.
---
## 7. Ce NU se atinge — lista explicita
Confirmate ca deja corecte si suficiente, fara nicio schimbare necesara pentru S5b:
1. **Sentinela `id_gestiune = -1000`** in `contabilizeaza_articol` (`PACK:7472-7476`) — ramane gate-ul
de aparare pentru orice linie care ajunge totusi la contabilizare cu aceasta valoare (drumul
invers, sectiunea 5, o foloseste ca sa NU descarce gestiune — ceea ce e exact problema de acolo,
nu ceva de "reparat" in sentinela insasi).
2. **Routing-ul `scrie_proforma` vs. `scrie_factura2`/`scrie_factura_avize`**
(`ofacturare.vc2:14282-14300`) — corect prin constructie, citeste `poDate.eProforma` la momentul
potrivit (Termina), nu are nevoie de nicio schimbare.
3. **Cele cinci garzi `eProforma=0` pe atasamente** (`ofacturare.prg:1960,1996,2052,2063,2103`) —
raman neschimbate, nu au legatura cu formularul (grid vs. dialog separat), doar cu momentul
listarii/salvarii PDF, care ramane acelasi apel indiferent de forma UI.
4. **Raportul propriu** (`PROFORMA`/`PROFORMA_VAL`, `ofacturare.prg:1638-1643`) — alegerea ramane pe
`poDate.eProforma`, neschimbata.
5. **`do_copiaza` (degradarea de tip)** — ramane exact cum e, decizia S5b (D) o cere explicit
("degradarea de tip din `do_copiaza` ramane pentru copiere si nu se aplica la regenerare").
6. **`copiere_factura` -> `factureaza(tip, toFactura)`** — punctul de intrare ramane neschimbat.
7. **`completeaza_setari_document`** — comentariul de la `:370` (`nIdTipDoc` necopiat) e deliberat si
ramane corect: documentul nou pleaca mereu ca Factura, indiferent de sursa, exact ce cere decizia
S5b (copierea deschide "document nou", cu antetul editabil, nu proforma mostenita).
8. **`cursor_retur_document` cu `V_COPIERE=1`** (`PACK:3949-4000`) — restaurarea `GESTIONABIL = B.IN_STOC`
la copiere ramane corecta si suficienta pentru cazul "copiere", fara nicio schimbare (nu e acelasi
mecanism cu comutarea in-sesiune de la sectiunea 5, care ramane un gol real).
9. **Numarul alocat la copiere e intotdeauna nou** (`ofacturare.prg:208,211`, neconditionat de
`llCopiere`) — nimic de schimbat.
10. **Alocarea de serie/numar a lui `do_schimba_tipdoc`** (sectiunea 3) — mecanismul de dealocare +
realocare ramane corect si reutilizabil ca atare in formularul unificat; doar absenta lui legata
de linii (sectiunile 4-5) e golul de acoperit.
---
## 8. Pasi de implementare, ordonati
1. **Confirma pe date reale linia exacta unde `id_gestiune` devine `-1000`** pentru o linie de
proforma (incertitudinea de la sectiunea 1.3) — headless, cu un `crsfactura` construit manual din
`Scatter` pe o linie de proforma reala, inainte de orice alta schimbare de cod.
*Gata cand:* se confirma cu certitudine (nu argument indirect) fie linia din
`do_initializeaza_articol`, fie alt punct, unde proprietatea `id_gestiune` a liniei devine
efectiv `-1000`.
2. **Extrage marcarea "linie negestionabila pentru proforma" intr-o metoda proprie**, reutilizabila
din trei locuri (sectiunea 4.1: incarcare initiala, adaugare linie noua, comutare combo pe linii
existente) — un singur loc de intretinut pentru regula `eProforma=1 => gestionabil=0,
id_gestiune=-1000`, nu trei copii.
*Gata cand:* toate cele trei puncte de intrare cheama aceeasi metoda, verificat prin grep pe
codul nou.
3. **Leaga metoda de la pasul 2 de `do_schimba_tipdoc`**, pe ramura care duce spre Proforma, aplicata
pe toate liniile deja prezente in `crsfactura` la momentul comutarii (sectiunea 4.1.c).
*Gata cand:* pe un document cu linii deja adaugate ca Factura, comutarea combo-ului pe Proforma
marcheaza `gestionabil=0`/`id_gestiune=-1000` pe fiecare linie existenta, verificabil prin citirea
directa a `crsfactura` dupa comutare.
4. **Proiecteaza si implementeaza drumul invers** (sectiunea 5) — varianta (a) sau (b), decizie a lui
Marius (sectiunea 11, punctul 1). *Gata cand:* pe un document cu linii adaugate sub Proforma,
comutat inapoi pe Factura inainte de Termina, fie liniile isi recapata `gestionabil`/`id_gestiune`
reale (varianta a, verificabil prin `descarca_gestiune` apelat corect la emitere — vezi sectiunea
9), fie comutarea inapoi e refuzata/necesita confirmare explicita (varianta b).
5. **Verifica riscul de la sectiunea 4.1, corolarul**: decide daca `id_gestiune`/lotul ales anterior pe
o linie acum negestionabila se pastreaza tacit sau se sterge din obiectul liniei — implementeaza
alegerea si documenteaz-o in cod (un rand de comentariu, per conventia proiectului).
6. **Regresie pe copiere**, fara nicio schimbare de cod asteptata (sectiunile 2, 6, 7) — pas de
verificare, nu de implementare: confirma ca formularul unificat, rulat pe drumul de copiere, produce
acelasi rezultat ca azi (sectiunea 9).
7. **Regresie pe proforma simpla** (fara comutare, document pornit direct ca Proforma) — confirma ca
pasii 2-3 nu au schimbat comportamentul cazului deja corect azi.
*Depinde de:* S5 (acoperirea tipurilor), S4/S4e (forma finala a incarcarii liniilor — pasul 2 trebuie
sa stie daca opereaza pe un cursor de masa sau pe randuri individuale, dupa ce alte povesti decid
asta pentru fiecare sursa).
---
## 9. Cum se verifica
**Proba ceruta de plan**: o proforma emisa din formularul unificat nu produce nota contabila si se
listeaza pe raportul ei; o copie produce un document nou cu numar nou si acelasi continut.
**Interogari SQL concrete** (numai `SELECT`, rulate cu unealta PowerShell + `sqlplus.exe`, per
mediul de proiect):
```sql
-- 1. Proforma emisa din formularul unificat: fara nota contabila, fara descarcare de gestiune
SELECT id_vanzare, tip, eproforma, sters FROM vanzari WHERE id_vanzare = :ID_TEST;
-- eproforma trebuie sa fie 1
SELECT COUNT(*) FROM vanzari_detalii WHERE id_vanzare = :ID_TEST;
-- trebuie sa coincida cu numarul de linii adaugate (liniile SE scriu, doar nu se contabilizeaza)
SELECT id_vanzare_det, id_articol, id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST;
-- id_gestiune trebuie sa fie NULL pe fiecare linie (sentinela -1000 tradusa de adauga_articol_factura)
SELECT COUNT(*) FROM note_contabile nc
JOIN act a ON a.id_fact = nc.id_fact -- sau echivalentul legaturii act/nota folosite in schema
WHERE a.cod = (SELECT cod FROM vanzari WHERE id_vanzare = :ID_TEST);
-- trebuie sa fie 0 -- nicio nota contabila generata
-- 2. Fara descarcare de gestiune: RUL nu are miscare noua dupa emiterea proformei
SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE AND id_articol IN (:articolele_testate);
-- trebuie sa fie identic cu inainte de emitere (0 randuri noi legate de acest document)
-- 3. Copie: document nou, numar nou, acelasi continut
SELECT id_vanzare, numar_act, serie_act, eproforma FROM vanzari WHERE id_vanzare = :ID_COPIE;
-- eproforma = 0; numar_act/serie_act diferite de documentul sursa
SELECT vd.id_articol, vd.cantitate, vd.pret, vd.id_gestiune
FROM vanzari_detalii vd WHERE vd.id_vanzare = :ID_COPIE
ORDER BY vd.id_articol;
-- comparat linie cu linie cu vanzari_detalii al documentului sursa (:ID_TEST) -- acelasi articol/cantitate/pret;
-- id_gestiune insa REAL (nu NULL), daca articolul e gestionabil -- confirma restaurarea de la sectiunea 2.6
-- 4. Drumul invers (sectiunea 5, dupa ce varianta e implementata): factura emisa dupa un du-te-vino prin Proforma
-- descarca gestiune normal, ca orice factura
SELECT id_vanzare, eproforma FROM vanzari WHERE id_vanzare = :ID_TEST_INVERS;
-- eproforma = 0
SELECT id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST_INVERS;
-- NU mai e NULL pe liniile gestionabile -- confirma ca varianta aleasa la pasul 4 (sectiunea 8) functioneaza
SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE_INVERS AND id_articol IN (:articolele_testate);
-- trebuie sa arate miscare noua -- gestiunea s-a descarcat
```
**Protocol**: fiecare interogare rulata **inainte si dupa** implementare, pe date de test resetate
identic, per memoria de proiect "zero cazuri in date nu e dovada" — minim un caz per scenariu descris
mai sus (proforma simpla, proforma cu switch la comutare, copiere din proforma, drumul invers),
nu doar "a mers o data".
---
## 10. Ce nu se poate testa headless
- **Comutarea combo-ului `Ct_clb_fdoc` cu gridul de linii vizibil in acelasi ecran** — capcana deja
confirmata pe acest proiect (`grid-coloane-nu-se-materializeaza-headless`, memorie de proiect):
sub `-A -T`, `ColumnCount`/`RecordSource` raman artefacte, comportamentul real al `LostFocus`
pe combo si al reincarcarii `clb_serie_act` cere harnessul UI vizibil.
- **Dialogul `frm_articol_factura` vs. `do_alege_stoc`** (sectiunea 1.3, decizia de dialog dupa
`gestionabil`) — depinde de randare de formular modal, nu apelabil direct fara UI.
- **Verificarea vizuala ca liniile deja adaugate isi schimba starea la comutare** (sectiunea 4.1.c) —
daca implementarea alege sa arate un indicator vizual (de exemplu, o coloana/culoare care marcheaza
"negestionabil"), acel indicator nu se verifica headless; **continutul** `crsfactura` (valorile
`gestionabil`/`id_gestiune`) **se poate** verifica headless, cu apeluri directe la metoda noua din
pasul 2 (sectiunea 8), fara sa deschida formularul.
- **Realocarea vizuala a seriei/numarului in `clb_serie_act`** — control de UI, comportamentul lui de
reincarcare (`do_initializeaza`) nu se verifica fara randare.
- **Ce se poate verifica headless, direct**: continutul `crsfactura`/`poDate` dupa apeluri directe
(nu prin click) la metoda de marcare (pasul 2), la `do_schimba_tipdoc`, si la rutina drumului
invers (pasul 4) — cu date de test pregatite manual (un `crsfactura` cu 2-3 linii, `poDate.eProforma`
comutat programatic inainte si dupa) — confirma `gestionabil`, `id_gestiune`, `poDate.eProforma`,
fara sa deschida formularul. La fel, toate interogarile SQL din sectiunea 9 sunt verificabile
headless (SQL direct, fara UI).
---
## 11. Riscuri si decizii ramase lui Marius
1. **Drumul invers (sectiunea 5) — varianta (a) sau (b).** Cel mai important punct deschis al acestui
raport: fara o decizie explicita aici, decizia 10 din plan ("proforma foloseste acelasi formular")
ramane incompleta — un document care trece prin Proforma si revine la Factura in aceeasi sesiune
poate iesi cu stocul nedescarcat, silentios. **Recomandare**: varianta (a) (re-interogare si
re-deschidere a alegerii de gestiune la comutarea inapoi), pentru ca varianta (b) (blocarea
comutarii) contrazice explicit decizia 10 ("combo-ul ramane in antetul unificat", implicit
liber de folosit in ambele sensuri) si ar surprinde operatorul cu o pierdere de munca fara
avertisment prealabil la momentul comutarii spre Proforma.
2. **Ce se intampla cu `id_gestiune`/lotul ales anterior pe o linie marcata ulterior negestionabil**
(sectiunea 4.1, corolarul) — pastrare tacita (mai simplu, fara cod nou) sau stergere explicita
(mai clar pentru un ecran viitor care ar afisa gestiunea). **Recomandare**: pastrare tacita — nu
ajunge niciodata la server oricum (sentinela `-1000` trimisa explicit ignora orice valoare veche),
iar stergerea ar cere cod suplimentar fara beneficiu functional, doar cosmetic.
3. **Incertitudinea ramasa din raportul-sursa** (sectiunea 1.3: linia exacta unde `id_gestiune` devine
`-1000`) — necesara inainte de a scrie codul pasului 2 (sectiunea 8), nu doar o curiozitate.
Nu e o decizie de produs, e o verificare tehnica obligatorie inaintea implementarii.
4. **Indicator vizual pentru liniile marcate negestionabil la comutare** (mentionat la sectiunea 10) —
optional, nu cerut explicit de plan; de decis daca merita un semnal in grid (culoare/iconita) sau
ramane invizibil pana la emitere. Nu blocheaza nimic, dar afecteaza UX-ul comutarii repetate.
5. **Testarea pe date reale a `FACT-007`** (mentionata in raportul-sursa ca argument indirect,
nu dovada) — daca Marius vrea certitudine completa inainte de implementare, un test manual pe
mediu de dezvoltare (proforma cu articol gestionabil din comanda, verificare directa
`VANZARI_DETALII.ID_GESTIUNE IS NULL`) inchide definitiv acest gol, in afara perimetrului
read-only al acestei cercetari.
---
## Handoff
Cercetare + proiectare incheiate intr-o singura sesiune, fara sa fie nevoie de predare de context.
Toate cele 11 sectiuni cerute de brief sunt complete, cu `fisier:linie` verificat direct pe fisierele
text reale (`ofacturare.vc2`, `ofacturare_comun.vc2`, `ofacturare.prg`, `ofacturare_comun.prg` — nu
`.bak`) si pe corpul `PACK_FACTURARE` de pe `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`.
**Descoperirea centrala** (sectiunea "Descoperire centrala"): mecanismul care blocheaza nota
contabila pe proforma nu e (doar) sentinela `-1000` din `contabilizeaza_articol`, ci alegerea
`scrie_proforma` (fara contabilizare deloc) vs. `scrie_factura2`/`scrie_factura_avize` (cu
contabilizare) in `do_scrie_factura`, facuta dupa `poDate.eProforma` la Termina. Aceasta descoperire
schimba unde trebuie sa se concentreze grija de proiectare: nu pe "cum ajunge `-1000` la server"
(deja acoperit corect de codul existent), ci pe **ce se intampla cu liniile deja marcate `-1000` cand
documentul nu mai e proforma la Termina** — exact riscul din sectiunea 5, singurul loc unde sentinela
chiar conteaza si azi nu exista nicio plasa.
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe
Oracle (doar `SELECT`/`Read`/`Grep` pe fisiere de pe disc).