Files
roafacturare/docs/cercetare/verif_proforma_alegere_stoc.md
2026-09-09 22:19:22 +03:00

22 KiB

Verificare — alegerea stocului pe proforma (runda de verificare)

Investigatie read-only, 10.08.2026. Raspunde la 4 intrebari punctuale cerute de team-lead, pentru decizia de proiectare S5b (comutare FACTURA <-> PROFORMA cu linii deja adaugate).

Continua fara sa reia: s5b_proforma_descarcare_gestiune.md (mecanismul gestionabil=0 / id_gestiune=-1000) si s5b_proiectare_proforma_copiere.md (proiectarea de runda 12, care a descoperit deja ca scrie_proforma nu cheama contabilizeaza_articol deloc).

Nicio modificare de cod, niciun git_sync.ps1/txt2vcx.ps1, niciun commit, nicio scriere Oracle (numai SELECT/Read/Grep pe fisiere de pe disc). Sursa Oracle folosita: D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (citat mai jos PACK:linie).

Status: COMPLET.


Rezumat executiv

  1. Da, pe calea principala si pe toate celelalte surse (cu exceptia copierii), stocul NU se alege la adaugarea unei linii pe proforma azi — pentru ca gestionabil a fost deja fortat pe 0 in VFP, in masa, inainte ca gridul de linii sa se deschida. Cand ruleaza Do Case-ul de la ofacturare.vc2:13803, poArticol.gestionabil e deja 0, nu valoarea reala din nomenclator.
  2. Marcarea negestionabila are un singur mecanism activ azi, exclusiv in VFP: UPDATE (m.lcCursor) SET gestionabil = 0 (ofacturare.prg:333-336), rulat in interiorul functiei factureaza(), imediat dupa incarcarea cursorului candidat (crsarticole) si inainte de construirea crsfactura (creeaza_facturacrs, linia 338) — adica la deschiderea documentului / incarcarea liniilor candidate, nu la Termina/salvare. Exista si un al doilea mecanism, in Oracle, in cursor_retur_document (PACK:3993-4000, CASE WHEN V_PROFORMA=1 THEN 0 ...), dar acesta e mort in fluxul curent: cursor_retur_document se cheama dintr-un singur loc (ofacturare.prg:268, ramura de copiere), cu V_PROFORMA = poDate.eProforma al documentului nou, care e intotdeauna 0 dupa copiere (nIdTipDoc nu se propaga de la sursa, ofacturare_comun.prg:370) — deci ramura V_PROFORMA=1 nu se activeaza niciodata azi. Nu e o contradictie intre cele doua rapoarte anterioare — sunt doua mecanisme reale in cod, dar doar unul (cel VFP) e activ pe traseul de creare a unei proforme; cel Oracle exista doar pentru copiere si acolo nu se declanseaza cu valoarea curenta a parametrului.
  3. pret_achizitie depinde de sursa: pe calea principala (cursor_preturi, lista de preturi) NU se completeaza deloc din cursor (coloana nici nu exista in SELECT-ul lui cursor_preturi) — ramane 0, prin acelasi tipar de valoare implicita ca la id_gestiune (do_initializeaza_articol). Pe sursele care incarca dintr-un document existent (cursor_avize, cursor_retur_document — avize si copiere), PRET_ACHIZITIE e selectat direct din VANZARI_DETALII, deci ajunge real.
  4. Daca proforma ar pastra id_gestiune real pana la salvare, nimic din contabilizare/stoc nu s-ar strica — pentru ca blocajul real nu e sentinela -1000, ci faptul ca scrie_proforma (calea Oracle aleasa la Termina cand eProforma=1) nu cheama niciodata contabilizeaza_articol/descarca_gestiune (confirmat deja in raportul-sursa de runda 12). adauga_articol_factura (care ar primi V_ID_GESTIUNE real) se cheama oricum, neconditionat de eProforma, pentru orice linie — deci un id_gestiune real ar ajunge fara probleme in VANZARI_DETALII_TEMP/VANZARI_DETALII, fara sa declanseze nimic in plus. do_alege_stoc nu rezerva stoc real — face doar un SELECT (cursoare cursor_gestiuni_articol*) si scade local, in memorie, cantitatile deja puse pe documentul curent (crsfactura), ca operatorul sa nu aleaga de doua ori acelasi lot; nu scrie nimic in Oracle. Deci lasarea lui do_alege_stoc sa ruleze pe proforma nu ar bloca stoc pentru nimeni. Singurul risc real gasit e cel deja semnalat in s5b_proiectare_proforma_copiere.md §5 (drumul invers, proforma -> factura in aceeasi sesiune), care ramane valabil indiferent de aceasta decizie.

Intrebarea 1 — se alege stocul la adaugarea unui articol pe proforma azi?

Raspuns: NU, pe niciuna dintre caile de creare directa a unei proforme (nu prin copiere).

Calea principala — cursor_preturi

cursor_preturi (PACK:2138-2646) e apelat pentru tnTip in {45, 1, 22, 5, 29, 7, 10, 23} (ofacturare.prg:275-282 — lista de preturi, inclusiv restaurant), adica cea mai comuna sursa pentru o proforma noua. SELECT-ul lui cursor_preturi intoarce GESTIONABIL ca C.IN_STOC AS GESTIONABIL (PACK:2192, :2297 — valoarea reala din NOM_ARTICOLE, fara nicio constienta de proforma; cursor_preturi nu are parametru V_PROFORMA, confirmat prin grep pe tot fisierul: V_PROFORMA apare doar la declaratia si corpul lui cursor_retur_document, PACK:408, 3939, 3944, 3952, 3957, 3994).

Insa, imediat dupa ce acest cursor e adus in crsarticole in VFP, inainte ca operatorul sa apuce sa vada gridul de linii:

COMUN\programe\ofacturare.prg:330-336
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
* 12.03.2021
IF poDate.eProforma = 1
    UPDATE (m.lcCursor) SET gestionabil  = 0
    GO TOP IN (m.lcCursor)
ENDIF

m.lcCursor = crsarticole (setat la :310). Acest bloc ruleaza dupa Do Case-ul de incarcare (:266-308) si inainte de creeaza_facturacrs([crsfactura]) (:338) — adica la compunerea listei de articole candidate, nu la salvare. Cand operatorul adauga o linie mai tarziu, Do Case-ul care alege dialogul (ofacturare.vc2:13803-13809) citeste poArticol.gestionabil, care e deja 0 pentru toate liniile candidate (setat in masa mai sus), deci intra pe ramura frm_articol_factura, niciodata pe Thisform.do_alege_stoc.

Celelalte cursoare (fara copiere)

Verificat direct in PACK_FACTURARE, niciunul din urmatoarele cursoare, folosite pentru celelalte surse ale unei proforme noi, nu are parametru V_PROFORMA si nici nu calculeaza GESTIONABIL in functie de proforma — toate intorc valoarea reala din nomenclator/contract:

Cursor Apelat pentru (tnTip) GESTIONABIL in SELECT Linie
cursor_preturi 45,1,22,5,29,7,10,23 C.IN_STOC PACK:2192,2297
cursor_contract 2,26,6,52 A.GESTIONABIL (coloana reala) PACK:2411,2487,2572
cursor_comanda 3,21,25,28,42,47 E.IN_STOC PACK:2789
cursor_lucrare 27 C.IN_STOC PACK:3029
cursor_articole_k 48,49 C.IN_STOC PACK:3117
cursor_avize 4 C.IN_STOC PACK:3634
cursor_aviz_nir 30 0 (hardcodat) PACK:3745
cursor_gestiune 41 A.GESTIONABIL PACK:4104
cursor_retur 8,9,24 (nu are coloana GESTIONABIL separata — vezi nota) PACK:3934-3948

Pentru toate aceste surse, aceeasi bucla ofacturare.prg:330-336 e singurul loc care forteaza gestionabil=0 cand poDate.eProforma=1 — mecanismul e identic indiferent de sursa, pentru ca UPDATE (m.lcCursor) SET gestionabil = 0 ruleaza pe crsarticole dupa orice ramura a Do Case-ului de la :266-308, nu doar pe ramura lista-de-preturi.

Ramura de copiere — singura cu parametru V_PROFORMA in Oracle

Case m.llCopiere (ofacturare.prg:267-268) e singura ramura care apeleaza cursor_retur_document, singurul cursor cu parametru V_PROFORMA. Insa la copiere, documentul nou pleaca intotdeauna cu eProforma=0 (nIdTipDoc explicit necopiat, ofacturare_comun.prg:370 — deja stabilit in s5b_proiectare_proforma_copiere.md §2.3), deci V_PROFORMA trimis e 0, iar ramura GESTIONABIL = B.IN_STOC (valoarea reala) se activeaza, nu WHEN V_PROFORMA=1 THEN 0. Asta e comportamentul corect si dorit la copiere (liniile redevin gestionabile) — dar confirma ca V_PROFORMA=1 nu apare niciodata cu valoarea 1 in vreun apel real azi (vezi Intrebarea 2).

Concluzie Intrebarea 1: pe orice cale de creare directa a unei proforme (nu copiere), la momentul Do Case-ului de adaugare a liniei (ofacturare.vc2:13803), poArticol.gestionabil e deja 0 — fortat de VFP la incarcare, nu valoarea reala din nomenclator. do_alege_stoc nu ruleaza niciodata pentru o linie noua adaugata sub eProforma=1, indiferent de sursa.


Intrebarea 2 — unde exact se face marcarea negestionabila si CAND?

Doua locuri in cod, dar un singur loc activ azi:

2.1 VFP — activ, la incarcarea documentului (nu la salvare)

COMUN\programe\ofacturare.prg:333-336, in interiorul procedurii factureaza(). Secventa completa in factureaza():

  1. :266-308 — Do Case pe tnTip/llCopiere, alege cursorul Oracle si il aduce in crsarticole.
  2. :311 — goExecutor.oExecute(lcSqlCursor, lcCursor) — executa efectiv apelul Oracle.
  3. :324-336 — daca sunt randuri, si daca poDate.eProforma = 1: UPDATE crsarticole SET gestionabil = 0.
  4. :338 — abia acum creeaza_facturacrs([crsfactura]) construieste cursorul gol de linii ale documentului; liniile candidate (crsarticole, deja marcate) raman disponibile pentru ca operatorul sa aleaga din ele.

Acest pas ruleaza la deschiderea ecranului de adaugare articole, mult inainte de Termina (do_scrie_factura, apelat doar la click pe butonul de finalizare — vezi 2.3 mai jos). Nu exista niciun UPDATE/REPLACE gestionabil With 0 la salvare in do_scrie_factura sau do_scrie_articole — am cautat explicit gestionabil in ambele metode (ofacturare.vc2:14197-14380,:13967-14150) si singura referinta la gestionabil in acea zona e citirea lui la Do Case-ul din :13803 (decizia de dialog), nu o scriere.

2.2 Oracle — exista in cod, dar mort in fluxul curent

cursor_retur_document (PACK:3949-4064), branch la PACK:3993-4000:

(case
   when V_PROFORMA = 1 then 0
   when V_COPIERE = 1 then B.IN_STOC
   else A.GESTIONABIL
 end) AS GESTIONABIL,

cu comentariu explicit in cod: -- V_PROFORMA: Daca este proforma (1), fac articolele negestionabile sa pot alege orice cantitate (PACK:3957). Acest cod exista si e corect scris, dar cursor_retur_document are un singur punct de apel in tot codul VFP (confirmat prin cautare in ofacturare.prg, singura potrivire e la :268; potrivirile suplimentare gasite in COMUN\.svn\pristine\* sunt copii istorice ale aceleiasi linii, nu apeluri suplimentare), si acolo V_PROFORMA trimis e poDate.eProforma al documentului nou din copiere, care e intotdeauna 0 (Intrebarea 1). Deci acest branch Oracle nu se activeaza niciodata cu valoarea 1 in fluxul curent — e cod mort din perspectiva efectului observabil, desi sintactic corect si prezent.

2.3 Clarificare pe "inainte de compunerea documentului" vs. "la salvare"

Formularea raportului de runda 9 ("VFP marcheaza toate liniile proformei negestionabile inainte de compunerea documentului") e corecta — "compunerea documentului" inseamna acolo constructia listei de articole candidate/crsfactura (pasul 4 de mai sus), care se intampla la deschiderea ecranului de facturare, nu la apasarea Termina. Nu exista o contradictie reala cu mentiunea V_PROFORMA din cursor_retur_document din brief — acel CASE Oracle e un mecanism separat, pentru un cursor diferit (candidati la copiere), care azi nu se declanseaza niciodata cu V_PROFORMA=1. Ambele afirmatii din surse sunt adevarate simultan, pentru ca descriu lucruri diferite.

Concluzie Intrebarea 2: singurul mecanism activ e UPDATE de masa in VFP (ofacturare.prg:333-336), care ruleaza in factureaza(), la incarcarea/compunerea listei de articole candidate — cu mult inainte de Termina/do_scrie_factura. Mecanismul Oracle din cursor_retur_document exista in cod dar nu se activeaza cu valoarea curenta a parametrilor.


Intrebarea 3 — se completeaza pret_achizitie pe liniile de proforma?

Depinde strict de sursa cursorului, la fel ca la gestionabil — dar cu rezultat opus pentru calea principala.

Ce cursoare selecteaza PRET_ACHIZITIE

Cautare directa in PACK_FACTURARE (grep PRET_ACHIZITIE): coloana nu apare deloc in cursor_preturi (PACK:2138-2646), cursor_contract (:2646-2952), cursor_comanda (:2952-3173), cursor_lucrare (:3173-3595), cursor_articole_k (:3595-3703), cursor_gestiune (:4158-4349). Apare doar in:

  • cursor_avize (in jurul liniilor PACK:3754-3864 — sursa VANZARI_DETALII/RUL, articol provenit dintr-un aviz existent);
  • cursor_retur_document (PACK:4026, A.PRET_ACHIZITIE direct din VANZARI_DETALII, folosit la copiere).

Ce se intampla in VFP cand lipseste

crsfactura (structura din creeaza_facturacrs, ofacturare_comun.prg:1777) are un camp pret_achizitie in schema — dar o linie noua nu se construieste prin copiere in masa din crsarticole, ci prin Scatter/Gather pe obiectul poArticol, populat cand utilizatorul alege un articol din grid. do_initializeaza_articol (ofacturare.vc2:13715-13719), apelat pentru orice linie care trece prin frm_articol_factura (ramura negestionabila, deci si orice linie de proforma):

If Type('toArticol.pret_achizitie')="U"
    AddProperty(toArticol,'pret_achizitie',0)
Endif
If Isnull(toArticol.pret_achizitie)
    toArticol.pret_achizitie = 0
Endif

Deci: pe calea principala (lista de preturi), pret_achizitie ramane 0 pe liniile de proforma — campul nici nu exista in crsarticole sursa (cursorul Oracle nu-l selecteaza), asa ca proprietatea lipseste pe poArticol si do_initializeaza_articol o creeaza cu valoare implicita 0, exact acelasi tipar defensiv folosit pentru id_gestiune=-1000 (aceeasi metoda, linii apropiate, :13618-13623 vs :13715-13719).

Pe sursele care carata un document existent (cursor_avize, si cursor_retur_document la copiere), pret_achizitie e populat real din VANZARI_DETALII.PRET_ACHIZITIE a documentului sursa, deci proprietatea exista pe poArticol si do_initializeaza_articol nu o suprascrie.

Unde ajunge

Indiferent de valoare (0 sau reala), poArt.pret_achizitie merge neschimbat catre Oracle ca parametru pozitional in apelul catre adauga_articol_factura:

ofacturare.vc2:14073
... + Alltrim(Str(poArt.pret_achizitie,18,gnPPret)) + [,] + ...

primit ca V_PRET_ACHIZITIE_TEMP (PACK:4995), copiat direct V_PRET_ACHIZITIE := V_PRET_ACHIZITIE_TEMP (PACK:5031, fara sentinela, spre deosebire de id_gestiune) si scris ca atare in VANZARI_DETALII_TEMP.PRET_ACHIZITIE (PACK:5229/5259), apoi in VANZARI_DETALII la scrie_in_vanzari.

Concluzie Intrebarea 3: pe calea principala (lista de preturi), pret_achizitie ramane 0 pe liniile de proforma — nu vine din do_alege_stoc (care nu ruleaza) si nici din cursorul Oracle (care nu-l selecteaza pe aceasta ramura). Pe sursele avize/copiere, valoarea reala din documentul sursa se pastreaza.


Intrebarea 4 — ce s-ar strica daca proforma ar pastra gestiunea reala pana la salvare?

4a. adauga_articol_factura se cheama si pentru liniile de proforma?

Da, neconditionat. do_scrie_factura (ofacturare.vc2:14197+) cheama Thisform.do_scrie_articole() la linia 14264, inainte de Do Case poDate.eProforma = 1 (care alege intre scrie_proforma si scrie_factura2, la :14282):

ofacturare.vc2:14264
llReturn = Thisform.do_scrie_articole() && se deschide tranzactie manuala
...
ofacturare.vc2:14282
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(...)}]

do_scrie_articole parcurge liniile din crsfactura si cheama adauga_articol_factura pentru fiecare (ofacturare.vc2:14069-14073, deja citat in raportul-sursa) — acelasi cod, indiferent de eProforma. Daca poArt.id_gestiune ar fi real (nu -1000), adauga_articol_factura l-ar traduce direct in V_ID_GESTIUNE2 := V_ID_GESTIUNE (PACK:5032-5034, gate-ul <> -1000) si l-ar scrie ca atare in VANZARI_DETALII_TEMP.ID_GESTIUNE (PACK:5237,5267), apoi in VANZARI_DETALII.ID_GESTIUNE la scrie_in_vanzari (INSERT INTO VANZARI_DETALII SELECT FROM VANZARI_DETALII_TEMP, fara nicio conditie suplimentara pe id_gestiune).

4b. Are efect ca linia sa ramana cu ID_GESTIUNE completat, cata vreme scrie_proforma nu cheama contabilizeaza_articol?

Niciunul, pe partea de contabilizare/descarcare. Confirmat deja (runda 12, s5b_proiectare_proforma_copiere.md, sectiunea "Descoperire centrala"): scrie_proforma (PACK:5637-5671) cheama doar scrie_in_vanzari — insereaza antetul si liniile ca atare, apoi seteaza VANZARI.EPROFORMA=1. Nu cheama contabilizeaza_articol in niciun caz. Sentinela -1000 conteaza doar in interiorul lui contabilizeaza_articol (PACK:7472-7476), care nu ruleaza deloc pentru scrie_proforma. Deci ID_GESTIUNE real pe o linie de proforma nu ar declansa descarca_gestiune, nu ar genera NOTE_CONTABILE, nu ar atinge RUL/STOC — pentru ca intreaga functie care ar face asta nu se executa pe traseul scrie_proforma.

4c. Rapoarte/view-uri care citesc ID_GESTIUNE pe documente eProforma=1 si ar arata altceva?

Cautare eproforma (case-insensitive) in tot PACK_FACTURARE: doar 3 potriviri, toate in scrie_proforma/comentarii (PACK:1408, 5666, 5668) — nicio alta procedura/vedere din pachet nu filtreaza sau calculeaza dupa EPROFORMA.

Pe partea VFP, crsDetaliiListare (folosit la relistarea unui document deja emis, inclusiv proforma — oproceduri_facturare.prg:1111-1126, sursa fact_vfacturi2) include coloana id_gestiune in schema si in SELECT, indiferent de eproforma (nu exista filtru pe eproforma in acest SELECT). Deci daca ID_GESTIUNE ar fi real pe o proforma, acest cursor l-ar citi ca atare la relistare — azi citeste NULL. Nu am putut confirma daca vreun raport .frx tiparit efectiv afiseaza id_gestiune/nume_gestiune pe documentul proforma insusi (rapoartele PROFORMA/PROFORMA_VAL nu au versiune text .fr2 generata in Rapoarte\, deci nu s-au putut grep-ui direct) — de regula gestiunea e un camp intern, nu unul tiparit pe un document comercial, dar afirmatia ramane neconfirmata pe cod, nu doar presupusa corecta. Vezi "Ce nu s-a putut stabili".

4d. do_alege_stoc rezerva stoc sau doar alege?

Doar alege — nu scrie nimic in Oracle. Corpul complet al do_alege_stoc (ofacturare.vc2:13200-13400+) face exclusiv: (1) un SELECT prin cursor_gestiuni_articol/cursor_gestiuni_articol_retur/cursor_gestiuni_articol_stoc0 (toate proceduri read-only, populeaza un cursor local crsgestarticoltemp), (2) o scadere locala, in memorie, a cantitatilor deja adaugate pe acelasi document, in aceeasi sesiune (ofacturare.vc2:13273-13311: SELECT ... FROM crsfactura ... INTO CURSOR crsFacturaArticoleTemp, apoi a.cantitate - Nvl(b.cantitate,0) la afisare), ca sa nu i se ofere operatorului sa aleaga de doua ori acelasi lot pe acelasi document. Nu exista niciun INSERT/UPDATE catre Oracle in aceasta metoda — goExecutor.oExecute apare o singura data, pentru SELECT-ul de la pasul (1). Blocarea/decrementarea reala a stocului se intampla abia la descarca_gestiune (PACK:7648-7797), apelata doar din contabilizeaza_articol, care (per 4b) nu ruleaza niciodata pentru scrie_proforma. Deci chiar daca do_alege_stoc ar rula pe liniile unei proforme, nu ar "tine" stoc blocat pentru nimeni — nu exista mecanism de rezervare reala in aplicatie pe acest traseu, doar o verificare locala de neduplicare in sesiunea curenta.

Concluzie Intrebarea 4: nimic din contabilizare, descarcare de gestiune sau stoc real nu s-ar strica daca proforma ar pastra id_gestiune real pana la salvare — blocajul functional e la nivelul routing-ului scrie_proforma (care nu cheama contabilizeaza_articol), nu la nivelul sentinelei -1000. Singurul loc unde s-ar vedea o diferenta reala e la relistarea documentului (crsDetaliiListare/fact_vfacturi2), unde id_gestiune ar aparea completat in loc de NULL — efect cosmetic/informational, nu unul care sa afecteze stocul sau contabilitatea, cu rezerva ca nu s-a confirmat daca vreun .frx chiar il tipareste. Riscul real ramas e cel deja identificat in s5b_proiectare_proforma_copiere.md §5 (drumul invers, proforma comutata inapoi in factura in aceeasi sesiune, inainte de Termina) — acela nu se schimba, indiferent de aceasta decizie, pentru ca depinde de routing-ul de la Termina, nu de momentul in care se alege gestiunea.


Ce nu s-a putut stabili

  1. Daca vreun raport .frx tiparit (PROFORMA/PROFORMA_VAL) afiseaza efectiv id_gestiune sau nume_gestiune pe documentul insusi — rapoartele nu au versiune text .fr2 generata in Rapoarte\, asa ca nu s-a putut grep pe continutul lor; ar necesita fie generarea textului cu git_sync.ps1/foxbin2prg (in afara mandatului read-only), fie deschidere in IDE VFP.
  2. Nu s-a rulat nimic pe date vii — toata analiza e trasare de cod static (VFP text + PL/SQL text), conform mandatului read-only.
  3. Cautarea "eproforma" in Oracle a acoperit doar PACK_FACTURARE — nu s-au verificat alte pachete/view-uri/rapoarte Oracle care ar putea referi VANZARI.EPROFORMA impreuna cu ID_GESTIUNE (de exemplu rapoarte de gestiune/stoc din alte module ROA). Zero rezultate in PACK_FACTURARE nu e dovada de completitudine pentru intreaga schema.
  4. cursor_retur (PACK:3934-3948, folosit pentru tnTip 8,9,24 — retururi) nu a fost citit integral pentru coloana GESTIONABIL (tabelul de la Intrebarea 1 il listeaza fara valoare confirmata) — nu are parametru V_PROFORMA (confirmat prin grep), deci concluzia generala (marcarea vine doar din VFP) ramane valabila, dar valoarea exacta intoarsa de coloana nu a fost verificata linie-cu-linie.

Handoff

Cercetare incheiata intr-o singura sesiune, fara sa fie nevoie de predare de context. Toate cele 4 intrebari din brief au raspuns cu dovada fisier:linie, plus rezumatul executiv de mai sus. Niciun fisier de cod atins, niciun git_sync.ps1/txt2vcx.ps1 rulat, niciun commit, nicio scriere pe Oracle.