Files
roafacturare/docs/cercetare/s5b_proiectare_proforma_copiere.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git,
nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de
cercetare pe care se sprijina modificarile din cod. O stergere acolo era
definitiva.

Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au
fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele
fisierelor de test la care se refereau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:17:17 +03:00

40 KiB

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):

(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):

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