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

21 KiB

Cercetare — Verificarea 4: descarcare de gestiune pe proforma (Oracle)

Investigatie read-only, 10.08.2026. Nicio modificare de cod, niciun git_sync.ps1. Continua docs\cercetare\proforma_copiere_puncte_intrare.md (nu il reia) si raspunde punctual la "Necunoscute ramase" #1 de acolo.

Nota sursa Oracle: docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (mentionat in brief) nu exista in docs\. Fisierul real e la D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (823 337 octeti, confirma marimea asteptata). Am semnalat discrepanta catre team-lead prin mesaj si am continuat pe acest fisier — toate citatele PACK:linie de mai jos sunt pe fisierul din DATABASE, nu pe unul din docs\, pentru ca al doilea nu exista.

Verdict (raspuns la intrebarea 2)

NU — la emiterea unei proforme, gestiunea nu se descarca, pentru niciun articol, indiferent daca articolul e cu adevarat gestionabil in nomenclator sau nu. Blocajul e prin design: VFP marcheaza toate liniile unei proforme "negestionabile" inainte de compunerea documentului, ceea ce le trimite la server cu sentinela id_gestiune = -1000 (dedus, nu confirmat linie-cu-linie — vezi sectiunea "Ce nu s-a putut stabili"), iar pe Oracle contabilizeaza_articol sare apelul catre descarca_gestiune exact pe acest sentinel. Concluzia se sprijina si pe intentia de business explicita din changelog (12.03.2021 / 2.7.x): "Proforma. Articolele din proforma sunt marcate 'negestionabile', astfel incat nu mai este necesara existenta articolelor in stoc pentru a genera proforma." — adica cerinta initiala a fost explicit "proforma trebuie sa mearga si fara stoc", nu doar "nu arata plafonul".


1. Tipurile de document "proforma"

Deja stabilit in proforma_copiere_puncte_intrare.md §1 si reconfirmat aici pe partea Oracle: proforma nu e o valoare in TIP (pack_facturare.ntip, 1-52) — e un atribut ortogonal.

  • VFP: poDate.nIdTipDoc = 23 (fata de 5 = FACTURA), setat din combo-ul "Tip document" (COMUN\clase\ofacturare.vc2:9396-9403, :8745-8754). Setter-ul deriva boolean-ul poDate.eProforma: COMUN\programe\ofacturare_comun.prg:593-599 — This.eProforma = IIF(m.tnIdTipDoc = This.nIdTipDocProforma, 1, 0).
  • Oracle: nu exista nIdTipDoc — documentul se scrie in VANZARI cu TIP normal (business type, 1-52), iar EPROFORMA e o coloana separata pe VANZARI. Dovada directa: PACK:5637-5671 (PROCEDURE scrie_proforma) — scrie intai antetul cu scrie_in_vanzari (acelasi helper ca la o factura normala), apoi:
    PACK:5666-5669
    -- completez vanzari.eproforma
    update vanzari
       set eproforma = 1
     where id_vanzare = pack_facturare.nid_vanzare;
    
    Deci pe Oracle, proforma e o factura normala in VANZARI/VANZARI_DETALII, cu un flag in plus.
  • Exista si un mecanism legacy, tabele separate PROFORME/PROFORME_DETALII (PACK:5674-5767, PROCEDURE scrie_proforma_old / sterge_proforma_old, :5413-5429) — nefolosit de fluxul curent (VFP apeleaza scrie_proforma, nu scrie_proforma_old; nu am gasit niciun apel VFP catre varianta _old in COMUN\programe\*.prg). Tratati ca schela moarta, nu ca mecanism activ.
  • V_TIP = -102 in citeste_setari_document (PACK:1960-1994, ramura :1985-1987, V_VARNAME := 'ID_FDOC_PROFORMA') e un cod folosit doar pentru alocarea de serie/numar (id_fdoc), apelat din VFP la initializeaza_setari_document(-102) (COMUN\clase\ofacturare.vc2:9415, deja in cercetarea anterioara) — nu are legatura cu pack_facturare.ntip, care ramane tipul de business real al documentului.

2. Descarcarea de gestiune la proforma — DA/NU si mecanismul exact

NU. Trasat pe trei straturi, VFP si Oracle:

2.1 VFP: articolele devin "negestionabile" inainte sa intre pe document

COMUN\programe\ofacturare.prg:330-336 (in factureaza, imediat dupa incarcarea cursorului sursa crsarticole, indiferent de sursa — lista de preturi, comanda, contract, aviz, retur):

* 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

gestionabil = 0 se propaga in crsfactura prin prelucreaza_facturacrs (COMUN\programe\ofacturare_comun.prg:1801-1810, coloana gestionabil e in lista de INSERT).

La adaugarea/editarea unei linii pe formular, ramura pe gestionabil decide dialogul: COMUN\clase\ofacturare.vc2:13803-13809:

Case (poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45)   && 45 = ROARESTAURANT
    ofrmadarticol = Createobject("frm_articol_factura", lnCantitateMax, .F.)

adica acelasi dialog folosit pentru orice articol negestionabil in restul aplicatiei (nu unul proforma-specific), care ocoleste do_alege_stoc (dialogul de alegere lot din stoc). Pentru articolele care intra pe frm_articol_factura, id_gestiune implicit e sentinela -1000 (COMUN\clase\ofacturare.vc2:13618-13623, do_initializeaza_articol: If Type('toArticol.id_gestiune') = "U" THEN AddProperty(toArticol,'id_gestiune',-1000); acelasi tipar la :17836-17837). La scriere, poArt.id_gestiune se trimite direct ca parametru V_ID_GESTIUNE catre pack_facturare.adauga_articol_factura (COMUN\clase\ofacturare.vc2:14069-14073: ... + Nvl(Alltrim(Str(poArt.id_gestiune)),[NULL]) + ...).

2.2 Oracle: id_gestiune = -1000 e sentinela care blocheaza descarcarea

adauga_articol_factura (PACK:4989-5284), primeste V_ID_GESTIUNE si il traduce:

PACK:5032-5034
IF V_ID_GESTIUNE <> -1000 THEN
  V_ID_GESTIUNE2 := V_ID_GESTIUNE;
END IF;

V_ID_GESTIUNE2 (necompletat, deci NULL, cand V_ID_GESTIUNE = -1000) e cel scris efectiv in VANZARI_DETALII_TEMP.ID_GESTIUNE (PACK:5237,5267).

La emitere, contabilizeaza_articol (PACK:7173-7547) parcurge liniile din VANZARI_DETALII_TEMP si apeleaza descarca_gestiune doar aici:

PACK:7472-7476
IF pack_facturare.ntip <> 4 THEN
  IF pack_facturare.nscadere_stoc = 1 AND
     detalii_articol.id_gestiune <> -1000 AND
     detalii_articol.in_stoc = 1 THEN
    pack_facturare.descarca_gestiune(...)

NULL <> -1000 evalueaza la NULL in PL/SQL (nu TRUE), deci conditia pica indiferent de in_stoc — liniile de pe o proforma nu ajung niciodata la descarca_gestiune, cata vreme id_gestiune a intrat ca -1000.

Exista si o a treia bariera, independenta, in interiorul lui descarca_gestiune (PACK:7648-7797), dar aceasta priveste articolul insusi, nu documentul:

PACK:7789-7797
-- NU SE DESCARCA GESTIUNEA PENTRU ARTICOLELE NEGESTIONABILE
-- ASTFEL INCAT SA SE POATA FACE OPERATII GEN AVIZ DIN CUSTODIE INCLUSIV CU ARTICOLE NEGESTIONABILE
SELECT MAX(IN_STOC) INTO lnInStoc FROM NOM_ARTICOLE WHERE ID_ARTICOL = V_ID_ARTICOL;
IF lnInStoc = 0 THEN GOTO SFARSIT; END IF;

Aceasta verifica NOM_ARTICOLE.IN_STOC real (nomenclator), nu flagul de proforma — protejeaza un articol cu adevarat negestionabil, indiferent de tipul documentului. Nu e mecanismul care protejeaza proforma; mecanismul de proforma e strict gate-ul id_gestiune <> -1000 de la 2.1-2.2.

2.3 in_stoc trimis de VFP: uneori ignorat de Oracle, dar nu e el gate-ul decisiv

V_IN_STOC_TEMP (parametrul care ajunge in VANZARI_DETALII_TEMP.IN_STOC) e fie preluat direct de la VFP (V_IN_STOC := V_IN_STOC_TEMP, ramurile implicita si restaurant, PACK:5200-5203, 5111-5112), fie recalculat de Oracle din nomenclator/contract, ignorand ce a trimis VFP, pe ramurile comenzi (PACK:5061-5066), avize (:5086-5091) si contract cu pret de contract (:5153-5158, 5184). Asta inseamna ca pentru o proforma facuta din comanda/aviz/contract, campul IN_STOC scris efectiv poate reveni la valoarea reala din nomenclator (1 pentru un articol gestionabil), dar asta nu conteaza — gate-ul din 2.2 cere id_gestiune <> -1000 AND in_stoc = 1 cu AND, iar id_gestiune ramane blocat la -1000/NULL indiferent de sursa documentului (sentinela vine din UI, la nivelul liniei, nu din cursorul de incarcare). Deci recalcularea lui in_stoc de catre Oracle pe aceste ramuri nu redeschide descarcarea.

3. De la proforma la factura

Nu exista o rutina de "transformare" dedicata — mecanismul e copierea, deja documentata complet in proforma_copiere_puncte_intrare.md §2 (tooltip explicit COMUN\clase\ofacturare_comun.vc2:1409: "Se foloseste si pentru generarea unei facturi din proforma prin copiere").

  • Punct de intrare VFP: frm_facturi.But_copiaza1.Click -> do_copiaza (degradeaza tipul de business, COMUN\clase\ofacturare_comun.vc2:3628-3713) -> copiere_factura (COMUN\programe\oproceduri_facturare.prg:150-153) -> factureaza(tip_degradat, toFactura).
  • Punct de intrare Oracle: acelasi pack_facturare.adauga_articol_factura / scrie_in_vanzari ca la orice document nou — nu exista un pack_facturare.transforma_proforma sau echivalent. Singurul apel Oracle specific copierii e cursorul de precompletare, pack_facturare.cursor_retur_document (vezi §4), apelat cu V_COPIERE = 1 (COMUN\programe\ofacturare.prg:267-268).
  • completeaza_setari_document(toDateAnterior, .T.) (COMUN\programe\ofacturare_comun.prg:362-412) nu propaga nIdTipDoc (linia comentata, :370) — documentul nou porneste cu nIdTipDoc implicit (5=FACTURA sau 6=AVIZ, dupa tnTip degradat, COMUN\programe\ofacturare.prg:187-196), deci poDate.eProforma = 0 pentru documentul nou, indiferent ca sursa era proforma.

4. Ce se intampla cu stocul intre proforma si factura

Nimic de reconciliat, pentru ca proforma nu a atins niciodata stocul (§2). Nu exista concept de "rezervare de stoc" pe proforma:

  • tabelele legacy PROFORME/PROFORME_DETALII (§1) nu au coloane de rezervare si oricum nu sunt pe calea activa;
  • nu am gasit, in PACK_FACTURARE, niciun apel care sa insereze in RUL sau sa actualizeze STOC la scrie_proforma — funcita se limiteaza la scrie_in_vanzari + UPDATE vanzari SET eproforma=1 (§1, PACK:5637-5671);
  • cautare directa in fisierul PACK pentru orice mentiune de rezervare pe proforma (rezerv, blocheaza stoc) nu a dat rezultate relevante (v. "Ce nu s-a putut stabili" pentru limitele cautarii text simple pe un fisier de 823 KB).

La copiere (transformarea efectiva in factura), gestionabilitatea reala se restaureaza: cursorul de precompletare la copiere, cursor_retur_document (PACK:3949-4000), calculeaza GESTIONABIL asa:

PACK: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,

La copiere, V_COPIERE = 1 e hardcodat (COMUN\programe\ofacturare.prg:267-268: cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil) — al treilea parametru pozitional 1 e V_COPIERE), iar V_PROFORMA trimis e poDate.eProforma al documentului nou, care e 0 (§3) — deci ramura V_PROFORMA=1 nu se activeaza la copiere, si GESTIONABIL = B.IN_STOC (flagul real din nomenclator). Rezultat: liniile copiate dintr-o proforma redevin gestionabile normal daca articolul chiar e in stoc, trec prin do_alege_stoc la editare (ofacturare.vc2:13804, ramura Otherwise), primesc un id_gestiune real, si descarcarea de gestiune se face normal la emiterea facturii rezultate din copiere — exact ca la orice factura noua, nu printr-un pas separat de "transformare".

5. Ce verifica serverul la descarcare, si cu ce eroare

Verificari observate direct in PACK_FACTURARE, toate independente de proforma (se aplica oricarei descarcari reale de gestiune):

  • Gestiune inexistenta/stearsa: PACK:7847-7858 — SELECT ... FROM NOM_GESTIUNI WHERE ID_GESTIUNE = V_ID_GESTIUNE AND STERS = 0; NO_DATA_FOUND -> FACT-007.
  • Stoc epuizat / lot inexistent: PACK:7860-8493 — construieste tab_stoc din RUL/STOC dupa criterii (articol, gestiune, cont, serie, pret etc.) si, daca nu gaseste nimic (SQL%ROWCOUNT = 0): FACT-008 ("Articolul ... nu mai e in stoc") sau, daca nici macar denumirea nu se gaseste, FACT-009.
  • Ramuri similare mai jos in acelasi fisier pentru alte cazuri de gestiune/combinatii invalide: FACT-010 (:10001), FACT-011 (:10323), FACT-014 combinatie invalida de gestiuni (:12127), FACT-019 gestiune (:10449), FACT-020/FACT-021 variante "nu mai e in stoc" (:10748,10752).
  • Optiune globala de bypass: RF_FACTURARE_FARA_STOC (PACK:7783-7784, lnFacturareFaraStoc) — permite facturarea peste cantitatea disponibila pentru articole gestionabile; nu e specifica proformei, e o optiune de firma generala.
  • Cota TVA lipsa / articol fara politica de pret — nu sunt verificari de stoc, dar sunt cele mai frecvente erori vecine (FACT-024 la contabilizeaza_articol, PACK:7278-7302; FACT-018, FACT-012, FACT-013 la cautarea cotei TVA in adauga_articol_factura, deja semnalate in plan_13_unificare_formular_facturare.md §G). O proforma cu id_gestiune=-1000 nu trece deloc prin verificarile de stoc de mai sus (FACT-007/008/009/010/011/014/019/020/021), pentru ca nu ajunge la descarca_gestiune — dar tot trece prin verificarea de politica de pret / cota TVA, care nu are legatura cu stocul.

6. Consecinte pentru S5b — constrangeri de proiectare

  1. Formularul unificat trebuie sa reproduca exact mecanismul gestionabil=0 la incarcarea liniilor cand poDate.eProforma = 1 — nu doar sa ascunda vizual plafonul. Fara acest pas, articolele gestionabile ar intra pe document cu id_gestiune real si ar declansa descarca_gestiune la emitere, contrazicand comportamentul de azi si cerinta de business ("proforma merge fara stoc").
  2. Nu se apeleaza do_alege_stoc (dialogul de alegere lot) pentru liniile unei proforme. Traseul corect e cel al articolului negestionabil (frm_articol_factura/do_initializeaza_articol), care garanteaza id_gestiune = -1000 pe linie.
  3. id_gestiune = -1000 trebuie sa ajunga efectiv in parametrul V_ID_GESTIUNE trimis catre pack_facturare.adauga_articol_factura — verificarea de gate e strict pe aceasta valoare (<> -1000), nu pe un flag de document. Daca formularul unificat schimba felul in care construieste liniile (de ex. reutilizeaza un obiect de linie comun facturii si proformei), acest -1000 trebuie sa fie explicit setat pe ramura eProforma=1, nu mostenit implicit.
  4. in_stoc/gestionabil trimis de VFP nu e suficient de la sine — pe unele surse (comanda, aviz, contract cu pret de contract) Oracle il rescrie din nomenclator/contract (§2.3). Gate-ul real e id_gestiune, deci formularul unificat nu poate conta pe faptul ca a trimis in_stoc=0; trebuie sa garanteze id_gestiune=-1000.
  5. La copiere proforma -> factura, comportamentul trebuie sa fie opus: liniile trebuie sa-si recapete gestionabilitatea reala (B.IN_STOC din nomenclator), nu sa ramana blocate la negestionabil. Decizia deja luata in plan ("degradarea de tip din do_copiaza ramane pentru copiere si nu se aplica la regenerare") e consistenta cu asta, dar merita spus explicit: regula eProforma=1 -> gestionabil=0 se aplica doar la incarcarea/compunerea unei proforme noi, nu si la copierea din ea — documentul nou pleaca cu eProforma=0 (§3-4) si trebuie sa lase Oracle sa recalculeze GESTIONABIL normal.
  6. Formularul unificat nu trebuie sa implementeze nicio logica de "eliberare stoc rezervat" la trecerea proforma -> factura — nu exista rezervare de stoc pe proforma (§4), deci nu exista nimic de eliberat. Riscul tehnic real ramane cel deja semnalat in plan §G (ordinea stergere-inaintea-reemiterii in aceeasi tranzactie la regenerare), care e independent de proforma.
  7. Verificarile de stoc (FACT-007/008/009/010/011/014/019/020/021) nu se vor manifesta niciodata pentru o linie de proforma cata vreme regula #1-#3 e respectata — formularul unificat nu are nevoie de tratament special pentru aceste coduri de eroare pe ramura proforma (nu pot aparea acolo), dar tot trebuie sa trateze erorile de politica de pret/TVA (FACT-012/013/018/024), care raman valabile si pe proforma.

7. Copierea proformei in formularul unificat — ce lipseste

Plecand de la proforma_copiere_puncte_intrare.md §2 (traseul general de copiere, deja complet documentat) si de la S5b din plan (plan_13_unificare_formular_facturare.md:1674-1689):

  • Ce exista deja si se reutilizeaza neschimbat: do_copiaza (degradarea de tip), copiere_factura, factureaza(tip, toFactura), completeaza_setari_document, cursor_retur_document cu V_COPIERE=1. Niciunul din aceste puncte de intrare nu are legatura speciala cu proforma dincolo de parametrul V_PROFORMA deja tratat corect (§4) — nu trebuie adaugat nimic nou aici pentru ca formularul unificat sa suporte copierea unei proforme.
  • Ce lipseste, specific formularului unificat, nu copierii in sine: mecanismul crsarticole intreg (incarcarea de masa + UPDATE ... SET gestionabil=0, §2.1) presupune un cursor complet incarcat inainte de afisare. Planul (plan_13_unificare_formular_facturare.md:1470) stabileste deja ca crsarticole nu se mai incarca in masa in formularul unificat — deci pasul "seteaza gestionabil=0 pe toate liniile cand eProforma=1" trebuie reimplementat linie-cu-linie, la momentul in care fiecare linie e adaugata pe formularul unificat (fie la copiere, fie la compunere noua), nu ca un singur UPDATE de masa pe un cursor care nu mai exista. Constrangerea #1-#3 de mai sus (sectiunea 6) e exact specificatia acestui pas lipsa.
  • Combo-ul de tip document si realocarea de serie — deja acoperite de decizia S5b din plan (Ct_clb_fdoc ramane, realocare la comutare); nu am gasit nimic suplimentar de adaugat aici fata de ce e deja scris in plan.

Ce nu s-a putut stabili

  1. Linia exacta unde poArt.id_gestiune devine efectiv -1000 pentru o linie de proforma, in loc de a ramane la valoarea implicita de camp (0, cursorul crsfactura creat cu id_gestiune N(20) fara NULL, iar prelucreaza_facturacrs nu include id_gestiune in lista sa de INSERT — COMUN\programe\ofacturare_comun.prg:1801-1810). Am gasit sentinela -1000 setata conditionat ("daca proprietatea lipseste") in do_initializeaza_articol (COMUN\clase\ofacturare.vc2:13618-13623), dar nu am confirmat ca proprietatea chiar "lipseste" (Type = 'U') in momentul in care frm_articol_factura proceseaza o linie de proforma provenita din Scatter pe crsfactura. Argument indirect, nu dovada directa: daca id_gestiune ar ajunge 0 (nu -1000) la server, descarca_gestiune ar cauta NOM_GESTIUNI WHERE ID_GESTIUNE=0 si ar arunca FACT-007 la fiecare emitere de proforma cu articol gestionabil din comanda/aviz — eroare care ar fi vizibila si raportata de ani (proforma exista din 2014+ in changelog); absenta oricarei asemenea raportari sustine indirect ca sentinela -1000 chiar ajunge la server, dar nu e o dovada pe cod.
  2. Nicio verificare pe date vii — tot ce e mai sus e trasare de cod static (VFP text + PL/SQL text), nu rulare/log real. Nu am rulat nimic, conform mandatului read-only.
  3. Cautarea de "rezervare de stoc pe proforma" (§4) s-a facut prin grep text simplu pe PACK_FACTURARE dupa cuvinte cheie (rezerv, PROFORMA) — nu e o dovada de completitudine pentru intreaga baza de date Oracle (declanșatoare/triggere pe VANZARI/VANZARI_DETALII, proceduri din alte pachete). Zero rezultate nu inseamna cu certitudine ca nu exista niciun mecanism de rezervare in alta parte a schemei.
  4. Fisierul docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql nu a fost creat de mine — am lucrat pe originalul din DATABASE\SCRIPTURI_CLAR. Daca cineva copiaza ulterior fisierul in docs\ cu alta numerotare de linii, citatele PACK:linie din acest raport trebuie re-verificate pe copia noua.

Verificari recomandate pe date vii (daca raman intrebari)

  • Emis o proforma de test cu un articol cu adevarat gestionabil si cu stoc real, sursa = comanda (ramura unde Oracle rescrie IN_STOC, §2.3) — confirma ca RUL/STOC nu se modifica dupa emitere si ca VANZARI_DETALII.ID_GESTIUNE a fost scris NULL pentru acea linie.
  • Acelasi test, sursa = lista de preturi (ramura unde Oracle are incredere in V_IN_STOC_TEMP) — pentru comparatie.
  • Copiaza proforma de mai sus in factura si confirma ca VANZARI_DETALII.ID_GESTIUNE al facturii rezultate e populat cu o gestiune reala si ca RUL inregistreaza descarcarea la emiterea facturii.
  • Interogare directa pe schema pentru triggere/joburi legate de EPROFORMA sau de rezervare de stoc, daca exista suspiciunea din punctul 3 de mai sus: SELECT trigger_name, table_name FROM user_triggers WHERE table_name IN ('VANZARI','VANZARI_DETALII','STOC','RUL');