# 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).