174 lines
15 KiB
Markdown
174 lines
15 KiB
Markdown
# Verificare adversariala — proiectarea parametrului de cont (`canal_cont_venit_fara_politica.md`, sectiunea "Proiectarea parametrului de cont contabil")
|
|
|
|
Mandat: **nu re-proiecta** — proiectarea exista deja in `canal_cont_venit_fara_politica.md:13-227`
|
|
(citita integral). Aici: incercare de a o sparge + inchiderea celor 4 goluri semnalate de autor.
|
|
Sursa PL/SQL: aceeasi, `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
|
(17217 linii). Oracle: doar `SELECT`, schema `MARIUSM_AUTO`/`ROA_CENTRAL`. VFP: doar `vfp_symbols.ps1`
|
|
(index, read-only). **Niciun fisier de cod atins.**
|
|
|
|
## Verdict, in patru randuri
|
|
|
|
**Proiectarea rezista la verificarea adversariala** — nu am gasit nicio eroare care sa-i invalideze
|
|
concluzia centrala. Am gasit **un rand din tabel mai slab decat descris** (`CU_TVA=1` hardcodat NU e
|
|
complet inofensiv — are un efect colateral masurabil, minor, prin `nproc_tva_max`), **un rand mai
|
|
tare decat descris** (`IN_VALUTA` din `nin_valuta` e o sursa solida, nu doar plauzibila — parametru
|
|
obligatoriu, fara `DEFAULT`), **o excludere confirmata corecta prin structura schemei** (articolul
|
|
"compus" nu poate exista pe o linie fara politica — nu o gaura), si **un gol raportat de autor acum
|
|
inchis cu fapt** (cele doua clase de la `:14069`/`:18089` sunt distincte, nu o duplicare a aceleiasi
|
|
metode — 2 locuri VFP de atins, nu 1). Pe decizia 35 (regenerare = acelasi cod): **DA, calea Oracle
|
|
e identica**, dar exista un obstacol real, deja proiectat separat (`idfact_refolosire_si_documente.md`),
|
|
**in alt pachet** (`PACK_CONTAFIN`), nu in `pack_facturare` — nu invalideaza design-ul curent, dar
|
|
regenerarea nu e completa fara acea a doua bucata de lucru.
|
|
|
|
---
|
|
|
|
## 1. Decizia 35 — regenerarea foloseste identic `pack_facturare`?
|
|
|
|
**DA pe calea Oracle relevanta pentru parametrul de cont, cu o exceptie deja cunoscuta si separata.**
|
|
|
|
Verificat direct: `scrie_factura2` reseteaza starea de sesiune la fiecare apel prin
|
|
`initializeaza_date_factura` (`PACK_FACTURARE:1808-1846`), care face
|
|
`DELETE FROM VANZARI_DETALII_TEMP;` (`:1835`) si `pack_facturare.nid_act := 0;` (`:1836`) —
|
|
**contorul folosit de `scrie_nota`/`scrie_discount`/`scrie_tva` la fiecare `INSERT INTO ACT_TEMP`
|
|
porneste curat la fiecare emitere, inclusiv la o a doua** (regenerare). Nimic in
|
|
`contabilizeaza_articol`, ramura noua inclusa, citeste vreo stare care ar presupune "documentul e
|
|
nou" — toate variabilele folosite (`nid_venchelt`, `nid_sectie_stoc`, `nin_valuta`, `nid_set`,
|
|
`nid_util`) sunt **citite**, nu verificate contra unei stari anterioare.
|
|
|
|
**Obstacolul real e in `PACK_CONTAFIN`, nu in `pack_facturare`**: reemiterea cu acelasi `ID_FACT` (ca
|
|
sa nu se dubleze documentul contabil la regenerare) ar da azi `ORA-00001` pe `PK_DOCUMENTE`, pentru ca
|
|
`SET_IDFACT` ia mereu `SEQ_IdFact.NEXTVAL` si documentul vechi ramane in tabel doar soft-sters
|
|
(`STERS=1`), nu disparut — analiza completa, cu solutia (variabila de pachet noua in `PACK_CONTAFIN`,
|
|
`MERGE ... WHEN MATCHED` pe `DOCUMENTE`), e deja facuta integral in
|
|
`idfact_refolosire_si_documente.md` (sectiunile A-C). **E exact "ceva care ar cere cod separat" — dar
|
|
separat inseamna alt pachet/alta procedura (`SET_IDFACT`, scrierea in `DOCUMENTE`), nu o ramura in
|
|
plus in `contabilizeaza_articol` sau `adauga_articol_factura`.** Ramane o bucata de lucru distincta,
|
|
necesara pentru ca regenerarea sa fie completa, dar nu intersecteaza si nu invalideaza design-ul
|
|
parametrului de cont.
|
|
|
|
**Un gol neadresat de design, gasit acum**: ramura `pack_facturare.ntip = 4` ("facturare din avize",
|
|
`:7520-7537`) apeleaza `scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount — design-ul
|
|
nu spune ce se intampla pe aceasta ramura in cazul fallback. In practica, **probabil nu conteaza**:
|
|
"facturare din aviz" cere azi ca linia sursa sa aiba deja `id_pol` (`adauga_articol_factura:5096`,
|
|
`AND A.ID_POL = V_ID_POL` pe `VANZARI_DETALII` a avizului sursa) — un aviz fara politica n-ar fi
|
|
ajuns niciodata pana la factura pe acest drum, deci scenariul "fallback pe `ntip=4`" e putin probabil
|
|
sa apara. **Dar design-ul nu spune asta explicit** — de adaugat o linie care sa acopere/exclude
|
|
explicit acest caz inainte de implementare, nu de presupus tacit.
|
|
|
|
## 2. Cele 4 goluri semnalate de autor
|
|
|
|
### 2a. `ofacturare.vc2:14069`/`:18089` — doua metode sau o duplicare?
|
|
|
|
`vfp_symbols.ps1 -Where 'ofacturare.vc2:14069'` -> **`frm_facturare_articole.do_scrie_articole`**
|
|
(`ofacturare.vc2:13967-14195`). `-Where 'ofacturare.vc2:18089'` -> **`frm_facturare_articole2.do_scrie_articole`**
|
|
(`ofacturare.vc2:18003-18221`). **Confirmat: doua clase distincte, `frm_facturare_articole` si
|
|
`frm_facturare_articole2`, fiecare cu propria metoda `do_scrie_articole`** (acelasi nume de metoda,
|
|
nu aceeasi clasa) — nu o duplicare literala a unui singur cod. **Consecinta pentru implementare**:
|
|
parametrul nou trebuie cablat **in doua locuri VFP**, nu unul — ambele clase construiesc separat
|
|
apelul RPC catre `adauga_articol_factura`. Nu schimba verdictul de fezabilitate, dar schimba
|
|
suprafata de lucru VFP fata de impresia "un singur loc de atins".
|
|
|
|
### 2b. `scrie_tva` la cota 0% cu `CU_TVA` hardcodat `1` — efect real, nu inofensiv
|
|
|
|
Verificat corpul `scrie_nota` (`:12537-12558`): actualizarea `nproc_tva_max`/`nid_jtva_coloana`/
|
|
`nTaxCode` **nu e neconditionata** — e in interiorul `IF V_CU_TVA = 1 THEN`, deci exact conditionata
|
|
de flagul pe care design-ul propune sa-l hardcodeze. In interior insa, comparatia
|
|
`IF pack_facturare.nproc_tva_max < V_PTVA THEN` **nu se uita la suma**, doar la rata — deci daca
|
|
articolul fallback are `proc_tvav` real 0% (scutit) si `CU_TVA` e fortat la `1`, rata 0% **intra in
|
|
comparatia de maxim** si, daca e prima/singura linie a documentului (`nproc_tva_max` initial `-1`,
|
|
`:1885`), **castiga** — seteaza `nid_jtva_coloana`/`nTaxCode` la valorile acelei linii scutite.
|
|
Aceste doua variabile sunt consumate mai departe **in aceeasi procedura `scrie_factura2`**, la
|
|
discountul global pe factura (`:6164-6184`, `V_DISCOUNT_FACTURA <> 0`): rata/coloana/taxcode ale
|
|
liniei scutite ar ajunge sa descrie linia de discount a **intregii facturi**, chiar daca alte linii
|
|
au TVA real. **Verdict: `CU_TVA=1` hardcodat nu e "inofensiv" in toate cazurile cum spune design-ul —
|
|
are un efect colateral real, dar restrans la o combinatie specifica (linie fallback cu TVA 0% +
|
|
discount global pe factura + acea linie e cea cu rata "maxima" vazuta pana atunci).** Nu invalideaza
|
|
alegerea `CU_TVA=1` ca implicit (majoritatea liniilor reale au TVA nenul), dar intareste, nu slabeste,
|
|
cererea deja facuta de autor ("de confirmat cu Marius") — motivul de confirmat e mai concret decat
|
|
"linie de TVA cu suma 0, inofensiv".
|
|
|
|
### 2c. `INSERT INTO VANZARI_DETALII ... FROM VANZARI_DETALII_TEMP` — linie exacta, confirmata
|
|
|
|
`PACK_FACTURARE:13705-13757`, in `scrie_in_vanzari` (spec `:13488`, apelata din `finalizeaza_factura`
|
|
`:14787`, care e apelata la finalul fluxului de verificare — nu din `scrie_factura2`, alta faza a
|
|
aceluiasi pipeline). **Confirmat: lista de coloane e explicita**, 24 coloane
|
|
(`ID_VANZARE, ID_ARTICOL, LOT, SERIE, ID_RATA, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
|
|
PRET, PROC_TVAV, ID_JTVA_COLOANA, ID_JTVA_COLOANA_EX, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT,
|
|
EXPLICATIE, PRET_CU_TVA, DIFERENTA, CUSTODIE, ID_VANZARE_SET, ID_CTR, TAXCODE`) — **nu** `SELECT *`.
|
|
Exact ce presupunea design-ul (sectiunea 2, citand `nota_contabila_fara_politica.md` fara
|
|
re-verificare): daca se doreste ca `CONT_VENIT` sa ajunga si in `VANZARI_DETALII` (trasabilitate),
|
|
**aceasta lista trebuie extinsa explicit** — fara acest pas, coloana noua ramane doar pe
|
|
`VANZARI_DETALII_TEMP` si `ACT_TEMP`, invizibila in `VANZARI_DETALII` dupa fapt. Confirmarea nu
|
|
schimba verdictul design-ului (deja marcase pasul ca "recomandat, nu strict necesar"), doar il
|
|
transforma din presupunere in fapt verificat.
|
|
|
|
### 2d. Apelanti `adauga_articol_factura` din restul suitei
|
|
|
|
Sarit, cum a cerut team-lead-ul — alt agent lucreaza pe suprafata de regresie.
|
|
|
|
---
|
|
|
|
## 3. Verificare adversariala pe tabelul din design (sectiunea 3 a raportului sursa)
|
|
|
|
| Rand din tabel | Incercare de infirmare | Rezultat |
|
|
|---|---|---|
|
|
| `IN_VALUTA` <- `pack_facturare.nin_valuta` | E setat pe toate fluxurile relevante, sau ramane nul si azi nu conteaza? | **Mai solid decat descris.** `nin_valuta := V_IN_VALUTA` (`:1902`) e in `initializeaza_date_factura`, apelata **o data la inceputul fiecarei emiteri**, cu `V_IN_VALUTA IN NUMBER` **fara `DEFAULT`** in semnatura (`:1827`) — VFP e obligat sa trimita o valoare, nu poate omite parametrul. Nu e un fallback de sesiune care "poate ramane nesetat" (ca `nid_venchelt`), e un flag de document obligatoriu, mereu curent. |
|
|
| `ID_VENCHELT`/`ID_SECTIE` <- variabile de sesiune | Cine le seteaza si cand? | **Confirmat, cu nuanta.** `nid_venchelt := V_ID_VENCHELT` si `nid_sectie_stoc := V_ID_SECTIE` (`:1876-1877`), tot in `initializeaza_date_factura`, din parametri **fara `DEFAULT`** insa **VFP poate trimite `NULL`** pe ei (sunt opționale ca *valoare*, nu ca prezenta in apel) — deci pot ramane `NULL` pe tot documentul. **Nu e o regresie**: pe ramura veche, `NVL(nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))` ar da tot `NULL` daca nici politica nu are aceste campuri — acelasi rezultat posibil, aceeasi cauza (sesiune nesetata), nu unul nou introdus de fallback. |
|
|
| Garda `descarca_gestiune` copiata identic | Acopera `id_gestiune=-1000` si `in_stoc`? | **Da, fara diferenta.** Garda foloseste `detalii_articol.id_gestiune`/`.in_stoc` — campuri populate identic de `adauga_articol_factura` indiferent de ramura (nu depind de politica azi, nici in design). Copierea literala a conditiei (`:7473-7475`) e corecta prin constructie. |
|
|
| `ASCD`/`ASCC` <- `GetAnaliticByGrupUtilizatori` | Ce intoarce pe `NO_DATA_FOUND`, e acceptabil? | **Confirmat `NULL`** (corp citit `:16704-16723`: `lcAcont` declarat fara valoare implicita, `EXCEPTION WHEN NO_DATA_FOUND THEN NULL;`, `RETURN lcAcont`). Acceptabil — e exact fallback-ul deja folosit azi, necondiționat, pe ramurile de aviz (`:7416-7417,7421-7422`) si `ACT_TEMP.ASCD/ASCC` sunt nullable (confirmat DDL, `canal_cont_venit_fara_politica.md` DDL section). Nu e un risc nou. |
|
|
| Articol "compus" (`V_COMPUS=1`) lasat in afara scopului | Poate un articol ales ad-hoc din nomenclator fi compus? | **NU — exclus prin structura schemei, nu prin presupunere.** Interogat direct `ALL_VIEWS.VCRM_POLITICI_PRET_ART`: coloana `COMPUS` citita de `contabilizeaza_articol` (`SELECT COMPUS, ID_POL_ART ... FROM VCRM_POLITICI_PRET_ART`, `:7279-7283`) e definita in view ca `CASE WHEN PA.ID_POL_ART IN (SELECT DISTINCT ID_PACHET FROM CRM_PACHETE_ARTICOLE WHERE STERS=0) THEN 1 ELSE 0 END` — o proprietate a **perechii (articol, politica)**, identificata prin `ID_POL_ART` (cheia surogat a randului din `CRM_POLITICI_PRET_ART`). O linie fara politica **nu are** un `ID_POL_ART` — deci intrebarea "e articolul compus" nu se poate pune structural pe aceasta ramura. (`NOM_ARTICOLE.COMPUS` exista ca alta coloana, aliasata `art_compus` in acelasi view, dar **nu e citita de `contabilizeaza_articol`** — irelevanta aici.) Excluderea din design e corecta, nu o gaura. |
|
|
| `RETURN V_INCASAT_CALCUL` | Se calculeaza corect pe ramura noua sau intoarce 0? | **Corect, prin constructie.** Design-ul specifica explicit aceeasi acumulare ca azi (`V_INCASAT_CALCUL := V_INCASAT_CALCUL + scrie_nota(...)`, apoi `- scrie_discount(...)`), cu `V_INCASAT_CALCUL` initializat `0` la declaratie (`:7178`), inainte de `IF`-ul care selecteaza ramura — identic cu azi. Niciun risc de `RETURN 0` gasit. |
|
|
|
|
---
|
|
|
|
## 4. Hardcodarile `SCD='4111'` / `CU_TVA=1` — exista o sursa mai buna?
|
|
|
|
Cautare directa in tot `PACK_FACTURARE` pentru orice sursa alternativa care nu trece prin
|
|
`NOTE_CONTABILE`: config de firma (`getoptiunefirma('CONT...')`), cont implicit pe partener/client
|
|
(`PARTENERI.CONT*`), flag de scutire TVA pe articol/client (`SCUTIT`, `EXCEPTAT`) — **zero rezultate
|
|
pentru toate cele trei cautari**. Singurul camp inrudit gasit, `ACT_TEMP.NEIMPOZAB`, e folosit in alt
|
|
scop (raportare sume neimpozabile), nu ca sursa pentru `CU_TVA`. **Concluzie: nu exista o sursa mai
|
|
buna in cod — hardcodarea (sau parametrul explicit trimis de VFP) ramane singura optiune.** Asta
|
|
intareste recomandarea deja facuta de autor: **de decis explicit cu Marius, nu de dedus din date**,
|
|
mai ales pentru `CU_TVA` dupa gasirea de la punctul 2b (efectul via `nproc_tva_max` nu mai e
|
|
"pur cosmetic").
|
|
|
|
---
|
|
|
|
## 5. Completare: `goExecutor.oExecuta` si cursorul de verificare — fals pozitiv, nu bug
|
|
|
|
Verdict: **fals pozitiv, confirmat cu argumente, nu doar presupus.** `oExecuta` (funcție-wrapper,
|
|
`COMUN\programe\oproceduri_comune.prg:121-159`) deleagă la `oExecute` (`:173-504`), care la rândul ei
|
|
face `SQLExec(lnHandle, lcSql, lcCursor)` (`:330`) — **`lcSql` e folosit exact cum a fost construit de
|
|
apelant, fără nicio rescriere care să adauge un bind lipsă.** Textul construit în
|
|
`COMUN\clase\ofacturare.vc2:14345-14359` (și identic la `:14373-14387`, `:18343-18371`) e sintaxă
|
|
ODBC escape `{call pack_facturare.scrie_factura2(...)}`, cu **16** argumente poziționale (15 `IN` +
|
|
`?@poDate.nid_vanzare` pentru `V_ID_VANZARE OUT NUMBER`, al 16-lea parametru declarat), paranteza se
|
|
închide imediat după — **al 17-lea parametru, `V_CURSOR_VERIFICARE OUT pack_facturare.cursor_facturare`
|
|
(spec `PACK_FACTURARE:656`), nu are niciun placeholder în text, nici legat, nici nelegat.** Asta e
|
|
**exact tiparul cunoscut al driverului ODBC Oracle pe care VFP îl folosește prin `SQLExec`**: cand
|
|
ultimul parametru declarat al unei proceduri e un `REF CURSOR OUT`, driverul îl detectează din
|
|
catalogul Oracle (nu din textul apelului) și întoarce automat rândurile lui ca *result set* al
|
|
apelului — **al treilea argument al `SQLExec`/`oExecute` (numele de cursor VFP, aici `lcCursorVerificare`)
|
|
e exact mecanismul de captare a acelui result set**, fără sa fie nevoie de bind explicit. Codul imediat
|
|
după apel (`ofacturare.vc2:14394-14397`, `llReturn = goExecutor.oExecuta(lcSql,lcCursorVerificare)`
|
|
urmat de `If Reccount(lcCursorVerificare)>0`) tratează cursorul ca fiind deja populat cu rânduri —
|
|
comportament incompatibil cu un apel eșuat sau cu un parametru nelegat (care ar da eroare Oracle,
|
|
nu un cursor gol interpretabil). **Nu pot confirma mecanismul din interiorul driverului însuși** (e
|
|
extern codebase-ului, verificabil doar prin comportament) — dar dovada indirectă e puternică: același
|
|
tipar apare identic în toate cele 4 locuri de apel găsite, neschimbat de-a lungul mai multor versiuni
|
|
(`v 2.0.13` -> `v 2.0.93`, comentarii de istoric vizibile în cod), iar ecranul de verificare (funcție
|
|
activă, folosită la fiecare emitere) depinde de acest cursor populat — dacă apelul ar eșua silențios,
|
|
ecranul de verificare n-ar arăta niciodată note propuse, ceea ce ar fi fost observat imediat. **Concluzie
|
|
pentru plan: anomalia nu există — nu trebuie tratată ca risc pe cele 7 produse.**
|
|
|
|
## Ce ramane neverificat
|
|
|
|
- Comportamentul exact al ramurii `ntip=4`/`scrie_fact_aviz_custodie` in scenariul fallback (punctul
|
|
1) — argumentat ca improbabil, nu testat/exclus explicit in design.
|
|
- Daca vreun raport Oracle activ presupune `ACT_TEMP.ID_VENCHELT`/`ID_SECTIE` populate pe liniile de
|
|
venit (relevant doar daca sesiunea nu seteaza `nid_venchelt`/`nid_sectie_stoc`) — in afara scopului
|
|
acestei verificari (cod PL/SQL only).
|
|
- Suprafata de regresie la nivelul apelantilor `adauga_articol_factura` din restul suitei — explicit
|
|
lasata altui agent.
|