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
38 KiB
Cercetare — proiectare S4 punctul 2: decuplarea registrului de cantitate ramasa de crsarticole
Investigatie READ-ONLY pentru punctul 2 al povestii S4 din
docs\plan_13_unificare_formular_facturare.md:2099-2113. Continua raportul punctului 1
(docs\cercetare\s4_cautare_articole_server.md) si corectiile din docs\cercetare\s4_puncte_deschise.md.
Fara editari de cod, fara git_sync.ps1/txt2vcx.ps1, fara commit, fara scriere pe Oracle (numai
SELECT). Nu ating COMUN\clase\ofacturare_comun.vc2 si COMUN\programe\ofacturare_editare.prg
(perimetrul altei sarcini). Decizia 39 (S4 ramane integrala, se desface registrul) nu e
reargumentata — proiectez CUM, nu DACA.
Sursa VFP: COMUN\clase\ofacturare.vc2 (frm_facturare_articole; frm_facturare_articole2/prototip
citit doar pentru confirmare, are aceeasi structura). Sursa Oracle:
D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (corpul
pachetului, versiune_db.txt = 2026_08_09_02).
Verdict (rezumat)
Descoperirea centrala: crsarticole.cantitate are azi DOUA roluri distincte, nu unul, si numai
unul din ele e "registrul de cantitate ramasa" pe care planul il numeste riscant.
- Rol A — cantitate ramasa de facturat dintr-un document sursa (doar comanda: tip
3,21,25,28,42,47; si avize: tip4). Cursoarele Oracle (cursor_comanda,cursor_avize) o calculeaza deja live la deschidere cacantitate_document - deja_facturat.do_scrie_facturao re-suma dincrsarticole(Calculate Sum) doar ca sa decida ce sa trimita mai departe (pnParametruAditional) — dar Oracle recalculeaza acelasi lucru independent, din tabele reale, ininchide_comanda()simarcheaza_facturat(), chiar in procedura care scrie factura. Cursorul VFP e o copie redundanta a unui calcul pe care Oracle il repeta oricum la scriere — exact problema "sursa unica" semnalata in misiune, si exact motivul pentru care decuplarea e posibila fara sa piarda paritatea: mutam citirea "cat a mai ramas" din cursorul local intr-un apel Oracle facut la momentul potrivit, nu intr-o a doua copie tinuta manual. - Rol B — plafon de cantitate in sesiune (lista de preturi gestionabila
1,22,29,2-jumatate; transfer23,41; retur8,9,24). Decrementat/incrementat indo_adauga_articol/do_modifica/do_stergeca sa nu lase operatorul sa adauge mai mult decat vede pe ecran, in aceeasi sesiune. Nu alimenteaza nicio decizie Oracle — nu apare in niciunCalculate Sumin afara celor doua locuri de la Rolul A, si Oracle nu-l citeste niciodata direct. E un plafon UI, nu un registru de business. - Corectie fata de raportul punctului 1: pasul 5 de acolo (
s4_cautare_articole_server.md:417-424) propune oprirea incarcarii in masa pe tipurile23,41(printre altele), presupunand ca ele n-au bookkeeping de pastrat — dar au Rol B (confirmat pe cod, sectiunea 1 de mai jos). Punctul 1 nu poate fi aplicat pe23,41fara ca punctul 2 sa acopere intai Rolul B pe aceste doua tipuri. - Recomandare de proiectare (detaliata la sectiunea 4): pentru Rolul A, inlocuieste
Select crsarticole / Calculate Sum(cantitate)cu un apel Oracle nou, la acelasi moment dindo_scrie_factura(dupado_scrie_articole(), candVANZARI_DETALII_TEMPe deja populat), care reface exact interogarea pe careinchide_comanda/marcheaza_facturato repeta oricum. Pentru Rolul B, plafonul devine un apel Oracle la cerere (aceeasi interogare care alimenteaza azicursor_preturi/cursor_comanda/cursor_gestiune, filtrata pe articol), nu o valoare tinuta in memorie — nu mai e nevoie de sincronizare manuala pentru ca nu mai exista o a doua copie.
1. Inventar exact al scrierilor/citirilor de bookkeeping
Verificat direct pe COMUN\clase\ofacturare.vc2 (nu pe .bak), linii confirmate cu vfp_symbols.ps1.
1.1 do_adauga_articol — scrierea la adaugarea unei linii (:12813-13086)
Dupa ce linia e scrisa in crsfactura, la :13034-13051:
Select (lcCursor)
lnRecno = Recno()
Do Case
Case gnScadereStoc = 1 And poDate.tip = 41 && aviz retur transfer catre subunitati lista pret
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
Go lnRecno
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 3, 4, 21, 25, 28, 42, 47)
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
Go lnRecno
Case Inlist(poDate.tip, 8, 9, 24) && factura retur lei si valuta, aviz retur
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
Go lnRecno
Case poArticol.gestionabil = 1 And gnScadereStoc = 1 And ;
(Inlist(poDate.tip, 1, 22, 29) Or (poDate.tip = 2 And poArticol.opt_facturare = 0))
Replace cantitate With IIF(cantitate - poArticol.cantitate > 0, cantitate - poArticol.cantitate, 0) For id_articol = poArticol.id_articol And gestionabil = 1
Go lnRecno
Endcase
lcCursor = crsarticole1 daca tlContract, altfel crsarticole (:12839-12845).
1.2 do_sterge — refacerea la stergerea unei linii (:14608-14677)
Select (lcCursor)
lnRecNo = Recno()
Do Case
Case gnScadereStoc = 1 And poDate.tip = 41
Select (lcCursor)
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip,3,4,21,25,28,42,47)
Select (lcCursor)
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
Case Inlist(poDate.tip,8,9,24)
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
Case poArticol.id_gestiune <> - 1000 And gnScadereStoc = 1 And ;
(Inlist(poDate.tip,1,22,29) Or (poDate.tip = 2 And poArticol.opt_facturare = 0))
Select (lcCursor)
Replace cantitate With cantitate + poArticol.cantitate For id_articol = poArticol.id_articol And gestionabil = 1
Endcase
lcCursor = crsarticole1 daca poArticol.opt_facturare <> 0, altfel crsarticole (:14615-14619).
Exact inversul semnului fata de do_adauga_articol, pe aceleasi patru grupuri de tipuri — simetrie
confirmata, nu presupusa.
1.3 do_modifica — ajustarea la schimbarea cantitatii pe o linie deja adaugata (:13746-13914)
Doua interactiuni distincte cu registrul, in aceeasi metoda:
(a) Calculul plafonului inainte de a arata dialogul (:13775-13798), comentat explicit in cod
ca "plafonul de cantitate = stocul disponibil reconstituit din cursorul sursa; fara randul in cursor
ramane cantitatea liniei" (:13775):
lnCantitateMax = poArticol.cantitate
lcCursorStoc = Iif(poArticol.opt_facturare = 0, [crsarticole], [crsarticole1])
...
Locate For id_c = poArticol.id_c
If Found()
Do Case
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 4, 21, 28, 42, 47)
lnCantitateMax = cantitate + poArticol.cantitate
Case Inlist(poDate.tip, 8, 9, 24)
lnCantitateMax = cantitate - poArticol.cantitate
Endcase
Endif
Aduna inapoi ce a consumat deja linia curenta, ca sa arate operatorului plafonul real (cat mai era
disponibil inainte de aceasta linie), trimis ca parametru la do_alege_stoc/frm_articol_factura.
(b) Ajustarea delta dupa ce operatorul schimba cantitatea (:13887-13904):
lnDeltaCantitate = poArticol.cantitate - lnCantitateVeche
If lnDeltaCantitate <> 0 And Used(lcCursorStoc)
Do Case
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 4, 21, 28, 42, 47)
Replace cantitate With cantitate - lnDeltaCantitate For id_c = poArticol.id_c
Case Inlist(poDate.tip, 8, 9, 24)
Replace cantitate With cantitate + lnDeltaCantitate For id_c = poArticol.id_c
Endcase
Endif
Gasit, nu presupus: do_modifica NU ajusteaza crsarticole pentru tipurile 1,22,29 si
2-lista-jumatate (grupul "gestionabil, plafon local" din do_adauga_articol/do_sterge) — doar
pentru grupul comanda/aviz (4,21,23,28,42,47) si retur (8,9,24). E o asimetrie preexistenta
in codul de azi (nu introdusa de S4): editarea cantitatii pe o linie de lista-de-preturi deja adaugata
nu recalibreaza plafonul local. Nu e de reparat aici — doar de semnalat, ca varianta noua sa nu
incerce sa reproduca o simetrie care nu exista azi.
1.4 do_adauga_tot — enumerare, nu bookkeeping propriu (:13169-13198)
Select crsarticole
Scan
...
Thisform.do_adauga_articol(.T.)
...
Endscan
Nu scrie direct in crsarticole — cheama do_adauga_articol pe fiecare rand, care face
bookkeeping-ul de la 1.1. Rolul lui crsarticole aici e al treilea, distinct de Rolul A/B: sursa
de enumerare ("ce randuri exista de adaugat"), necesara oricum pentru populare (S4 punctul 1), nu
doar pentru bookkeeping.
1.5 do_scrie_factura — citirea care alimenteaza decizia de inchidere (:14301-14338)
Doua ramuri, singurele doua locuri din intregul fisier unde Calculate Sum(cantitate) ruleaza peste
crsarticole (confirmat cu grep pe tot fisierul, nicio a treia aparitie):
Case poDate.Tip = 4
Select crsarticole
Calculate Sum(cantitate) To lnCantitateRamasa
If lnCantitateRamasa > 0
pnFacturaRetur = 7 - amessagebox("Doriti sa se inregistreze si avizul de retur?",4+32,"Confirmare")
pnParametruAditional = 1
Else
pnFacturaRetur = 0
pnParametruAditional = 0
Endif
...
Case Inlist(poDate.Tip,3,21,25,28,42,47)
Select crsarticole
Calculate Sum(cantitate) To lnCantitateRamasa
If lnCantitateRamasa <> 0
pnParametruAditional = 7 - amessagebox("Doriti sa se inchida comanda?",4+32,"Confirmare")
Endif
...
pnParametruAditional default e 0 (:14222, setat inainte de Do Case). Pentru orice alt tip
(inclusiv 41, 23, 2/6/26/52, 45/48/49), ramane 0 fara sa fie calculat din crsarticole deloc
(exceptie: tip 48 il suprascrie cu poDate.coeficient_k, fara legatura cu inchiderea — :14367-14369).
Concluzie: doar 4 si Inlist(3,21,25,28,42,47) sunt Rolul A. Restul tipurilor nu ating deloc
aceasta parte a lui do_scrie_factura — inclusiv 41/23, care au totusi bookkeeping Rol B activ la
1.1-1.3.
2. Semantica registrului azi
2.1 Ce inseamna cantitate la incarcare, per grup
Confirmat pe corpul cursoarelor Oracle (ff_...PACK_FACTURARE.sql):
cursor_comanda(:2952-3171per raportul punctului 1; verificat aici direct la sectiunea "aviz"/comanda a interogarii,:3060-3081): coloanacantitateeA.CANTITATE - NVL(D.CANTITATE, 0)undeDeSUM(cantitate)deja facturat dinVANZARI_DETALIIpe acelasiid_comanda(:3060-3068), filtratWHERE ... SIGN(A.CANTITATE) * (A.CANTITATE - NVL(D.CANTITATE, 0)) > 0(:3079). E deja "ramas de facturat", calculat live la deschidere — nu cantitatea comandata bruta.cursor_avize(:3703-3874): coloanaA.CANTITATE - NVL(D.CANTITATE, 0) AS CANTITATE(analog la:3118), plus agregarea finala (:3820-3872) care scadeVANZARI_CANTITATIdeja consumat dinVANZARI_DETALII(WHERE A.CANTITATE <> NVL(B.CANTITATE, 0),:3871). Acelasi tipar: "ramas", nu cantitatea avizata bruta.- Restul grupurilor (Rol B:
1,22,29,2-lista,23,41transfer,8,9,24retur):cantitatevine dincursor_preturi/cursor_gestiune, care e stoc disponibil (LEFT JOINpeSTOC, raportul punctului 1 sectiunea 1.1/1.5), nu "ramas de facturat dintr-un document" — pentru ca aceste tipuri nu au un document sursa cu cantitati de urmarit (lista de preturi n-are document sursa; transferul/returul opereaza pe stoc, nu pe o comanda).
2.2 Cine o scade si cand
Vezi sectiunea 1: la fiecare do_adauga_articol (scade sau creste, dupa tip), la fiecare do_sterge
(inversul), si la do_modifica doar pentru grupul Rol A + retur (2.1(b) de mai sus).
2.3 Ce se intampla la adaugare / stergere / modificare cantitate — rezumat comportamental
| Actiune | Rol A (comanda 3/21/25/28/42/47, aviz 4) | Rol B (lista gestionabila 1/22/29/2, transfer 23/41, retur 8/9/24) |
|---|---|---|
| Adauga linie | crsarticole.cantitate scade (creste la retur/41) cu cantitatea adaugata |
idem, plafon local |
| Sterge linie | reface exact simetric | reface exact simetric |
| Modifica cantitate pe linie existenta | ajusteaza cu delta (23/4/21/28/42/47 si 8/9/24 explicit) | nu ajusteaza pe 1/22/29/2-lista (asimetrie preexistenta, 1.3) |
La scriere (do_scrie_factura) |
suma peste crsarticole decide pnParametruAditional (inchidere) |
nu se suma niciodata — nu alimenteaza nicio decizie Oracle |
3. Regula de inchidere automata — exact, cu dovada Oracle
3.1 Ce trimite VFP si ce face Oracle cu el
pnParametruAditional merge la Oracle prin scrie_factura_avize (tip 4) sau scrie_factura2
(toate celelalte, inclusiv comanda). Ambele cheama in interior finalizeaza_factura(...)
(ff_...PACK_FACTURARE.sql:14770-14852), care are un CASE unic pe pack_facturare.ntip (setat
intern din tipul documentului, nu parametru separat):
CASE
WHEN V_PARAMETRU_ADITIONAL = 1 AND pack_facturare.ntip IN (3, 21, 25, 28, 42, 47) THEN
-- comanda: V_PARAMETRU_ADITIONAL este V_INCHIDERE_COMANDA
pack_facturare.inchide_comanda();
WHEN pack_facturare.ntip = 4 THEN
-- aviz: V_PARAMETRU_ADITIONAL este V_VERIFICARE_FACTURAT
pack_facturare.scrie_cantitati_vanzari_avize;
pack_facturare.scrie_corespondente_vanzari(1);
pack_facturare.marcheaza_facturat(V_PARAMETRU_ADITIONAL);
WHEN pack_facturare.ntip = 24 THEN
pack_facturare.scrie_corespondente_vanzari(2);
pack_facturare.marcheaza_facturat(V_PARAMETRU_ADITIONAL);
WHEN pack_facturare.ntip in (2, 6, 52) THEN
pack_facturare.scrie_rate_factura(V_DATAORA);
WHEN pack_facturare.ntip in (8, 9) THEN
pack_facturare.scrie_corespondente_vanzari(3);
ELSE
dbms_output.put_line('---');
END CASE;
(:14818-14839)
41 si 23 nu apar deloc in acest CASE — cad pe ELSE, fara nicio actiune de inchidere.
Confirma pe cod ce arata si sectiunea 1.5: transferul intre subunitati nu are un "document sursa" de
inchis, doar un plafon de stoc (Rol B). 2/6/26/52 (contract) intra pe ramura scrie_rate_factura,
care scrie scadentarul de rate — nu e o "inchidere" in sensul comanda/aviz, e alta operatie.
3.2 Comanda — inchide_comanda() (:5769-5820)
Ruleaza doar cand V_PARAMETRU_ADITIONAL = 1 — adica doar daca crsarticole a aratat cantitate
ramasa nenula si operatorul a raspuns "Da" la "Doriti sa se inchida comanda?" (ofacturare.vc2:14336-14337).
Cand ramane 0 (fara diferenta), inchide_comanda() nu se cheama deloc — pentru ca o comanda complet
livrata se raporteaza deja "inchisa" prin simplul fapt ca interogarea de mai jos nu mai returneaza
randuri, fara nicio actiune suplimentara.
Ce face efectiv (:5771-5818): insereaza un rand compensator in COMENZI_ELEMENTE, cu
CANTITATE = ramas (calculat live, independent de VFP, din NVL(C.CANTITATE,0) + NVL(B.CANTITATE,0) - A.CANTITATE unde B = deja facturat in aceasta tranzactie (VANZARI_DETALII_TEMP) si C = deja
facturat istoric (VANZARI/VANZARI_DETALII)) — asta face ca o interogare ulterioara de tip
cursor_comanda (aceeasi forma de WHERE, SIGN(...) * (A.CANTITATE - NVL(D.CANTITATE,0)) > 0) sa
nu mai gaseasca randuri ramase, adica sa "inchida" comanda prin recalcul, nu printr-un flag setat.
Nu exista o coloana INCHISA/status persistat pe comanda (cautare INCHISA in tot pachetul —
zero potriviri) — starea "deschisa/inchisa" e intotdeauna derivata din suma COMENZI_ELEMENTE vs
VANZARI_DETALII, nu citita dintr-un camp.
Important pentru paritate: cantitatea trimisa de VFP (pnParametruAditional, un simplu 1/0)
nu participa la calculul cantitatii inserate — Oracle o recalculeaza singur din tabele reale.
Rolul VFP e strict binar: "a fost cerut sa se forteze inchiderea?".
3.3 Avize — marcheaza_facturat(V_VERIFICARE) (:15381-15418)
IF V_VERIFICARE = 0 THEN
UPDATE VANZARI SET FACTURAT = 1, ID_UTILFACT = ...
WHERE ID_VANZARE IN (SELECT id_vanzare_aviz ... WHERE id_vanzare_Fact = pack_facturare.nid_vanzare AND sters=0 AND tip<>3)
AND FACTURAT = 0;
ELSE
UPDATE VANZARI SET FACTURAT = 1, ...
WHERE ID_VANZARE IN (
SELECT id_vanzare FROM (
SELECT a.id_vanzare, SUM(a.cantitate - NVL(b.cantitate,0)) AS ramas
FROM vanzari_detalii a LEFT JOIN (...VANZARI_CANTITATI...) b ON ...
WHERE a.id_vanzare IN (...avizele referite de aceasta factura...)
GROUP BY a.id_vanzare
) WHERE ramas = 0
);
END IF;
Aici exista un flag real persistat: VANZARI.FACTURAT. V_VERIFICARE (= pnParametruAditional
trimis de VFP) alege intre doua moduri:
V_VERIFICARE = 0(VFP a vazutlnCantitateRamasa = 0, adica a crezut ca tot ce era in avizele referite s-a facturat): marcheaza TOATE avizele referite ca facturate, fara re-verificare — increde in calculul VFP.V_VERIFICARE = 1(VFP a vazut ceva ramas): recalculeaza per-aviz, din tabele reale (vanzari_detalii/vanzari_cantitati), si marcheaza doar avizele individuale al carorramas = 0— mai stric, pentru ca VFP nu mai e sigur.
Observatie relevanta pentru punctul 2 al plafonului de risc: cand un singur crsarticole agrega
mai multe avize (facturare din mai multe avize deodata), suma globala poate fi 0 chiar daca un aviz
individual din grup mai are ramas si altul are exces care-l compenseaza — caz in care V_VERIFICARE=0
ar marca toate ca facturate, inclusiv cel cu ramas real. Comportament preexistent, nu introdus
de decuplare — dar varianta noua trebuie sa-l reproduca identic (aceeasi conditie de trigger: suma
globala peste toate liniile din tranzactia curenta, nu per-document), nu sa-l "repare" din greseala.
3.4 Cine altcineva verifica remainder — scrie_cantitati_vanzari_avize (:15420-15479)
Ruleaza necondiționat pe ramura tip=4, indiferent de V_VERIFICARE — scrie in VANZARI_CANTITATI
cate din cantitatile avizate au fost efectiv consumate de aceasta factura (mecanismul care alimenteaza
ramas la sectiunea 3.3). Nu depinde de crsarticole — foloseste VANZARI_DETALII_TEMP (deja
populat de do_scrie_articole(), apelat inaintea acestui Do Case din VFP, :14264) si tabele reale.
Confirma independenta totala de crsarticole a partii Oracle — singurul lucru pe care Oracle il
primeste de la VFP e semnalul binar pnParametruAditional.
4. Proiectare — variante comparate
Varianta (a) — cursor propriu, minimal, decuplat de populare
Un cursor separat (crsregistru), incarcat cu id_c/id_articol + cantitate ramasa, populat din
aceeasi interogare care alimenteaza azi crsarticole, dar independent de campurile de populare
(pret, TVA, valuta...) pe care S4 punctul 1 le muta pe cautare filtrata.
Ce se atinge: aceleasi patru metode (do_adauga_articol, do_sterge, do_modifica,
do_scrie_factura) — schimba doar numele cursorului tinta, logica ramane identica.
Ce se rupe: nimic structural — e schimbarea minima de sintaxa.
Cost: mic, dar nu rezolva problema de fond: tot exista o a doua copie a "cat a mai ramas",
tinuta manual in memorie VFP, care trebuie sa ramana in sincron cu ce calculeaza Oracle la scriere
(sectiunea 3). E acelasi risc de azi, doar cu un cursor mai ingust. Nu e "sursa unica" — e aceeasi
sursa dubla, doar mai mica.
Varianta (b) — recalculul pe server la scriere, in loc de suma locala (RECOMANDATA pentru Rolul A)
Inlocuieste cele doua Select crsarticole / Calculate Sum(cantitate) To lnCantitateRamasa din
do_scrie_factura (:14303-14304, :14334-14335) cu un apel Oracle nou, facut dupa
Thisform.do_scrie_articole() (:14264, care populeaza deja VANZARI_DETALII_TEMP cu exact ce e pe
cale sa fie scris) si inainte de Do Case care alege scrie_factura_avize/scrie_factura2.
Doua proceduri Oracle noi, minimale, care reutilizeaza exact interogarea pe care
inchide_comanda/marcheaza_facturat o ruleaza oricum peste doua randuri mai jos in acelasi flux —
nu o interogare noua, o extragere a celei existente intr-o forma care returneaza un numar in loc sa
scrie:
-- comanda: acelasi WHERE ca inchide_comanda (:5771-5818), dar SUM in loc de INSERT
FUNCTION cantitate_ramasa_comanda(V_ID_COMANDA IN NUMBER) RETURN NUMBER IS
V_RAMAS NUMBER;
BEGIN
SELECT NVL(SUM(SIGN(A.CANTITATE) *
GREATEST(SIGN(A.CANTITATE) * (A.CANTITATE - NVL(B.CANTITATE,0) - NVL(C.CANTITATE,0)), 0)), 0)
INTO V_RAMAS
FROM COMENZI_ELEMENTE A
LEFT JOIN (SELECT ID_ARTICOL, ID_POL, ID_VALUTA, PRET, SUM(CANTITATE) AS CANTITATE
FROM VANZARI_DETALII_TEMP GROUP BY ID_ARTICOL, ID_POL, ID_VALUTA, PRET) B
ON A.ID_ARTICOL = B.ID_ARTICOL AND A.ID_POL = B.ID_POL AND A.ID_VALUTA = B.ID_VALUTA AND A.PRET = B.PRET
LEFT JOIN (... acelasi C ca in inchide_comanda, VANZARI/VANZARI_DETALII istoric ...) C ON ...
WHERE A.STERS = 0 AND A.ID_COMANDA = V_ID_COMANDA;
RETURN V_RAMAS;
END;
-- avize: acelasi calcul ca in marcheaza_facturat(V_VERIFICARE=1), dar returnat, nu aplicat
FUNCTION exista_ramas_avize(V_LISTAID IN VARCHAR2) RETURN NUMBER IS ...
Ce se atinge: do_scrie_factura (inlocuieste 4 linii VFP cu un apel Oracle + citire scalar), plus
doua functii Oracle noi in PACK_FACTURARE, care extrag logica deja scrisa in inchide_comanda/
marcheaza_facturat, nu o duplica din nimic. do_adauga_tot, do_sterge, do_modifica raman
neschimbate pentru Rolul A — nu mai au ce sa faca, pentru ca nimic nu mai citeste suma lor.
Ce se rupe: niciun comportament — recalculul Oracle e mai precis decat suma locala (vede
concurenta: doi operatori facturand din aceeasi comanda simultan; crsarticole local nu vede
modificari facute de alt utilizator dupa deschiderea formularului — bug latent de azi, pe care
varianta (b) il elimina ca efect secundar, nu ca scop).
Cost: doua functii Oracle noi (SQL, nu logica noua — extrase din proceduri existente), un apel
suplimentar goExecutor in do_scrie_factura.
De ce e "sursa unica": Oracle calculeaza "cat a ramas" o singura data, in doua locuri (recalcul
inainte de scriere + recalcul in inchide_comanda/marcheaza_facturat la scriere), din aceeasi
interogare, niciodata dintr-o copie VFP. Nu mai exista sincronizare de mentinut, pentru ca nu mai
exista a doua stare.
Varianta (c) — plafon Rol B recalculat la cerere, nu tinut in memorie (RECOMANDATA pentru Rolul B)
Pentru grupul Rol B (1,22,29,2-lista, 23,41, 8,9,24), plafonul din do_modifica
(lnCantitateMax, sectiunea 1.3(a)) si decrementul din do_adauga_articol/do_sterge (sectiunea
1.1/1.2) devin un apel Oracle facut la fiecare adaugare/editare, in loc de o valoare tinuta in
crsarticole — interogare filtrata pe id_articol, aceeasi forma ca cursor_preturi/
cursor_gestiune filtrate (deja proiectate la S4 punctul 1, sectiunea 4), minus stocul deja rezervat
in sesiunea curenta (calculabil din crsfactura insusi, sumand liniile deja adaugate cu acelasi
id_articol — cursor local, dar de linii efectiv adaugate, nu de "cat mai era", deci nu mai poate
diverge de continutul real al facturii in curs).
Ce se atinge: do_adauga_articol (:13034-13051 dispare, inlocuit cu un apel la cerere din
do_alege_stoc, care oricum face verificare de stoc pe server — de confirmat la implementare daca
verificarea existenta acopera deja acest plafon sau trebuie extinsa), do_sterge (:14640-14669
Rol B dispare, ramane doar Rolul A), do_modifica (:13775-13798 plafonul se cere pe server, nu se
reconstituie din cursor).
Ce se rupe: de verificat cu Marius — plafonul Rol B azi e un avertisment soft (do_alege_stoc
primeste lnCantitateMax ca parametru, dar verificarea reala de stoc la comitere ramane oricum in
Oracle, la adauga_articol_factura/scriere; codul citit aici nu arata un blocaj hard bazat pe
crsarticole — de confirmat explicit, nu presupus, ca UX-ul de azi (mesaj/limitare la alegerea
cantitatii) nu depinde de faptul ca plafonul persista in memorie intre doua adaugari ale aceluiasi
articol in aceeasi sesiune, ci doar de valoarea citita la momentul respectiv).
Cost: mediu — mai multe interogari Oracle mici (una per adaugare/editare de linie gestionabila),
in loc de aritmetica locala. Justificat de acelasi motiv ca varianta (b): elimina a doua copie.
Comparatie si recomandare finala
| (a) cursor propriu minimal | (b) recalcul server (Rol A) | (c) plafon la cerere (Rol B) | |
|---|---|---|---|
| E "sursa unica"? | Nu — copie mai mica, tot manuala | Da | Da |
| Risc de divergenta fata de Oracle | Identic cu azi | Eliminat (Oracle citeste Oracle) | Eliminat |
| Cost implementare | Mic | Mic-mediu (2 functii Oracle) | Mediu (mai multe roundtrip-uri) |
| Rezolva corectia sectiunii 0 (23/41 blocheaza punctul 1) | Nu direct | N/A (23/41 nu sunt Rol A) | Da |
Recomandare: (b) pentru Rolul A, (c) pentru Rolul B. Impreuna elimina complet nevoia de a incarca
crsarticole in masa pentru bookkeeping — incarcarea ramasa (daca ramane) e strict pentru
enumerare la "adauga tot" (sectiunea 1.4), care e un scop diferit si mai ingust (nu mai trebuie sa
tina sincron o cantitate, doar sa listeze randuri candidate o singura data, la deschidere).
5. Cazul limita: adauga si sterge inainte de salvare
Azi: do_adauga_articol decrementeaza crsarticole/crsarticole1 (Rol A si/sau B, dupa tip);
do_sterge reface exact simetric, inainte de orice salvare (sectiunile 1.1-1.2 sunt perechi
simetrice pe fiecare grup de tipuri). Suma finala din crsarticole la momentul do_scrie_factura
reflecta corect doar liniile ramase in crsfactura, indiferent cate au fost adaugate si sterse
intre timp — pentru ca fiecare stergere anuleaza exact adaugarea corespunzatoare.
In varianta recomandata (b+c): cazul limita devine trivial, nu doar acoperit — pentru ca nu
mai exista o stare intermediara de sincronizat. Rolul A: do_scrie_factura calculeaza remainder-ul
o singura data, la scriere, din VANZARI_DETALII_TEMP care contine exact liniile ramase in
crsfactura la acel moment (populat de do_scrie_articole() chiar inainte, Scan peste crsfactura
curent — sectiunea 4 varianta (b)) — liniile adaugate-si-sterse nu ajung niciodata in
VANZARI_DETALII_TEMP, deci nu influenteaza calculul, fara nicio actiune suplimentara. Rolul B:
plafonul la fiecare adaugare se cere din nou pe server, minus ce e deja in crsfactura in acel
moment — o stergere anterioara pur si simplu nu mai apare in acea suma, fara "refacere" explicita.
Cazul limita nu mai e un caz special de tratat — e comportamentul implicit al oricarei citiri facute
din starea curenta, in loc de dintr-un contor tinut manual.
6. Per tip de document
| Tip(uri) | Rol azi | Ce se schimba in varianta recomandata |
|---|---|---|
Comanda 3,21,25,28,42,47 |
Rol A (inchidere automata) | do_scrie_factura cheama functia Oracle noua (4b) in loc de Calculate Sum; do_adauga_tot/do_sterge raman (enumerare + Rol B nu se aplica pe comanda insasi, doar pe articolele ei individuale daca sunt gestionabile — de verificat, comanda nu apare in niciuna din listele Rol B de la sectiunea 1, deci pare curatata deja) |
Avize 4 |
Rol A (marcheaza_facturat) | idem, functia Oracle pentru avize (4b) |
Contract 2,6,26,52 |
Jumatate crsarticole (delegat la cursor_preturi, per raportul punctului 1) e Rol B doar pentru poDate.tip=2 And opt_facturare=0; restul (crsarticole1, rate+articole OPT_FACTURARE=3) nu are bookkeeping in sectiunea 1 (needitat de cautare, ramane cum e azi, per raportul punctului 1 sectiunea 7) |
Rolul B pe jumatatea opt_facturare=0 trece pe varianta (c); 26,52 nu apar in niciuna din listele Rol B/A gasite in cod — de verificat separat, posibil fara bookkeeping deloc pe aceste doua, nesemnalat ca atare in codul citit aici |
Transfer 41 (standard); 23 (doar prototip, standard il trateaza ca lista de preturi) |
Rol B pur (niciun Rol A — confirmat sectiunea 3.1, 41/23 cad pe ELSE in finalizeaza_factura) |
Trece integral pe varianta (c); aceasta e corectia care debloca pasul 5 al punctului 1 — fara ea, oprirea incarcarii in masa pe 23,41 (propusa acolo) ar sparge plafonul de stoc existent |
Lista de preturi 1,5,7,10,22,29 |
Rol B doar pentru articole gestionabile (1,22,29; 5,7,10 nu apar in Do Case-urile Rol B — fara bookkeeping, confirmat pe cod) |
1,22,29 trec pe varianta (c); 5,7,10 nu au nimic de decuplat, deja curatate |
Retur 8,9,24 |
Rol B (inversul directiei fata de restul) | Varianta (c), simetric |
Restaurant 45, K 48,49 |
Nu ating Rol A/B (in afara Do Case-urilor gasite; 45 explicit exclus la poArticol.gestionabil=0 Or gnScadereStoc=0 Or poDate.tip=45 in ambele metode, deci ocoleste bookkeeping-ul indiferent de gestionabilitate) |
Fara schimbare — deja fara bookkeeping de decuplat |
Tipuri semnalate ca neclare, de stabilit separat, nu presupuse aici: 26 (aviz din contract) si
52 (contract in valuta/alt subtip) nu apar explicit in niciuna din listele Rol A/B din sectiunea 1 —
codul citit in aceasta sesiune nu confirma nici prezenta, nici absenta bookkeeping-ului pe ele
specific; tratamentul lor pare sa urmeze grupul 2,6 din Inlist-urile care le includ, dar niciun
Do Case din sectiunea 1 nu le mentioneaza individual in afara de includerea in Inlist(poDate.tip, 2, 6, 52) la do_scrie_articole (rate) — nu la bookkeeping-ul de crsarticole.
7. Pasi de implementare, ordonati
- Pas 1 — Oracle: extrage functiile de recalcul remainder (
cantitate_ramasa_comanda,exista_ramas_avize) din logica deja scrisa ininchide_comanda/marcheaza_facturat, fara sa modifice acele proceduri. Gata cand: apelate manual cu parametrii unei comenzi/unui grup de avize cunoscute, valoarea returnata coincide cu suma pe careCalculate Sum(cantitate)din VFP o calculeaza azi pestecrsarticole, pe acelasi document, in aceeasi stare (comparatie directa, inainte de orice alta schimbare). - Pas 2 — VFP:
do_scrie_facturacheama functiile noi in loc deCalculate Sum(sectiunea 4b), pastrand identic restul logicii (pnFacturaRetur, mesajele de confirmare,pnParametruAditional). Gata cand: pe un document de comanda si unul de aviz, cu acelasi scenariu (facturare partiala), dialogul de confirmare apare in acelasi moment si cu acelasi rezultat ca inainte de Pas 2 — regresie manuala, comparatie inainte/dupa pe acelasi document. - Pas 3 — Oracle: functie de plafon Rol B filtrata pe articol, reutilizand
WHERE-ul dincursor_preturi/cursor_gestiune(deja proiectat la S4 punctul 1, sectiunea 4), minus ce e deja incrsfacturapentru acelasi articol. Gata cand: pe un articol gestionabil cunoscut, valoarea returnata coincide cucrsarticole.cantitatede azi, la aceeasi stare a sesiunii (inainte de orice adaugare). - Pas 4 — VFP:
do_adauga_articol/do_sterge/do_modificafolosesc plafonul cerut pe server pentru grupul Rol B, in loc deReplace cantitatepecrsarticole(sectiunea 4c). Gata cand: cazul limita de la sectiunea 5 (adauga-apoi-sterge inainte de salvare) produce acelasi plafon disponibil ca azi, verificat manual pe un articol cu stoc limitat. - Pas 5 — activarea pasului 5 al punctului 1 pe
23/41(oprirea incarcarii in masa), acum ca Pasul 4 a mutat Rolul B in afara luicrsarticole. Gata cand: deschiderea formularului pe tip41(si23pe prototip) nu mai executacursor_gestiune/cursor_preturila deschidere, dar adaugarea unui articol tot respecta plafonul de stoc (verificat manual). - Pas 6 — curatare:
crsarticoleramane incarcat doar unde e nevoie de enumerare (comanda, aviz, contract-rate — pentru "adauga tot" existent), fara bookkeeping legat de el. Gata cand: grep peofacturare.vc2pentruReplace cantitate.*crsarticole(in afara populare) nu mai gaseste potriviri indo_adauga_articol/do_sterge/do_modifica. - Pas 7 — verificare paritate finala (sectiunea 8), pe toate tipurile cu document sursa.
Depinde de: S4 punctul 1 (Pasii 1-4 din PROIECTAREA acelui punct trebuie sa existe, dar Pasii 1-4
de aici pot fi facuti independent, inaintea sau in paralel cu populare — nu depind de cautarea
filtrata, doar de crsarticole/crsfactura existente azi). Pasul 5 de aici depinde explicit de Pasul
4 de aici (nu poate porni inaintea lui).
8. Cum se verifica paritatea inchiderii automate
Comanda (nu exista flag INCHISA persistat — cautat explicit in tot pachetul, zero potriviri;
starea se deriva din COMENZI_ELEMENTE vs VANZARI_DETALII):
-- inainte de facturare partiala + inchidere fortata (flux vechi vs nou, aceeasi comanda X)
{call pack_facturare.cursor_comanda(V_DATA_CURS, V_TIP, 'X', V_ID_UTIL, :cursor)}
-- numara randurile ramase (trebuie sa fie 0 dupa inchidere fortata, identic pe ambele fluxuri)
SELECT COUNT(*) FROM (...continutul cursorului de mai sus...);
-- verificare directa a compensatiei inserate de inchide_comanda
SELECT ID_ARTICOL, ID_POL, CANTITATE FROM COMENZI_ELEMENTE
WHERE ID_COMANDA = :X ORDER BY ID_COMANDA_ELEMENT DESC; -- randul nou trebuie sa fie identic pe ambele fluxuri (aceeasi cantitate compensatorie)
Avize (flag real: VANZARI.FACTURAT):
SELECT ID_VANZARE, FACTURAT, ID_UTILFACT FROM VANZARI
WHERE ID_VANZARE IN (:lista_avize_test)
ORDER BY ID_VANZARE;
-- rulat dupa fiecare flux (vechi, apoi nou, pe date de test resetate identic), FACTURAT trebuie sa coincida rand cu rand
Protocol de comparatie, aplicabil pe fiecare tip cu document sursa (comanda: alege una din
3,21,25,28,42,47; avize: 4):
- reseteaza datele de test la aceeasi stare (aceeasi comanda/aviz, aceleasi cantitati ramase);
- factureaza partial pe fluxul vechi (cod actual), noteaza rezultatul interogarilor de mai sus;
- reseteaza din nou la aceeasi stare initiala;
- factureaza identic (aceleasi linii, aceleasi cantitati) pe fluxul nou (dupa Pasii 1-2 din sectiunea 7), noteaza acelasi rezultat;
- compara — trebuie sa fie identic, inclusiv pe cazul limita de la sectiunea 5 (adauga-si-sterge inainte de salvare) si pe cazul avizelor multiple agregate intr-o singura factura (sectiunea 3.3, riscul de marcare in bloc).
Zero cazuri testate azi nu e dovada (memorie de proiect) — protocolul de mai sus cere minim un caz per tip din lista, plus cazul limita, nu doar "a mers o data".
9. Riscuri si ce ramane de decis de Marius
- Corectia sectiunii 0/6: pasul 5 al proiectarii punctului 1 (
s4_cautare_articole_server.md:417-424) presupune ca23,41n-au bookkeeping de pastrat — gasit aici ca au Rol B activ. De confirmat cu Marius daca punctul 1 se re-deschide pentru aceasta corectie sau ramane cum e, cu mentiunea ca aplicarea pe23,41asteapta finalizarea punctului 2 (asa cum recomand la Pasul 5, sectiunea 7). - Asimetria
do_modificafata dedo_adauga_articol/do_sterge(sectiunea 1.3, grupul1,22,29,2-lista nu se ajusteaza la editarea cantitatii unei linii existente) — preexistenta, nu introdusa de decuplare. De decis daca varianta noua (Rol B pe server, sectiunea 4c) trebuie sa reproduca exact aceasta asimetrie (plafonul nu se recalculeaza corect la editare pe aceste tipuri, ca azi) sau sa o corecteze ca efect secundar al recalcularii la cerere (care, prin natura ei, ar elimina automat asimetria — orice cerere de plafon citeste starea curenta, indiferent daca a fost o adaugare sau o editare). Recomandare: lasat sa se corecteze de la sine (comportament mai corect, cost zero suplimentar, dar semnaleaza explicit ca S4 schimba acest comportament punctual, nu doar "decupleaza" — de mentionat in changelog daca se alege aceasta cale). - Riscul de concurenta multi-utilizator, semnalat ca beneficiu la sectiunea 4b, dar nediscutat cu
Marius: varianta recomandata face
crsarticolelocal sa nu mai poata diverge de Oracle la scriere, dar tot poate divarge in timpul editarii (doi operatori pe aceeasi comanda, unul vede plafonul invechit pana la urmatoarea lui adaugare/editare). Nu e un risc nou introdus — e identic cu azi pe cursorul static incarcat o data la deschidere — dar varianta (c) il reduce (cere plafonul proaspat la fiecare adaugare, nu o singura data la deschidere), fara sa-l elimine complet (ramane fereastra intre "am cerut plafonul" si "am scris linia"). De mentionat ca imbunatatire, nu garantie. - Cont Rol B pe contract
26,52: nu s-a gasit dovada nici de prezenta, nici de absenta bookkeeping pe aceste doua tipuri in codul citit (sectiunea 6) — de verificat separat inainte de a le include in Pasul 3/4 al implementarii, nu de presupus ca urmeaza grupul2,6. - Functiile Oracle noi (Pas 1, sectiunea 7) sunt o extragere, nu o duplicare — dar tot inseamna cod
PL/SQL nou in
PACK_FACTURARE, care trebuie revizuit separat de cineva familiar cu pachetul (schema exacta aJOIN-urilor dininchide_comanda, reprodusa aici din citire, nu din executie reala pe Oracle — verificarea Pasului 1 din sectiunea 7 e obligatorie inainte de a continua).
Handoff
Cercetare incheiata in aceasta sesiune. Toate cele 9 puncte cerute sunt acoperite, cu citate
fisier:linie verificate direct pe fisierele reale (ofacturare.vc2, nu .bak; corpul
PACK_FACTURARE.sql, nu spec-ul comentat). Descoperirea centrala (Rol A vs Rol B, si corectia asupra
pasului 5 al punctului 1 pentru 23/41) nu era vizibila din raportul punctului 1 — acela trata
crsarticole ca un singur registru omogen; aici s-a aratat ca sunt doua mecanisme cu scopuri
diferite, unul (Rol A) recalculat oricum de Oracle la scriere si deci usor de mutat pe server fara
pierdere de paritate, celalalt (Rol B) un plafon UI care nu alimenteaza nicio decizie de business.
Niciun fisier de cod atins, niciun git_sync.ps1/txt2vcx.ps1 rulat, nicio scriere pe Oracle (doar
Read/Select-String/grep pe fisiere de pe disc).