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:
- Determina
lnIdTipDocdin textul combo-ului (FACTURA/PROFORMA/BON FISCAL/altfelFACTURA). - Cheama neconditionat
poDate.initializeaza_setari_document(...)cu un cod special (-101bon fiscal,-102proforma, saupoDate.Tipaltfel) — realoca setarile de document (serie/numar) pentru noul tip, inainte sa stie daca tipul chiar s-a schimbat. - Iesire timpurie daca tipul nu s-a schimbat:
IF poDate.nIdTipDoc = m.lnIdTipDoc THEN RETURN. - 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), apoipoDate.nIdTipDoc = m.lnIdTipDoc(declanseazanIdTipDoc_Assign, care actualizeazaeProforma/eBonFiscal), sipoGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)reincarca seriile disponibile pentru noul tip, populandclb_serie_act. - 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.eProformadevine0prinnIdTipDoc_Assign, corect.- La Termina, routing-ul alege
scrie_factura2/scrie_factura_avize(sectiunea "Descoperire centrala") — care chiar cheamacontabilizeaza_articolpentru fiecare linie. contabilizeaza_articolverifica exact sentinelaid_gestiune <> -1000(PACK:7472-7476) inainte de a chemadescarca_gestiune. Liniile ramase marcate-1000de 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_gestiunecand tipul revine la Factura in aceeasi sesiune. Singurul loc din tot codul citit unde gestionabilitatea se "restaureaza" ecursor_retur_documentla copiere (GESTIONABIL = B.IN_STOCcandV_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 incrsfactura.
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:
Case m.llCopieree prima ramura verificata in Do Case-ul de incarcare a cursorului (ofacturare.prg:266-268) — pentru orice document copiat, cursorul de baza e intotdeaunacursor_retur_document, indiferent de tipul original. Un document copiat nu (mai) e niciodata de tip3(comanda) dupa degradarea dindo_copiaza(sectiunea 2.1: degradare catre1,5,7,10,22,23— CORECTIE runda 13, cifra veche1,5,10,22era gresita; setul real e citit din primulCASE,ofacturare_comun.vc2:3693-3694) — deci nu poate ajunge in ramuraInlist(poDate.Tip,3,21,25,28,42,47)a luido_scrie_factura(ofacturare.vc2:14332-14338), care e singurul loc undeid_cconteaza pentru "cantitate ramasa" (Rol A, pers4_punct2_registru_cantitate_ramasa.md).do_stergepotriveste peid_c(ofacturare.vc2:14652-14655), dar aceasta potrivire conteaza doar daca ceva citestecrsarticoleca registru dupa aceea — ceea ce nu se intampla pe tipurile rezultate din copiere (1,5,7,10,22,23, toate in ramuraOtherwisea Do Case-ului de scriere, faraCalculate 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:
- Sentinela
id_gestiune = -1000incontabilizeaza_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). - Routing-ul
scrie_proformavs.scrie_factura2/scrie_factura_avize(ofacturare.vc2:14282-14300) — corect prin constructie, citestepoDate.eProformala momentul potrivit (Termina), nu are nevoie de nicio schimbare. - Cele cinci garzi
eProforma=0pe 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. - Raportul propriu (
PROFORMA/PROFORMA_VAL,ofacturare.prg:1638-1643) — alegerea ramane pepoDate.eProforma, neschimbata. do_copiaza(degradarea de tip) — ramane exact cum e, decizia S5b (D) o cere explicit ("degradarea de tip dindo_copiazaramane pentru copiere si nu se aplica la regenerare").copiere_factura->factureaza(tip, toFactura)— punctul de intrare ramane neschimbat.completeaza_setari_document— comentariul de la:370(nIdTipDocnecopiat) 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).cursor_retur_documentcuV_COPIERE=1(PACK:3949-4000) — restaurareaGESTIONABIL = B.IN_STOCla 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).- Numarul alocat la copiere e intotdeauna nou (
ofacturare.prg:208,211, neconditionat dellCopiere) — nimic de schimbat. - 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
- Confirma pe date reale linia exacta unde
id_gestiunedevine-1000pentru o linie de proforma (incertitudinea de la sectiunea 1.3) — headless, cu uncrsfacturaconstruit manual dinScatterpe o linie de proforma reala, inainte de orice alta schimbare de cod. Gata cand: se confirma cu certitudine (nu argument indirect) fie linia dindo_initializeaza_articol, fie alt punct, unde proprietateaid_gestiunea liniei devine efectiv-1000. - 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. - Leaga metoda de la pasul 2 de
do_schimba_tipdoc, pe ramura care duce spre Proforma, aplicata pe toate liniile deja prezente incrsfacturala momentul comutarii (sectiunea 4.1.c). Gata cand: pe un document cu linii deja adaugate ca Factura, comutarea combo-ului pe Proforma marcheazagestionabil=0/id_gestiune=-1000pe fiecare linie existenta, verificabil prin citirea directa acrsfacturadupa comutare. - 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_gestiunereale (varianta a, verificabil prindescarca_gestiuneapelat corect la emitere — vezi sectiunea 9), fie comutarea inapoi e refuzata/necesita confirmare explicita (varianta b). - 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). - 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).
- 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_fdoccu 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/RecordSourceraman artefacte, comportamentul real alLostFocuspe combo si al reincarcariiclb_serie_actcere harnessul UI vizibil. - Dialogul
frm_articol_facturavs.do_alege_stoc(sectiunea 1.3, decizia de dialog dupagestionabil) — 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(valorilegestionabil/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/poDatedupa apeluri directe (nu prin click) la metoda de marcare (pasul 2), lado_schimba_tipdoc, si la rutina drumului invers (pasul 4) — cu date de test pregatite manual (uncrsfacturacu 2-3 linii,poDate.eProformacomutat programatic inainte si dupa) — confirmagestionabil,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
- 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.
- 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-1000trimisa explicit ignora orice valoare veche), iar stergerea ar cere cod suplimentar fara beneficiu functional, doar cosmetic. - Incertitudinea ramasa din raportul-sursa (sectiunea 1.3: linia exacta unde
id_gestiunedevine-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. - 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.
- 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 directaVANZARI_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).