diff --git a/changelog_roafacturare.txt b/changelog_roafacturare.txt index e0ecdfd..4306f1b 100644 --- a/changelog_roafacturare.txt +++ b/changelog_roafacturare.txt @@ -1,50 +1,9 @@ - - - - - - + 0.00 + Z0... + 4410.00 + 0.00 + E0 + Scutit cu drept de deducere... + +``` + +Categoria `Z` a grupului orfan vine din `GetTipTaxa` ramura cu ramura (`xmlefactura.prg:1116-1140`): +`intracomunitar=0` sare peste `'K'`, `taxare_inversa=0` sare peste `'AE'`, `scutit=0` sare peste +`'E'`, si cade pe `Case m.lcTipFactura = 'F' And m.lnProcTva = 0` -> **`'Z'`** (`:1128-1129`). + +In plus, cautarea categoriei pentru `AllowanceCharge` (`xmlefactura.prg:782-786`) devine ambigua: +`Select tip From C_TVA_FACTURA Where proc_tva = 0 Into Array agettipcota` gaseste acum **doua** +randuri (`E` si `Z`), iar codul ia neconditionat `agettipcota(1)` — primul in ordinea cursorului, nu +cel corect prin vreun criteriu. Ordinea nu e garantata de VFP si nu a fost fixata experimental; +categoria alocarii poate iesi `E` sau `Z` dupa caz — oricare din ele e gresita ca modelare, doar in +mod diferit. + +**Nu s-a reprodus scenariul end-to-end prin program** (ar fi cerut o factura scutita cu discount de +document, iar in baza de dev nu exista — sectiunea 4.2). Ramane stabilit din citirea codului plus +semantica `IIF`/NULL masurata la 3.1, nu dintr-o factura rulata efectiv prin program. + +### 3.3. Validare offline: XML-ul cu grup orfan nu e respins, dar controlul negativ functioneaza + +XML-urile de mai sus au fost construite pornind de la o factura reala acceptata de ANAF ca schelet +si trecute prin validatorul local `DUKIntegrator.jar` (acelasi apel ca la 4.3): + +| scenariu | ce contine | rezultat | +|---|---|---| +| `scutit_bug` | scutit + discount de document, **cu grupul orfan** (ce produce codul azi) | **ok** | +| `scutit_corect` | scutit + discount, un singur `TaxSubtotal` net (varianta corecta) | **ok** | +| `control_stricat` | `TaxExclusiveAmount` stricat intentionat, ca martor ca validatorul chiar valideaza | **eroare `BR-CO-13` + `BR-CO-15`** | + +Controlul negativ conteaza: fara el, un „ok" pe `scutit_bug` n-ar dovedi nimic — ar putea insemna +ca validatorul nu verifica deloc coerenta totalurilor. Cu el, e clar ca validatorul **chiar +verifica**, si totusi trece grupul orfan. + +**De ce trece**: regulile EN16931 pe categorii (`BR-S-08`, `BR-E-08`, `BR-Z-08`) cer ca baza +declarata pe o categorie sa fie egala cu *suma liniilor din acea categorie minus alocarile din +aceeasi categorie*. Grupul orfan respecta asta trivial (`0 - 539.82 = -539.82`), iar grupul `E` la +fel (`4410.00 - 0`). Aritmetica inchide; **modelarea fiscala e cea gresita**, si asta niciun +schematron nu prinde. + +Aceeasi rezerva ca in 4.3: validatorul local e din ianuarie 2022 (`ro16931-ubl-1.0.8`); validatorul +online curent al ANAF nu a fost apelat (interdictie explicita). + +> **Verdict sectiunea 3**: contopirea corecta descrisa mai sus e confirmata prin masuratoare pentru +> factura obisnuita (3.1) si infirmata pentru factura scutita / taxare inversa / intracomunitara +> (3.2), unde discountul de document formeaza un `TaxSubtotal` orfan cu baza negativa si categorie +> ambigua (`E` sau `Z`). Pe niciuna din cele doua forme validatorul local nu respinge XML-ul (3.3, +> si 4.3 pentru cazul cu cote mixte) — deci ramane, ca si defectul din sectiunea 4, o eroare +> **tacuta**, nu una care blocheaza factura. + +--- + +## 4. Defect real sau gol de proiectare + +**Verdict: NU s-a putut dovedi un defect de VALIDARE — nici pe factura obisnuita, nici pe cea +scutita cu grupul orfan (sectiunea 3.2-3.3), validatorul local nu respinge XML-ul. S-a dovedit in +schimb un defect de ATRIBUIRE FISCALA: pe facturi cu cote mixte, tot TVA-ul discountului de +document se scade din cota maxima, si pe facturi scutite/taxare inversa/intracomunitare, o +modelare fiscala gresita (grup orfan) care trece neobservata.** Adica exact ce banuia Marius, dar +consecinta stabilita nu e "factura respinsa", ci "TVA colectat gresit sau baza modelata gresit, +tacut". + +> **Nuanta ramasa, dupa inchiderea ipotezei din sectiunea 3**: ipoteza grupului orfan **s-a +> confirmat** pentru facturile scutite/taxare inversa/intracomunitare (3.2), dar validatorul local +> **nu** o respinge (3.3) — deci nu devine, cum s-ar fi putut anticipa, un defect de validare +> propriu-zis, ci ramane in aceeasi categorie cu restul sectiunii 4: o eroare de modelare care nu +> blocheaza factura. Testele din 4.3 (cote mixte) si cele din 3.3 (factura scutita) acopera acum +> ambele forme. + +### 4.1. Dovada ca mecanismul chiar produce `AllowanceCharge` de document in productie + +Am cautat in cele ~250 de XML-uri eFactura salvate pe disc (`D:\ROA\Efactura`, `D:\ROA\ROAEFACTURA`, +`D:\ROA\ROAFACTURARE\Utile\efactura`) blocurile `cac:AllowanceCharge` care contin `cac:TaxCategory` +(marca alocarii **de document**; cele de linie nu au `TaxCategory`, au `MultiplierFactorNumeric`). +Exemple reale: + +| fisier | Amount | TaxCategory | Percent | +|---|---|---|---| +| `D:\ROA\Efactura\2024_01\VENDING_MASTER_SRL\efactura_20240125_1526_BEST_POWER_DANCE_S_R_L_.xml` | 13.84 / 25.02 (doua) | S / S | 9 / 19 | +| `D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml` | 539.82 | E | 0 | +| `D:\ROA\Efactura\2024_03\VENDING_MASTER_SRL\efactura_20240301_3736_BRIANA_ALIMENT_SRL.xml` | 411.01 | S | 9 | +| `D:\ROA\Efactura\2024_07\VENDING_MASTER_SRL\efactura_20240426_7446_CIA_TRADE_POSSIBLE_SRL.xml` | 176.15 | S | 9 | + +Forma emisa (exemplu real, `...2885...`): +```xml +false + 95 + Discount + 539.82 + E0 + VAT +``` + +**Precizare de onestitate**: niciunul dintre aceste patru nu pot sa-l atribui sigur discountului de +**document**. Dimpotriva — la `...7446...` factura are linii pe 9% (2016.51) si pe 19% (1477.11), +iar unica alocare cade pe **9%**; daca ar fi fost discount de document, `Max(proc_tvav)` ar fi dat +**19%**. Deci acolo sunt discounturi **pe linie** cu `discount_evidentiat = 1` (sectiunea 1.4). La +fel `...1526...`, unde cele doua alocari sunt exact 2.00% din baza fiecarei cote. **Nu am gasit pe +disc un XML in care sa pot identifica pozitiv un discount de document.** Ce dovedesc aceste fisiere +e ca **traseul cod -> `AllowanceCharge` cu `TaxCategory` chiar functioneaza in productie** si ca +gruparea pe cote e reala cand exista mai multe pseudo-linii de discount. + +**Al treilea indiciu, pe acelasi fisier `...2885...`, in sens invers.** Factura e integral **scutita +cu drept de deducere**: toate cele trei linii sunt `E` / `Percent 0` cu `TaxExemptionReason = +"Scutit cu drept de deducere"`, `DocumentCurrencyCode = EUR`, si are `AllowanceCharge` la nivel de +document de 539.82 cu `TaxCategory ID = E`. Are **un singur `TaxSubtotal`**: `TaxableAmount = +3870.18`, adica exact `4410.00 − 539.82` — **net de discount, fara grup orfan**. + +Asta e semnificativ dupa sectiunea 3.2: `expltva` face parte din cheia de grupare, iar grupul unic +de aici poarta `TaxExemptionReason` completat. Daca pseudo-linia de discount ar fi avut `expltva` +gol si `id_jtva_coloana = 0` (cazul discountului de **document**, descris la 3.2), gruparea nu s-ar +fi putut contopi — ar fi iesit doua grupuri, ca in `scutit_bug` (3.3). Contopirea observata aici +dovedeste deci ca pseudo-linia purta un `id_jtva_coloana` real, adica era un discount **pe linie** +(`discount_evidentiat = 1`), nu de document — un al treilea indiciu, independent de cele doua de mai +sus (alocare pe cota minima la `...7446...`; doua alocari la `...1526...`), care coroboreaza aceeasi +concluzie: cele patru XML-uri de productie gasite sunt discounturi pe linie, nu de document. + +Fisierul arata insa **pozitiv forma corecta** pentru o factura scutita cu discount de document: cand +pseudo-linia poarta `id_jtva_coloana` corect, grupul iese unul singur si net — exact forma +recomandata la sectiunea 6, punctul 1. Deci recomandarea are un precedent real in productie, nu doar +o constructie sintetica de test. **Rezerva onesta**: fisierul e din februarie 2024, iar inferenta +presupune ca traseul de cod e neschimbat de atunci; ramane adevarat ca **nu exista niciun XML de +productie identificat pozitiv ca discount de DOCUMENT**. + +**Identitatea fisierului, verificata pe SHA256.** +`D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml` (5922 +octeti) are acelasi SHA256 (`3303AFE34BC9E3E84DA9B3527C435DD9B8CB0BC7881D7D2F660A250237E23C64`) cu +`4172939206.xml` din `...\TRIMISE\4172939206_3250292036.zip` — deci e exact ce a plecat la ANAF, si +a fost **acceptat**. Continutul din `...\ERORI\4172675286_3249814520.zip` e un mesaj de eroare de +446 octeti pentru o incarcare **anterioara**, cu alt index de incarcare, si listeaza exact doua +reguli: `BR-CO-15` si `BR-CL-04` (cod de moneda invalid). Deci respingerea aceea **nu** e a +fisierului cu `currencyID="RON"` discutat la sectiunea 2.3 — acela a trecut. + +### 4.2. Datele din baza de dev — putine, dar arata ca situatia e posibila + +`MARIUSM_AUTO@ROA_CENTRAL`, doar `SELECT`: +- facturi nesterse cu `VANZARI.DISCOUNT <> 0`: **3** (id_vanzare 165 / 575 / 576; + `discount_evidentiat` = 1 / 1 / 0). Toate trei au **o singura cota** pe linii (1.19, 1.24, 1.24). +- facturi nesterse cu **cote mixte** pe linii: **34**. + +Deci in dev nu exista intersectia "discount de document + cote mixte". **Asta nu dovedeste nimic** — +volumele de dev sunt mici, iar ambele ingrediente exista separat. In productie combinatia e banala +(orice comerciant cu alimente 9%/11% + nealimentare 21% care da un discount global). + +### 4.3. Ce am testat efectiv cu validatorul ANAF, offline + +Am folosit **validatorul local** `D:\ROA\COMUNROA\dist_efactura\DUKIntegrator.jar` (`-v FACT1`, +reguli `ro16931-ubl-1.0.8`), acelasi pe care il apeleaza `xmlefactura.prg:1166-1194`. **Nu s-a +trimis nimic catre ANAF; nu s-a apelat niciun serviciu web.** XML-urile de test sunt in +`%TEMP%\claude\...\scratchpad\` (base/scenA/scenB/scenC_*), construite plecand de la un XML real +valid. + +| test | ce contine | rezultat | +|---|---|---| +| `base` | XML-ul real `...7446...`, nemodificat (martor) | **ok** | +| `scenA` | cote mixte 21% + 11%, discount 200 lei atribuit **integral** cotei 21% (exact ce genereaza programul) | **ok** | +| `scenB` | 100 lei @21% + 5000 lei scutit, discount 510 lei la 21% -> `TaxableAmount = -410.00`, `TaxAmount = -86.10` | **ok** | +| `scenC_corect` | acelasi ca scenA, in EUR, cu `AllowanceCharge/Amount currencyID="EUR"` | **ok** | +| `scenC_bug` | idem, dar cu `currencyID="RON"` pe alocare (ce face codul la `:778`) | **ok** | + +**Concluzii, inclusiv cele care imi infirma ipotezele:** +1. Atribuirea intregului discount cotei maxime **trece validarea** — deci nu e o eroare pe care ANAF + sa o prinda. E o eroare **tacuta**. +2. Ipoteza mea ca o `TaxableAmount` **negativa** ar fi respinsa e **infirmata** de validatorul local. +3. Ipoteza ca neconcordanta de moneda (`currencyID="RON"` pe factura in EUR) ar fi respinsa e tot + **infirmata** de validatorul local. +4. **Rezerva**: validatorul instalat e din **ianuarie 2022** (`DUKIntegrator.jar`, 19.01.2022, + reguli 1.0.8). Regulile ANAF s-au inasprit de atunci. Punctele 2 si 3 raman "neinfirmate de + validatorul disponibil local", nu "garantat acceptate azi de ANAF". + +### 4.4. Scenariul reproductibil al defectului REAL (fiscal) + +**Factura**: client intern, in lei, `discount_evidentiat = 0`. +- Linia 1: marfa cota standard, 1 buc x 1000.00 lei, **21%** +- Linia 2: marfa cota redusa, 1 buc x 1000.00 lei, **11%** +- Discount de document: **10%** -> `VANZARI.DISCOUNT = 200.00` + +**Ce face programul**: +- `Max(proc_tvav)` = 1.21 -> pseudo-linia de discount primeste **21%** + (`oproceduri_facturare.prg:1387` / `ofacturare_comun.vc2:4495`) +- `valdiscounttva = Round(200 * 0.21, 2) = 42.00` (`ofacturare_comun.prg:1893`) + +**Ce iese in XML** (verificat ca valid — `scenA`): +``` +AllowanceCharge: Amount 200.00, TaxCategory S, Percent 21 +TaxSubtotal S/21: TaxableAmount 800.00 TaxAmount 168.00 +TaxSubtotal S/11: TaxableAmount 1000.00 TaxAmount 110.00 +TaxAmount total 278.00 ; LineExtension 2000.00 ; TaxExclusive 1800.00 ; TaxInclusive 2078.00 +``` + +**Ce ar fi trebuit** (discount repartizat proportional cu baza: 100 lei pe fiecare cota): +``` +AllowanceCharge #1: 100.00, S, 21 AllowanceCharge #2: 100.00, S, 11 +TaxSubtotal S/21: 900.00 / 189.00 +TaxSubtotal S/11: 900.00 / 99.00 +TaxAmount total 288.00 ; TaxInclusive 2088.00 +``` + +**Diferenta: 10.00 lei TVA colectat in minus**, adica 5% din TVA-ul facturii — si creste cu ecartul +dintre cote si cu marimea discountului. **Sensul e mereu acelasi**: cota maxima absoarbe tot +discountul, deci TVA-ul declarat e **prea mic**. Riscul e al emitentului (TVA colectat +subdeclarat), nu al ANAF-ului care respinge documentul. Aceeasi valoare gresita ajunge si in nota +contabila si in jurnalul de TVA, pentru ca `valdiscounttva` e acelasi camp. + +**Caz-limita, tot valid dupa validator dar clar absurd** (`scenB`): factura cu 100 lei la 21% si +5000 lei scutit, discount de document 10% (510 lei). Tot discountul intra pe cota 21%, unde baza e +100 lei -> `TaxSubtotal S/21` iese cu `TaxableAmount = -410.00` si `TaxAmount = -86.10`. Factura +declara **TVA negativ** pe o livrare normala. Nu e nevoie de cote diferite de zero pe ambele parti: +ajunge o factura in care liniile de la cota maxima sunt o mica parte din total. + +### 4.5. Gol de proiectare, distinct de defect + +**Explicatia ("reason") nu exista ca notiune in program.** In XML `AllowanceChargeReason` e literalul +`"Discount"` si `ReasonCode` e `"95"`, ambele hardcodate (`xmlefactura.prg:774-776`). Textul construit +in VFP — `"Discount 10.00 % Factura"` (`ofacturare_comun.prg:1370`) — **nu ajunge in XML**. Nu exista +nicio coloana pe `VANZARI` pentru motivul discountului si niciun control in formular. Deci raspunsul +la a doua jumatate a intrebarii lui Marius: **nu, discountul de document nu are azi explicatie — +are o constanta.** + +--- + +## 5. Recomandare pentru formularul unificat (#13) + +**Sa NU ceara utilizatorului cota. Sa sparga discountul automat, proportional cu baza fiecarei cote, +si sa expuna doar un camp optional de motiv.** Argumentul e ca discountul de document nu *are* o cota +proprie de ales: prin definitie e un procent aplicat bazei intregii facturi (`lnTotalBaza = +Sum(valdiminuatftva)` peste toate liniile, `ofacturare_comun.prg:1888`), deci natura lui de TVA e +determinata de liniile pe care le reduce, nu de o optiune. A pune operatorul sa aleaga o cota inseamna +a-i cere sa ia o decizie fiscala pe care datele o dau deja, si a deschide o a doua cale de a gresi — +azi `Max()` greseste consecvent, un camp liber ar greseste imprevizibil. Repartizarea proportionala e +si singura care satisface modelul EN16931, unde `TaxableAmount`-ul fiecarei categorii se calculeaza ca +*net de linii al categoriei minus alocarile de document ale aceleiasi categorii*. **Costul de +implementare e mic, dar atinge doua locuri, nu unul** (sectiunea 1.5): `prelucreaza_facturacrs` +(`ofacturare_comun.prg:1886-1897`) trebuie sa insereze cate un rand-sentinela **per cota** in loc de +unul singur, cu `tnDiscount` repartizat pe baza fiecarei cote (ultima cota preia diferenta de +rotunjire, ca suma sa fie exact `VANZARI.DISCOUNT`), **si** `recalculeaza_totaluri_vanzari` +(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16081-16092`) trebuie sa inlocuiasca `MAX(...)` cu suma +repartizarii, altfel `VANZARI.TOTAL_TVA` ramane pe regula veche si diverge de XML; **eFactura nu are +nevoie de nicio modificare structurala** — +`xmlefactura.prg:758-792` grupeaza deja `GROUP BY proc_tva` si emite cate un `AllowanceCharge` per +cota, iar `C_TVA_FACTURA` include deja pseudo-liniile in `TaxSubtotal`. Doua consecinte de decis +explicit cu Marius: **factura tiparita** va arata N randuri "Discount X % Factura" in loc de unul (pe +cote mixte), si **nota contabila** primeste TVA-ul discountului spart pe cote. Pentru "explicatie", +recomand un singur camp text optional pe document, folosit ca `AllowanceChargeReason` in locul +constantei `"Discount"` (`ReasonCode` ramane `95`) — util si pe hartie, si e singura bucata care chiar +cere UI nou. + +**Precizare, dupa sectiunea 3.2**: "eFactura nu are nevoie de modificare" e adevarat doar daca +fiecare pseudo-linie noua, per cota, primeste si `id_jtva_coloana` **al grupului pe care il reduce**, +nu doar `proc_tva` al lui. Cota singura nu ajunge — cheia de grupare din `xmlefactura.prg:246` are +cinci campuri, iar `expltva` se completeaza tot prin `id_jtva_coloana` (sectiunea 3.2). O +implementare care seteaza doar `proc_tva` pe randul-sentinela ar lasa grupul orfan exact acolo unde +e azi, pe facturile scutite / cu taxare inversa / intracomunitare. Fisierul `...2885...` de la +sectiunea 4.1 e dovada pozitiva ca, atunci cand `id_jtva_coloana` e setat corect, forma iese unul +singur si net — deci reparatia are deja un precedent real in productie, nu doar o constructie de +test. + +--- + +## Verificat direct vs. dedus + +**Verificat direct (citit in cod, cu `fisier:linie`)** +- randul-sentinela `ZZZZ` si campurile lui — `ofacturare_comun.prg:1886-1897` +- transformarea in pseudo-linia `"Discount % Factura"` — `ofacturare_comun.prg:1361-1392` +- `Max(proc_tvav)` pe toate cele patru cai de apel — `oproceduri_facturare.prg:1387`, + `ofacturare_comun.vc2:4495` si `:7294`, `ofacturare_stoc.prg:582` +- a doua implementare a aceleiasi reguli, in PL/SQL, si campurile pe care le scrie — + `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16024` (procedura), `:16081-16092` (`MAX(...)`), + `:16049-16062` (`TOTAL_TVA`/`TOTAL_CU_TVA` derivate din el), `:16214-16227` (`update vanzari`) +- euristica de detectie in eFactura — `xmlefactura.prg:230` +- generarea `AllowanceCharge` de document, cu `GROUP BY proc_tva` — `xmlefactura.prg:756-795` +- sursa lui `TaxCategory/ID` (`GetTipTaxa`, `xmlefactura.prg:1103-1143`) si a lui `Percent` +- includerea pseudo-liniei in `C_TVA_FACTURA` -> `TaxSubtotal` — `xmlefactura.prg:242-248, 822-851` +- inchiderea totalurilor `LegalMonetaryTotal` — `xmlefactura.prg:863-898` +- ca generatorul viu e `xmlefactura.prg`, nu `anaf_efactura` — `oexport.prg:1586-1594`, + `ofacturare.prg:2107-2126` +- ca ramura "pret cu TVA" (unde pseudo-linia iese cu `valftva = 0`) nu poate ajunge in eFactura — + `ofacturare.prg:1112, 1856-1861` + filtrul `tip_doc_394` din `xmlefactura.prg:276-279` +- semantica `LEFT JOIN` + `IIF`/`INLIST` peste `.NULL.` in cheia de grupare (sectiunea 3.1/3.2) — + `xmlefactura.prg:226-248` +- de ce grupul orfan nu poate fi completat prin `completeaza_explicatie_tva` — filtrul de scan vs. + filtrul de update (`ofacturare_comun.prg:2094` vs. `:2108, 2129`, sectiunea 3.2) +- sursa categoriei `Z` a grupului orfan, ramura cu ramura in `GetTipTaxa` — `xmlefactura.prg:1116-1140` + (sectiunea 3.2) +- incarcarea `cJTVAVanzariTemp` doar cu `id_jtva_coloana > 0`, motivul pentru care randul 0 nu se + gaseste — `updateserver.prg:578` (sectiunea 3.2) + +**Verificat prin rulare** +- 4 XML-uri reale de productie cu `AllowanceCharge` de document (sectiunea 4.1) +- 3 facturi cu discount in baza de dev, 34 cu cote mixte (`SELECT`, sectiunea 4.2) +- 5 validari DUKIntegrator offline pe cote mixte (sectiunea 4.3) — **au infirmat doua dintre + ipotezele initiale** +- semantica `IIF()` peste `.NULL.` masurata cu o sonda headless (`probe_null.prg`, sectiunea 3.1) — + intoarce ramura falsa, nu `.NULL.` +- 3 validari DUKIntegrator offline pe scenariul grupului orfan (`scutit_bug`, `scutit_corect`, + plus `control_stricat` ca martor negativ, sectiunea 3.3) — grupul orfan **nu** e respins +- identitatea SHA256 a XML-ului EUR de productie fata de fisierul chiar trimis la ANAF, si citirea + celor doua zip-uri (`TRIMISE`/`ERORI`) care arata ca respingerea gasita e a unei incarcari + anterioare, pentru alte reguli (sectiunea 4.1) + +**Dedus, NEverificat prin rulare** +- calculul numeric din scenariul 4.4 (aritmetica pe formulele citite, nu o factura rulata efectiv + prin program) +- riscul `agettipcota(1)` fara garda pe `_Tally` (`xmlefactura.prg:782-786`) — n-am reusit sa-l + provoc si nu-l afirm ca defect +- ce se intampla in nota contabila / jurnalul de TVA cu `valdiscounttva` — am dedus din faptul ca e + acelasi camp, n-am urmarit consumatorii +- scenariul "factura scutita + discount de document" **end-to-end prin program** (sectiunea 3.2): + gruparea separata e stabilita din cod + semantica `IIF`/NULL masurata, nu dintr-o factura reala + rulata prin program — in baza de dev nu exista intersectia (sectiunea 4.2) +- ordinea randurilor in `C_TVA_FACTURA` dupa `GROUP BY` (deci daca `agettipcota(1)` alege `E` sau + `Z` in cazul grupului orfan) — nu e garantata de VFP si nu a fost fixata experimental + +**Neacoperit — si stiut ca neacoperit** +- daca `VANZARI.TOTAL_TVA` (Oracle, sectiunea 1.5) si TVA-ul din XML sunt confruntate undeva si care + primeaza — am constatat doar ca ambele exista si folosesc aceeasi regula +- daca un discount de document **negativ** e posibil din UI (ar inversa `MAX`-ul din Oracle, 1.5) +- calea in **valuta** (`crsfacturafinalaval` / `prelucreaza_factura_valuta`, + `ofacturare_comun.prg:1540+`) — am presupus simetrie cu cea in lei pe baza campurilor `v*` + paralele de la `:1894-1895`, n-am parcurs-o linie cu linie +- daca `Max(proc_tvav)` e cota corecta si pentru **proforme** in vreun sens special +- comportamentul validatorului ANAF **curent** (cel local e din 2022, atat pentru cote mixte cat si + pentru grupul orfan) + +--- + +## Intrebari ramase pentru Marius + +1. **Repartizarea proportionala se aplica retroactiv la relistare?** O factura veche relistata sau + retrimisa in eFactura ar genera alt XML decat cel trimis initial. Se accepta, sau noul + comportament se leaga de o data / de un flag? +2. **Pe factura tiparita**: e in regula sa apara N randuri "Discount X % Factura" (unul pe cota), sau + vrei un singur rand vizibil si spargerea doar in XML / nota contabila? +3. **Rotunjirea**: pe cine cade diferenta de rotunjire cand discountul nu se imparte exact + (ex. 100 lei pe trei cote)? Propunerea mea: pe cota cu baza cea mai mare. +4. **Discountul care depaseste baza unei cote** (cazul din 4.4, factura majoritar scutita): dupa + repartizarea proportionala problema dispare de la sine — confirmi ca nu mai e nevoie de nicio + garda separata? +5. **Campul de motiv**: il vrei per document (un singur text), sau e suficient sa ramana constanta + `"Discount"` si sa nu adaugam UI? +6. **`discount_evidentiat`**: mai e folosit in productie? Schimba semnificativ ce se tipareste + (sectiunea 1.4) si e a doua sursa de pseudo-linii "Discount" in XML. +7. **Cele doua implementari ale regulii** (VFP + Oracle, sectiunea 1.5): se schimba amandoua in + aceeasi livrare, sau Oracle ramane pe regula veche o vreme? Daca raman desincronizate, + `VANZARI.TOTAL_TVA` si TVA-ul din eFactura vor diferi pe facturile cu cote mixte si discount. +8. **Discount de document negativ** (majorare): e posibil din UI? Daca da, `MAX`-ul din Oracle alege + cota cea mai **mica**, iar `Max(proc_tvav)` din VFP tot pe cea mai mare — adica cele doua + implementari ar diverge deja azi, inainte de orice modificare. + diff --git a/docs/cercetare/discount_document_cota_tva_b.md b/docs/cercetare/discount_document_cota_tva_b.md new file mode 100644 index 0000000..10dece3 --- /dev/null +++ b/docs/cercetare/discount_document_cota_tva_b.md @@ -0,0 +1,189 @@ +# Supliment: gruparea pseudo-liniei de discount in `C_TVA_FACTURA` + +Completare la `discount_document_cota_tva.md`, scrisa de al doilea agent pe aceeasi intrebare. +**Nu repeta** raportul principal — acopera exact punctul pe care acela il lasa deschis la finalul +sectiunii 3 („ipoteza gruparii separate — nu am atins-o deloc, o verifica alt agent"), plus doua +verificari prin rulare pe care raportul principal nu le are. + +Cercetare read-only: niciun fisier de cod atins, zero write-back, pe Oracle numai `SELECT`, niciun +apel catre ANAF (validarea s-a facut **offline**, cu DUKIntegrator din `D:\ROA\COMUNROA\dist_efactura`). + +## Verdict in 5 randuri + +Ipoteza pe care o ridicasem — ca pseudo-linia de discount de document, avand `id_jtva_coloana = 0`, +iese din `LEFT JOIN` cu `coloana_jv` NULL si formeaza propriul grup in `C_TVA_FACTURA`, cu +`TaxableAmount` negativ — este **infirmata pentru factura obisnuita** si **confirmata pentru factura +scutita / cu taxare inversa / intracomunitara**. In al doilea caz XML-ul chiar iese cu doua +`TaxSubtotal`-uri pe cota 0, unul cu baza negativa si categoria `Z`. **Dar validatorul nu-l +respinge** — l-am rulat. Deci: comportament gresit ca modelare, nu defect care blocheaza factura. + +--- + +## 1. Ce am masurat, nu dedus: `IIF()` peste NULL in VFP + +Toata ipoteza atarna de o singura intrebare: ce intoarce `Iif(Left(.Null.,2) = 'CE', 1, 0)`. Daca ar +intoarce `.NULL.`, cheia de grupare a pseudo-liniei ar diferi de a liniilor reale si gruparea s-ar +rupe pe **orice** factura. + +Am rulat o sonda headless (`vfp9.exe -A -T`) care reproduce exact `LEFT JOIN`-ul si `GROUP BY`-ul din +`xmlefactura.prg:226-248`, cu o pseudo-linie de discount pe `id_jtva_coloana = 0`: + +``` +1) IIF(.NULL.="CE",1,0) -> tip=N isnull=.F. +2) IIF(LEFT(.NULL.,2)="CE",1,0) -> tip=N isnull=.F. +3) IIF(INLIST(.NULL.,...),1,0) -> tip=N isnull=.F. + linie=ARTICOL A | coloana_jv nul=.F. | intracom=0 | scutit=0 + linie=ARTICOL B | coloana_jv nul=.F. | intracom=0 | scutit=0 + linie=Discount 10.00 % Factura | coloana_jv nul=.T. | intracom=0 | scutit=0 +4) grupuri in C_TVA_FACTURA = 1 + grup: cota=21 baza=1350 tva=283.50 (1500 - 150, corect) +``` + +**`IIF()` intoarce ramura falsa, nu `.NULL.`**, chiar daca conditia e nula. Deci pe o factura +interna obisnuita pseudo-linia primeste `intracomunitar=0, taxare_inversa=0, scutit=0` — exact ca +liniile reale — si **se contopeste in grupul cotei maxime**. Sectiunile 3 si 4 din raportul principal +raman valabile. + +Sonda: `...\scratchpad\probe_null.prg`. Nu depinde de baza de date si se poate reface oricand. + +## 2. Unde ipoteza se confirma totusi: facturile scutite / taxare inversa / intracomunitare + +Egalitatea de mai sus tine doar cat timp **si liniile reale** au `scutit=0` si `expltva` gol. Pe o +factura scutita nu e asa: + +| camp din cheia `Group By` (`xmlefactura.prg:246`) | liniile reale (scutite) | pseudo-linia de discount de document | +|---|---|---| +| `id_jtva_coloana` | id real (ex. 11) | **0** — `prelucreaza_facturacrs` nu-l seteaza niciodata pe randul `ZZZZ` (`ofacturare_comun.prg:1891-1896`) | +| `coloana_jv` (din `LEFT JOIN`) | `'WRSCDD'` | `.NULL.` — `cJTVAVanzariTemp` e incarcat cu `where afisat > 0 and id_jtva_coloana > 0` (`updateserver.prg:578`), deci **nu exista rand cu id 0** | +| `scutit` | **1** | **0** | +| `expltva` | `'Scutit cu drept de deducere'` | **gol** | + +Ultimul rand merita explicat, pentru ca e contraintuitiv: `completeaza_explicatie_tva` +(`ofacturare_comun.prg:2077-2137`) chiar **include** pseudo-linia in lista de id-uri cautate — scanul +de la `:2094` e `For proc_tva = 1 And Nvl(id_jtva_coloana,-1) <> -1`, iar pseudo-linia are +`proc_tva = 1` (cota 0) si `id_jtva_coloana = 0`, deci trece filtrul. Dar interogarea de la `:2108` +e `where id_jtva_coloana in (...)` pe `jtva_coloane`, unde id-ul 0 nu exista, asa ca `Replace expltva +... For NVL(id_jtva_coloana,0) = m.lnIdJtva` (`:2129`) nu o atinge niciodata. Ramane goala. + +Doua campuri diferite din cinci => **doua grupuri**. XML-ul iese cu: + +```xml + + ... Discount + 539.82 + Z0... + +0.00 + -539.82 + 0.00 + Z0... + 4410.00 + 0.00 + E0 + Scutit cu drept de deducere... + +``` + +Categoria `Z` a grupului orfan vine din `GetTipTaxa` ramura cu ramura (`xmlefactura.prg:1116-1140`): +`intracomunitar=0` sare peste `'K'`, `taxare_inversa=0` sare peste `'AE'`, `scutit=0` sare peste +`'E'`, si cade pe `Case m.lcTipFactura = 'F' And m.lnProcTva = 0` -> **`'Z'`** (`:1128-1129`). + +In plus, cautarea categoriei pentru `AllowanceCharge` (`xmlefactura.prg:782-786`) devine ambigua: +`Select tip From C_TVA_FACTURA Where proc_tva = 0 Into Array agettipcota` gaseste acum **doua** +randuri (`E` si `Z`), iar codul ia neconditionat `agettipcota(1)` — primul in ordinea cursorului, nu +cel corect prin vreun criteriu. + +**Nu am reprodus scenariul end-to-end prin program** (ar fi cerut o factura scutita cu discount de +document, si in baza de dev nu exista — vezi punctul 4). Il stabilesc din citirea codului plus +semantica `IIF`/NULL masurata la punctul 1. + +## 3. Validare offline: XML-ul gresit **nu** e respins + +Am construit XML-urile si le-am trecut prin DUKIntegrator +(`java -jar DUKIntegrator.jar -c -v FACT1 `, exact apelul din +`xmlefactura.prg:1192`), pornind de la o factura reala acceptata de ANAF ca schelet: + +| scenariu | fisier | rezultat | +|---|---|---| +| scutit + discount de document, **cu grupul orfan** (ce produce codul azi) | `scutit_bug.xml` | **ok** | +| scutit + discount, un singur `TaxSubtotal` net (varianta corecta) | `scutit_corect.xml` | **ok** | +| cote mixte 21%+11%, tot discountul pe 21% (ce produce codul azi) | `mixt_azi.xml` | **ok** | +| cote mixte 21%+11%, discount repartizat proportional | `mixt_proportional.xml` | **ok** | +| discount mai mare decat baza cotei maxime -> `TaxableAmount = -400.00` si `TaxAmount = -84.00` pe grupul 21% | `mixt_baza_negativa.xml` | **ok** | +| **control negativ**: `TaxExclusiveAmount` stricat intentionat | `control_stricat.xml` | **eroare BR-CO-13 + BR-CO-15** | + +Controlul negativ conteaza: fara el, cele cinci „ok" n-ar dovedi nimic. Validatorul chiar valideaza. + +**De ce trec**: regulile EN16931 pe categorii (BR-S-08, BR-E-08, BR-Z-08) cer ca baza declarata pe o +categorie sa fie egala cu *suma liniilor din acea categorie minus alocarile din aceeasi categorie*. +Grupul orfan respecta asta trivial (`0 - 539.82 = -539.82`), iar grupul `E` la fel (`4410.00 - 0`). +Aritmetica inchide; **modelarea fiscala e cea gresita**, si asta niciun schematron nu prinde. + +Avertisment onest: validatorul local e din 2022 (`ro16931-ubl-1.0.8`, jar-ul din ianuarie 2022). +Validatorul online curent al ANAF poate fi mai strict. **Nu l-am apelat** — interdictie explicita. + +## 4. De ce nu am putut proba pe date reale + +- In baza accesibila (`MARIUSM_AUTO@ROA_CENTRAL`) exista **3** facturi cu discount de document, toate + cu **o singura cota** si toate anterioare eFacturii (2008, 2014 x2), niciuna cu `efactura = 1`. + Separat exista 34 de facturi cu cote mixte, dar **niciuna** cu discount de document. Deci + intersectia care ne intereseaza e goala. + **Asta nu inseamna ca nu apare in productie** — baza de dev are 712 facturi in total. +- Cele 4 XML-uri de productie cu `AllowanceCharge` de document pe care le-am examinat sunt, dupa + toate semnele, **discounturi pe linie** cu `discount_evidentiat = 1`, nu discounturi de document: + cel de la `2024_07\...CIA_TRADE_POSSIBLE` are cote 9% si 19%, iar alocarea unica e pe **9%** — + adica pe cota **minima**, ceea ce regula `Max(proc_tvav)` nu poate produce; iar + cel de la `2024_01\...BEST_POWER_DANCE` are **doua** alocari (9% si 19%), ceea ce discountul de + document, fiind o singura pseudo-linie, nu poate produce nici el. + Nu am gasit niciun XML de productie care sa fie sigur discount de **document**. +- Firmele din `D:\ROA\Efactura` (VENDING_MASTER, CLEVER_MOTORS, ...) nu au scheme in baza accesibila + (`select ... from all_users` — zero potriviri), deci nu am putut confrunta XML-ul cu antetul lui. + +## 5. Un lucru dovedit pe un fisier real, nu dedus + +Pe `D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml`: + +``` +BT-5 DocumentCurrencyCode = EUR + ... 539.82 +539.82 +``` + +Aceeasi suma apare o data ca `RON` si o data ca `EUR` — `currencyID` e hardcodat `"RON"` la +`xmlefactura.prg:778`, in timp ce restul documentului foloseste `mmoneda`. Doua precizari care +schimba interpretarea: + +- fisierul de pe disc este **identic pe SHA256** cu cel din + `...\TRIMISE\4172939206_3250292036.zip`, deci e exact ce a plecat la ANAF; +- si a fost **acceptat**. Respingerea din `...\ERORI\4172675286_3249814520.zip` e a unei incarcari + *anterioare* si e pentru alte doua reguli (`BR-CO-15` si `BR-CL-04` — cod de moneda invalid, + probabil `EURO` in loc de `EUR`, ceea ce explica linia de corectie `xmlefactura.prg:267`). + +Deci `currencyID="RON"` pe factura in valuta e o **neconformitate reala si activa in cod azi**, dar +nu am dovada ca ar cauza o respingere. + +## 6. Ce inseamna pentru #13 + +Repartizarea proportionala pe cote — recomandarea din raportul principal — **rezolva si problema de +aici**, fara nicio garda in plus: daca discountul se sparge pe cotele care exista efectiv pe factura, +fiecare bucata mosteneste `id_jtva_coloana` si `expltva` ale grupului ei, grupul orfan dispare, iar +`agettipcota(1)` nu mai poate fi ambiguu. Subscriu, cu doua completari: + +1. **Pseudo-liniile de discount trebuie sa primeasca `id_jtva_coloana` al grupului pe care il reduc**, + nu doar cota. Cota singura nu ajunge — cheia de grupare are cinci campuri, iar `expltva` se + completeaza tot prin `id_jtva_coloana`. O implementare care seteaza doar `proc_tva` ar lasa + grupul orfan exact acolo unde e azi. +2. **Regula traieste in doua locuri** — VFP (`Calculate Max(proc_tvav)`) si PL/SQL + (`MAX(ROUND(discount * (proc_tvav - 1), ...))` in `recalculeaza_totaluri_vanzari`). Daca se + schimba doar una, `VANZARI.TOTAL_TVA` si TVA-ul din XML vor diferi pe facturile cu cote mixte. + +## 7. Ce ramane deschis + +- Scenariul de la punctul 2 **nu e reprodus prin program**, ci dedus din cod + semantica `IIF` + masurata. O confirmare ar cere o factura scutita cu discount de document, introdusa manual. +- Ordinea randurilor in `C_TVA_FACTURA` dupa `GROUP BY` (deci ce alege `agettipcota(1)` intre `E` si + `Z`) nu e garantata de VFP si nu am fixat-o experimental. Am presupus `scutit=0` inaintea lui + `scutit=1`; daca e invers, categoria alocarii iese `E` in loc de `Z` — tot gresita, dar altfel. +- Validatorul ANAF **online** curent nu a fost testat (interdictie). Tot ce spun despre „nu e + respins" se refera la DUKIntegrator 2022 de pe disc. +- Nu am verificat calea in **valuta** (`prelucreaza_factura_valuta`) linie cu linie. diff --git a/docs/cercetare/discount_in_rapoarte_si_efactura.md b/docs/cercetare/discount_in_rapoarte_si_efactura.md new file mode 100644 index 0000000..dd63570 --- /dev/null +++ b/docs/cercetare/discount_in_rapoarte_si_efactura.md @@ -0,0 +1,216 @@ +# Discount pe linie (DISCOUNT_UNITAR) in afara formularului de facturare — rapoarte .frx, eFactura, SAF-T + +Cercetare read-only pentru S4c (plan_13). Nu s-a modificat niciun fisier, nu s-a rulat git_sync. + +## Verdict (5-10 randuri) + +Niciun raport tiparit de factura (`factura.fr2`, `facturatip*.fr2`, `factura_val*.fr2`, +`invoice*.fr2`, `proforma*.fr2`) nu afiseaza discountul pe linie ca coloana separata — nici ca +valoare, nici ca procent. Rapoartele tiparesc `pretftva` (pret unitar) si `valftva` (valoare linie) +care sunt **deja nete de discount** (calculate ca `pretftva-discountftva` / `Sum(valdiminuatftva)` +in cursorul intermediar `crsfacttemp`, construit de `prelucreaza_factura`/`creeaza_crsfacttemp` din +`ofacturare_comun.prg`). eFactura (`xmlefactura.prg`) foloseste **exact acelasi cursor** — +comentariul din cod chiar spune asta explicit — deci `LineExtensionAmount`/`PriceAmount` trimise la +ANAF sunt aceleasi valori nete, cu optiunea (dezactivata implicit, din flag-uri globale) de a +adauga si un `cac:AllowanceCharge` informativ pe linie. SAF-T (D406): **nu exista niciun generator +D406/SAF-T in ROAFACTURARE** — exista doar tabele de nomenclator cu prefix `saft_` (coduri TVA/plata) +folosite pe partea de achizitii, fara legatura cu `DISCOUNT_UNITAR`. + +**Raspuns la intrebarea care conteaza**: daca discountul pe linie devine editabil direct in grid, +**nu se strica nimic in codul rapoartelor/eFacturii** — ele citesc oricum campurile finale +(`valdiminuatftva`/`discountftva`/`discount_unitar`) din cursor, indiferent cum au fost populate +(dialog separat vs. editare in grid). **Riscul real e in alta parte**: exista deja azi o cale prin +care discountul e editabil direct in grid (`vdiscountftva`, pe factura in valuta) care **nu +declanseaza recalcularea** lui `valdiminuatftva`/`valdiminuatctva` — vezi `docs\cercetare\discount_verificare2.md` +punctul 3, ultimul paragraf. Daca S4c extinde editarea in grid la toate coloanele de discount fara +sa cablaje si recalculul, rapoartele si eFactura vor tipari/trimite **valori vechi** (stale), pentru +ca ambele citesc `valdiminuatftva`, nu `discount_unitar` direct. + +## 1. Rapoartele .frx/.fr2 tiparite + +**Cautare**: `Grep 'DISCOUNT'` si apoi `Grep 'disc'` (case-insensitive) in toate `.fr2` din +`COMUN\Rapoarte` cu glob `*factur*.fr2`, `*proforma*.fr2`, `*invoice*.fr2` — **zero potriviri** in +toate cele (factura.fr2, facturatip.fr2, facturatip_a5.fr2, facturatip_cuchit.fr2, factura_a5.fr2, +factura_chit.fr2, factura_val.fr2, factura_val_a5.fr2, invoice.fr2, invoice_a5.fr2, proforma.fr2, +proforma_orig.fr2, proforma_val.fr2, proforma_val_orig.fr2). Singurele 2 fisiere `.fr2` din toata +`COMUN\Rapoarte` care contin "DISCOUNT" sunt `rap_nir_materiale.fr2` si `rap_nir_marfuri.fr2` — +rapoarte de NIR (receptie), nu facturi emise. + +**Ce se tipareste per linie** (confirmat in `factura.fr2`): +``` +1002: +1014: +1026: +1062: -- procent TVA, nu discount +``` +Deci: cantitate, pret unitar, valoare linie, procent TVA. Nici discount valoric, nici procent +discount. + +**De unde vin `pretftva`/`valftva` — si de ce sunt deja nete**: cursorul de tiparire +(`crsfacttemp`) e construit de `Procedure prelucreaza_factura` (`COMUN\programe\ofacturare_comun.prg:1055-1059`), +apelata din `COMUN\programe\ofacturare.prg:1887`: +``` +prelucreaza_factura([crsfactura], [crsfacturaset], [crsfacturafinala], poDate.discount_evidentiat, poDate.in_valuta, lnPretListAviz, poDate.nListareDetaliata) +``` +In interior, `Do Case` pe `tnDiscountEvidentiat`/`lnPretListAviz` (`ofacturare_comun.prg:1156-1248`). +Cazul comun, `tnDiscountEvidentiat = 0` (checkbox-ul "Se pune in evidenta discount-ul..." NEBIFAT — +comportamentul implicit): +``` +1182: Sum(cantitate) As cantitate,pretftva-discountftva As pretftva,; +... +1186: Sum(valdiminuatftva) As valftva,Sum(valdiminuattva) As valtva,; +``` +adica pretul tiparit = pret brut minus discountul pe unitate, iar valoarea tiparita = suma +`valdiminuatftva` (deja calculata la adaugarea articolului ca `pretftva - discount_unitar`, cf. +`ofacturare.vc2:13126`, deja documentat in `discount_pe_articol.md` sectiunea 3). Simetric pentru +varianta cu TVA la `:1226-1230`. + +**Cazul `tnDiscountEvidentiat = 1`** (checkbox bifat — discountul "se pune in evidenta"): +``` +1165: pretftva,Nvl(codbare,... -- pretftva RAMANE brut, nu se scade discountftva +1166-1167: Sum(valftva) As valftvai,... Sum(valftva) As valftva,Sum(valtva) As valtva, +1168: discountftva,Sum(valdiscountftva) As valdiscountftva,Sum(valdiscounttva) As valdiscounttva, +``` +Aici `pretftva`/`valftva` tiparite raman **brute** (neta de discount), iar `discountftva`/ +`valdiscountftva`/`valdiscounttva` sunt calculate in cursor **dar nu sunt tiparite de niciun .frx** +(confirmat, zero hit pe "disc" in .fr2). Practic, cand discountul e "pus in evidenta", pretul si +valoarea linie tiparite pe factura NU reflecta discountul per linie — discountul apare doar ca linie +separata de document (randul-sentinela `denumire = Replicate('Z',20)`, populat din +`Thisform.nbazafdiscount`/`nbazafdiscountval` la `ofacturare.vc2:14534,14591,18532,18602` si +`ofacturare_comun.prg:1891`) — asta e insa discountul **de document** (`VANZARI.DISCOUNT`), nu +`DISCOUNT_UNITAR` pe linie. Nu am gasit nicio dovada ca acest mod (`discount_evidentiat=1`) ar fi +folosit uzual — e o optiune existenta, documentata deja in `discount_pe_articol.md` punctul 3 ca +schimband "doar mecanismul aritmetic", dar aici se vede consecinta suplimentara: **si ce se +tipareste**. + +**Fara impartiri la pret zero pe randul de discount**: unde raportul calculeaza procent +(`proc_disc`), sursa e protejata explicit: +``` +1184-1185 (ofacturare_comun.prg): Iif(cu_tva=0,Iif(pretftva<>0,Round(discountftva/pretftva*100,2),0),; + Iif(pretctva<>0,Round(discountctva/pretctva*100,2),0)) +``` +deci nu exista risc de impartire la zero — dar, ca observat mai sus, `proc_disc` nu e tiparit +nicaieri in .frx-urile de factura verificate (e calculat doar pentru consum intern/eFactura). + +## 2. eFactura (ANAF/UBL) — `xmlefactura.prg` + +**Concluzie**: eFactura foloseste **acelasi cursor de linii ca cel de la tiparire**, explicit +documentat in cod: +``` +oexport.prg:1590: * tcCursorLiniiFactura = numele cursorului cu liniile facturii, asa cum arata la listare +``` +Lantul: `ofacturare.prg:2126` -> `goExport.export2xml_efactura(poDate, m.lcCursoreFactura, ...)` cu +`lcCursoreFactura` = `lcCursorFacturaTemp` (= `'crsFacturaFinala'`, `ofacturare.prg:1892`, sau +`'crsfacturafinalaval'` pentru valuta) -> `xmlefactura.prg:231`: +``` +Select a.*, ... From (m.tcCursorLiniiFactura) a Left Join cJTVAVanzariTemp b ... Into Cursor C_IES_FORM Readwrite +``` +Deci `C_IES_FORM` (sursa liniilor XML) e construit direct din cursorul de tiparire — aceleasi +`pretftva`/`pretftvai`/`valftva`/`valftvai`/`proc_disc` descrise la punctul 1, cu aceeasi dependenta +de `poDate.discount_evidentiat`. + +**Ce se trimite pe linie**: +- `cbc:LineExtensionAmount` (BT-131) = `C_IES_FORM.valftva` — `xmlefactura.prg:935-937`. +- `cbc:PriceAmount` = `C_IES_FORM.pretftva` — `xmlefactura.prg:1043-1046`. +- Optional (doar daca flag global `gnEFACTURA_XML_DISC_PLISTA_LINIE = 1` SI `pretftvai > pretftva`): + un `cac:AllowanceCharge` la nivel de linie cu `Amount = valftvai-valftva`, + `BaseAmount = valftvai`, `MultiplierFactorNumeric = proc_disc` — `xmlefactura.prg:952-971`. +- Optional (flag separat `gnEFACTURA_XML_DISC_PLISTA_ART = 1`): un `cac:AllowanceCharge` la nivel de + `cac:Price` cu discountul unitar (`ABS(pretftvai-pretftva)`) — `xmlefactura.prg:1054-1067`. + +Ambele flag-uri sunt verificate cu `TYPE(...) = 'N' AND ... = 1` — daca variabila globala nu exista +sau e 0 (implicit), **nu se trimite niciun `AllowanceCharge` pe linie**; discountul e vizibil pentru +ANAF doar implicit, prin faptul ca `PriceAmount * InvoicedQuantity = LineExtensionAmount` (adica +pretul e deja net). Nu am gasit unde sunt setate global aceste doua variabile (`gnEFACTURA_XML_DISC_PLISTA_LINIE`/`_ART`) — +posibil optiuni de firma necautate in acest fisier; nu afecteaza concluzia principala. + +**Discount de document, separat**: exista o eticheta euristica in `C_IES_FORM` +(`Iif('DISCOUNT' $ Upper(a.denumire) And a.pretftva < 0, 1, 0) As discount`, `xmlefactura.prg:230`) +care detecteaza randuri-pseudo-articol de discount/taxa (denumire contine "DISCOUNT", pret negativ) +si le exclude din liniile normale (`Scan For discount = 0`, `:901`), agregandu-le separat intr-un +`cac:AllowanceCharge` la nivel de document (`:756-793`, motiv "Discount", cod 95). Acesta e +discountul de document, nu `DISCOUNT_UNITAR` pe linie — mecanism diferit de randul-sentinela +`Replicate('Z',20)` folosit la tiparire (punctul 1). + +**Nicio validare care ar respinge discount > pret**: n-am gasit nicio verificare explicita in +`xmlefactura.prg` care sa blocheze o linie cu `discount_unitar` mai mare ca pretul (ar rezulta +`pretftva` negativ pe linie individuala; codul are doar o corectie generala pentru +`pretftva < 0` la nivel de linie completa, `xmlefactura.prg:236-239`, care inverseaza semnul +cantitate/pret — nu specifica pentru discount). + +## 3. SAF-T (D406) + +**Concluzie: nu exista implementare SAF-T/D406 in ROAFACTURARE.** Cautat explicit: +- `Grep 'SAF-T|SAFT|D406|declaratie406|GenereazaSAFT|GenereazaD406'` in tot `COMUN\programe` si + `COMUN` — nicio potrivire pe un generator de fisier D406/SAF-T XML. +- `Glob '**/*saft*'` pe tot proiectul si pe `COMUN` — zero fisiere cu "saft" in nume. +- Singurele aparitii reale (nu fals-pozitive de tip "safe") sunt: + - `oinit_optiuni.prg:523-524`: `gl406 = (TYPE('gnD406') = 'N' and m.gnD406 = 1)` — un flag "firma + are activata SAFT 406", comentat "Firma are activata SAFT 406". + - `oproceduri_comune.prg:889-896,6083,6151`, `ointroduceri.prg:1597`: `saft_taxtable` si + `saft_mecanisme_plati` — tabele de nomenclator (coduri de TVA/mecanism de plata compatibile + SAF-T) folosite pe **partea de achizitii** (limitare deducere TVA la introducerea facturilor de + achizitie), nu pe vanzari/facturare. + - `oproceduri_comune.prg:6066-6067,6137-6138,6195`: comentarii care mentioneaza "Taxa SAFT tip" + ca denumire pentru codurile de TVA, tot pe achizitii. +- Niciuna din aceste aparitii nu are legatura cu `DISCOUNT`/`DISCOUNT_UNITAR` — cautarea `DISCOUNT` + in fisierele care contin "saft" (`oproceduri_comune.prg`, `ointroduceri.prg`, `pmenu.prg`, + `oinit_optiuni.prg`) nu a gasit nicio intersectie intre cele doua seturi de rezultate. + +**Interpretare**: `gl406` pare sa activeze doar validari/coduri suplimentare pentru conformitate +SAF-T pe partea de achizitii (poate pentru ca alt produs din suita, ROACONT, genereaza efectiv +D406 din datele contabile) — ROAFACTURARE nu produce singur un fisier D406. + +## 4. Alte consumatori care ar presupune discount uniform pe document + +Nu am gasit vreun raport/export care sa presupuna un discount unic pe tot documentul in sensul de +"aceeasi valoare/procent pe toate liniile" — mecanismul de discount de document +(`VANZARI.DISCOUNT`, randul `Replicate('Z',20)`) e tratat ca **valoare agregata separata**, adaugata +ca linie proprie (in tiparire) sau ca `AllowanceCharge` de document (in eFactura), nu redistribuita +implicit pe liniile existente — cu o exceptie: `xmlefactura.prg` are logica de **distribuire** +explicita a discountului/taxelor de document pe articole cand bifa `chkDistribuieDiscount`/ +`llDistribuieDiscountTaxe` e activa (`xmlefactura.prg:12189-12320`, `:12534-12790`) — asta insa +opereaza pe discountul de document, nu presupune ca discountul de linie (`DISCOUNT_UNITAR`) e +uniform; dimpotriva, distribuie proportional cu valoarea fiecarei linii, deci suporta discount +diferit per linie din start. + +## Ce ar strica S4c, concret + +**Nu se strica codul rapoartelor sau al eFacturii** — ambele citesc valori finale din cursor +(`valdiminuatftva`, `discountftva`, `pretftva` deja net), indiferent de sursa UI a discountului. + +**Se poate strica invariantul pe care se bazeaza**, daca editarea in grid nu recalculeaza: +- **Dovada ca gaura exista deja azi**: pe factura in valuta, coloana `vdiscountftva` e editabila + direct in grid (`ofacturare.vc2:12340-12345`, fara `ReadOnly`) dar **fara niciun handler + `Valid`/`InteractiveChange`** care sa recalculeze `vvaldiminuatftva`/totalurile — confirmat cautat + explicit in `docs\cercetare\discount_verificare2.md` punctul 3, ultimul paragraf ("editarea directa + in grid NU declanseaza recalcularea automata a `vvaldiminuatftva`/totaluri, doar modifica valoarea + bruta in `crsfactura`"). +- **De ce conteaza pentru rapoarte/eFactura**: `valdiminuatftva`/`valdiminuatctva` (nu + `discount_unitar` brut) sunt campurile citite de `prelucreaza_factura`/`creeaza_crsfacttemp` + pentru `valftva` tiparit si trimis la ANAF (punctele 1 si 2 de mai sus). Daca operatorul modifica + discountul direct in grid si acel camp nu se recalculeaza, factura tiparita si XML-ul ANAF vor + arata **valoarea veche** (dinainte de editare) — o discrepanta reala intre ce vede operatorul in + grid si ce se tipareste/trimite. +- **Concluzie pentru plan**: daca S4c extinde editarea de discount la toate coloanele din grid + (inclusiv `discountctva`, azi read-only pe lei), trebuie cablat un recalcul echivalent cu + `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`) pe evenimentul de editare + din grid — altfel riscul de mai sus (deja prezent azi doar pe valuta) se extinde la toate + facturile in lei. + +## Ramas de verificat + +- Valorile implicite ale flag-urilor globale `gnEFACTURA_XML_DISC_PLISTA_LINIE` si + `gnEFACTURA_XML_DISC_PLISTA_ART` (unde sunt setate ca optiune de firma) — nu am cautat sursa lor, + doar am confirmat ca cu `TYPE(...) <> 'N'` (nedefinite) comportamentul e "fara AllowanceCharge pe + linie". +- Nu am verificat pe date reale (Oracle/XML generat) un caz cu `discount_evidentiat=1` si + `DISCOUNT_UNITAR` populat simultan, ca sa confirm empiric (nu doar din citirea codului) ca factura + tiparita arata pretul brut. +- Nu am urmarit `crsfacturafinalaval` (varianta valuta a cursorului final) linie cu linie — am + presupus ca urmeaza acelasi patron ca `crsfacturafinala`/`crsfacttemp` (cursorul in lei), pe baza + simetriei campurilor `vpretftva`/`vvalftva`/`vdiscountftva` gasite deja documentate in + `discount_verificare2.md` si `discount_pe_articol.md`; n-am reverificat separat sursa SQL pentru + varianta valuta. +- Nu am identificat un al doilea produs (ROACONT?) care sa genereze efectiv D406 din datele scrise + de ROAFACTURARE — presupunerea din sectiunea 3 e speculativa, nu confirmata cu cod. diff --git a/docs/cercetare/factura_retur_document.md b/docs/cercetare/factura_retur_document.md new file mode 100644 index 0000000..1d5003b --- /dev/null +++ b/docs/cercetare/factura_retur_document.md @@ -0,0 +1,185 @@ +# Cercetare: factura de retur ca document de sine statator (tip 8/9) + aviz retur (24) + +Corectie fata de `retur_si_lista_preturi.md`: acea cercetare a documentat corect `But_retur` +(retur de articole *in interiorul* unei facturi normale, tip 1/5/7/10 — selectie **per articol**). +Aici e documentat mecanismul **separat**: factura de retur ca document propriu (tip 8 = retur lei, +tip 9 = retur valuta), unde utilizatorul alege **facturile sursa la nivel de document**, iar linia +de articole se populeaza integral din acele facturi. + +## 1. Punctul de intrare si cursorul Oracle + +Tile-ul de pe ecranul principal de facturare, `Page2.Cw1` (`COMUN\clase\ofundal_facturare.vc2:882-884`): +``` +PROCEDURE Page2.Cw1.do_actiune + DO facturare_lista_de_preturi IN oproceduri_facturare.prg +ENDPROC +``` +`facturare_lista_de_preturi` (`COMUN\programe\oproceduri_facturare.prg:114-116`) = `Do politica.mpr`, +care ruleaza meniul shortcut generat din `Meniuri\politica.mn2:14-15,45-46`: +``` +DEFINE BAR 2 OF Shortcut PROMPT "\ cate un camp cu acelasi nume in cursorul VFP `crsarticole` (maparea VFP +exacta camp-cu-camp nu a fost trasata pana in `crsfactura`; nu era necesara pentru raspuns). + +## 4. Ce se poate face manual (`frm_facturare_articole`, `COMUN\clase\ofacturare.vc2:10968`) + +- **Stergere linie**: `do_sterge` (`:14608-14693`) nu e restrictionat pe tip; pentru retur + (`Case Inlist(poDate.tip,8,9,24)`, `:14658-14659`) cantitatea stearsa se reintoarce in cursorul + sursa (`Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c`) — deci + **da, se pot sterge linii aduse**, iar cantitatea redevine disponibila pentru re-adaugare. +- **Retur partial (modificare cantitate)**: coloana de cantitate din grila sursa isi schimba titlul + in "Cant. max. de returnat" pentru tip 8/9 (`:15236-15239`); validarea in `do_verifica_articol` + (`:14743-14754`, `llRetur = Inlist(poDate.tip,8,9,24)`) respinge doar cazul in care cantitatea + ceruta ar depasi maximul returnabil — utilizatorul poate introduce orice cantitate <= maxim, + deci **da, retur partial e posibil**. `do_modifica` (`:13746-13914`) trateaza explicit + `Inlist(poDate.tip,8,9,24)` la `:13788,13895` fara blocaj suplimentar. +- **Adaugare linie care NU e in facturile sursa**: `do_adauga_articol` (`:12813-13086`) preia + articolul mereu din cursorul sursa al gridului (`lcCursor = [crsarticole]`, apoi + `Select (lcCursor) / Scatter Name poArticol`, `:12843-12851`) — pentru tip 8/9 acest `crsarticole` + e chiar rezultatul `cursor_retur` de la punctul 3, deci contine **doar** liniile facturilor alese. + Nu exista pe acest formular o cale de a alege un articol din lista de preturi completa cand + `poDate.tip` e 8/9 (spre deosebire de contract/comanda unde `crsarticole` ramane lista de preturi + libera) — **nu se poate adauga o linie in afara facturilor sursa**, prin design-ul continutului + cursorului, nu printr-o validare explicita de interzicere. + +## 5. Aviz de retur (tip 24) + +Acelasi mecanism de baza: `Inlist(tnTip,8,9,24)` la `ofacturare.prg:306-307` (acelasi +`pack_facturare.cursor_retur`) si aceleasi ramuri `Inlist(poDate.tip,8,9,24)` in +`frm_facturare_articole` (`do_adauga_articol:12863`, `do_alege_stoc:13230`, `do_modifica:13788,13895`, +`do_verifica_articol:14743`, `do_sterge:14658`). Diferenta: la antet, `nIdTipDoc` = 6 (AVIZ, ramura +`Otherwise` la `ofacturare.prg:194-195`, pentru ca 24 nu e `<21` si nu e in `Inlist(45,48,49,51,52)`), +iar formularul de date e `frm_date_aviz` (`ofacturare.prg:225`, ramura `Otherwise`), nu +`frm_date_factura`. Punctul de intrare pentru tip 24: `emitere_aviz_clienti` cu `tnTip=7` +(`COMUN\programe\oproceduri_facturare.prg:221-222`, `factureaza(24) && Retur aviz`), apelat din +tile-ul `Page3.Cw1` -> `aviz_clienti.mpr` (neverificat detaliat, nu s-a insistat conform cerintei). + +## 6. Relatia cu `But_retur` + +Mecanisme **complet separate**, confirmate pe cod: +- **Vizibilitate**: `but_retur` e vizibil doar pentru tip 1/5/7/10 (`ofacturare.vc2:15127`, + `This.but_retur.Visible = .T.` in ramura `Case Inlist(poDate.tip,1,5,7,10)`) — pe un document + tip 8/9 acest buton nu exista deloc in UI (ramura lui la `Init` e alta, `:15236`). +- **Formular de alegere a facturii sursa**: `But_retur` foloseste `caut_facturi_multiple_client_articol` + (`oproceduri_facturare.prg:2124-2159`, filtrat suplimentar pe `b.id_articol = ?pnIdArticol` — + cautare per-articol, cu titlu simplu "Alegeti factura"), tip 8/9 document foloseste + `caut_facturi_multiple_client` (`:2091-2121`, fara filtru pe articol, multi-select, titlu + "Alegeti facturile ..."). Doua functii diferite, in acelasi fisier, cu semnaturi aproape identice + dar interogari SQL diferite. +- **Procedura Oracle de populare a liniilor**: documentul tip 8/9 foloseste + `pack_facturare.cursor_retur` la nivel de document intreg (populeaza `crsarticole` cu toate + liniile facturilor alese, punctul 3 de mai sus). `But_retur` nu apeleaza `cursor_retur`/ + `cursor_retur_document` deloc — foloseste `pack_facturare.cursor_gestiuni_articol_retur` + (`ofacturare.vc2:13230`, per articol individual, in `do_alege_stoc`) pentru a alege + gestiunea/seria/lotul de returnat pentru articolul deja selectat din lista de preturi a facturii + normale curente. +- **Punct comun**: niciunul direct. Singurul element comun e conventia de semn a cantitatii + (`Iif(Inlist(poDate.tip,8,9,24),(-1),1)`, folosita si in `do_alege_stoc` al `But_retur`, + `:13279-13309`, si pe formularul `frm_facturare_articole2`, `:17574-17585`) si textul UI + ("cant. max. de returnat"). Concluzie: sunt doua fluxuri de cod independente care ating aceeasi + clasa de formular (`frm_facturare_articole`) dar prin metode si proceduri Oracle diferite. diff --git a/docs/cercetare/garda_aviz_facturat.md b/docs/cercetare/garda_aviz_facturat.md new file mode 100644 index 0000000..569b65f --- /dev/null +++ b/docs/cercetare/garda_aviz_facturat.md @@ -0,0 +1,235 @@ +# Garda "aviz deja facturat" — verificare pe cod + +Verificare pe cod, fara nicio modificare de fisier. Sursele citate: + +- export pachet: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` + (mai jos: `EXPORT:`); +- corpul **desfasurat pe DB** (`MARIUSM_AUTO@ROA_CENTRAL`, `all_source`), citit ca sa se confirme ca + ce e in export e si ce ruleaza (mai jos: `DB PACK_FACTURARE:`). **Numerotarea difera**: + `sterge_factura` incepe la `EXPORT:5432`, dar la `DB PACK_FACTURARE:4192`. Nu exista offset + constant; textul insa e identic cuvant cu cuvant pe zona gardelor. + +--- + +## 1. Blocheaza garda de azi si un AVIZ care are deja factura generata din el? + +**DA.** Nu e nevoie de nicio garda noua pentru cazul asta — este exact ce testeaza a doua garda din +`sterge_factura`, prin `TIP IN (1, 2)`. + +`EXPORT:5464-5476` = `DB PACK_FACTURARE:4224-4236`: + +``` + -- ptr. un aviz normal: + -- verific daca exista facturi sau avize de retur pe avizul resp. + SELECT COUNT(*) + INTO V_NR_AVIZE_FACT + FROM VANZARI_CORESP + WHERE STERS = 0 + AND ID_VANZARE_AVIZ = V_ID_VANZARE + AND TIP IN (1, 2); + + IF V_NR_AVIZE_FACT > 0 THEN + RAISE_APPLICATION_ERROR(-20000, + 'Pe acest aviz s-au emis facturi / aviz de retur. Trebuie sa stergeti mai intai facturile / avizul de retur!'); + END IF; +``` + +Cheia e semantica lui `TIP`, citita din **singurul scriitor** al tabelei, ramura cu ramura +(`finalizeaza_factura`, `EXPORT:14818-14839`): + +| `VANZARI_CORESP.TIP` | scris cand | `ID_VANZARE_FACT` | `ID_VANZARE_AVIZ` | e retur? | +|---|---|---|---|---| +| **1** | `ntip = 4` — facturare din aviz (`EXPORT:14823-14826`) | factura | avizul-sursa | **NU** | +| 2 | `ntip = 24` — aviz de retur (`EXPORT:14828-14830`) | avizul de retur | avizul original | DA | +| 3 | `ntip in (8, 9)` — factura de retur (`EXPORT:14834-14836`) | factura de retur | factura originala | DA | + +Deci `TIP = 1` este **corespondenta aviz -> factura normala**, nu retur. Garda de la `EXPORT:5473` +prinde ambele cazuri; textul mesajului chiar o spune ("s-au emis **facturi** / aviz de retur"). +Formularea din brief ("garda de azi blocheaza documentul care are facturi/avize **de retur**") vine +dintr-o citire a comentariului `-- verific daca exista facturi sau avize de retur` in care "de retur" +se distribuie si peste "facturi"; codul zice altceva. + +Deci: la ora asta, **exact regula ceruta de decizia 50 este deja implementata pentru avize** +(document cu urmasi = blocat; capatul lantului = liber). + +### Ce NU citeste garda + +- **`VANZARI.FACTURAT` nu e citit de nicio garda.** Pe calea de stergere e doar **scris** + (`EXPORT:5504-5510` pentru `V_TIP = 24`, `EXPORT:5527-5533` pentru `V_TIP = 4` — resetare la 0 + cand dispare factura/avizul de retur), iar la facturare e **pus** de `marcheaza_facturat` + (`EXPORT:15381-15418`). Niciun `IF` / `RAISE` nu il interogheaza. Semnalul autoritar este + `VANZARI_CORESP`, `FACTURAT` e derivat redundant. +- **`ID_VANZARE_SURSA` / `ID_VANZARE_DEST` nu exista.** `VANZARI_CORESP` are exact 5 coloane + (interogare pe `all_tab_columns`): `ID_VANZARE_CORESP`, `ID_VANZARE_FACT`, `ID_VANZARE_AVIZ`, + `STERS`, `TIP`. Nici `VANZARI` nu are coloane `*_SURSA` / `*_DEST` (aceeasi interogare). + +--- + +## 2. Exista alte garzi pe calea de stergere / modificare? + +### 2a. Oracle — nu exista alta, dar garda e **atinsa pe ambele ramuri** de stergere + +Interogare pe `all_source` (owner curent): singurele obiecte care contin `VANZARI_CORESP` sunt +**`PACK_FACTURARE` (package body)** si **`TRG_VANZARI_CORESP_BEFOINS`**. Deci nu exista o a doua +garda ascunsa in alt pachet. + +**Triggerele nu contin garzi** (sursa citita integral din `all_source`): + +| trigger | tabela | eveniment | ce face | +|---|---|---|---| +| `TRG_VANZARI_BEFOUPD` | `VANZARI` | UPDATE | 4 apeluri `pack_audit.verifica_val` (NR_ACT, SERIE_ACT, DATA_ACT, DATA_SCAD) — audit, nu blocare | +| `TRG_VANZARE_BEFOINS` | `VANZARI` | INSERT | — | +| `TRG_VANZARI_DET_BEFOINS` | `VANZARI_DETALII` | INSERT | — | +| `TRG_VANZARI_CORESP_BEFOINS` | `VANZARI_CORESP` | INSERT | doar `SEQ_VANZARI_CORESP.nextval` | + +**Punct important de verificat, pentru ca la prima citire pare o portita:** in +`frm_facturi.do_sterge` apelul direct `pack_facturare.sterge_factura` e **comentat** pe ramura +documentelor cu note contabile — `ofacturare_comun.vc2:4807-4809` (`*!* modificare v 2.2.5`), iar +ramura activa (`:4810-4811`) cheama numai `pack_contafin.finalizeaza_stergere_nota`. **Nu e o +portita**: lantul se inchide in Oracle. + +``` +PACK_CONTAFIN:8322-8326 SELECT COUNT(*) INTO lnEInVanzari FROM vanzari WHERE cod = tnCod; + IF lnEInVanzari > 0 THEN + pack_facturare.sterge_din_vanzari(tnCod, tnAn, tnLuna, tnIdUtil); +PACK_FACTURARE:14996-14998 SELECT ID_VANZARE INTO V_ID_VANZARE FROM VANZARI WHERE COD = V_COD; + pack_facturare.sterge_factura(V_ID_VANZARE, V_LUNA, V_AN, V_ID_UTIL); +``` + +Apelantii lui `sterge_factura` in baza (cautare pe `all_source`) sunt exact doi: +`PACK_FACTURARE.sterge_din_vanzari` (`DB PACK_FACTURARE:14998`) si procedura standalone +`STERGE_DOCUMENT` (`DB STERGE_DOCUMENT:416`). Ambele cai trec prin garda. + +### 2b. VFP — pe calea de stergere: nicio garda pe urmasi + +`frm_facturi.do_sterge` (`COMUN\clase\ofacturare_comun.vc2:4661-4877`) verifica, inainte de apel: +luna inchisa (`:4676`), `sters = 1` (`:4689`), confirmare (`:4694`), luna curenta (`:4701`) si +referinte de incasari/plati (`ReferinteDocumenteNota`, `:4723-4727`). **Nimic despre `FACTURAT`, +`VANZARI_CORESP` sau urmasi** — verificarea lantului e delegata integral Oracle-ului. + +### 2c. VFP + Oracle — pe calea de **modificare**: nicio garda, nici pe urmasi, nici pe lant + +`frm_facturi.do_modifica` (`ofacturare_comun.vc2:4541-4640`) blocheaza doar doua lucruri +(`:4590`, `:4635`): + +``` + If poRec.sters = 0 AND NVL(m.pnEFactura,0) = 0 + ... + amessagebox("Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in eFactura!", 48, "Atentie") +``` + +`pack_facturare.modifica_date_factura` (`EXPORT:14439-14509`) e un lant de `UPDATE`-uri fara niciun +`IF` de validare si fara niciun `RAISE_APPLICATION_ERROR`. Ceea ce e coerent cu ce modifica azi +(ruta, delegat, agent, masina, dataora exp., text aditional, tip SAFT, eFactura, serie/numar/data act, +data scadenta) — campuri de antet care nu ating lantul aviz -> factura. + +--- + +## 3. Concluzia operationala pentru decizia 50 / S7 + +**Regula ceruta de decizia 50 exista deja, si nu are gol pe aviz.** `sterge_factura` implementeaza +azi, in trei garzi consecutive, exact "documentul cu urmasi e blocat, capatul lantului e liber": + +| garda | `EXPORT` | ce blocheaza | +|---|---|---| +| 1 | 5452-5462 | **factura** care are facturi de retur peste ea (`TIP = 3`) | +| 2 | 5466-5476 | **aviz** care are factura (`TIP = 1`) sau aviz de retur (`TIP = 2`) peste el | +| 3 | 5480-5494 | **factura din aviz** ale carei avize-sursa au primit intre timp aviz de retur | + +A treia garda merita subliniata pentru decizia 50: factura din aviz e editabila **doar** cat timp +niciunul dintre avizele ei nu are aviz de retur; nu e "capat de lant" neconditionat. + +**Deci S7 nu are nevoie de o garda noua ca regula — dar are nevoie de o citire noua a aceleiasi +conditii, ca moment.** Garda de azi e in interiorul lui `sterge_factura`, adica se manifesta ca +`ORA-20000` **in mijlocul** pasului de stergere din regenerare, dupa ce utilizatorul a completat +formularul si dupa ce s-a deschis tranzactia. Pentru un flux de editare asta e o esuare tarzie. +Ce lipseste e un **pre-flight read-only**, inainte de intrarea in formular, pe exact aceeasi +conditie (fara reimplementarea regulii): + +```sql +select count(*) from vanzari_coresp + where sters = 0 and id_vanzare_aviz = :id_vanzare and tip in (1, 2, 3) +``` + +plus, pentru cazul "factura din aviz", subinterogarea din garda 3 (`EXPORT:5480-5488`). Garda din +`sterge_factura` ramane pe loc ca plasa de siguranta — nu se muta, nu se slabeste. + +Interogarea se face pe `VANZARI_CORESP`, **nu** pe `VANZARI.FACTURAT`: `FACTURAT` e un marcaj +redundant, derivat, scris/resetat de `marcheaza_facturat` / `sterge_factura`, si necitit de nicio +garda; `VANZARI_CORESP` e semnalul autoritar. + +--- + +## 4. Inventar: ce tipuri de documente-parinte produc urmasi prin `VANZARI_CORESP`? + +**Doar trei, toate scrise din acelasi `CASE` din `finalizeaza_factura` (`EXPORT:14818-14839`), prin +singurul scriitor `scrie_corespondente_vanzari` (`EXPORT:15481-15516`, singurul `INSERT INTO +VANZARI_CORESP` din tot pachetul — si din toata baza, cf. `all_source`).** + +| parinte | urmas | `TIP` | de unde vine lista parintilor | +|---|---|---|---| +| **aviz** (`VANZARI.TIP in 21, 22, 26, 42`) | factura din aviz (`ntip = 4`) | 1 | `clistaid_avize`, setat la `EXPORT:7037` in `scrie_factura_avize`; lista o construieste VFP prin `caut_avize`, filtrata `a.tip in (21,22,26,42) and a.facturat = 0` — `COMUN\programe\oproceduri_facturare.prg:2036-2038` | +| **aviz** | aviz de retur (`ntip = 24`) | 2 | `clistaid` | +| **factura** | factura de retur (`ntip in 8, 9`) | 3 | `clistaid` | + +**Ce NU produce urmasi prin `VANZARI_CORESP`** (deci decizia 50 nu are acoperire acolo prin garda +existenta): + +- **proforma -> factura.** Proforma sta in `VANZARI` cu `EPROFORMA = 1`; `finalizeaza_factura` nu are + ramura care sa scrie corespondenta pentru ea. `TIP = 4` este o **propunere** de la S5c, nu cod + existent. Mai mult, `pack_facturare.sterge_proforma` (`EXPORT:5610-5635`) **nu are nicio garda** — + corpul ei e doar doua `UPDATE ... SET STERS = 1` pe `VANZARI` si `VANZARI_DETALII`. O proforma din + care s-a emis factura se poate sterge azi fara niciun avertisment. (Calea VFP: + `ofacturare_comun.vc2:4707-4719`, care iese din `do_sterge` inainte de restul verificarilor.) +- **comanda -> factura.** Legatura e `VANZARI.ID_COMANDA` + `pack_facturare.inchide_comanda` + (`EXPORT:5769`), nu `VANZARI_CORESP`. `COMENZI` **nu are coloana `FACTURAT`** (verificat pe + `all_tab_columns`) — flagul `facturat` din grid vine din view-ul de incarcare. Garda exista, dar e + **in VFP si pe comanda**, nu pe factura: `COMUN\clase\ocomenzi.vc2:1806-1807` la modificare + ("Aceasta comanda a fost facturata si nu mai poate fi modificata!") si `:2065-2066` la stergere. +- **contract -> factura.** Legatura e `VANZARI.ID_CTR` + `CTR_RATE_FACTURI` + (atinsa de `sterge_factura` la `EXPORT:5566-5580` pentru `V_TIP IN (2, 6, 52)`); nicio linie in + `VANZARI_CORESP`, nicio garda de tip "are urmasi". + +--- + +## 5. Ce am verificat direct vs. ce am dedus + +**Verificat direct pe cod / pe metadate:** + +- textul celor trei garzi din `sterge_factura`, in export **si** desfasurat din `all_source` (identice); +- `CASE`-ul din `finalizeaza_factura` care da semantica lui `TIP`, ramura cu ramura; +- corpul lui `scrie_corespondente_vanzari` si `marcheaza_facturat`; +- ca `INSERT INTO VANZARI_CORESP` exista intr-un singur loc (grep pe export + `all_source` pe toata baza); +- sursa integrala a celor 4 triggere de pe `VANZARI` / `VANZARI_DETALII` / `VANZARI_CORESP`; +- lista completa a apelantilor lui `sterge_factura` (`all_source`), inclusiv lantul + `finalizeaza_stergere_nota -> sterge_din_vanzari -> sterge_factura`; +- coloanele reale ale `VANZARI_CORESP`, `PROFORME`, si absenta lui `FACTURAT` din `COMENZI` + (`all_tab_columns`); +- `frm_facturi.do_sterge` si `frm_facturi.do_modifica` integral, plus `modifica_date_factura`; +- filtrul `caut_avize` din `oproceduri_facturare.prg`. + +**Dedus / cu limite declarate:** + +- Ca `TIP = 1` inseamna "factura normala din aviz" e o deductie din singurul punct de scriere + (`ntip = 4` -> `scrie_corespondente_vanzari(1)`) — solida, dar e deductie, nu o eticheta declarata + intr-un nomenclator. +- Ca setul tipurilor-parinte pentru `TIP = 1` e `{21, 22, 26, 42}` vine din filtrul VFP `caut_avize`, + nu dintr-o restrictie in Oracle. Pachetul insereaza **orice** `ID_VANZARE` primit in + `clistaid_avize`, fara filtru pe tip (`EXPORT:15494-15504`). Alt apelant (alt produs ROA, un import) + ar putea introduce alte tipuri. +- Nu am verificat daca vreun **alt produs ROA** (ROACONT / ROAGEST / ...) are o cale proprie de + stergere care ocoleste `sterge_factura`. Am verificat doar ca in Oracle nu exista alt apelant si + ca in ROAFACTURARE ambele ramuri din `do_sterge` ajung acolo. + +**Din date, deci nedovaditor:** interogarea pe `VANZARI_CORESP` din baza de dev arata `TIP=1` cu +parinti de tip 21 si 22, `TIP=2` cu parinte 22, `TIP=3` cu parinte 1 — consistent cu tabelul de mai +sus, dar volumul e de ordinul unitatilor (8 randuri in total). **Nu e dovada**; concluziile de mai +sus vin din cod. + +## 6. Ce nu s-a putut stabili + +Nimic din cele 4 intrebari nu a ramas nedeterminat. Singurul punct pe care l-as lasa explicit +deschis pentru S7 este cel de la §4: **golul real nu e pe aviz, e pe proforma** — +`sterge_proforma` nu are absolut nicio garda, iar daca S5c chiar introduce `TIP = 4` +(proforma -> factura), garda din `sterge_factura` **nu se aplica automat**, pentru ca `sterge_proforma` +e o procedura complet separata care nu o apeleaza. diff --git a/docs/cercetare/gol_ntip4_factura_din_avize.md b/docs/cercetare/gol_ntip4_factura_din_avize.md new file mode 100644 index 0000000..c59822b --- /dev/null +++ b/docs/cercetare/gol_ntip4_factura_din_avize.md @@ -0,0 +1,231 @@ +# Golul ntip=4 (facturare din avize) fata de proiectarea parametrului de cont contabil + +Cercetare read-only, continua `canal_cont_venit_fara_politica.md` (sectiunea "Proiectarea +parametrului de cont contabil", liniile 13-227). Confirma si extinde golul deja semnalat, mai +succint, in `parametru_cont_contabilizeaza_articol.md:49-56,167-168`. + +Sursa Oracle: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` +(17217 linii). **Toate liniile citate mai jos sunt verificate direct pe acest fisier in aceasta +runda** — numerele din cererea initiala (7520-7537, 5096) s-au dovedit **identice**, fara offset, +fata de fisierul curent (nu +17 cum se anticipa). + +## Verdict, in cinci randuri + +**Ramura `ntip=4` e EXCLUSA STRUCTURAL, dar nu prin mecanismul presupus initial** (nu prin garda +`id_pol` din `cursor_articol`/FACT-024 a lui `contabilizeaza_articol`). Blocajul real e **cu o +functie mai devreme**: `adauga_articol_factura`, ramura `WHEN pack_facturare.ntip = 4` +(`:5080-5103`), cauta randul original din avizul sursa prin `A.ID_POL = V_ID_POL` **fara niciun +handler de exceptie** — daca `V_ID_POL` e `NULL` (exact cazul "articol fara politica"), `NULL = +NULL` nu se potriveste niciodata in SQL, deci `SELECT ... INTO` **cade mereu cu `NO_DATA_FOUND` +neprins**, la momentul **adaugarii articolului in factura** (`adauga_articol_factura`), cu mult +inainte ca linia sa ajunga vreodata la `contabilizeaza_articol`. Practic, un articol fara politica +**nu poate fi adaugat deloc** pe un document `ntip=4` — eroarea apare la primul pas, nu la scriere. +**Gol sora mai important, gasit in aceasta cercetare**: ramurile de **aviz** (`ntip` in afara +bucket-ului "factura") — `28`/`29` (SCD hardcodat `461`) si toate celelalte (SCD hardcodat `418`) +— **raman perfect accesibile** cu `cont_venit` populat, iar ramura noua propusa hardcodeaza +`SCD='4111'` necondiționat de `ntip`, ceea ce ar scrie contul gresit pe orice aviz cu articol fara +politica. Vezi sectiunea "Ramuri sora" mai jos — mai grav decat golul `ntip=4` insusi, pentru ca +e efectiv atins, nu doar teoretic. + +## 1. Lantul de dovezi pentru "ntip=4 e exclus" — pas cu pas + +### 1.1. `ntip` e o variabila de pachet, setata o singura data per document + +``` +ff_...:165 ntip NUMBER(2); -- declarata in spec, variabila publica de pachet +ff_...:1882 pack_facturare.ntip := V_TIP; -- in initializeaza_date_factura (una din cele 4 supraincarcari, ~1650-1918) +``` + +`V_TIP` vine din `poDate.Tip` la apelul VFP `pack_facturare.initializeaza_date_factura(...)` +(`COMUN\clase\ofacturare.vc2:13981-13999`, argumentul `Alltrim(Str(poDate.Tip))` pozitionat ca +`V_TIP`). O data setat, `pack_facturare.ntip` ramane valabil pentru toate apelurile ulterioare +din aceeasi tranzactie (`adauga_articol_factura`, apoi `scrie_factura2`/`scrie_factura_avize_retur`/ +`scrie_aviz_retur` -> `contabilizeaza_articol`) — **e o singura valoare per document, nu per linie**. + +### 1.2. Apelanti VFP care produc `poDate.Tip = 4` + +Comentat consecvent `&& factura din aviz` / `&& aviz` in tot codul VFP: +``` +COMUN\clase\ofacturare_comun.vc2:4347 If poDate.tip = 4 && factura din aviz +COMUN\programe\oproceduri_facturare.prg:1253 If poDate.tip = 4 && factura din aviz +COMUN\programe\ofacturare.prg:1512 Case poDate.tip = 4 +COMUN\clase\ofacturare.vc2:9534,9636,9674,14301,15151,18299,19022 Case poDate.tip = 4 (afisare/validare) +``` +`ofacturare.prg:1268,1488,1504` trateaza si combinatia `poDate.tip = 4 And Not +Empty(poDate.id_comanda_aviz)` alaturi de `ntip IN (3,21,28,42,47)` — confirma ca `ntip=4` e +categoria distincta "facturare pe baza de avize existente" (formularul dedicat de facturare din +avize), separata de facturarea directa sau din comenzi. + +### 1.3. `adauga_articol_factura`, ramura `ntip=4` — blocajul real + +``` +ff_...:4989-5015 PROCEDURE adauga_articol_factura(..., V_ID_POL IN NUMBER, ...) +``` +`V_ID_POL` e parametru direct (nu derivat), trimis de VFP la +`COMUN\clase\ofacturare.vc2:14072` (si identic `:18107`): +``` +Nvl(Alltrim(Str(poArt.id_pol)),[NULL]) +``` +Daca `poArt.id_pol` e `.NULL.` in VFP (cazul "articol fara politica" — premiza intregului +proiect #13), `Str()` propaga `.NULL.`, iar `Nvl(...)` trimite literalul SQL `NULL` — deci +`V_ID_POL` ajunge `NULL` in Oracle exact cand articolul n-are politica. + +In corpul `adauga_articol_factura`, CASE-ul care determina pretul/TVA/valuta ramurii (`:5052-5220`) +are, pentru `ntip=4`: +``` +ff_...:5080-5103 + WHEN pack_facturare.ntip = 4 THEN + -- facturare din avize + SELECT DISTINCT A.PRET, A.PROC_TVAV, A.ID_VALUTA, A.PRET_CU_TVA, B.IN_STOC + INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC + FROM VANZARI_DETALII A + LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL + WHERE A.ID_ARTICOL = V_ID_ARTICOL + AND A.ID_POL = V_ID_POL + AND NVL(A.ID_GESTIUNE, -1000) = V_ID_GESTIUNE + AND A.DISCOUNT_UNITAR = V_DISCOUNT_UNITAR + AND NVL(A.CONT, 'XXXX') = V_CONT + AND A.ID_VANZARE IN (SELECT X FROM table(cast(CHARN2COLLECTION(pack_facturare.clistaid, ',') AS num_tab))); +``` +**Fara niciun `EXCEPTION WHEN NO_DATA_FOUND`** — spre deosebire de ramurile vecine din acelasi +CASE: `ntip=45` are handler cu `FACT-018` (`:5140-5145`), `V_OPT_FACTURARE=3` are handler cu +fallback + `FACT-012` (`:5167-5185`), `ELSE` are handler cu `FACT-013` (`:5188-5198`). Doar ramurile +`ntip IN (3,21,28,42,47)` (`:5053-5078`, cauta in `COMENZI_ELEMENTE` dupa comanda, nu dupa politica) +si `ntip=4` sunt fara plasa de siguranta. + +Cu `V_ID_POL = NULL`, `A.ID_POL = V_ID_POL` **nu se poate potrivi niciodata** (semantica SQL: orice +comparatie cu `NULL` da `UNKNOWN`, niciodata `TRUE`, indiferent ce e stocat in `A.ID_POL`) — +`SELECT ... INTO` (fara `DISTINCT`+agregat care sa tolereze zero randuri) **arunca mereu +`NO_DATA_FOUND`**. Neprinsa aici, exceptia se propaga necaptata pana la apelantul PL/SQL (blocul +anonim generat de VFP), care se intoarce la `goExecutor` ca eroare Oracle bruta (`ORA-01403: no +data found`), **nu ca `FACT-024`** — vezi punctul 3 mai jos pentru diferenta. + +**Concluzie pas 1.3**: un articol fara politica (`V_ID_POL=NULL`, exact scenariul `cont_venit`) +**nu poate fi adaugat pe un document `ntip=4`** — apelul `adauga_articol_factura` insusi esueaza, +inainte ca `VANZARI_DETALII_TEMP` sa primeasca randul, deci **cu mult inainte** ca +`contabilizeaza_articol` (unde e ramura noua `cont_venit`) sa vada vreodata acea linie. + +### 1.4. Blocajul secundar (daca 1.3 n-ar exista): `contabilizeaza_articol` insusi + +Chiar daca ipotetic linia ar ajunge in `VANZARI_DETALII_TEMP` cu `id_pol=NULL`, funcia care scrie +efectiv factura are propria garda, **inaintea oricarui ntip**: +``` +ff_...:7278-7302 BEGIN + SELECT COMPUS, ID_POL_ART INTO V_COMPUS, V_ID_POL_ART + FROM VCRM_POLITICI_PRET_ART + WHERE ID_ARTICOL = detalii_articol.id_articol + AND ID_POL = detalii_articol.id_pol; + EXCEPTION + WHEN NO_DATA_FOUND THEN + ... RAISE_APPLICATION_ERROR(-20000, '... (FACT-024)'); + END; +``` +Aceasta ruleaza **la inceputul functiei, inainte de `OPEN cursor_articol`** (`:7393`) si deci +inainte de CASE-ul pe `ntip` (`:7398-7423`) si de IF-ul `ntip<>4` (`:7472`). Cu `id_pol=NULL`, +`FACT-024` cade aici, indiferent de `ntip` — **confirma independent ca ramura `ntip=4` de la +`:7520-7537` (`scrie_fact_aviz_custodie`) e neatinsa azi de o linie fara politica**, e a doua +plasa de siguranta, redundanta cu 1.3 dar pe alt strat. + +**Ramura propusa `cont_venit IS NOT NULL`** (proiectare `canal_cont_venit_fara_politica.md` +sectiunea 3) se planteaza "inainte de `:7278`" — deci **inlocuieste ambele garda de mai sus** cand +`cont_venit` e populat. Asta e sigur (design-ul insusi), dar devine irelevant pentru `ntip=4` +pentru ca linia nu ajunge niciodata aici (blocata la 1.3). + +## 2. Ce se intampla defensiv daca cineva ajunge totusi acolo + +Doua scenarii distincte, cu raspunsuri diferite: + +**(a) Scenariul normal — `cont_venit` populat, `id_pol` NULL (cum cere premiza proiectului)**: +esec **la adaugarea articolului**, nu la scrierea facturii. Eroare Oracle bruta +(`ORA-01403: no data found`), aparuta din `adauga_articol_factura` (`:5080-5103`), propagata prin +`goExecutor` catre VFP ca `oPrelucrareEroare()` — **nu `FACT-024`** (acela e alt cod, alta functie, +alt moment). Utilizatorul vede un mesaj Oracle generic, nu un mesaj de business explicativ. Nu +exista azi niciun cod care sa traduca acest caz intr-un mesaj prietenos — e un gol de UX, nu de +integritate a datelor (nu se scrie nimic gresit, doar esueaza fara explicatie buna). + +**(b) Scenariul defensiv real — `cont_venit` SI `id_pol` populate simultan pe aceeasi linie** +(nimic in schema nu impune exclusivitate reciproca; `VANZARI_DETALII_TEMP.CONT_VENIT` e o coloana +noua, fara `CHECK` care sa lege de `ID_POL`). Daca cineva (bug in VFP, sau folosire deliberata +combinata pe viitor) ar trimite ambele populate pe un document `ntip=4`: pasul 1.3 ar reusi +(`V_ID_POL` nu mai e NULL, deci `SELECT` din `VANZARI_DETALII` isi gaseste randul), linia intra in +`VANZARI_DETALII_TEMP` cu `cont_venit` **si** `id_pol` completate. La `contabilizeaza_articol`, +ramura noua (`detalii_articol.cont_venit IS NOT NULL`) preia controlul complet — **fara sa +verifice `ntip`** — si executa necondiționat `scrie_nota` + `descarca_gestiune` + discount, exact +ca pentru o factura normala. **Nu cade cu nicio eroare. Scrie tacut date gresite**: sare complet +peste `scrie_fact_aviz_custodie` (`:7521-7536`), care exista tocmai pentru ca marfa a iesit deja +din gestiune cand s-a scris avizul sursa — rularea `descarca_gestiune` in loc de acea functie +**descarca stocul a doua oara** pentru aceeasi marfa, fara nicio garda care sa prinda dubla +scadere. Acesta e raspunsul relevant pentru textul de plan: nu un crash, un **defect tacut de +date** daca vreodata cele doua canale se intalnesc pe aceeasi linie. + +## 3. Toate valorile `ntip` relevante in zona `contabilizeaza_articol` (`:7173-7547`) — verdict per valoare + +Ramura noua (`cont_venit IS NOT NULL`) inlocuieste tot ce e listat mai jos, necondiționat de `ntip` +(design-ul, sectiunea 3, nu mentioneaza `ntip` deloc in continutul ramurii noi). Verdictul de mai +jos e "atinsa" = poate ajunge cu `cont_venit` populat prin `adauga_articol_factura` fara sa esueze +la pasul 1.3-echivalent; "neatinsa" = blocata structural inainte de a ajunge aici (ca `ntip=4`); +"ambigua" = depinde de alt cod neverificat complet in aceasta runda. + +| `ntip` | Comportament azi in `contabilizeaza_articol` | Atinsa de linie `cont_venit`? | Verdict fata de ramura noua | +|---|---|---|---| +| `<=20` sau `IN (43,44,45,46,48,49,51,52)` (`:7400-7412`) | SCD/ASCD din politica (`crs_rand_articol.scd`) — bucket "factura" | **DA** — `adauga_articol_factura` pentru aceste `ntip` foloseste calea generica (`ELSE`, `:5187-5203`, fara cerinta de `id_pol`) sau ramuri proprii (`2,6,26,52`→contract, `45`→restaurant) care oricum accepta `V_PRET_TEMP` cand nu gasesc potrivire | **E cazul acoperit de design** — SCD='4111' hardcodat e gandit exact pentru acest bucket. Fara probleme noi, cu exceptia sub-cazurilor de mai jos. | +| `= 46` (`nTipNotaPlata`), sub-caz al bucketului "factura" | `scrie_nota` **NU se apeleaza** (`:7441`, `IF ntip <> nTipNotaPlata`) | DA (e in bucketul de mai sus) | **GOL SORA nou, neconsemnat in proiectare**: ramura noua apeleaza `scrie_nota` necondiționat — pentru un document `NotaPlata` cu linie `cont_venit`, s-ar scrie o nota care azi e sarita intentionat. De adaugat aceeasi garda in ramura noua. | +| `= 7`, sub-caz al bucketului "factura" | `EXPLICATIE` primeste sufix `' INVOICE:' + cdescriere` (`:7431-7433`) | DA | **Gol cosmetic**: ramura noua foloseste direct `detalii_articol.explicatia`, fara sufix. Probabil acceptabil (design-ul il numeste "mai bun"), dar e o schimbare de continut pe documente de export, de confirmat. | +| `IN (8,9)`, sub-caz al bucketului "factura" | `EXPLICATIE` primeste sufix `' RETUR FACTURA:' + cdescriere` (`:7434-7436`) | DA | Acelasi gol cosmetic ca mai sus. | +| `IN (28,29)` (`:7413-7417`) | SCD hardcodat `'461'` — aviz catre clienti debitori | **DA** — `adauga_articol_factura` pentru `28` intra pe ramura `ntip IN (3,21,28,42,47)` (`:5053-5078`), care cauta in `COMENZI_ELEMENTE` dupa comanda+pret, **nu dupa `id_pol`** — un articol fara politica poate gasi potrivire aici daca exista element de comanda corespunzator | **GOL SORA, mai grav decat `ntip=4`**: ramura noua ar scrie `SCD='4111'` in loc de `'461'` pe un aviz catre client debitor — cont contabil gresit, atins efectiv. | +| toate celelalte (`ELSE`, `:7418-7422`: `21,22,23,24,25,27,30,41,42,47,...`) | SCD hardcodat `'418'` — aviz generic | **DA, pentru aceleasi motive** (multe din aceste `ntip` — `3,21,42,47` — trec prin ramura "din comenzi" fara cerinta de `id_pol`; altele avansate direct din bucket implicit `ELSE` din `adauga_articol_factura`, `:5187-5203`, care nu cere `id_pol` deloc) | **Acelasi gol SCD hardcodat**, aplicabil la orice document de tip aviz (nu doar 28/29). | +| `= 4` (`:7472,7520-7537`) | `scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount | **NU** — exclus structural la pasul 1.3 (`adauga_articol_factura` esueaza inainte) | Neatins in scenariul normal (a); atins doar in scenariul defensiv (b) daca cineva forteaza `id_pol` si `cont_venit` simultan — vezi sectiunea 2. | + +**Cel mai important rezultat al acestui tabel**: golul cerut explicit (`ntip=4`) e cel **mai putin +grav** dintre cele gasite, pentru ca e singurul exclus structural. Golurile reale, efectiv atinse, +sunt hardcodarea `SCD='4111'` pe orice document de tip **aviz** (`28`,`29` si bucket-ul `ELSE`) si +omisiunea gardei `ntip=46` pentru `scrie_nota`. + +## 4. Text propus pentru plan (sectiunea deciziei 34) + +``` +**Ramura `ntip=4` (facturare din avize, `:7520-7537`) — exclusa structural, nu necesita cod +suplimentar.** `adauga_articol_factura`, ramura `WHEN ntip=4` (`:5080-5103`), cauta randul sursa +din `VANZARI_DETALII` prin `A.ID_POL = V_ID_POL`, fara handler de exceptie — cu `V_ID_POL=NULL` +(exact cazul liniei fara politica), comparatia nu se poate potrivi niciodata, deci apelul cade cu +`ORA-01403` la adaugarea articolului, inainte ca linia sa ajunga la `contabilizeaza_articol`. Un +articol fara politica nu poate fi, structural, adaugat pe un document `ntip=4`; ramura noua +`cont_venit` nu are nimic de tratat aici. + +**Gol descoperit, de acoperit inainte de implementare**: ramurile de **aviz** (`ntip IN (28,29)` → +`SCD='461'`, celelalte → `SCD='418'`, `:7413-7422`) **raman accesibile** cu `cont_venit` populat — +`adauga_articol_factura` nu cere `id_pol` pentru aceste `ntip` (foloseste calea "din comenzi" sau +calea implicita). Ramura noua trebuie sa aleaga `SCD` in functie de `ntip` (pastrand `'461'`/`'418'` +pentru avize), nu sa hardcodeze `'4111'` necondiționat — altfel orice aviz cu articol fara politica +primeste contul de clienti in loc de contul de aviz. Similar, ramura noua trebuie sa pastreze garda +`IF ntip <> nTipNotaPlata` inainte de a apela `scrie_nota` (azi sarita pentru `ntip=46`). + +**Defensiv (opțional)**: daca se doreste o garda explicita in loc de a te baza pe imposibilitatea +structurala de mai sus, se poate adauga `IF detalii_articol.cont_venit IS NOT NULL AND +pack_facturare.ntip = 4 THEN RAISE_APPLICATION_ERROR(...)` la inceputul ramurii noi — util doar +daca cineva ar reusi vreodata sa populeze simultan `id_pol` si `cont_venit` pe o linie `ntip=4` +(azi imposibil pe caile VFP cunoscute), caz in care ramura noua ar rula altfel tacut +`descarca_gestiune` in loc de `scrie_fact_aviz_custodie`, riscand dubla descarcare de gestiune. +``` + +## 5. Ce nu s-a putut stabili in aceasta runda + +- **Daca exista vreo cale VFP care sa populeze simultan `poArt.id_pol` si viitorul `cont_venit`** + (scenariul defensiv (b)) — cercetarea de fata arata doar ca schema/codul Oracle nu o impiedica; + n-am gasit (si nici n-am cautat exhaustiv) un loc in VFP care ar genera aceasta combinatie azi. + Probabil imposibil pe fluxurile curente (proiectarea de baza presupune `cont_venit` populat + **doar** cand `id_pol` lipseste), dar nu demonstrat exhaustiv pe tot codul VFP. +- **Comportamentul exact `oExecute`/`oPrelucrareEroare` la o exceptie Oracle neprinsa** (scenariul + (a)) — presupus ca ajunge la utilizator ca mesaj Oracle brut prin mecanismul general de eroare al + `goExecutor`, dar nu am urmarit codul `oPrelucrareEroare()` in aceasta runda pentru a confirma + formatarea exacta. +- **Daca `ntip IN (23,25,30,41)` (transfer subunitati) si `IN (42,47)` (custodie) pot, in practica, + sa primeasca un articol fara politica** prin ramura "din comenzi" a `adauga_articol_factura` + (`:5053-5078`) — argumentat structural posibil (nu cere `id_pol`), dar nu testat pe date, nu + urmarit pana la capat daca `COMENZI_ELEMENTE` insusi ar putea contine un rand fara politica. + +## Handoff + +Cercetare incheiata, toate cele 5 livrabile cerute de team-lead sunt in sectiunile 1-4 de mai sus. +Niciun cod atins, nicio scriere Oracle, doar `SELECT`/`Read`/`Grep`. Context consumat moderat — +nu a fost necesara predarea de context. diff --git a/docs/cercetare/handoff_s4_runda1.md b/docs/cercetare/handoff_s4_runda1.md new file mode 100644 index 0000000..3bc124f --- /dev/null +++ b/docs/cercetare/handoff_s4_runda1.md @@ -0,0 +1,191 @@ +# Handoff — S4 runda 1 (PAGE3 doar afisare), predare pe prag de context + +Sesiune intrerupta la prag de context (`monitorizare-context.md`). Stare **stabila si consistenta** +(text = binar), dar cu schele de diagnostic inca in cod — de scos inainte de livrare. + +## 1. Ce am modificat, unde, si write-back-ul + +Toate in `COMUN` (cross-proiect). + +- **`COMUN\programe\ofacturare_editare.prg`** — **write-back N/A (e `.prg`, nu `.vcx`, se ruleaza direct)**. + Antet actualizat (linia cu `*!* helpere...`) + doua functii noi, adaugate la coada fisierului: + - `IncarcaVanzareNota(tnCod, tnNract, tcSerieAct, tdDataAct)` — gaseste randul din `VANZARI` + corespunzator notei curente, in cursorul `tvanz`. + - `IncarcaArticoleFactura(tnIdVanzare)` — incarca liniile active din `VANZARI_DETALII` (+ denumire + articol/gestiune/valuta) in cursorul `tvd`. + Fisier ASCII (fara diacritice), editat direct cu Edit tool — fara riscul de corupere cp1252. + +- **`COMUN\clase\omodificari.vc2`** (clasa `frm_modific2024`) — **text si binar SINCRONIZATE**, + ambele includ inca **cod de diagnostic care trebuie scos**: + - PAGE3 nou pe `pgfArticole` (`PageCount=3`, `PAGE3.Caption="Articole factura"`) — linia cu + `PageCount = 3, ;` in blocul `ADD OBJECT 'pgfArticole'`. + - Grid nou `pgfArticole.PAGE3.grdArticoleFactura` (13 coloane, `RecordSource="tvd"`, + `ReadOnly=.T.` la nivel de grid si pe fiecare `Text1`), plus intrarile `OBJECTDATA` + corespunzatoare (cautabile dupa `grdArticoleFactura`). + - Proprietati noi pe clasa: `lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare` (in blocul + `*`, cautabile dupa `lareaarticolevanzari`/`nidvanzare`/`ntipvanzare`). + - `Load()`: placeholder `CREATE CURSOR tvd (...)` daca nu e deja deschis — **necesar**, altfel + grid-ul incearca `USE tvd` la constructie si pica pe dialog nativ "Open" (vezi sectiunea 4). + - `Show()`: bloc nou (cautabil dupa `This.lAreArticoleVanzari`) care apeleaza + `IncarcaVanzareNota`/`IncarcaArticoleFactura` si comuta `pgfArticole.PageCount` intre 2 si 3. + - **DE SCOS inainte de livrare**: 5 linii `STRTOFILE(...'diag_class.txt'...)` inserate ca + checkpoint-uri de depanare in `Init()` (3 linii: inceput, dupa `DoDefault()`, sfarsit) si + `Load()` (2 linii: inceput, sfarsit) — cautabile dupa `diag_class`. Nu au efect functional + (doar scriu intr-un fisier de log), dar nu trebuie sa ramana in cod livrat. + - **Fidelity check txt2vcx: OK** la ultimul write-back (cel CU liniile de diagnostic inca in el). + `.vcx`/`.vct` au mtime 12:10, exact dupa acel write-back — deci **binarul reflecta exact + `.vc2`-ul curent**, inclusiv diagnosticul. + +### Backup-uri pe disc (`COMUN\clase\`) + +| Fisier | Continut | +|---|---| +| `omodificari.vc2.pre_s4runda1.bak` | Originalul, dinaintea oricarei modificari S4 | +| `omodificari.vc2.pre_diag.bak` | Versiunea mea **curata** (fara cele 5 linii de diagnostic) — **asta e sursa de folosit pentru versiunea finala** | +| `omodificari.vc2.mine_s4_full.bak` | Identic cu `pre_diag.bak` (copie facuta in timpul unui test de izolare) | +| `omodificari.vcx.mine_s4.bak` / `omodificari.vct.mine_s4.bak` | **Binarul** corespunzator lui `pre_diag.bak`, copiat direct (fara compilare) — restaurare instantanee | + +**Pasul urmator recomandat**: `Copy-Item omodificari.vc2.pre_diag.bak -> omodificari.vc2`, +`Copy-Item omodificari.vcx.mine_s4.bak -> omodificari.vcx` (+ `.vct`) — restaurare **instantanee** +(fara `txt2vcx`) la versiunea curata cu PAGE3 functional, fara schela de diagnostic. Verifica dupa +aceea cu un `diff` ca `omodificari.vc2` chiar nu mai contine `diag_class`. + +## 2. Ce mai ramane din runda 1 + +Din sectiunea E / runda 1 a planului (A.2 PageCount + A.3 detectie + B.1 incarcare + B.2 marcaje): + +- **A.2 (PageCount)** — FACUT, testat static (fidelity OK), **netestat live pe formular** (vezi + sectiunea 3 — blocaj de mediu, nu s-a ajuns sa vad `PageCount` pe formularul instantiat real). +- **A.3 (detectie)** — FACUT, testat **direct** (fara UI) cu rezultate PASS pe date reale (sectiunea + 3). Deviaza de la recomandarea planului (cauta si de ce, sectiunea 4). +- **B.1 (incarcare cursoare)** — FACUT, testat direct, PASS. +- **B.2 (marcaje stare linii)** — **NU e in scope runda 1** (grid-ul e strict readonly, fara + editare — marcajele `_modificat`/`sters`/`id_vanzare_det=0` sunt pentru runda 2). Nu e inceput. + +**Ce lipseste efectiv**: verificarea end-to-end ca, pe formularul REAL (`Createobject('frm_modific2024')` ++ `.Show()`), `PageCount` chiar ajunge 3 pe o factura si 2 pe o nota fara `VANZARI` — blocata de +problema de mediu din sectiunea 3. Codul e scris si compilat curat; ramane doar confirmarea live. + +## 3. Ce am testat, cu ce rezultat + +Suita: `COMUN\utile\Teste\editare_factura\test_page3_articole.prg` (headless, `vfp9.exe -A -T`, +conexiune reala `MARIUSM_AUTO`). Log: `test_page3_articole_log.txt` in acelasi folder. + +**PASS, pe date reale, testat direct (apel functie, fara formular)**: +- `cod=1140888` → `IncarcaVanzareNota` gaseste `id_vanzare=1050`, `tip=1`; `IncarcaArticoleFactura` + incarca 4 linii — coincide exact cu baza de regresie din `progres.md` (`TOTAL_CU_TVA=1924.59`). +- `cod=1140885` → gaseste `id_vanzare=1047`, `tip=-12` (nu e factura, e alt tip de document) — + confirma ca detectia functioneaza pe **orice** tip din `VANZARI`, nu doar facturi (decizia 19). +- `cod=1125486` (an=2008, luna=2, notă contabila fara nicio legatura cu `VANZARI`) → 0 randuri + gasite — cazul negativ cerut de criteriul de "gata" al rundei. +- **Coliziune pe `cod=1139934`** (patru randuri active in `VANZARI`, acelasi cod): filtrul compus + (cod+nract+serie_act+data_act) gaseste EXACT randul cerut (`nract=375/SSS` → `id_vanzare=882`) si + NU gaseste nimic pentru un `nract` fara corespondent (`nract=13`) — vezi sectiunea 4, e important. + +**NETESTAT live (formular real)**: `verifica_pagecount_form` (in acelasi fisier de test) instantiaza +`frm_modific2024` exact ca `do_editare_factura` (`Createobject`+`Show`) si verifica `PageCount` — +**se blocheaza intr-un dialog nativ VFP "View Parameter"** in timpul `Createobject`, inainte sa +apuce sa ruleze vreo linie din `Show()`. Vezi sectiunea 4 pentru diagnosticul facut si o pista noua, +neexplorata inca, primita de la team-lead. + +**`test_baseline_isolation.prg`** (in acelasi folder) — **fisier temporar de diagnostic, se poate +sterge** dupa ce cineva confirma diagnosticul de mai jos independent; nu face parte din suita +finala (antetul lui o spune explicit). L-am folosit ca sa demonstrez ca **binarul ORIGINAL (dinainte +de S4) NU are acest blocaj** in acelasi mediu de test (`PageCount=2`, `done`, fara dialog) — deci +problema e din diff-ul meu, nu un gol preexistent de mediu. + +## 4. Descoperiri care nu trebuie pierdute + +### `VANZARI.COD` NU e unic — dovedit pe date, nu presupus + +Contrar recomandarii A.3 din plan ("`SELECT ... FROM vanzari WHERE cod = tact.cod`"), am verificat +direct pe `MARIUSM_AUTO`: +- Indexul `IDX_VANZARI_04` e pe `(STERS, COD)` si e **NONUNIQUE** — schema insasi nu garanteaza + unicitate. +- `cod=1139934` are **4 randuri active** (`STERS=0`) in `VANZARI`, cu `TIP`/`NUMAR_ACT` diferite + (375/SSS, 24/PF, 25/PF, 26/PF). `cod=1139555` are 2 randuri cu `TIP` diferit (43 si 1). +- Filtrarea suplimentara pe `NUMAR_ACT`+`SERIE_ACT`+`DATA_ACT` (toate disponibile pe cursorul `tact` + ca `nract`/`serie_act`/`dataact`, din `vact_tot`) rezolva ambiguitatea: `(COD, NUMAR_ACT, + SERIE_ACT, DATA_ACT)` verificat **fara nicio dublura** pe toata tabela `VANZARI`. +- `ID_FACT` (alternativa mentionata in `progres.md` — "toate legaturile trec prin ID_FACT sau + ID_VANZARE, niciodata prin cod") **nu e utilizabil ca filtru unic aici**: e `NULL` pe o fractie + mare de randuri chiar si pentru facturi normale (`tip=1`: 58 din 183 `NULL`; `tip=51`: 58 din 74 + `NULL`), deci n-ar gasi avizele/tipurile fara facturare clasica — ar incalca decizia 19. + +**Concluzie aplicata in cod**: `IncarcaVanzareNota` filtreaza pe cele 4 coloane compuse, nu doar pe +`cod`. Aceasta e o **corectie de fond fata de A.3 din plan**, nu o improvizatie — sectiunea C.1 din +`plan_06_s4_proiectare.md` ar trebui sa mentioneze asta daca cineva o rescrie. + +### Blocaj "View Parameter" la `Createobject('frm_modific2024')` — cauza NECONFIRMATA + +Simptom: procesul `vfp9.exe` ramane blocat (CPU~0, Responding=True) intr-un dialog nativ **"View +Parameter"** exact in timpul liniei `Createobject([frm_modific2024], lnIdSet)`, inainte sa ajunga +la orice linie din `Show()` — confirmat cu checkpoint-uri `STRTOFILE` in `Init()`/`Load()` (niciunul +nu s-a scris, deci blocajul e chiar mai devreme decat `Load()`, sau checkpoint-urile nu s-au atins +din alt motiv neexplicat). + +**Exclus cu dovezi** (nu pierde timp re-verificand): +- **Nu e cursorul `tvd` lipsa** — am incercat atat placeholder in `Load()`, cat si pre-creare in + scriptul de test inainte de `Createobject`; blocajul a persistat identic. +- **Nu e un gol de mediu preexistent** — binarul ORIGINAL (`omodificari.vcx.mine_s4.bak` restaurat + temporar) ruleaza curat in ACELASI mediu de test (`test_baseline_isolation.prg`, `PageCount=2`, + fara dialog). Deci e ceva din diff-ul meu. +- **Un blocaj anterior, diferit** (`File 'crsjtvatemp.dbf' does not exist`, dialog "Open") era + intr-adevar preexistent — cerut de `Column63` din `grdRulaje` (PAGE1, cod netusat de mine), + rezolvat in scriptul de test cu `update_jtva_coloane("", "crsJtvaTemp", 0)` inainte de + `Createobject` (nu in clasa — e o lipsa de mediu de test, nu de productie: in productie, + `crsJtvaTemp` e populat pe alte cai inainte sa ajunga un utilizator la acest formular). +- Am citit `_pageframe.Init()` (`_baza.vc2:514-523`, itereaza `For i = 1 To PageCount` si cheama + `tradu(.Caption)`) ca prim suspect — **`tradu()` (`oproceduri_comune.prg:1746`) e confirmat + inofensiv** (doar `STRTRAN` pe diacritice, fara Oracle/View). Nu e cauza, dar ramane singurul cod + DEPENDENT de `PageCount` gasit prin cautare in `_baza.vc2`/`_frm_base.vc2`/`_grd_base.vc2`. +- Am cautat "CREATE SQL VIEW" / `USE ... VIA` in toata ierarhia de clase (`omodificari`, `_baza`, + `_frm_base`, `_grd_base`, `gridextras`) — **zero rezultate**. Niciun view local gasit static. + +**Pista noua, neexplorata** (primita de la team-lead, nu verificata de mine inca): dialogul "View +Parameter" apare tipic cand o variabila referita prin `?variabila` (SQL passthrough) sau intr-un +view parametrizat e declarata `LOCAL` in loc de `PRIVATE` intr-un harness de test — `LOCAL` nu e +vizibil in rutinele apelate. Exemplu citat: `do_editare_factura` declara +`Private pnAn, pnLuna, lnCod, lnIdFact, lnIdVanzare` (`ofacturare_comun.vc2:3742`). **Nu am apucat +sa verific** daca `test_page3_articole.prg` foloseste `LOCAL` unde codul original ar cere `PRIVATE` +undeva in lantul `IncarcaCursoareModificareNota`/`Createobject` — e primul lucru de incercat inainte +de a relua sapatul cu `EnumWindows`/`PrintWindow` (costisitor, ~15 cicluri de test in aceasta +sesiune doar pentru asta). + +## 5. Capcane de mediu platite in aceasta sesiune + +- **Sterge intotdeauna `.fxp`-ul vechi inainte de fiecare rulare de test** — m-a pacalit o data: + am editat `.prg`-ul dar am uitat sa sterg `.fxp`, iar VFP a rulat tacut codul VECHI compilat, + dand rezultate inconsistente intre rulari identice in aparenta. Simptom: log-ul arata alt + comportament decat sursa curenta ar trebui sa produca. +- **FoxBin2Prg NU pastreaza ordinea textuala la scriere-citire (roundtrip)** pentru ADD OBJECT + multiple si proprietati custom — le REGENEREAZA in ordine proprie (alfabetica pentru + coloane/obiecte fii, alta regula neclara pentru proprietati custom simple — `nidvanzare` a iesit + INAINTEA lui `nid_set`, desi `_` < `v` in ASCII). Fidelity-check-ul compara text-sursa cu + text-regenerat, deci **orice ordine "naturala" (alfabetica presupusa de mine) poate pica fidelity**. + Solutie aplicata: dupa primul fidelity FAIL, am adoptat ca sursa noua textul regenerat din + `\verify\*.vc2` (e deja in forma canonica), nu am incercat sa ghicesc ordinea corecta. + Confirma exact indicatia din `flux-editare-vfp-text.md`. +- **`InputMask` cu literal (nu `get_mask(...)`)** trebuie **ghilimeluit** (`"9.99"`), nu bar (`9.99`) + — altfel VFP il interpreteaza numeric, nu ca string de format. Prins abia la inspectia manuala a + textului regenerat, fidelity-check-ul NU l-a semnalat separat (a picat impreuna cu reordonarea). +- **Grid nou cu `RecordSource` pe un cursor care nu exista inca la constructia formularului** → + VFP incearca `USE ` ca fisier fizic si arata dialogul nativ "Open" daca nu-l + gaseste pe disc — exact acelasi tipar deja documentat in clasa pentru `saft_taxtable`/ + `saft_mecanisme_plati` (`Load()`, verificat `If !Used(...)` inainte de orice altceva). Solutia + e aceeasi: placeholder gol creat in `Load()`, INAINTE ca framework-ul sa construiasca grid-urile. +- **Query-uri Oracle multiple rulate manual (sqlplus) au fost esentiale** pentru validarea empirica + a filtrului compus pe `VANZARI` — nu s-ar fi descoperit coliziunea pe `cod` doar din cod static. + +## 6. Ce recomand pentru urmatorul agent + +1. Restaureaza versiunea curata (sectiunea 1, pasul recomandat) — 2 copieri de fisier, fara + compilare, verifica cu `grep diag_class` ca a disparut. +2. Incearca pista `LOCAL`/`PRIVATE` din sectiunea 4 pe `test_page3_articole.prg` inainte de orice + alta depanare GUI. +3. Daca se rezolva: reruleaza `test_page3_articole.prg` complet, confirma PASS pe toate cazurile + (inclusiv `verifica_pagecount_form`), sterge `test_baseline_isolation.prg` + `.fxp` + log-ul lui, + scrie diff-ul (diff aplicat (sters), `git diff --no-index `) si + raportul (`docs\cercetare\rec_s4_runda1.md`) cerute initial de team-lead. +4. Daca team-lead sau alt agent scrie capcana "View Parameter / LOCAL vs PRIVATE" in + `testare-ui-vfp.md`, nu o duplica — team-lead a intrebat explicit cine o scrie. diff --git a/docs/cercetare/idfact_refolosire_si_documente.md b/docs/cercetare/idfact_refolosire_si_documente.md new file mode 100644 index 0000000..53d6b3c --- /dev/null +++ b/docs/cercetare/idfact_refolosire_si_documente.md @@ -0,0 +1,337 @@ +Cercetare S9: reemiterea unui document cu acelasi ID_FACT (SET_IDFACT + DOCUMENTE) +==================================================================================== + +Verdict (rezumat) +------------------ +**DA, dar cu conditii — nu e o schimbare izolata.** `SET_IDFACT` activ (`PACK_CONTAFIN.pck:3037-3040`) +ia neconditionat un `ID_FACT` nou din `SEQ_IdFact`; niciun apelant din toata suita (~25-30 copii +identice ale `oscrie_in_fisiere.prg`, cate una per produs) ii impune un `ID_FACT`. Varianta comentata +(`:3016-3035`, cauta pe `NRACT+SERIE_ACT+DATAACT+ID_CTR` cu `STERS=0`) **nu rezolva S9** ca atare: +dupa stergerea soft a documentului vechi, `STERS` e deja 1, cautarea nu-l gaseste, si cade tot pe +secventa — plus riscul de coliziune cu un document viitor neinrudit care are aceleasi 4 campuri. +Blocajul real nu e in `SET_IDFACT`, ci in scrierea din `DOCUMENTE`: acolo e un `INSERT` simplu +(`:796-817`) pe `ID_DOC`, care e **PRIMARY KEY** (`PK_DOCUMENTE`, unic, `fn_script.sql:5192-5198`). +Stergerea (`STERGE_DIN_ACT:1835-1855`) e soft-delete pur — `ACT.STERS=1`, apoi `DOCUMENTE.STERS=1` +pentru id_fact-urile gasite pe acel `COD` — randul vechi **nu se sterge fizic**. Deci reemiterea cu +acelasi `ID_FACT`, cu codul de azi neschimbat, ar arunca `ORA-00001` la insertul in `DOCUMENTE`, +pentru ca randul cu acel `ID_DOC` inca exista (doar marcat `STERS=1`). Varianta `MERGE` deja +comentata (`:818-847`) nu ajuta din prima: are doar `WHEN NOT MATCHED`, fara `WHEN MATCHED`, deci +daca randul exista deja l-ar ignora tacit — documentul reemis ar ramane cu `DOCUMENTE.STERS=1`. +Concluzie operationala: reutilizarea e posibila, dar cere trei schimbari simultane (detaliate la C.7), +nu doar reactivarea codului comentat din `SET_IDFACT`. + +A. SET_IDFACT +-------------- + +### A.1 — Ramura activa vs. ramura comentata + +Cod integral, `PACK_CONTAFIN.pck:3014-3040`: + +``` + ------------------------------------------------------------------------------------ + /* -- 25.02.2013 : am comentat pentru ca se face unirea id_fact pe listarea din JC/JV + PROCEDURE SET_IDFACT(tdDataAct ACT_TEMP.DATAACT%TYPE, + tcSerie_Act ACT_TEMP.SERIE_ACT%TYPE, + tnNrAct ACT_TEMP.NRACT%TYPE, + tnId_Ctr ACT_TEMP.ID_CTR%TYPE) IS + V_ID_FACT DOCUMENTE.ID_DOC%TYPE; + BEGIN + BEGIN + SELECT ID_DOC + into pack_contafin.nIdFact + FROM DOCUMENTE + WHERE NRACT = tnNrAct + AND NVL(SERIE_ACT, '+-') = NVL(tcSerie_Act, '+-') + AND DATAACT = tdDataAct + AND NVL(ID_CTR, 0) = NVL(tnId_Ctr, 0) + AND STERS = 0; + EXCEPTION + WHEN NO_DATA_FOUND THEN + SELECT SEQ_IdFact.NEXTVAL into pack_contafin.nIdFact FROM DUAL; + END; + END SET_IDFACT;*/ + ------------------------------------------------------------------------------------ + PROCEDURE SET_IDFACT(V_GCS IN VARCHAR2) IS + BEGIN + SELECT SEQ_IdFact.NEXTVAL into pack_contafin.nIdFact FROM DUAL; + END SET_IDFACT; +``` + +Diferenta de comportament daca s-ar reactiva varianta comentata: in loc sa genereze mereu un +`ID_FACT` nou, ar cauta intai in `DOCUMENTE` un document **nesters** (`STERS=0`) cu aceeasi +combinatie `NRACT + SERIE_ACT + DATAACT + ID_CTR`, si doar daca nu gaseste nimic ar cere secventa. +Doua probleme pentru S9: +- **nu se aplica la reemitere**: documentul vechi e deja marcat `STERS=1` inainte de rescriere + (vezi B.6), asa ca filtrul `STERS=0` nu-l gaseste — cade tot pe `SEQ_IdFact.NEXTVAL`, exact ca azi; +- **risc de coliziune**: daca NRACT/SERIE_ACT/DATAACT/ID_CTR ar coincide intamplator cu alt document + nesters (nu neaparat cel pe care il reemitem), i-ar imprumuta ID_FACT-ul aceluia. +Semnatura veche are 4 parametri de identitate diferiti de semnatura activa (1 parametru `V_GCS`, +folosit doar ca "user"/context, nefolosit in corp) — deci reactivarea ar cere fie o supraincarcare, +fie schimbarea semnaturii peste tot unde e chemata (vezi A.2). + +### A.2 — Apelanti + +**Un singur punct de apel in PL/SQL**: `PACK_CONTAFIN.pck:721`, in bucla din `SCRIE_IN_ACT`: + +``` + pack_contafin.set_idfact(V_GCS); + /* 25.02.2013 : se face cumularea id_fact pe listarea din RC/RJ + PACK_CONTAFIN.SET_IDFACT(itemfact.dataact, itemfact.serie_act, itemfact.nract, itemfact.id_ctr);*/ + lnIdFact := get_idFact(); +``` + +buclat pe grupuri distincte `(NRACT, DATAACT, DATAIREG, SERIE_ACT, ID_CTR, ID_SET)` extrase din +`ACT_TEMP WHERE ID_FACT = -1` (`:713`) — deci `-1` e sentinela "acest rand cere un `ID_FACT` nou". + +`SCRIE_IN_ACT` insusi e apelat **doar pe drumul de scriere**, niciodata pe cel de stergere — +in `finalizeaza_scriere_act_rul` (`:8449-8459`): + +``` + if tnScrieSterge <> 2 then + pack_contafin.SCRIE_IN_ACT(user); + ... + else + pack_contafin.STERGE_DIN_ACT(user, lnAn, lnLuna, tnCod, tnIdUtil, tnModificareNota); + ... +``` + +Deci `SET_IDFACT` nu se atinge deloc la stergere — confirma ca azi cele doua operatii (stergere + +reemitere) sunt independente unele de altele in privinta ID_FACT. + +**Cine cheama `final_scriere_act_rul_local` / `SCRIE_IN_ACT` din VFP**: acelasi fisier +`COMUN\programe\oscrie_in_fisiere.prg`, replicat identic in ~25-30 produse ROA (verificat prin grep +pe `*.prg` in tot `D:\ROA`): `ROAFACTURARE`, `ROACONT` (output), `ROAGEST`, `ROAIMOB`, `ROAEFACTURA`, +`ROADEVIZE`, `ROAVIN`, `ROACASA`, `ROASAL`, `ROAPRETURI`, `ROAOBINV`, `ROARESTAURANT`, +`ROACOMENZI`, `ROAAPROV`, `ROASITOP`, `ROASITFIN`, `ROADECL`, `ROASALSPEC`, `ROABAVERT`, +`ROACONIMPORT`, `ROAPRINT`, s.a. — plus doua variante care apeleaza `SCRIE_IN_ACT` direct, fara +wrapper-ul local: `CONTAFIN2ORA\VFP2ORA\Programe\oscrie_in_fisiere.prg:367`, +`ROADEFSALARII\COMUN\programe\oscrie_in_fisiere.prg:141`, +`ROARESTAURANTCONFIG\COMUN_ROA\programe\oscrie_in_fisiere.prg:135`. +Un al doilea import vechi (`COMUN\datemenu\xold\import_xdbf\oscrie_in_fisiere.prg`) apare in multe +produse dar e **comentat integral** (`*!*`) — inactiv. + +**Niciun apelant nu impune un `ID_FACT`.** Toti trimit `ID_FACT = -1` in `ACT_TEMP` (via +`sql_temp_insert` in `oscrie_in_fisiere.prg:126-137`, care copiaza direct campurile din cursorul VFP, +inclusiv `id_fact`) si lasa `SCRIE_IN_ACT` sa-l completeze din secventa. Confirmare suplimentara in +`COMUN\clase\omodificari.vc2:4131-4132`: `"daca se schimba partenerul, se pune id_fact = -1 in loc +de 0 pentru a se putea genera un nou id_fact"` — exact sentinela pe care se bazeaza bucla din +`SCRIE_IN_ACT`. + +### A.3 — Variabila/parametru existent pentru un ID_FACT dorit + +**Nu exista azi.** `PACK_CONTAFIN` are o variabila de pachet `nIdFact` (setata de `SET_IDFACT`, +citita de `GET_IDFACT`), dar e scrisa neconditionat de fiecare apel al lui `SET_IDFACT` — nu poate +fi folosita ca "intrare" fara sa se schimbe corpul procedurii. Nu exista niciun `nid_...` global, nici +alt parametru de sesiune care sa transmita un `ID_FACT` dorit lui `SET_IDFACT` sau lui `SCRIE_IN_ACT`. +Ar trebui adaugata o variabila noua de pachet (ex. un "ID_FACT fortat", implicit `NULL`), citita +**doar** in corpul activ al lui `SET_IDFACT`, consumata si resetata la prima folosire — vezi C.8. +Acelasi diagnostic e deja notat in planul de proiect, `plan_13_unificare_formular_facturare.md:1717-1719`: +`"ID_FACT. Se citeste inainte de stergere ... si se impune documentului nou, printr-un comutator +folosit numai de regenerare. SET_IDFACT nu se schimba neconditionat — e cod comun intregii suite."` + +B. DOCUMENTE la reemitere cu acelasi ID_FACT +---------------------------------------------- + +### B.4 — INSERT sau UPDATE/MERGE azi + +`PACK_CONTAFIN.pck:788-817`, in interiorul buclei din `SCRIE_IN_ACT`, cod activ (INSERT simplu): + +``` + -- SCRIE IN DOCUMENTE + lnTvaIncasare := case when itemfact.tva_incasare > 0 then 1 else 0 end; + -- 25.02.2013 : am repus insertul pentru ca se face cumularea id_fact pe listarea din RC/RJ + INSERT INTO DOCUMENTE + (ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT, DATAACT, DATAIREG, ID_CTR, ID_SET) + VALUES + (lnIdFact, lnID_Util, LD_DATAORA, lnTvaIncasare, itemfact.serie_act, itemfact.nract, + itemfact.dataact, itemfact.dataireg, decode(itemfact.id_ctr, 0, NULL, itemfact.id_ctr), + itemfact.id_set); + /* + -- modificare ROACONT v 2.4.0 (27.12.2012): am adaugat serie_act, nract, dataact + -- modificare 21.02.2013: am adaugat id_ctr + MERGE INTO DOCUMENTE A + USING (SELECT lnIdFact as ID_DOC, itemfact.serie_act as SERIE_ACT, itemfact.nract as NRACT, + itemfact.dataact as DATAACT, + decode(itemfact.id_ctr, 0, NULL, itemfact.id_ctr) AS ID_CTR + FROM DUAL) B + ON (A.ID_DOC = B.ID_DOC) + WHEN NOT MATCHED THEN + INSERT (ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT, DATAACT, ID_CTR) + VALUES (B.ID_DOC, lnID_Util, LD_DATAORA, lnTvaIncasare, B.SERIE_ACT, B.NRACT, B.DATAACT, B.ID_CTR);*/ +``` + +Deci **azi e mereu un INSERT nou** — pe drumul normal (ID_FACT vine din secventa, mereu inexistent +in `DOCUMENTE`) asta e corect, dar pentru cazul S9 (ID_FACT reutilizat, deja existent — chiar si +soft-sters) INSERT-ul ar da eroare de unicitate (vezi B.5). Varianta `MERGE` comentata are doar +`WHEN NOT MATCHED` — daca s-ar reactiva neschimbata, un `ID_DOC` deja existent ar fi pur si simplu +ignorat (fara `WHEN MATCHED`), documentul reemis ramanand cu randul vechi din `DOCUMENTE` +(cu `TVA_INCASARE`/`ID_SET` nemodificate si, mai grav, cu `STERS` neresetat — vezi B.6). + +### B.5 — Constrangere de unicitate pe DOCUMENTE + +DDL gasit in `D:\ROA\DATABASE\ALTELE\Creare_server_scripturi\FirmaNoua\fn_script.sql:5162-5198`: + +``` +CREATE TABLE "DOCUMENTE" ("ID_DOC" NUMBER(20, 0) NOT NULL ENABLE, "DATAORA" DATE NOT NULL ENABLE, + "ID_UTIL" NUMBER(5, 0) NOT NULL ENABLE, "STERS" NUMBER(1, 0) NOT NULL ENABLE, + "DATAORAS" DATE, "ID_UTILS" NUMBER(5, 0) NOT NULL ENABLE) ... +... +CREATE UNIQUE INDEX "PK_DOCUMENTE" ON "DOCUMENTE" ("ID_DOC") ... +ALTER TABLE "DOCUMENTE" ADD CONSTRAINT "PK_DOCUMENTE" PRIMARY KEY ("ID_DOC") USING INDEX ... ENABLE +``` + +**Da: `ID_DOC` e PRIMARY KEY, unic.** (Coloanele `SERIE_ACT/NRACT/DATAACT/DATAIREG/ID_CTR/ID_SET/ +TVA_INCASARE` din INSERT-ul de la B.4 nu apar in acest script — script vechi de firma noua, +probabil tabela a fost alterata ulterior cu `ALTER TABLE ADD COLUMN`; constrangerea de PK insa e +stabila si nu are motiv sa fi fost scoasa.) Un `INSERT INTO DOCUMENTE (ID_DOC=...)` cu o valoare de +`ID_DOC` care exista deja (chiar si `STERS=1`) arunca `ORA-00001: unique constraint (PK_DOCUMENTE) +violated`. Nu am gasit alta constrangere de unicitate pe `DOCUMENTE`; nici FK de la `ACT.ID_FACT` +catre `DOCUMENTE.ID_DOC` (`ACT.ID_FACT` e doar indexat, `IDX_ID_FACT`, `fn_script.sql:1637`, fara +constrangere de integritate referentiala gasita) — deci reinserarea de randuri in `ACT` cu +`ID_FACT` reutilizat **nu** e blocata de vreo constrangere pe `ACT` insusi; blocajul e exclusiv pe +`DOCUMENTE`. + +### B.6 — Randurile vechi la stergere: fizic sterse, STERS=1, sau raman + +**Soft-delete pur, pe toate cele patru tabele relevante — nimic nu se sterge fizic.** + +`STERGE_DIN_ACT` (`PACK_CONTAFIN.pck:1815-1859`), apelata din `finalizeaza_scriere_act_rul` cand +`tnScrieSterge = 2`: + +``` + PROCEDURE STERGE_DIN_ACT(V_GCS VARCHAR2, tnAn in number, tnLuna in number, tnCod in number, + tnId_utils in number, tnTip IN NUMBER) is + -- tnTip : 0 = modificare ; 1 = stergere + ... + BEGIN + ... + UPDATE /*+ index(ACT IDX_COD) */ ACT + SET STERS = 1, DATAORAS = LD_DATAORA, ID_UTILS = lnId_util + WHERE COD = tnCod and an = tnAn and luna = tnLuna; + + IF lnTip = 1 THEN + UPDATE DOCUMENTE + SET STERS = 1, ID_UTILS = LNID_UTIL, DATAORAS = LD_DATAORA + WHERE ID_DOC IN + (SELECT /*+ index(ACT IDX_COD) */ DISTINCT ID_FACT + FROM ACT + WHERE COD = tnCod and an = tnAn and luna = tnLuna + AND ID_FACT <> 0 + and ID_SET not in (90501, 90021) + AND NOT (SCD = '4426' AND SCC = '4428') + AND NOT (SCD = '4428' AND SCC = '4427')); + END IF; + + update act_temp set suma = -suma, suma_val = -suma_val; + END STERGE_DIN_ACT; +``` + +`ACT` -> `STERS=1` (randurile raman fizic, pe acelasi `COD`). Cand `tnTip=1` (stergere, nu simpla +modificare), **`DOCUMENTE` primeste si el `STERS=1`**, pentru toate `ID_FACT` distincte gasite pe +acel `COD` in `ACT` (cu exceptiile de mai sus pentru seturi/conturi speciale). Nicaieri nu am gasit +un `UPDATE DOCUMENTE ... SET STERS = 0` — cautare exhaustiva in `PACK_CONTAFIN.pck` (`grep -i +"UPDATE DOCUMENTE"`) a dat un singur rezultat, cel de mai sus. Deci nu exista azi niciun mecanism +care sa "reinvie" un rand din `DOCUMENTE`. + +La fel, `STERGE_DIN_RUL` / `STERGE_DIN_RUL_OBINV` (`:1861-1893`) fac `UPDATE ... SET STERS = 1` pe +`RUL`/`RUL_OBINV`, fara stergere fizica. + +Pentru `IREG_PARTENERI` si `JV2007` situatia e diferita in mod util pentru S9: acestea nu sunt copii +1:1 ale documentului, ci **agregate recalculate prin `MERGE`** pe chei de business (an, luna, cont / +`ID_FDOC`, `ID_FACT`, `NRACT`, `SERIE_ACT`, `DATAACT`, `DATAIREG`, `ID_PART`, ...) — vezi +`SCRIE_JV_2007` (`:3328-3373+`) si `SCRIE_IN_IREG_PARTENERI`/`EXECUTA_SCRIE_IN_IREG` (`:7030+`, +`:7607+`). Ambele sunt apelate **si pe drumul de scriere, si pe cel de stergere** +(`finalizeaza_scriere_act_rul:8495-8578`, fara conditie pe `tnScrieSterge` in afara sursei: `act_temp` +la scriere/stergere, `act` la refacere). La stergere, `sterge_document` (`:7699-8118`) reinsereaza in +`ACT_TEMP` copii ale randurilor vechi din `ACT`/`RUL`/`RUL_OBINV`, **cu acelasi `ID_FACT` ca inainte** +(coloana `id_fact` e copiata direct din sursa, nu resetata la `-1`), asa ca `MERGE`-ul din +`SCRIE_JV_2007`/`SCRIE_IN_IREG_PARTENERI` recalculeaza (compenseaza) exact randul cu acel `ID_FACT` +— nu creeaza duplicate. Deci reutilizarea `ID_FACT`-ului **nu produce dubluri in `JV2007`/ +`IREG_PARTENERI`**; problema e strict izolata la `DOCUMENTE`. + +Nota din plan, care confirma independent aceasta zona de risc: +`plan_13_unificare_formular_facturare.md:1737`: `"De verificat si daca DOCUMENTE primeste un al +doilea rand pe acelasi ID_DOC sau il refoloseste."` — raspunsul, dupa aceasta cercetare: **niciuna +din cele doua, in starea actuala a codului — ar arunca eroare de unicitate**, nu ar produce liniste +tacuta cu duplicat sau refolosire. + +C. Verdictul care conteaza +---------------------------- + +### C.7 — Se poate reemite cu acelasi ID_FACT fara sa se strice nimic? + +**DA, dar cu conditii** — niciuna dintre ele nu e implementata azi: + +1. **Nu folosi varianta comentata a lui `SET_IDFACT` ca atare.** Cautarea `STERS=0` nu gaseste + documentul (deja sters soft inainte de rescriere) si e vulnerabila la coliziuni pe chei de + business partajate cu alt document. In loc de cautare, ID_FACT-ul trebuie **citit explicit + inainte de stergere** (VFP il are deja in cursorul documentului editat) si **transmis** pe drumul + de scriere — exact ce zice planul S9. +2. **`SET_IDFACT` are nevoie de o cale de intrare noua, inerta implicit.** O variabila de pachet + noua (ex. "ID_FACT fortat", `NULL` default) + o procedura noua de setat-o explicit inainte de + scriere; corpul activ al `SET_IDFACT` verifica intai variabila, o consuma si o reseteaza; daca e + `NULL` (cazul general — toate celelalte ~25-30 apeluri din suita), comportamentul e identic cu + azi (secventa). Nu se toarna in semnatura existenta `SET_IDFACT(V_GCS)`, ca sa nu se schimbe + apelul de la niciun alt caller. +3. **Scrierea in `DOCUMENTE` trebuie sa devina un upsert real, cu `WHEN MATCHED`.** Nici INSERT-ul + activ, nici MERGE-ul comentat (fara `WHEN MATCHED`) nu revigoreaza un rand soft-sters. E nevoie de + un `MERGE ... WHEN MATCHED THEN UPDATE SET STERS = 0, DATAORA = ..., ID_UTIL = ..., TVA_INCASARE + = ..., SERIE_ACT = ..., NRACT = ..., DATAACT = ..., DATAIREG = ..., ID_CTR = ..., ID_SET = ...` + pe langa `WHEN NOT MATCHED THEN INSERT` (cazul normal, ID_FACT nou din secventa). +4. **Succesiunea corecta**, in aceeasi tranzactie (deja planificata in S9 la nivel de VFP — vezi + `plan_13...md:1713-1719`, mutarea stergerii in tranzactia deschisa de scriere): sterge documentul + vechi (soft-delete, ca azi) -> seteaza variabila de la punctul 2 cu `ID_FACT`-ul citit inainte de + stergere -> scrie documentul nou prin drumul obisnuit (`ACT_TEMP` cu `ID_FACT = -1` ca de obicei, + dar acum grupul respectiv va primi ID_FACT-ul fortat in loc de unul nou) -> commit doar dupa ce + ambele operatii au avut succes; orice eroare -> rollback total, documentul vechi ramane intact. + +- **NU** (fara aceste conditii): `INSERT INTO DOCUMENTE` cu `ID_DOC` reutilizat, cat timp randul vechi + e inca prezent cu `STERS=1`, arunca `ORA-00001` pe `PK_DOCUMENTE` — asta e dovada, nu presupunere + (B.5 + B.4). + +### C.8 — Suprafata de risc pentru restul suitei + +- **~25-30 cai de apel** trebuie sa ramana neafectate: toate copiile `COMUN\programe\ + oscrie_in_fisiere.prg` din fiecare produs ROA (listate in A.2), plus cele 3 variante care apeleaza + `SCRIE_IN_ACT` direct. Toate trec prin acelasi punct final: `SET_IDFACT(V_GCS)` in + `PACK_CONTAFIN.pck:3037`. +- **Garantia structurala propusa**: variabila de pachet noua, default `NULL`/inert, citita **doar** + in interiorul corpului activ al lui `SET_IDFACT` (nu in semnatura, nu in vreun parametru propagat + de apelanti); se seteaza explicit **doar** de codul de regenerare din ROAFACTURARE, chiar inainte + de a incepe scrierea documentului reemis, si se consuma/reseteaza la prima citire (fie in + `SET_IDFACT`, fie la finalul tranzactiei, ca sa nu "scurgi" valoarea catre urmatoarea scriere + neinrudita din aceeasi sesiune Oracle — relevant daca `goExecutor`/conexiunea e reutilizata intre + operatii ale aceluiasi utilizator in acelasi produs). Cu default inert si citire localizata strict + in corpul lui `SET_IDFACT`, toate celelalte ~25-30 cai raman byte-for-byte identice cu azi — nu e + nevoie sa se verifice fiecare apelant individual, garantia e la sursa (variabila neinitializata = + comportament vechi). +- **Riscul real nu e in `SET_IDFACT`, ci in `DOCUMENTE`.** Schimbarea INSERT->MERGE cu `WHEN MATCHED` + la `PACK_CONTAFIN.pck:796-817` e riscanta pentru toata suita in alt sens: `WHEN MATCHED` s-ar + declansa doar cand `ID_DOC` (generat din secventa) coincide cu unul existent — ceea ce, pe drumul + normal, nu se intampla niciodata (secventa e monoton crescatoare, nu se repeta), deci practic + ramura noua e inerta pentru toti apelantii actuali si activa doar cand variabila de la C.8 a fost + setata explicit. Totusi orice modificare la acest INSERT/MERGE e in cod comun apelat de toata + suita si trebuie testata pe cel putin un ciclu normal de scriere (fara variabila fortata) in + fiecare produs care scrie facturi/note, nu doar in ROAFACTURARE. + +Ramas de verificat pe baza vie +-------------------------------- +- Structura curenta reala a tabelei `DOCUMENTE` (DDL-ul citat e dintr-un script vechi de "firma + noua"; coloanele `SERIE_ACT/NRACT/DATAACT/DATAIREG/ID_CTR/ID_SET/TVA_INCASARE` folosite de + INSERT-ul activ nu apar in acel DDL — probabil adaugate ulterior prin `ALTER TABLE`). De rulat pe + baza vie: `SELECT column_name, nullable FROM user_tab_columns WHERE table_name = 'DOCUMENTE' + ORDER BY column_id;` si `SELECT constraint_name, constraint_type FROM user_constraints WHERE + table_name = 'DOCUMENTE';` — sa se confirme ca `PK_DOCUMENTE` e inca activa si ca nu exista alta + constrangere aparuta intre timp. +- Daca exista deja documente reale in productie unde `DOCUMENTE.ID_DOC` a fost, intr-un fel sau + altul, reutilizat (ex. printr-o interventie manuala) — de verificat cu + `SELECT id_doc, count(*) FROM documente GROUP BY id_doc HAVING count(*) > 1;` (ar trebui sa fie + gol, dat fiind PK-ul, dar merita confirmat inaintea oricarei schimbari). +- Comportamentul exact al `finalizeaza_stergere_nota`/`finalizeaza_modificare_nota` (`:8601+`, + nu au fost citate integral aici) fata de `ID_FACT`/`ID_FACTD` — relevante daca reemiterea schimba + seria/numarul, nu doar continutul (cazul S9 "de baza" e cel mai simplu: acelasi NRACT/SERIE_ACT). +- Comportamentul lui `EXECUTA_SCRIE_TVA`/`SCRIE_JC_2007` (mentionate la `:8504-8540`, cazul an < + 2007 sau JC) fata de reutilizarea ID_FACT — nu au fost verificate in detaliu, doar `SCRIE_JV_2007`. +- Nu am putut testa efectiv (fara acces la baza vie) daca `ORA-00001` e chiar eroarea ridicata de + Oracle in acest scenariu exact — e o deductie directa din DDL (PK unic + INSERT simplu pe coloana + respectiva), dar merita o rulare de proba pe o baza de test inainte de a proiecta solutia finala. diff --git a/docs/cercetare/idpol_comanda_contract.md b/docs/cercetare/idpol_comanda_contract.md new file mode 100644 index 0000000..3b108a2 --- /dev/null +++ b/docs/cercetare/idpol_comanda_contract.md @@ -0,0 +1,553 @@ +# De unde vine SCC-ul pe facturile din comandă/contract — și dacă modelul se poate refolosi pentru #13 + +Cercetare read-only, pe cod (VFP text + export `PACK_FACTURARE` curent) și pe baza vie (`MARIUSM_AUTO` +pe `ROA_CENTRAL`, doar `SELECT`, 10.08.2026). Nu modifică nimic — nici `pack_facturare`, nici +`ofacturare_comun.vc2`, nici `ofacturare_editare.prg`. + +Continuă `docs\cercetare\coresp_cont_venchelt.md` (secțiunea 9) și `docs\plan_13_unificare_formular_facturare.md` +(J-quater), care stabiliseră deja: FACT-024 verifică apartenența articolului la politica **de pe linie**, +`id_pol` e parametru per-linie trimis de VFP (`V_ID_POL`, `adauga_articol_factura`), și pe contract +articolul „liber" nu e de fapt fără politică — vine deja cu una atașată prin `cursor_contract`/`cursor_preturi`. + +**Runda 2 (reformulare Marius):** prima trecere de mai jos (secțiunile D–C, păstrate ca dovadă) presupunea +că întrebarea era „de unde vine `id_pol`". Marius a corectat premisa: el chiar emite facturi pe bază de +document care **nu au** politică de preț și notă atașată **prin lanțul obișnuit** — ceea ce contrazicea +concluzia inițială („`id_pol` gol → `FACT-024`, mereu"). Secțiunea 0 de mai jos reconciliază contradicția, +pe cod și pe date reale, și e livrabilul principal al acestei runde. Sursa SQL folosită în ambele runde: +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (823.337 octeți — fișierul +cu același nume din `docs\` a fost șters în timpul sesiunii anterioare; numerele de linie de mai jos sunt +verificate direct pe fișierul din `SCRIPTURI_CLAR`, nu preluate din plan). + +--- + +## 0. Reconcilierea contradicției — Marius are dreptate, dar mecanismul nu e cel presupus în runda 1 + +**Verdict, cu citat**: `id_pol` `NULL` **tot** blochează `contabilizeaza_articol` necondiționat — asta nu +s-a schimbat, e reconfirmat mai jos. Ce s-a stabilit greșit în runda 1 e presupunerea că **orice** linie de +document trece prin `contabilizeaza_articol`. **Nu e adevărat**: pachetul are cel puțin **două rute +paralele**, folosite azi în producție (confirmate pe date reale), care scriu o notă contabilă de vânzare +**fără `id_pol` și fără `CRM_POLITICI_PRETURI`** — pentru că nu apelează deloc `contabilizeaza_articol`. + +### 0a. Reconfirmare: `contabilizeaza_articol` nu tolerează `id_pol NULL`, în nicio ramură + +```sql +-- ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7275-7302 (inceputul functiei, necondiționat) +BEGIN + BEGIN + SELECT COMPUS, ID_POL_ART + INTO V_COMPUS, V_ID_POL_ART + FROM VCRM_POLITICI_PRET_ART + WHERE ID_ARTICOL = detalii_articol.id_articol + AND ID_POL = detalii_articol.id_pol; -- id_pol NULL => X=NULL => NO_DATA_FOUND, mereu + EXCEPTION + WHEN NO_DATA_FOUND THEN + ... SELECT NUME_LISTA_PRETURI INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol; + RAISE_APPLICATION_ERROR(-20000, '... (FACT-024)'); +``` +Nu există niciun `IF detalii_articol.id_pol IS NULL THEN ...` înainte de acest bloc — e primul lucru pe +care funcția îl face, necondiționat, pentru orice apel. **Bonus, deja semnalat în runda 7** (`plan_13... +md:1451-1457`, reconfirmat aici pe fișierul curent): al doilea `SELECT` din `EXCEPTION` (cel care aduce +`lcPolitica` pentru mesaj) filtrează tot pe `id_pol = detalii_articol.id_pol`, deci și el dă `NO_DATA_FOUND` +când `id_pol` e `NULL` — utilizatorul nu vede mesajul formatat „FACT-024", ci un `ORA-01403` brut, +necaptat. În ambele cazuri: **eroare, tranzacție întreruptă, nimic scris**. Deci dacă o linie chiar ajunge +la `contabilizeaza_articol` cu `id_pol` gol, nu există azi nicio cale silențioasă de succes. + +### 0b. Ce am găsit real, pe date vii — 384 din 1113 linii `VANZARI_DETALII` active (34%) au `ID_POL` `NULL` + +```sql +select count(*), sum(case when id_pol is null then 1 else 0 end) from vanzari_detalii where sters=0; +-- 1113 384 +``` +Deci liniile facturate fără `id_pol` **există masiv** în producție — nu e un caz exotic. Împărțite pe +`VANZARI.TIP`: + +| `TIP` | linii fără `id_pol` | din ele, rânduri `ACT` cu `SCC` populat | interpretare | +|---|---|---|---| +| `-12` | 97 | 588/604 | **ROAAUTO** (confirmat, `tip=-12` e explicit ROAAUTO — `plan_13...md:1710,1735`) | +| `-1..-13` (restul) | ~140 | majoritar populat | variante storno/aviz ale documentelor de mai jos, nu cercetate individual | +| `2` (**contract**) | 19 | populat, vezi 0c | facturare pe bază de contract, **rată/scadențar**, nu articol | +| `1` (listă prețuri) | 1 | — | un singur caz, neexplorat separat | +| `51` | 121 | 179/275 (cumulat) | tip necunoscut în codul VFP citit — posibil retail/POS, neidentificat | +| **`3` (comandă, ROAFACTURARE)** | **0 din 66** | — | **niciun caz** — vezi 0d | + +`select count(*), sum(case when id_pol is null then 1 else 0 end) from vanzari_detalii d join vanzari v on v.id_vanzare=d.id_vanzare where d.sters=0 and v.sters=0 and v.id_comanda is not null` → **66 / 0**. Pe ruta +`COMENZI` a lui ROAFACTURARE (secțiunea A de mai jos), **`id_pol` nu e niciodată gol, pe datele disponibile.** +Contradicția lui Marius nu se reproduce pe acest obiect precis. + +### 0c. Mecanismul găsit: `contabilizeaza_rata` — notă contabilă direct din contract, fără politică, fără `id_pol` + +Contractele cu facturare pe **rate/scadențar** (`CONTRACTE.OPT_FACTURARE IN (1,2)`) nu trec liniile prin +`contabilizeaza_articol` — trec prin o funcție **separată**, `contabilizeaza_rata`: +```sql +-- ff_...:7549-7597 +FUNCTION contabilizeaza_rata(detalii_rata VANZARI_DETALII_TEMP%ROWTYPE) RETURN NUMBER IS +BEGIN + BEGIN + SELECT NVL(B.ID_VENCHELT, pack_facturare.nid_venchelt), ..., C.SCD, C.ASCD, C.SCC, C.ASCC, C.CU_TVA, C.IN_VALUTA + INTO ... + FROM CONTRACTE A + LEFT JOIN CRM_NOTE_VANZARI B ON A.ID_NOTA = B.ID_NOTA -- <- nota vine din CONTRACTE.ID_NOTA, NU din id_pol + LEFT JOIN NOTE_CONTABILE C ON B.ID_SET = C.ID_SET + WHERE A.STERS = 0 AND A.ID_CTR = detalii_rata.id_ctr AND B.STERS = 0; + EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE_APPLICATION_ERROR(-20000, 'Nu sunt configurate notele de vanzare pentru acest contract!'); + END; + ... + V_INCASAT_CALCUL := V_INCASAT_CALCUL + pack_facturare.scrie_nota(..., V_SCD, V_ASCD, V_SCC, V_ASCC, ...); +``` +`CONTRACTE` are coloană proprie `ID_NOTA` (confirmat pe schema vie: `NUMBER`, nullable) — **contractul însuși +duce nota contabilă**, ales o singură dată la configurarea lui, independent de orice `id_pol`/politică de +preț per articol. Confirmă exact tiparul din `cursor_contract` (secțiunea B mai jos, ramura „rată" a +cursorului, `ff_...:2837-2893`): `NULL as id_articol, ..., NULL AS ID_POL` — liniile de rată **nu au +niciodată `id_pol`, prin construcție**, pentru că nu sunt articole din nomenclator, sunt rate de scadențar. + +**Confirmat pe o factură reală** (`MARIUSM_AUTO`, 10.08.2026): +```sql +-- id_vanzare 360, tip 2 (contract), linia din VANZARI_DETALII: id_articol NULL, id_pol NULL +-- ACT pentru id_fact=5039903: +-- ID_ACT SCD ASCD SCC ASCC SUMA EXPLICATIA +-- 102204 4111 11 704 300 RATA 1 +-- 102205 4111 11 4427 57 TVA RATA 1 +``` +O factură reală, cu o linie fără `id_articol` și fără `id_pol`, **cu notă contabilă scrisă corect** (`SCD +4111`, `SCC 704`) — exact dovada cerută. Mecanismul: **nota nu vine prin politică deloc**, vine direct din +`CONTRACTE.ID_NOTA`, o singură dată per contract, la configurare — un tipar „un document duce o notă fixă", +nu „fiecare linie duce o politică de preț". + +### 0d. De ce nu se reproduce pe `COMENZI` (ROAFACTURARE): tabelul nu are echivalentul lui `ID_NOTA` + +```sql +select column_name from user_tab_columns where table_name='COMENZI'; +-- ID_COMANDA, NR_COMANDA, DATA_COMANDA, ID_PART, ID_GESTIUNE, ID_SECTIE, ID_SECTIE2, ID_CTR, ... (29 coloane) +-- nicio ID_NOTA, nicio ID_POL, nicio coloana de contabilizare +``` +`COMENZI` (antetul) nu duce nicio informație contabilă — singura legătură posibilă e `ID_CTR` (dacă +comanda vine dintr-un contract). `COMENZI_ELEMENTE.ID_POL` rămâne singurul canal, și e `NOT NULL` (secțiunea +A). Deci pe **acest** obiect, mecanismul „notă fără politică" descris de Marius **nu există** — dacă +experiența lui vine de aici, ar însemna fie (a) o versiune de bază/schemă diferită de `MARIUSM_AUTO`, fie +(b) o factură pe care a facturat-o efectiv prin ruta **contract cu rate** (secțiunea 0c) sau prin **ROAAUTO** +(`tip -12`, deviz — tipar deja documentat în `coresp_cont_venchelt.md` §9b: `adauga_articol_factura_deviz` +→ `scrie_in_vanzari`, fără `contabilizeaza_articol`, cu notă scrisă separat de fiecare produs), și a numit-o +generic „factură pe bază de comandă". **Nu pot decide între aceste ipoteze fără să întreb** — dovada de cod +și de date arată clar CARE mecanisme există și niciunul nu e pe `COMENZI` propriu-zis. + +### 0e. Variantele din brief, verdict pe fiecare + +- *„liniile de comandă primesc totuși un `id_pol` de undeva"* — **confirmat, dar nu ascuns**: da, primesc, + documentat deja în secțiunea A/D veche, e vizibil în UI (`v_articole`), nu explică „fără politică". +- *„`contabilizeaza_articol` nu e apelată pe ruta comandă, ci altă procedură"* — **fals pentru `COMENZI`** + (`ofacturare.prg:292-293` → `cursor_comanda` → `crsarticole` → `do_scrie_articole` → `adauga_articol_factura` + → `contabilizeaza_articol`, ruta normală); **adevărat pentru contract-cu-rate** (`contabilizeaza_rata`) și + pentru ROAAUTO/ROAACNPRO (proceduri proprii, deja documentate). +- *„există o ramură de fallback în `contabilizeaza_articol` care ajunge la notă fără politică"* — **fals**, + reconfirmat la 0a; funcția nu are asemenea ramură, blochează necondiționat. +- *„FACT-024 se ridică doar când `id_pol` e nenul dar articolul nu e membru, `id_pol` nul merge pe alt + drum"* — **fals ca „alt drum în aceeași funcție"**; **adevărat ca „alt drum = altă funcție"** (0c/0d). + +--- + +## D. Răspunsul la întrebarea lui Marius, în cinci rânduri (actualizat după reconciliere) + +**Depinde care „comandă".** Pe obiectul `COMENZI`/`COMENZI_ELEMENTE` al ROAFACTURARE (facturare „pe bază +de comandă", tip 3), SCC-ul vine tot din `NOTE_CONTABILE.SCC` prin `id_pol` — **`id_pol` nu lipsește +niciodată**, e coloană `NOT NULL` (confirmat pe schemă și pe date: 0 din 6868/66 linii fără el), populată la +adăugarea articolului dintr-o listă deja restrânsă la o singură politică (`v_articole`/`com_vpreturi_utilizator`), +politică aleasă o dată pe comandă. **Dacă experiența lui Marius vine din facturarea contractelor cu rate +(scadențar, `OPT_FACTURARE IN (1,2)`) sau din ROAAUTO**, atunci da, există o rută reală, azi în producție, +care scrie nota **fără nicio politică de preț**: pe contracte-cu-rate, nota vine direct din +`CONTRACTE.ID_NOTA` (o coloană pe contract, aleasă o singură dată la configurarea lui, independentă de orice +`id_pol` per linie) — `contabilizeaza_rata`, nu `contabilizeaza_articol`. Pe ROAAUTO/ROAACNPRO, nota o scrie +o procedură proprie a produsului (`pack_acn.salveaza_regdoc` etc.), tot în afara lanțului `CRM_POLITICI_PRETURI`. +**Aceste rute nu sunt „`id_pol` gol tratat cu grijă" — sunt căi care nu ating deloc `id_pol`/`contabilizeaza_articol`.** +Pe contract-cu-**articole** (nu rate), tiparul rămâne cel din runda 1: `CTR_ARTICOLE.ID_POL_ART` → membru +al unei politici reale, ca și pe comandă. + +## E. Se poate refolosi „același model" pentru articolul ad-hoc din #13? + +**Parțial. Mecanismul de jos (RPC + „apartenența e garantată prin construcția listei") e direct +transplantabil — dar e deja exact ce propune rețeta în 4 pași din J-quater, nu o alternativă mai simplă +la ea. Partea care ar simplifica cu adevărat lucrurile — „ia pur și simplu politica curentă și adaugă +articolul în ea" — e decizia 24, deja retrasă de Marius, din motive care rămân valabile.** + +Detaliat: + +1. **Ce arată comandă/contract, tehnic**: operatorul nu alege niciodată `id_pol` pentru un articol anume — + alege **o politică** (una dintre cele la care are drept, via `UTILIZATORI_ROL_INTERN` → + `POLITICI_GRUPURI` → `CRM_POLITICI_PRETURI`), iar din acel moment orice articol afișat spre alegere e + deja membru al ei (`com_vpreturi_utilizator`/`cPol_pret_art` filtrează `id_pol_art`/`id_pol IS NOT NULL` + la sursă). Verificarea FACT-024 „trece" pe comandă/contract **nu pentru că ar exista un fallback** — ci + pentru că nu există cale, în UI, de a ajunge la un articol care nu e deja membru. E o garanție de + construcție a listei, nu o validare separată. +2. **Acest tipar exact e deja documentat, ca fezabil, în `coresp_cont_venchelt.md` secțiunea „e)" / J-quater + punctul 3** — RPC `pack_preturi.adauga_politica_pret_art` (deja folosit în producție, + `ofacturare.vc2:15551-15587`) pentru „asigură apartenența", plus trimiterea lui `id_pol` neschimbat la + `adauga_articol_factura`. Nu e nimic nou de inventat aici — comanda/contractul confirmă că tiparul + „RPC + listă pre-filtrată" funcționează în producție de multă vreme, dar **planul A din J-quater deja îl + folosește**. Refolosirea nu elimină pasul, îl confirmă. +3. **Ce NU rezolvă modelul comandă/contract e alegerea SCC-ului corect per articol** — pe comandă/contract + politica e **una singură, fixă**, aleasă pentru tot documentul/sesiunea, cu un singur `SCC` (orice ar + fi el) pentru toate articolele adăugate așa. Aplicat identic pe `caut_articol` (restricționează + rezultatele căutării din nomenclator la politica curentă a operatorului), rezultatul ar fi + **exact ceea ce există deja azi ca „Cauta in lista de preturi…"** — nu rezolvă cazul pe care S4g/#13 îl + cere explicit: un articol care **nu e membru al niciunei politici** (`plan_13...md:1711-1716`: „din + nomenclator se oferă și articole gestionabile, și negestionabile"; „un articol fără `ID_POL` nu ajunge + la cont gol, ci la eroare"). +4. **„Alege o politică fixă și pune articolul în ea" e literalmente decizia 24, deja retrasă** + (`plan_13...md:926-930`): *„Decizia 24: articolul ales din nomenclator primește `id_pol`-ul unei + politici de pret implicite. RETRASĂ în runda 8... prețul lui era o întrebare de configurare cu + contabilul (care politică, cu ce `SCC`, una sau mai multe) și un cont de venit uniform pentru orice + articol adăugat așa. Marius a ales în loc derivarea contului. Nu se reintroduce."* Motivul retragerii + nu s-a schimbat: o politică unică (fie ea „a operatorului", ca pe comandă) dă un **singur** cont de + venit oricărui articol, indiferent de natura lui (marfă/producție/serviciu) — exact opusul deciziei 27, + care cere `SCC` diferențiat după clasa contului de gestiune al articolului (`707`/`711`/`702`/`703`/ + `7015`/`7018`/`704`). +5. **Deci ce rămâne de făcut e exact pasul central din rețeta în 4 pași**: nu „cum trimit un `id_pol` + valid la Oracle fără să ating `pack_facturare`" (asta e deja rezolvat, și demonstrat funcțional de + comandă/contract) — ci „**care** politică, cu **care** `SCC`, pentru **acest** articol anume" — acolo + comanda/contractul nu ajută, pentru că ele nu rezolvă niciodată această întrebare: politica le vine + gata aleasă, de operator, o singură dată, nu calculată per articol. + +**Verdict la „mă gândeam la același model" (dacă modelul e comandă/contract-cu-articole)**: da, la nivel de +mecanism (RPC de inserare + membership garantat prin listă filtrată) — dar acel nivel era deja acoperit de +planul A/decizia 27-bis. Nu, la nivelul la care ar simplifica ceva („nu mai calculăm SCC per articol, luăm +politica gata aleasă") — pentru că acela e exact decizia 24, respinsă cu un motiv care rămâne valabil. + +### E-bis. Dar există un al treilea model, mai simplu — cel din `contabilizeaza_rata` (secțiunea 0c) + +Reconcilierea de la secțiunea 0 arată un tipar pe care raportul din runda 1 nu-l luase în calcul: **un +document poate duce el însuși o notă contabilă fixă, complet în afara `CRM_POLITICI_PRETURI`, iar +`pack_facturare` are deja o funcție care face exact asta** (`contabilizeaza_rata`, sursa `CONTRACTE.ID_NOTA`). +Aplicat la #13, ideea ar fi: nu mai deriva `SCC` per articol și nu mai caut/construi o politică tehnică — scrie +nota direct, cu conturile calculate în VFP (regula deciziei 27: `CORESP_CONT_VENCHELT`/`NOM_ARTICOLE.CONT`/ +`704`), printr-o cale de scriere **paralelă**, ca și pentru rate. + +**De ce nu se poate fără să ating `pack_facturare`, și asta contează, dat fiind decizia 27-bis**: +- `contabilizeaza_rata` există deja, dar e legată strict de `VANZARI_DETALII_TEMP` cu semantică de „rată de + contract" (`detalii_rata.id_ctr` obligatoriu în interogare) — nu e apelabilă pentru un rând de articol + obișnuit (`id_articol` populat, `id_pol` gol) fără o funcție **nouă**, analogă, în `pack_facturare`. +- Sursa notei la rată e `CONTRACTE.ID_NOTA` — o coloană pe un document care **există deja** și **se + configurează o dată** (la crearea contractului). Pentru articolul ad-hoc de pe formularul unificat nu + există azi un asemenea „document-sursă cu notă fixă" — ar trebui inventat (ex. o coloană similară pe + antetul facturii, sau parametru nou la `adauga_articol_factura`) — tot cod nou în `PACK_FACTURARE`. +- **Constrângerea „pack_facturare rămâne neatins" (decizia 27-bis) exclude explicit acest drum.** Dacă + Marius e dispus s-o relaxeze, tiparul `contabilizeaza_rata` e un precedent real, mai simplu decât rețeta + în 4 pași — pentru că elimină complet nevoia unei „politici tehnice" și a RPC-ului de apartenență + (`pack_preturi.adauga_politica_pret_art`): SCC-ul ar merge direct în `scrie_nota`, fără ocolul prin + `CRM_POLITICI_PRET_ART`. Dacă nu, rămâne exact ce spunea verdictul din runda 1: rețeta în 4 pași (planul + A, tot în VFP) rămâne singura variantă compatibilă cu constrângerea. + +**Verdict final, actualizat**: modelul comandă/contract (runda 1) nu ajută, motivele rămân cele deja arătate. +Dar există un al doilea model real, comandă**-rate**/contract-rate, care **ar** ajuta — cu prețul de a +renunța la „`pack_facturare` neatins". E o decizie de produs pentru Marius, nu o concluzie tehnică unică — +raportul pune ambele opțiuni pe masă, cu costul lor exact. + +**Corecție importantă, din verificarea independentă de mai jos („Completare de la sesiunea principală"):** +politica `7 DISCOUNT` (870 linii de comandă, `SCD=667`/`SCC=4111`, notă `2 DISCOUNT`) e deja, în producție, +o **politică tehnică folosită doar ca rutare contabilă**, nu ca listă comercială — exact tiparul „o politică +per scop contabil" pe care planul A/decizia 27-bis îl propune (nu decizia 24, care era o politică unică +pentru *orice* articol ad-hoc, indiferent de natură). Deci precedentul operațional pentru „politică tehnică, +una per destinație contabilă" **există deja pe ruta comandă** — mai puternic decât `gnId_pol_pret_stoc` +(care e un singur cont pentru tot stocul). **Dar** aceeași verificare arată că garanția „apartenență prin +construcția listei" (punctul 1 de mai sus) **nu e etanșă pe date**: 37 din 6868 linii active de comandă au +un articol care nu (mai) e membru al politicii înregistrate pe linie — verificare, cauză și comportament la +facturare, deschise, vezi secțiunea finală. + +--- + +## A. Ruta COMANDĂ + +### A.1 — `cursor_comanda` selectează `ID_POL`? Da, direct de pe linia comenzii + +```sql +-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2996-3054 (ramura factură) +OPEN V_CURSOR FOR + SELECT ROWNUM as id_c, + A.ID_ARTICOL, + NULL AS LOT, + NULL as SERIE, + A.ID_POL, -- <- direct din COMENZI_ELEMENTE, nu derivat + A.ID_VALUTA, ... + FROM COMENZI_ELEMENTE A + LEFT JOIN CRM_POLITICI_PRET_ART B + ON A.ID_POL = B.ID_POL AND A.ID_ARTICOL = B.ID_ARTICOL + ... + WHERE A.STERS = 0 AND A.ID_COMANDA = V_ID_COMANDA ... +``` +(identic la `:3084-3140` pentru ramura aviz, `V_TIP > 20`). `A.ID_POL` e coloană directă pe +`COMENZI_ELEMENTE` — `LEFT JOIN CRM_POLITICI_PRET_ART B` de mai jos **nu** derivă `id_pol`, doar aduce +`DISCOUNT_UNITAR`/`PROC_TVAV` pentru acea combinație `(id_pol, id_articol)`. + +### A.2 — Unde e stocat: `COMENZI_ELEMENTE.ID_POL`, coloană `NOT NULL` + +Confirmat pe schema vie (`MARIUSM_AUTO`, `ROA_CENTRAL`, 10.08.2026): +```sql +select column_name, data_type, nullable from user_tab_columns where table_name='COMENZI_ELEMENTE'; +-- ID_POL NUMBER N <- NOT NULL +select count(*), sum(case when id_pol is null then 1 else 0 end) from comenzi_elemente where sters=0; +-- 6868 0 +``` +**`ID_POL` e obligatoriu la nivel de constrângere de tabel**, nu doar „de obicei populat" — și cele 6868 +linii active din baza vie confirmă 0 excepții. E o coloană de **linie**, nu de antet (nu există `ID_POL` +pe `COMENZI`). + +### A.3 — Cine îl pune acolo la crearea comenzii + +Nu e o alegere liberă per articol — e moștenit dintr-o **politică aleasă o singură dată pe comandă**: + +``` +COMUN\clase\ocomenzi.vc2:1849-1870 (do_modifica, la deschiderea/crearea comenzii) +Do Case + Case Inlist(loRec.interna,2,5) + If lnTip = 0 And Reccount('crscomanda_curenta')>0 + lnIdPol = id_pol && preia politica de pe comanda existenta + Else + loCauta = caut_politici_curente_utilizator() && cautare manuala, comanda noua + If !Empty(Nvl(loCauta.id_pol,0)) + lnIdPol = loCauta.id_pol + Else + Return + Endif + Endif + update_articole_politica(lnIdPol) + Case loRec.interna = 3 + update_articole_politica_comenzi([IDPOLITICAPRET]) && optiune implicita globala + Otherwise + update_articole_politica_comenzi([ID_LISTA_PRETURI_PV]) && optiune implicita globala +Endcase +``` +Deci: pentru un tip de comandă (`interna` 2/5) operatorul **caută explicit** o politică +(`caut_politici_curente_utilizator`); pentru celelalte tipuri, politica vine dintr-o **opțiune globală** +(`gnIdPoliticaPret`/`gnId_lista_preturi_PV`, citite în `extrage_optiuni_firma`, +`update_comenzi.prg:16-29`). Nu se moștenește de la client. + +Politica aleasă filtrează lista de articole disponibile de adăugat: +``` +COMUN\programe\update_comenzi.prg:31-60 (update_articole_politica) +select p.id_articol,p.id_pol,p.pret,... from com_vpreturi_utilizator p ... +where p.id_util = <> and p.id_pol = <> +``` +```sql +-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\01\ff_2026_01_21_01_FACTURARE.sql:3-30 +create or replace view com_vpreturi_utilizator as +select a.id_util, b.id_politica as id_pol, ..., d.id_articol, d.pret, ... + from utilizatori_rol_intern a + left join politici_grupuri b on a.id_grup = b.id_grup + left join crm_politici_preturi c on b.id_politica = c.id_pol + left join crm_politici_pret_art d on c.id_pol = d.id_pol + left join nom_articole e on d.id_articol = e.id_articol + where a.sters = 0 and b.sters = 0 and ... and d.id_pol is not null and ... +``` +`v_articole` (grid-ul de adăugare articole pe comandă, `ocomenzi.vc2:4005-4014`, +`RecordSource = "v_articole"`) e populat strict din acest view, **filtrat `d.id_pol IS NOT NULL`** — deci +orice articol afișat spre alegere e deja membru garantat. La alegere (`do_adauga`, +`ocomenzi.vc2:4645-4702`) și la salvare (`do_scrie_articole`, `:4864-4899`): +``` +ocomenzi.vc2:4885-4893 +Scatter Name poArticol +... +lcSql = [begin pack_comenzi.adauga_articol_comanda(] + Alltrim(Str(This.nId)) + [,] + ; + Alltrim(Str(poArticol.id_articol)) + [,] + Alltrim(Str(poArticol.id_pol)) + [,] + ... +``` +`poArticol.id_pol` (scatter din `v_articole`) merge neschimbat în `COMENZI_ELEMENTE.ID_POL`. Nu există în +`ocomenzi.vc2` niciun al doilea punct de adăugare a articolelor pe comandă (nicio cale către `caut_articol` +de nomenclator liber) — `v_articole` e singura sursă. + +### A.4 — Ce se întâmplă când linia de comandă are `ID_POL` gol la facturare + +**Nu se poate întâmpla, prin construcție dublă**: (a) `COMENZI_ELEMENTE.ID_POL` e `NOT NULL` la nivel de +Oracle — un `INSERT` cu `id_pol` gol ar da `ORA-01400`, nu ajunge niciodată să fie facturat; (b) singurul +punct de inserare din VFP (`pack_comenzi.adauga_articol_comanda`, apelat cu `poArticol.id_pol` din +`v_articole`) nu poate produce `id_pol` gol, pentru că sursa (`com_vpreturi_utilizator`) filtrează deja +`id_pol IS NOT NULL`. Verificat pe date reale: 0 din 6868 linii active. Spre deosebire de nomenclator +(`caut_articol`, unde coloana `id_pol` **nu există deloc** în SELECT), pe comandă întrebarea „ce se +întâmplă la gol" nu are un răspuns de cod (fallback/eroare) pentru că premisa nu apare niciodată. + +--- + +## B. Ruta CONTRACT + +Produsul e separat, **`D:\ROA\ROACONTRACTE`** (există în arbore, alături de `ROAFACTURARE`). + +### B.1 — Cursorul de facturare selectează `ID_POL`? Da, dar prin `ID_POL_ART`, nu direct + +```sql +-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2718-2836 (cursor_contract, V_CURSOR2 = grd_contracte) +SELECT ..., id_pol, ... + FROM (SELECT A.ID_CTR, C.ID_ARTICOL, NULL as id_rata, C.ID_POL, ... + FROM CONTRACTE A + LEFT JOIN CTR_ARTICOLE B ON A.ID_CTR = B.ID_CTR + LEFT JOIN CRM_POLITICI_PRET_ART C ON B.ID_POL_ART = C.ID_POL_ART -- <- cheia stocata e ID_POL_ART + LEFT JOIN NOM_ARTICOLE E ON C.ID_ARTICOL = E.ID_ARTICOL ... +``` +Grid-ul principal de adăugare articole pe contract (`crsarticole`, folosit pentru „adăugare liberă") **nu** +vine din acest cursor — vine dintr-un apel separat, în interiorul aceleiași proceduri: +```sql +-- ff_...:2940-2948 +pack_facturare.cursor_preturi(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_ID_GESTIUNE_INIT, + V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR); +``` +adică **exact aceeași sursă ca „lista de prețuri" (secțiunea C)** — confirmă din nou concluzia din runda +anterioară (`retur_si_lista_preturi.md` B7): grid-ul „liber" de pe contract nu e liber de politică, e lista +de prețuri normală a operatorului. + +### B.2 — Unde e stocat pe contract: `CTR_ARTICOLE.ID_POL_ART` (linie, nullable) + +```sql +select column_name, nullable from user_tab_columns where table_name='CTR_ARTICOLE'; +-- ID_POL_ART Y <- nullable, spre deosebire de COMENZI_ELEMENTE.ID_POL +select count(*), sum(case when id_pol_art is null then 1 else 0 end) from ctr_articole; +-- 27 6 +``` +Spre deosebire de comandă, contractul stochează **`ID_POL_ART`** — FK direct la rândul din +`CRM_POLITICI_PRET_ART` (articol+politică), nu la politică ca atare — și coloana **e nullable**, fără +constrângere de tabel. Pe datele reale (bază de dezvoltare, eșantion mic — 27 rânduri în total, deci +**nu extrapolez procentul la producție**), 6 din 27 rânduri nu au `id_pol_art`. Dacă un asemenea rând ar +ajunge selectat spre facturare prin `V_CURSOR2`/`grd_contracte` (nu prin `crsarticole`, care e mereu lista +de prețuri), `LEFT JOIN ... ON B.ID_POL_ART = C.ID_POL_ART` nu găsește nimic, `id_pol` iese `NULL` în +cursor — același drum spre FACT-024 ca la nomenclator. **Neverificat**: dacă UI-ul (`grd_contracte`) permite +efectiv selecția unei asemenea linii spre facturare sau o filtrează/dezactivează — nu am urmărit acel cod +de grid, în afara bugetului acestei runde. + +### B.3 — Cine îl pune acolo la crearea contractului + +`D:\ROA\ROACONTRACTE\Clase\ferestre_contracte.vc2`, `PROCEDURE do_adauga_obiectul` (`:9454+`): +``` +:9464-9470 +Case gnParametru_prog = 1 && clienti + lcSel = [{call pack_crm.get_politici_grup(] + Alltrim(Str(gnIdUtil)) + [,] + ; + Alltrim(Str(goContract.id_valuta)) + [,?gnIdSucursala)}] + ... INTO Cursor crsPoliticiGrup1 ... +``` +``` +:9486-9500 +fpp = Createobject("frm_politica_preturi") && operatorul alege O politica dintre cele la care are drept +fpp.Show(1) +... +Select *, PRETFTVA As pret_unitar, 1 As cant, ... FROM cPol_pret_art With (Buffering = .T.) ; + WHERE ales = 1 INTO Cursor cAles && articolele bifate, deja membre ale politicii alese +``` +`pack_crm.get_politici_grup` e echivalentul ROACONTRACTE al lanțului +`UTILIZATORI_ROL_INTERN → POLITICI_GRUPURI → CRM_POLITICI_PRETURI` folosit și de comandă/factură normală — +operatorul primește lista politicilor lui, alege una, `cPol_pret_art` (bind pe `CRM_POLITICI_PRET_ART` +pentru politica aleasă) afișează articolele ei, iar rândurile bifate (`ales=1`) devin linii `CTR_ARTICOLE` +cu `id_pol_art` = `ID_POL_ART`-ul din acel rând. Din nou: **nu se alege un articol liber, se alege dintr-o +listă deja restrânsă la politică.** + +### B.4 — Ce se întâmplă cu `ID_POL_ART` gol la facturare + +Vezi B.2 — spre deosebire de comandă, **nu e imposibil prin constrângere de schemă** (coloana e nullable), +și există cazuri reale (6/27) în baza de dezvoltare. Comportamentul la facturare (dacă acea linie ajunge +selectată) urmează același `NULL → FACT-024` ca la nomenclator — **neconfirmat direct pe o factură reală**, +dedus din structura JOIN a cursorului (B.1). + +--- + +## C. Ruta LISTĂ DE PREȚURI (tip 1/5/7/10/22/29/45), ca termen de comparație + +**Corectare față de premisa din brief**: `id_pol` pe această rută **nu vine din alegerea operatorului pe +document** pentru majoritatea tipurilor — vine automat din rolul operatorului logat, exact ca pe comandă, +dar fără nicio interacțiune UI. + +`ofacturare.prg:279-282` (apelul cursorului pentru tip 1,5,7,10,22,23,29): +``` +lcSqlCursor = [{call pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta,] + ; + [?poDate.id_gestiune_init,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala)}] +``` +`poDate.id_pol` **nu e printre parametri**. `cursor_preturi` (`ff_...:2138-2354`) derivă `A.ID_POL` intern: +```sql +-- ff_...:2222-2251 (V_TIP IN (1,2)) / 2330-2354 identic ca structura, tot fara parametru id_pol +FROM (select a1.id_util, a3.id_pol, ... + from utilizatori_rol_intern a1 + left join politici_grupuri a2 on a1.id_grup = a2.id_grup + left join crm_politici_preturi a3 on a2.id_politica = a3.id_pol + ... + where a1.id_util = V_ID_UTIL + and NVL(a1.ID_SUCURSALA,-99) = NVL(V_ID_SUCURSALA,-99) + and ) A +LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL = B.ID_POL +``` +Adică `id_pol` per linie vine din rolul de utilizator (`UTILIZATORI_ROL_INTERN.ID_UTIL = V_ID_UTIL`) și +sucursală — **niciodată dintr-o alegere pe documentul curent**, pentru tip 1/2/5/7/10/22/29/45. + +Câmpul de căutare vizibil pe antet există, dar e **inert pentru aceste tipuri**: +``` +COMUN\clase\ofacturare.vc2:6822-6825 +ADD OBJECT 'Ct_clb_politici_preturi' AS ct_clb_cautare WITH ; + cconditie = ISNULL(poDate.id_pol) or EMPTY(poDate.id_pol), ; + cprocedura = thisform.do_cauta_politica, ... +``` +`poDate.id_pol` e citit efectiv (trimis la Oracle) **doar** pentru `cursor_gestiune`, tip 41/23 +(`ofacturare.prg:297,777`); e validat ca obligatoriu **doar** pentru aceleași două tipuri +(`ofacturare.vc2:7334-7337`: *„Nu ati ales politica de preturi!"*). Pe factura normală din listă de +prețuri, câmpul poate rămâne gol fără nicio consecință — nu alimentează nimic. + +**Concluzie C**: „lista de preț pe document" nu există ca mecanism activ pentru rutele principale — e „lista +de preț a operatorului logat", determinată automat de rolul lui în `UTILIZATORI_ROL_INTERN`. Fiecare linie +din `crsarticole` vine deja cu propriul `A.ID_POL` din acest join; dacă operatorul are mai multe grupuri de +politici active simultan, teoretic același articol ar putea apărea de mai multe ori cu `id_pol` diferit — +**neverificat pe date reale**, în afara bugetului acestei runde. + +--- + +## Ce nu s-a putut stabili și de ce + +- **B.4, comportamentul exact la facturare a unei linii de contract cu `ID_POL_ART` gol** — dedus din + structura JOIN a `cursor_contract`, nu confirmat pe o factură reală emisă din contract cu o asemenea + linie (ar necesita un contract de test cu o linie fără politică, apoi o încercare de facturare — în + afara bugetului read-only al acestei runde). +- **Dacă `grd_contracte` (UI) permite selecția/afișarea unei linii cu `id_pol_art` gol** — nu am urmărit + codul de grid din `ROACONTRACTE`/`ofacturare.vc2` pentru acest caz specific. +- **Dacă un operator cu mai multe grupuri de politici active poate primi articole duplicate cu `id_pol` + diferit în `crsarticole`** (secțiunea C) — plauzibil din structura JOIN-ului (fără `DISTINCT` pe + `a1.id_util`), neverificat pe date reale. +- **Eșantionul `CTR_ARTICOLE` e mic (27 rânduri) în baza de dezvoltare** — raportul 6/27 fără + `id_pol_art` e o dovadă reală, dar nu extrapolabilă direct la volumul de producție; merită o verificare + separată pe o bază cu volum de producție dacă decizia depinde de frecvența cazului. +- **Cele 37 de linii de comandă active cu articol ne-membru al politicii de pe linie** (semnalate în + „Completare de la sesiunea principală") — cauza (import? editare ulterioară a politicii? ștergere din + `CRM_POLITICI_PRET_ART` după ce comanda a fost creată?) nu e stabilită, și nici comportamentul real la + facturare (`FACT-024`/`ORA-01403` așteptat pe cod, neconfirmat pe o încercare reală). E cea mai + importantă verificare rămasă înainte de S4g — dacă garanția „membership prin construcția listei" se sparge + și pe alte linii similare, tot raționamentul din secțiunea E (paragraful 1) trebuie recalibrat. +- **`VANZARI.TIP = 51`** — 121 de linii fără `id_pol`, cu 179/275 rânduri `ACT` cu `SCC` populat cumulat pe + documentele aferente — nu am identificat ce tip de document e (nu apare în `Do Case` din + `ofacturare.prg:266-308`); posibil vânzare retail/POS sau un tip specific altui produs ROA. Neexplorat. +- **Restul tipurilor negative (`-1`…`-13`, în afară de `-12`=ROAAUTO)** — nu au fost identificate individual; + presupun variante storno/aviz ale rutelor deja documentate, dar nu s-a verificat pe cod care produs le + generează. +- **Dacă experiența lui Marius vine efectiv din contract-cu-rate, din ROAAUTO, sau din altă rută + neidentificată (`51`, negative)** — nu s-a putut stabili fără să-l întrebăm direct; secțiunea 0d + enumeră ipotezele plauzibile, cu dovezi pentru fiecare, dar nu poate alege între ele. + +--- + +## Completare de la sesiunea principala (confirmare pe schema, 10.08.2026) + +Verdictul de mai sus a fost verificat independent, pentru ca **contrazice o afirmatie a lui Marius** +(„fac facturi pe baza de comanda care nu au politica de pret"). Confirmat: + +- `COMENZI_ELEMENTE.ID_POL` — `nullable = N`, constrangerea `SYS_C0015376` (`"ID_POL" IS NOT NULL`) plus + `FK_COMENZI_ELEMENTE_002`. In date: **7108 randuri total, 0 cu `ID_POL` nul, 0 cu `ID_POL` = 0**, 9 + politici distincte in uz. Pe liniile active (`STERS = 0`): 6868, tot 0 nule. +- `CTR_ARTICOLE.ID_POL_ART` — `nullable = Y`. Confirmata asimetria semnalata in raport. +- Toate cele 9 politici folosite pe linii de comanda au `ID_NOTA` si ajung la un `SCC`. + +**Doua observatii noi, care nu erau in raport:** + +1. **Politica `7 DISCOUNT` e o politica tehnica de rutare contabila, folosita in producție.** Apare pe + **870 de linii de comanda** si trimite la nota `2 DISCOUNT` (`SCD = 667`, `SCC = 4111`). Nu e o lista + comerciala de preturi — e o politica existenta doar ca sa duca linia la o nota contabila anume. **E + precedentul de care are nevoie decizia 27**, si e mai apropiat de ce cere #13 decat + `gnId_pol_pret_stoc` (care e o singura politica pentru tot stocul). Tiparul „politica tehnica per scop + contabil" nu trebuie inventat: exista. +2. **Garantia „apartenenta prin construcția listei" nu e etanșă in date.** Din 6868 de linii active de + comanda, **37 au un articol care nu e membru al politicii de pe linie** + (`NOT EXISTS (SELECT 1 FROM crm_politici_pret_art WHERE id_pol = e.id_pol AND id_articol = e.id_articol)`). + Adica 37 de linii care, la facturare, ar trebui sa cada cu `FACT-024`. Cauza nu e stabilita — import, + sau editarea / stergerea articolului din politica dupa introducerea comenzii. **Punctul 1 al secțiunii E + („nu exista cale, in UI, de a ajunge la un articol care nu e deja membru") e corect despre UI, dar nu + despre starea datelor.** De verificat inainte de S4g: daca acele 37 de linii se factureaza fara eroare, + exista o cale nedescoperita si intrebarea deciziei 32 se redeschide. + +Interogarile: schema `MARIUSM_AUTO` pe `ROA_CENTRAL`, doar `SELECT`. diff --git a/docs/cercetare/inventar_controale_formulare.md b/docs/cercetare/inventar_controale_formulare.md new file mode 100644 index 0000000..61d0c0f --- /dev/null +++ b/docs/cercetare/inventar_controale_formulare.md @@ -0,0 +1,179 @@ +# Inventar controale — formulare facturare (ROAFACTURARE, `COMUN\clase`) + +Investigatie read-only (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit). Toate liniile +sunt din fisierele text `.vc2` (FoxBin2Prg), verificate pe fisierul real (nu `.bak`). Scop: pregatirea +unui mockup pentru un formular de facturare unificat — inventarul reflecta controalele existente, nu +o propunere noua. + +## 1. Butoane `frm_facturare_articole` (`ofacturare.vc2:10968-15739`) + +Grid sursa (comanda/lista preturi) = `grd_articole` (`Left=9,Top=336,Width=326,Height=130`, +`RecordSource=crsarticole`, `ofacturare.vc2:11570`). Grid destinatie (linii factura) = `grd_factura` +(`Left=379,Height=337`, `RecordSource=crsfactura`, `ofacturare.vc2:12263`). + +| Control | Clasa de baza | Caption/Picture | L/T/W/H | ToolTipText | Pozitie fata de grid | `fisier:linie` | +|---|---|---|---|---|---|---| +| But_modifica1 | but_modifica | (fara caption, `modific_sus.bmp`) | 773/61/30/27 | "Modificare (CTRL+M)" | dreapta-sus `grd_factura` (Anchor=9) | `ofacturare.vc2:11192` | +| But_sterge1 | but_sterge | `sterg_sus.bmp` | 811/61/30/27 | "Stergere (CTRL+D)" | dreapta-sus `grd_factura`, langa But_modifica1 | `ofacturare.vc2:11229` | +| But_renunt1 | but_renunt | `renunt_sus.bmp` | 792/1/30/27 | "Renuntare (ESC)" | coltul din dreapta-sus al formularului | `ofacturare.vc2:11202` | +| But_reset1 | but_reset | `reset_sus.bmp` | 303/295/30/27 | "Reseteaza" | deasupra `grd_articole` (Top grid=336) | `ofacturare.vc2:11213` | +| But_urmator1 | but_urmator, `caction=do_urmator` | `urmator1.bmp` | 343/348/30/27 | "Verificare" | lateral-dreapta `grd_articole` (Left grid+Width+8=343) | `ofacturare.vc2:11237` | +| But_urmator2 | but_urmator, `caction=do_urmator2` | `urmator1.bmp` | 343/143/30/27 | "Verificare" | deasupra `grd_articole`, langa `grd_contracte` | `ofacturare.vc2:11247` | +| **But_urmator_tot1** ("adauga tot din comanda") | but_urmator_tot, `caction=do_adauga_tot` | `urmator1_tot.bmp`, **fara ToolTipText** | 343/378/30/27, `Visible=.F.` implicit | — | lateral-dreapta `grd_articole`, sub But_urmator1 | `ofacturare.vc2:11257` | +| But_retur | but_retur | `retur1.bmp` | 343/407/30/27, `Visible=.F.` implicit | "Retur" | lateral-dreapta `grd_articole`, sub But_urmator_tot1 | `ofacturare.vc2:11221` | + +**"Adauga tot din comanda" — But_urmator_tot1.Click -> `do_adauga_tot`** +(`ofacturare.vc2:13169-13198`): parcurge `crsarticole` (SCAN) si apeleaza +`Thisform.do_adauga_articol(.T.)` pentru fiecare linie; daca articolul e gestionabil si cantitatea +ramasa >0, cere confirmare "Nu ati selectat toata cantitatea... treceti la urmatorul?" +(`aMessageBox` cu butoane Da/Nu/Renunta, cod 7). + +Vizibilitate pe tip document (`Init`, `ofacturare.vc2:14976-15344`, `Do Case poDate.tip`): + +| Control | Vizibil cand | Dovada | +|---|---|---| +| But_urmator_tot1 | `poDate.eProforma=1`, `poDate.lCopiere`, `tip=3` (comanda), `tip=4` (din avize), `tip in(21,28,42,47)` (aviz din comanda), `tip=25`, `tip in(8,9)` (retur factura), `tip=24` (retur aviz) | `ofacturare.vc2:15113,15120,15150,15166,15177,15215,15240,15245` | +| But_retur | `tip in(1,5,7,10)` (facturare din lista de preturi) | `ofacturare.vc2:15127` — comentariu explicit: "pot sa fac retur de articole intr-o factura de vanzare" | +| But_urmator2 / `grd_contracte` | eliminate (`RemoveObject`) daca nu exista `crsarticole1` (fara contracte pe formular) | `ofacturare.vc2:15294-15324` | + +**Conventia de clase de butoane** (suita ROA): clasa de baza `buton` (`_cmd_base.vc2:16`, +`AS _cmdbase OF "_cmd_base.vcx"`) — 30x27px implicit, `Caption=""` (buton doar cu imagine), +`BackColor=alb`, `SpecialEffect=1`. `Click` (`_cmd_base.vc2:41-70`) executa dinamic +`This.Parent.()` (macro pe `cAction`/`clistaparametri`) — de-asta majoritatea instantelor nu +au `Caption`/`Click` propriu, doar `caction=`. Subclasele concrete (`but_nou`, +`but_sterge`, `but_modifica`, `but_urmator_tot`, etc.) sunt in `cmd_butoane.vc2:7-441`, fiecare +hardcodand `caption`/`cpictureup`/`cpicturedown`/`Picture` (`..\grafice\*.bmp`, stare sus/jos) si +`ToolTipText` cu shortcut intre paranteze (ex. "Modificare (CTRL+M)"). `but_nou` (`do_adauga`, +`nou_sus.bmp`, "Adaugare (CTRL+N)") — `cmd_butoane.vc2:214`. + +## 2. Butoane `frm_facturare_articole2` (prototip, `ofacturare.vc2:15741-19355`) + +Diferenta arhitecturala fata de #1: **un singur grid** `grd_factura` (`Left=12,Top=204,Height=240`, +`ofacturare.vc2:16601`) cu editare inline prin comboboxuri in celule (`cCodMat.cboCodmat`, +`cDenumire.cCboDenumire`, `cGestiune.cCboGestiune`) — nu exista casete separate de cautare articol +(`ct_codmat`/`ct_articole`) si nici `crsarticole`/comanda sursa. + +| Control | Clasa de baza | Caption/Picture | L/T/W/H | ToolTipText | Pozitie fata de grid | `fisier:linie` | +|---|---|---|---|---|---|---| +| **But_nou1** | but_nou (mostenit, `caption=do_adauga` suprascris in clasa) | `nou_sus.bmp` | 719/175/30/27 | "Adaugare (CTRL+N)" | deasupra-dreapta `grd_factura` (Top grid=204) | `ofacturare.vc2:15936` | +| But_sterge1 | but_sterge | `sterg_sus.bmp` | 747/175/30/27 | "Stergere (CTRL+D)" | deasupra-dreapta `grd_factura`, langa But_nou1 | `ofacturare.vc2:15955` | +| But_renunt1 | but_renunt | `renunt_sus.bmp` | 716/1/30/27 | "Renuntare (ESC)" | coltul din dreapta-sus | `ofacturare.vc2:15944` | + +**But_nou1.Click -> `do_adauga`** (`ofacturare.vc2:17118-17122`, override propriu, nu +`but_nou`.caction implicit): `SELECT crsFactura / APPEND BLANK / this.grd_factura.SetFocus()` — +adauga direct un rand gol in grid si da focus, spre deosebire de `do_adauga_articol` complex din #1. + +Nu exista `But_modifica` in aceasta clasa — editarea se face inline in celulele gridului, nu prin +dialog separat. + +**Bonus relevant pentru mockup**: acest prototip **incorporeaza deja aceleasi controale de antet** +(`Ct_clb_venchelt`, `Ct_clb_sectie`, `Ct_clb_responsabil`, `Ct_clb_lucrare`, `Ct_clb_altele`, +`Ct_clb_valuta`, `clb_fdoc`, `Clb_serie_act1`, `Clb_nract`, `Clb_dataact`, `Clb_data_scadenta`, +`Clb_zi_curs`) direct pe formularul de articole, la `ofacturare.vc2:15741+182..749` — e cea mai +apropiata schita existenta de un "formular unificat". + +## 3. Antet `frm_date_factura` (`ofacturare.vc2:8482-9869`) si `frm_date_aviz` (`ofacturare.vc2:6566-7618`) + +| Control cerut | `frm_date_factura` — caption real | `frm_date_aviz` — caption real | Tip/container | Obligatoriu / vizibil conditionat | +|---|---|---|---|---| +| tip venit/cheltuiala | `Ct_clb_venchelt` = "Venit / cheltuiala" | `Ct_clb_venchelt` = "Venit / cheltuiala" | `ct_clb_cautare` (container cautare) | eliminat pe aviz pentru `tip=23,41,25` (transfer/retur) — `ofacturare.vc2:7438-7461` | +| sectie | `Ct_clb_sectie` = "Sectie" | `Ct_clb_sectie` = "Sectie" | `ct_clb_cautare` | intotdeauna prezent in ambele (nu s-a gasit `RemoveObject`) | +| responsabil | `Ct_clb_responsabil` = "Responsabil" | `Ct_clb_responsabil` = "Responsabil" | `ct_clb_cautare` | eliminat cand `gnScadereStoc=0` sau `tip in(4,7,8,9,48,49)` — factura: `9646-9707`; aviz: eliminat pe majoritatea tipurilor cu comanda/lista | +| lucrare | `Ct_clb_lucrare` = "Lucrare" | `Ct_clb_lucrare` = "Lucrare" | `ct_clb_cautare` | nu s-a gasit eliminare conditionata | +| altele | `Ct_clb_altele` — label dinamic ("Altele" implicit) | idem | `ct_clb_cautare`, `.do_schimba_explicatia(...)` | eticheta se schimba pe tip: "Nr. contract"/"Nr. comanda"/"Nr. factura"/"Nr. facturi"/"Locatie" (`9633-9643`); eliminat complet daca `gnScadereStoc=0 and tip in(1,5,10)` fara copiere | +| valuta | `Ct_clb_valuta` = "Valuta" | **nu exista pe aviz** | `ct_clb_cautare` | eliminat daca `poDate.in_valuta=0` (`ofacturare.vc2:9725-9732`) | +| fel document | `Ct_clb_fdoc` = combo `_combobox1` FACTURA/PROFORMA/BON FISCAL, label "Tip document" | `Ct_clb_fdoc` = camp cautare "Felul documentului" (`caut_ora.vcx`) | container diferit intre cele doua forme (combo la factura, cautare la aviz) | intotdeauna vizibil | +| serie | `Clb_serie_act` label "Serie document" | `Clb_serie_act` (fara label explicit override) | `clb_serie_act` (`serii_numere.vcx`) | eliminat daca `poDate.rezultat_serii` nu e in `(1,2,3)` — nicio serie configurata | +| numar | `Clb_nract` = "Numar document" | `Clb_nract` = "Nr. documentului" | `clb_tx_simplu`, `InputMask=get_mask(14,0)` | mereu prezent | +| data act | `Clb_dataact` = "Data document" | `Clb_dataact` = "Data documentului" | `clb_tx_data` | mereu prezent | +| data scadenta | `Clb_data_scadenta` = "Data scadenta" | **nu exista pe aviz** | `clb_tx_data` | **dezactivat** (nu eliminat) cand `gnScadentaAutomata=1` (`.dezactiveaza()`, `9713-9715`) | +| zi curs | `Clb_zi_curs` = "Data curs valutar" | `Clb_zi_curs` = "Data cursului valutar" | `clb_tx_data` | eliminat pe factura daca `tip in(8,9)` retur (`9718-9722`) | +| client | `Ct_clb_nume_client` = "Nume client" | `Ct_clb_nume_client` = "Nume client" | `ct_clb_cautare` | eticheta se schimba pe aviz ("Retur de la"/"Gestiune sursa") in functie de tip transfer | + +Control specific doar in `frm_date_factura`: `Ct_clb_gestiune_init`="Gestiune sursa" (`ToolTipText`: +"Daca nu alegeti gestiunea, la scaderea din stoc a unui articol va vor fi aratate stocurile tuturor +gestiunilor pe care aveti drepturi.") — eliminat impreuna cu `Ct_clb_responsabil` in majoritatea +cazurilor `gnScadereStoc=0`; `txtCodFiscal`/`lblCodFiscal` (cod fiscal, `ReadOnly`, langa client) si +`txtSoldLei`/`lblSoldLei` (sold curent client, populat din `GetSoldClient()` daca +`poDate.id_client<>0`); `But_verifica1` (verificare ANAF). Control specific doar in +`frm_date_aviz`: `Ct_clb_politici_preturi`="Politica de preturi" — eliminat pe majoritatea +tipurilor cu comanda. + +Toate conditiile de vizibilitate sunt in `Init` (nu in `do_schimba_tipdoc`, care doar realoca +seria/numarul): `frm_date_factura.Init` = `ofacturare.vc2:9563-9796`; `frm_date_aviz.Init` = +`ofacturare.vc2:7354-7600` (citit doar pana la ~`7533`; ultimele ~65 linii nu au fost verificate, +vezi "Necunoscute ramase"). + +## 4. `frm_alte_date` (`ferestre_cere_date.vc2:2219-3353`) + +| Control | Caption | Grupare logica | `fisier:linie` | +|---|---|---|---| +| `Ct_clb_delegat` | "Delegat" | Delegat/transport | `2553` | +| `Ct_clb_masina` | "Masina" | Delegat/transport | `2569` | +| `Ct_clb_agent` | "Agent" | Delegat/transport | `2537` | +| `Clb_dataora_exp` | "Data si ora expedierii" | Delegat/transport | `2443` | +| `opt_incasat` (radiogrup 4 optiuni) | "Fara incasare" / "Chitanta" / "Bon fiscal" / "POS Card" | Incasare | `2638` | +| `Cb_casa` | "Casa" (-> "Banca POS" pe POS) | Incasare | `2362` | +| `Clb_serie_chit` | "Serie chitanta" | Incasare (doar Chitanta) | `2503` | +| `Clb_nrchit` | "Nr. chitanta" (-> "Nr. bon" pe Bon fiscal/POS) | Incasare | `2480` | +| `Clb_incasat` | "Incasat" | Incasare | `2461` | +| `cmdModificaBon` | (icon `but_modifica`) | Incasare (doar Bon fiscal), `caction=do_modifica_bon` | `2526` | +| `chkPOS` | "POS" | Incasare (doar Bon fiscal) | `2409` | +| `chkDetaliat` | "Detaliat" | Incasare (doar Bon fiscal) | `2396` | +| `cboTipFactura` | (combo, langa label "Tip factura") | Incasare | `2378` | +| `clb_adresa_facturare` | "Adresa facturare" | Adresa de facturare | `2422` | +| `Ed_tx_simplu1` | "Text aditional (max. 1000 caractere)" | Text aditional | `2585` | +| `But_modifica1` | (icon) `caction` implicit -> `do_modifica` | actiune pe delegat (deschide `nom_parteneri_modifica`) | `2343` | + +**`actualizeaza_tipincasare`** (`2698-2856`) comuta vizibilitatea pe `This.opt_incasat.Value`: + +| Valoare | Vizibile | Ascunse | Numar alocat | +|---|---|---|---| +| 1 = Fara incasare | — | `cb_casa,clb_serie_chit,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon,chkPOS,chkDetaliat` | dezaloca 16(chitanta)/3(bon)/26(POS) | +| 2 = Chitanta | `cb_casa,clb_serie_chit,clb_nrchit,clb_incasat,_shape3,lb_simplu1` | `cmdModificaBon,chkPOS,chkDetaliat` | `Thisform.clb_serie_chit.genereazaNumar()` (`2783`) | +| 3 = Bon fiscal | `cb_casa,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon,chkPOS,chkDetaliat` | `clb_serie_chit` | `Thisform.do_aloca_nr_bon([CLICK])` (`2807`) | +| 4 = POS/Card | `cb_casa,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon` | `clb_serie_chit,chkPOS,chkDetaliat` | `Thisform.do_aloca_nr_pos([CLICK])` (`2844`) | + +`cmdModificaBon.Click -> do_modifica_bon` (`3001-3010`) deschide `viz_config_serii_complet WITH 3` +(`oserii_numere.prg`) si, la confirmare, dezaloca+realoca numarul de bon fiscal. + +## 5. Butoane `frm_facturi` (lista facturi, `ofacturare_comun.vc2:1168-5126`, fisierul real — +verificat, nu `.pre_s4butoane.bak`) + +| Control | Caption/Picture | Metoda apelata (`caction`/override) | `fisier:linie` | +|---|---|---|---| +| `but_modifica1` | `modific_sus.bmp` | `caction` implicit = `inainte_de_do_modifica` -> meniu `xmenu("Modificare date factura;Editare factura (articole, cantitati, preturi)")` -> optiune 1: `do_modifica()`, optiune 2: **`do_editare_factura()`** | ADD OBJECT `1424`; `inainte_de_do_modifica` `4925-4934`; `do_modifica` `4538-4637`; `do_editare_factura` `3715-3869` | +| `But_modifica2` | (fara caption, Top=341 — pe grid-ul de detalii, nu in bara de sus) | `caction=do_modifica_explicatie`, ToolTipText "Modificare explicatie articol" | `1434`; `do_modifica_explicatie` `4639-4656` | +| `But_copiaza1` | `copy_sus.bmp`, `Visible=.F.` implicit | `caction` mostenit = `do_copiaza` | `1404`; `do_copiaza` `3628-3713` | +| `But_sterge1` | `sterg_sus.bmp` | `caction` mostenit (`inainte_de_do_sterge` din clasa `but_sterge`) -> `do_sterge` | `1463`; `do_sterge` `4658-4874` | +| `But_listare1` | `listare_sus.bmp` | `do_listare` | `1414` | +| `But_verifica1` | (fara Picture explicit) | `do_verifica`, ToolTipText "Vericare coduri fiscale pe serverul ANAF" | `1473` | +| `But_attach1` | `attach_sus.bmp` | `caction=` gol, override `But_attach1.Click` | `1394` | + +**Nu exista buton separat "editare 2024"**: `do_editare_factura` este optiunea 2 din meniul popup +deschis de `but_modifica1` (`inainte_de_do_modifica`), nu un buton propriu. + +`Init` (`4936-4959`): daca `glLunaInchisa` (luna contabila inchisa), +`Thisform.but_sterge1.Visible = .F.` si se scoate dreptul de stergere din `gcAcces` — singura +conditionare de vizibilitate pe drept gasita in aceasta clasa. + +## Necunoscute ramase + +- `do_modifica` (frm_facturare_articole, `13746-13914`, 168 linii) si `do_sterge`/`do_editare_factura` + (frm_facturi) nu au fost citite in detaliu — doar identificate ca existenta/semnatura; daca + mockup-ul are nevoie de logica exacta de validare la modificare/stergere linie, trebuie citite + explicit. +- `frm_date_aviz.Init` (`7354-7600`) a fost citit doar pana la ~`7533`; ultimele ~65 linii (probabil + finalizare `laPozitii`/repozitionare, simetrice cu `frm_date_factura`) nu au fost verificate. +- Nu am verificat daca vizibilitatea controalelor din sectiunea 3 mai depinde si de **drept de + utilizator** (nu doar `poDate.tip`/`gnScadereStoc`/`gnScadentaAutomata`) — codul citit foloseste + doar variabile globale de setare firma si tipul documentului, nicio verificare explicita + `gcAcces`/drepturi pe aceste containere. +- Dimensiunile exacte (`Width`/`Height`) ale containerelor `ct_clb_cautare`/`clb_tx_*` nu sunt + listate explicit in multe instante (mostenite din clasa de baza `caut_ora.vcx`/`lb_tx.vcx`) — nu + am deschis acele biblioteci pentru dimensiuni implicite; doar `Left`/`Top`/`TabIndex` sunt + suprascrise per instanta. +- Nu am inspectat `caut_ora.vcx`/`lb_tx.vcx`/`serii_numere.vcx` (clasele de baza ale containerelor + `clb_*`/`ct_clb_*`) pentru a confirma dimensiunile standard sau comportamentul exact al + proprietatii `cconditie` (pare sa controleze cand campul de cautare cere obligatoriu o valoare, + nu vizibilitatea). diff --git a/docs/cercetare/legatura_linie_retur.md b/docs/cercetare/legatura_linie_retur.md new file mode 100644 index 0000000..e02cfd4 --- /dev/null +++ b/docs/cercetare/legatura_linie_retur.md @@ -0,0 +1,228 @@ +# Cercetare: legatura "linie de retur -> linie/factura originala" se persista undeva? + +## Verdict (10 randuri) + +**NU se persista la nivel de linie. PARTIAL la nivel de document, si numai cand a fost aleasa o +singura factura sursa.** Cursorul Oracle care populeaza grila de retur pentru N.1 (documente tip +8/9/24, `pack_facturare.cursor_retur_document`, `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3949-4062`) +**nu selecteaza deloc** `A1.ID_VANZARE` sau `A1.ID_VANZARE_DET` din `VANZARI_DETALII` — legatura cu +factura/linia sursa se pierde chiar in query-ul care aduce datele in VFP, inainte sa existe vreo +sansa sa fie afisata. INSERT-ul final in `VANZARI_DETALII` (`scrie_in_vanzari`, documentat deja in +`rec_cale_vanzari_detalii.md:105-113`) nu are nicio coloana de tip sursa. Exista in schimb un link +**la nivel de document intreg**: `VANZARI_CORESP` (`TIP=3`, `ID_VANZARE_FACT`=documentul de retur, +`ID_VANZARE_AVIZ`=fiecare factura sursa aleasa) — dar cand utilizatorul alege mai multe facturi +sursa deodata (selectie multipla, suportata explicit de dialog), acest link iti da **multimea** de +facturi posibile, nu factura exacta a liniei. Pentru N.2 (`But_retur`, per articol) nu exista niciun +document nou scris separat — returul e o linie in factura normala curenta, iar "sursa" e verificata +doar la nivel de stoc (`RUL`/`STOC` filtrat pe `COD` legat de `VANZARI.ID_VANZARE`), nu persista ca +atribut al liniei noi. Concluzie pentru S4f: **se livreaza fara coloana de provenienta pe linie**; +cel mult se poate afisa, cand e un singur document sursa, "factura de retur X provine din factura Y" +la nivel de document (din `VANZARI_CORESP`), nu per linie. + +## 1. Ce face Oracle cu `poDate.listaid` + +Doua cai, in functie de mecanism: + +**N.1 (document, tip 8/9/24)**: `poDate.listaid` = CSV de `id_vanzare` (facturile alese la pasul de +cautare multipla, `ofacturare.vc2:9200`: `poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")`). +Ajunge la Oracle ca parametru `V_LISTAID` in `pack_facturare.cursor_retur(V_IN_VALUTA, V_LISTAID, +V_ID_UTIL, V_CURSOR)` (`ff_...sql:3934-3947`), care e doar un wrapper peste +`cursor_retur_document(V_IN_VALUTA, V_LISTAID, V_COPIERE=0, V_PROFORMA, V_ID_UTIL, V_CURSOR)` +(`:3949-4062`). Acolo `V_LISTAID` devine CTE-ul `CRS` (`:3962-3964`): +```sql +WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR))) +``` +folosit doar ca filtru: `WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)` +(`:4054-4055`). **Filtru, nu persistare** — dupa acest `WHERE`, `ID_VANZARE` nu mai apare deloc in +lista de coloane a `SELECT`-ului extern (`:3965-4028`: `ID_C` (=`ROWNUM`, sintetic), `ID_ARTICOL`, +`LOT`, `SERIE`, `ID_POL`, preturi, `GESTIONABIL`, `CANTITATE`, `ID_GESTIUNE`, `PRET_ACHIZITIE`... dar +nu `ID_VANZARE` si nu `ID_VANZARE_DET`). Cursorul primit de VFP in `crsarticole` nu are, deci, nicio +coloana care sa spuna din ce factura/linie vine randul. + +Separat, la scrierea efectiva a documentului de retur, `scrie_corespondente_vanzari(3)` +(`:14834-14836`, apelata din `finalizeaza_factura`) foloseste tot `pack_facturare.clistaid` +(= acelasi `poDate.listaid`, tinut in variabila de sesiune a pachetului) ca sa scrie in +`VANZARI_CORESP` (`:15481-15516`): +```sql +INSERT INTO VANZARI_CORESP (ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP) + SELECT pack_facturare.nid_vanzare, ID_VANZARE, 3 + FROM VANZARI WHERE ID_VANZARE IN (SELECT X FROM table(charn2collection(V_LISTAID,','))); +``` +Asta scrie **cate un rand per factura sursa aleasa** (nu per linie), cu noul document de retur ca +`ID_VANZARE_FACT` si fiecare sursa ca `ID_VANZARE_AVIZ` (numele coloanei e generic, reutilizat si +pentru perechi aviz-factura, `TIP=1/2`, vezi `sterge_factura:5452-5457,5582-5585`). + +**N.2 (`But_retur`, per articol)**: `poDate.listaid` = `"ID_ARTICOL:ID_VANZARE"` (una sau mai multe +perechi CSV, `ofacturare.vc2:12888`: `thisform.cListaIdArticoleRetur = ... + STR(poArticol.id_articol) + ':' + poDate.listaid`, +trimis la Oracle la `:13978`). In `pack_facturare` (verificat in ramura `WHEN V_CANTE < 0 and +pack_facturare.clistaid is not null and instr(pack_facturare.clistaid, ':') > 0`, +`ff_...sql:8142-8212`) e folosit ca filtru pentru calculul cantitatii disponibile de retur din +rulaj (`RUL`), NU ca sa scrie o legatura: +```sql +AND A.COD IN (SELECT COD FROM VANZARI WHERE ID_VANZARE IN + (SELECT id_vanzare FROM (SELECT CAST(GETWORDNUM(id_articol_id_vanzare,1,':') AS NUMBER(20,0)) id_articol, + CAST(GETWORDNUM(id_articol_id_vanzare,2,':') AS NUMBER(20,0)) id_vanzare + FROM (SELECT x AS id_articol_id_vanzare FROM table(cast(CHARC2COLLECTION(pack_facturare.clistaid,',') AS char_tab)))) + WHERE id_articol = V_ID_ARTICOL)) +``` +Foloseste `listaid` doar ca sa restranga `RUL` la miscarile de stoc (`A.COD`) legate de factura +aleasa, ca sa calculeze **cantitatea inca disponibila in gestiune din acea vanzare** pentru articolul +respectiv (`tab_stoc`, tip=1 in acel `SELECT`). Nu se scrie nicio linie noua de legatura in vreun +tabel — e folosit exclusiv ca filtru intr-un calcul de disponibil. + +## 2. Coloana de provenienta pe `VANZARI_DETALII` + +Nu exista. DDL-ul complet nu e disponibil in acest repo (nu exista `CREATE TABLE VANZARI_DETALII` in +`docs/`; confirmat deja de cercetari anterioare — `docs/cercetare/discount_verificare2.md:104-111`, +`COMUN\docs\cercetare\rec_tva_vanzari.md:19`), dar structura efectiva a fost verificata pe schema live in +`docs/cercetare/rec_cale_vanzari_detalii.md:157-169` (interogare `all_tab_columns`, 08.08.2026): +35 de coloane, PK `ID_VANZARE_DET` (generat prin trigger `TRG_VANZARI_DET_BEFOINS` din +`SEQ_VANZARI_DETALII`), FK logic `ID_VANZARE`. Lista coloanelor relevante citata acolo: `ID_ARTICOL`, +`PRET`, `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`, +`ID_VALUTA`, `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`, `TAXCODE`, `LOT`, `STERS`, `VALIDAT`, +`DATAORA_VALID`, `ID_UTIL_VALID`, `ID_UTILS`, `DATAORAS`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT`, +`PRETD`, `ID_VALUTAD`, `PRETV_ORIG`, `ID_CTR`, `ID_RATA`. **Nicio coloana** de tipul +`ID_VANZARE_SURSA`, `ID_DETALIU_SURSA`, `ID_FACT_SURSA`, `ID_VANZARE_RETUR`, `ID_ORIGINAL` sau orice +self-referinta catre o alta linie `VANZARI_DETALII`. Cautat explicit acele siruri in tot exportul +`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (~28000 linii) si in `COMUN\docs\` — zero potriviri +(vezi comanda de mai jos, sectiunea "Ramas de verificat"). + +Confirmarea independenta finala si cea mai directa: INSERT-ul care trece liniile din +`VANZARI_DETALII_TEMP` in `VANZARI_DETALII` la finalizarea oricarui document (inclusiv retur, pentru +ca `scrie_in_vanzari` e comun tuturor tipurilor) are lista de coloane explicita +(`rec_cale_vanzari_detalii.md:105-113`): +```sql +INSERT /*+ APPEND */ INTO VANZARI_DETALII + (ID_VANZARE, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD, + PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, ID_JTVA_COLOANA) +SELECT pack_facturare.nid_vanzare, ID_ARTICOL, ... FROM VANZARI_DETALII_TEMP WHERE ... +``` +Nicio coloana sursa in lista. Cum `VANZARI_DETALII_TEMP` insasi e populata pentru retur din +`cursor_retur_document` (care, cf. punctul 1, nu aduce `ID_VANZARE`/`ID_VANZARE_DET` sursa), legatura +e pierduta cu doi pasi inainte de a ajunge la acest INSERT — nu doar "nu se scrie", ci "nu mai exista +in date la momentul scrierii". + +## 3. Tabel separat de legatura + +**Da, exista, dar la nivel de document, nu de linie**: `VANZARI_CORESP(ID_VANZARE_FACT, +ID_VANZARE_AVIZ, TIP, STERS)`. Folosit pentru mai multe perechi de corespondenta, disambiguizate prin +`TIP`: +- `TIP=1`: factura scrisa dintr-un aviz (`scrie_corespondente_vanzari(1)`, la facturare din aviz, + `:14826`); +- `TIP=2`: aviz de retur (`scrie_corespondente_vanzari(2)`, `:14830`, la `ntip=24`); +- `TIP=3`: **factura de retur** (`scrie_corespondente_vanzari(3)`, `:14834-14836`, la `ntip in (8,9)`) + — exact cazul cerut de S4f. + +Verificarile de stergere din `sterge_factura` (`:5450-5494`) confirma semantica: interogheaza +`VANZARI_CORESP WHERE ID_VANZARE_AVIZ = V_ID_VANZARE AND TIP = 3` pentru "exista facturi de retur pe +aceasta factura?" — deci pentru o factura normala, `ID_VANZARE_AVIZ` (nume generic, refolosit) e ea +insasi, iar `ID_VANZARE_FACT` gasit prin acea interogare e factura(le) de retur emise pe baza ei. +Invers, pentru un document de retur dat, `SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP WHERE +ID_VANZARE_FACT = :id_retur AND TIP = 3` da **toate** facturile sursa alese la emiterea acelui retur +(poate fi mai multe, cf. selectie multipla din `caut_facturi_multiple_client`, +`docs/cercetare/factura_retur_document.md:67-85`). + +**Limita exacta**: cand pe un document de retur exista o singura factura sursa in `VANZARI_CORESP`, +"factura originala" e determinata fara ambiguitate pentru **toate** liniile documentului de retur +(pentru ca nu exista alt candidat). Cand exista mai multe (utilizatorul a bifat 2+ facturi la +cautare), `VANZARI_CORESP` da multimea, dar nu se poate spune care linie de retur vine din care +factura din multime — informatia care ar face diferenta (ID_VANZARE per linie in cursorul de +populare) a fost deja aruncata la pasul 1/2. Nu exista niciun tabel `VANZARI_RETUR`, `RETUR_DETALII` +sau `LEGATURI_DOCUMENTE`; cautate explicit in `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si in +`COMUN\docs\` — zero potriviri. + +`DOCUMENTE.AVIZE` (coloana denormalizata pe `VANZARI`, nu tabel separat) e completata redundant tot +din `VANZARI_CORESP` (`scrie_corespondente_vanzari:15505-15514`, `UPDATE VANZARI SET AVIZE = ...`) — +tine text afisabil (serie+numar avize), nu un id structurat, si nu e populata pentru `TIP=3` (doar +pentru avize, vezi apelul unic la acel `UPDATE` in corpul procedurii, comun tuturor `V_TIP`-urilor +dar cu sens practic doar pentru avize-spre-factura). + +## 4. Cum calculeaza serverul maximul returnabil + +**Raspunsul e diferit pe cele doua mecanisme, si niciunul nu se sprijina pe o legatura persistata +linie-la-linie:** + +**N.1 (document)**: NU exista un calcul de "cat s-a mai returnat deja". Coloana pe care utilizatorul +o vede ca "Cant. max. de returnat" (`ofacturare.vc2:15238,15243` — doar schimbare de caption pe capul +de coloana, nu logica noua) e pur si simplu `CANTITATE` a liniei originale, asa cum vine din +`cursor_retur_document` (`A1.CANTITATE`, `:4036`, filtrat doar pe `A1.STERS = 0`, fara nicio +agregare cu alte retururi anterioare pe aceeasi linie). Validarea din +`do_verifica_articol` (`:14743-14754`) nu face niciun calcul suplimentar de disponibil — blocheaza +doar cazul `tnCantitate >= 0` cand esti in mod retur (semnul gresit), nu o depasire de maxim istoric. +**Consecinta directa**: daca acelasi utilizator emite doua facturi de retur separate pe aceeasi +factura sursa, a doua interogare `cursor_retur_document` va aduce din nou linia originala cu +`CANTITATE` **intreaga**, neredusa de primul retur — nimic in cod nu scade sau marcheaza cat s-a +returnat deja la acest nivel. Acesta e cel mai clar indiciu ca legatura linie-la-linie *nu* e tinuta +nicaieri pentru N.1: daca ar fi fost tinuta, ar fi trebuit folosita exact aici, ca sa capeze +cantitatea, si nu e. + +**N.2 (`But_retur`)**: exista un calcul real de disponibil, dar e un calcul de **stoc/rulaj**, nu de +"cat s-a returnat pe acea linie". Ramura din `pack_facturare` citata la punctul 1 +(`ff_...sql:8142-8212`) calculeaza cantitatea inca prezenta in `RUL` (miscari de stoc) care a venit +din vanzarea aleasa (`RUL.COD` legat prin `VANZARI.COD`, filtrat pe `id_articol:id_vanzare` din +`listaid`), plus `STOC`/`RUL_TEMP` pentru cazul general. E un calcul valid de "poti scoate din +gestiune atat cat inca exista acolo cu provenienta asta", sprijinit pe legatura persistata +`RUL.COD -> VANZARI.COD` (asta e adevarata "legatura care exista" pentru N.2) — dar e o legatura la +nivelul miscarii de stoc, nu un contor "cantitate returnata" pe `VANZARI_DETALII`, si nu se +translateaza in nicio coloana de provenienta pe linia noua scrisa. + +## 5. Ce se poate afisa efectiv + +- **Numarul/seria facturii originale la nivel de document de retur, cand exista o singura factura + sursa**: SE POATE, din `VANZARI_CORESP` (`TIP=3`) join `VANZARI` pe `ID_VANZARE_AVIZ`. Interogare + de rulat pe baza vie (nu verificata aici, doar formulata): + ```sql + SELECT v.serie_act, v.numar_act + FROM VANZARI_CORESP c JOIN VANZARI v ON v.ID_VANZARE = c.ID_VANZARE_AVIZ + WHERE c.ID_VANZARE_FACT = :id_document_retur AND c.TIP = 3 AND c.STERS = 0; + ``` + Daca randul e unic, se poate afisa "provine din factura X" **pe capul documentului de retur** + (nu pe linie — toate liniile ar arata aceeasi sursa, pentru ca e singura posibila). +- **Cand documentul de retur are 2+ facturi sursa** (selectie multipla permisa de dialog): se poate + afisa lista de facturi sursa posibile (tot din `VANZARI_CORESP`), dar NU care linie vine din care + factura din lista — informatia nu exista. +- **Numarul facturii originale pe fiecare linie individuala de retur**: NU SE POATE, in niciun caz — + nici cand exista un singur document sursa (pentru ca "linie cu linie" nu inseamna nimic diferit de + "documentul cu documentul" atunci, dar afirmatia stricta "aceasta linie de retur vine din linia Y a + facturii" nu poate fi demonstrata din date, doar presupusa cand exista un singur candidat), si sigur + nu cand exista mai multe facturi sursa sau cand linia e libera (permisa explicit de decizia 22). +- Pentru N.2 (retur in factura normala, tip 1/5/7/10): nu exista deloc "document de retur" separat de + arata provenienta — linia de retur e o linie normala (cu cantitate negativa) in factura curenta; + singura urma a sursei e `RUL.COD`/`VANZARI.COD` folosita tranzitoriu la calculul de disponibil, nu + persistata pe linia noua din `VANZARI_DETALII`. + +## Verdict pentru plan (S4f) + +**NU se persista legatura linie-la-linie.** Pentru documentul de retur (N.1), se poate afisa +"factura sursa" **la nivel de document**, din `VANZARI_CORESP` (TIP=3), **doar cand exista exact o +singura factura sursa aleasa** — cazul cu selectie multipla da doar multimea, fara atribuire per +linie. Pe linia individuala de retur nu se poate afisa nimic verificabil din date, in niciun caz. +Recomandare pentru S4f: se livreaza fara coloana de provenienta pe linie (cf. deciziei deja scrise in +plan, "nu se inventeaza"); daca se vrea un gest minim, singura afisare sustenabila cu date e un text +de tip "Retur pentru factura: X" pe **capul** documentului (deja exista, cf. +`factura_retur_document.md:85`: `poDate.text_aditional = ... + poDate.descriere`, unde +`poDate.descriere` e lista serie+numar a facturilor alese) — nu pe linie, si nu nou, e deja acolo. + +## Ramas de verificat pe baza vie + +- Nu s-a interogat live daca `VANZARI_CORESP.TIP=3` e scris consecvent pentru fiecare factura de + retur emisa istoric (posibil sa existe date vechi scrise inainte ca aceasta ramura sa existe, sau + prin alt cod neexaminat aici) — de rulat: + ```sql + SELECT COUNT(*) FROM VANZARI v + WHERE v.TIP IN (8,9) AND v.STERS = 0 + AND NOT EXISTS (SELECT 1 FROM VANZARI_CORESP c WHERE c.ID_VANZARE_FACT = v.ID_VANZARE AND c.TIP = 3); + ``` + Daca da >0, exista facturi de retur fara nicio legatura de document persistata, nici macar cea + partiala descrisa mai sus. +- Nu s-a verificat live cate din facturile de retur existente au >1 factura sursa in + `VANZARI_CORESP` (adica cate din documentele reale ar cadea in cazul "multime, nu atribuire") — + utila pentru a decide cat de des s-ar aplica de fapt afisarea propusa la punctul 5. +- Nu s-a gasit (si nici nu era in scop) un mecanism separat pentru avize de retur custodie (`tip=50`, + marcat "in lucru" in `tipuri_documente_facturare.md`) — daca S4f ajunge sa acopere si acel tip, de + recercetat separat. +- Subagentul de research auxiliar lansat pentru confirmarea independenta a definitiei + `caut_facturi_multiple_client`/`caut_facturi_multiple_client_articol` nu a returnat un raport + utilizabil (rezultat gol la finalizare); definitiile au fost gasite si citite direct in aceasta + sesiune, in `COMUN\programe\oproceduri_facturare.prg:2091-2160`, deci nu blocheaza verdictul, dar + nu a adaugat nimic peste ce e deja in acest raport. diff --git a/docs/cercetare/linii_comanda_articol_nemembru.md b/docs/cercetare/linii_comanda_articol_nemembru.md new file mode 100644 index 0000000..048fca1 --- /dev/null +++ b/docs/cercetare/linii_comanda_articol_nemembru.md @@ -0,0 +1,182 @@ +# Linii de comanda cu articol nemembru al politicii de pret — verificare facturare + +Status: FINALIZAT. + +## Verdict + +**DA — s-a facturat cel putin o linie fara ca FACT-024 sa se declanseze.** Comanda `497`, linia +`4294507522` (VOUCHER DISCOUNT) / politica `7` (DISCOUNT), a fost facturata cu succes in factura +`ID_VANZARE=1028` (serie `SSS`, numar `535`, `FACTURAT=1`, `DATA_FACTURAT=2026-03-20`), desi +articolul **nu e membru** al politicii 7 in `CRM_POLITICI_PRET_ART` la data acestei cercetari +(10.08.2026). **Decizia 32 se REDESCHIDE partial**: nu pentru ca ar exista o cale de cod care ocoleste +verificarea (codul chiar cheama `contabilizeaza_articol` pentru acest tip de vanzare, cf. punctul 3), +ci pentru ca datele arata ca membru-ul politicii **s-a schimbat dupa facturare** — vezi punctul 4. +Pentru celelalte 34 de linii cu acelasi articol/politica (35 in total cu `CANTITATE<0`), **nu exista +nicio factura, nici macar stearsa** — la ele fisura ramane doar o stare inconsistenta nefacturata +(vezi punctul 1). + +Observatie structurala separata de cea de mai sus: **inca 2 linii** (comenzile 114 si 115, alt articol, +alta politica) au `CANTITATE>0`, adica **nefacturate inca deloc** — pentru acestea afirmatia "ar trebui +sa cada la facturare" e neverificata pentru ca nu au fost niciodata trecute prin `contabilizeaza_articol`. + +## 1. S-a facturat vreuna din ele fara eroare? + +Interogare: pentru fiecare din cele 37 linii `COMENZI_ELEMENTE` cu `ID_POL NOT NULL` si articol +nemembru in `CRM_POLITICI_PRET_ART`, am cautat linii `VANZARI_DETALII` (prin `VANZARI.ID_COMANDA`) +cu acelasi `ID_ARTICOL`+`ID_POL`, **inclusiv cele sterse** (ca sa nu ratez o factura anulata): + +```sql +select ce.id_comanda_element, ce.id_comanda, ce.id_articol, ce.id_pol, ce.cantitate, + (select count(*) from vanzari v join vanzari_detalii vd on vd.id_vanzare=v.id_vanzare + where v.id_comanda = ce.id_comanda and vd.id_articol=ce.id_articol and vd.id_pol=ce.id_pol) as nr_total_incl_sters +from comenzi_elemente ce +where ce.id_pol is not null and ce.cantitate < 0 +and not exists (select 1 from crm_politici_pret_art cppa + where cppa.id_pol = ce.id_pol and cppa.id_articol = ce.id_articol); +``` + +Rezultat: **doar comanda 497** are potriviri (2 linii `VANZARI_DETALII`, id 1494 si 1495, ambele +`STERS=0`, `CANTITATE=-1` fiecare, apartinand facturii `ID_VANZARE=1028`). Toate celelalte 34 de +comenzi (349, 351, 352, 359, 360, 377, 388, 404, 412, 417, 429, 443, 447, 450, 451, 453, 455, 456, +457, 466, 471, 475, 486, 500, 501) au **0 potriviri**, inclusiv sters. + +**Interpretare structurala a mecanismului** (cititul codului, nu presupunere): singurul loc care +insereaza randuri cu `CANTITATE<0` in `COMENZI_ELEMENTE` e procedura `inchide_comanda` +(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:5769-5820`): + +```sql +INSERT INTO COMENZI_ELEMENTE (... CANTITATE ...) + SELECT ..., NVL(C.CANTITATE, 0) + NVL(B.CANTITATE, 0) - A.CANTITATE AS CANTITATE, ... + FROM COMENZI_ELEMENTE A + LEFT JOIN (... FROM VANZARI_DETALII_TEMP ...) B ON ... -- lotul curent de facturare + LEFT JOIN (... FROM VANZARI JOIN VANZARI_DETALII ...) C ON ... -- deja facturat anterior + WHERE A.ID_COMANDA = :comanda + AND SIGN(A.CANTITATE) * A.CANTITATE > + SIGN(A.CANTITATE) * (NVL(C.CANTITATE, 0) + NVL(B.CANTITATE, 0)); +``` + +Asta insemna ca randul negativ **nu dovedeste singur ca linia a fost facturata** — el reprezinta +*diferenta ramasa* fata de cantitatea comandata, la momentul **inchiderii** comenzii, chiar daca +`B` (lotul curent) si `C` (facturat anterior) sunt amandoua 0. O comanda inchisa fara sa i se +factureze o anumita linie tot primeste un rand `CANTITATE = -A.CANTITATE`. De-asta 34 din 35 de +linii "negative" nu au nicio factura in spate: comenzile respective au fost **inchise** (probabil +manual, ca "restanta anulata"), nu facturate pe acea linie. **Zero cazuri in date pentru acestea nu +demonstreaza ca nu se poate factura — demonstreaza doar ca nu s-a intamplat.** + +Pentru comanda 497 insa, potrivirea cu 2 linii reale, active, `FACTURAT=1` in `VANZARI_DETALII` +e dovada directa (nu inferenta) ca acea combinatie articol/politica **a ajuns** intr-o factura emisa. + +## 2. Reconfirmarea cifrei si lista liniilor + +Cifra **37** se reconfirma exact (nu 6868/nemembru cum sugera masuratoarea anterioara — aceea era +alt numitor; numitorul corect pentru linii cu `ID_POL NOT NULL` e **7108**, nu 6868): + +```sql +select count(*) from comenzi_elemente ce where ce.id_pol is not null; -- 7108 +select count(*) from comenzi_elemente ce where ce.id_pol is not null + and not exists (select 1 from crm_politici_pret_art cppa + where cppa.id_pol = ce.id_pol and cppa.id_articol = ce.id_articol); -- 37 +``` + +Compozitie (verificata, `select sign(cantitate), count(*) ... group by sign(cantitate)`): +- **35 linii** cu `CANTITATE=-1`: acelasi articol/politica in toate — `ID_ARTICOL=4294507522` + ("VOUCHER DISCOUNT"), `ID_POL=7` ("DISCOUNT"). Comenzi: 349(x2), 351, 352(x2), 359, 360(x2), 377, + 388, 404, 412(x2), 417, 429, 443, 447, 450, 451, 453, 455, 456, 457, 466, 471(x2), 475(x2), + 486(x2), 497(x2), 500, 501(x2). +- **2 linii** cu `CANTITATE=+10` (nefacturate inca, nicio urma de consum): + - comanda 114, `ID_ARTICOL=4294507508` ("CAFEA TEST - PRODUS TEST PENTRU IMPORT WEB"), `ID_POL=2` + ("LISTA 2"), data comanda 2025-09-10, `COMENZI.STERS` neverificat suplimentar dar randul e + prezent activ. + - comanda 115, `ID_ARTICOL=4171144217` ("CAFEA 1"), `ID_POL=2`. **Anomalie separata**: `COMENZI` + nu contine niciun rand cu `ID_COMANDA=115` — antetul comenzii lipseste, doar linia de element a + supravietuit. NEDETERMINABIL DIN DATE de ce (nu exista audit/istoric pe `COMENZI`); posibil + date de test/import, dat fiind ca articolul insusi e "CAFEA TEST ... PENTRU IMPORT WEB" pe + cealalta linie similara. + +Toate cele 37 de linii au `COMENZI_ELEMENTE.STERS=0` (nicio linie de comanda marcata stearsa). + +Stare de facturare per linie: singura cu urma reala de facturare e comanda 497 (vezi punctul 1); +restul de 36 sunt **nefacturate** pe aceasta combinatie articol/politica. + +## 3. Verificare cod FACT-024 (`contabilizeaza_articol`) + +Blocul citat in brief (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7278-7302`) e identic cu ce ruleaza +in productie — verificat direct in codul compilat, nu doar in scriptul istoric (fisierul `.sql` e un +istoric cumulativ cu **doua definitii** ale `contabilizeaza_articol` in text, la linia 746 si la +7173; doar a doua e cea vie): + +```sql +select owner, line from all_source + where name='PACK_FACTURARE' and type='PACKAGE BODY' + and text like '%FACT-024%'; +``` +confirma acelasi text de eroare in schema `MARIUSM_AUTO` (Dev). + +Verificarea foloseste view-ul `VCRM_POLITICI_PRET_ART`: +```sql +SELECT COMPUS, ID_POL_ART INTO ... FROM VCRM_POLITICI_PRET_ART + WHERE ID_ARTICOL = detalii_articol.id_articol AND ID_POL = detalii_articol.id_pol; +EXCEPTION WHEN NO_DATA_FOUND THEN ... RAISE_APPLICATION_ERROR(-20000, ... '(FACT-024)'); +``` +Am verificat textul view-ului (`user_views`): `VCRM_POLITICI_PRET_ART` are `CRM_POLITICI_PRET_ART PA` +ca tabel-sursa (driving table) al tuturor `LEFT JOIN`-urilor, fara niciun `WHERE` care sa filtreze +`PA` (nu exista coloana `STERS` pe `CRM_POLITICI_PRET_ART`). Deci view-ul are exact acelasi set de +perechi `(ID_POL, ID_ARTICOL)` ca tabelul de baza — verificarea mea prin `NOT EXISTS` pe +`CRM_POLITICI_PRET_ART` e echivalenta cu ce evalueaza codul. **Concluzie: pentru toate cele 37 de +linii, `SELECT ... INTO` ar da `NO_DATA_FOUND` -> `FACT-024`, daca ar trece prin +`contabilizeaza_articol` ACUM, cu starea curenta a `CRM_POLITICI_PRET_ART`.** + +Apelul e neconditionat pentru facturi standard: am cautat toate apelurile +`pack_facturare.contabilizeaza_articol(tab_detalii(i))` in codul **compilat** (`all_source`, schema +`MARIUSM_AUTO`) — 6 aparitii, linii 4877, 4901, 5594, 5618, 5876, 5900. Linia 4901 e in procedura +`scrie_factura2` (facturarea principala), in ramura `ELSE` a unui `CASE pack_facturare.ntip` care +exclude doar transferurile intre subunitati (23,25,30,41), avizele de custodie (42,47) si facturile +cu rate (2,6,52 cu `id_rata<>0`) — **tip 3 (tipul facturii 1028) cade in ELSE, deci +`contabilizeaza_articol` chiar s-a executat pentru acea linie.** + +Prin urmare, pentru comanda 497: fie (a) la data facturarii (20.03.2026) articolul 4294507522 CHIAR +era membru al politicii 7 si a fost scos ulterior din `CRM_POLITICI_PRET_ART`, fie (b) verificarea a +fost ocolita altfel. Nu am gasit dovada de tip (b) — vezi punctul 4. + +## 4. Cauza + +`CRM_POLITICI_PRET_ART` **nu are coloana `STERS`** si nu exista niciun tabel de istoric/audit pentru +ea (`select table_name from user_tables where table_name like '%POLITICI%' or ... '%AUDIT%'` — doar +tabelele de configurare curenta, niciunul de istoric). Stergerile din aceasta tabela sunt deci +**fizice, fara urma**. Nu pot reconstitui direct daca perechea (politica 7, articol 4294507522) a +existat la 20.03.2026. + +Coloanele de audit disponibile pe randurile facturii/comenzii **nu arata nicio editare ulterioara**: +`VANZARI.ID_UTILS`/`DATAORAS` si `VANZARI_DETALII.ID_UTILS`/`DATAORAS` (1494, 1495) sunt toate NULL — +niciun `UPDATE` standard nu a atins aceste randuri dupa creare. Deci nu exista dovada ca linia de +factura ar fi fost modificata ulterior prin fluxul nou de "editare factura emisa" (adaugat recent in +proiect, cf. changelog 2.11.15) — desi asta nu exclude o editare care ar fi ocolit acele coloane de +audit, doar ca nu am gasit dovada ei. + +Indiciu indirect, dar nu concludent: articolul 4294507522 e denumit **"VOUCHER DISCOUNT"**, iar +politica 7 e **"DISCOUNT"** — o pereche generica folosita ca linie de discount pe **35 de comenzi +diferite**, nu un caz izolat. Reutilizarea masiva a exact aceleiasi perechi sustine ipoteza unei +**modificari de catalog** (articolul-voucher scos ulterior din lista politicii DISCOUNT ca decizie de +business/curatare), mai degraba decat 35 de erori de introducere independente. **Aceasta ramane insa +o inferenta din tipar, nu o dovada directa — se marcheaza NEDETERMINABIL DIN DATE strict pe cauza si +data exacta a schimbarii.** + +## Ce am incercat si ce a esuat + +- Doua incercari de a delega cautarea codului sursa VFP/PL-SQL unui subagent `Explore` au esuat cu + raspunsuri goale/neinformative ("Nimic nou.", "No new input to act on.") — fara nicio dovada ca ar + fi rulat vreo cautare. Am renuntat la delegare si am facut cautarile direct cu Grep si Read. +- Interogarea `all_source` fara `owner=` a intors linii duplicate/interclasate (schema `ACN` are de + asemenea un `PACK_FACTURARE`) — a trebuit filtrat explicit pe `owner='MARIUSM_AUTO'` pentru + rezultate coerente. +- Un `prompt '---text---'` intre doua instructiuni SQL in acelasi fisier `.sql` a produs iesire + amestecata (headerul s-a lipit de urmatoarea instructiune) — am renuntat la `prompt` si am rulat + interogarile in fisiere separate. + +## Date tehnice folosite + +- Conexiune: `MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL` (Dev), via + `D:\ROA\instantclient_19_18\sqlplus.exe`, fisiere `.sql` ASCII in scratchpad, niciun `INSERT`/`UPDATE`/`DDL` rulat. +- Cod verificat: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` + (text istoric cumulativ) si pachetul compilat `MARIUSM_AUTO.PACK_FACTURARE` (`all_source`, sursa de + adevar pentru ce ruleaza efectiv). diff --git a/docs/cercetare/modifica_date_factura_parametri.md b/docs/cercetare/modifica_date_factura_parametri.md new file mode 100644 index 0000000..53b925e --- /dev/null +++ b/docs/cercetare/modifica_date_factura_parametri.md @@ -0,0 +1,124 @@ +# Cercetare: `pack_facturare.modifica_date_factura` — parametri, mapare pe coloane si pe controale + +Sursa pachetului: `D:\ROA\ROAACNPRO\PACK_FACTURARE.pck` (declaratie spec `:921-935`, corp `:14392-14462`). +Apel VFP: `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_modifica` (`:4538-4637`), +apelul efectiv la `:4599-4614`. Formular de editare: `frm_modifica_factura` (`:5262-5774`). + +Nu exista alt fisier `.pck`/`.sql` cu semnatura diferita pentru `modifica_date_factura` in +`D:\ROA\ROAFACTURARE` sau `COMUN`; niciun alt loc din VFP (in afara de `ofacturare_comun.vc2` si +copia identica `.bak`) nu apeleaza procedura — cautat cu grep pe `*.vc2/*.sc2/*.prg` in tot arborele. + +## 1-2. Tabel parametri (semnatura + destinatie in Oracle) + +| # | Parametru | Tip (`.pck:921-935`) | Coloana / tabel scrise in corp (`.pck:14392-14462`) | Observatii | +|---|---|---|---|---| +| 1 | `V_ID_VANZARE` | `NUMBER` | Folosit doar in `WHERE ID_VANZARE = V_ID_VANZARE` (`:14424`) si ca sursa pentru `lnIdFact` (`:14426`) | Identitate randului, niciodata scris | +| 2 | `V_ID_RUTA` | `NUMBER` | `VANZARI.ID_RUTA` (`:14414`) | Scris neconditionat | +| 3 | `V_ID_DELEGAT` | `NUMBER` | `VANZARI.ID_DELEGAT` (`:14415`) | Scris neconditionat | +| 4 | `V_ID_AGENT` | `NUMBER` | `VANZARI.ID_AGENT` (`:14416`) | Scris neconditionat | +| 5 | `V_ID_MASINA` | `NUMBER` | `VANZARI.ID_MASINA` (`:14417`) | Scris neconditionat | +| 6 | `V_DATAORA_EXP` | `DATE` | `VANZARI.DATAORA_EXP` (`:14418`) | Scris neconditionat | +| 7 | `V_ID_FACTURARE` | `VANZARI.ID_FACTURARE%TYPE` | `VANZARI.ID_FACTURARE` (`:14419`) | Scris neconditionat | +| 8 | `V_LISTARE_DETALIATA` | `VANZARI.LISTARE_DETALIATA%TYPE` | `VANZARI.LISTARE_DETALIATA = NVL(V_LISTARE_DETALIATA, 0)` (`:14420`) | NVL la 0 | +| 9 | `V_TEXT_ADITIONAL` | `VANZARI.TEXT_ADITIONAL%TYPE` | `VANZARI.TEXT_ADITIONAL` (`:14421`) | Scris neconditionat, fara NVL | +| 10 | `V_TIP_SAFT` | `VANZARI.TIP_SAFT%TYPE DEFAULT NULL` | `VANZARI.TIP_SAFT` (`:14422`) | Scris neconditionat | +| 11 | `V_EFACTURA` | `VANZARI.EFACTURA%TYPE DEFAULT NULL` | `VANZARI.EFACTURA` (`:14423`) | Scris neconditionat | +| 12 | `V_DATA_ACT` | `VANZARI.DATA_ACT%TYPE DEFAULT NULL` | `VANZARI.DATA_ACT` (`:14448`) + `DOCUMENTE.DATAACT`, `ACT.DATAACT`, `IREG_PARTENERI.DATAACT`, `JV2007.DATAACT`, `RUL.DATAACT` si `RUL.DATAOUT` (`:14449-14453`), toate `WHERE ID_FACT = lnIdFact` | Doar daca `V_DATA_ACT IS NOT NULL AND V_DATA_ACT <> NVL(ldDataAct, SYSDATE)` (`:14447`) | +| 13 | `V_DATA_SCAD` | `VANZARI.DATA_SCAD%TYPE DEFAULT NULL` | `VANZARI.DATA_SCAD` (`:14457`) + `ACT.DATASCAD`, `IREG_PARTENERI.DATASCAD` (`:14458-14459`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_DATA_SCAD IS NOT NULL AND V_DATA_SCAD <> NVL(ldDataScad, sysdate)` (`:14456`) | +| 14 | `V_NUMAR_ACT` | `VANZARI.NUMAR_ACT%TYPE DEFAULT NULL` | `VANZARI.NUMAR_ACT` (`:14439`) + `DOCUMENTE.NRACT`, `ACT.NRACT`, `IREG_PARTENERI.NRACT`, `JV2007.NRACT`, `RUL.NRACT` (`:14440-14444`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_NUMAR_ACT IS NOT NULL AND V_NUMAR_ACT <> NVL(lnNrAct, 0)` (`:14438`) | +| 15 | `V_SERIE_ACT` | `VANZARI.SERIE_ACT%TYPE DEFAULT NULL` | `VANZARI.SERIE_ACT` (`:14429`) + `DOCUMENTE.SERIE_ACT`, `ACT.SERIE_ACT`, `IREG_PARTENERI.SERIE_ACT`, `JV2007.SERIE_ACT`, `RUL.SERIE_ACT` (`:14430-14434`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_SERIE_ACT IS NOT NULL AND V_SERIE_ACT <> NVL(lcSerieAct, '')` (`:14428`) | + +Parametrii 2-11 se scriu neconditionat in `VANZARI` la fiecare apel (chiar cu `NULL`, daca asa vine +argumentul). Parametrii 12-15 (`data_act`, `data_scad`, `numar_act`, `serie_act`) au tratament +conditionat: se propaga si in `DOCUMENTE`/`ACT`/`IREG_PARTENERI`/`JV2007`/`RUL` (unde e cazul), +filtrate dupa `ID_FACT` (nu dupa `ID_VANZARE`) — deci ating toate randurile din acele tabele legate +de acelasi document, nu doar randul `VANZARI` curent. Se scriu doar daca valoarea trimisa e +`NOT NULL` si difera de valoarea curenta. + +## 3. Apelul din VFP + +`COMUN\clase\ofacturare_comun.vc2:4599-4614`, in interiorul unui `SCAN` peste `crsfacturi` (una sau +mai multe inregistrari alese): + +``` +begin pack_facturare.modifica_date_factura(<>, + <>, + <>, + <>, + <>, + to_date('<>','YYYYMMDDHH24:MI:SS'), + <>, + <>, + ?poRec.text_aditional, + ?poRec.tip_saft, + ?poRec.efactura, + ?poRec.data_act, + ?poRec.data_scad, + ?poRec.numar_act, + ?poRec.serie_act); + end; +``` + +Toate argumentele vin din structura `poRec`, populata printr-un `Scatter Name poRec Memo` din +cursorul `crsfacturi` la intrarea in `do_modifica` (`:4538-4581`), apoi editata prin +`frm_modifica_factura` cat timp e afisat (`:4588-4590`), inainte de executia `SCAN`-ului. + +Observatie de comportament: cand sunt alese mai multe inregistrari (`lnNrInreg > 1`), ramura +`Otherwise` (`:4565-4580`) face `Scatter Name poRec Memo Blank` si reseteaza explicit +`id_ruta`, `id_delegat`, `id_agent`, `id_masina`, `dataora_exp`, `id_facturare`, +`listare_detaliata`, `tip_saft`, `text_aditional`, `efactura` la valori goale/`NULL`/`0` — +campurile `data_act`/`data_scad`/`numar_act`/`serie_act` raman goale prin acelasi `NVL`. Daca +formularul nu le modifica explicit, `SCAN`-ul trimite aceste valori goale la **fiecare** inregistrare +aleasa (parametrii 2-11 se scriu neconditionat in corpul procedurii — vezi tabelul de mai sus). + +## 4. Mapare parametru -> control -> fereastra + +| Parametru | Control | Fereastra | Observatii | +|---|---|---|---| +| `V_ID_VANZARE` | — | — | Nu are control; e identitatea randului din `crsfacturi` (`poRec.id_vanzare`) | +| `V_ID_RUTA` | `Ct_clb_ruta` (`ControlSource` prin `cprocedura=thisform.do_cauta_ruta`, seteaza `poRec.id_ruta`) | `frm_modifica_factura` (`:5510-5526`, cautare `:5695-5721`) | | +| `V_ID_DELEGAT` | `Ct_clb_delegat` (`do_cauta_delegat` seteaza `poRec.id_delegat`) | `frm_modifica_factura` (`:5473-5489`, cautare `:5654-5678`) | | +| `V_ID_AGENT` | `Ct_clb_agent` (`do_cauta_agent` seteaza `poRec.id_agent`) | `frm_modifica_factura` (`:5455-5471`, cautare `:5639-5652`) | | +| `V_ID_MASINA` | `Ct_clb_masina` (`do_cauta_masina` seteaza `poRec.id_masina`) | `frm_modifica_factura` (`:5491-5508`, cautare `:5680-5693`) | | +| `V_DATAORA_EXP` | `Clb_dataora_exp.Text_simplu1` (`ControlSource="poRec.dataora_exp"`) | `frm_modifica_factura` (`:5437-5453`) | validat obligatoriu in `inainte_de_do_termin` (`:5723-5734`) | +| `V_ID_FACTURARE` | `clb_adresa_facturare` (`do_cauta_adresa` seteaza `poRec.id_facturare` + `poRec.adresa_facturare`) | `frm_modifica_factura` (`:5418-5435`, cautare `:5621-5637`) | | +| `V_LISTARE_DETALIATA` | `chkDetaliat` (`ControlSource="poRec.listare_detaliata"`) | `frm_modifica_factura` (`:5379-5390`) | | +| `V_TEXT_ADITIONAL` | `Ed_tx_simplu1._EDBASE1` (`ControlSource="poRec.text_aditional"`, `MaxLength=1000`) | `frm_modifica_factura` (`:5528-5551`) | comentariul VFP la `:4575` spune "MAXIM 250 CARACTERE", dar `MaxLength` control e 1000 — contradictie neverificata mai departe | +| `V_TIP_SAFT` | `cboTipFactura` (`ControlSource="poRec.tip_saft"`, `RowSource` din `saft_tip_facturi`) | `frm_modifica_factura` (`:5335-5351`) | `Visible = m.gl406` (`:5739-5741`), deci controlul e ascuns cand `gl406` e fals (`gl406` definit in `COMUN\programe\oinit_optiuni.prg:524`) | +| `V_EFACTURA` | **niciun control** | — | `poRec.efactura` vine doar din `Scatter` initial pe `crsfacturi` (cazurile `lnNrInreg=0/1`, `:4538-4564`) sau e fortat `0` la selectie multipla (`:4576`); formularul `frm_modifica_factura` nu are niciun obiect legat de `poRec.efactura` — nu poate fi schimbat de utilizator din acest formular azi | +| `V_DATA_ACT` | `txtDataAct` (`ControlSource="poRec.data_act"`) | `frm_modifica_factura` (`:5572-5581`) | vezi blocaj la punctul 5 | +| `V_DATA_SCAD` | `txtDataScad` (`ControlSource="poRec.data_scad"`) | `frm_modifica_factura` (`:5583-5592`) | vezi blocaj la punctul 5 | +| `V_NUMAR_ACT` | `txtNrAct` (`ControlSource="poRec.numar_act"`) | `frm_modifica_factura` (`:5594-5603`) | vezi blocaj la punctul 5 | +| `V_SERIE_ACT` | `txtSerieAct` (`ControlSource="poRec.serie_act"`) | `frm_modifica_factura` (`:5605-5614`) | vezi blocaj la punctul 5 | + +Nu exista niciun parametru mapat pe `frm_alte_date` (`COMUN\clase\ferestre_cere_date.vc2:2219`) — +clasa aceea nu e implicata deloc in fluxul `do_modifica`/`modifica_date_factura`. + +## 5. Parametri blocati/needitabili azi in `frm_modifica_factura` + +Patru checkbox-uri controleaza accesul la seria/numarul/datele actului, si patru textbox-uri asociate: + +- `chkSerieAct`, `chkNrAct`, `chkDataAct`, `chkDataScad`: definite cu `Enabled = .F.` + (`:5405-5416`, `:5392-5403`, `:5353-5364`, `:5366-5377`). Devin `Enabled` doar in `PROCEDURE Init` + (`:5743-5746`): + ``` + this.chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0)) + this.chkNrAct.Enabled = !EMPTY(NVL(poRec.numar_act,0)) + this.chkDataAct.Enabled = !EMPTY(NVL(poRec.numar_act,0)) + this.chkDataScad.Enabled = !EMPTY(NVL(poRec.numar_act,0)) + ``` + adica doar daca factura are deja un numar de act completat (`poRec.numar_act` nenul) la + deschiderea formularului. Pe o factura fara act (proforma/nefinalizata), toate patru raman + needitabile. +- `txtSerieAct`, `txtNrAct`, `txtDataAct`, `txtDataScad`: definite cu `Enabled = .F.` + (`:5605-5614`, `:5594-5603`, `:5572-5581`, `:5583-5592`) si se activeaza doar din evenimentul + checkbox-ului corespunzator — `chkSerieAct.Valid` (`:5770-5772`), `chkNrAct.Valid` + (`:5766-5768`), `chkDataAct.Valid` (`:5758-5760`), `chkDataScad.Click` (`:5762-5764`) — deci + necesita ca checkbox-ul insusi sa fie deja `Enabled` (conditia de mai sus). +- `V_EFACTURA`: fara control — needitabil in acest formular indiferent de stare (punctul 4). +- `V_ID_VANZARE`: nu e un camp de editat, e identitatea randului. + +Toti ceilalti parametri (`id_ruta`, `id_delegat`, `id_agent`, `id_masina`, `dataora_exp`, +`id_facturare`, `listare_detaliata`, `text_aditional`, `tip_saft`) au controale mereu `Enabled` +(fara restrictie in cod), `tip_saft` fiind conditionat doar de vizibilitate (`gl406`), nu de +`Enabled`. diff --git a/docs/cercetare/nota_contabila_fara_politica.md b/docs/cercetare/nota_contabila_fara_politica.md new file mode 100644 index 0000000..bd68816 --- /dev/null +++ b/docs/cercetare/nota_contabila_fara_politica.md @@ -0,0 +1,224 @@ +# Nota contabila pentru articol fara politica de pret (proiect #13) + +Cercetare pe cod, fara modificari. Continua raportul anterior +`cont_venit_corespondente.md` (SCC vine prin lantul +`CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI.ID_NOTA -> CRM_NOTE_VANZARI.ID_SET -> NOTE_CONTABILE`, +citit de `cursor_articol` din `pack_facturare.contabilizeaza_articol`). + +Sursa: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` +(pachet `pack_facturare`, 17020 linii) si `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg` + +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql` (pachet `pack_auto`). + +--- + +## 1. Ce face `contabilizeaza_articol` cand `id_pol` e NULL/0 + +Raspuns: **ridica exceptia FACT-024 si intreruce functia, inainte sa ajunga la cursorul care +citeste SCC**. Nu exista o ramura de default si nu se scrie nota cu SCC NULL pentru acest caz. + +Corpul complet e la `ff_...:7182-7556`. Primul lucru pe care il face (inainte de `cursor_articol`, +inainte de orice `scrie_nota`) e: + +``` +ff_...:7286-7311 +BEGIN + SELECT COMPUS, ID_POL_ART + INTO V_COMPUS, V_ID_POL_ART + FROM VCRM_POLITICI_PRET_ART + WHERE ID_ARTICOL = detalii_articol.id_articol + AND ID_POL = detalii_articol.id_pol; +EXCEPTION + WHEN NO_DATA_FOUND THEN + SELECT DENUMIRE INTO lcArticol FROM NOM_ARTICOLE WHERE id_articol = detalii_articol.id_articol; + SELECT NUME_LISTA_PRETURI INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol; + RAISE_APPLICATION_ERROR(-20000, + 'Articolul ' || detalii_articol.id_articol || '|' || lcArticol || + ' nu este definit in politica de preturi ' || + detalii_articol.id_pol || '|' || lcPolitica || '! (FACT-024)'); +END; +``` + +`ID_POL = detalii_articol.id_pol` cu `id_pol` NULL nu poate potrivi niciun rand in SQL standard +(`X = NULL` e necunoscut, nu adevarat) — deci pentru `id_pol` NULL sau 0 (presupunand ca nu exista +o politica cu `ID_POL = 0`), `SELECT INTO` intra mereu pe `NO_DATA_FOUND`. + +**Observatie suplimentara, neceruta explicit dar relevanta**: in ramura de exceptie, al doilea +`SELECT ... INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol` sufera +de aceeasi problema — cu `id_pol` NULL, si acest `SELECT INTO` da tot `NO_DATA_FOUND`, care **nu e +tratat** in acest bloc (nu exista un al doilea handler). Deci mesajul FACT-024 "curat" (cu numele +politicii) nu s-ar afisa niciodata pentru cazul `id_pol IS NULL` — s-ar propaga o exceptie +`NO_DATA_FOUND` neprinsa din interiorul handler-ului, cu alt mesaj Oracle generic, nu FACT-024 cu +text. Codul articolului (`lcArticol`) apuca sa fie rezolvat inaintea acestui eșec, deci +identificarea articolului nu s-ar pierde in log/eroare, dar textul final ar fi ORA-01403, nu +mesajul FACT-024 formatat. Pentru `id_pol = 0` (nu NULL), daca exista vreo politica cu id 0, acel +`SELECT` ar reusi si mesajul FACT-024 ar iesi corect. + +Cursorul `cursor_articol` (care citeste SCD/ASCD/SCC/ASCC din `NOTE_CONTABILE` prin lant) **nu se +mai deschide** — funcția a ridicat deja exceptia mai sus. Deci intrebarea "cursorul intoarce zero +randuri, ce face codul mai departe" nu se pune in acest caz: nu ajunge acolo. + +Concluzie Q1: **orice linie de vanzare cu `id_pol` NULL/0 trimisa la `contabilizeaza_articol` in +starea actuala a codului arunca o eroare si opreste tranzactia** (nu scrie nota cu cont NULL, nu +pune default). Cerinta proiectului #13 ("articol din nomenclator fara politica de pret, pret +tastat manual, pe orice tip de document") ar lovi FACT-024 (sau exceptia neprinsa descrisa mai sus) +daca linia respectiva e trecuta prin acest cod neschimbat. + +--- + +## 2. Validare la `scrie_nota` pentru cont NULL + +Raspuns: **nu exista**. Corpul complet e la `ff_...:12338-12570`. + +- Singura verificare care ar fi atins asta e comentata: + ``` + ff_...:12441-12453 + /* IF V_SUMA IS NULL THEN + RAISE_APPLICATION_ERROR(...) ... + END IF;*/ + ``` + Si oricum verifica `V_SUMA` (suma calculata), nu `V_SCD`/`V_SCC`. +- `INSERT INTO ACT_TEMP (... SCD, ASCD, SCC, ASCC ...)` (`ff_...:12458-12544`) insereaza direct + `V_SCD`/`V_ASCD`/`V_SCC`/`V_ASCC` primite ca parametri, fara niciun `NVL`/`CASE`/verificare de + NULL pe ele. +- Nu exista `NOT NULL` verificat in cod pentru aceste coloane in acest fisier (constrangerea ar fi + la nivel de DDL al tabelei `ACT`/`ACT_TEMP`, care nu face parte din pachetul PL/SQL cercetat — + vezi sectiunea Neverificat). + +Deci daca cineva ar ocoli blocul de la punctul 1 (de ex. printr-un `id_pol` care *exista* in +`CRM_POLITICI_PRET_ART` dar al carui lant `CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE` +e incomplet, asa incat `D.SCC` iese NULL din LEFT JOIN), `scrie_nota` ar scrie nota cu `SCC = NULL` +fara sa protesteze. Asta e insa un scenariu diferit de "fara politica de pret" (aici politica exista, +dar lantul de sub ea e incomplet) — nu e cazul cerut de proiectul #13 asa cum a fost formulat. + +--- + +## 3. Toti apelantii lui `contabilizeaza_articol` in fisier + +Cautare exhaustiva `Grep 'contabilizeaza_articol'` pe tot fisierul: 3 apeluri (in afara de propria +definitie si un comentariu): + +| Linie | Procedura apelanta | Domeniu / flux | +|---|---|---| +| `ff_...:6150` | `scrie_factura2` (corp `ff_...:6029-6272`) | **Fluxul principal de scriere** — pentru orice document ale carui randuri nu cad in ramurile speciale ale `CASE`-ului de la `ff_...:6081-6151`: transfer intre subunitati (`ntip IN (23,25,30,41)`), factura/aviz custodie (`ntip IN (42,47)`), rata cu `id_rata<>0` (`ntip IN (2,6,52)`). Toate celelalte tipuri — **factura normala** (`ntip<=20`), hotel, restaurant, nota de plata, vanzare retail, tipurile 48/49/51/52, aviz simplu, aviz catre clienti debitori, si **aviz retur (tip 24)** cand e scris prin `scrie_factura2` — ajung la `contabilizeaza_articol` aici. Aceasta e apelarea "de baza" pe care se sprijina interpretarea corpului functiei facuta la punctul 1 (ramurile `CASE` din interiorul `contabilizeaza_articol`, `ff_...:7407-7432`, mapeaza direct pe tipurile de document tratate de acest apelant). | +| `ff_...:6867` | `scrie_factura_avize_retur` (corp `ff_...:6273-6666`) | Flux **factura scrisa pe baza de avize + retur simultan**. Apelul e in interiorul buclei pe `articole_aviz` (`ff_...:6841-6960` zona), pentru randurile cu `custodie = 1` (marfa in custodie ce se descarca din avize). | +| `ff_...:7149` | `scrie_aviz_retur` (corp `ff_...:7094-7180`) | Procedura dedicata **aviz retur** (`V_ID_DELEGAT, V_ID_MASINA, ...`). Apelul e in ramura `ELSE` a testului `pack_facturare.nfactavizcust = 1` (`ff_...:7124-7150`) — deci pentru randurile care **nu** sunt in custodie. | + +Concluzie Q3: **da**, `contabilizeaza_articol` e apelata pe fluxul principal de facturare +(`scrie_factura2`, care acopera factura normala si majoritatea celorlalte tipuri de document), nu +doar din `scrie_aviz_retur`. Raportul anterior lasase asta neverificat; e confirmat aici cu cele 3 +puncte de apel gasite si citate mai sus — nu exista un al 4-lea apel in fisier. + +--- + +## 4. Precedentul ROAAUTO "Alte servicii" — **nu e de fapt un precedent pentru cazul cerut** + +Asta contrazice premisa din cerere. Firul (`crsalteserv -> crsvanztemp -> adauga_articol_factura_deviz`) +duce spre un flux care **ocoleste complet `contabilizeaza_articol`**, deci nu demonstreaza ce se +intampla in `pack_facturare` pentru un articol fara politica — demonstreaza doar ca exista un *alt* +mecanism de contabilizare, in afara pachetului `pack_facturare`. + +Pasii, cu citate: + +1. **`crsalteserv -> crsvanztemp`** (`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1040-1041`): + ``` + Insert Into (lcCursorDeviz)(id_articol,denumire,explicatie,cantitate,um,id_jtva_coloana,proc_Tvav,taxcode,pret,discount_unitar) ; + Select id_articol,denumire,"",cantitate,um,CAST(lnIdJTvaColoana as N(6) null) as id_jtva_coloana,CAST(lnProcTvav as N(7,2) null) as proc_tvav,CAST(lnTaxCode as N(6) null) as taxcode,pretftva,0 From crsalteserv Where !Deleted() + ``` + Nu exista coloana `id_pol` in `crsalteserv`, in `lcCursorDeviz`, sau in cursorul `crsvanztemp` + creat la `oproceduri_devize.prg:1190-1192` (schema explicita a cursorului nu include `id_pol`). + +2. **`crsvanztemp -> adauga_articol_factura_deviz`** (`oproceduri_devize.prg:1240-1257`): apelul RPC + trimite `id_articol, explicatie, serie, pret_achizitie, pretd, id_valuta_d, pret, id_valuta, + curs, multiplicator, proc_tvav, id_jtva_coloana, cantitate, discount_unitar, id_gestiune, cont, + pret_cu_tva, NULL, NULL, taxcode` — **fara `id_pol`**, pentru ca procedura insasi nu are un + parametru `id_pol`. + +3. **Semnatura `adauga_articol_factura_deviz`** (spec `ff_...:468-488`, corp `ff_...:4675-4745`): + nu exista `V_ID_POL IN NUMBER` printre parametri. `INSERT INTO VANZARI_DETALII_TEMP` la + `ff_...:4697-4744` **nu include coloana `ID_POL`** in lista de coloane inserate — deci + `ID_POL` ramane pe valoarea implicita a coloanei (probabil NULL; DDL-ul tabelei nu face parte + din pachetul cercetat, vezi Neverificat). **Asta confirma partea (a) a intrebarii: da, liniile + "alte servicii" ajung cu `id_pol` gol** in `VANZARI_DETALII_TEMP`. + +4. **Dar acele randuri nu trec niciodata prin `contabilizeaza_articol`.** Apelantul din + `oproceduri_devize.prg` nu cheama `scrie_factura2` / `scrie_factura_avize` / `scrie_aviz_retur` + (singurele proceduri care cheama `contabilizeaza_articol`, vezi punctul 3). In schimb cheama, in + ordine (`oproceduri_devize.prg:1211-1310`): + - `pack_facturare.initializeaza_date_factura(...)` + - bucla `pack_facturare.adauga_articol_factura_deviz(...)` (doar insert in `VANZARI_DETALII_TEMP`) + - opțional `pack_facturare.scrie_incasari(...)` (incasare, nu venit din vanzare) + - `pack_facturare.scrie_in_vanzari(0, ...)` (`ff_...:13497-13962`) + - `pack_auto.actualizeaza_deviz(...)` (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`) + + **`scrie_in_vanzari` nu scrie nici o nota contabila de venit.** Corpul complet + (`ff_...:13497-13962`) face: `INSERT INTO VANZARI` (antetul documentului), `scrie_cursuri`, + `scrie_seturi`, apoi + ``` + ff_...:13714-13766 + INSERT /*+ APPEND */ INTO VANZARI_DETALII (..., ID_POL, ...) + SELECT ..., ID_POL, ... FROM VANZARI_DETALII_TEMP; + ``` + (copiaza `ID_POL` — care e NULL pentru aceste randuri — direct in `VANZARI_DETALII`, fara nicio + validare), si la final actualizeaza totalurile cache pe `VANZARI`. **Nu apare niciun apel catre + `contabilizeaza_articol`, `scrie_nota`, `cumuleaza_note_act` sau `finalizeaza_factura` in acest + corp** (verificat citind procedura cap-coada). + + `pack_auto.actualizeaza_deviz` (`ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`) face doar: + ``` + SELECT MAX(ID_FACT) INTO lnIdFact FROM ACT WHERE COD = pack_contafin.get_cod() AND SCD NOT LIKE '5%'; + UPDATE DEV_ORDL SET PROC_TVAV = ... WHERE ID_ORDL IN (...); + UPDATE RUL SET ID_FACT = lnIdFact WHERE ...; + UPDATE NOM_LUCRARI SET ID_FACT = lnIdFact WHERE ... (doar pt anumite id_set); + ``` + Adica **presupune ca exista deja un rand in `ACT`** (nu `ACT_TEMP`) cu `SCD NOT LIKE '5%'` pentru + codul documentului curent, si doar propaga `ID_FACT` gasit acolo spre `DEV_ORDL`/`RUL`/`NOM_LUCRARI`. + Nu insereaza el insusi in `ACT` sau `NOTE_CONTABILE`, si nu contine nicio referinta la `SCC`, + `NOTE_CONTABILE` sau `CRM_POLITICI*` (cautat explicit, zero rezultate in acest fisier). + +**Concluzie Q4, corectand premisa din cerere**: liniile "Alte servicii" chiar ajung cu `id_pol` gol +(confirmat), dar **nu demonstreaza ca `contabilizeaza_articol` tolereaza `id_pol` gol** — pentru ca +nu trec niciodata prin el. Ele demonstreaza doar ca exista deja, pentru fluxul deviz ROAAUTO, o cale +de facturare paralela (`scrie_in_vanzari` + `pack_auto.actualizeaza_deviz`) care **nu genereaza deloc +nota contabila de venit prin `pack_facturare`**. Daca acele randuri capata totusi un cont de venit in +`ACT`/`NOTE_CONTABILE` in productie, mecanismul nu e vizibil in codul cercetat (posibil un +trigger pe tabel, un job batch, sau o scriere manuala/alt modul neexaminat — vezi Neverificat). +**Nu se poate folosi acest precedent ca raspuns de fapt la intrebarea 1.** + +--- + +## 5. Vreun mecanism care sa foloseasca `NOM_ARTICOLE.CONT` drept cont de venit + +Raspuns: **NU**, in `pack_facturare`. + +Cautat explicit `Grep -i 'NOM_ARTICOLE\.CONT'` pe intregul fisier +`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` (17020 linii) — **zero potriviri**. Singurele utilizari +ale `NOM_ARTICOLE` gasite in `contabilizeaza_articol` insusi sunt pentru `DENUMIRE` (mesajul de +eroare FACT-024, `ff_...:7295-7298`), nu pentru `CONT`. + +`crs_rand_articol.scc` (folosit ca `V_SCC` la `ff_...:7434`) vine exclusiv din +`D.SCC` din join-ul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE` +(`ff_...:7261, 7275-7276`) — nu exista ramura alternativa care sa substituie `NOM_ARTICOLE.CONT` +cand politica lipseste. (Raportul anterior confirmase deja separat ca `NOM_ARTICOLE.CONT` e folosit +doar ca **cont de gestiune**, transmis catre `descarca_gestiune`, `ff_...:7499`.) + +--- + +## Neverificat + +- **Continutul efectiv al `NOTE_CONTABILE`/`CRM_POLITICI_PRET_ART`** (daca exista politici cu + `ID_POL = 0`, ce SCC au liniile din productie pentru documente "alte servicii" existente) — + necesita interogare pe baza de date, nu doar cod. +- **Constrangerile DDL** ale `VANZARI_DETALII_TEMP.ID_POL` si `ACT_TEMP.SCC`/`ACT.SCC` (NOT NULL? + default?) — scripturile DDL ale acestor tabele nu au fost cautate/citite in aceasta cercetare + (am cautat doar in pachetele PL/SQL `ff_...COMUN_PACK_FACTURARE.sql` si `..._AUTO_PACK_AUTO.sql`). +- **Cine scrie efectiv contul de venit (`ACT`/`NOTE_CONTABILE`) pentru documentele "alte servicii" + din ROAAUTO**, daca se scrie deloc. `pack_auto.actualizeaza_deviz` presupune ca exista deja un + rand `ACT` cu `SCD NOT LIKE '5%'` pentru codul documentului curent — sursa acelui rand nu a fost + gasita in `oproceduri_devize.prg` sau in corpul `scrie_in_vanzari`/`actualizeaza_deviz` cercetate. + Ar trebui cautat: alte proceduri `pack_auto` (fisierul `ff_2026_03_31_02_AUTO_PACK_AUTO.sql` are + si alte proceduri necercetate aici), triggere pe `VANZARI`/`VANZARI_DETALII`/`ACT`, sau apeluri + din alte forme VFP (`.scx`/`.vcx`) din ROAAUTO care preced acest apel RPC. Nu am cautat exhaustiv + triggere in afara folderului `SCRIPTURI_CLAR\2026` (cautare limitata, zero rezultate acolo). +- **Ce se intampla exact daca `id_pol = 0` si exista o politica reala cu `ID_POL = 0`** (caz + teoretic, neexclus explicit din schema) — ar schimba concluzia de la "eroare neprinsa" la + "FACT-024 cu mesaj complet", dar tot ramane o eroare, nu un default silentios. diff --git a/docs/cercetare/optiune_firma_cont_debit.md b/docs/cercetare/optiune_firma_cont_debit.md new file mode 100644 index 0000000..c97cae8 --- /dev/null +++ b/docs/cercetare/optiune_firma_cont_debit.md @@ -0,0 +1,373 @@ +# Optiune de firma pentru contul de debit (`SCD`) pe ramura fara politica de pret (#13, decizia 36) + +Cercetare read-only, terminata. Continua `canal_cont_venit_fara_politica.md` (proiectarea +parametrului de cont, sectiunea "Proiectarea parametrului de cont contabil", `:27-241`) si +verificarea ei, `parametru_cont_contabilizeaza_articol.md`. Acolo, randul `SCD` din tabelul de +sectiunea 3 propunea **`SCD` hardcodat `'4111'`**, marcat explicit "de confirmat cu Marius" +(`canal_cont_venit_fara_politica.md:146`). **Decizia 36 (Marius, 10.08.2026): `SCD` vine dintr-o +optiune de firma, cu `4111` ca implicit** — motivul fiind ca se poate schimba la client fara +recompilare. Aici e proiectarea acelei optiuni. Decizia 31 se aplica: datele din baza de dev arata +ce *exista*, nu ce e configurat la client — nu se generalizeaza pe numere, doar pe tipar/structura. + +Oracle interogat doar `SELECT`, schema `MARIUSM_AUTO`/`ROA_CENTRAL`. Niciun fisier de cod atins. + +--- + +## 0. Rezumat, in cinci randuri + +Mecanismul de optiuni de firma **exista deja de 15+ ani** (`PACK_SESIUNE.getoptiunefirma`, tabelul +`OPTIUNI`, 458 randuri azi) si are **exact tiparul cerut deja in productie, in acelasi pachet**: +`pack_facturare.scrie_incasare2` seteaza `V_SCD` dintr-o optiune (`RF_CONT_INCASARE_BONFISCAL` etc., +cate una per tip de incasare), cu fallback pe un cont hardcodat daca optiunea lipseste — **acelasi +camp (`SCD`), acelasi pachet, aceeasi sursa Oracle** (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`, +liniile 13191-13233, tratate integral in sectiunea 2). Ecranul de editare (`frm_optiuni`/ +`frm_optiuni_nou`, `COMUN\clase\oOptiuni.vc2`) e **complet generic** peste tabelul `OPTIUNI` — +adaugarea cheii noi **nu cere niciun cod VFP nou** in ecranul de optiuni, doar randul in tabel. +Propunerea (sectiunea 3): cheia noua `FACT_SCD_ARTFPRET` (sau numele ales de Marius), instalata cu +valoare implicita `'4111'` chiar in randul din `OPTIUNI` (tiparul deja folosit de toate exemplele +gasite), **plus** fallback hardcodat `'4111'` in cod langa `getoptiunefirma`, in oglinda exacta a +tiparului `scrie_incasare2` — dubla plasa de siguranta (instalare veche fara randul din `OPTIUNI` +**si** rand editat gresit/gol raman amandoua acoperite). + +--- + +## 1. Cum functioneaza mecanismul de optiuni de firma, azi + +### 1.1 Stocare — tabelul `OPTIUNI` + +Interogat direct (`ALL_TAB_COLUMNS`, schema curenta), 458 randuri azi: + +| Coloana | Tip | Null | Rol | +|---|---|---|---| +| `ID_OPTIUNI` | `NUMBER` | N | cheie surogat, PK implicita | +| `VARNAME` | `VARCHAR2(30)` | Y | cheia optiunii — cautata `UPPER(TRIM(...))`, deci case-insensitive in practica | +| `VARTYPE` | `VARCHAR2(10)` | Y | eticheta de tip pentru randare/parsare in VFP: `CHARACTER`/`NUMERIC`/`CURRENCY`/`DATE`/`DATETIME`/`LOGICAL` — **descriptiva, nu impusa de Oracle**; distinct azi: `CHARACTER` 156, `NUMERIC` 288, `LOGICAL` 11, `DATE` 3 | +| `VARVALUE` | `VARCHAR2(1000)` | Y | valoarea, **mereu text**, indiferent de `VARTYPE` | +| `VARVALUE2` | `CLOB` | Y | valoare alternativa, folosita de un mecanism separat (`citeste_optiune(tcOptiune, tlValue2)`, `oinit_optiuni.prg:802-823`) — irelevant aici | +| `VARDESC` | `VARCHAR2(100)` | Y | descriere afisata in ecran | +| `PROGRAM` | `VARCHAR2(30)` | Y | produsul care a creat optiunea (metadata) | +| `PROGRAME` | `VARCHAR2(1000)` | Y | lista CSV de produse pentru care optiunea e vizibila/relevanta (folosita la filtrare in incarcarea globala VFP, sectiunea 1.4) | +| `ID_PRG_OWNER` | `NUMBER` | Y | neexaminat mai departe, neрелevant aici | + +**Fara `ID_FIRMA`.** Motivul: fiecare firma are **schema Oracle proprie** — tabelul `OPTIUNI` din +schema curenta de conexiune **este** deja "optiunile firmei curente"; nu exista randare +multi-firma in acelasi tabel. Confirmat si de codul VFP: `frm_optiuni.cschema` e setat la +constanta de compilare `CONTAFIN_ORACLE` (`oOptiuni.vc2:1089`, acelasi simbol folosit peste tot in +COMUN pentru "schema firmei curente"), iar `getoptiunefirma(tcS, tcOptiune)` **primeste** un +parametru de schema (`tcS`, azi mereu `USER`) dar **nu il foloseste in interogare** — `select +varvalue from optiuni` fara calificare de schema (cod exact la sectiunea 1.2) — pentru ca sesiunea +Oracle e deja conectata direct in schema firmei. `tcS` ramane doar din compatibilitate istorica cu +supraincarcarea `getOptiuneFirma(tcOptiune)` (fara schema), care apeleaza pe cea cu doi parametri +cu `user` (sectiunea 1.2). + +### 1.2 Semnatura si contract — `PACK_SESIUNE.getoptiunefirma` + +Sursa curenta confirmata prin `ALL_SOURCE` pe pachetul live (`PACK_SESIUNE`, tip `PACKAGE BODY`) — +identica, verificat, cu ultima versiune gasita in `SCRIPTURI_CLAR` +(`2014/04/ff_2014_04_24_01_COMUN_PACK_SESIUNE.sql:328-350`; nicio modificare de atunci, cautare +`FUNCTION getoptiunefirma` in `SCRIPTURI_CLAR/2024`, `/2025`, `/2026` — zero rezultate): + +```sql +FUNCTION getoptiunefirma(tcS IN VARCHAR2, tcOptiune IN VARCHAR2) + RETURN VARCHAR2 IS + lcValue Optiuni.Varvalue%Type := ''; +BEGIN + BEGIN + select varvalue + INTO lcValue + from optiuni + where UPPER(TRIM(VARNAME)) = UPPER(TRIM(tcOptiune)); + EXCEPTION + WHEN OTHERS THEN + lcValue := ''; + END; + RETURN lcValue; +END; + +FUNCTION getOptiuneFirma(tcOptiune IN VARCHAR2) RETURN VARCHAR2 IS + lcValue Optiuni.Varvalue%Type := ''; +BEGIN + lcValue := getOptiuneFirma(user, tcOptiune); + return lcValue; +END; +``` + +Contract, verificat pe `RETURN`, nu dedus din nume: + +- **Parametru**: numele optiunii (`tcOptiune`), cautat `UPPER(TRIM(...))` — insensibil la caz si la + spatii; a doua forma (`getOptiuneFirma(tcOptiune)`, un singur parametru) e cea folosita azi peste + tot in `pack_facturare` (vezi sectiunea 2) — deleaga la forma cu doi parametri cu `user`. +- **Cand optiunea NU exista** (`NO_DATA_FOUND`, prins de `WHEN OTHERS` generic): **`RETURN ''`** + (string gol), **niciodata `NULL` explicit si niciodata exceptie propagata**. Insa in Oracle un + `VARCHAR2` egal cu `''` **este** `NULL` la nivel de reprezentare (Oracle nu distinge stringul gol + de `NULL`) — deci orice apelant care testeaza `IF V_X IS NULL THEN` dupa un apel la + `getoptiunefirma` **prinde si cazul "optiune inexistenta"**, fara cod suplimentar. Acesta e + exact tiparul folosit de toti apelantii gasiti (sectiunea 2). +- **`WHEN OTHERS THEN lcValue := ''`** inghite **orice** eroare, nu doar `NO_DATA_FOUND` (de ex. si + `TOO_MANY_ROWS` daca ar exista vreodata doua randuri cu acelasi `VARNAME` — nu exista constrangere + `UNIQUE` pe `VARNAME`, doar convenția aplicatiei). Nu e un risc nou introdus de propunerea de + fata — e comportamentul mecanismului de 15+ ani, pastrat ca atare. +- **Variante**: **o singura functie**, intotdeauna `VARCHAR2`. **Nu exista** `getoptiunefirma` + numerica/booleana separata in Oracle — conversia se face **la apelant**, in PL/SQL cu + `TO_NUMBER(NVL(...))` (exemplu confirmat, acelasi fisier, `scrie_incasare2`, + `ff_...:13177`: `TO_NUMBER(NVL(pack_sesiune.getoptiunefirma('D406'), '0'))`) sau in VFP cu + `VAL()`/`CTOD()`/etc., pe baza coloanei `VARTYPE` (vezi `oinit_optiuni.prg:179-211`, functia + `actualizeaza_optiuni_program`, care materializeaza fiecare optiune intr-o variabila globala VFP + cu prefix dupa tip: `gc` pentru `CHARACTER`, `gn` pentru `NUMERIC`, + `gl` pentru `LOGICAL` etc.). **Pentru un cont contabil (`VARCHAR2(4)`), varianta corecta e + cea de baza, `VARTYPE='CHARACTER'`** — exact tipul folosit de toate exemplele cu cont din + sectiunea 2, fara nicio conversie necesara la citire in PL/SQL (`V_SCD` e deja `VARCHAR2`). + +### 1.3 Ecranul de editare — complet generic peste tabel + +`COMUN\clase\oOptiuni.vc2`, doua clase relevante: + +- **`frm_optiuni`** (`:1064-1314`) — grid pe view-ul VFP local `v_optiuni` (populat dintr-un + `SELECT * FROM .optiuni`, vezi `oinit_optiuni.prg:337` in `viz_optiuni`), 4 coloane + (`varname`, `vartype`, `varvalue`, `vardesc`), cu cautare doar dupa `varname LIKE` + (`do_cauta`, `:1261-1278`) si butoane Adauga/Modifica/Sterge care deschid `frm_optiuni_nou`. +- **`frm_optiuni_nou`** (`:1316-1462`) — formular Adauga/Modifica cu 4 campuri: `varname` (text, + `Format="!K"` = uppercase), `vartype` (combobox cu lista fixa `CHARACTER,CURRENCY,NUMERIC, + DATETIME,DATE,LOGICAL`), `varvalue` (text), `vardesc` (text). Validare la salvare + (`inainte_de_do_termin`, `:1417-1450`): **doar "nu e gol"** pe `varname`, `vartype`, `varvalue` — + **nicio validare de format sau de continut** (nu verifica lungime, nu verifica daca `varvalue` + e un cont existent in planul de conturi, nimic specific per optiune). +- **`cus_odata_optiuni`** (`:139-206`) — clasa de persistenta: `INSERT`/`UPDATE`/`DELETE` generic pe + `optiuni (varname, vartype, varvalue, vardesc)` — confirma ca **orice cheie noua** functioneaza + fara nicio schimbare la acest cod; scrie exact cele 4 coloane editabile din ecran. + +**Concluzie la intrebarea "adaugarea unei optiuni noi cere cod nou in ecran": NU.** Ecranul e +generic peste tabel dupa cele 4 coloane editabile. Randul nou (`FACT_SCD_ARTFPRET`, sectiunea 3) +apare automat in grid dupa ce e inserat, si e editabil din prima fara nicio modificare VFP. + +*Nota conexa, nu necesara pentru mecanismul Oracle*: la fiecare pornire de sesiune, VFP +materializeaza **toate** randurile din `OPTIUNI` in variabile globale (`optiuni_firma`, +`oinit_optiuni.prg:234-310`), filtrate `Isnull(programe) Or gcNumeProgram $ programe` — deci coloana +`PROGRAME` conteaza doar pentru **vizibilitatea in acest mecanism global VFP**, nu pentru +`getoptiunefirma` (care nu se uita deloc la `PROGRAME`). Optiunea noua nu are nevoie de acest canal +VFP (Oracle o citeste direct), dar completarea corecta a `PROGRAME` evita zgomot/confuzie daca +cineva se uita in `frm_optiuni` din ROAFACTURARE si se asteapta sa vada optiunea listata acolo cu +`gcNumeProgram = 'ROAFACTURARE'` in `PROGRAME`. + +--- + +## 2. Exemple reale de optiuni existente, cu lantul complet + +### 2a. `RF_CONT_INCASARE_*` — exact tiparul cerut, in acelasi pachet, pe acelasi camp `SCD` + +**Cel mai relevant exemplu posibil**: patru optiuni care alimenteaza direct `SCD` pe o nota +contabila, in `pack_facturare.scrie_incasare2` — **aceeasi functie Oracle pe care propunerea de la +`canal_cont_venit_fara_politica.md` o modifica** (acelasi fisier, +`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`). + +**Randul din tabel** (interogat direct, 4 randuri, VARTYPE `CHARACTER` la toate): + +| VARNAME | VARVALUE azi | VARDESC | PROGRAM | PROGRAME | +|---|---|---|---|---| +| `RF_CONT_INCASARE_BONFISCAL` | `5311` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111` | `ROAFACTURARE` | `ROAFACTURARE,ROAACNPRO,ROACOMENZI,ROACONTRACTE,ROAGEST,ROARESTAURANT` | +| `RF_CONT_INCASARE_CARDBANCAR` | `5125` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5113 = 4111` | idem | idem | +| `RF_CONT_INCASARE_TICHETE` | `5323` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5323 = 4111` | idem | idem | +| `RF_CONT_INCASARE_CHITANTA` | `5311` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111` | idem | idem | + +(`VARDESC` mentioneaza si `= 4111` — nu e o eroare de copy-paste: e nota ca partea de credit a +notei de incasare e contul de clienti `4111`, deci descrierea documenteaza ambele parti ale notei, +nu doar valoarea implicita a optiunii.) + +**Insertia originala** (`SCRIPTURI_CLAR/2010/08/ff_2010_08_11_01_OPTIUNI.sql:9-19`), tiparul de +migrare de urmat: + +```sql +insert into optiuni (VARNAME, VARTYPE, PROGRAM, VARVALUE, VARDESC, ID_PRG_OWNER, PROGRAME) +values ('RF_CONT_INCASARE_BONFISCAL', 'CHARACTER', 'ROAFACTURARE', '5311', + 'CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111', null, 'ROAFACTURARE'); +``` + +**Apelul din PL/SQL** (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:13161-13234`, +`PROCEDURE scrie_incasare2`), citit integral — **exact tiparul "optiune cu fallback hardcodat" cerut +de decizia 36**: + +```sql +V_SCD := V_PSCD; -- parametru explicit, daca a fost dat de apelant (ff_...:13185) +CASE + WHEN V_TIP = nTipIncasareBonFiscal THEN + ... + IF V_SCD IS NULL THEN + V_SCD := PACK_SESIUNE.getoptiunefirma('RF_CONT_INCASARE_BONFISCAL'); + END IF; + IF V_SCD IS NULL THEN + V_SCD := '5311'; -- fallback hardcodat, instalare veche + END IF; + ... +END CASE; +``` + +Trei niveluri, in ordine: **(1) parametru explicit daca a fost dat, (2) optiunea de firma, (3) +constanta hardcodata** — folosita si daca randul din `OPTIUNI` lipseste complet (instalare veche, +migrare neaplicata) *si* daca a fost sters/golit manual din ecran. Acest cont ajunge apoi direct in +`INSERT INTO ACT_TEMP (..., SCD, ...)` (`ff_...:13241-13249` in continuare), **fara nicio validare +suplimentara de format sau de existenta in planul de conturi** — nici in Oracle, nici in ecranul de +optiuni (sectiunea 1.3), nici la `INSERT`. + +**Ecranul de editare**: acelasi `frm_optiuni`/`frm_optiuni_nou` generic (sectiunea 1.3) — niciun +ecran dedicat pentru aceste 4 optiuni, se editeaza ca text simplu in grid. + +### 2b. `D406` — optiune numerica/booleana folosita ca flag, cu conversie explicita la citire + +`VARNAME='D406'`, `VARTYPE='NUMERIC'`, `VARVALUE='1'`, `PROGRAM='ROACONT'`, +`PROGRAME='ROACONT,ROAAUTO,ROACONTRACTE,ROADEVIZE,ROAFACTURARE,...'`. Citita in acelasi +`scrie_incasare2` (`ff_...:13177`): `lnD406 := TO_NUMBER(NVL(pack_sesiune.getoptiunefirma('D406'), +'0'))` — **tipar de citire pentru optiune numerica**: `getoptiunefirma` intoarce mereu text, +apelantul face `NVL(...,'0')` inainte de `TO_NUMBER` (aici fallback-ul `'0'` e in acelasi loc cu +apelul, nu separat ca la `RF_CONT_INCASARE_*` — variatie minora de stil intre autori, nu o regula +diferita). + +### 2c. `CONT411` si `DEVCONTALTELE` — optiuni "cont" mai vechi, azi orfane in cod + +`CONT411` (`VARTYPE='CHARACTER'`, `VARVALUE='4111'`, `VARDESC='411 SAU 4111'`, fara `PROGRAM`) si +`DEVCONTALTELE` (`VARTYPE='CHARACTER'`, `VARVALUE='704'`, `VARDESC='CONT ALTE SERVICII FACTURA'`, +`PROGRAM='ROAAUTO'`) — cautare `grep` pentru ambele in tot `SCRIPTURI_CLAR`: **niciun apelant activ +gasit** (doar insertiile originale din 2011). Probabil cod dezafectat sau folosit doar din alt +produs/raport neexaminat aici. Mentionate pentru completitudine si pentru ca `CONT411` confirma +**exact valoarea implicita ceruta de Marius** (`4111`) ca precedent de configurare tot pe un cont +de clienti — dar **nu** au un lant activ de apel, deci nu sunt un exemplu de "tipar in uz" la fel de +tare ca `RF_CONT_INCASARE_*`. + +### 2d. `EFACTURA_CONT_ART_E`/`EFACTURA_CONT_ART_P` — optiune "cont implicit pe articol", azi goala + +`VARTYPE='CHARACTER'`, `VARVALUE` **goala** azi (nesetata pe baza curenta), `VARDESC='CONT ARTICOLE +IMPLICIT FACTURI EMISE'`/`'...PRIMITE'`, `PROGRAM='ROACONT'`. Confirma ca tiparul "cont implicit +configurabil, gol pana e setat manual" exista deja ca optiune (nu s-a putut verifica in aceasta +runda cine o citeste — cautare in afara scopului, modulul `ROACONT`/eFactura nu a fost deschis). +Relevanta doar ca a patra confirmare a conventiei de nume (`EFACTURA_CONT_ART_`), nu ca lant +complet verificat. + +--- + +## 3. Propunerea concreta + +### 3.1 Cheia optiunii + +**`FACT_SCD_ARTFPRET`** — respecta conventia dominanta observata in tabel: prefix de modul +(`FACT` pentru optiunile specifice `pack_facturare`/ROAFACTURARE, ca `FACTSETURI`, +`FACTALGREPCOM`, `FACTURACUCHITANTA`, `FACTURAEMAIL`, `FACTURASOLD` — toate `PROGRAM='ROAFACTURARE'`) +plus sufix descriptiv scurt fara diacritice si fara spatii (`SCD` = campul contabil vizat, +`ARTFPRET` = "articol fara politica de pret" — in oglinda cu numele deja folosit in proiectarea +anterioara, "ramura fara politica"). Nicio cheie `SCD`/`SCC` existenta azi in tabel (cautare directa, +zero rezultate) — nu exista risc de coliziune de nume. **Alternativa mai scurta**, daca Marius +prefera: `SCD_FARA_POLITICA` (fara prefix de modul) — tiparul din tabel nu e strict, exista si chei +fara prefix (`D406`, `CONT411`); prefixul `FACT_` e recomandat, nu obligatoriu. + +### 3.2 Valoarea implicita si unde traieste + +**Tiparul deja stabilit si folosit de `RF_CONT_INCASARE_*` (sectiunea 2a) se copiaza identic**: +implicitul traieste **in doua locuri**, nu unul: + +1. **In randul din `OPTIUNI`, la instalare** — valoarea `VARVALUE='4111'` scrisa direct de scriptul + de migrare (sectiunea 3.4). Asta e valoarea pe care o vede si o poate schimba Marius/clientul din + `frm_optiuni`, fara recompilare — exact cerinta deciziei 36. +2. **Hardcodat in PL/SQL, langa apelul `getoptiunefirma`**, ca a doua plasa de siguranta pentru + instalari vechi unde migrarea nu a rulat inca sau randul a fost sters/golit manual din ecran: + +```sql +V_SCD := PACK_SESIUNE.getoptiunefirma('FACT_SCD_ARTFPRET'); +IF V_SCD IS NULL THEN -- acopera si randul lipsa, si VARVALUE gol/sters din ecran (sectiunea 1.2) + V_SCD := '4111'; +END IF; +``` + +Plasata **in ramura noua din `contabilizeaza_articol`** (`canal_cont_venit_fara_politica.md:146`, +randul `SCD` din tabelul design-ului), **in locul** liniei `SCD := '4111'` hardcodat propuse acolo — +inlocuire punctuala, restul ramurii (`ASCD` calculat din `GetAnaliticByGrupUtilizatori(..., +V_SCD)`, deja dependent de `V_SCD`) ramane neschimbat, primeste automat valoarea corecta indiferent +de sursa (optiune sau fallback). + +### 3.3 Comportamentul cand optiunea lipseste sau e invalida + +- **Lipseste randul din `OPTIUNI`** (instalare veche, migrare neaplicata): `getoptiunefirma` intoarce + `''` -> tratat `IS NULL` in Oracle -> fallback la `'4111'` hardcodat (sectiunea 3.2, pasul 2). + **Acoperit prin constructie**, acelasi tipar ca `RF_CONT_INCASARE_*`. +- **Randul exista dar `VARVALUE` e gol** (sters manual din ecran, fara sa fi fost stearsa cheia): + identic cazului anterior — `''` tot `IS NULL` in Oracle. **Acoperit prin constructie.** +- **`VARVALUE` contine un cont care nu exista in planul de conturi**, sau un string cu lungime/ + format invalid: **nu exista nicio validare azi**, nici in `getoptiunefirma`, nici in ecranul de + optiuni (sectiunea 1.3, "nicio validare de format sau de continut"), nici la `INSERT INTO + ACT_TEMP` (coloana `SCD` e `VARCHAR2(4) NULL` fara `CHECK`, fara `FK`, confirmat in DDL-ul deja + citat in `canal_cont_venit_fara_politica.md`, sectiunea DDL). Un cont inexistent ar ajunge pe nota + neschimbat — **exact acelasi risc pe care il are deja `RF_CONT_INCASARE_*` de 15 ani**, nu unul + nou introdus de aceasta propunere. Daca se doreste o garda, ar trebui adaugata **la citire** (in + ramura noua din `contabilizeaza_articol`, dupa cele doua atribuiri de mai sus, cu un `SELECT + COUNT(*) FROM PLCONT WHERE COD_CONT = V_SCD` sau echivalent) — **nu exista azi un precedent de + validare pe niciuna dintre optiunile de tip cont examinate**, deci adaugarea uneia ar fi o + imbunatatire noua, nu o continuare a unui tipar existent; de decis explicit (sectiunea 4). +- **Lungime > 4 caractere** in `VARVALUE` (campul din tabel e `VARCHAR2(1000)`, mult mai lat decat + `ACT_TEMP.SCD VARCHAR2(4)`): ar da `ORA-12899` (value too large) la `INSERT INTO ACT_TEMP` daca + cineva scrie din greseala un text lung in ecran — eroare Oracle standard, nu un `NULL`/gol tacut; + comportament acceptabil ca gardă minimala "gratuita", fara cod suplimentar. + +### 3.4 Scriptul de migrare — schita, neaplicata + +Tiparul exact al insertiei `RF_CONT_INCASARE_BONFISCAL` (sectiunea 2a), idempotent prin verificarea +existentei prealabile (tipar comun in scripturile de migrare vazute in `SCRIPTURI_CLAR`, desi +insertia originala din 2010 nu avea garda — cea de mai jos o adauga, mai sigura pentru un script +care ar putea rula de doua ori): + +```sql +-- ff__NN_COMUN_OPTIUNI.sql (schita, neaplicata) +DECLARE + V_CNT NUMBER; +BEGIN + SELECT COUNT(*) INTO V_CNT FROM OPTIUNI WHERE UPPER(TRIM(VARNAME)) = 'FACT_SCD_ARTFPRET'; + IF V_CNT = 0 THEN + INSERT INTO OPTIUNI (VARNAME, VARTYPE, PROGRAM, VARVALUE, VARDESC, PROGRAME) + VALUES ('FACT_SCD_ARTFPRET', 'CHARACTER', 'ROAFACTURARE', '4111', + 'CONTUL DEBITOR (SCD) PENTRU ARTICOL FACTURAT FARA POLITICA DE PRET', + 'ROAFACTURARE'); + END IF; +END; +/ +exec pack_migrare.UpdateVersiune('ff__NN_COMUN_OPTIUNI'); +commit; +``` + +`ID_PRG_OWNER` omis (rand `NULL` in toate exemplele citate, coloana pare neutilizata activ pentru +optiuni de firma simple). `PROGRAME='ROAFACTURARE'` — restrans, spre deosebire de +`RF_CONT_INCASARE_*` care listeaza 6 produse; de largit doar daca se confirma (in afara scopului +acestei cercetari, sectiunea "Ce ramane de decis") ca alte produse din suita ajung sa apeleze aceeasi +ramura din `contabilizeaza_articol` prin `adauga_articol_factura` simplu. + +### 3.5 Validare la salvare in ecran + +**Nu** — confirmat la sectiunea 1.3: `frm_optiuni_nou` valideaza doar "camp nevid" pe cele 3 campuri +principale, nicio validare specifica per-cheie. Optiunea noua se comporta identic cu restul tabelei: +utilizatorul poate scrie orice text de pana la 1000 caractere in `VARVALUE`, cu riscurile descrise +la sectiunea 3.3. + +--- + +## 4. Ce ramane de decis + +1. **Numele exact al cheii** — `FACT_SCD_ARTFPRET` propus (sectiunea 3.1); Marius poate prefera alt + nume sau formatul mai scurt `SCD_FARA_POLITICA`. +2. **Se adauga o validare a contului la citire** (cont trebuie sa existe in planul de conturi) sau + se pastreaza tiparul actual, fara validare, ca la `RF_CONT_INCASARE_*`? Niciun exemplu existent nu + valideaza — a introduce validare ar fi prima data cand se face asta pe o optiune de tip cont. +3. **`PROGRAME` restrans la `ROAFACTURARE` sau extins** ca la `RF_CONT_INCASARE_*` (6 produse)? Tine + de raspunsul, inca deschis in `parametru_cont_contabilizeaza_articol.md` sectiunea 2d, la + intrebarea daca alte produse din suita apeleaza `adauga_articol_factura` simplu. +4. **Textul exact din `VARDESC`** — schita de mai sus e o propunere, nu o cerinta. + +--- + +## Ce nu s-a putut stabili + +- **Cine citeste azi `EFACTURA_CONT_ART_E`/`EFACTURA_CONT_ART_P`** (sectiunea 2d) — cautarea s-a + oprit la insertia originala; modulul `ROACONT`/eFactura nu a fost deschis, in afara scopului + cerut (exemplul a servit doar ca a patra confirmare de conventie de nume, nu ca lant complet). +- **Definitia view-ului VFP local `v_optiuni`** (folosit ca `RecordSource` in `frm_optiuni`) — e un + cursor/view generat dinamic prin `gencursor()`/`goExecutor.oExecute()` din `oinit_optiuni.prg`, nu + un obiect Oracle persistent (interogare `DBMS_METADATA` pe `V_OPTIUNI` a esuat cu "object not + found", asteptat) — nu schimba nimic din propunere, doar noteaza ca numele nu desemneaza o vedere + Oracle. +- **Daca `CONT411`/`DEVCONTALTELE` (sectiunea 2c) mai au vreun apelant in alt produs din suita** + (ROACONT, ROAGEST etc.) — cautarea s-a limitat la `SCRIPTURI_CLAR` (tot codul PL/SQL istoric); nu + s-a cautat in `.vc2`/`.prg` din alte COMUN-uri de produs. diff --git a/docs/cercetare/pack_auto_actualizeaza_deviz.md b/docs/cercetare/pack_auto_actualizeaza_deviz.md new file mode 100644 index 0000000..0d96364 --- /dev/null +++ b/docs/cercetare/pack_auto_actualizeaza_deviz.md @@ -0,0 +1,210 @@ +# Cercetare: `pack_auto.actualizeaza_deviz` si riscul asupra devizului ROAAUTO la #13 + +Scop: `plan_13_unificare_formular_facturare.md` vrea sa permita adaugarea unei linii noi pe o +factura deja emisa, inclusiv pe facturile ROAAUTO (`tip = -12`). Intrebarea: ce se intampla cu +**devizul** din ROAAUTO cand factura primeste o linie pe care devizul nu o stie. Punctul de +plecare a fost `pack_auto.actualizeaza_deviz`, apelat din +`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1310`, al carei corp nu fusese citit inainte de +aceasta cercetare. + +Continua `roaauto_facturi.md` si `roaauto_articole_lista_preturi.md` (deja citite, nu reiau +constatarile lor: acelasi `PACK_FACTURARE`, `VANZARI_DETALII_TEMP` -> `VANZARI_DETALII`, +`tip=-12`, liniile sintetice `-100000..-100008` plus liniile reale "Alte servicii", editorul +`frm_modific2024` read-only, `oviz_devize.vc2:4536` blocheaza modificarea dupa `nrfact`). + +## 1. Unde e definit `PACK_AUTO` + +`D:\ROA\DATABASE\SCRIPTURI_CLAR\\\ff_..._AUTO_PACK_AUTO.sql` — 14 versiuni istorice +gasite (2013-2026). Sursa de adevar folosita (cea mai recenta, dupa conventia deja aplicata in +`roaauto_facturi.md` pentru `PACK_FACTURARE`): + +**`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql`** (1804 linii, +antet "17.03.2026 / robert / creare procedura setOptiuneInchidere"). + +Semnatura in spec: `:74-76`. Corp: `:692-733`. + +## 2. Ce face `actualizeaza_deviz`, pas cu pas + +Corpul complet (`ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`): + +```sql +procedure actualizeaza_deviz(tnProcTvav IN NUMBER, + tcSirIdOrdl IN VARCHAR2, + tnIdSet IN NUMBER) is + lcSeparator VARCHAR2(1) := ','; + lnIdFact DOCUMENTE.ID_DOC%TYPE; +begin + -- nu iau pack_contafin.get_idfact pentru ca s-ar putea sa am id_fact de la incasare, nu de la factura + SELECT MAX(ID_FACT) + INTO lnIdFact + FROM ACT + WHERE COD = pack_contafin.get_cod() + AND SCD NOT LIKE '5%'; + + UPDATE DEV_ORDL + SET PROC_TVAV = tnProcTvav + WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator))); + + UPDATE /*+ INDEX(RUL IDX_RUL_001) */ RUL + SET ID_FACT = lnIdFact + WHERE ID_SET <> 229 + AND ID_LUCRARE IN (SELECT ID_LUCRARE FROM DEV_ORDL + WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator)))); + + IF tnIdSet IN (31003, 31004, 31005, 31006, 31007, 31011) THEN + UPDATE NOM_LUCRARI + SET ID_FACT = lnIdFact + WHERE ID_LUCRARE IN (SELECT ID_LUCRARE FROM DEV_ORDL + WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator)))); + END IF; +end actualizeaza_deviz; +``` + +Trei pasi, toti indexati pe `ID_ORDL`/`ID_LUCRARE` (comanda/lucrarea din ROAAUTO), **niciunul pe +`VANZARI`/`VANZARI_DETALII`**: + +1. Citeste `lnIdFact` = cel mai mare `ID_FACT` din `ACT` pentru "codul" curent de sesiune + (`pack_contafin.get_cod()`), excluzand notele de incasare (`SCD NOT LIKE '5%'`) — comentariul + din cod explica de ce: la momentul apelului pot exista in `ACT` atat o nota de incasare cat si + nota/factura propriu-zisa, si vrea explicit factura. +2. `UPDATE DEV_ORDL SET PROC_TVAV = tnProcTvav` — scrie cota de TVA pe comenzile din + `tcSirIdOrdl` (lista CSV de `ID_ORDL`, despartita cu `charn2collection`). +3. `UPDATE RUL SET ID_FACT = lnIdFact WHERE ID_SET <> 229 AND ID_LUCRARE IN (...)` — leaga + inapoi randurile din `RUL` (jurnalul de manopera/materiale al lucrarii, folosit si la + `frm_inchidere_productie.do_executa` pentru totalul pe sectii, vezi §4) de documentul creat. +4. Doar daca `tnIdSet` e unul din `{31003,31004,31005,31006,31007,31011}`, acelasi `ID_FACT` se + scrie si pe `NOM_LUCRARI` (nomenclatorul de lucrari). + +**Nu exista niciun `SELECT`/`UPDATE` pe `VANZARI` sau `VANZARI_DETALII` in `actualizeaza_deviz`** +si, verificat suplimentar, **in tot pachetul `PACK_AUTO`** (grep pe `VANZARI` in cele 1804 linii +ale fisierului: 0 potriviri). Deci raspunsul direct la intrebarea din brief: procedura **nu +recalculeaza niciun total al devizului din liniile facturii** — nu citeste liniile facturii deloc. +Ce face e sa "stampileze" `ID_FACT` (documentul de vanzare/nota abia creat) pe randurile din `RUL` +si `NOM_LUCRARI` legate de comenzile din `tcSirIdOrdl`, plus sa actualizeze cota TVA pe +`DEV_ORDL`. E o legatura *deviz -> document*, nu un recalcul *document -> total deviz*. + +## 3. Ce se intampla cu o linie de factura pe care devizul nu o are + +Raspunsul e neted, pentru ca premisa intrebarii (un `SUM` peste `VANZARI_DETALII`) nu exista in +cod: **procedura nu vede deloc liniile facturii**, nici cele cunoscute (sintetice +`-100000..-100008`), nici cele reale (nomenclator, cazul "Alte servicii" din raportul anterior), +nici una noua adaugata din afara. Ea opereaza exclusiv pe `ID_ORDL`/`ID_LUCRARE` (identitatea +comenzii ROAAUTO), primite ca parametru `tcSirIdOrdl` — nu deriva niciodata acest identificator +din continutul `VANZARI_DETALII`. + +Precedentul "Alte servicii" (articole reale, `id_articol` pozitiv, cf. +`roaauto_articole_lista_preturi.md` §2) confirma exact acest comportament pe date deja live: acele +linii coexista cu liniile sintetice in `VANZARI_DETALII` de multa vreme, fara ca +`actualizeaza_deviz` sa faca vreo distinctie — pentru ca nu se uita la `VANZARI_DETALII` deloc. + +**Consecinta pentru #13**: adaugarea unei linii noi pe `VANZARI_DETALII` pentru o vanzare +`tip=-12` nu poate "dezechilibra" ce scrie `actualizeaza_deviz`, pentru simplul motiv ca aceasta +procedura nu depinde de continutul `VANZARI_DETALII`. Nu exista *eroare*, nu exista *ignorare +explicita* — e o absenta totala de citire. + +Important insa (nuanta pe care intrebarea din brief o presupune implicit gresit): asta **nu** +inseamna ca "devizul" (asa cum e prezentat operatorului in ROAAUTO) ar reflecta automat noua +linie. Totalul devizului afisat in `oviz_devize.vc2` e calculat client-side din `RUL` (vezi +`crsTotaluri`, construit cu un `SELECT SUM(...) FROM RUL ... GROUP BY ID_SECTIE` in +`oviz_devize.vc2:7816-7823`, in `frm_inchidere_productie.do_executa`) si din cursoarele +`crsdeviz`/`crsalteserv` calculate in `oproceduri_devize.prg` la momentul facturarii — niciodata +din `VANZARI_DETALII`. Deci o linie adaugata ulterior pe factura **nu va aparea niciodata** in +totalul devizului asa cum il calculeaza ROAAUTO, indiferent daca `actualizeaza_deviz` mai ruleaza +sau nu — pur si simplu acel total nu are o sursa care sa citeasca `VANZARI_DETALII`. + +## 4. Cand e apelata + +Cautare in ROAAUTO (`Grep` pe `actualizeaza_deviz`, cele trei aparitii de cod, plus istoric +`.??2`) si in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR` (niciun alt pachet Oracle nu o apeleaza — grep +pe `actualizeaza_deviz` in tot arborele SCRIPTURI_CLAR gaseste doar fisierele care *definesc* +`PACK_AUTO`/vechiul `PACK_DEVIZE`, nu vreun apel extern): + +Trei puncte de apel, toate in VFP, niciunul intr-un trigger/job Oracle: + +1. **La emiterea facturii** — `oproceduri_devize.prg:1310`, in acelasi bloc `lcSql` cu + `pack_facturare.scrie_in_vanzari` (linia 1302), cu `tnIdSet` dinamic (`lnIdSet`, calculat mai + sus in `factureaza_deviz`). Comentariu la linia 1311: *"am scos apelul catre + `pack_devize.dev_completeaza_rul`; e inclus in `actualizeaza_deviz`"* — confirma ca functia a + inlocuit o procedura mai veche cu acelasi scop (legare RUL -> document). +2. **`frm_inchidere_productie.do_executa`** (`oviz_devize.vc2:7896`, clasa la `:7143`) — cu + `tnIdSet` **fix, 31007** — apelata dupa ce formularul de **inchidere productie** scrie o + *nota contabila* (`oscrie_in_fisiere()` peste cursorul `actactan`, `:7889`), **nu** o factura; + nu apeleaza `pack_facturare.scrie_in_vanzari`, deci nu atinge `VANZARI` deloc pe acest drum. +3. **`frm_inchidere_regie.do_executa`** (`oviz_devize.vc2:8907`, clasa la `:8093`) — identic, + `tnIdSet` fix **31006**, pentru inchiderea de **regie** (overhead), tot printr-o nota + contabila, nu o factura. + +Deci `actualizeaza_deviz` nu e specifica facturarii — e o operatie generica *"leaga +comenzile/lucrarile astea de ultimul document contabil creat pentru codul curent"*, refolosita si +la doua fluxuri de inchidere interna care nu produc facturi de vanzare. In niciunul din cele trei +cazuri nu e re-declansata mai tarziu (nu exista un al patrulea apel, nici din UI, nici din +schedule/trigger Oracle) — deci editarea ulterioara a unei facturi deja emise (obiectivul #13) nu +ar re-invoca automat aceasta procedura, pentru ca nimic din codul citit o cheama in afara celor 3 +puncte de mai sus. + +## 5. Alte proceduri din `PACK_AUTO` care citesc `VANZARI`/`VANZARI_DETALII` pentru un deviz + +**Niciuna.** Grep pe `VANZARI` in intregul fisier `ff_2026_03_31_02_AUTO_PACK_AUTO.sql` (1804 de +linii, tot pachetul, nu doar `actualizeaza_deviz`) — 0 potriviri. `PACK_AUTO` nu are nicio +dependenta de tabelele de vanzari/facturare; toata legatura cu partea financiara trece prin `ACT` +(note contabile) si `DEV_ORDL`/`RUL`/`NOM_LUCRARI` (structura proprie ROAAUTO). + +Singurul loc care **citeste** liniile facturii pentru re-listarea unui deviz e in afara +`PACK_AUTO`, pe partea VFP: `relisteaza_factura_deviz` +(`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1516-1576`, mentionata in raportul anterior). +Verificat acum sursa: interogheaza direct view-urile COMUN `fact_vfacturi` +(`:1531`, filtrat `WHERE cod = ...`) si **`fact_vfacturi_detalii`** (`:1564`, +`select * from fact_vfacturi_detalii where id_vanzare = ` — fara alt filtru). +View-ul e definit in +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2024\06\ff_2024_06_04_01_COMUN_FACTURARE.sql:8-94`: +`create or replace view fact_vfacturi_detalii as select ... from vanzari_detalii a left join +nom_articole b ... ` — un simplu wrapper peste `VANZARI_DETALII` cu join-uri de denumire, **fara +nicio clauza `WHERE`** care sa restranga la id-uri de articol cunoscute sau la liniile +"originale" ale devizului. + +**Consecinta**: o linie noua inserata in `VANZARI_DETALII` pentru acel `id_vanzare` **va aparea +automat** la o re-listare/re-tiparire ulterioara prin `relisteaza_factura_deviz` — comportament +asteptat de la un view neconditionat, nu o stricare. Nu exista risc de eroare sau de excludere pe +acest drum; riscul (daca exista) e doar de **asteptare a utilizatorului**: factura retiparita va +arata linia noua, dar ecranul de deviz din ROAAUTO (total calculat din `RUL`, §3) nu o va arata +niciodata, pentru ca nu deriva din `VANZARI_DETALII`. + +## 6. Concluzie operationala pentru #13 + +**Da, se poate adauga o linie pe o vanzare `tip=-12` fara sa "strice" `pack_auto.actualizeaza_deviz` +sau vreo alta procedura din `PACK_AUTO`** — pentru ca niciuna din ele nu citeste `VANZARI`/ +`VANZARI_DETALII`; nu exista mecanism Oracle-side care sa recalculeze/verifice totalul devizului +din liniile facturii, deci nu exista nimic de dezechilibrat la acel nivel. Nu e nevoie de niciun +apel Oracle suplimentar din partea ROAFACTURARE ca sa "anunte" ROAAUTO despre linia noua — +`actualizeaza_deviz` n-ar face nimic util cu acea informatie oricum (opereaza pe `ID_ORDL`, nu pe +linii de factura). + +Ce **nu** rezolva aceasta concluzie, si ramane responsabilitatea planului #13, nu a acestei +proceduri: + +- **Desincronizare de afisare, nu de date**: totalul devizului aratat in ROAAUTO + (`oviz_devize.vc2`, calculat din `RUL`/cursoarele de facturare) nu va reflecta niciodata o + linie adaugata ulterior direct pe `VANZARI_DETALII` — nici azi (cu liniile "Alte servicii"), + nici cu o linie noua din editorul unificat. Daca produsul vrea ca devizul sa "stie" de linia + noua vizual, ar trebui cod nou in ROAAUTO (nu exista azi), nu un apel catre `actualizeaza_deviz`. +- **Reprintarea** (`relisteaza_factura_deviz`) va include automat linia noua (§5) — de verificat + daca asta e comportamentul dorit de produs sau o suprindere pentru operatorul ROAAUTO. +- Confirmat si direct in cod ROAFACTURARE: `Grep` pe `pack_auto` in + `D:\ROA\ROAFACTURARE` (inclusiv `COMUN`) nu gaseste nicio referinta in cod, doar in `docs\` + (planul #13 si aceste rapoarte) — azi ROAFACTURARE nu apeleaza deloc `PACK_AUTO`, confirmand ca + nu exista deja o presupunere ascunsa de sincronizare intre cele doua. + +## Neverificat + +- Ce reprezinta exact tabelele `ACT`/`RUL`/`NOM_LUCRARI`/`DEV_ORDL` la nivel de schema completa + (DDL/comentarii) — inteles doar din felul in care sunt folosite in cod, nu dintr-un dictionar de + date citit explicit. +- Daca vreun raport fiscal/SAF-T (mentionat generic in `ff_2022_03_24_01_COMUN_SAFT.sql`, nedeschis) + agrega `VANZARI_DETALII` intr-un mod care ar fi afectat de o linie noua pe `tip=-12` — in afara + ariei `PACK_AUTO` cerute, nu a fost investigat. +- Daca `frm_incasare_finala.do_recalculeaza_total`/`calculeaza_total` (`oviz_devize.vc2:6494,6700`) + ar putea fi reconciliate manual de un operator ca sa "vada" o linie adaugata ulterior — nu am + citit corpul acestor metode, doar semnalul ca exista. +- Istoricul complet al celorlalte 13 versiuni `AUTO_PACK_AUTO.sql` nu a fost comparat linie cu + linie cu cea din 2026-03 — presupun (conform conventiei deja aplicate pentru `PACK_FACTURARE`) + ca cea mai recenta e sursa de adevar pentru comportamentul curent din productie. diff --git a/docs/cercetare/parametru_cont_contabilizeaza_articol.md b/docs/cercetare/parametru_cont_contabilizeaza_articol.md new file mode 100644 index 0000000..80245a3 --- /dev/null +++ b/docs/cercetare/parametru_cont_contabilizeaza_articol.md @@ -0,0 +1,173 @@ +# 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. diff --git a/docs/cercetare/rec_cale_vanzari_detalii.md b/docs/cercetare/rec_cale_vanzari_detalii.md new file mode 100644 index 0000000..9134158 --- /dev/null +++ b/docs/cercetare/rec_cale_vanzari_detalii.md @@ -0,0 +1,272 @@ +# Cercetare: calea VFP->Oracle la emitere si calea propusa pentru editare (#6, punctul E.4) + +Sursa: interogari SELECT proaspete pe `MARIUSM_AUTO@ROA_CENTRAL` (08.08.2026), acelasi mediu si +aceeasi versiune de baza ca in `rec_s5_oracle_vanzari.md` (nu s-a schimbat nimic intre cele doua +cercetari din aceeasi zi). Numerele de linie PL/SQL de mai jos sunt dintr-un export propriu, facut +in aceasta sesiune, din `all_source` pentru `PACK_FACTURARE` (schema/pachet identic cu cel din +raportul anterior). Partea VFP e din cache-ul text `.vc2` din proiect (nu binarul). + +Aceasta cercetare inchide golul lasat de `rec_s5_oracle_vanzari.md`, sectiunea E.4: cum ajung +liniile facturii in Oracle la emitere si care e calea corecta pentru editare. + +## 1. Calea de azi, capat la capat + +### 1.1 VFP: cine populeaza `VANZARI_DETALII_TEMP` + +Raspuns: **Oracle**, prin apeluri per-linie facute din VFP, nu VFP direct prin INSERT. + +- `frm_facturare_articole.do_scrie_articole` (`COMUN\clase\ofacturare.vc2:13967-14195`): + - `:13981-13999` cheama `pack_facturare.initializeaza_date_factura(...)` (antetul documentului, + tine minte parametrii in stare de sesiune pe pachet: `pack_facturare.ntip`, `nid_sucursala`, + `nid_util` etc. — folosite mai jos de `adauga_articol_factura`). + - `:14036-14115`: `SELECT crsfactura` -> `SCAN`, cate un `Scatter Name poArt MEMO` per linie, apoi + pentru fiecare linie fara rata de contract (`ELSE` la `:14051`) construieste text PL/SQL literal + (nu `{call}` cu parametri legati) si apeleaza + `pack_facturare.adauga_articol_factura(id_temp, id_articol, serie, explicatie, id_pol, + id_gestiune, pret_achizitie, pretd, id_valutad, pret, id_valuta, cu_tva, gestionabil, + cantitate, discount, cont, curs, multiplicator, id_jtva_coloana, id_part_rez, id_lucrare_rez, + pretv_orig, id_set_fact, id_ctr, gnIdUtil, taxcode, lot)` (`:14069-14091`), executat imediat + prin `goExecutor.oExecute(lcSql)` (`:14105`) — **un apel Oracle per linie de factura**, in + bucla, nu un singur apel cu tot cursorul. + - Pentru liniile din seturi: `pack_facturare.initializeaza_seturi_temp` + un apel + `pack_facturare.adauga_articol_set(...)` per linie de set (`:14117-14154`), scrie in + `VANZARI_SETURI_TEMP`. +- `frm_facturare_articole.do_scrie_factura` (`:14197-14554`): dupa ce toate liniile au fost urcate + in TEMP prin apelurile de mai sus, apeleaza finalizarea documentului — + `pack_facturare.scrie_factura2(...)` (facturi, `:14345`/`:14373`) sau + `pack_facturare.scrie_proforma(...)` (`:14286`) sau `scrie_factura_avize(...)`/ + `scrie_factura_avize_retur(...)` dupa tipul documentului — **un singur apel**, fara sa mai + transmita liniile (ele sunt deja in `VANZARI_DETALII_TEMP`, populata de bucla anterioara, in + ACEEASI sesiune/tranzactie Oracle). + +### 1.2 Oracle: `adauga_articol_factura` — nu doar INSERT, RECALCULEAZA din sursa originala + +Verificat sursa completa a procedurii publice `pack_facturare.adauga_articol_factura` (semnatura cu +`V_ID_TEMP` ca prim parametru, cea apelata de VFP la `:14069`) — **nu** e un simplu INSERT cu +valorile primite de la VFP. Ramifica pe `pack_facturare.ntip` (starea de sesiune setata la +`initializeaza_date_factura`) si **re-deriva** `V_PRET`, `V_PROC_TVAV`, `V_ID_VALUTA`, +`V_PRETURI_CU_TVA`, `V_IN_STOC` direct din documentul-sursa: +- `ntip IN (3,21,28,42,47)` (facturare din comenzi): `SELECT ... FROM COMENZI_ELEMENTE ...` +- `ntip = 4` (facturare din avize): `SELECT ... FROM VANZARI_DETALII ...` (avizul deja scris) +- `ntip = 45` (restaurant): calcul din `JTVA_COLOANE` + `CRM_POLITICI_PRET_ART` +- `V_OPT_FACTURARE = 3` (contract): `SELECT ... FROM CTR_ARTICOLE ...` +- fallback (`ELSE`): doar cota TVA din `JTVA_COLOANE`, restul (`V_PRET`, `V_ID_VALUTA`, + `V_PRETURI_CU_TVA`, `V_IN_STOC`) preluate ca atare din parametrii transmisi de VFP. + +Abia dupa acest bloc `CASE` urmeaza INSERT-ul propriu-zis: + +```sql +INSERT INTO VANZARI_DETALII_TEMP + (ID_TEMP, ID_ARTICOL, SERIE, LOT, EXPLICATIA, ID_POL, PRET_ACHIZITIE, PRETD, ID_VALUTAD, + PRET, PRET_CU_TVA, PROC_TVAV, CANTITATE, DISCOUNT_UNITAR, ID_GESTIUNE, CONT, ID_VALUTA, + CURS, MULTIPLICATOR, ID_JTVA_COLOANA, IN_STOC, ID_VANZARE_SET, ID_PART_REZ, ID_LUCRARE_REZ, + PRETV_ORIG, CUSTODIE, ID_CTR, ID_UTIL, TAXCODE) +VALUES (...) +``` + +(am confirmat integral corpul, INSERT-ul e ultimul bloc din procedura, imediat dupa `END CASE`). + +**Consecinta directa pentru #6**: `adauga_articol_factura` NU poate fi reutilizata ca atare pentru +editare — depinde de starea de sesiune `pack_facturare.ntip`/`clistaid`/`id_ctr` care descrie +DOCUMENTUL SURSA de la emitere (comanda/aviz/contract), stare care nu exista si nu are sens la o +editare ulterioara a facturii deja emise. Orice procedura noua pentru editare trebuie sa scrie +direct in `VANZARI_DETALII` (tabela reala), nu prin acest drum. + +Exista si `sterge_articol_factura(V_ID_TEMP, V_ID_UTIL)` — `DELETE FROM VANZARI_DETALII_TEMP WHERE +ID_TEMP = V_ID_TEMP` — folosita doar cat timp factura e in curs de compunere (inainte de emitere), +nu dupa. + +### 1.3 Oracle: `scrie_factura2` -> `finalizeaza_factura` -> `scrie_in_vanzari` — trecerea TEMP -> real + +`scrie_factura2` (PACK_FACTURARE, corpul activ, nu varianta comentata din cod): +- `:70` `pack_contafin.sterge_temp_actrul()`, seteaza `pack_facturare.ntotftva/ntottva` din + parametri (valori calculate in VFP). +- `:84` `SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP` — citeste TOATA tabela + temp intr-un array PL/SQL, fara filtru pe sesiune (GTT-ul e oricum izolat per sesiune/tranzactie). +- Bucla pe `tab_detalii`, ramificata tot pe `ntip` (transfer intre subunitati, restaurant etc.) — + pentru cazul standard de factura apeleaza mai departe spre `pack_facturare.finalizeaza_factura`. + +`finalizeaza_factura` (nu comentata): +```sql +pack_facturare.initializeaza_scriere_actrul(V_DATAORA); +pack_facturare.scrie_in_vanzari(V_DISCOUNT_FACTURA, V_ID_DELEGAT, V_ID_MASINA, V_ID_FACTURARE, + V_LISTARE_DETALIATA, V_DATAORA_EXP, V_ID_AGENT, V_TEXT_ADITIONAL, pack_facturare.nid_vanzare); +pack_facturare.finalizeaza_scriere_actrul(); +-- update vanzari set id_fact = ... (completare id_fact dupa scrierea in contabilitate) +``` + +`scrie_in_vanzari` — gasit exact mecanismul care lipsea din raportul anterior: +- `INSERT INTO VANZARI (...) VALUES (...) RETURNING ID_VANZARE INTO pack_facturare.nid_vanzare` + (randul parinte, un rand per document din bucla pe `tab_vanz`, tabela interna cu documentele de + scris — cazul normal e un singur document). +- `pack_facturare.scrie_cursuri(pack_facturare.nid_vanzare)`. +- **Trecerea efectiva TEMP -> real**: + ```sql + INSERT /*+ APPEND */ INTO VANZARI_DETALII + (ID_VANZARE, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD, + PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, ID_JTVA_COLOANA) + SELECT pack_facturare.nid_vanzare, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, + ID_VALUTAD, PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, + ID_JTVA_COLOANA + FROM VANZARI_DETALII_TEMP + WHERE ID_COMANDA = tab_vanz(i).id_comanda AND NUMAR_ACT = tab_vanz(i).numar_act + ORDER BY ID_TEMP; + ``` + Cheia de potrivire intre randul nou din `VANZARI` si liniile lui din TEMP e + `(ID_COMANDA, NUMAR_ACT)` — nu `ID_TEMP` direct — pentru ca un singur apel poate scrie mai multe + documente `VANZARI` deodata (facturare pe mai multe comenzi/numere de act simultan); fiecare + document isi ia doar liniile care se potrivesc. +- Coloanele COPIATE in `VANZARI_DETALII` sunt un subset din `VANZARI_DETALII_TEMP` (16 coloane) — + `ID_TEMP`, `ID_CTR`, `ID_UTIL`, `TAXCODE`, `LOT`, `ID_VANZARE_SET`, `ID_PART_REZ`, + `ID_LUCRARE_REZ`, `PRETV_ORIG`, `CUSTODIE`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST` + raman DOAR in TEMP, nu se copiaza in tabela reala (`VANZARI_DETALII` nu are coloanele `ID_TEMP`, + `NUMAR_ACT`, `ID_COMANDA` etc. — confirmat din `all_tab_columns`, vezi 2.2). +- Urmeaza blocul de agregare (`SELECT INTO` din `VANZARI_DETALII_TEMP`, deja documentat in + `rec_s5_oracle_vanzari.md` A.3-A.4) si `UPDATE VANZARI SET total_fara_tva=..., ...` cu totalurile + denormalizate. + +**PK-ul `VANZARI_DETALII.ID_VANZARE_DET`** se genereaza automat: trigger `TRG_VANZARI_DET_BEFOINS` +(`BEFORE INSERT ... FOR EACH ROW`, verificat prin `dbms_metadata.get_ddl`) face exclusiv +`SELECT SEQ_VANZARI_DETALII.NEXTVAL INTO :NEW.ID_VANZARE_DET FROM DUAL` — nu seteaza alte coloane. +Deci **orice INSERT nou in `VANZARI_DETALII`** (inclusiv unul scris pentru #6, la adaugare de linie +noua la editare) primeste automat PK-ul corect fara sa fie nevoie de secventa apelata manual din +codul de editare. + +## 2. Natura tabelei temp si structura + +### 2.1 `VANZARI_DETALII_TEMP` — Global Temporary Table, `ON COMMIT DELETE ROWS` + +Confirmat din `all_tables`: + +| TABLE_NAME | TEMPORARY | DURATION | +|---|---|---| +| VANZARI | N | — | +| VANZARI_DETALII | N | — | +| VANZARI_DETALII_TEMP | **Y** | **SYS$TRANSACTION** | +| VANZARI_SETURI_TEMP | **Y** | **SYS$TRANSACTION** | + +`DURATION = SYS$TRANSACTION` inseamna GTT cu `ON COMMIT DELETE ROWS` — randurile dispar la commit +(sau rollback), nu doar la sfarsit de sesiune. **Consecinta directa**: orice solutie care ar +reutiliza `VANZARI_DETALII_TEMP` pentru editare (#6) trebuie sa umple tabela SI sa consume +rezultatul in ACEEASI tranzactie — nu poate fi umpluta intr-un pas si citita in altul, ca in fluxul +de emitere unde umplere (bucla `adauga_articol_factura`) si consum (`scrie_factura2`) se intampla +deja in aceeasi conexiune/tranzactie, inainte de commit. + +### 2.2 Coloane: `VANZARI_DETALII` vs `VANZARI_DETALII_TEMP` + +`VANZARI_DETALII` (35 coloane, din `all_tab_columns`) — cheie primara `ID_VANZARE_DET` (NOT NULL, +generata din secventa prin trigger), FK logic `ID_VANZARE` (NOT NULL). Coloane relevante pentru +editare: `PRET` (NOT NULL), `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `STERS` +(NOT NULL), `VALIDAT`/`DATAORA_VALID`/`ID_UTIL_VALID`, `ID_UTILS`/`DATAORAS` (audit), +`DIFERENTA` (NOT NULL), `CUSTODIE`/`DESCARCAT` (NOT NULL) — ultimele patru nu au valoare implicita +vizibila in trigger, deci probabil `DEFAULT` la nivel de coloana (nu s-a verificat separat, nu era +in scope). + +`VANZARI_DETALII_TEMP` (28 coloane) — **fara PK real** (`ID_TEMP` e generat in VFP, folosit doar ca +identificator temporar de linie in cadrul sesiunii curente de compunere a facturii), **fara** +`ID_VANZARE`/`ID_VANZARE_DET`/`STERS`/`VALIDAT` (nu se aplica inainte de a exista documentul +parinte). Are in schimb coloane specifice etapei de compunere, care NU exista in tabela reala: +`ID_TEMP`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST`, `ID_UTIL` (vs `ID_UTILS` in cea reala). + +Coloanele comune folosite de editare (S4): `ID_ARTICOL`, `PRET`, `CANTITATE`, `PRET_CU_TVA`, +`DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`, `ID_VALUTA`, `CURS` (doar in TEMP; in +`VANZARI_DETALII` nu exista `CURS` per linie — cursul e doar pe document, in `VANZARI_CURSURI`/ +`VANZARI.CURS`), `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`/`EXPLICATIA` (nume diferit!), `TAXCODE`, +`LOT`. + +## 3. Stergerea si adaugarea de linii + +### 3.1 Stergere — mecanism EXISTENT azi doar la nivel de document intreg, NU per linie + +Cautat explicit orice `UPDATE VANZARI_DETALII SET STERS` in `PACK_FACTURARE` — gasite doar doua +locuri, ambele sterg TOATE liniile unui document, niciodata o singura linie: + +- `sterge_factura(V_ID_VANZARE, ...)`: + `UPDATE VANZARI_DETALII SET STERS = V_STERS, ID_UTILS = V_ID_UTIL, DATAORAS = V_DATAORA WHERE + ID_VANZARE = V_ID_VANZARE AND STERS = V_NESTERS` (V_STERS variaza 0/1 dupa tipul documentului, + cazul normal fiind 1). +- `sterge_proforma(V_ID_VANZARE, ...)`: acelasi tipar, exclusiv pe proforme. + +**Nu exista azi un mecanism Oracle sau VFP de stergere per-linie in `VANZARI_DETALII`** — orice +"stergere de linie la editare" ceruta de S4/S4b e functionalitate NOUA, de adaugat la #6, nu o +reutilizare a ceva existent. + +Exista insa un precedent apropiat de UPDATE punctual per linie, util ca sablon: +`modifica_explicatie_articol(V_ID_VANZARE_DET, V_EXPLICATIE, V_ID_UTIL, V_TAXCODE)` — +`UPDATE VANZARI_DETALII SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE WHERE ID_VANZARE_DET = +V_ID_VANZARE_DET` — exact modelul de "UPDATE tintit prin `goExecutor`" mentionat ca varianta in +planul S5/S6, deja folosit azi din `frm_modifica_articol_factura` (mostenire de la #7, vezi +`plan_06_editare_factura.md`). + +### 3.2 Adaugare de linie noua — nu cere nimic special in plus fata de un INSERT direct + +`ID_VANZARE_DET` vine automat din `SEQ_VANZARI_DETALII` prin trigger la orice `INSERT INTO +VANZARI_DETALII`, indiferent de cine face insert-ul (nu doar fluxul de emitere) — vezi 1.3. Deci +adaugarea unei linii noi la editare **nu** cere obtinerea manuala a unui ID din secventa; e suficient +un `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE, PRET_CU_TVA, ...) VALUES +(...)` (fara `ID_VANZARE_DET` in lista de coloane) intr-o procedura noua, apelata din +`finalizeaza_modificare_nota` alaturi de recalculul de totaluri propus in `rec_s5_oracle_vanzari.md` +punctul B. + +## 4. Propunerea pentru S4/S5/S6 — cum trimite editarea liniile modificate in Oracle + +### Optiuni comparate + +**A. Refolosirea `VANZARI_DETALII_TEMP` + o "mini scrie_in_vanzari" pentru editare** — ar insemna ca +formularul de editare (S4) sa populeze TEMP la fel ca la emitere (apeluri per linie), apoi o +procedura noua sa faca INSERT-urile/UPDATE-urile in `VANZARI_DETALII` din TEMP, in aceeasi +tranzactie (impusa de `ON COMMIT DELETE ROWS`, vezi 2.1). Risc: reproduce complexitatea lui +`adauga_articol_factura` (ramificare pe `ntip`/sursa document) fara sa aiba sens la editare — acolo +nu exista comanda/aviz/contract "curent" din care sa se re-deriva pretul; ar trebui un mod nou, +simplificat, de populare a TEMP doar pentru editare, ceea ce complica inutil un cod deja incarcat. +Risc mediu-mare de regresie pe calea de emitere daca vreo modificare atinge accidental +`adauga_articol_factura`/`scrie_in_vanzari` partajate. + +**B. UPDATE/INSERT/soft-DELETE punctual direct in `VANZARI_DETALII`, prin `goExecutor`, dintr-o +procedura noua dedicata editarii** — fara sa treaca deloc prin `VANZARI_DETALII_TEMP`. Model deja +existent si folosit: `modifica_explicatie_articol` (UPDATE tintit pe `ID_VANZARE_DET`). Pentru cele +trei operatii cerute de S4/S4b: + - **Editare cantitate/pret/`pret_cu_tva`** pe o linie existenta: `UPDATE VANZARI_DETALII SET + CANTITATE=..., PRET=..., PRET_CU_TVA=..., DISCOUNT_UNITAR=... WHERE ID_VANZARE_DET = :id`. + - **Stergere linie**: `UPDATE VANZARI_DETALII SET STERS=1, ID_UTILS=:util, DATAORAS=SYSDATE WHERE + ID_VANZARE_DET = :id` — acelasi tipar ca `sterge_factura`, dar tintit pe o singura linie (nou, + nu exista azi, vezi 3.1). + - **Adaugare linie noua**: `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE, + PRET_CU_TVA, DISCOUNT_UNITAR, PROC_TVAV, ID_GESTIUNE, CONT, ID_VALUTA, ID_JTVA_COLOANA, SERIE, + EXPLICATIE, TAXCODE, LOT) VALUES (...)` — PK automat din trigger (vezi 3.2). + Apoi, in ACEEASI tranzactie (deschisa deja de `do_deschide_tranzactie` in fluxul de editare a + notei, cf. `rec_s5_oracle_vanzari.md` B "Idempotenta si tranzactionalitate"), se cheama procedura + de recalcul a totalurilor propusa in `rec_s5_oracle_vanzari.md` (`recalculeaza_totaluri_vanzari`), + care citeste direct din `VANZARI_DETALII WHERE ID_VANZARE=:id AND STERS=0` — **fara nicio + dependenta de `VANZARI_DETALII_TEMP`**. + +### Recomandare: **Varianta B** + +Argumentul principal e riscul de regresie asupra emiterii: Varianta A ar obliga fie la extinderea +lui `adauga_articol_factura` cu o ramura noua "editare" (cod partajat cu emiterea, risc direct pe +calea critica), fie la duplicarea partiala a logicii lui in altă procedura (risc de divergenta, +exact problema pe care #8 a corectat-o deja pentru calculul de totaluri). Varianta B nu atinge deloc +`adauga_articol_factura`/`scrie_in_vanzari`/`VANZARI_DETALII_TEMP` — cod nou, izolat, apelat doar +din calea de editare (`finalizeaza_modificare_nota`), cu acelasi profil de risc "mic" motivat deja +pentru `recalculeaza_totaluri_vanzari` in `rec_s5_oracle_vanzari.md`. In plus, B se potriveste +natural cu S4b (verificare/sincronizare explicita, nu silentioasa): fiecare rand modificat/sters/ +adaugat poate fi tratat ca o comanda separata, usor de enumerat utilizatorului inainte de aplicare +("linia X: cantitate 5 -> 8, pret 10 -> 12"), pe cand Varianta A ar produce un singur bloc opac de +recalcul din care nu se pot extrage usor liniile individuale schimbate. + +**Observatie tehnica pentru implementare**: la editarea preturilor, NU se recalculeaza `V_PROC_TVAV` +din `JTVA_COLOANE`/comanda/contract ca la emitere (asta ar reintroduce dependenta de sursa +documentului, respinsa mai sus) — cota de TVA ramane cea deja persistata pe linie +(`VANZARI_DETALII.PROC_TVAV`), coerent cu felul in care `pret_cu_tva`/`proc_tvav` sunt deja tratate +ca date proprii ale liniei si nu recalculate la fiecare atingere (cf. #7). + +## 5. Ce ramane neconfirmat / in afara scopului acestei cercetari + +- Coloanele `STERS`, `VALIDAT`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT` din `VANZARI_DETALII` sunt + `NOT NULL` fara sa aiba valoare din trigger — probabil au `DEFAULT` la nivel de coloana + (`all_tab_columns.data_default` nu a fost interogat, nu era necesar pentru concluziile de mai + sus); de verificat explicit inainte de a scrie INSERT-ul de linie noua din Varianta B, ca sa nu + fie nevoie sa le populeze manual. +- Valorile implicite exacte pentru `DEFAULT` (daca exista) pe coloanele NOT NULL de mai sus. +- Comportamentul `VANZARI_SETURI_TEMP`/liniile din seturi la editare (#6 nu pare sa acopere editarea + seturilor, doar articolele individuale — de clarificat cu Marius daca seturile sunt in scope). diff --git a/docs/cercetare/rec_d42_efactura.md b/docs/cercetare/rec_d42_efactura.md new file mode 100644 index 0000000..598a67b --- /dev/null +++ b/docs/cercetare/rec_d42_efactura.md @@ -0,0 +1,245 @@ +# Implementarea deciziei 42 — articole needitabile pe factura trimisa in eFactura + +Scris 10.08.2026. Implementare + testare + write-back verificat. **ZERO commit** (git/svn), +conform interdictiei primite. + +> ## COMPLETARE, 10.08.2026 ora ~12:55 — discountul de antet intra sub garda +> +> Agentul care a scris acest raport a apucat sa faca **modificarea de cod si write-back-ul**, apoi a +> cazut pe limita de sesiune **inainte de verificare**. Blocul de mai jos e scris de orchestrator, +> care a preluat si a dus verificarea la capat. +> +> **Decizia lui Marius**: `txtDiscountArt` (discountul de antet) **se blocheaza si el** cand documentul +> e trimis in eFactura — schimba totalurile unui document deja depus la ANAF, chiar daca nu atinge +> nicio linie. +> +> **Codul**: `omodificari.vc2:14813` — `This.pgfArticole.PAGE3.txtDiscountArt.ReadOnly = +> This.lArticoleReadOnly`, prin acelasi flag, fara mecanism nou. +> +> **A doua cale de scriere — cautata si exclusa**: `txtDiscountArt.Valid` (`:16592`) doar cheama +> `Thisform.ActualizeazaBaraTotaluri()`, deci **recalculeaza afisajul, nu scrie discountul nicaieri**. +> Nu exista buton sau apel programatic care sa-l seteze ocolind caseta, deci garda dubla care a fost +> necesara la butoanele de articole (`Click`) **nu isi are rostul aici**. Salvarea propriu-zisa trimite +> discountul ca parametru catre `recalculeaza_totaluri_vanzari` (decizia 41), luand valoarea din caseta. +> +> **Verificat de orchestrator pe disc, dupa caderea agentului**: +> - **Write-back complet**, dovedit prin reconversie binar -> text in cache temporar si comparatie +> **octet cu octet**: identic, 540712 octeti. Cens de octeti `2 aa . 2 e3 . 2 fe`, zero `EF BF BD`. +> - **Regresia rerulata integral pe starea de pe disc**, dupa write-back (binar 12:33:09): +> `test_page3_articole` **14/2** (artefactul headless cunoscut), `test_adauga_linie_articol` **20/0**, +> `test_adauga_linie_valuta` **16/0**, `test_ui_sterge_linie` **8/0**, `test_verdict_act_rul` **26/0**, +> `test_incarca_vanzare_din_nota` **5/0**, `test_s5_validari_articole` **35/0**. Nicio regresie. +> - **Acoperire noua**: `test_efactura_readonly.prg` extins cu doua asertii si rerulat — +> **23 PASS / 0 FAIL** (de la 21). Cele doua noi: `caz A: txtDiscountArt.ReadOnly = .T.` pe documentul +> real trimis in eFactura, si `caz B: txtDiscountArt.ReadOnly = .F. (neregresat)` pe cel netrimis. +> Garda e verificata deci **in ambele sensuri**, nu doar pe cazul pozitiv. +> - Zero procese `vfp9.exe` ramase, zero commituri. +> +> **Ce NU e acoperit**: suita UI vizibila (`test_ui_efactura_readonly.prg`, 14/0) **nu a fost rerulata** +> dupa adaugarea discountului — ea verifica `.When`-urile de celula, neatinse de aceasta completare, +> deci riscul e mic, dar golul e declarat, nu ascuns. +> +> Diff-ul consolidat (COMUN + ROAGEST) e regenerat in diff aplicat (sters). + +## Ce s-a schimbat, si unde + +### `COMUN\clase\omodificari.vc2` (`frm_modific2024`) + +- **Proprietate noua** `lArticoleReadOnly` (Boolean, implicit `.F.`): `*p:` la `:6827`, valoare + implicita la `:6866`. +- **`Show`** (`:14788-14827`): calculeaza flagul in acelasi bloc unde se determina + `lAreArticoleVanzari`/`nIdVanzare`/`nTipVanzare` (adica doar cand `ofacturare_editare.prg` e + incarcat si documentul are rand in `VANZARI`): + `This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0))` (`:14798`). In blocul care + pregateste `PAGE3`, aplica flagul: `cmdAdaugaArticol.Enabled`/`cmdStergeArticol.Enabled` = + `!lArticoleReadOnly` (`:14811-14812`), `lblArticoleReadOnly.Visible = lArticoleReadOnly` + (`:14813`). **`PAGE3` continua sa se pregateasca si sa se afiseze normal** — + `PregatesteArticoleFacturaEditare`/`PageCount=3`/`grdArticoleFactura.Refresh()` raman neatinse, + conform corectiei explicite a lui Marius (articolele raman vizibile). +- **Control nou** `pgfArticole.PAGE3.lblArticoleReadOnly` (`ADD OBJECT` la `:12776`): eticheta + discreta, `Visible=.F.` implicit, pozitionata pe randul butoanelor (`Top=4`, `Left=360`, + `Width=390`) — la dreapta lui `cmdAdaugaArticol` (care se termina la `Left+Width=345`), deci + **nu ia spatiu din grid** (gridul ramane la `Top=26`). Text: „Articole needitabile - factura + trimisa in eFactura", `ForeColor RGB(180,120,0)` (aceeasi nuanta de atentionare folosita deja + la verdictul ACT/RUL divergent). +- **Garda pe butoane** (`cmdAdaugaArticol.Click` `:16488`, `cmdStergeArticol.Click` `:16531`): + `OR Thisform.lArticoleReadOnly` adaugat la conditia de `RETURN` timpuriu — belt-and-suspenders + fata de `Enabled=.F.`, pentru ca `Enabled` nu blocheaza un apel programatic al metodei `.Click()`. +- **Garda pe celule** — cele patru `.When` care controleaza editabilitatea pe rand (mecanismul + din S5): `cCantitateArt.Text1.When` (`:16549`), `cPretAchizitieArt.Text1.When`, + `cPretArt.Text1.When` (`:16570`), `cPretCuTvaArt._checkbox1.When` (`:16587`) — toate primesc + `Thisform.lArticoleReadOnly OR ...` (respectiv `!Thisform.lArticoleReadOnly AND ...` pe + checkbox, unde conditia veche era inversa). Se construieste **peste** mecanismul de + editabilitate per rand din S5 (`id_vanzare_set`/`id_vanzare_det`), nu-l inlocuieste. +- **Notele/rulajele nu sunt atinse**: `Grid1` (nota contabila), `grdRulaje`/`grdRulajeObinv` + (paginile 1/2) raman complet neschimbate — verificat explicit in test (`Grid1.ReadOnly` ramane + `.F.` pe documentul din eFactura). + +### `COMUN\programe\ofacturare_editare.prg` + +Corectie necesara descoperita in timpul lucrului (vezi mai jos „Decizie/corectie luata pe +parcurs"): `EsteInEFactura` se apeleaza cu `VANZARI.ID_FACT`, **nu** cu `id_vanzare` — sunt doua +coloane distincte (verificat pe date: 0 potriviri `id_fact = id_vanzare` din 142 facturi tip=1). +Contractul existent (`ofacturare_comun.vc2:3764`, neatins) apela deja `EsteInEFactura(lnIdFact)` +cu `lnIdFact = crsfacturi.id_fact`. `tvanz` (populat de `IncarcaVanzareNota`) nu avea aceasta +coloana. + +- `CreeazaCursorTvanzGol` (linia 153 din fisier): adaugat `id_fact I NULL` la structura cursorului + gol (fallback pe eroare Oracle/cod lipsa). +- `IncarcaVanzareNota` (linia 177): adaugat `v.id_fact` la lista de coloane selectate din + `VANZARI`. Restul interogarii (join, filtre) neatins. +- Antetul fisierului (o singura intrare cumulativa, rescrisa): data actualizata la 10.08.2026, + mentiunea `id_fact` adaugata la lista de campuri incarcate. + +## Decizie/corectie luata pe parcurs — de raportat, nu era in briefing + +Implementarea initiala folosea `EsteInEFactura(This.nIdVanzare)` (adica `id_vanzare`). Verificare +pe `MARIUSM_AUTO`: + +```sql +SELECT COUNT(*) total, SUM(CASE WHEN id_fact = id_vanzare THEN 1 ELSE 0 END) equal_cnt +FROM vanzari WHERE tip = 1 AND sters = 0; +-- 142 total, 0 equal_cnt +``` + +`id_fact` si `id_vanzare` sunt spatii de ID complet diferite (`id_fact` are valori de forma +`8008013`, `id_vanzare` valori mici de forma `1013`) — cu `id_vanzare` gardarea nu s-ar fi +declansat NICIODATA in productie (nicio coincidenta intamplatoare intre cele doua plaje). Corectat +inainte de scrierea testelor, folosind exact coloana pe care o foloseste deja calea din +`ofacturare_comun.vc2:3764` (`lnIdFact = crsfacturi.id_fact`). + +## Write-back — verificat prin reconversie + diff, nu pe mtime + +`txt2vcx.ps1 -AllowComun` rulat de trei ori (prima incercare a picat fidelity-check-ul din cauza +ordinii gresite a blocului `ADD OBJECT` — proprietatile/obiectele dintr-o clasa `.vcx` trebuie in +ordine STRICT alfabetica dupa nume, nu dupa ZOrder; a doua rulare a picat pentru ca lipsea +corectia `id_fact`; **a treia rulare, OK**, fidelity check trecut). + +Verificare **independenta** de fidelity-check-ul intern al `txt2vcx.ps1`: reconversie separata a +binarului proaspat scris (`vcx2txt.ps1 -Source omodificari.vcx` intr-un cache temporar izolat) + +`diff`/`cmp` octet cu octet fata de `.vc2`-ul din proiect: + +``` +diff -q omodificari.vc2 /omodificari.vc2 -> IDENTIC +cmp omodificari.vc2 /omodificari.vc2 -> BYTE-IDENTIC +``` + +Text si binar sunt sincrone, dovedit, nu presupus. + +## Cens de octeti (regula cp1250) — inainte/dupa, identic cu baseline + +`omodificari.vc2` are diacritice cp1250 preexistente (tooltip-uri „Renunțare/Adăugare/Ștergere", +octeti `0xFE`/`0xE3`/`0xAA`). **Baseline: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`.** + +Editarea celor doua proprietati noi (`*p:`/`*`) s-a facut initial cu tool-ul `Edit` +(2 apeluri) — asta a **stricat** censul (`6 ef / 6 bf / 6 bd`, adica `EF BF BD` x2 aparitii x3 +octeti), exact capcana documentata (`Edit`/`Write` re-encodeaza tot fisierul la orice scriere, +indiferent cat de mica). **Reparat** inainte de a continua: octetii corecti (`Renun[FE]are`, +`Ad[E3]ugare`, `[AA]tergere`) preluati din `git cat-file blob HEAD:clase/omodificari.vc2` +(varianta necorupta, comisa) si inlocuiti inapoi punctual. Toate editarile ulterioare (Show(), +garzile pe butoane/celule, blocul `ADD OBJECT` al etichetei noi) s-au facut **pe octeti**, cu +Perl (`<:raw`/`>:raw`, cautare/inlocuire literala `\Q...\E`, fara nicio decodare de encoding), +tocmai ca sa nu se repete problema. **Cens final: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`** — +identic cu baseline, verificat dupa fiecare grup de editari. + +`ofacturare_editare.prg` nu are octeti `>=0x80` (cens gol inainte si dupa) — editat direct cu +`Edit`, fara risc. + +## Regresie — cifre numarate din loguri, toate DUPA ultima editare de cod + +Ultima editare de cod: `omodificari.vc2` scris in binar la `11:37:23` (a treia rulare +`txt2vcx.ps1`, cu corectia `id_fact`); `ofacturare_editare.prg` editat inainte de asta. Toate +rularile de mai jos sunt **dupa** acel moment. + +| Suita | Rezultat | Baseline | Stare | +|---|---|---|---| +| `test_page3_articole.prg` | 14 PASS / 2 FAIL | 14/2 | **neregresat** (cele 2 FAIL = artefactul headless cunoscut, ColumnCount/ReadOnly pe grid needitabil sub `-A -T`) | +| `test_adauga_linie_articol.prg` | 20 PASS / 0 FAIL | 20/0 | **neregresat** | +| `test_adauga_linie_valuta.prg` | 16 PASS / 0 FAIL | 16/0 | **neregresat** | +| `test_ui_sterge_linie.prg` | 8 PASS / 0 FAIL | 8/0 | **neregresat** | +| `test_verdict_act_rul.prg` | 26 PASS / 0 FAIL | 26/0 | **neregresat** | +| `test_incarca_vanzare_din_nota.prg` | 5 PASS / 0 FAIL | 5/0 | **neregresat** | +| `test_s5_validari_articole.prg` | 35 PASS / 0 FAIL | 35/0 | **neregresat** (linia care contine cuvantul "FAIL" e text descriptiv al unei ramuri moarte deja documentate, nu un esec real — `REZULTAT: 35 PASS / 0 FAIL`) | + +Zero regresii pe toate cele sapte suite existente. + +## Suite noi + +### `COMUN\utile\Teste\editare_factura\test_efactura_readonly.prg` (headless) — **21 PASS / 0 FAIL** + +Documente REALE, gasite prin interogare (nu inventate): +- **Caz A — trimis in eFactura**: `id_vanzare=1013` (`cod=1140509`, an=2025, luna=8), + `VANZARI.ID_FACT=8008013`, prezent in `ANAF_EFACTURA`. +- **Caz B — NEtrimis** (regresie): descoperit prin proprietate (`DescoperaCazTest`, + `FACTURA_ARTICOLE`), `cod=1140895` (id_vanzare=1050, documentul deja folosit de restul suitei). + +Acopera: `EsteInEFactura` direct (cu `0` si cu `8008013`); `lArticoleReadOnly` corect pe ambele +cazuri; **`PAGE3` NU e suprimata** (`PageCount=3`, `RecordSource` neschimbat, `tvd` cu linii); +`cmdAdaugaArticol`/`cmdStergeArticol.Enabled`; vizibilitatea etichetei; garda pe +`cmdStergeArticol.Click()` (apelat direct, fara dialog modal — confirma ca linia nu se modifica +pe documentul din eFactura si ca se modifica normal pe cel obisnuit); `Grid1.ReadOnly` neatins +(nota ramane editabila). Nu scrie in Oracle. + +**Ce nu acopera** (documentat explicit in fisier, nu ascuns): editabilitatea per-celula +(`.When()` pe coloanele gridului) — coloanele **nu se materializeaza sub `-A -T`** +(`ColumnCount=0`, eroare 1925 „Unknown member" la accesul pe nume), artefact cunoscut si +documentat (`grid-coloane-nu-se-materializeaza-headless`). Mutat in suita UI de mai jos. + +### `COMUN\utile\Teste\editare_factura\test_ui_efactura_readonly.prg` (UI vizibil) — **14 PASS / 0 FAIL** + +Rulat prin harnessul corect (`vfp_ui_harness.ps1 -TestPrg ... -Steps @(...) -SyncDir ...`), nu +prin lansare directa — o incercare initiala prin `Start-Process` direct a dat tot `ColumnCount=0`; +cauza reala **nu era lansarea**, ci lipsa apelului `IncarcaArticoleFactura(1013, +'crsArticoleFactura')` **inainte** de `Createobject` — gridul se leaga o singura data, la +construire (in `Load()`), pe cursorul `tvd` existent atunci; fara precarcare, `Load()` creeaza +`tvd` GOL, iar fallback-ul din `Show()` il **recreeaza prin SQL** dupa constructie — cursor diferit +de cel pe care s-a legat gridul, deci `ColumnCount` ramane 0. Corectat dupa tiparul deja folosit de +`test_ui_s5_grid_pret_achizitie.prg`. + +Acopera, pe acelasi document real (`id_vanzare=1013`, in eFactura): gridul are 15 coloane +(neregresat); `.When()` pe toate cele patru controale (`cCantitateArt`, `cPretArt`, +`cPretAchizitieArt`, `cPretCuTvaArt._checkbox1`) intorc `.F.` — **needitabile pe orice linie +normala, nu doar pe liniile din set**; caz sintetic de control (`lArticoleReadOnly` comutat manual +pe `.F.` pe acelasi document/aceleasi obiecte) — cele trei celule redevin editabile, deci gating-ul +e demonstrat pe flag, nu pe o coincidenta a documentului ales. Captura: +`screenshots_efactura\step_0_grid_efactura_readonly.png`. Nu scrie in Oracle. + +## Ce NU s-a putut testa, si de ce + +- **Dialogul de adaugare articol** (`frm_articol_factura`, `Show(1)` modal): garda de pe + `cmdAdaugaArticol.Click()` (Enabled=.F. + guard in cod) nu s-a putut exercita prin click real + headless — consecvent cu limitarea deja documentata pe restul suitei S4/S5 (dialog modal). Doar + `Enabled=.F.` verificat direct. +- **Ramura moarta preexistenta, gasita dar NEATINSA** (nu in scope): linia + `IF !Used('tvd') OR !This.lAreArticoleVanzari OR Thisform.lArticoleReadOnly` din + `cmdAdaugaArticol.Click` (`:16497`) foloseste `This.lAreArticoleVanzari` — `This` acolo e + butonul, nu formularul, deci proprietatea nu exista pe el. Ramane neexecutata in practica pentru + ca `!Used('tvd')` e `.F.` de fiecare data cand butonul chiar e vizibil (short-circuit VFP), deci + runtime-ul nu ajunge niciodata sa evalueze operandul gresit. Preexistenta editarii mele + (adaugarea mea e doar `OR Thisform.lArticoleReadOnly`, corect scris cu `Thisform`), nu se + repara aici — in afara scope-ului deciziei 42. +- **Verificarea vizuala pe ecran de Marius**: eticheta discreta, pozitionarea ei fata de butoane, + culoarea, comportamentul real la click de mouse pe grid. + +## Stare finala verificata + +- **Write-back facut si dovedit** pentru `omodificari.vc2` (reconversie + diff octet cu octet, + identic). `ofacturare_editare.prg` e `.prg` — sursa directa, fara pas de write-back. +- Cens de octeti pe `omodificari.vc2`: **`2 aa / 2 e3 / 2 fe`, zero `EF BF BD`** — identic cu + baseline, verificat dupa ultima editare. +- Zero procese `vfp9.exe` ramase (verificat cu `tasklist`) dupa toate rularile mele. Procesul + `vfp9.exe` al agentului `s8-creare-variante` (fereastra „S8 - creare documente") a ramas + intact, neatins. +- Zero scrieri in Oracle in toata sesiunea — toate interogarile (`EsteInEFactura`, + `IncarcaCursoareModificareNota`, `IncarcaVanzareNota`, `IncarcaArticoleFactura`) sunt `SELECT`. +- Zero commit (git/svn). +- Diff consolidat: diff aplicat (sters) (ambele fisiere). +- Fisiere noi, necomise: `COMUN\utile\Teste\editare_factura\test_efactura_readonly.prg` (+ log), + `COMUN\utile\Teste\editare_factura\test_ui_efactura_readonly.prg` (+ log + screenshot in + `screenshots_efactura\`). + +## Interzis — respectat + +`ofacturare.vc2`, `ofacturare_comun.vc2`, `comun.vc2`: neatinse (verificat — niciun `Edit`/`Write` +pe ele in aceasta lucrare). `actualizeaza_vanzari`, `PACK_CONTAFIN`, `PACK_FACTURARE`: neatinse. +Niciun `INSERT`/`UPDATE` pe `id_vanzare` `1049`/`1050`/`1048`. diff --git a/docs/cercetare/rec_datoria6_baza_regresie.md b/docs/cercetare/rec_datoria6_baza_regresie.md new file mode 100644 index 0000000..be4c59d --- /dev/null +++ b/docs/cercetare/rec_datoria6_baza_regresie.md @@ -0,0 +1,188 @@ +# Datoria 6 — ce s-a intamplat cu baza de regresie #6/S4 si cum a fost re-ancorata + +09.08.2026. Schema `MARIUSM_AUTO@ROA_CENTRAL`. **Strict citiri pe Oracle** — nicio scriere, niciun +commit. + +## 1. Ce s-a intamplat cu documentul (dovada pe randuri) + +Ipoteza de plecare **se confirma**: documentul nu s-a pierdut, i s-a **realocat `cod`-ul**. +`ID_VANZARE = 1050` exista, e activ si nu si-a schimbat niciun total — doar `COD` a trecut de la +**1140888** la **1140895**. + +``` +ID_VANZARE COD STERS TIP NUMAR_ACT SERIE DATA_ACT ID_FACT TOTAL_CU_TVA + 1050 1140895 0 1 547 SSS 07.08.2026 8009660 1924.59 + 1047 1140885 0 -12 544 SSS 07.08.2026 8009657 747.79 + 1048 1140894 0 1 545 SSS 07.08.2026 8009658 302.51 + 1049 1140887 0 1 546 SSS 07.08.2026 8009659 573.81 +``` + +Pe intervalul 1140880-1140900 exista **doar** aceste 4 randuri; `max(cod)` in `VANZARI` e +**1140895**, `max(id_vanzare)` e **1050**. Nu exista niciun rand pe `cod = 1140888`. + +Nota contabila arata acelasi lucru — acelasi antet, acelasi total, doar `STERS` si `COD` diferite: + +``` + COD AN LUNA STERS N NRACT SERIE DATAACT ID_FACT SUMA_TOT + 1140888 2026 8 1 24 547 SSS 07.08.2026 8009660 4836.67 + 1140895 2026 8 0 24 547 SSS 07.08.2026 8009660 4836.67 +``` + +24 de randuri vechi marcate `STERS=1` pe `cod` vechi, 24 de randuri noi active pe `cod` nou, cu +`nract`/`serie_act`/`dataact`/`id_fact` identice si aceeasi suma. Este exact semnatura lui +`finalizeaza_modificare_nota` + `pack_facturare.actualizeaza_vanzari(cod_vechi, cod_nou)`. +`VANZARI_DETALII` pe `id_vanzare = 1050` are in continuare **4 linii active** — neatins, corect +(scrierea in `VANZARI_DETALII` e S5, inca neimplementata). + +**Cand**, pe secunda (`ACT.DATAORAS` = marcarea ca sters, `ACT.DATAORA` = crearea randului): + +| Actiune | Moment | +|---|---| +| `cod=1140893` marcat sters, `cod=1140894` creat (`id_vanzare=1048`) | 08.08.2026 09:16:29 / 09:16:30 | +| `cod=1140888` marcat sters, `cod=1140895` creat (`id_vanzare=1050`) | 08.08.2026 14:05:15 / 14:05:16 | + +Deci **nu** testul de write-back aprobat a mutat documentul de regresie: acela a lucrat pe +`id_vanzare = 1048` dimineata la 09:16 (`1140886 -> 1140893 -> 1140894`, consemnat in `progres.md`). +Mutarea lui `1050` e o **a doua salvare, la 14:05**, pe un alt document — cel folosit ca ancora de +regresie. Nu exista in `ACT` niciun `cod` intermediar intre 1140888 si 1140895 (1140889-1140892 n-au +randuri), deci a fost o singura realocare. + +**Nimic de recreat.** Datele nu s-au pierdut; ancorarea suitelor era gresita. Prin urmare **nu se +cere nicio decizie de INSERT/UPDATE** din partea lui Marius pe partea de date. + +Ancorele celorlalte cazuri sunt neatinse: `id_vanzare` 1047 (`cod=1140885`), 506 (`cod=1137874`), +882 (`cod=1139934`) sunt toate active pe acelasi `cod` ca inainte — se realoca doar documentele care +chiar se salveaza, adica cele din luna curenta, singurele care trec de garzile din +`do_editare_factura`. + +## 2. Cifrele masurate azi, inainte de modificare + +Cifra din `progres.md` (`7 PASS / 3 FAIL`) era **veche**: numara doar cele 10 asertii de la runda 2, +inainte ca blocul 3A sa adauge alte 5. Masurat azi, pe starea de pe disc: + +| Suita | Inainte | Dupa | +|---|---|---| +| `test_page3_articole.prg` | **8 PASS / 7 FAIL** (15 verificari) | **13 PASS / 2 FAIL** | +| `test_incarca_vanzare_din_nota.prg` | **4 PASS / 1 FAIL** | **5 PASS / 5** | + +Ambele rulari (inainte si dupa): `exit code 0`, **0 dialoguri native**, sub +`COMUN\utile\Teste\watchdog_vfp.ps1 -AutoDismiss` (care sterge `.fxp`-ul inainte de fiecare +lansare). `loForm.ClassLibrary` e asigurat de blocul existent +`RELEASE CLASSLIB omodificari` + `SET CLASSLIB TO D:\ROA\ROAFACTURARE\COMUN\clase\omodificari.vcx`, +neatins de modificarea de fata. + +Motivul concret al esecurilor: `IncarcaCursoareModificareNota` filtreaza `STERS = 0`, iar toate cele +24 de randuri `ACT` de pe `cod=1140888` sunt `STERS=1` — deci `tact` venea **cu 0 randuri**, iar +suita nici nu ajungea sa instantieze formularul (`PageCount = -1` in log). + +## 3. Ce s-a schimbat in suite si de ce + +Principiul aplicat e **varianta 1 din brief**: suitele isi descopera singure documentul de test, dupa +**proprietatea ceruta de asertie**, nu dupa identitatea lui. Ancorarea pe `cod` era condamnata prin +constructie (se realoca la fiecare salvare); ancorarea pe `id_vanzare` ar fi rezistat, dar tot cere +un numar scris de mana intr-un fisier de test. + +### Fisier nou: `COMUN\utile\Teste\editare_factura\descopera_caz_test.prg` + +`DescoperaCazTest(, )` lasa in alias un rand cu `cod, an, luna, id_vanzare, tip, +nlin` si intoarce `.T.`/`.F.` Sase cazuri, fiecare o proprietate: + +| Caz | Proprietatea ceruta | Rezolvat azi la | +|---|---|---| +| `FACTURA_ARTICOLE` | `tip=1` activa, cu linii active, a carei nota are **primul** rand (`min(id_act)`) pe acelasi `(nract, serie_act, dataact)` ca vanzarea | `cod=1140895`, `id_vanzare=1050`, 4 linii | +| `NEFACTURA_ARTICOLE` | la fel, dar `tip <> 1` (decizia 19 — detectia merge pe orice tip) | `cod=1140885`, `id_vanzare=1047`, `tip=-12` | +| `FARA_RULAJE` | linii active + **zero** randuri in `vrul_tot` si `vrul_obinv_tot` | `cod=1140885`, `id_vanzare=1047` | +| `PRIM_RAND_ORB` | nota al carei **prim** rand NU duce la vanzare, dar un triplet ulterior da | `cod=1140401`, `id_vanzare=1005` | +| `COLIZIUNE_COD` | idem + `cod` cu 2+ randuri active in `VANZARI` + un triplet din nota fara corespondent | `cod=1139934`, `id_vanzare=882`, `nract` fara corespondent = 13 | +| `NOTA_FARA_VANZARI` | nota activa fara rand in `VANZARI` pe **niciun** triplet | `cod=1140883`, an 2026, luna 7 | + +Doua lucruri contau la proiectare: + +- **Independenta fata de codul testat.** Interogarile merg pe `VACT_TOT` / `VRUL_TOT` / + `VRUL_OBINV_TOT` / `VANZARI` / `VANZARI_DETALII` cu join direct, adica pe **alt drum** decat + `IncarcaVanzareNota` / `IncarcaVanzareDinNota` / `IncarcaArticoleFactura`. Valoarea asteptata + (`id_vanzare`, `tip`, numarul de linii) nu vine de la functia verificata, deci asertia nu devine + tautologica. +- **Ordonare determinista** (`order by v.id_vanzare desc`, respectiv `an/luna/cod desc`), ca doua + rulari succesive pe aceleasi date sa aleaga acelasi document. Cazul ales e scris in log la + inceputul fiecarei rulari, ca sa se vada pe ce document s-a masurat. +- **Nicio potrivire = FAIL explicit**, nu test sarit: `caz_negasit` scrie in log + `niciun document din schema nu satisface conditia cazului` + `FAIL`. + +Interogarile au fost validate intai direct in `sqlplus` (fiecare intoarce documentul asteptat), abia +apoi puse in cod. + +### `test_page3_articole.prg` + +Toate cele sase documente hardcodate (`1140888`, `1140885`, `1125486`, `1139934`, `1137874`) au fost +inlocuite cu cazul descoperit corespunzator. Structura asertiilor e neschimbata; procedurile de +verificare si-au pastrat corpul, doar au primit prin parametru ce inainte era scris in ele: + +- `verifica_coliziune_cod` primea zero parametri si continea `1139934 / 375 / 'SSS' / 31.12.2021` si + `nract=13`; acum primeste `cod`, tripletul care **trebuie** gasit, `id_vanzare` asteptat si + **tripletul complet** care **nu trebuie** gasit. Descoperirea intoarce cele trei coloane ale + randului negativ de pe acelasi rand (`keep (dense_rank first order by nract)`), ca sa nu se combine + `nract`-ul unui rand cu `serie_act`-ul altuia — altfel asertia negativa ar fi trecut din alt motiv + decat cel testat. +- **O asertie s-a intarit, niciuna nu s-a slabit.** Cazul B (`tip <> 1`) trecea inainte cu + `tnLiniiAsteptate = -1`, adica verificarea numarului de linii era dezactivata; acum primeste + numarul real din `VANZARI_DETALII` (2) si il verifica. + +### `test_incarca_vanzare_din_nota.prg` + +Aceleasi patru cazuri, plus cazul EOF, trecute pe descoperire. Nicio schimbare de asertie. + +## 4. Ce a ramas neacoperit + +**Doua asertii din blocul 3A raman FAIL, si NU din cauza datelor** — sunt artefactul de mediu deja +consemnat ca datoria 7 in `progres.md`: + +``` +EROARE 1925 [VERIFICA_EDITARE_GRID:353] Unknown member COLUMN5. + structura grid/cursor (ColumnCount=14, lmodificat L, valoare N) = FAIL + ReadOnly (cantitate/pret/pret_cu_tva editabile, checkbox pe pret_cu_tva, restul readonly) = FAIL + stare initiala (lmodificat=.F., valoare calculata corect) = PASS + dupa editare cantitate (lmodificat=.T., valoare recalculata) = PASS +``` + +Sub `vfp9.exe -A -T` grid-ul nu se materializeaza: `ColumnCount` raporteaza `0` si `ColumnN` nu +exista ca membru, oricat de complet ar fi definita clasa. Repartitia 2 FAIL (structura, `ReadOnly`) +/ 2 PASS (cursor, calcul) e **exact** cea prezisa in `progres.md`, datoria 7 — deci suita e acum +inapoi la starea ei dinaintea degradarii bazei, nu mai bine si nu mai rau. + +Cele doua asertii **nu au fost atinse, slabite sau sterse**. Ele sunt oricum acoperite corect pe +ecran de `COMUN\utile\Teste\editare_factura\test_ui_fix_editabil_subtotal.prg` sub +`vfp_ui_harness.ps1` (11/11 PASS, `ColumnCount=14`, consemnat in `progres.md`). Daca se doreste +curatarea zgomotului, varianta corecta e cea deja propusa la datoria 7 — rescrierea lor ca +verificare **statica** pe memo-ul `Properties` din `.vcx` — dar asta e alta lucrare, nu re-ancorare. + +Altele: + +- Nu s-a verificat comportamentul suitelor pe alta schema decat `MARIUSM_AUTO`. Descoperirea e + scrisa sa mearga pe orice schema, dar nu a fost probata pe `ROMFAST` sau `VENDING`. +- Cazurile `PRIM_RAND_ORB` si `NOTA_FARA_VANZARI` se rezolva azi la alte documente decat inainte + (`1140401` in loc de `1137874`, `1140883` in loc de `1125486`) — proprietatea testata e insa + aceeasi, iar ambele trec. Vechile documente raman valide, doar ca nu mai sunt primele in ordinea + determinista. +- Nu s-a atins nimic din `omodificari.vc2`, `ofacturare_comun.vc2`, `ofacturare_editare.prg` sau + binarele lor. `git status` in `COMUN` confirma: singurele fisiere de test schimbate sunt cele doua + suite plus fisierul nou. + +## 5. Fisiere + +| Fisier | Stare | +|---|---| +| `COMUN\utile\Teste\editare_factura\descopera_caz_test.prg` | **nou** | +| `COMUN\utile\Teste\editare_factura\test_page3_articole.prg` | modificat | +| `COMUN\utile\Teste\editare_factura\test_incarca_vanzare_din_nota.prg` | modificat | +| diff aplicat (sters) | diff-ul celor trei | + +Comenzile de rulare: + +```powershell +powershell -ExecutionPolicy Bypass -File D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1 -Script 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_page3_articole.prg' -AutoDismiss -TimeoutSec 300 +powershell -ExecutionPolicy Bypass -File D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1 -Script 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_incarca_vanzare_din_nota.prg' -AutoDismiss -TimeoutSec 240 +``` + +Logurile: `..._log.txt` langa fiecare suita; rezumatul watchdog-ului in +`COMUN\utile\Teste\editare_factura\watchdog_out\`. diff --git a/docs/cercetare/rec_dec42_proiectare.md b/docs/cercetare/rec_dec42_proiectare.md new file mode 100644 index 0000000..308df91 --- /dev/null +++ b/docs/cercetare/rec_dec42_proiectare.md @@ -0,0 +1,312 @@ +# Proiectare — Decizia 42: articole factura vizibile, needitabile dupa trimiterea in eFactura + +Scris 10.08.2026. Agent read-only, doar cercetare/proiectare — nicio modificare de cod facuta +de acest agent. + +## ATENTIE — implementarea era deja IN LUCRU, de catre alt agent; bugul de mai jos e DEJA CORECTAT + +Inainte de a ajunge la proiectare: la momentul cercetarii, `COMUN\clase\omodificari.vc2` avea deja +**modificari necomise in working tree** (`git status` in `COMUN`: `M clase/omodificari.vc2`) care +implementeaza aproape exact Decizia 42 corectata (vizibil, doar needitabil). Agentul `d42-efactura`, +activ in aceeasi sesiune, lucra chiar atunci pe exact acest subiect. + +**Acest raport nu descrie deci o proiectare de la zero, ci: (1) verificarea contractelor si (2) un +review al diff-ului in lucru, cu un bug real gasit si dovedit pe schema Oracle — vezi §1 si §8.** + +**UPDATE, dupa trimiterea raportului**: `d42-efactura` a gasit acelasi bug independent, in timpul +propriei lucrari (nu din briefingul primit de la team-lead), l-a corectat si scris in binar +**inainte** sa primeasca mesajul meu. **Verificat direct pe disc de acest agent** (nu doar preluat +din raportarea lui `d42-efactura`): `git diff` pe `COMUN\clase\omodificari.vc2` arata +`This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0))`, iar `git diff` pe +`COMUN\programe\ofacturare_editare.prg` arata `v.id_fact` adaugat in SELECT-ul din +`IncarcaVanzareNota` si `id_fact I NULL` adaugat in schema `CREATE CURSOR tvanz` din +`CreeazaCursorTvanzGol` — exact varianta B recomandata la §8. **Bugul e inchis, nu mai necesita +actiune.** + +Legat de linia necomisa gasita atunci in `ROAGEST\Programe\roagest.prg` +(`SET PROCEDURE TO ofacturare_editare.prg ADDITIVE`, "Not Committed Yet" la 10.08.2026 11:28): +`d42-efactura` confirma ca nu e a lui — a atins doar `omodificari.vc2` si `ofacturare_editare.prg` +(plus fisiere de test noi). Ramane deci dintr-un alt bloc de lucru (posibil S9), de verificat separat +de team-lead — nu descrie starea lui `d42-efactura`. + +--- + +## 1. Cum se afla ca documentul e in eFactura + +**Functie**: `FUNCTION EsteInEFactura`, globala (nu metoda de clasa), definita in +`COMUN\programe\ofacturare_editare.prg:16-25`: + +``` +*!* parametru: id_fact +*!* verifica daca factura cu id_fact dat a fost deja trimisa in eFactura (anaf_efactura) +FUNCTION EsteInEFactura + LPARAMETERS tnIdFact + LOCAL lcSql, lnEFactura, llSucces + lnEFactura = 0 + lcSql = [select count(*) as lneFactura from anaf_efactura where id_fact = ] + Alltrim(Str(Nvl(m.tnIdFact,0))) + llSucces = goExecutor.oSelecteaza2Value(m.lcSql, @lnEFactura) + RETURN (Nvl(m.lnEFactura,0) > 0) +ENDFUNC +``` + +**Contract, deschis din `RETURN`**: primeste `tnIdFact` — **`ID_FACT`, nu `ID_VANZARE`** — si intoarce +`.T./.F.` (numar de randuri in `ANAF_EFACTURA` cu acel `id_fact` > 0). Foloseste `goExecutor` +(disponibil global, aceeasi conventie ca restul aplicatiei). + +**Disponibilitate cross-project — VERIFICAT, nu presupus**: `ofacturare_editare.prg` e inregistrat +prin `SET PROCEDURE ... ADDITIVE` in toate cele trei aplicatii: + +| App | Fisier | Linie | Stare git | +|---|---|---|---| +| ROAFACTURARE | `Programe\roafacturare.prg` | 214 | comis demult | +| ROACONT | `Programe\roacont.prg` | 212 | comis, `3af0089` (08.08.2026) — mesaj: *"Fara el, pagina de articole ale facturii nu apare in registrul jurnal > modificare"* | +| ROAGEST | `Programe\roagest.prg` | 260 | **necomis**, adaugat azi 10.08.2026 (vezi avertismentul de mai sus) | + +Deci `EsteInEFactura` **este** apelabila din contextul `omodificari.vc2`, inclusiv din ROACONT si +(dupa commit-ul in curs) ROAGEST. Comentariul din `omodificari.vc2:14786-14787` ("apare doar cand +ofacturare_editare.prg e incarcat (doar ROAFACTURARE - ROACONT/ROAGEST nu-l inregistreaza)") e +**invechit** — scris inainte de commit-ul `3af0089`, care a inversat exact aceasta premisa pentru +ROACONT. Nu se corecteaza comentariul in acest raport (read-only), dar oricine implementeaza ar +trebui sa-l actualizeze cand atinge zona. + +**BUG GASIT in diff-ul in lucru — `id_fact` confundat cu `id_vanzare`.** Apelul din +`omodificari.vc2:14798` (diff necomis) e: + +``` +This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare) +``` + +dar `This.nIdVanzare` e populat cu `tvanz.id_vanzare` (linia 14796), adica `VANZARI.ID_VANZARE` — +**alt camp** decat `VANZARI.ID_FACT`, pe care `EsteInEFactura` il asteapta. Dovada, in trei straturi: + +1. **Cod**: precedentul functional existent, `ofacturare_comun.vc2:3739` (`lnIdFact = id_fact`) si + `:3764` (`EsteInEFactura(lnIdFact)`), citeste `id_fact` dintr-un camp separat de `id_vanzare` + (`:3734`, `lnIdVanzare = id_vanzare`, aceeasi cursor `crsfacturi`, doua LOCAL-uri distincte). + Acelasi tipar in `anaf_efactura.prg:3123-3124`: `pnIdFact = id_fact` si `lnIdVanzare = id_vanzare` + citite **pe acelasi rand** din `crsFacturiEmise` — daca ar fi acelasi numar, codul n-ar avea + nevoie de doua variabile. +2. **Schema Oracle** (verificat read-only, `user_tab_columns`, `MARIUSM_AUTO`): `VANZARI` are **ambele** + coloane, `ID_FACT` si `ID_VANZARE`, distincte; `ANAF_EFACTURA` are doar `ID_FACT`. +3. **Date reale** (verificat read-only, join `anaf_efactura.id_fact = vanzari.id_fact`): pentru + documentele deja trimise in eFactura, cele doua valori difera constant — + + | id_fact | id_vanzare | cod | numar_act | data_act | + |---|---|---|---|---| + | 8008013 | 1013 | 1140509 | 510 | 31-AUG-25 | + | 8007922 | 1007 | 1140439 | 503 | 03-JAN-25 | + | 8007836 | 993 | 1140380 | 490 | 15-AUG-24 | + | 8007810 | 991 | 1140369 | 488 | 11-JUL-24 | + +Cu bug-ul curent, `EsteInEFactura(1013)` cauta `id_fact = 1013` in `ANAF_EFACTURA` — care nu exista +cu acel numar — deci **intoarce mereu `.F.`**, chiar si pentru documente reale trimise in eFactura. +Garda ar fi complet inoperanta pe date reale. Corectia: §8. + +Niciun document dintre cele patru de mai sus nu e din luna curenta (limitare de date deja cunoscuta +din bloc — garda de editare cere luna curenta), deci un test headless pe un caz real din productie +de date nu se poate face acum; testul trebuie sa foloseasca un cursor `tact`/`tvanz` mock cu +`id_fact` setat manual (acelasi tipar folosit deja pentru `id_vanzare_set` in S5). + +## 2. Unde se aseaza conditia in omodificari.vc2 + +Locul folosit deja de diff-ul in lucru e corect si e cel recomandat: `frm_modific2024.Show()` +(`omodificari.vc2:14748-14837` in varianta comisa `1c42ae0`; extins la 14748-14856 in diff-ul +necomis), imediat dupa blocul care rezolva `This.nIdVanzare`/`This.nTipVanzare` prin +`IncarcaVanzareDinNota('tact')` si inainte de `This.pgfArticole.PageCount = 3`. E punctul unde +formularul stie deja daca documentul curent are rand in `VANZARI` (`This.lAreArticoleVanzari`) — +conditia eFactura se agata direct pe acest rezultat, fara sa duplice logica de potrivire +cod/nract/serie_act/dataact -> id_vanzare. + +Proprietatea noua, `lArticoleReadOnly` (declarata la nivelul clasei, langa `lAreArticoleVanzari`, +`omodificari.vc2:6827` si `6867` in diff), e citita apoi din `.When`-urile coloanelor editabile ale +gridului `grdArticoleFactura` (mecanismul de editabilitate per rand din S5, §3-4) — deci conditia +de formular **compune** cu mecanismul existent, nu-l inlocuieste. + +## 3. Controale care se dezactiveaza — confirmate pe cod + +| Control | Path | Linii (comis) | Ce face diff-ul | +|---|---|---|---| +| Buton adaugare | `pgfArticole.PAGE3.cmdAdaugaArticol` | Click: `16488-16529` | `.Enabled = !This.lArticoleReadOnly` la afisare (`Show`); plus guard `OR Thisform.lArticoleReadOnly` in `Click` (aparare in adancime, cazul `Enabled` sarit programatic) | +| Buton stergere | `pgfArticole.PAGE3.cmdStergeArticol` | Click: `16531-16540` | idem | +| Cantitate | `grdArticoleFactura.cCantitateArt.Text1.When` | `16549-16554` | adauga `Thisform.lArticoleReadOnly OR` in fata conditiei existente `Nvl(tvd.id_vanzare_set,0)<>0` | +| Pret achizitie | `grdArticoleFactura.cPretAchizitieArt.Text1.When` | `16556-16561` | idem | +| Pret | `grdArticoleFactura.cPretArt.Text1.When` | `16570-16575` | idem | +| Pret cu TVA (checkbox) | `grdArticoleFactura.cPretCuTvaArt._checkbox1.When` | `16587-16589` | `RETURN !Thisform.lArticoleReadOnly AND ...` | +| Discount | `pgfArticole.PAGE3.txtDiscountArt` | — | **neatins in diff** — vezi nota de mai jos | + +**Gol observat**: `txtDiscountArt` (discountul pe factura, cu `ControlSource = tvanz.discount`) nu +are `.When` si nu apare in diff. Daca discountul e considerat parte din "articolele facturii" in +sensul Deciziei 42 (afecteaza totalul liniilor), ramane editabil dupa trimiterea in eFactura — de +clarificat cu Marius sau de inclus explicit. Nu era in lista `cmdAdaugaArticol`/`cmdStergeArticol` +ceruta explicit in brief, deci il semnalez ca gol, nu-l tratez ca bug. + +## 4. Tipar read-only pe grid, folosit deja in proiect + +Nu se inventeaza un tipar nou. Proiectul (din S5) foloseste deja exact acest idiom pentru randurile +needitabile (liniile din seturi, `id_vanzare_set <> 0`): fiecare coloana editabila a gridului are +un handler `.When` care intoarce `.F.` cand conditia de blocare e adevarata — VFP nu lasa controlul +sa intre in editare daca `.When` intoarce `.F.`. Diff-ul in lucru extinde exact aceste `.When`-uri +existente, adaugand `Thisform.lArticoleReadOnly OR` in fata conditiei deja acolo — e continuarea +directa a mecanismului S5, nu un tipar paralel. Confirma cererea din brief: "foloseste tiparul deja +folosit in proiect, nu inventa unul nou" — respectat. + +## 5. Feedback vizual pentru utilizator + +Diff-ul adauga o eticheta noua, `pgfArticole.PAGE3.lblArticoleReadOnly` +(`omodificari.vc2:12857-12871` in diff), cu `Caption = "Articole needitabile - factura trimisa in +eFactura"`, `Visible` legat de `This.lArticoleReadOnly` in `Show()`. E minimul cerut de brief — +niciun tipar de marcaj standard read-only nu exista deja in proiect (nu s-a gasit unul in cautarea +pentru §4), deci eticheta simpla e alegerea rezonabila. Singura observatie: eticheta e pozitionata +`Top = 4, Left = 360` — de verificat pe ecran (fara VFP IDE nu se poate confirma vizual) ca nu se +suprapune cu alt control din PAGE3 la latimile de forma folosite azi. + +## 6. Suprafata de regresie — de ce notele obisnuite din registrul jurnal nu sunt atinse + +Blocul care calculeaza `lArticoleReadOnly` ruleaza **doar** in interiorul conditiei deja existente: + +``` +IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Reccount('tact') > 0 + IncarcaVanzareDinNota('tact') + IF Reccount('tvanz') = 1 + This.lAreArticoleVanzari = .T. + ... + This.lArticoleReadOnly = EsteInEFactura(...) + ENDIF +ENDIF +``` + +`IncarcaVanzareDinNota` cauta in `VANZARI` un rand cu tripletul (cod, nract, serie_act, dataact) al +notei curente. Pentru o nota contabila obisnuita (nu factura de vanzare), acest rand nu exista, +`Reccount('tvanz')` ramane `0`, deci **intreg blocul `IF Reccount('tvanz') = 1` e sarit** — +`This.lAreArticoleVanzari` ramane `.F.` si `This.lArticoleReadOnly` ramane la valoarea implicita +`.F.` (setata explicit chiar inainte de bloc, `:14791`). Consecinta directa: `This.pgfArticole.PageCount += 2` (fara PAGE3), deci `cmdAdaugaArticol`/`cmdStergeArticol`/gridul de articole **nici nu exista** +pentru documentul respectiv — nu doar ca nu sunt dezactivate, ci nu sunt instantiate in fluxul de +afisare. Riscul de regresie pe notele obisnuite din ROAGEST/ROACONT e deci **zero prin constructie**, +mostenit din gating-ul `lAreArticoleVanzari` deja livrat si testat in S4/S5 — Decizia 42 doar adauga +o conditie suplimentara **in interiorul** ramurii deja izolate pentru facturi de vanzare, nu schimba +gating-ul insusi. Acesta e exact argumentul cerut in brief la punctul 6, si e verificabil citind +codul, nu presupus. + +`comun.vc2` (clasa `afisjurcom`, `do_modifica`, `2222-2572`) nu e atins de acest diff — ramane +neschimbat, confirmand ca intreaga logica sta in `omodificari.vc2`, un singur punct de intretinere. + +## 7. Teste minime propuse (headless, in stilul suitei existente) + +Suita `COMUN\utile\Teste\editare_factura\` foloseste harness-ul `ui_harness.prg` + `asserteaza`, cu +formular vizibil (`WindowType = 0`, `Show()`, `DOEVENTS FORCE`), fara input real (nici un +`keybd_event`/`SendInput` — doar citire directa de proprietati si apel direct de metode). Propunere, +dupa modelul `test_ui_sterge_linie.prg`: + +**`test_d42_efactura_readonly.prg`** — cazul pozitiv, cu date mock (nu exista document real din +luna curenta trimis in eFactura, cf. §1): +1. Incarca `tact`/`tvd` pentru un document real din luna curenta cu `lAreArticoleVanzari = .T.` + (ex. acelasi `id_vanzare = 1049` mentionat in handoff intermediar (sters), daca inca in luna + curenta la momentul rularii). +2. Dupa `IncarcaVanzareDinNota`, forteaza `tvanz.id_fact` (sau `tact.id_fact`, dupa care varianta se + alege in §8) la o valoare stiuta ca exista in `ANAF_EFACTURA` (mock: `INSERT` intr-un cursor + local, nu in Oracle) — sau, mai simplu, mock-uieste direct `EsteInEFactura` prin + `SET PROCEDURE`/redefinire temporara, dupa tiparul deja folosit in proiect pentru izolarea de + Oracle in teste UI. +3. `assert`: `loForm.lArticoleReadOnly = .T.` +4. `assert`: `loForm.pgfArticole.PAGE3.cmdAdaugaArticol.Enabled = .F.` +5. `assert`: `loForm.pgfArticole.PAGE3.cmdStergeArticol.Enabled = .F.` +6. `assert`: `loForm.pgfArticole.PAGE3.lblArticoleReadOnly.Visible = .T.` +7. `assert`: grid-ul e in continuare **vizibil** si populat — `loForm.pgfArticole.PageCount = 3`, + `Reccount('tvd') > 0` — confirma partea corectata a deciziei (nu se suprima pagina). +8. `assert`: pozitionare pe `grdArticoleFactura`, `SetFocus` pe coloana `cCantitateArt` nu intra in + editare — verificat prin apelul direct al handlerului `.When` (`loForm.pgfArticole.PAGE3. + grdArticoleFactura.cCantitateArt.Text1.When()` trebuie sa intoarca `.F.`), nu prin tastare. + +**`test_d42_efactura_editabil.prg`** — cazul negativ (control): acelasi document, dar +`EsteInEFactura` mock-uit sa intoarca `.F.` -> toate assert-urile de mai sus inversate (`Enabled = +.T.`, `.When()` nu intoarce `.F.` din cauza flagului — poate intoarce `.F.` din alt motiv, ex. +`id_vanzare_set`, testat separat). + +**`test_d42_nota_obisnuita_neatinsa.prg`** — cazul de regresie cerut la §6: incarca o nota +contabila fara corespondent in `VANZARI` (orice test existent din `COMUN\utile\Teste\` care nu tine +de facturare), verifica `loForm.pgfArticole.PageCount = 2` si ca `lArticoleReadOnly` ramane `.F.` +fara sa fi fost nevoie sa se apeleze `EsteInEFactura` deloc (se poate confirma indirect, verificand +ca nu s-a facut nicio interogare Oracle suplimentara, sau direct daca se mock-uieste functia cu un +contor de apeluri). + +Toate cele trei suite: fara scriere in Oracle, `QUIT` la final, log langa `.prg` cu acelasi nume + +`_log.txt`, conform conventiei din handoff intermediar (sters). + +## 8. Schita de diff — corectia necesara peste diff-ul in lucru + +Doua variante, cu recomandare pentru B. + +**Varianta A — minima**, refoloseste `id_fact` deja prezent pe cursorul `tact` (confirmat: `tact` +vine din view-ul `vact_tot`, care are coloana `id_fact`, folosita deja in `ofacturare_comun.vc2:3776` +ca `Locate For Nvl(id_fact, 0) = lnIdFact` pe acelasi cursor `actactan`/`tact`): + +```diff +--- a/COMUN/clase/omodificari.vc2 ++++ b/COMUN/clase/omodificari.vc2 +@@ frm_modific2024.Show + This.nIdVanzare = tvanz.id_vanzare + This.nTipVanzare = tvanz.tip +- This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare) ++ This.lArticoleReadOnly = EsteInEFactura(Nvl(tact.id_fact, 0)) +``` + +Risc al variantei A: presupune ca recordul curent din `tact` (la momentul `Show()`, imediat dupa +incarcare, deci pe Top) are `id_fact` valid pentru documentul gasit — valabil in cazul normal, dar +nu la fel de robust ca precedentul din `ofacturare_comun.vc2:3775-3783`, care cauta explicit randul +cu `id_fact` potrivit inainte sa cada pe fallback (`Go Top`). + +**Varianta B — recomandata**, aduce `id_fact` chiar pe `tvanz` (randul din `VANZARI` deja gasit prin +tripletul cod/nract/serie_act/dataact), simetric cu `id_vanzare`: + +```diff +--- a/COMUN/programe/ofacturare_editare.prg ++++ b/COMUN/programe/ofacturare_editare.prg +@@ CreeazaCursorTvanzGol +- CREATE CURSOR tvanz (id_vanzare I, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ; ++ CREATE CURSOR tvanz (id_vanzare I, id_fact I NULL, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ; + in_valuta I NULL, id_valuta I NULL, nume_val C(20) NULL) +@@ IncarcaVanzareNota +- lcSql = [select v.id_vanzare, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ; ++ lcSql = [select v.id_vanzare, v.id_fact, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ; + [v.in_valuta, v.id_valuta, nv.nume_val from vanzari v left join nom_valute nv on nv.id_valuta = v.id_valuta where v.cod = ] + Transform(m.tnCod) + ; + +--- a/COMUN/clase/omodificari.vc2 ++++ b/COMUN/clase/omodificari.vc2 +@@ frm_modific2024.Show + This.nIdVanzare = tvanz.id_vanzare + This.nTipVanzare = tvanz.tip +- This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare) ++ This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact, 0)) +``` + +Varianta B e mai robusta pentru ca foloseste `VANZARI.ID_FACT` direct — exact coloana pe care +`ANAF_EFACTURA` o are ca cheie (verificat pe schema Oracle, §1) — fara sa depinda de pozitia +curenta a cursorului `tact`. Costul: doua fisiere in loc de unul (`ofacturare_editare.prg` + +`omodificari.vc2`), plus verificarea ca `select v.id_fact` nu produce eroare Oracle daca vreun rand +vechi din `VANZARI` are `id_fact` `NULL` (coloana e deja `NULL`-abila judecand dupa restul cursorului, +`in_valuta I NULL` etc., deci nu ar trebui sa fie o problema). + +**Neschimbat, corect asa cum e**: restul diff-ului (`.When`-urile pe grid, `cmdAdaugaArticol`/ +`cmdStergeArticol.Enabled`, eticheta `lblArticoleReadOnly`) — vezi §2-5. + +--- + +## Rezumat pentru implementare + +1. Diff-ul lui `d42-efactura` (necomis la momentul acestui raport, in `COMUN\clase\omodificari.vc2` + + `COMUN\programe\ofacturare_editare.prg`) e **structural corect** — locul (§2), controalele + (§3), tiparul de grid (§4) si feedback-ul (§5) respecta cerintele Deciziei 42 corectate. +2. **Bug gasit, dovedit pe schema si date, si INCHIS**: `EsteInEFactura(This.nIdVanzare)` trebuia sa + foloseasca `id_fact`, nu `id_vanzare` — `d42-efactura` l-a gasit independent si l-a corectat cu + exact varianta B din §8 (`EsteInEFactura(Nvl(tvanz.id_fact,0))`), **verificat pe disc de acest + agent** dupa corectie. Nu mai necesita nicio actiune. +3. Gol de clarificat, nu bug: `txtDiscountArt` ramane editabil dupa eFactura — de decis cu Marius + daca discountul intra sub "articole" (§3). Confirmat si de `d42-efactura` ca gol cunoscut, in + afara scope-ului primit de la team-lead. +4. Comentariul din `omodificari.vc2:14786-14787` ("ROACONT/ROAGEST nu-l inregistreaza") e invechit + de la commit-ul `3af0089` — de actualizat cand se atinge zona (§1). +5. Teste propuse in §7, aliniate stilistic cu suita existenta — de confirmat cu `d42-efactura` daca + au fost deja scrise (mesajul lui mentioneaza "doua fisiere noi de test"). +6. `ROAGEST\Programe\roagest.prg` (linia necomisa `SET PROCEDURE TO ofacturare_editare.prg`) **nu** + apartine lui `d42-efactura` — ramane dintr-un alt bloc de lucru, de identificat separat de + team-lead. diff --git a/docs/cercetare/rec_editare_factura.md b/docs/cercetare/rec_editare_factura.md new file mode 100644 index 0000000..09ffad9 --- /dev/null +++ b/docs/cercetare/rec_editare_factura.md @@ -0,0 +1,262 @@ +# Cercetare: editare factura emisa (netrimisa inca in eFactura) + +Sursa: cache text `.??2` (deja la zi, negenerate acum) + `.prg` din working copy +`D:\ROA\ROAFACTURARE`. Cod Oracle (pachete `PACK_FACTURARE`, `PACK_CONTAFIN`, `PACK_DOCUMENTE`) +**NU e in working copy** — doar apelurile RPC din VFP sunt vizibile; corpul PL/SQL trebuie cautat +in schema Oracle, nu exista local. + +## 1. Fluxul de EMITERE factura + +**Punct de intrare (meniu -> procedura generica `factureaza`)**, `COMUN\programe\oproceduri_facturare.prg`: +- `facturare_contracte` (:130-134) -> `factureaza(2/6/52)` — pe baza de **contract** +- `facturare_comenzi` (:140) -> `factureaza(3)` — pe baza de **comanda** +- `facturare_avize` (:145) -> `factureaza(4)` — pe baza de **aviz** +- `emitere_aviz_clienti` / `_debitori` / `_custodie` / `_transfer` (:203-275) -> `factureaza(tip)` cu tip-uri diverse + pentru avize (comanda/lista preturi/contract/lucrare/NIR/retur) +- `copiere_factura` (:152) -> `factureaza(toFactura.Tip, toFactura)` — copiere/modificare-inainte-de-emitere + (nu e "editare dupa emitere", e o factura noua pornita din datele alteia) + +`factureaza` (`COMUN\programe\ofacturare.prg:90`) delegheaza imediat la **`factureaza2`** (acelasi fisier, +`:170`+; bucla mare pana ~`:850`). Pasi, in ordine: + +1. **Formular date antet**: `frm_date_factura` / `frm_date_aviz` / `frm_date_aviz_lucrare` (alegere dupa `tnTip`, + `ofacturare.prg:220-226`) — culege client, data, delegat, ruta, incasare etc. in obiectul `poDate`. +2. **Alocare numar document**: `poGeneratorNumere.verifica_numar(...)` / `.dezaloca_numar(...)` (`:250-260`) — + numerotare seriala pe tip document, alocata/deblocata aici. +3. **Populare cursor articole**, ramificat dupa `tnTip` (vezi punctul 6 mai jos) — cheama diverse + `pack_facturare.cursor_*` (preturi/contract/comanda/avize/gestiune/lucrare/retur/aviz_nir) — `:266-308`. +4. **Formular articole**: `frm_facturare_articole` / `frm_facturare_articole2` / `frm_avizare_lucrare` + (`COMUN\clase\ofacturare.vc2`) — user editeaza cantitati/preturi/explicatii inainte de scriere efectiva. +5. **Scriere efectiva** — metoda `do_scrie_factura` a formularului de articole + (`COMUN\clase\ofacturare.vc2:14067-14360`, dublata identic in `frm_facturare_articole2` la `:18090-18370`): + - **`pack_facturare.scrie_proforma(...)`** (:14071) — doar daca `poDate.eProforma=1`: scrie **doar in + VANZARI**, explicit comentat in cod "*nu si in contabilitate*" (:14070). + - **`pack_facturare.scrie_factura_avize(...)`** (:14103) — cand `poDate.Tip = 4` (facturare din aviz). + - **`pack_facturare.scrie_factura2(...)`** (:14130, :14158) — comenzi (tip 3/21/25/28/42/47) sau alte tipuri + ("otherwise": lista de preturi etc.). + - Toate aceste RPC-uri scriu in **VANZARI** si intorc un **cursor de verificare** (`lcCursorVerificare`) + cu liniile de nota contabila propuse: coloane `id_act, ascd, ascc (asociat cont debit/credit), id_partd, + id_partc, dataactt, datairegt, datascadt, suma` (:14184-14206). + - Daca exista randuri in cursorul de verificare (adica nu e proforma), se afiseaza **`Do Form verificare`** + (:14189) — utilizatorul poate corecta clientul (`id_partd/id_partc`) sau explicatiile (`ascd/ascc`) inainte + de a confirma nota. + - **`pack_facturare.finalizeaza_scriere_verificare(...)`** (:14247, si varianta `scrie_factura_avize_retur` + la :14219/:14282 pentru retur-uri) — **aici se scriu efectiv notele contabile si rulajele** in Oracle, + folosind eventualele corectii din formularul de verificare (`pcSirDifAcont`, `pcSirDifPart`). +6. **Alte efecte laterale**: + - Incasare/bon fiscal: `poGeneratorNumere.verifica_numar(16, poDate.nr_incasare)` (:503), `listeaza_bon_fiscal(...)` + (:528) — chitanta/incasare asociata facturii, daca `poDate.incasat <> 0`. + - Listare: `listeaza_ofacturare()` (:535). + - Stoc: pentru facturare directa din stoc exista un flux paralel, `oscrie_vanzare_din_stoc` + (`COMUN\programe\ofacturare_stoc.prg:318-412`) — apeleaza `pack_facturare.initializeaza_date_factura`, + `adauga_articol_factura_stoc`, `scrie_in_vanzari`, si la final + `update vanzari set id_fact = pack_contafin.get_idFact() where id_vanzare = ?` (:412) — leaga VANZARI + de identificatorul notei contabile (`id_fact`). + +## 2. Fluxul de EDITARE factura existenta + +Formular: `frm_facturi` (lista facturi emise), clasa `COMUN\clase\ofacturare_comun.vc2`. + +- **`frm_facturi.do_modifica`** — `ofacturare_comun.vc2:4382-4482`. + - Verifica intai daca factura e deja in `anaf_efactura` (vezi punct 5) si daca `sters=0`; altfel blocheaza + editarea (:4476-4478, mesaj "Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in + eFactura!"). + - Deschide `frm_modifica_factura` (:4433) si la confirmare (`gnButon=1`) executa + **`pack_facturare.modifica_date_factura(...)`** (:4443-4460) cu parametrii: + `id_vanzare, id_ruta, id_delegat, id_agent, id_masina, dataora_exp, id_facturare, listare_detaliata, + text_aditional, tip_saft, efactura, data_act, data_scad, numar_act, serie_act`. +- **Formular `frm_modifica_factura`** (`ofacturare_comun.vc2:5096-5608`) — campurile editabile efectiv expuse in + UI sunt: ruta, delegat, agent, masina, data/ora expeditie, tip facturare (`id_facturare`), + listare detaliata, text aditional, tip SAFT, si (conditionat de `numar_act` nenul) data act/scadenta/numar + act/serie act — vezi `Init` (:5570-5584) si handlerele `chkDataAct/chkNrAct/chkSerieAct` (:5592-5606). + **NU exista pe formular campuri pentru client, data facturii, seria/numarul facturii sau articole/preturi** — + acestea nu pot fi schimbate prin acest flux de editare. +- **Confirmat: editarea NU atinge notele contabile si rulajele.** `modifica_date_factura` primeste doar + campurile enumerate mai sus (metadate document/logistica), nu recalculeaza sume, nu scrie in tabelele de + note (vezi punct 3) si nu apeleaza vreun echivalent `PACK_CONTAFIN`. Nicio referinta la `pack_contafin` in + `do_modifica`. +- Editarea explicatiei unui articol de pe factura: **`frm_modifica_articol_factura`** + (`ofacturare_comun.vc2:5023-5095`, apelat din `frm_facturi.do_modifica_explicatie` :4484-4501) — + apeleaza doar **`pack_facturare.modifica_explicatie_articol(id_vanzare_det, ...)`** (:5067) — schimba + **doar textul explicatiei**, nu cantitatea/pretul/cont-ul. Nici aici nu se ating notele contabile. + +## 3. Tabelele de note contabile si rulaje + +Nu exista schema DBC/DDL in working copy (tabelele sunt Oracle, VFP le vede via view-uri `gcs.v...`). Din +codul de stergere (`ofacturare_comun.vc2:4573-4614`) rezulta 3 seturi de date, toate filtrate pe +`an, luna, cod` (cod = `vanzari.cod`, identificatorul facturii): + +| View interogat (VFP) | Cursor local | Camp cheie randuri | Rol | +|---|---|---|---| +| `gcs.vact_tot` | `actactan` / cursor `v_act` | `id_act` (+ `id_set`, `id_fact`, `id_factd`, `id_factc`) | Note contabile (randuri debit/credit) | +| `gcs.vrul_tot` | `rul_temp` / cursor `v_rul` | `id_rul` | Rulaje | +| `gcs.vrul_obinv_tot` | `rul_temp_obinv` / cursor `v_rul_obinv` | `id_rul_obinv` | Rulaje obiecte de inventar | + +**Cheia de legatura factura -> nota**: `VANZARI.id_fact` — populat la emitere prin +`pack_contafin.get_idFact()` (`COMUN\programe\ofacturare_stoc.prg:412`) sau intors ca parametru OUT din +`scrie_factura2`/`finalizeaza_scriere_verificare` (`?@poDate.nid_vanzare` e id_vanzare, nu id_fact — id_fact +pare populat separat de pachetul Oracle). Pe partea de nota, actul (`ACT`) are coloanele `id_fact`/`id_factd`/ +`id_factc` folosite de `pack_documente.ReferinteDocumenteNota`/`ReferinteDocument` +(`COMUN\programe\odocumente.prg:8-46`) pentru a detecta referinte incrucisate intre documente (facturi ce se +storneaza/compenseaza reciproc). + +**Camp de provenienta pentru regenerare**: nu exista un camp explicit gen `sursa_id_vanzare` pe ACT/RUL vizibil +in cod VFP; legatura se face prin **`cod` (numarul facturii) + `an` + `luna`** — vezi filtrul identic in +`do_sterge` (:4573,4587,4599). Asta e mecanismul deja folosit pentru "sterge tot ce apartine facturii X": +select pe `vact_tot`/`vrul_tot`/`vrul_obinv_tot where cod = and an=... and luna=...`, apoi +sterge. **E si "reteta" pentru un eventual regenereaza = sterge + refa**, cu observatia ca id-urile +(`id_act`, `id_set`, `id_fact`, `id_factd`) trebuie citite inainte de stergere daca se doreste refacere +identica (linia 4642-4647 le extrage din `actactan` inainte de a apela stergerea). + +## 4. Operatie existenta de STORNARE / STERGERE factura (cheie pentru "regenereaza") + +Da — **`frm_facturi.do_sterge`** (`COMUN\clase\ofacturare_comun.vc2:4503-4719`) e exact mecanismul +"sterge nota + rulaje asociate unei facturi": + +- Blocheaza daca luna e inchisa (`glLunaInchisa`, :4518-4520) sau daca factura e deja stearsa (:4531-4534) sau + daca nu e din luna curenta (:4543-4546). +- **Cale proforma**: `pack_facturare.sterge_proforma(?pnIdVanzare, ?gnIdUtil)` (:4550) — nu are note, deci + stergere simpla din VANZARI. +- **Cale factura/aviz reala**: + 1. Verifica blocaj de referinte: `ReferinteDocumenteNota(pnAn, pnLuna, lnCod)` (:4565, + `COMUN\programe\odocumente.prg:8`, cheama `pack_documente.ReferinteDocumenteNota`) — daca documentul + e referentiat de incasari/plati, blocheaza stergerea (:4567 mesaj "Documentul are referinte..."). + 2. Interogheaza `vact_tot`/`vrul_tot`/`vrul_obinv_tot` (vezi punct 3) ca sa afle daca exista note/rulaje + de sters (`llRul`). + 3. Daca exista randuri in `actactan`, deschide formularul **`verificare`** (:4620-4624) pentru + confirmare vizuala a ce se sterge, apoi porneste tranzactie explicita (`SQLSetprop(...,"Transactions",2)`, + :4625) si: + - fie ruleaza `OSCRIE_IN_FISIERE(2,.F.,llRul)` + **`pack_contafin.finalizeaza_stergere_nota(luna, an, + NULL, id_set, cod, id_fact, id_factd, id_util)`** (:4642-4653) — varianta cu rulaje/fisiere multiple, + - fie apeleaza direct **`pack_facturare.sterge_factura(?pnIdVanzare, ?gnLuna, ?gnAn, ?gnIdUtil)`** + (:4689) daca nu exista note/rulaje de tratat special. + 4. Commit/rollback explicit, apoi revine pe tranzactie automata. + +Asta confirma ca infrastructura "sterge + reface" exista deja pe partea de stergere completa +(`sterge_factura` + `finalizeaza_stergere_nota`); un flux de "regenerare la editare" ar putea reutiliza aceste +doua RPC-uri Oracle inainte de a re-emite (echivalentul pasilor de la punctul 1, pasul 5) — dar acest +lant nu exista azi cablat din formularul de editare (`do_modifica`). + +## 5. `anaf_efactura.id_fact` — verificare "a fost deja trimisa in eFactura" + +- **Citire/test**: `COMUN\clase\ofacturare_comun.vc2:4428-4432`, in `frm_facturi.do_modifica`: + ``` + lcSql = [select count(*) as lneFactura from anaf_efactura where id_fact = ] + ALLTRIM(STR(poRec.id_fact)) + llSucces = goExecutor.oSelecteaza2Value(m.lcSql, @pneFactura) + If poRec.sters = 0 AND NVL(m.pnEFactura,0) = 0 + ... permite editarea ... + Else + amessagebox("Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in eFactura!", 48, "Atentie") + ``` + (`pnEFactura`/`pneFactura` sunt aceeasi variabila — VFP e case-insensitive.) +- Expresia de test: **exista cel putin un rand in `ANAF_EFACTURA` cu `id_fact = VANZARI.id_fact` a facturii + curente** => considerata "trimisa" => editare blocata. +- **Scriere**: `VANZARI.id_fact` insusi e populat la emitere (`pack_contafin.get_idFact()`, + `COMUN\programe\ofacturare_stoc.prg:412`, echivalent probabil si in `scrie_factura2`/ + `finalizeaza_scriere_verificare` pe partea Oracle, nevizibil in VFP). `ANAF_EFACTURA.id_fact` e populat: + - la import de factura de achizitie (nu de emitere): `UpdateEFacturaIdFact` + (`COMUN\programe\import_efactura.prg:229`) — `update anaf_efactura set id_fact = ?pnIdFact where id = ?pnId + and NVL(id_fact,0)=0`. + - manual din formular: `frm_import_efactura.importmodifica` (`anaf_efactura.vc2:12930`) — + `UPDATE crsFacturi SET id_fact = m.pnIdFact WHERE id = m.lnIdEfactura`. + IPOTEZA: pentru facturile **emise** (nu achizitie), popularea `anaf_efactura.id_fact` la trimitere se face + probabil in clasa server `AnafeFacturaServer`/`ANAFeFactura` (`COMUN\programe\anaf_efactura.prg`, ex. + `UpdateDbFactura` :2018-2155) sau in Oracle la generarea XML — nu am gasit un UPDATE explicit VFP care + leaga `id_fact` la momentul trimiterii unei facturi emise; ar trebui verificat separat (posibil in PL/SQL). + +## 6. Cele 4 tipuri de sursa — ramificare si diferente + +Ramificarea e pe **`VANZARI.tip`** (camp numeric), vazuta in doua locuri, cu acelasi grupaj: + +`ofacturare.prg:266-308` (populare cursor articole la pornirea emiterii) si +`ofacturare.vc2:14067-14174` (`do_scrie_factura`, alegerea RPC-ului de scriere): + +| Sursa | `tip` (exemple) | Cursor populare articole | RPC scriere | +|---|---|---|---| +| **Lista de preturi** | 1,5,7,10,22,23,29,45 (si 48/49 varianta `cursor_articole_k`) | `pack_facturare.cursor_preturi(...)` | `scrie_factura2` (ramura "otherwise") | +| **Contract** | 2,6,26,52 | `pack_facturare.cursor_contract(...)` | `scrie_factura2` (ramura "otherwise") | +| **Comanda** | 3,21,25,28,42,47 | `pack_facturare.cursor_comanda(...)` | `scrie_factura2` (ramura explicita `Inlist(poDate.Tip,3,21,25,28,42,47)`, cu intrebare "Doriti sa se inchida comanda?" :14122) | +| **Aviz** | 4 | `pack_facturare.cursor_avize(...)` | `scrie_factura_avize` (ramura `poDate.Tip = 4`, cu intrebare "Doriti sa se inregistreze si avizul de retur?" :14091) | +| (alte, mentionate de completitudine) | 27 (lucrare), 30 (aviz din NIR), 41 (transfer gestiune), 8/9/24 (retur) | `cursor_lucrare`/`cursor_aviz_nir`/`cursor_gestiune`/`cursor_retur` | variante `scrie_factura_avize_retur`/`scrie_proforma` | + +**Ce difera intre ele ca note contabile/rulaje**: apelul final e mereu +`pack_facturare.finalizeaza_scriere_verificare(...)` (sau `scrie_factura_avize_retur` pentru retururi) — +adica *acelasi* punct final scrie notele, insa RPC-ul de scriere in VANZARI difera (`scrie_factura_avize` vs +`scrie_factura2`), iar la comanda/aviz apar intrebari suplimentare de inchidere document sursa (comanda +inchisa / aviz retur generat) care produc efecte laterale suplimentare pe langa nota standard. Diferentele +fine intre `scrie_factura2` si `scrie_factura_avize` (ce conturi/rulaje difera intern) sunt in PL/SQL, deci +**nu sunt vizibile in working copy**. + +## 7. Blocaje deja existente la editare + +- **Sters**: `poRec.sters = 0` obligatoriu (`ofacturare_comun.vc2:4432`). +- **Trimis in eFactura**: `anaf_efactura` are rand cu acelasi `id_fact` (punct 5), acelasi test la + `:4428-4432`. +- **Luna inchisa** — nu e verificat in `do_modifica` (doar in `do_sterge`, :4518-4520, `If glLunaInchisa Return`). + Deci editarea (schimbare ruta/delegat/etc.) **nu e blocata de luna inchisa**, doar stergerea. IPOTEZA/ + observatie pentru discutie: daca se adauga regenerare de note la editare, ar trebui adaugat si acest test. +- **Nu e din luna curenta** — verificat doar la stergere (:4543-4546: `(pnAn*12)+pnLuna <> (gnAn*12)+gnLuna`), + nu la editare. +- **Referinte incasari/plati** — verificat doar la stergere (`ReferinteDocumenteNota`, :4565-4569), nu la + editare. +- **Are chitanta/incasare** — nu am gasit un blocaj explicit *la editare*; la stergere, verificarea de mai + sus (`ReferinteDocumenteNota`) acopera implicit si incasarile asociate (comentariul din + `odocumente.prg:5-6` mentioneaza "id_fact ... pe id_factd sau id_factc"). + +## 8. Numerotare si documente legate — ce se poate schimba la editare + +- **Client**: NU se poate schimba din `frm_modifica_factura` (formularul nu are control pentru client/partener; + vezi punct 2). Singurul loc unde apare o restrictie explicita despre client e in fluxul de **verificare la + emitere** (nu la editare): `ofacturare.vc2:14198-14201` — daca diferenta de partener ar afecta un cont "41*", + `amessagebox("Nu puteti modifica clientul!",48,"Atentie") / Return .F.` — asta e in timpul emiterii + (formularul `verificare`), nu in editarea ulterioara. +- **Data facturii / seria / numarul facturii**: NU sunt pe formularul de editare. Campurile editabile + `data_act`/`numar_act`/`serie_act` (:4444-4458) sunt pentru "act" (document justificativ atasat, cu + checkbox-uri de activare `chkDataAct/chkNrAct/chkSerieAct`), NU seria/numarul facturii insesi — acelea + se aloca ireversibil la emitere prin `poGeneratorNumere` (punct 1, pasul 2). +- **Articole**: nu se pot modifica cantitati/preturi din fluxul de editare a facturii — doar explicatia unui + articol (`frm_modifica_articol_factura`, punct 2). +- **Aviz consumat / comanda / contract — flag "facturat"**: Exista camp **`facturat`** pe `COMENZI` + (folosit in UI: `ct_comenzi.actualizeaza_grid1` colora randurile cu `facturat=1`, + `COMUN\clase\ocomenzi.vc2:1144`; verificari `loRec.facturat=1` in `do_modifica`/`do_sterge` ale comenzii, + :1806, :2065 — blocheaza modificarea/stergerea comenzii deja facturate; filtru rapid `facturat=1`/`facturat=0` + in criterii, :2178). Marcarea `facturat=1` se face insa pe partea Oracle (`pack_facturare.scrie_factura2` + etc.), nu am gasit un `UPDATE comenzi SET facturat` direct in VFP — deci **nu e vizibil in working copy** + exact unde se scrie flagul, doar ca e citit/verificat pe partea VFP. Nu am gasit camp echivalent `facturat` + pe AVIZE sau CONTRACTE in codul cercetat (posibil alt nume de camp sau tot pe partea Oracle) — + IPOTEZA/necesita cautare suplimentara daca devine relevant. + +--- + +## Rezumat concluzii cheie + +1. **Emiterea** (`COMUN\programe\ofacturare.prg:90` -> `factureaza2`) scrie in VANZARI via + `pack_facturare.scrie_proforma/scrie_factura_avize/scrie_factura2`, apoi (daca nu e proforma) afiseaza + formularul `verificare` cu liniile de nota propuse si finalizeaza notele/rulajele prin + **`pack_facturare.finalizeaza_scriere_verificare`** (`COMUN\clase\ofacturare.vc2:14247`, variante retur la + :14219/:14282). Ramificarea pe cele 4 surse (lista preturi/contract/comanda/aviz) e pe `VANZARI.tip`, + vezi tabel la punctul 6. +2. **Editarea existenta** (`frm_facturi.do_modifica`, `COMUN\clase\ofacturare_comun.vc2:4382-4482`, via + `pack_facturare.modifica_date_factura`) atinge DOAR metadate logistice (ruta/delegat/agent/masina/ + dataora_exp/tip_facturare/listare_detaliata/text_aditional/tip_saft/data-numar act) — **confirmat: nu + atinge notele contabile/rulajele si nu recalculeaza sume**. Nu se pot schimba client, data/serie/numar + factura sau articole. +3. Notele contabile si rulajele stau in view-urile Oracle `vact_tot`/`vrul_tot`/`vrul_obinv_tot` + (chei `id_act`/`id_rul`/`id_rul_obinv`), legate de factura prin **`cod`+`an`+`luna`** (identic filtru + folosit si la stergere) si prin `VANZARI.id_fact` (populat de `pack_contafin.get_idFact()`). +4. **Exista deja** un flux complet de stergere-cu-note: `frm_facturi.do_sterge` + (`ofacturare_comun.vc2:4503-4719`) — foloseste `pack_facturare.sterge_factura` + + `pack_contafin.finalizeaza_stergere_nota`, cu verificare de referinte (`ReferinteDocumenteNota`) si + confirmare vizuala (formularul `verificare`). **Aceasta e infrastructura direct reutilizabila pentru un + "regenereaza = sterge + refa"** al notelor/rulajelor la editare. +5. **`anaf_efactura.id_fact`**: testul de blocare editare e `select count(*) from anaf_efactura where + id_fact = ` (`ofacturare_comun.vc2:4428-4432`) — daca exista rand, factura e + considerata trimisa si editarea e refuzata. Punctul exact unde se scrie `anaf_efactura.id_fact` la + trimiterea unei facturi **emise** nu e vizibil in VFP (ipoteza: pe partea Oracle/XML) — cel gasit in VFP e + doar pentru facturi de achizitie importate. +6. **Blocaje actuale**: editarea verifica doar `sters=0` si `netrimisa in eFactura`; NU verifica luna + inchisa, NU verifica "luna curenta", NU verifica referinte incasari/plati — aceste 3 verificari exista + doar pe fluxul de **stergere**, nu si pe cel de **editare** (`do_sterge` vs `do_modifica`, + `ofacturare_comun.vc2:4503` vs `:4382`). +7. Cod PL/SQL Oracle (`PACK_FACTURARE`, `PACK_CONTAFIN`, `PACK_DOCUMENTE`) nu e in working copy — doar + semnaturile RPC apelate din VFP sunt vizibile local. diff --git a/docs/cercetare/rec_s4_runda1.md b/docs/cercetare/rec_s4_runda1.md new file mode 100644 index 0000000..3f73d1a --- /dev/null +++ b/docs/cercetare/rec_s4_runda1.md @@ -0,0 +1,85 @@ +# S4 runda 1 (PAGE3, doar afisare) — raport de inchidere, 08.08.2026 + +Runda 1 din S4 (`plan_06_s4_proiectare.md`) e **inchisa**. Diff-ul de review: +diff aplicat (sters). Istoricul complet al sesiunii de implementare/depanare: +`docs\cercetare\handoff_s4_runda1.md`, handoff intermediar (sters), +handoff intermediar (sters). + +## Ce s-a implementat + +- **`COMUN\programe\ofacturare_editare.prg`**, doua functii noi la coada fisierului: + - `IncarcaVanzareNota(tnCod, tnNract, tcSerieAct, tdDataAct)` — gaseste randul din `VANZARI` + corespunzator notei curente (filtru compus `cod+nract+serie_act+data_act`, necesar pentru ca + `VANZARI.COD` nu e unic — vezi `progres.md`), cursor `tvanz`. + - `IncarcaArticoleFactura(tnIdVanzare)` — liniile active (`sters=0`) din `VANZARI_DETALII`, cu + denumire articol/gestiune/valuta pentru afisare, cursor `tvd`. +- **`COMUN\clase\omodificari.vc2`** (`frm_modific2024`): + - `pgfArticole.PageCount = 3`, `PAGE3.Caption = "Articole factura"`. + - Grid nou `pgfArticole.PAGE3.grdArticoleFactura` — 13 coloane, `RecordSource="tvd"`, strict + **readonly** (grid + fiecare `Text1`). + - Proprietati noi pe clasa: `lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare`. + - `Load()`: placeholder `CREATE CURSOR tvd (...)` — evita dialogul nativ "Open" pe `RecordSource` + inexistent la constructia grid-ului. + - `Show()`: bloc nou care cheama `IncarcaVanzareNota`/`IncarcaArticoleFactura` si comuta + `pgfArticole.PageCount` intre 2 si 3 dupa `lAreArticoleVanzari`. + +Write-back facut, text si binar sincronizate, fidelity-check OK. + +## Ce s-a testat, si cu ce rezultat + +Suita `COMUN\utile\Teste\editare_factura\test_page3_articole.prg`, headless sub +`COMUN\utile\Teste\watchdog_vfp.ps1 -AutoDismiss`, conexiune reala `MARIUSM_AUTO`. Rulare finala +(dupa curatarea instrumentatiei de depanare): **exit code 0, 0 dialoguri, toate cazurile PASS** +(`test_page3_articole_log.txt`): + +| Caz | Verifica | Rezultat | +|---|---|---| +| `cod=1140888` (factura, tip=1) | `IncarcaVanzareNota`/`IncarcaArticoleFactura` direct: `id_vanzare=1050`, `tip=1`, 4 linii | PASS | +| `cod=1140885` (tip=-12, cu rand VANZARI) | idem: `id_vanzare=1047`, `tip=-12` (detectia merge pe orice tip, nu doar facturi) | PASS | +| `cod=1125486` (nota fara rand VANZARI) | 0 randuri gasite (caz negativ) | PASS | +| `cod=1139934` (4 randuri VANZARI cu acelasi cod) | filtrul compus gaseste exact `id_vanzare=882` pe `nract=375/SSS`, 0 randuri pe `nract=13` (fara corespondent) | PASS | +| `cod=1140888`, `frm_modific2024` instantiat (`Createobject`+`Show`, ca in `do_editare_factura`) | `PageCount=3`, `lAreArticoleVanzari=.T.`, `nIdVanzare=1050` | PASS | +| `cod=1125486`, idem | `PageCount=2`, `lAreArticoleVanzari=.F.` | PASS | + +Testul nu atinge Oracle in scriere — doar `SELECT`-uri prin `goExecutor`. + +## Ce NU e acoperit (ramane pentru runde ulterioare) + +- **Editarea in memorie a liniilor din grid** — runda 2 din S4; grid-ul e strict readonly in + aceasta runda, prin design (B.2 din plan, marcaje `_modificat`/`sters`/`id_vanzare_det=0`, nu e + inceput). +- **Inchiderea prin `do_renunt()`** — netestata, la fel ca in rundele S1-S3 (mediul minimal de + test are `ControlCount=0` pe formularul principal `frm_facturi`, deci butoanele native nu sunt + disponibile pentru a conduce fluxul complet prin UI). +- **Fluxul UI complet prin "Terminat"** — netestat; `verifica_pagecount_form` instantiaza + `frm_modific2024` direct (`Createobject`+`Show`), la fel ca `do_editare_factura`, dar nu conduce + formularul pana la inchidere. Validarea din `inainte_de_do_termin` nu e exercitata de aceasta + suita. +- Garda **referinte** pe `do_editare_factura` ramane netestata izolat (fara candidat pozitiv in + datele de test) — mostenit din rundele anterioare, nu specific rundei 1 din S4. + +## Blocaje rezolvate in aceasta sesiune (istoric, nu de reluat) + +Trei blocaje separate au impiedicat verificarea live inainte de aceasta runda de inchidere, toate +documentate pe larg in handoff intermediar (sters) si `COMUN\docs\testare-ui-vfp.md`: + +1. `DO ... WITH gnAn, gnLuna` pasa prin referinta si umbrea variabilele in procedurile apelate, + provocand dialogul nativ "View Parameter" pe un `?gnAn` din SQL passthrough — remediat cu + pasare prin valoare (`DO ... WITH (gnAn), (gnLuna)`). +2. Harnessul de test (`test_init_env_auto.prg`) incarca implicit clasa `omodificari` din copia de + lucru **ROACONT**, nu din fisierul editat — remediat cu `RELEASE CLASSLIB` + `SET CLASSLIB TO` + pe calea completa a fisierului sub test. +3. Proprietatile custom noi (`lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare`) lipseau din + `*` (intrarile `*p:`) — regula era documentata doar pentru metode + (`*m:`); aplicata acum si aici, in `COMUN\docs\flux-editare-vfp-text.md`. + +## Curatare facuta la inchidere + +- Scoase liniile de log `[BISECT]` si `[DIAG]` (`ClassLibrary`/`PEMSTATUS`) din + `test_page3_articole.prg` — erau instrumentatie de depanare, fara rol in suita finala. Corectiile + de fond (parantezele pe `(gnAn)`/`(gnLuna)`, blocul `RELEASE CLASSLIB`/`SET CLASSLIB TO`) au + ramas, cu comentariile lor. +- Sters `test_baseline_isolation.prg` (+ `.FXP` + log) — era temporar prin design; concluzia lui + ("binarul original nu are blocajul") s-a dovedit nula, testa copia ROACONT a clasei, nu fisierul + editat (vezi capcana #2 de mai sus). +- Suita rulata din nou dupa curatare — PASS pe toate cazurile (tabelul de mai sus). diff --git a/docs/cercetare/rec_s5_oracle_vanzari.md b/docs/cercetare/rec_s5_oracle_vanzari.md new file mode 100644 index 0000000..75b6249 --- /dev/null +++ b/docs/cercetare/rec_s5_oracle_vanzari.md @@ -0,0 +1,231 @@ +# Cercetare Oracle S5/S6/S7 (plan #6 - editare factura emisa) + +Sursa: export proaspat din `MARIUSM_AUTO@ROA_CENTRAL` (`all_source`), facut in aceasta sesiune +(08.08.2026), NU copia veche de pe disc din martie. Inainte de export s-a verificat tabela +`VERSIUNE`: ultimul script aplicat e `ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`, identic cu +`versiune_db.txt` din radacina proiectului (`2026_08_06_12`) - MARIUSM_AUTO e la zi. + +Numerele de linie de mai jos sunt din exportul acestei sesiuni (`PACK_FACTURARE.pck` = 17010 +linii, `PACK_CONTAFIN.pck` = 9041 linii, `PACK_DOCUMENTE.pck` = 96 linii), NU din +`ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql` de pe disc, care e vechi si nu contine modificarile de +la #8. Fisierele exportate au ramas in scratchpad-ul sesiunii, nu sunt commise. + +## A. Starea reala de azi + +### A.1 `actualizeaza_vanzari` (PACK_FACTURARE.pck:16015-16025) + +Confirmat: face EXCLUSIV realinierea `cod` + `STERS=0`, nimic altceva. + +``` +UPDATE VANZARI_DETALII SET STERS = 0 + WHERE ID_VANZARE IN (SELECT ID_VANZARE FROM VANZARI WHERE COD = V_COD_VECHI); +UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI; +``` + +Important, nu era explicit in plan: al doilea `UPDATE` schimba `COD` pe ACELASI rand din `VANZARI` +(filtrat dupa vechiul cod) - `ID_VANZARE` (cheia primara) NU se schimba niciodata la editare. Asta +conteaza direct pentru sectiunea C. Comentariul inline de la `:16018` ("de modificat in caz ca il +las sa stearga manual inregistrari din VANZARI_DETALII") confirma ca extinderea era anticipata. + +### A.2 `PACK_CONTAFIN.finalizeaza_modificare_nota` / `finalizeaza_stergere_nota` + +`finalizeaza_modificare_nota` (PACK_CONTAFIN.pck:8601-8651): cheama +`pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou)` DOAR daca +`SELECT COUNT(*) FROM vanzari WHERE cod = tnCod` > 0 (`:8613-8617`). Daca `cod`-ul vechi nu exista +in `vanzari` (nota nu vine dintr-o factura), pasul e sarit tacut, fara eroare - comportament corect +pentru orice alt tip de nota (ROAGEST/ROACONT). + +`finalizeaza_stergere_nota` (`:8653-8709`) e simetric: cheama `sterge_din_vanzari` doar daca +`cod`-ul exista in vanzari. Spre deosebire de `actualizeaza_vanzari`, +`sterge_din_vanzari` (PACK_FACTURARE.pck:16027-16042) cauta `ID_VANZARE` dupa `cod` si, daca nu-l +gaseste, ridica `RAISE_APPLICATION_ERROR(-20000, ... FACT-017 ...)` - dar acest caz nu poate aparea +in fluxul normal, fiindca apelul e deja gardat de count-ul de mai sus. + +### A.3-A.4 `scrie_in_vanzari` (PACK_FACTURARE.pck:13491-13956) - formula de calcul + +Extrasa integral (bloc `SELECT INTO` la `:13763-13931`, `UPDATE VANZARI` la `:13933-13949`). +Coloane denormalizate scrise: `discount_tva, valoare_achizitie, total_fara_tva, total_tva, +total_cu_tva, valval, tvaval, totval, id_valuta, curs, multiplicator, serie_incasat, nr_incasat, +suma_incasat, tip_incasat` (14 coloane). + +Sursa datelor: **`VANZARI_DETALII_TEMP`** (tabela globala temporara de sesiune, NU +`VANZARI_DETALII`) + `VANZARI_SETURI_TEMP` (pentru linii-set) + `VANZARI_CURSURI` (deja scrisa cu +`id_vanzare`-ul curent, la `:13704`, prin `scrie_cursuri`). `V_DISCOUNT_FACTURA` e parametru +explicit al procedurii (discountul de pe document), nu o coloana citita din `VANZARI`. + +Formula per-linie e deja extrasa in doua FUNCTII REUTILIZABILE, pure, cu parametri expliciti - +NU inline: `calculeaza_total_fara_tva_fact` / `calculeaza_total_tva_fact` +(PACK_FACTURARE.pck:15858-16013). Ambele ramifica explicit pe `V_PRET_CU_TVA` (`:15871`, `:15976`), +apeland `calculeaza_total_cu_tva_fact` cand flagul e 1. Deci **partea de calcul per linie e deja +partajabila** - nu trebuie reinventata. + +Ce NU e extras e blocul de AGREGARE (suma pe toate liniile documentului, tratarea liniilor din +seturi, alegerea `curs`/`multiplicator`/`id_valuta` din `vanzari_cursuri`), care e inline in +`scrie_in_vanzari` si depinde de: +- `VANZARI_DETALII_TEMP` ca sursa (nu `VANZARI_DETALII`); +- stare de sesiune pe pachet: `pack_facturare.nin_valuta`, `ndiscount_evidentiat`, + `cserie_act_incasare` / `nnumar_act_incasare` / `nsuma_incasare` / `ntip_doc_incasare` (acestea + din urma populate DOAR la emiterea unei facturi-cu-incasare combinata - nu au sens la o editare + ulterioara, vezi C); +- `pack_def.GetIdMonedaNationala()`. + +**Cine mai apeleaza `scrie_in_vanzari`**: doar 2 locuri, ambele INSERT-then-populate cu +`RETURNING ID_VANZARE`: `scrie_proforma` (`:5643`) si `finalizeaza_factura` (`:14790`). Extragerea +blocului de agregare intr-o functie interna parametrizata pe sursa (TEMP la emitere, +`VANZARI_DETALII` real la editare) e SIGURA fata de ambii apelanti, daca varianta "sursa=TEMP" +pastreaza exact comportamentul actual. + +**Risc concret de reutilizare naiva pentru S5**: coloanele `serie_incasat/nr_incasat/ +suma_incasat/tip_incasat` NU trebuie recalculate la editare - vin din stare de sesiune specifica +emiterii unei facturi-cu-incasare, fara nicio sursa persistenta din care sa fie reconstruite la o +editare ulterioara. O procedura noua care ar copia tot `UPDATE`-ul din `scrie_in_vanzari` le-ar +suprascrie cu `NULL` la fiecare editare, rupand legatura cu incasarea. Procedura propusa pentru S5 +trebuie sa scrie DOAR cele 11 coloane de totaluri/curs, nu si aceste 4. + +### A.5 Flagul `VANZARI_DETALII.PRET_CU_TVA` + +Deja parte a calculului in Oracle, nu doar in VFP: valoarea intra in agregare din +`a1.pret_cu_tva <- vd.pret_cu_tva <- VANZARI_DETALII_TEMP.PRET_CU_TVA` pentru liniile directe +(`:13883`) sau din `VANZARI_SETURI_TEMP.pret_cu_tva` pentru liniile din seturi (`:13909`), apoi +`calculeaza_total_fara_tva_fact`/`calculeaza_total_tva_fact` ramifica pe el (`:15871`, `:15976`). +Orice extragere pentru S5 trebuie sa citeasca acelasi `VANZARI_DETALII.PRET_CU_TVA` per linie (deja +persistat, cf. #7) - nu exista alta sursa de adevar pentru el. + +## B. Propunerea pentru S5 + +### Varianta A vs B + +`actualizeaza_vanzari` e apelata NECONDITIONAT de `finalizeaza_modificare_nota` pentru ORICE +editare de nota al carei `cod` exista in `vanzari` - nu doar facturi din #6, ci orice tip de +document din VANZARI editat azi prin `frm_modific2024`/`afisjurcom.do_modifica`, folosit deja de +ROAGEST/ROACONT in registrul jurnal. Daca **Varianta A** (extinderea in-place a lui +`actualizeaza_vanzari` cu recalcul de totaluri) e aleasa, recalculul ar rula automat la ORICE +editare de nota cu `cod` in vanzari, inclusiv editari care azi nu ating deloc sumele (ex. doar +`explicatie`/`cont`). Risc de regresie: **mediu-mare**, extins la toata suita ROA, nu doar la #6. + +**Varianta B** (procedura sora noua, ex. `recalculeaza_totaluri_vanzari`, apelata explicit din +`finalizeaza_modificare_nota` doar cand editarea a atins efectiv `VANZARI_DETALII`): risc **mic** - +`actualizeaza_vanzari` ramane neschimbata (zero impact pe restul aplicatiilor ROA care o folosesc +azi), noua procedura e un pas suplimentar, opt-in. + +**Recomandare: Varianta B**, motivata exclusiv de riscul de regresie asupra editarilor de note care +NU sunt facturi si trec prin acelasi `finalizeaza_modificare_nota`. + +### Schita procedurii propuse + +``` +PROCEDURE recalculeaza_totaluri_vanzari(V_ID_VANZARE IN NUMBER) IS + -- reia blocul de agregare din scrie_in_vanzari (:13763-13931), cu sursa + -- VANZARI_DETALII WHERE ID_VANZARE = V_ID_VANZARE AND STERS = 0 + -- in loc de VANZARI_DETALII_TEMP; V_DISCOUNT_FACTURA citit din VANZARI.DISCOUNT + -- (coloana existenta pe randul curent, populata la INSERT din acelasi parametru, :13682) +BEGIN + SELECT discount, ... INTO lnDiscountFactura, ... FROM vanzari WHERE id_vanzare = V_ID_VANZARE; + -- acelasi SELECT agregat ca in scrie_in_vanzari, sursa VANZARI_DETALII in loc de _TEMP + UPDATE vanzari + SET discount_tva = ..., valoare_achizitie = ..., total_fara_tva = ..., + total_tva = ..., total_cu_tva = ..., valval = ..., tvaval = ..., totval = ..., + id_valuta = ..., curs = ..., multiplicator = ... + -- FARA serie_incasat/nr_incasat/suma_incasat/tip_incasat, vezi A.4 + WHERE id_vanzare = V_ID_VANZARE; +END; +``` + +Apelata din `finalizeaza_modificare_nota` (PACK_CONTAFIN.pck:8601), dupa `actualizeaza_vanzari`. + +### Mecanismul de scriere VFP in `VANZARI_DETALII` la emitere + +Cautare in cache-ul text `COMUN` pentru `VANZARI_DETALII_TEMP` / `DETALII_TEMP` (case-insensitive): +**niciun rezultat**, inclusiv in `oscrie_in_fisiere.prg`. Tabela temp e populata probabil printr-un +helper generic de upload cursor->tabela (nume de tabela asamblat dinamic sau printr-un mecanism +care nu apare ca literal in sursa text). Nu am putut confirma static calea exacta - de cercetat +separat, pe partea VFP, inainte de proiectarea S4 (ce cursor/tabela temp foloseste editarea: +reutilizeaza `VANZARI_DETALII_TEMP` sau una noua). + +### Idempotenta si tranzactionalitate + +Fluxul de editare a notei ruleaza deja intr-o singura tranzactie manuala: +`Thisform.do_deschide_tranzactie()` (comun.vc2:2448) ... `oscrie_in_fisiere` + apelul catre +`finalizeaza_modificare_nota` (`:2484-2486`) ... `Thisform.do_inchide_tranzactie(...)` (`:2535`, +`COMMIT`/`ROLLBACK` in functie de succes). Procedura noua, apelata DIN INTERIORUL +`finalizeaza_modificare_nota`, mosteneste automat aceeasi tranzactie: la esec pe jumatate, +`ROLLBACK`-ul anuleaza tot (nota + `vanzari` + noile totaluri) - nu exista fereastra de +inconsistenta. `recalculeaza_totaluri_vanzari` propusa e ea insasi idempotenta ca `UPDATE ... WHERE +id_vanzare = :id` (poate rula de mai multe ori pe acelasi id fara efect cumulativ, spre deosebire +de un `INSERT`). + +## C. S6 - legaturile care depind de `cod` + +Verificat pe cod (nu date, dat fiind ca `MARIUSM_AUTO` are date de test): + +| Legatura | Cheie reala | Ramane valida? | +|---|---|---| +| `vanzari_coresp` | `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ` (coloane confirmate din `all_tab_columns`) | DA - `actualizeaza_vanzari` nu schimba `ID_VANZARE` (vezi A.1), doar `COD` pe acelasi rand | +| `facturat` (aviz/comanda, `marcheaza_facturat`) | `ID_VANZARE`, exclusiv (PACK_FACTURARE.pck:15384-15421, foloseste doar `pack_facturare.nid_vanzare`, niciodata `cod`) | DA | +| chitanta/incasare (`SERIE_INCASAT`/`NR_INCASAT`/`SUMA_INCASAT`/`TIP_INCASAT`) | denormalizate direct pe randul `VANZARI` (nu FK), scrise o singura data la `scrie_in_vanzari` (`:13777-13780`) | DA - `actualizeaza_vanzari` nu le atinge; raman la valoarea de la emitere, ceea ce e comportamentul corect (nu se recalculeaza la editare de sume, vezi A.4/B) | +| `ReferinteDocumenteNota`/`ReferinteDocument` (PACK_DOCUMENTE.pck:25-93) | `ACT.id_factd`/`id_factc` = `id_fact`-ul documentului, filtrat `cod <> tnCod` | DA - e o VERIFICARE PRE-EDITARE (gate, apelata inainte sa se schimbe ceva), nu o legatura persistenta; nu depinde de `cod`-ul rezultat dupa editare | +| `anaf_efactura` | `ID_FACT` (coloana confirmata) | DA - `id_fact` nu se schimba la editare (by design, deja stabilit in plan) | +| `documente` | `DOCUMENTE.ID_DOC = ACT.ID_FACT` (confirmat din `MERGE INTO DOCUMENTE ... ON A.ID_DOC = B.ID_DOC` unde `B.ID_DOC` vine din `ACT_TEMP.ID_FACT`, PACK_CONTAFIN.pck:858-880) | DA - si se REACTUALIZEAZA automat (SERIE_ACT/NRACT/DATAACT) la fiecare scriere de nota, inclusiv editare, prin acelasi `MERGE` care ruleaza in `cumuleaza_note_act` | + +**Concluzie generala**: toate legaturile intre note contabile trec prin `ID_FACT` +(`ACT.id_factd/id_factc`, `DOCUMENTE.id_doc`, `ANAF_EFACTURA.id_fact`), niciodata prin `cod`. Cum +#6 pastreaza `id_fact` neschimbat by design, toate raman valide fara nicio interventie +suplimentara. Singura legatura care trece prin altceva (`ID_VANZARE`, PK-ul randului `VANZARI`) e +`vanzari_coresp`/`marcheaza_facturat`, si acesta ramane la fel neschimbat (doar `COD` se rescrie pe +acelasi rand, vezi A.1). **S6 se poate inchide ca "confirmat pe cod, fara lucru suplimentar +necesar"**, nu doar ca "de verificat". + +## D. S7 - rotunjirea la reeditare + +`verifica_total_document` (PACK_FACTURARE.pck:16073-...) insereaza o linie de corectie in +`ACT_TEMP` cand suma recalculata din `ACT_TEMP` insusi (`V_TOTFTVA_VER`/`V_TOTTVA_VER`) difera de +`pack_facturare.ntotftva`/`ntottva` (valori calculate in VFP, trecute prin stare de sesiune inainte +de scriere). `ACT_TEMP` e complet repopulat la fiecare scriere - procedura nu "tine minte" nimic +intre apeluri, in Oracle. + +**Risc identificat pe partea VFP**: la editare, `afisjurcom.do_modifica` (comun.vc2:2352-2366) +incarca in cursorul `tact` TOATE randurile curente ale notei +(`SELECT * FROM vact_tot WHERE cod = ... [AND STERS = 0] ORDER BY id_act`) - asta include si linia +de corectie inserata la salvarea anterioara (e un rand `ACT` normal, fara marcaj special care sa o +distinga). La resalvare, acest rand curge inapoi prin `oscrie_in_fisiere` in `ACT_TEMP` impreuna cu +liniile editate de utilizator, iar `verifica_total_document` ruleaza din nou pe noul `ACT_TEMP` +(care deja contine vechea corectie). + +**Nu se poate decide static** daca rezultatul e o a doua corectie suprapusa peste prima, sau daca +mecanismul e self-consistent (adica `V_TOTFTVA_VER` deja include vechea corectie in suma agregata, +iar `pack_facturare.ntotftva` recalculat de VFP coincide, deci nu se mai adauga nimic) - depinde de +cum recalculeaza VFP `ntotftva`/`ntottva` la editare, cod care nu a fost verificat in aceasta trecere +(afara de scope-ul Oracle-only al acestei cercetari). + +De notat: acest mecanism NU e nou pentru #6 - e folosit azi neschimbat de orice editare de nota +prin `do_modifica` (ROAGEST/ROACONT registru jurnal), pe orice tip de document, de ani de zile. +Daca ar acumula corectii sistematic la editari repetate, ar fi deja o problema cunoscuta pe editari +non-factura. Asta scade riscul, dar nu-l elimina pentru cazul specific facturii, unde S4/S5 +introduc o cale noua de a schimba cantitati/preturi care nu exista azi in `do_modifica` generic (pe +notele generice azi de regula nu se schimba baza de calcul TVA in acelasi fel). + +**Raspuns**: nu se poate decide din cod - testul deja propus in plan (S7: trei editari consecutive, +verifica nr. de linii de corectie in `ACT`) e calea corecta si suficienta; nu exista scurtatura +statica. + +## E. Intrebari deschise + +1. **Semnatura procedurii noi**: apel neconditionat din `finalizeaza_modificare_nota` oricand + `cod`-ul exista in `vanzari` (simplu, cost mic - un `SELECT`+`UPDATE` in plus si pe editari care + nu ating articolele), sau flag explicit `tnAtinsArticole` trecut din VFP (evita lucru inutil, dar + risc de flag uitat/gresit)? **Recomandare: apel neconditionat** - cost neglijabil, elimina o + clasa de bug. +2. **Discountul de document la editare**: `V_DISCOUNT_FACTURA` la emitere e parametru explicit din + VFP; planul S4/S4b nu mentioneaza editarea discountului de pe document, doar cantitate/pret/ + `pret_cu_tva` pe linie. La S5, discountul se citeste din `VANZARI.DISCOUNT` (neschimbat) - de + confirmat cu Marius ca asta e comportamentul dorit (discountul de document ramane needitabil in + #6). +3. **`VALVAL`/`TVAVAL`/`TOTVAL`** (totaluri in valuta): planul S5 mentioneaza explicit doar + `total_fara_tva`/`total_tva`/`total_cu_tva`/`valoare_achizitie`/`discount_tva`. Fac parte din + acelasi bloc de agregare din `scrie_in_vanzari` - **recomandare: le includem in recalcul**, cost + suplimentar zero, evita inconsistenta pe documentele in valuta straina. +4. **Mecanismul de populare al `VANZARI_DETALII_TEMP`** la emitere n-a putut fi gasit static in + cache-ul text `COMUN` (cautare literal, fara rezultate) - necesita cercetare VFP separata inainte + de a proiecta calea exacta de scriere pentru editarea din S4 (aceeasi tabela temp, sau una noua). +5. **S7**: raspunsul cere test dinamic (3 editari consecutive), nu poate fi confirmat static - de + pastrat explicit in scope-ul S8, nu doar "de verificat" generic in text. diff --git a/docs/cercetare/retur_si_lista_preturi.md b/docs/cercetare/retur_si_lista_preturi.md new file mode 100644 index 0000000..f571faa --- /dev/null +++ b/docs/cercetare/retur_si_lista_preturi.md @@ -0,0 +1,198 @@ +# Cercetare: retur din facturi anterioare + adaugare din lista de preturi pe document cu sursa + +## A. Returul din facturi anterioare — cum functioneaza AZI + +### A.1 `But_retur` — lant de apel + +Definitia clasei butonului (`COMUN\clase\cmd_butoane.vc2:324-338`): +``` +DEFINE CLASS but_retur AS buton OF "_cmd_base.vcx" + caction = do_retur + ... + Visible = .F. +ENDDEFINE +``` +Butonul e instantiat pe `frm_facturare_articole` la `COMUN\clase\ofacturare.vc2:11221-11227` (`Visible=.F.` implicit) si devine vizibil doar conditionat, in `Init`, la `ofacturare.vc2:15122-15127`: +``` +Case Inlist(poDate.tip, 1, 5, 7, 10) && modificare v 2.0.56 : am adaugat 10 + && facturare pe baza de lista de preturi + ... + This.but_retur.Visible = .T. && pot sa fac retur de articole intr-o factura de vanzare +``` +Deci butonul e vizibil **doar pe documente de tip 1/5/7/10** (facturare pe baza de lista de preturi), nu pe facturi de retur propriu-zise (8/9) si nu pe documente cu sursa contract/comanda. + +`caction=do_retur` -> click apeleaza `frm_facturare_articole.do_retur` (`ofacturare.vc2:13963-13965`): +``` +PROCEDURE do_retur + Thisform.do_adauga_articol(.F., .F., .T.) +ENDPROC +``` +adica `do_adauga_articol(tlImplicit=.F., tlContract=.F., tlRetur=.T.)` (`frm_facturare_articole.do_adauga_articol`, `ofacturare.vc2:12813-12823`). + +Pas cu pas in `do_adauga_articol` (`ofacturare.vc2:12813-12900`), ramura `tlRetur`: +1. Utilizatorul selecteaza un articol (din `crsarticole`, lista de preturi) si o cantitate — `lnCantitate = poArticol.cantitate`. +2. `Thisform.do_verifica_articol(...)` valideaza cantitatea. +3. La `ofacturare.vc2:12881-12890` (case `Otherwise`, articol gestionabil): +``` +OTHERWISE + lnListaIdOld = poDate.listaid + IF m.tlRetur + loCauta = caut_facturi_multiple_client_articol(poDate.id_client, poDate.in_valuta, poDate.id_valuta, .F., poArticol.id_articol) + If m.gnButon = 1 and !Empty(Nvl(loCauta.id_vanzare, 0)) + poDate.listaid = Alltrim(Str(loCauta.id_vanzare)) + * ID_ARTICOL:ID_VANZARE,ID_ARTICOL:ID_VANZARE + thisform.cListaIdArticoleRetur = thisform.cListaIdArticoleRetur + IIF(!EMPTY(thisform.cListaIdArticoleRetur), ',', '') + ALLTRIM(STR(poArticol.id_articol)) + ':' + poDate.listaid + Endif + ENDIF + llSucces = Thisform.do_alege_stoc(poArticol.id_articol, lnCantitate, poArticol.denumire, tlImplicit, poArticol.pretftva, poArticol.discount_unitar, loGrid, m.tlRetur) +``` +4. `do_alege_stoc(..., tlRetur)` (`ofacturare.vc2:13200-13250`) foloseste ramura retur pentru a construi cursorul de stoc/gestiuni de unde se alege articolul de returnat: +``` +If Inlist(poDate.tip,8,9,24) Or m.tlRetur && factura retur lei, factura retur valuta, aviz retur sau factura normala cu retur de articole + lcSql = [{call pack_facturare.cursor_gestiuni_articol_retur(...)}] +``` +5. Rezultatul e afisat printr-un formular `frm_articol_gest_factura` (`Createobject("frm_articol_gest_factura",tnCantitate,tlImplicit,tlRetur)` la `ofacturare.vc2:13353` si `:13361/:13375`), unde utilizatorul alege gestiunea/seria si cantitatea de returnat. +6. La salvare, `do_scrie_articole` (`ofacturare.vc2:13967-13979`) trimite `poDate.listaid` (perechile `id_articol:id_vanzare` construite la pasul 3) catre `pack_facturare.initializeaza_date_factura`. + +Concluzie: **nu exista un singur dialog "alege facturile sursa" pentru tot documentul** — selectia facturii sursa se face **per articol**, in momentul adaugarii fiecarei linii, printr-un dialog generic de cautare. + +### A.2 Dialogul si criteriile de cautare a facturii sursa + +Formularul e generic — functia `cauta_alfa` (dialog de picker standard ROA), invocata din `caut_facturi_multiple_client_articol` (`COMUN\programe\oproceduri_facturare.prg:2124-2159`): +``` +Function caut_facturi_multiple_client_articol + Lparameters tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple, tnIdArticol + ... + lcSelect = [select a.serie_act,a.numar_act,a.data_act,a.dataora,a.id_vanzare ] + ; + [from vanzari a join vanzari_detalii b on a.id_vanzare = b.id_vanzare and b.id_articol = ?pnIdArticol ] + + lcFiltruOriginal = [a.sters=0 and a.tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) ] + gcCondSucursala + ; + lcFiltruPart + lcFiltruValuta + ... + lcTitlu = [Alegeti factura] && (tlFacturiMultiple = .F. la apelul din do_adauga_articol) + loCauta = cauta_alfa(lcSelect, lcFiltru, lcSchema, lcOrder, lcColoane, lcTitlu, lcTitluColoane, lcNumeProc, llToateIreg, lcFiltruOriginal, lcPrimaColoana, lnPornire, lnTipReturn, lcIdColumn) +``` +Coloane afisate: `Serie act, Numar act, Data, Data inreg.` Criterii de filtrare in interogare: +- **articolul curent** (`b.id_articol = ?pnIdArticol`, obligatoriu — cautarea se face per-articol, nu la nivel de document); +- **client** (`a.id_part = ?pnIdPart`, doar daca `tnIdPart` nu e gol — `lcFiltruPart`, `oproceduri_facturare.prg:2133`); +- **valuta** (`a.in_valuta`/`b.id_valuta`, `lcFiltruValuta`, `:2134`); +- **tip document sursa restrictionat la vanzari normale**: `a.tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)` — exclude explicit tipurile de retur (8,9,24), deci nu se poate face retur dintr-un retur. +- **numar factura / perioada**: nu exista filtru SQL dedicat in interogare; `cauta_alfa` e dialogul generic de cautare alfa/browse al suitei (permite filtrare interactiva pe coloanele afisate, dar asta e comportament generic al dialogului, nu parametru specific — neverificat mecanismul intern de filtrare al `cauta_alfa`). + +Rezultatul (`loCauta.id_vanzare`) e folosit ca `poDate.listaid` pentru articolul curent. + +### A.3 Tipuri de document retur + +Confirmat in cod, cu **3 valori**, nu doar 8/9 (comentariu explicit la `ofacturare.vc2:12862`): +``` +* 8 = Retur lei, 9 = retur valuta sau factura de vanzare normala, dar cu retur de articole, 24 = aviz retur +``` +Folosite consistent in tot `frm_facturare_articole`: `Inlist(poDate.tip, 8, 9, 24)` la `:13043`, `:13230`, `:13728`, `:14751`; `Inlist(poDate.tip, 8, 9)` separat la `:15236` (UI caption) si `:9718` (eliminare camp curs valutar). Pe `frm_facturare_articole2` (varianta "2" a formularului) aceleasi tipuri apar fara 24 in unele locuri (`:17531`: doar 8,9 — de verificat daca e omisiune sau intentionat, neclar din cod). + +Semnul cantitatilor pe retur — la `do_alege_stoc` (`ofacturare.vc2:13273, :13300, :13309`): +``` +Select Iif(poDate.tip=41,-1,1)*Sum(cantitate) As cantitate, ... +... +Where a.cantitate - Iif(Inlist(poDate.tip,8,9,24),(-1),1) * Nvl(b.cantitate,0) > 0 +``` +adica pentru tip 8/9/24 semnul cantitatii deja pe factura curenta se scade cu semn opus (`-1` in loc de `1`), consistent cu inregistrari de retur. + +**Ziua de curs eliminata pe retur** — confirmat, dar linia corecta e in `COMUN\clase\ofacturare.vc2:9717-9722` (`frm_date_factura.Init`), NU in `ofacturare_comun.vc2` (fisierul citat in plan nu are randul respectiv — `ofacturare_comun.vc2` are doar 7432 de linii): +``` +*!* modificare v 2.0.56 +If Inlist(poDate.tip, 8, 9) + lnHeight = lnHeight - .clb_zi_curs.Height + laPozitii(.clb_zi_curs.TabIndex, 2) = 1 + .RemoveObject('clb_zi_curs') +Endif +*!* modificare v 2.0.56 ^ +``` +Corolar in acelasi `Init` de `frm_facturare_articole` (`ofacturare.vc2:15098`): `Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "")` — eticheta de curs valutar nu afiseaza data pe retur. + +### A.4 Retur partial + +Da, e posibil. Coloana de cantitate din `grd_articole`, pe tip 8/9, isi schimba explicit titlul in "cantitate maxima de returnat" (`ofacturare.vc2:15236-15239`): +``` +Case Inlist(poDate.tip, 8, 9) && factura retur lei/valuta + This.grd_articole.cCantitate.header1.Caption = [Cant. max. de returnat] + This.cmesaj_cantitate = [Nu se mai poate face retur pentru acest articol!] +``` +si analog pentru aviz retur (tip 24) la `:15242-15245`. Cantitatea introdusa de utilizator e validata in `do_verifica_articol` (`:14743-14754`): +``` +llRetur = Inlist(poDate.tip,8,9,24) +Do Case + Case poArticol.gestionabil = 0 Or gnScadereStoc = 1 Or m.llFacturareFaraStoc + llReturn = .T. + Case (tnCantitate >= 0 And m.llRetur ) Or ... + lcMesaj = Alltrim(Thisform.cmesaj_cantitate) + amessagebox(lcMesaj,0+48,"Atentie") + llReturn = .F. +``` +adica sistemul respinge doar cazul in care cantitatea ramasa dupa retur ar iesi din domeniul valid (`tnCantitate >= 0` fiind conditia de eroare pe retur, unde cantitatile de retur sunt negative) — deci utilizatorul poate introduce orice cantitate <= maximul returnabil calculat de `cursor_gestiuni_articol_retur`, inclusiv mai mica (retur partial). Formularul de alegere (`frm_articol_gest_factura`, ramura `tlRetur`, `:13353-13385`) permite editarea cantitatii inainte de confirmare. + +### A.5 Legatura stocata linie-de-retur -> linie originala + +**Nu am gasit o coloana pe linie in `VANZARI_DETALII`** (de tip "id linie sursa") in codul VFP text disponibil. Ce exista, e legatura **la nivel de antet de document**, prin parametri output ai apelurilor Oracle: +- `poDate.nid_vanzare` / `poDate.nid_vanzare_retur` — populati ca parametri `?@...` in `pack_facturare.scrie_factura_avize_retur(...)` (`ofacturare.vc2:14448`, `:14509`) si `pack_facturare.finalizeaza_scriere_verificare(...)` (`:14472`); resetati implicit la `oDateFactura.Reset` (`COMUN\programe\ofacturare_comun.prg:569`: `.nid_vanzare_retur = 9999999999`). +- La nivel de sesiune VFP (nu persistat ca coloana confirmata), `thisform.cListaIdArticoleRetur` acumuleaza perechi `ID_ARTICOL:ID_VANZARE` (`ofacturare.vc2:12888`) trimise ca `poDate.listaid` catre `pack_facturare.initializeaza_date_factura` (`:13977-13979`) — asta e mecanismul prin care Oracle *primeste* info despre factura sursa per articol, dar daca acesta persista intr-o coloana dedicata pe `VANZARI_DETALII` (ex. id linie/document sursa) **nu se poate confirma din sursa VFP** — pachetul `pack_facturare` e in Oracle, in afara acestui repo. Cercetare existenta in `docs\cercetare\rec_cale_vanzari_detalii.md` si `rec_s5_oracle_vanzari.md` (interogari live pe schema Oracle, 08.08.2026) nu mentioneaza vreo coloana de tip `ID_DET_SURSA`/`ID_VANZARE_RETUR` pe `VANZARI_DETALII`. **Neverificat.** + +--- + +## B. Adaugarea din lista de preturi pe document cu sursa (comanda/contract) + +### A6/B6. Ce se vede azi pe cod + +`frm_facturare_articole.Init`, `Do Case` pe `poDate.tip` (`ofacturare.vc2:15108-15248`) configureaza UI-ul per tip. Puncte relevante: + +- **Contract (tip 2, 6)** — `ofacturare.vc2:15129-15143`: +``` +Case Inlist(poDate.tip, 2, 6) + && facturare pe baza de contract + This.lb_titlu_alb_b121.Caption = [FACTURA PE CTR. ] + Alltrim(poDate.descriere) + This.grd_articole.RemoveObject('cSerie') + && articole din lista de preturi + This.grd_articole.cCantitate.header1.Caption = [Cantitate in stoc] + This.cmesaj_cantitate = [Acest articol nu este pe stoc!] +``` +Comentariul "articole din lista de preturi" e explicit: `grd_articole` (cu butonul `But_urmator1`/`do_urmator`->`do_adauga_articol()`, cursorul `crsarticole`) **ramane activ si populat cu lista de preturi** chiar si pe document de tip contract. In paralel, `grd_contracte` (buton `But_urmator2`/`do_urmator2`->`do_adauga_articol(.F.,.T.)`, cursorul `crsarticole1`) afiseaza articolele restrictionate la contract. + +Cei doi cursori se populeaza distinct: pentru `tnTip in (2,26,6,52)`, apelul e `pack_facturare.cursor_contract(...)` (`COMUN\programe\ofacturare.prg:283-290`), care umple atat `crsarticole` cat si (separat) `crsarticole1` (confirmat prin `Create Cursor crscontracte ... Select Distinct ... From (lcCursor + [1])`, `ofacturare.prg:433-440` — `lcCursor+'1'` = `crsarticole1` deja exista la acel punct). + +- **Comanda (tip 3)** — `ofacturare.vc2:15144-15150`: +``` +Case poDate.tip = 3 + && facturare pe baza de comanda + This.lb_titlu_alb_b121.Caption = [FACTURA LA COMANDA ] + Alltrim(poDate.descriere) + This.grd_articole.RemoveObject('cSerie') + This.grd_articole.cCantitate.header1.Caption = [Cantitate comandata] + This.cmesaj_cantitate = [A fost facturata intreaga cantitate comandata pentru acest articol!] + This.but_urmator_tot1.Visible = .T. +``` +Aici `grd_articole`/`crsarticole` e populat direct de `pack_facturare.cursor_comanda(...)` (`ofacturare.prg:292-293`, tip in `(3,21,25,28,42,47)`) — adica **doar articolele comenzii**, nu lista de preturi libera. Nu exista al doilea cursor (`crsarticole1` nu se creeaza pentru tip 3, doar pentru `2,6,26` conform `ofacturare.prg:433`), deci in `Init` la `ofacturare.vc2:15294-15324`: +``` +If !Used('crsarticole1') + ... + Thisform.RemoveObject('grd_contracte') + Thisform.RemoveObject('but_urmator2') +Endif +``` +grila si butonul secundar se elimina complet. + +Exceptie: la **copiere de factura/aviz** (`poDate.lCopiere`), indiferent de tip, codul adauga explicit lista de preturi peste cursorul existent (`ofacturare.prg:454-473`): +``` +IF m.llCopiere + * Adaug in lista articolelor cursorul cu lista de preturi ca sa pot adauga si alte articole in factura copiata/modificata + lcSqlCursor = [{call pack_facturare.cursor_preturi(...)}] + ... + SELECT crsArticole + APPEND FROM DBF(m.lcCursorTemp) +ENDIF +``` + +### B7. Unde e restrictia + +Nu e un `Visible`/`Enabled` pe buton (butonul `But_urmator1`/lista-de-preturi exista si e vizibil pentru toate tipurile, la fel `grd_articole`) si nu e o validare la salvare — **restrictia e la nivelul continutului cursorului de articole disponibile** (`crsarticole`), decis de care procedura Oracle il populeaza in `factureaza()`/`factureaza2()` (`ofacturare.prg:266-308`, `Do Case` pe `tnTip`): +- tip **1,5,7,10** (lista de preturi) si **2,6,26,52** (contract) -> `cursor_preturi`/`cursor_contract`: `crsarticole` contine lista de preturi completa (nerestrictionata la sursa) => **azi se poate deja adauga liber din lista de preturi pe un document contract**. +- tip **3,21,25,28,42,47** (comanda) -> `cursor_comanda`: `crsarticole` contine **doar** articolele comenzii => **azi NU se poate** adauga o linie libera din lista de preturi pe un document comanda, decat prin copiere de document (`llCopiere`, ramura separata mai sus). + +Concluzie B: distinctia plan-ului ("comanda sau contract, tipurile 2,6,26,52") nu e uniforma in codul actual — **contractul (2,6,26,52) are deja acces liber la lista de preturi** (al doilea grid `grd_contracte` e doar un adaos, nu o restrictie), in timp ce **comanda (3) e restrictionata strict la continutul comenzii**, fara optiune de adaugare libera in fluxul normal (doar la copiere de document). diff --git a/docs/cercetare/roaauto_articole_lista_preturi.md b/docs/cercetare/roaauto_articole_lista_preturi.md new file mode 100644 index 0000000..0b9a6e0 --- /dev/null +++ b/docs/cercetare/roaauto_articole_lista_preturi.md @@ -0,0 +1,163 @@ +# ROAAUTO — adaugarea de articole reale din nomenclator pe langa MANOPERA/MATERIALE + +## Corectie fata de `roaauto_facturi.md` + +Raportul anterior a descris facturarea ROAAUTO ca fiind formata **doar** din linii sintetice +(`-100000`..`-100008`). E incomplet: exista un mecanism separat, **"Alte servicii"**, care adauga +linii cu `id_articol` real (pozitiv), din nomenclatorul de articole, in plus fata de liniile +sintetice MANOPERA/MATERIALE. Mecanismul e cablat **direct in `factureaza_deviz`**, deci face +parte din acelasi flux descris anterior, nu dintr-un flux alternativ. + +## 1. Unde se adauga articole din nomenclator + +Formular: `frm_incasare_finala` (`D:\ROA\ROAAUTO\Clase\oviz_devize.vc2:5435-7143`), aratat de +`frm_emitere_facturi.do_factureaza_final` (`oviz_devize.vc2:2569` `ofrmincasare=Createobject('frm_incasare_finala',...)`) +si `do_factureaza_final_part` (`:3539`) — adica **la momentul emiterii facturii finale**, nu pe +formularul de deviz propriu-zis (`oviz_devize` nu are buton de adaugare articol la crearea +devizului). + +Metoda: `frm_incasare_finala.do_adauga` (`oviz_devize.vc2:6539-6580`): +``` +lcXMLArticole = cauta_nom_articole([in_stoc = 0 and in_crm = 1 and id_articol not between -100008 and -100000]) +... +Replace id_articol With loArticol.id_articol,denumire With loArticol.denumire,um With loArticol.um,codmat With loArticol.codmat,; + id_lucrare With lnIdLucrare,nrord With lcNrOrd +``` +Cauta in `vnom_articole_toate` (nomenclatorul de articole, cursor `crstmpart`) prin +`cauta_nom_articole` (`COMUN\programe\ocautare.prg:1781`), filtrat la articole **fara gestiune** +(`in_stoc = 0`) si **vizibile in CRM** (`in_crm = 1`), excluzand explicit id-urile sintetice +(`not between -100008 and -100000`). Articolele alese se adauga in grila `grd_altele` +(`oviz_devize.vc2:6060`, coloane `cDenumire/cNrord/cCantitate/cPretFTva/cValoareftva`, +`oviz_devize.vc2:6121-6222`), sustinuta de cursorul `crsalteserv`, creat inainte de afisarea +formularului in `frm_emitere_facturi.do_factureaza_final` (`oviz_devize.vc2:2553-2555`) si +`do_factureaza_final_part` (`:3522-3524`): +``` +Create Cursor crsalteserv(id_articol N(14) Not Null,denumire c(100) Not Null,codmat c(50) Null,um c(10) Null,; + id_lucrare N(14) Not Null,nrord c(100) Not Null,; + cantitate N(14,gnPCant) Default 1,pretftva N(14,gnPPretV) Default 0,valoareftva N(14,gnPc) Default 0) +``` +**Important**: `do_adauga` NU completeaza `pretftva`/`cantitate` — raman la valorile implicite +(1, respectiv 0). Pretul si cantitatea se **introduc manual** de operator in grila +(`grd_altele.cPretFTva.Text1.LostFocus` / `cCantitate.Text1.LostFocus`, ambele apeland +`Thisform.do_modifica_alteserv()` care recalculeaza `valoareftva = cantitate * pretftva`, +`oviz_devize.vc2:6692-6698,7064-7070`). Nu exista o cautare/preluare automata de pret pentru +aceste articole in acest flux (vezi punctul 6). + +## 2. Cum ajung in factura — linii separate, NU cumulate + +In `factureaza_deviz` (`Programe\oproceduri_devize.prg`), liniile sintetice MANOPERA/MATERIALE/ +DISCOUNT/AVANS se insereaza in cursorul `crsdeviz` (`:936-983`) cu id-uri fixe (`-100000`.. +`-100008`). La pasul de cumulare pentru optiunea "articol cumulat REPARATII AUTO" (`:999-1012`) +se cumuleaza **doar** liniile cu `id_articol IN (-100003,-100000,-100002,-100001)` — liniile din +`crsalteserv` nu sunt incluse in acest `INLIST`, deci nu pot fi absorbite in cumulare. + +Liniile din `crsalteserv` se insereaza **separat**, dupa cumulare, cu `id_articol` real: +``` +If Used('crsalteserv') + Insert Into (lcCursorDeviz)(id_articol,denumire,explicatie,cantitate,um,id_jtva_coloana,proc_Tvav,taxcode,pret,discount_unitar) ; + Select id_articol,denumire,"",cantitate,um,CAST(lnIdJTvaColoana as N(6) null) as id_jtva_coloana,...,pretftva,0 From crsalteserv Where !Deleted() +Endif +``` +(`oproceduri_devize.prg:1036-1041`) — deci id-ul real din `nom_articole` trece nemodificat. +De acolo, fiecare rand ajunge in `crsvanztemp` (`:1190-1206`) si e scris in Oracle prin +`pack_facturare.adauga_articol_factura_deviz` (`:1240-1257`, apelul SQL construit cu +`Alltrim(Str(poArticol.id_articol))` la `:1241`), care insereaza direct in +`VANZARI_DETALII_TEMP` cu `ID_ARTICOL = V_ID_ARTICOL` (pozitiv, real) — vezi implementarea in +`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:4675-4745`. Confirmare: **articolele din "Alte +servicii" raman linii proprii, cu id_articol real, si NU se aduna in liniile sintetice +MANOPERA/MATERIALE.** + +## 3. Gestiunea + +`id_gestiune` in cursorul `lcCursorDeviz` are `DEFAULT null` (`oproceduri_devize.prg:885`) si +**niciun** `INSERT INTO` — nici cel al liniilor sintetice, nici cel al liniilor din +`crsalteserv` (`:1040-1041`) — completeaza aceasta coloana. La transferul in `crsvanztemp` +(`:1201-1206`) coloana `id_gestiune` (definita fara valoare implicita explicita la `:1190`) nu e +in lista `SELECT`, deci ramane 0 (implicit numeric), transmis ca literal `0` catre +`adauga_articol_factura_deviz` (`:1255`, `Alltrim(Str(poArticol.id_gestiune))`), care il scrie ca +atare in `VANZARI_DETALII_TEMP.ID_GESTIUNE` — nu NULL, ci **0**. Nu exista descarcare de gestiune +pentru aceste linii; e consistent cu faptul ca articolele oferite spre alegere sunt filtrate +explicit `in_stoc = 0` (articole/servicii fara gestiune de stoc). **Concluzie: liniile din "Alte +servicii" NU au gestiune reala completata**, exact ca liniile sintetice — diferenta e doar +`id_articol`. + +## 4. Stergere / modificare inainte de facturare + +- Stergere: `frm_incasare_finala.do_sterge` (`oviz_devize.vc2:6730-6741`), cu confirmare + (`amessagebox("Sunteti sigur ca doriti sa stergeti articolul "+lcArticol+" de pe factura?",4+32,...)`), + marcheaza randul `Delete` in cursorul bufferat (exclus apoi prin `Where !Deleted()` la punctul 2). +- Modificare cantitate/pret: direct in grila (`grd_altele.cCantitate`/`cPretFTva`), vezi punctul 1. +Toate aceste actiuni sunt posibile **doar cat timp `frm_incasare_finala` e deschis, inainte de +`do_termin`** (validarea din `inainte_de_do_termin`, `:6749-6801`, verifica duplicate si valori 0, +dar nu mai permite reintrarea in formular dupa emitere). + +## 5. Dupa facturare — cmd_modifica1 si cai alternative + +Confirmat, cu corectie de context: `Thisform.cmd_modifica1.Enabled=Iif(Empty(nrfact),.T.,.F.)` +se afla in `frm_emitere_facturi.grdcomenzi.AfterRowColChange` (`oviz_devize.vc2:4536`), nu intr-o +metoda separata — se re-evalueaza la fiecare schimbare de rand in grila de comenzi, dezactivand +butonul "Modifica" de indata ce comanda are `nrfact` completat. Verificat suplimentar: +- `frm_emitere_facturi.verifica_stornare` (`oviz_devize.vc2:4513-4514`) e **gol** (`PROCEDURE + verifica_stornare / ENDPROC`, fara cod) — nu exista storno de comanda/deviz in acest formular. +- Singurul "storno" gasit in `oviz_devize.vc2` e `do_storneaza_avans` (`:4256`, folosit din + `do_factureaza_final`/`do_factureaza_final_part` la `:2345`/`:3305`) — priveste exclusiv + reversarea unui **avans incasat**, nu redeschiderea unei facturi/deviz deja emise si nu permite + adaugarea de articole pe o factura emisa. +- Am gasit un mecanism generic, in afara `oviz_devize.vc2`, care poate **sterge complet** o + factura deja emisa: `frm_facturi.do_sterge` (`COMUN\clase\ofacturare_comun.vc2:4649-4689`), + care apeleaza `pack_facturare.sterge_factura(?pnIdVanzare,...)`. In Oracle, + `sterge_factura` (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:5441-...`) face un **soft-delete** + (`UPDATE VANZARI SET STERS = 1 ...`, `:5505-5508`), cu verificari ca nu existe deja facturi/avize + de retur legate. Aceasta e o stergere a intregii facturi (delete + re-emitere ulterioara), + **nu** o adaugare/modificare de articole pe factura existenta. + **Neverificat**: nu am putut confirma, in bugetul acestei cercetari, daca `frm_facturi` e + cablat intr-un meniu ROAAUTO (grep-ul pentru instantieri `frm_facturi` in fisiere specifice + ROAAUTO — in afara de `COMUN\` — nu a gasit potriviri de cod, doar metadate ascunse) si nici + daca stergerea Oracle reseteaza `nrfact`/`facturat` pe comanda ROAAUTO de origine (ar necesita + urmarirea `pack_auto`, in afara scriptului analizat). Deci **nu pot confirma sau infirma cu + dovada directa** o cale completa "sterge factura -> reface devizul cu articole diferite" din + interiorul ROAAUTO; pot confirma doar ca un asemenea instrument de stergere exista generic in + suita si ca in `oviz_devize.vc2` nu exista niciun cod care sa modifice/adauge articole pe o + factura deja emisa. +- **Concluzie pe intrebarea centrala**: constatarea anterioara ramane valabila si dupa aceasta + cercetare — `oviz_devize.vc2` insusi nu ofera nicio cale de a adauga/modifica articole pe o + comanda/deviz cu `nrfact` completat; singura cale gasita spre o factura deja emisa e stergerea + totala (soft-delete) prin ecranul generic COMUN, nu o editare in linie. + +## 6. Nomenclator vs. lista de preturi — doua surse, cea folosita de ROAAUTO e nomenclatorul brut + +Da, exista doua surse diferite in suita ROA: +- **Nomenclatorul brut de articole**: `vnom_articole_toate` / `nom_articole`, interogat prin + `cauta_nom_articole` (`COMUN\programe\ocautare.prg:1781-1823`, `SELECT ... FROM ] + gcS + [.vnom_articole_toate a` + la `:1799-1803`). **Acesta e cel folosit de `frm_incasare_finala.do_adauga`** — fara pret + atasat automat (pretul se tasteaza manual, vezi punctul 1). +- **Motorul de preturi negociate/politici de pret**: `pack_facturare.cursor_preturi`, apelat din + `COMUN\programe\ofacturare.prg` in `factureaza`/`factureaza2` (liniile 276/281/464/755/760) — + parte a mecanismului generic COMUN de facturare/avizare pe baza de "lista de preturi" + (`factureaza(22)`, `factureaza(29)`, `factureaza(23)`, `factureaza(41)` — comentate explicit + `pe baza de lista de preturi` in `COMUN\programe\oproceduri_facturare.prg:205,237,268`). + **Nu am gasit niciun apel** al acestui `factureaza`/`factureaza2` generic in fisierele + specifice ROAAUTO (`oviz_devize.vc2`, `oproceduri_devize.prg`) — grep-urile pentru + `cursor_preturi` si pentru `factureaza(` in afara de `COMUN\programe\ofacturare.prg` nu au + gasit potriviri in cod ROAAUTO. **Concluzie**: fluxul propriu ROAAUTO de facturare deviz + (`factureaza_deviz`) foloseste exclusiv nomenclatorul brut, fara calcul automat de pret + negociat; motorul `cursor_preturi` pare sa apartina unui flux de facturare/avizare generic, + separat, neapelat din codul ROAAUTO analizat. + +## Neverificat / limitari + +- Wiring-ul meniu -> `frm_facturi` in ROAAUTO (punctul 5). +- Efectul `sterge_factura` asupra coloanelor `nrfact`/`facturat` pe comanda ROAAUTO (necesita + `pack_auto`, neinclus in scriptul Oracle analizat). +- Numele exact al butonului/caption care declanseaza `frm_incasare_finala.do_adauga` — nu am + gasit in `oviz_devize.vc2` un `Click` explicit legat de `do_adauga`; e foarte probabil declansat + de un buton generic `cmd_adauga` prin conventia clasei de baza `_frm_base` (folosita si de alte + metode `do_xxx`/`cmd_xxx` din acelasi fisier), dar nu am gasit dovada directa a legaturii. +- Indexul ROAAUTO a fost reconstruit local (`_symbols.tsv`, permis explicit); nu a fost rulat + `git_sync.ps1` si nu s-a generat text nou din binare in ROAAUTO. + +## Nota + +Raportul a fost scris initial (din greseala) la calea gresita `D:\ROA\ROAAUTO\docs\cercetare\...` +in loc de `D:\ROA\ROAFACTURARE\docs\cercetare\...`. Acest fisier e livrarea corecta, la calea +ceruta. diff --git a/docs/cercetare/roaauto_facturi.md b/docs/cercetare/roaauto_facturi.md new file mode 100644 index 0000000..65a1235 --- /dev/null +++ b/docs/cercetare/roaauto_facturi.md @@ -0,0 +1,167 @@ +# Cercetare: facturile emise din ROAAUTO si relatia lor cu VANZARI_DETALII + +Scop: pentru `plan_13_unificare_formular_facturare.md` — poate formularul unificat sa acopere si +facturile emise din ROAAUTO (piese + manopera pe deviz), stiind ca ele "se pot modifica ulterior"? + +## Metoda si ce am putut verifica + +ROAAUTO (`D:\ROA\ROAAUTO`) e un working copy **migrat** (are deja `.vc2`/`.sc2` in-tree, ca +ROAFACTURARE), deci am cautat direct in text, **fara sa rulez `git_sync.ps1`** (interzis explicit). +Nu pot garanta ca acel text e sincron 100% cu binarul curent (nu l-am regenerat) — daca vreo linie +citata pare sa nu corespunda comportamentului live, motivul cel mai probabil e text neactualizat, nu +o citire gresita. + +`D:\ROA\_vfp_textcache\roaauto\_symbols.tsv` exista dar indexeaza **doar `.prg`** (1924 intrari, +toate `.prg`; niciun `.vc2/.sc2`) — probabil generat inainte de migrarea in-tree. Nu m-am bazat pe +el; am cautat direct cu Grep in `.vc2`/`.prg` din arbore. + +Pentru partea Oracle am gasit sursa curenta a pachetului `PACK_FACTURARE` (COMUN, shared) in +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` (17020 linii, +cel mai recent script din istoricul de migrari) — folosita ca sursa de adevar pentru semnaturile +procedurilor. + +## 1. Formularul/programul care emite facturi in ROAAUTO + +Nu e un formular separat de facturare, ci un **modul apelat din formularul de devize/comenzi** +(`frm_...` in `oviz_devize.vc2`, clasa cu `cmd_factavans`/`cmd_factfinal`). Logica de facturare +propriu-zisa e in `Programe/oproceduri_devize.prg`: + +- `Procedure factureaza_deviz` — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:846` (semnatura la + linia 847: `Lparameters tnIdComanda,tcNrOrd,tcNrInmat,tcDenop,tnMultiple,...`). +- Apelata din formular in `D:\ROA\ROAAUTO\Clase\oviz_devize.vc2` (liniile 1834, 2201, 3146, 4056, + 4456) prin butoanele de facturare avans/final. +- Exista si `Procedure relisteaza_factura_deviz` — + `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1516` — pentru **re-listarea** (re-tiparirea) unei + facturi deja emise, nu pentru editarea ei. + +## 2. Cum scrie in Oracle: aceleasi proceduri, cale comuna cu ROAFACTURARE + +Da — **acelasi pachet Oracle `PACK_FACTURARE`** (COMUN, shared cu ROAFACTURARE), apelat prin +`goExecutor.oExecute`, cu doi apeluri specifice pe langa cele generice: + +- `pack_facturare.initializeaza_date_factura(...)` — + `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1211` +- `pack_facturare.adauga_articol_factura_deviz(...)` (varianta **_deviz** a + `adauga_articol_factura`, cu parametri expliciti de pret/gestiune/valuta in loc sa caute articolul + din nomenclator) — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1240`, in bucla `Scan`/`Endscan` + peste cursorul `crsvanztemp`. +- `oscrie_in_fisiere(0,.F.,.T.)` — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1269`. +- `pack_facturare.scrie_incasari(...)` — linia 1294 (doar daca exista incasare la emitere). +- `pack_facturare.scrie_in_vanzari(0, id_delegat, id_masina, id_facturare, ..., @poDate.nid_vanzare)` + — linia 1302, **urmata in acelasi `lcSql`** de `pack_auto.actualizeaza_deviz(...)` (linia 1310) — + un apel specific ROAAUTO, care actualizeaza starea devizului/comenzii dupa facturare (marcheaza + `RUL`, seteaza `nrfact` etc., nu am citit corpul lui `pack_auto` — pachet separat, nu l-am cautat). +- Corpul `adauga_articol_factura_deviz` (spec+body in `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:468` + si `:4675-4745`) face un simplu `INSERT INTO VANZARI_DETALII_TEMP (...)` — **exact tabela de + staging** pe care o foloseste si facturarea normala din ROAFACTURARE (`adauga_articol_factura`, + linia 549 din spec, insereaza in acelasi `VANZARI_DETALII_TEMP`). +- **Premisa lui Marius e confirmata, cu o nuanta**: ROAAUTO nu scrie direct in `VANZARI_DETALII`; ca + si fluxul normal, trece prin `VANZARI_DETALII_TEMP`, iar transferul definitiv catre + `VANZARI`/`VANZARI_DETALII` se face in `pack_facturare.scrie_in_vanzari` + (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:915-923` spec, body la `:13497-13962`) — **acelasi + punct final** pe care il foloseste orice alta facturare ROA (avize, comenzi, contracte). Nu exista + cale Oracle proprie ROAAUTO pentru scrierea liniilor de factura. + +## 3. Tipul de document: `tip = -12` + +`factureaza_deviz` construieste obiectul de date cu +`poDate = Createobject("oDateFactura",lnIdSet,-12)` — +`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:922` — al doilea parametru e `tip`. Clasa +`oDateFactura` nu am gasit-o definita ca text (nu apare `DEFINE CLASS oDateFactura` in nicio sursa +text din ROAAUTO sau din COMUN al oricarui proiect cautat) — probabil traieste intr-un `.vcx` inca +neconvertit sau e generata dinamic; **neverificat** unde anume e clasa, dar e clar shared (folosita +si in `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2`, ex. liniile 3894/4075/7090, cu acelasi +tipar `Createobject("oDateFactura", tip1, tip2)`). + +**`tip = -12` e deja cunoscut in ROAFACTURARE** — nu ca un cod nou, ci ca ceva deja intalnit si +documentat in cercetarea recenta de regresie a editarii facturii (proiectul S4, +`docs\progres.md:339`, `:1142`; `docs\cercetare\rec_datoria6_baza_regresie.md:93`; +`docs\cercetare\rec_s4_runda1.md:38`; `COMUN\docs\cercetare\rec_view_articole_vanzare.md:108`), pe un rand +real din baza de test `MARIUSM_AUTO`: `cod=1140885` -> `id_vanzare=1047`, `tip=-12`. Acolo e descris +explicit: **"nu e factura, e alt tip de document"** (handoff intermediar (sters)) si decizia produsului +(decizia 19) e ca detectia liniilor de articole "merge pe orice tip, nu doar facturi" — adica +`IncarcaArticoleFactura`/`IncarcaVanzareNota` din `COMUN\programe\ofacturare_editare.prg` (mentionate +in `rec_datoria6_baza_regresie.md:83-84`) nu filtreaza dupa `tip=1`, deci **vad si randurile +`tip=-12`** deja, fara cod suplimentar. + +## 4. Ce e specific fata de o factura obisnuita + +- **Camp de legatura cu masina**: `V_ID_MASINA` e transmis catre `pack_facturare.scrie_in_vanzari` + (`oproceduri_devize.prg:1304`). Nu e un camp exclusiv ROAAUTO — `ID_MASINA` e coloana standard pe + `VANZARI`, cunoscuta si de fluxurile generice din pachet: `modifica_date_factura` are parametrul + `V_ID_MASINA IN NUMBER` (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:946`), la fel + `scrie_factura2`, `scrie_factura_avize`, `scrie_factura_avize_retur` (liniile 618-747 din acelasi + fisier). Deci nu e o bariera de schema — ROAFACTURARE stie deja de `ID_MASINA`. +- **Liniile facturii nu sunt articole individuale din nomenclator, ci sume cumulate cu + pseudo-articole (id negative)**: `factureaza_deviz` agrega toate sumele din contul analitic al + devizului (`actactan`) pe categorii si insereaza cate o linie sintetica per categorie — + `-100000` MANOPERA (`oproceduri_devize.prg:965-967`), `-100001` DISCOUNT MANOPERA (`:972`), + `-100003` MATERIALE (`:947-949`), `-100005` AVANS (`:936-937`), `-100006` STORNARE AVANS + (`:1026-1027`), `-100007`/`-100008` INSPECTIE TEHNICA / SPALARE AUTO (`:979-982`). Optional, daca + `gnAUTOIdArticolReparatii` e setat, toate liniile de mai sus se **cumuleaza intr-o singura linie** + cu un articol real din nomenclator ("REPARATII AUTO", `:989-1004`). Confirmat pe date reale in + `COMUN\docs\cercetare\rec_view_articole_vanzare.md:108-109`: `id_vanzare=1047` (`tip=-12`) are doar + **2 randuri** in `VANZARI_DETALII`, cu `id_gestiune`/`nume_gestiune` NULL (linii nestocate, + netinute in gestiune). Deci pe factura nu apar piesele individual — apar totaluri + MATERIALE/MANOPERA (cu exceptia cazului cumulat, unde apare un singur articol generic). +- **`pack_auto.actualizeaza_deviz(...)`** e chemat imediat dupa `scrie_in_vanzari`, in acelasi + bloc SQL (`oproceduri_devize.prg:1310`) — leaga vanzarea nou creata inapoi de deviz/comanda + (tabelele `DEV_*`/comenzi din ROAAUTO). Corpul lui `pack_auto` nu a fost citit (pachet separat, + in afara ariei `PACK_FACTURARE` cautate) — **neverificat** ce tabele ROAAUTO scrie exact. +- **`DEV_TIP_DEVIZ`** (garantie/postgarantie/regie, `oproceduri_devize.prg:1797`) e o clasificare + **diferita**, a devizului insusi, nu are legatura cu `VANZARI.tip=-12`. + +## 5. Cum se modifica azi o astfel de factura + +- **Din ROAAUTO**: nu exista editare a liniilor dupa facturare. Formularul de devize/comanda + (`oviz_devize.vc2`) **dezactiveaza explicit butonul de modificare** dupa ce comanda are numar de + factura: `Thisform.cmd_modifica1.Enabled=Iif(Empty(nrfact),.T.,.F.)` — + `D:\ROA\ROAAUTO\Clase\oviz_devize.vc2:4536`. Singura actiune disponibila post-facturare gasita in + cod e re-listarea (`relisteaza_factura_deviz`, `oproceduri_devize.prg:1516`), care doar reciteste + antetul/liniile din `fact_vfacturi`/`fact_vfacturi_detalii` pentru reprintare, fara sa scrie + nimic. N-am gasit niciun apel din ROAAUTO catre `pack_facturare.modifica_date_factura` (grep fara + rezultate in tot arborele ROAAUTO) — nici macar antetul (delegat/masina/text aditional) nu pare + editabil din ROAAUTO dupa emitere. **Neverificat**: n-am acoperit tot `oviz_devize.vc2` (fisier de + >8800 linii) linie cu linie, doar zonele gasite prin grep pe termenii relevanti — nu exclud un alt + punct de editare cu alt nume de metoda. +- **Din ROAFACTURARE**: exista deja, in lucru (proiectul S4), un editor de factura care + **citeste** liniile oricarui `tip` (inclusiv `-12`) din `VANZARI_DETALII` — funcțiile + `IncarcaVanzareNota`/`IncarcaArticoleFactura` in `COMUN\programe\ofacturare_editare.prg` (adaugate + conform `docs\cercetare\handoff_s4_runda1.md:79-81`, verificate pe `cod=1140885` -> `id_vanzare=1047`, + `tip=-12`, `docs\cercetare\rec_datoria6_baza_regresie.md:93`) si formularul `frm_modific2024` + (`COMUN\clase\omodificari.vc2`), cu un grid nou `grdArticoleFactura` pe pagina 3 "Articole factura". + **Insa acest grid e in prezent READ-ONLY**: "grid nou `grdArticoleFactura` (...), `ReadOnly` la + nivel de grid **si** pe fiecare `Text1`" (`docs\cercetare\handoff_s4_runda1.md:80-82`) — deci azi + se poate **vizualiza**, nu edita, de aici (nici adaugare, nici stergere de linii). + +## 6. Se poate adauga azi o linie libera / din lista de preturi pe o astfel de factura? + +Pe cod, **nu, din nicaieri, azi**: + +- Din ROAAUTO: butonul de modificare a devizului e dezactivat dupa facturare + (`oviz_devize.vc2:4536`), iar singura cale de scriere gasita (`factureaza_deviz`) ruleaza o + singura data la emitere; nu exista un `adauga_articol_...` apelabil ulterior pe o vanzare deja + scrisa. Structura insasi a liniilor (sume cumulate pe pseudo-articole negative, sectiunea 4) e + diferita de o linie normala de factura cu articol real din lista de preturi — un eventual "adauga + articol" ar trebui sa lucreze langa niste linii care nu reprezinta articole reale. +- Din ROAFACTURARE: editorul nou (`frm_modific2024`) **vede** liniile (orice `tip`, deci si cele + venite din ROAAUTO), dar gridul e read-only — nu exista azi cod de adaugare/scriere pe acest grid. + Nu am gasit alt formular ROAFACTURARE (`frm_facturi` clasic) care sa editeze articolele unei + vanzari cu `tip=-12` — cautarea `ROAAUTO` in `D:\ROA\ROAFACTURARE` (in afara de `COMUN`) nu da + potriviri de cod, doar mentiuni in `docs\` (progres.md, cercetare); `Grep` pe `ROAAUTO` in + `D:\ROA\COMUNROA` a expirat (arbore prea mare) si nu a fost reincercat — **neverificat** daca + `COMUNROA` (copia partajata separata de `COMUN` din ROAAUTO/ROAFACTURARE) are vreo referinta + directa la ROAAUTO. + +## Concluzie pentru planul de unificare + +Premisa lui Marius se confirma pe cod: facturile ROAAUTO ajung, prin acelasi `PACK_FACTURARE` +shared si acelasi `VANZARI_DETALII_TEMP` -> `VANZARI_DETALII`, in aceeasi tabela pe care o citeste +ROAFACTURARE — deci un formular unificat **le poate vedea** fara cod special de recunoastere a +sursei (dovada: editorul S4 le vede deja, testat pe date reale). Ce lipseste nu e recunoasterea, ci +**capacitatea de scriere**: azi nimic, in niciun produs, nu poate adauga o linie pe o vanzare +`tip=-12` — gridul nou din ROAFACTURARE e deliberat read-only, iar ROAAUTO isi blocheaza propriul +formular dupa facturare. Particularitatea reala de gestionat e ca liniile existente nu sunt articole +individuale, ci sume cumulate MATERIALE/MANOPERA pe pseudo-articole cu `id_articol` negativ — orice +UI care adauga articole din lista de preturi "langa" ele trebuie sa decida cum coexista cu randuri +care nu au `id_gestiune`/nu corespund unui articol real. diff --git a/docs/cercetare/rute_scriere_antet.md b/docs/cercetare/rute_scriere_antet.md new file mode 100644 index 0000000..7798250 --- /dev/null +++ b/docs/cercetare/rute_scriere_antet.md @@ -0,0 +1,249 @@ +# Rute de scriere pe antetul unui document DEJA EMIS (VANZARI/ACT/RUL) — Grupele A/B/C + +Cercetare read-only, fara nicio modificare de cod. Sfera: `pack_facturare.modifica_date_factura` +(14 campuri, deja documentat in `modifica_date_factura_parametri.md`) e confirmata ca **singura** +cale directa pentru serie/numar/data act/data scadenta + ruta/delegat/masina/agent/dataora_exp/ +id_facturare/listare_detaliata/text_aditional/tip_saft/efactura. Intrebarea de aici: exista alte +cai, pentru campurile din grupele A/B/C, in afara drumului de emitere initiala. + +**Rezumat** + +| Camp | Verdict | Nota | +|---|---|---| +| A. ID_VENCHELT | NU (cu o rezerva) | vezi pct. 1.4 — posibil editabil direct in grid, neverificat pana la capat | +| A. ID_SECTIE | **DA** (nou, activ) | prin editare directa a notei contabile, pct. 1.3-1.4 | +| A. ID_RESPONSABIL | **DA** (nou, activ) | idem | +| A. ID_LUCRARE | **DA** (nou, activ) | idem | +| B. ID_FDOC (tip document) | NU | cautat, negasit — pct. 2 | +| B. ID_VALUTA | NU | cautat, negasit | +| B. Zi curs | NU | cautat, negasit | +| B. ID_CLIENT | NU | cautat, negasit | +| B. sursa/"altele" | NU | cautat, negasit | +| B. gestiune sursa | NU | cautat, negasit | +| B. politica de preturi | NU | cautat, negasit | +| C. opt_incasat/casa/serie chit/nr chit/incasat/POS | NU direct, PARTIAL indirect | scrise doar la emitere (pct. 3.1); posibila cale indirecta prin editarea notei contabile daca incasarea e pe acelasi `cod` (pct. 3.2, neconfirmat pana la capat) | + +--- + +## 1. Grupul A — analiticele de antet (ID_VENCHELT, ID_SECTIE, ID_RESPONSABIL, ID_LUCRARE) + +### 1.1 Unde traiesc si cum se scriu la emitere + +Coloanele sunt pe `ACT` (`ACT.ID_VENCHELT%TYPE`, `ACT.ID_SECTIE%TYPE`, `ACT.ID_RESPONSABIL%TYPE`, +declarate asa in pachetul Oracle), nu pe `VANZARI`. La emitere, `frm_date_factura`/`frm_date_aviz` +(`COMUN\clase\ofacturare.vc2:9041-9706` / `:6900-7516`, controale `Ct_clb_venchelt`/`Ct_clb_sectie`/ +`Ct_clb_responsabil`/`Ct_clb_lucrare`) populeaza variabilele de sesiune Oracle +`pack_facturare.nid_venchelt` / `nid_sectie_stoc` / `nid_responsabil` / `nid_lucrare` +(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:1884-1887`), care se scriu o singura data, uniform pe +tot documentul, in `INSERT INTO ACT_TEMP` la contabilizare (comentariul explicit de la linia 1286 +din `PACK_CONTAFIN.pck`: *"Am completat ID_RESPONSABIL la instructiunile INSERT INTO ACT_TEMP"*). +Nicaieri in cele doua fisiere Oracle centrale nu exista `UPDATE ACT SET ID_VENCHELT|ID_SECTIE| +ID_RESPONSABIL|ID_LUCRARE = ...` (grep combinat `UPDATE ACT ... SET` + fiecare coloana, zero +potriviri, atat in pachetul curent `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` cat si in +`PACK_CONTAFIN.pck`, `PACK_UPDATE.pck`, `PACK_MIGRARE.pck` din `COMUN\docs`). + +`frm_date_factura`/`frm_date_aviz` sunt instantiate **doar** din interiorul `ofacturare.vc2` +insusi (nu am gasit niciun `Createobject` catre ele in afara clasei) — sunt exclusiv wizard-ul de +la emitere, nu apar pe niciun flux de editare ulterioara. + +**Concluzie partiala**: `modifica_date_factura` (calea documentata deja) NU atinge aceste 4 +campuri, si nu exista niciun `UPDATE` Oracle separat pe ele. **Pana aici, verdictul era NU.** + +### 1.2 Descoperire care schimba raspunsul: `frm_facturi.do_editare_factura` (functionalitate noua) + +Commit-ul cel mai recent din branch (`97d1613`, *"#6 editare factura emisa: omodificari.vcx intra +in proiect"*) a adaugat exact ce lipsea. Exista deja o cercetare anterioara in acest depozit +(`docs\cercetare\rec_modific2024.md`, scrisa inainte de acest commit) care documenteaza clasa +`frm_modific2024` (`COMUN\clase\omodificari.vc2:6375+`, editor generic de nota contabila, +partajat cu ROAGEST) si concluziona explicit: *"clasa exista deja, dar codul apelant din +ROAFACTURARE NU exista inca — trebuie scris"*. **Acel gol a fost umplut** — am gasit apelantul, +cablat si activ: + +`COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_editare_factura` (~linia 3690-3869): + +- Garda: blocheaza daca factura a fost trimisa in eFactura (`EsteInEFactura`, `:3764-3767`). +- `IncarcaCursoareModificareNota(lnCod, pnAn, pnLuna, .F.)` (helper din `ofacturare_editare.prg`, + citit dar nemodificat) incarca nota contabila completa (`vact_tot`/`vrul_tot`/`vrul_obinv_tot` + filtrate pe `cod`) in cursoarele READWRITE `actactan`/`tact`/`rul_temp`/`trul`/ + `rul_temp_obinv`/`trul_obinv` (`:3769`). +- `Omodif = Createobject([frm_modific2024], lnIdSet)` + `Omodif.Show()` (`:3796-3797`) — deschide + editorul modal pe cursorul `tact` deja incarcat cu nota facturii curente. +- Daca userul apasa Terminat (`buton = 1`, `:3799-3833`): deschide tranzactie manuala, sterge nota + veche (`OSCRIE_IN_FISIERE(2,.T.,.T.)`), rescrie din cursoarele editate (`tact`->`actactan`, + `trul`->`RUL_TEMP`, `trul_obinv`->`RUL_TEMP_OBINV`, cu `id_util`/`sters=0` noi), + `OSCRIE_IN_FISIERE(0,.T.,.T.)` (scriere noua), apoi + `pack_contafin.finalizeaza_modificare_nota(...)`, commit/rollback. + +Acest `finalizeaza_modificare_nota` (`PACK_CONTAFIN.pck:8601-8651`, citat deja in +`rec_modific2024.md`) **resincronizeaza `vanzari`** dupa editare: `SELECT COUNT(*) FROM vanzari +WHERE cod = tnCod; IF > 0 THEN pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou); END IF;`. + +### 1.3 Ce e efectiv editabil in `tact`/`trul` — verificat prin `do_modifica` + +`frm_modific2024.do_modifica` (`omodificari.vc2:13836-13957`) e mecanismul generic prin care un +camp din grid deschide un dialog de cautare (`cauta_alfa`) si apoi face `REPLACE` pe cursorul +corespunzator. Pentru `trul`/`trul_obinv` (nivel LINIE de nota, nu antet unic), `CASE` explicit +gestioneaza: + +``` +CASE m.lcControl = 'nrord' -> replace nrord with loCauta.nrord, id_lucrare with loCauta.id_lucrare +CASE m.lcControl = 'sectie' -> replace sectie with loCauta.sectie, id_valuta with loCauta.id_sectie (!) +CASE m.lcControl = 'nresp' -> replace nresp with loCauta.nume, id_responsabil with loCauta.id_responsabil +```//omodificari.vc2:13934-13943 + +Deci **ID_LUCRARE si ID_RESPONSABIL sunt editabile explicit**, pe cursorul `trul` (nivel de linie +RUL), prin acest mecanism, azi, din UI, prin `do_editare_factura`. Linia `sectie` are ce pare a fi +un bug preexistent (`replace ... id_valuta with loCauta.id_sectie` in loc de `id_sectie` — cod +existent, nu l-am atins, doar il semnalez ca observatie relevanta pentru evaluarea "functioneaza +sau nu in practica"): daca bug-ul e real, campul afisat `sectie` (text) se schimba, dar coloana +`id_sectie` propriu-zisa risca sa NU se actualizeze corect (se suprascrie `id_valuta` in loc). +Nu am testat comportamentul, doar am citit codul. + +**ID_VENCHELT**: NU exista un caz `CASE m.lcControl = 'venchelt'` (sau `dst_chlt`) in +`do_modifica` pentru `trul`/`trul_obinv` — cautat explicit in tot procedeul (13836-13957), zero +potriviri. Insa Grid1 al clasei (proprietatile de coloana, in afara metodelor) are o coloana cu +`ControlSource = "dst_chlt"` (`omodificari.vc2:753, 2862, 7393` — a treia aparitie e in intervalul +propriu al clasei `frm_modific2024`), deci explicatia venit/cheltuiala **e afisata** in grid. Daca +acea coloana permite editare directa de text (nu prin popup de cautare) sau daca exista un alt +handler (buton dedicat, dublu-click) care leaga `id_venchelt`, nu am verificat pana la capat — vezi +"Necunoscute ramase". **Raspuns pentru ID_VENCHELT: NU confirmat ca ruta activa, dar cu o rezerva +neinchisa** (grid-ul afiseaza coloana, mecanismul de editare exact al ei nu a fost trasat complet). + +### 1.4 Nivel LINIE vs. nivel ANTET + +O nuanta importanta: campurile din grupul A, la nivel Oracle, traiesc pe `ACT` (fiecare linie de +nota isi are propriile `ID_VENCHELT`/`ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE`), desi la EMITERE +se scriu uniform (aceeasi valoare pe toate liniile unui document, din variabilele de sesiune). +Editarea prin `frm_modific2024` e la nivel de LINIE (fiecare rand din `trul` se editeaza separat), +nu o singura bifa de antet — deci userul poate, teoretic, sa lase valori diferite pe linii diferite +ale aceleiasi facturi dupa editare, lucru care nu se putea intampla la emiterea initiala (uniforma). +Asta conteaza pentru orice raportare care presupune "un singur ID_SECTIE per factura". + +**Verdict grup A**: **DA** pentru `ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE` (cale noua, activa, +`frm_facturi.do_editare_factura` -> `frm_modific2024` -> `OSCRIE_IN_FISIERE` -> +`pack_contafin.finalizeaza_modificare_nota` -> Oracle `ACT`/`RUL`, cu resincronizare in `VANZARI`). +**NU confirmat** pentru `ID_VENCHELT` prin acelasi mecanism generic (`do_modifica` nu are caz +pentru el), dar cu rezerva de la 1.3 neinchisa complet. + +--- + +## 2. Grupul B — identitate/sursa (fdoc, valuta, zi curs, client, altele, gestiune sursa, +politica preturi) + +Controalele (`Ct_clb_fdoc`, `Ct_clb_valuta`, `Clb_zi_curs`, `Ct_clb_nume_client`, `Ct_clb_altele`, +`Ct_clb_gestiune_init`, `Ct_clb_politici_preturi`) au fost gasite **exclusiv** in metodele proprii +ale `frm_date_aviz`/`frm_date_factura`/`frm_date_aviz_lucrare` din `ofacturare.vc2` — acelasi +wizard de emitere de la grupul A (cautare `vfp_symbols.ps1 -Grep` pe toate cele 7 nume de +control, in tot proiectul indexat: 316 fisiere, zero potriviri in afara `ofacturare.vc2`). + +Cautat in Oracle (pachetul curent + `PACK_CONTAFIN.pck` + `PACK_UPDATE.pck` + `PACK_MIGRARE.pck`): +`UPDATE VANZARI ... SET (ID_FDOC|ID_VALUTA|ID_CLIENT|ID_PART|ID_GESTIN|ID_POL) = ...` si +`UPDATE ACT ... SET` idem — zero potriviri (grep multiline, fereastra 400 caractere dupa +`UPDATE`). Toate cele 13 aparitii ale `UPDATE VANZARI` din pachetul curent au fost inspectate +individual (`sterge_factura`, `sterge_proforma`, `marcheaza_facturat`, +`scrie_corespondente_vanzari`, plus `modifica_date_factura`) — niciuna nu atinge aceste coloane +(ating `STERS`/`FACTURAT`/`ID_UTILFACT`/`DATA_FACTURAT`/`AVIZE`/`COD`/serie-numar-data-scadenta). + +`frm_modific2024`/`do_editare_factura` (descoperirea de la grupul A) nu ajuta aici: cursoarele pe +care le editeaza (`tact`/`trul`/`trul_obinv`) sunt nota contabila (conturi, sume, gestiuni de +STOC, TVA) — nu contin fdoc/valuta document/client/politica de preturi ale facturii; acestea sunt +proprietati ale `VANZARI`, populate o singura data la `scrie_factura2`/`scrie_in_vanzari`, in afara +oricarui flux gasit de editare ulterioara. + +**Verdict grup B: NU**, pentru toate cele 7 campuri — cautat si negasit, in ambele straturi +(Oracle: pachetul de facturare curent + cele 3 pachete conexe; VFP: toate clasele indexate de +`vfp_symbols.ps1`, 1128 clase / 9281 metode / 2461 proceduri). + +--- + +## 3. Grupul C — incasarea + +### 3.1 Unde se scrie la emitere + +Controalele din `frm_alte_date` (`COMUN\clase\ferestre_cere_date.vc2:2219+`) — `opt_incasat`, +`Cb_casa`, `Clb_serie_chit`, `Clb_nrchit`, `Clb_incasat`, `chkPOS` — au `ControlSource` pe +proprietati ale obiectului `poDate` (`poDate.nIncasatPos`, si prin cod pe `poDate.incasat`, +`poDate.nr_incasare`, `poDate.ntip_incasare`, `poDate.id_casa`, `poDate.serie_chit`), NU pe un +cursor legat direct de tabel. `frm_alte_date` e instantiat exclusiv din +`frm_facturare_articole(2).inainte_de_do_termin` si din doua fluxuri de listare +(`ofacturare.prg:listare_protocol`, `ofacturare_stoc.prg:oscrie_vanzare_din_stoc`, +`oproceduri_listari.prg:listare_protocol`) — toate parti ale fluxului de EMITERE, niciuna de +editare ulterioara. + +La `do_scrie_factura` (`ofacturare.vc2:14260-14389`, ambele forme `frm_facturare_articole` si +`frm_facturare_articole2`), valorile din `poDate` sunt asamblate intr-un string +`lcListaIncasare` (format `tip|suma|id_casa;...`) si trimise ca parametru la +`pack_facturare.scrie_factura2` / `scrie_factura_avize` / `scrie_proforma` (apel RPC unic, la +emitere). In Oracle, `scrie_factura2` cheama intern `pack_facturare.scrie_incasari` (linii 6204, +7034 din pachetul curent), care parseaza lista si cheama `scrie_incasare2` per linie +(`:13096-13168` -> `:13170+`) — aceasta insereaza o **nota contabila noua in `ACT_TEMP`** +(coloane vazute: `ACT.SUMA`, `ACT.ID_PARTD` pentru casa/banca, `ACT.ASCC`/`SCD`/`ASCD`, +`ACT_TEMP.PAYMENTCODE`), adica incasarea devine ea insasi o linie de jurnal (cont 5311/5121 etc. +vs. 4111), scrisa prin acelasi flux `ACT_TEMP -> ACT` ca restul documentului, nu un camp separat +pe `VANZARI`. `scrie_incasare2` nu are niciun apelant in afara lui `scrie_incasari` (cautat cu +grep in tot pachetul de facturare — un singur call-site). + +### 3.2 Cale de schimbare DUPA emitere + +Nu exista nicio procedura Oracle de tip "modifica_incasare"/"corecteaza_incasare" (cautat +`PROCEDURE ... (modifica|corecteaza|actualizeaza)...incasa...` in pachetul curent si +`PACK_CONTAFIN.pck` — zero potriviri). `scrie_incasari`/`scrie_incasare2` nu au niciun apelant VFP +direct (cautat in tot proiectul indexat — zero potriviri) — sunt folosite doar intern, o singura +data, la emitere. + +**PARTIAL, neconfirmat pana la capat**: incasarea scrisa la emitere e o linie de nota contabila +(`ACT`/`RUL`) ca oricare alta. Daca acea linie foloseste acelasi `cod` (acelasi document contabil) +ca restul facturii, atunci mecanismul nou de la Grupul A (`frm_facturi.do_editare_factura` -> +`frm_modific2024`) ar incarca-o si pe ea in grid — si, editand campurile generice de nota +(cont/gestiune/suma, prin acelasi `do_modifica` sau direct in grid), un utilizator ar putea +schimba indirect suma/contul incasarii, deci si `casa`/`suma incasata` efectiva. **Nu am confirmat +daca incasarea primeste acelasi `cod` sau un `cod` separat** — ar necesita fie testare live +(interzisa aici, doar cercetare pe cod), fie citirea completa a insertiei din `scrie_incasare2` +(am citit doar semnatura si primele ~35 linii, nu INSERT-ul propriu-zis in `ACT_TEMP`). Marchez +explicit ca necunoscuta ramasa, nu ca "DA" confirmat. + +`opt_incasat`/`chkPOS`/serie-numar chitanta/bon **ca atare** (proprietati `poDate` folosite doar +pentru alocarea de numere de serie si generarea listei `lcListaIncasare`) nu au niciun camp +persistent separat pe care sa-l poata atinge editarea ulterioara a notei — nu exista coloane +`VANZARI.OPT_INCASAT`/`VANZARI.SERIE_CHIT` in tot ce am cautat. + +**Verdict grup C**: NU pentru o cale directa/documentata de schimbare dupa emitere; PARTIAL, +neconfirmat, prin editarea generica a notei contabile (acelasi mecanism nou de la Grupul A), +DACA incasarea partajeaza `cod`-ul cu restul documentului. + +--- + +## Ce am cautat (pentru un "NU" verificabil) + +- **Oracle**: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` + (17020 linii, pachetul curent) — grep pe `UPDATE VANZARI`, `UPDATE ACT`, `UPDATE DOCUMENTE` (toate + aparitiile citite in context), plus grep combinat `UPDATE ... SET =` (fereastra + multiline 300-400 caractere) pentru fiecare coloana din grupele A/B/C. Idem pe + `D:\ROA\ROAFACTURARE\COMUN\docs\PACK_CONTAFIN.pck` (9041 linii), `PACK_UPDATE.pck` (2420 linii), + `PACK_MIGRARE.pck` (407 linii). +- **VFP**: `vfp_symbols.ps1 -Grep -CodeOnly` (index complet: 316 fisiere, 1128 clase, 9281 metode, + 2461 proceduri) pentru fiecare nume de coloana Oracle si fiecare nume de control VFP din enunt + (`Ct_clb_venchelt/sectie/responsabil/lucrare/fdoc/valuta/altele/gestiune_init/politici_preturi`, + `Clb_zi_curs`, `opt_incasat`, `Cb_casa`, `Clb_serie_chit`, `Clb_nrchit`, `Clb_incasat`, `chkPOS`). + Pentru fiecare formular gasit (`frm_date_factura`, `frm_date_aviz`, `frm_alte_date`), am cautat + toti apelantii (`Createobject("...")`) in tot proiectul indexat, ca sa confirm ca sunt doar pe + fluxul de emitere. +- **Citire, fara scriere**: `ofacturare_comun.vc2` (metoda `do_editare_factura`, ~3690-3869) si + `ofacturare_editare.prg` (integral, 457 linii) — apartin altui fir de lucru, doar citite. +- `omodificari.vc2` (16464 linii) — clasa `frm_modific2024`: citite integral `do_modifica` + (13836-13957) si `do_salvare` (13985-14042); NU am citit toate cele ~180 de metode ale clasei + (Grid1.Column*, pgfArticole.*) — posibil sa existe alte cai de editare directa in grid, + nedescoperite prin `do_modifica`. + +## Necunoscute ramase + +1. **ID_VENCHELT** (grup A): daca e editabil in grid-ul `frm_modific2024` prin alt mecanism decat + `do_modifica` (coloana `dst_chlt` exista in grid) — neverificat pana la capat. +2. **Incasarea pe acelasi `cod`** (grup C): daca linia de incasare scrisa de `scrie_incasare2` + foloseste acelasi `cod` ca restul facturii (ceea ce ar face-o editabila prin + `do_editare_factura`) — necesita citirea INSERT-ului efectiv in `ACT_TEMP` din + `scrie_incasare2` (nu doar semnatura, citita aici doar partial) sau verificare pe date reale. +3. **Bug-ul de la `sectie`** (`omodificari.vc2:13941`, `id_valuta with loCauta.id_sectie` in loc de + `id_sectie`) — semnalat ca observatie, nu investigat mai departe (nu face parte din intrebare, + dar afecteaza increderea in verdictul "DA" pentru `ID_SECTIE": campul e teoretic editabil, dar + codul care il scrie pare sa aiba un bug care ar putea sa nu-l actualizeze corect in practica). diff --git a/docs/cercetare/s10_pret_contract_reemitere.md b/docs/cercetare/s10_pret_contract_reemitere.md new file mode 100644 index 0000000..7fb8183 --- /dev/null +++ b/docs/cercetare/s10_pret_contract_reemitere.md @@ -0,0 +1,468 @@ +# S10 (runda 3) — Proiectare: pretul re-derivat din contract la reemiterea #13 + +Agent de proiectare (research), read-only. Nicio editare de cod, nicio scriere Oracle in afara de +`SELECT`. Sursa SQL: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` +(17217 linii — **de data asta liniile citate coincid exact cu cele din raportul vechi +`s10_pret_rederivat.md`, fara offset**). Date verificate pe `MARIUSM_AUTO@ROA_CENTRAL`, doar +`SELECT`, 11.08.2026 — baza de dezvoltare, esantion mic (vezi sectiunea 2), nu productie. + +## 0. Rezumat + +**Intrebare adaugata de team-lead, raspuns scurt: NU.** `cursor_retur_document` (procedura folosita +la INCARCAREA liniilor unui document existent, apelata din VFP cu `V_COPIERE=1`) **citeste pretul +ca atare din `VANZARI_DETALII.PRET`, nu il re-deriva** — nu exista niciun `JOIN` catre +`CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART` in toata procedura (`:3949-4062`). Presupunerea din S8b se +confirma pe cod. Detaliu complet, cu dovezi, in sectiunea 1bis (nou adaugata, tratata ca prioritara +fata de restul raportului, conform cererii). + +**Faptul portant (scrierea, nu citirea) se reconfirma integral** (`:5146-5185`) si se extinde cu +doua descoperiri noi: + +1. **TVA-ul si identitatea valutei sunt re-derivate din exact acelasi `JOIN`/`SELECT` ca pretul**, + pe aceeasi ramura de contract — nu doar pretul e in pericol la reemitere, ci pretul + cota de + TVA + valuta articolului, impreuna, tot-sau-nimic (sectiunea 3). +2. **Nu exista nicio a doua re-derivare mai departe in lant** — `PRET` trece neschimbat de la + `VANZARI_DETALII_TEMP` in `VANZARI_DETALII` (`scrie_in_vanzari`, `:13705-13757`, coloana `PRET` + copiata direct, fara `DECODE`/recalcul) si `contabilizeaza_articol` nu scrie niciodata pe + `VANZARI_DETALII.PRET` (scrie doar `ACT_TEMP`, nota contabila). Singurul loc de re-derivare e + `adauga_articol_factura:5146-5185` (sectiunea 1) — **la scriere, niciodata la citire** (sectiunea + 1bis). + +Pe date reale (esantion mic, baza de dezvoltare): **3 din 11 linii deja facturate pe contract au +azi un pret de contract diferit de pretul efectiv facturat** — divergenta nu e ipotetica, e deja +prezenta (sectiunea 2). + +**Recomandare** (detaliata in sectiunea 4): varianta **(c) avertizare + confirmare explicita**, +implementabila integral in VFP cu `goExecutor` (fara sa ating `pack_facturare`, conform deciziei +27-bis), cu optiunea de a trece la **(b) blocare stricta** daca Marius prefera zero schimbari +tacite vreodata. Varianta **(d) ocolire a ramurii** e posibila mecanic dar produce o regresie reala +(pierderea legaturii `VANZARI_DETALII.ID_CTR`) — **nu o recomand**. + +--- + +## 1. Reverificarea faptului portant + +Cod citit direct din sursa (`:5039-5220`), nu preluat din raportul vechi. + +```sql +-- :5039-5050 — V_OPT_FACTURARE se calculeaza intern, din V_ID_CTR primit de la VFP +IF pack_facturare.ntip IN (2, 6, 26, 52) THEN + BEGIN + SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR; + EXCEPTION + WHEN NO_DATA_FOUND THEN + V_OPT_FACTURARE := 4; + END; +END IF; +``` + +```sql +-- :5146-5185 — ramura de contract, in interiorul CASE-ului principal (:5052-5220) +WHEN V_OPT_FACTURARE = 3 THEN + BEGIN + SELECT DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR), + B.PROC_TVAV, + B.ID_VALUTA, + A.PRET_CU_TVA, + C.IN_STOC + INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC + FROM CTR_ARTICOLE A + LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL_ART = B.ID_POL_ART + LEFT JOIN NOM_ARTICOLE C ON B.ID_ARTICOL = C.ID_ARTICOL + WHERE B.ID_ARTICOL = V_ID_ARTICOL AND A.ID_CTR = V_ID_CTR AND B.ID_POL = V_ID_POL; + EXCEPTION + WHEN NO_DATA_FOUND THEN + ... V_PRET := V_PRET_TEMP; V_ID_VALUTA := V_ID_VALUTA_TEMP; V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP; V_IN_STOC := V_IN_STOC_TEMP; + END; +``` + +**Confirmat, cuvant cu cuvant fata de runda 9**: cu `OPT_FACTURARE = 3` (sau `NULL`, implicit 3), +daca `CTR_ARTICOLE.PRET_UNITAR <> 0`, acea valoare **inlocuieste** `V_PRET_TEMP` (pretul trimis de +VFP), necondiționat de faptul ca operatorul a atins sau nu linia. Procedura **nu are nicio notiune +de reemitere** — parametrii sunt toti `IN` (`:4989-5059`, verificat), niciun canal care sa spuna +"nu recalcula". `NO_DATA_FOUND` (politica/articol lipsa) e singurul caz care respecta pretul +trimis. + +### Nu exista alt loc care suprascrie pretul + +- **`scrie_in_vanzari`** (`:13488+`), care muta liniile din `VANZARI_DETALII_TEMP` in + `VANZARI_DETALII` (tabela finala) la finalizarea documentului: `PRET` e in lista de coloane + copiate **neschimbat** (`:13705-13757`, `SELECT ... PRET ... FROM VANZARI_DETALII_TEMP` → + `INSERT INTO VANZARI_DETALII (..., PRET, ...)`), fara `DECODE`/recalcul. Cautare exhaustiva pe + fisier: **zero** `INSERT INTO VANZARI_DETALII` (tabela finala, nu `_TEMP`) in afara de acest + punct. +- **`contabilizeaza_articol`** (`:7173-7547`, reconfirmat structural fata de runda 9) citeste + `detalii_articol.pret` (randul deja scris in `VANZARI_DETALII_TEMP`) doar ca sa calculeze suma + notei contabile — nu scrie niciodata inapoi pe `VANZARI_DETALII.PRET`. +- **`modificare_politica_stoc`** (`:2122-2135`) face un `UPDATE CRM_POLITICI_PRET_ART SET PRET=0, + ...` — e singurul alt `SET PRET` gasit in tot fisierul, dar reseteaza **politica** la schimbarea + monedei stocului, complet in afara lantului de facturare a unei linii; nu atinge vreo linie de + document. + +**Concluzie sectiune**: raspunsul din runda 9 se confirma **integral**, fara nicio corectie, si se +inchide explicit intrebarea "mai exista si alte locuri" — nu mai exista. + +--- + +## 1bis. Citirea la incarcare: `cursor_retur_document` re-deriva pretul? + +**Adaugat la cererea team-lead-ului — critic pentru S8b, tratat ca prioritar.** + +### Verdict: NU. Pretul se citeste ca atare din `VANZARI_DETALII.PRET`, fara nicio re-derivare. + +Corp complet, `PACK_FACTURARE:3949-4062`: + +```sql +PROCEDURE cursor_retur_document(V_IN_VALUTA IN NUMBER, + V_LISTAID IN VARCHAR2, + V_COPIERE IN NUMBER, + V_PROFORMA IN NUMBER, + V_ID_UTIL IN NUMBER, + V_CURSOR OUT cursor_facturare) IS +... +BEGIN + pack_facturare.initializeaza_facturare(V_ID_UTIL); + + OPEN V_CURSOR FOR + WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR))) + SELECT ROWNUM AS ID_C, A.ID_ARTICOL, A.LOT, A.SERIE, A.ID_POL, A.ID_VALUTA, + ... DISCOUNT_UNITAR (derivat din A.DISCOUNT_UNITAR) ..., + B.CODMAT, B.CODBARE, B.DENUMIRE, NVL(B.UM,'') AS UM, + ... GESTIONABIL ..., A.CANTITATE, A.PROC_TVAV, A.ID_JTVA_COLOANA, + A.PRET_CU_TVA AS PRETURI_CU_TVA, A.CURS, A.MULTIPLICATOR, + (CASE WHEN NVL(C.MONEDA_NATIONALA,1)<>1 + THEN ROUND(A.CURS * ROUND(A.PRET, pack_sesiune.nzecimale_pretvval) / NVL(A.MULTIPLICATOR,1), pack_sesiune.nzecimale_pretv) + ELSE ROUND(A.PRET, pack_sesiune.nzecimale_pretv) + END) + A.DIFERENTA AS PRET, + ... + FROM (SELECT A1.ID_ARTICOL, A1.LOT, A1.SERIE, A1.ID_POL, A1.DISCOUNT_UNITAR, A1.DIFERENTA, + A1.CANTITATE, A1.PROC_TVAV, A1.ID_JTVA_COLOANA, + NVL2(A1.ID_GESTIUNE,1,0) AS GESTIONABIL, A1.PRET_CU_TVA, A1.PRET, A1.ID_VALUTA, + NVL(A2.CURS,0) AS CURS, NVL(A2.MULTIPLICATOR,1) AS MULTIPLICATOR, + A1.EXPLICATIE, A1.ID_GESTIUNE, A1.PRET_ACHIZITIE, A1.PRETD, A1.ID_JTVA_COLOANA_EX + FROM VANZARI_DETALII A1 + LEFT JOIN VANZARI_CURSURI A2 ON A1.ID_VANZARE = A2.ID_VANZARE AND A1.ID_VALUTA = A2.ID_VALUTA + WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)) A + LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL + LEFT JOIN NOM_VALUTE C ON A.ID_VALUTA = C.ID_VALUTA + ORDER BY B.DENUMIRE; +END cursor_retur_document; +``` + +**Analiza surselor, coloana cu coloana:** + +- **`A.PRET`** (`:4009-4015`, alias final `PRET`) vine din subquery-ul `A` care e + **`VANZARI_DETALII A1`** direct (`A1.PRET`, `:4041`) — **nicio referinta la + `CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART`/`CRM_POLITICI_PRETURI` in toata procedura** (cautare + exhaustiva pe corpul de la `:3949-4062`: zero hit-uri pentru oricare din cele trei tabele). + Singurele `JOIN`-uri sunt: + - **`VANZARI_CURSURI A2`** (`:4051-4053`), pe `ID_VANZARE`+`ID_VALUTA` — aduce `CURS`/ + `MULTIPLICATOR` **stocate pe documentul insusi** (cursul valutar de la momentul cand a fost + scris, nu un curs "de azi" recalculat — tabela `VANZARI_CURSURI`, nu `CURS`). + - **`NOM_ARTICOLE B`** (`:4056-4057`) — doar campuri descriptive (`CODMAT`, `CODBARE`, + `DENUMIRE`, `UM`), acelasi tipar confirmat deja in `rec_pret_lazy.md` A2 pentru alte cursoare + din pachet — niciodata sursa de pret. + - **`NOM_VALUTE C`** (`:4058-4059`) — doar `MONEDA_NATIONALA`/`NUME_VAL`, folosit in `CASE` ca sa + decida **cum se formateaza** afisarea (lei vs. valuta), nu ca sa aduca un pret alternativ. + - Expresia finala pe `PRET` e o **transformare de afisare** a lui `A.PRET` (rotunjire + + conversie in valuta folosind `A.CURS`/`A.MULTIPLICATOR` **stocate pe document**) plus + `A.DIFERENTA` (coloana proprie pe `VANZARI_DETALII`, un ajustaj deja persistat pe linie, nu o + recalculare live) — nu o re-derivare dintr-o sursa externa. +- **Acelasi tipar pe `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`** — toate citite + direct din `A1.*` (`VANZARI_DETALII`), fara vreun `JOIN` catre politica/contract. + +**Confirmare suplimentara pe partea VFP — `cursor_retur_document` cu `V_COPIERE=1` e chiar calea +folosita la reincarcarea unui document existent, cu prioritate fata de ramura de contract**: + +``` +COMUN\programe\ofacturare.prg:266-283 +Do Case + Case m.llCopiere + lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}] + Case Inlist(tnTip, 48, 49) + ... + Case tnTip = 45 + ... + Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi + ... + Case Inlist(tnTip, 2, 26, 6, 52) && contract + lcSqlCursor = [{call ]+gcS+[.pack_facturare.cursor_contract(...)}] +``` + +`Do Case` in VFP executa **doar prima ramura adevarata**. Cand `llCopiere` e activ (incarcarea unui +document existent — exact scenariul "editare prin regenerare" descris de team-lead), procedura +apelata e **intotdeauna** `cursor_retur_document`, **indiferent de `tnTip`** — ramura de contract +(`cursor_contract`, `:283+`) nu se mai ajunge deloc, chiar daca documentul e de tip 2/6/26/52. +`cursor_retur` (`:3934-3947`) e doar un wrapper subtire peste `cursor_retur_document` cu +`V_COPIERE=0`/`V_PROFORMA=0`, pentru returul propriu-zis — nu schimba concluzia. + +### Consecinta pentru S8b + +**Presupunerea "re-derivarea e o proprietate a scrierii, nu a citirii" se confirma pe cod, fara +rezerve.** Deschiderea formularului de editare (etapa II, incarcarea liniilor existente) **nu** +declanseaza nicio re-derivare de pret/TVA/valuta — documentul apare "neschimbat" la simpla +deschidere, exact cum a presupus S8b. Mecanismul de detectare a schimbarii (compara starea +incarcata cu starea la confirmare) **poate functiona ca premisa** — riscul de suprascriere tacita +descris in restul acestui raport (sectiunile 1-6) apare **doar la re-scriere** (regenerare efectiva +prin `adauga_articol_factura`), nu la incarcare. Cele doua momente (citire la deschidere, scriere +la regenerare) sunt guvernate de **cursoare Oracle complet diferite** (`cursor_retur_document` vs. +`adauga_articol_factura`), fara cod comun — o modificare facuta doar pe unul din ele (de exemplu o +garda adaugata la scriere, variantele (b)/(c) din sectiunea 4) **nu afecteaza** comportamentul +celuilalt. + +--- + +## 2. Tipurile afectate si frecventa in date + +**Tipurile de document**: `pack_facturare.ntip IN (2, 6, 26, 52)` (`:5039`), confirmat identic cu +tabelul din `rec_editare_factura.md:176` (contract = tip 2/6/26/52, ramura de scriere +`scrie_factura2`, la fel ca lista de preturi — nu are RPC separat). + +**Frecventa in date** (schema `MARIUSM_AUTO`, doar `SELECT`, 11.08.2026): + +```sql +-- VANZARI_DETALII cu ID_CTR populat, active: 74 +-- din care, articolul are un rand corespunzator in CTR_ARTICOLE (id_ctr+id_articol): 11 +-- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI diferit de VANZARI_DETALII.PRET: 3 +-- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI egal cu VANZARI_DETALII.PRET: 8 +-- din acelea, CTR_ARTICOLE.PRET_UNITAR = 0 (nu suprascrie): 0 +``` + +**Interpretare, cu grija la marimea esantionului**: baza e de dezvoltare, cu doar 27 randuri totale +in `CTR_ARTICOLE` — nu extrapolez procentul (27%) la volumul de productie. Dar **3 cazuri reale, +nu zero**, e dovada suficienta ca fenomenul **chiar se intampla**, nu doar teoretic: daca oricare +din cele 3 facturi ar fi reemisa azi prin regenerare (#13 etapa II), suma ei s-ar schimba tacit, +fara nicio actiune a operatorului legata de pret. Simetric, cele 8 cazuri "egal" arata ca in +majoritatea situatiilor curente reemiterea ar fi azi inofensiva — ceea ce face problema mai +insidioasa, nu mai putin reala: cand se produce, nimeni nu se astepta, pentru ca de obicei nu se +intampla nimic vizibil. + +**Nu exista coloana de audit pe `CTR_ARTICOLE`** (confirmat din `user_tab_columns`: `ID_CTR_ART, +ID_CTR, ID_POL_ART, PRET_UNITAR, CANT, COEF_DISCOUNT, VAL_DISCOUNT, ID_VALUTA, EXPLICATIE, UM, +ID_ARTICOL, PRET_CU_TVA, ID_LOCATIA, PROC_TVAV, VALOARE` — nicio `DATA_MODIF`/`ID_UTIL_MODIF`) — +nu exista cale de a masura *cat de des* se schimba `PRET_UNITAR` in timp, doar *cate cazuri +divergente exista azi*. Raspunsul la "cat de des" ramane nedeterminabil din date; ce se poate +spune e doar ca divergenta **exista deja**, azi, pe un esantion mic. + +--- + +## 3. Descoperire noua: TVA si valuta sunt re-derivate din **aceeasi** interogare ca pretul + +Din citatul de la sectiunea 1: `SELECT DECODE(A.PRET_UNITAR, ...), B.PROC_TVAV, B.ID_VALUTA, ...` +— **un singur `SELECT`**, trei valori. Asta inseamna ca varianta de raspuns nu poate trata pretul +izolat: daca politica de pret a contractului (`CRM_POLITICI_PRET_ART`, aceeasi `B`) si-a schimbat +si cota de TVA sau valuta intre timp, reemiterea le suprascrie **pe amandoua**, prin acelasi +mecanism, in aceeasi conditie (`NO_DATA_FOUND` → respecta ce a trimis VFP; altfel → suprascrie). + +Comparativ cu discountul si cursul (reconfirmare fata de runda 9, sectiunile 6-7 ale raportului +vechi, verificate din nou pe sursa curenta): + +| Camp | Tratament pe ramura de contract | Risc la reemitere | +|---|---|---| +| **Pret** (`V_PRET`) | `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)` — suprascris daca `PRET_UNITAR<>0` | **Da — faptul portant** | +| **TVA** (`V_PROC_TVAV`) | `B.PROC_TVAV`, din acelasi `SELECT`, fara fallback conditionat separat | **Da — aceeasi conditie ca pretul** | +| **Valuta articol** (`V_ID_VALUTA`) | `B.ID_VALUTA`, idem | **Da — aceeasi conditie** | +| **Discount** (`V_DISCOUNT_UNITAR`) | Parametru trimis de VFP, scris direct in `INSERT` (`:5266`), nicio ramura din `CASE` il citeste | **Nu — mereu respectat, indiferent de ramura** | +| **Curs** (`V_CURS`) | `DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste doar 0 cu 1 | **Nu direct — dar vezi mai jos** | + +**Consecinta combinata pret+valuta+curs**: daca `V_ID_VALUTA` re-derivat difera de valuta pentru +care VFP a calculat `V_CURS` (de ex. politica a trecut de la EUR la USD intre emitere si +reemitere), documentul reemis scrie **noua valuta cu vechiul curs trimis de VFP** — nicio +validare incrucisata intre cele doua (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). Aceeasi +observatie ca in runda 9, dar acum cu dovada ca sursa comuna (pret+TVA+valuta) inseamna ca **orice +gardă construita pentru pret trebuie sa verifice si TVA si valuta in acelasi pas**, nu doar pretul +— altfel o factura reemisa poate trece garda de pret nemodificata si totusi sa iasa cu alta cota +de TVA sau alta valuta. + +**Discountul nu ridica aceeasi problema** — e mereu respectat ca atare, indiferent de ramura, +deci nu are nevoie de nicio garda la reemitere. + +--- + +## 4. Variantele de raspuns + +Toate patru presupun ca infrastructura de "sterge + reemite" din #13 etapa II trimite, pentru +liniile de pe un document-contract, acelasi `V_ID_CTR` care era pe linia originala (asta e +premisa explicita a sarcinii: "acelasi drum ca la emitere" — daca `V_ID_CTR` nu s-ar retrimite, +linia nici n-ar mai fi "de pe contract"). + +### (a) Se accepta re-derivarea — nicio garda + +**Ce se face**: nimic — regenerarea apeleaza `adauga_articol_factura` exact ca la emitere, cu +acelasi `V_ID_CTR`. Comportamentul existent (sectiunea 1) se aplica neschimbat. + +**Ce se strica**: criteriul din plan ("reemitere identica produce exact aceleasi sume" — vezi +sectiunea 6) **esueaza silentios** pe orice linie de contract al carei pret/TVA/valuta a fost +modificat intre timp. Dovedit sa se intample azi, pe date reale (sectiunea 2, 3 din 11). Utilizatorul +care corecteaza o cantitate pe o factura veche poate vedea, fara sa i se spuna, totalul facturii +schimbat pentru un articol la care nici nu s-a uitat. + +**Cod atins**: zero. **Regresie pe emiterea normala**: zero (comportamentul de azi ramane +identic). + +### (b) Se blocheaza reemiterea cand pretul curent difera + +**Ce se face**: inainte de a porni stergerea+regenerarea, VFP ruleaza un `SELECT` (prin +`goExecutor`, exact tiparul deja folosit in `ofacturare.prg` pentru alte verificari punctuale — +`goExecutor.oSelecteaza2Value(...)`, `:2628`, `:2661`) care compara, pentru fiecare linie a +documentului cu `ID_CTR` populat, pretul/TVA/valuta stocate pe linie +(`VANZARI_DETALII.PRET`/`PROC_TVAV`/`ID_VALUTA`) fata de ce ar recalcula azi ramura de contract +(`CTR_ARTICOLE.PRET_UNITAR`/`CRM_POLITICI_PRET_ART.PROC_TVAV`/`.ID_VALUTA`, acelasi `JOIN` ca in +sectiunea 1, dar rulat direct din VFP, fara sa ating `pack_facturare`). Daca gaseste macar o +divergenta, **blocheaza regenerarea** cu mesaj ("Pretul de pe contract s-a schimbat pentru +articolul X: -> . Actualizati contractul sau anulati regenerarea.") si nu porneste +deloc stergerea. + +**Ce se strica**: orice regenerare pe un document cu **cel putin o linie** de contract cu pret +schimbat e blocata **integral**, chiar daca utilizatorul voia sa corecteze cu totul altceva +(ruta, o cantitate pe alta linie). Nu exista cale de a "accepta noul pret si continua" fara o a +doua interactiune — varianta (c) rezolva exact asta. + +**Cod atins**: doar VFP (o interogare noua, read-only, plus un dialog de blocare), inaintea +apelului la infrastructura de stergere+regenerare din #13. **Zero atingere `pack_facturare`** +(decizia 27-bis respectata). **Regresie pe emiterea normala**: zero — garda ruleaza doar pe +calea de regenerare, nu la emiterea unui document nou. + +### (c) Se avertizeaza si se cere confirmare, cu diferenta afisata + +**Ce se face**: aceeasi interogare de detectie ca la (b), dar in loc sa blocheze, afiseaza un +dialog cu lista liniilor divergente si delta (pret vechi vs. nou, si TVA/valuta daca difera — +vezi sectiunea 3, toate trei trebuie aratate, nu doar pretul) si cere confirmare explicita +("Continuati cu noile preturi de pe contract?" Da/Nu). La "Nu", regenerarea se opreste ca la (b). +La "Da", regenerarea continua normal si ramura de contract face exact ce face azi (suprascrie). + +**Ce se strica**: nu opreste suprascrierea insasi — doar o face **vizibila si asumata** inainte +sa se produca, exact criteriul cerut de plan ("un rezultat explicit decis, nu accidental"). Nu +ofera o cale de a **pastra** vechiul pret in timp ce se accepta restul modificarii — pentru asta +ar trebui combinata cu (d), cu costul ei (vezi mai jos). Risc de "warning fatigue": daca +divergenta e frecventa in productie (nu se poate masura azi, sectiunea 2), utilizatorii pot invata +sa dea click pe "Da" fara sa citeasca. + +**Cod atins**: identic cu (b) plus un dialog cu doua butoane in loc de unul singur. **Regresie pe +emiterea normala**: zero. + +### (d) Se ocoleste ramura — reemiterea trimite pretul documentului pe o cale care nu trece prin `DECODE` + +**Posibil mecanic, cu un cost real.** Ramura de contract se selecteaza din doua conditii, ambele +in afara controlului direct al apelantului per-linie: + +1. `pack_facturare.ntip IN (2,6,26,52)` — variabila **de sesiune** (`:1882`, + `pack_facturare.ntip := V_TIP`), setata o singura data la initializarea documentului (tipul + documentului insusi), nu per linie. A o schimba ar insemna sa reclasifici tot documentul + (afecteaza si alte ramuri `CASE` care depind de `ntip` — `:1905`, `:5053`, `:6056-6124`, + `:14820` — cu efecte necunoscute si necontrolate) — **contrazice premisa "acelasi drum ca la + emitere"** data explicit in sarcina. Nu recomand aceasta sub-varianta. +2. `V_ID_CTR` — parametru **per linie**, trimis explicit de VFP la fiecare apel + `adauga_articol_factura`. Daca VFP trimite `V_ID_CTR = NULL` pentru o linie la reemitere, + blocul de la `:5039-5050` gaseste `NO_DATA_FOUND` (cautarea `WHERE ID_CTR = NULL` nu potriveste + nimic) → `V_OPT_FACTURARE := 4` → ramura `WHEN V_OPT_FACTURARE = 3` nu se mai potriveste → + cade pe `ELSE` (`:5187-5203`) → `V_PRET := V_PRET_TEMP` (pretul documentului, respectat ca + atare, la fel TVA prin `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA`). + + **Costul**: `V_ID_CTR` e si coloana scrisa in `VANZARI_DETALII_TEMP`/`VANZARI_DETALII.ID_CTR` + (`:5280`, copiata neschimbat mai departe la `:13730`/`:13755`). Trimitand `NULL` la reemitere, + linia **pierde definitiv legatura cu contractul** in tabela finala — orice raport care + grupeaza/filtreaza pe `VANZARI_DETALII.ID_CTR` (regasire vanzari pe contract, situatii + contractuale) nu va mai gasi acea linie dupa reemitere, desi articolul a fost, de fapt, livrat + pe acel contract. E o regresie permanenta si greu de observat (nu da eroare, doar dispare + tacit dintr-un raport), pe un camp folosit azi activ (`rec_pret_lazy.md`/`idpol_comanda_contract.md` + confirma ca legatura cu contractul e cheie de raportare in tot lantul `PACK_FACTURARE`). + In plus, o data pierduta legatura, **o a doua reemitere ulterioara nu ar mai putea re-deriva + pretul din contract nici daca s-ar dori** — comutarea e ireversibila per linie, nu un flag + comutabil. + +**Nu recomand varianta (d)** — costul (pierderea trasabilitatii contract-linie, permanenta) e mai +mare decat problema pe care o rezolva, si nici macar nu rezolva complet criteriul "reemitere +identica" (rezolva doar pretul/TVA/valuta acelei linii, cu pretul unei regresii pe alt camp). + +--- + +## 5. Discount, TVA, curs valutar — verificare fata de sectiunile 6-7 din raportul vechi + +Deja tratat integral in sectiunea 3 de mai sus (descoperirea principala a acestei runde). Rezumat +scurt pentru trasabilitate fata de cererea explicita: + +- **Discountul** (sectiunea 6 raport vechi): reconfirmat — niciodata re-derivat, indiferent de + ramura. **Nu ridica problema de reemitere.** +- **TVA-ul** (sectiunea 6 raport vechi): reconfirmat ca "mereu recalculat, din surse diferite pe + ramura" — dar cu precizarea noua ca pe ramura de contract, sursa **coincide exact** cu sursa + pretului (acelasi `SELECT`, aceeasi conditie `NO_DATA_FOUND`). **Ridica aceeasi problema ca + pretul, prin acelasi mecanism** — orice varianta (b)/(c) trebuie sa acopere si TVA, nu doar pret. +- **Cursul valutar** (sectiunea 7 raport vechi): reconfirmat — `V_CURS` mereu respectat ca atare + (`DECODE(V_CURS,0,1,V_CURS)`), nu se re-deriva direct. **Dar** identitatea valutei + (`V_ID_VALUTA`) se re-deriva pe aceeasi ramura de contract, din acelasi `SELECT` — daca politica + a schimbat valuta intre timp, reemiterea scrie noua valuta cu vechiul curs trimis de VFP, fara + nicio validare incrucisata (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). **Ridica aceeasi + problema, prin acelasi mecanism, si trebuie acoperita de aceeasi garda.** + +--- + +## 6. Cazul "reemitere identica" + +Criteriul din plan (S12) cere ca o reemitere fara nicio modificare intentionata sa produca +**exact** aceleasi sume. Verificat pe fiecare ramura a `CASE`-ului de la `:5052-5220` (nu doar +ramura de contract): + +| Ramura (`ntip`) | Garantat identic la reemitere? | De ce | +|---|---|---| +| `ELSE` (lista de preturi, fara contract) | **Da, cu o rezerva minora** | `V_PRET := V_PRET_TEMP` — passthrough direct (`:5200`). TVA vine din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` (`:5188-5198`) — identic **doar daca** acea coloana de TVA nu a fost ea insasi editata intre timp (posibil, dar alt tip de risc, nu cel cerut de aceasta sarcina). | +| `45` (restaurant) | **Da, cu aceeasi rezerva** | `V_PRET := V_PRET_TEMP` setat **inainte** de orice `SELECT` (`:5109`) — respectat necondiționat. TVA vine tot din `JTVA_COLOANE` (`:5114-5139`), aceeasi rezerva ca mai sus. | +| `3,21,28,42,47` (comenzi) | **Nu garantat, dar esueaza tare, nu tacit** | `SELECT ... WHERE A.PRET = V_PRET_TEMP` (`:5077`) — daca pretul din comanda-sursa nu mai coincide exact cu ce trimite VFP, **`NO_DATA_FOUND` netratat** → eroare Oracle bruta, regenerarea pica integral, nu produce o suma gresita silentios. Diferit structural de ramura de contract. | +| `4` (aviz) | **Nu garantat, silentios** | `SELECT DISTINCT A.PRET ... FROM VANZARI_DETALII` (`:5082-5103`), **fara filtru pe pret** — re-citeste pretul curent al liniei din avizul-sursa necondiționat. Daca avizul insusi a fost modificat intre emiterea initiala si reemitere, sau daca exista mai multe randuri candidate (`DISTINCT` pe o potrivire ambigua), reemiterea poate lua alt pret, tacit — **acelasi tipar de risc ca la contract**, dar pe alt document-sursa. | +| `V_OPT_FACTURARE=3` (contract) | **Nu garantat, silentios** | Sectiunea 1 — faptul portant. | + +**Observatie in afara perimetrului cerut, dar direct relevanta**: daca #13 etapa II ajunge sa +regenereze si facturi emise initial din **aviz** (`ntip=4`), nu doar din contract, **acelasi tip +de risc silentios exista si acolo**, prin alt mecanism (re-citire necondiționata din +`VANZARI_DETALII` a avizului-sursa, fara filtru pe pret). Raportul vechi (`s10_pret_rederivat.md`, +sectiunea 4) semnalase deja asta ca "neverificat, in afara bugetului" — de data asta o marchez +explicit ca **decizie deschisa**: daca reemiterea din #13 se aplica si documentelor provenite din +aviz, aceeasi discutie (a/b/c/d) trebuie purtata si pentru acea ramura, cu propriul cost (sursa +de comparat e alt document, nu `CTR_ARTICOLE`). Nu am extins cercetarea de date pe aceasta ramura +(in afara cererii explicite a sarcinii, care a cerut "cazul confirmat", adica cel de contract). + +**Concluzie sectiune**: criteriul "reemitere identica" e garantat azi doar pe ramurile +`ELSE`/restaurant (fara contract, fara comanda, fara aviz). Pe comenzi, o divergenta produce +eroare vizibila (rau, dar nu tacit). Pe contract **si** pe aviz, o divergenta produce o suma +gresita fara nicio semnalare — acestea sunt cele doua ramuri care au nevoie de o decizie explicita +de tipul (a)-(d) inainte ca #13 etapa II sa poata pretinde ca satisface criteriul S12. + +--- + +## 7. Ce ramane de decis de Marius + +1. **(b) blocare stricta vs. (c) avertizare+confirmare** — (c) satisface literal criteriul "decis + explicit, nu accidental" cerut de plan si nu intrerupe fluxul quasi-niciodata (doar cand chiar + exista divergenta); (b) e mai sigur (niciodata nu se schimba nimic fara interventie manuala pe + contract) dar mai frustrant daca divergentele sunt frecvente in productie — lucru nemasurabil + azi (sectiunea 2). **Recomand (c)** ca implicit, cu mesajul aratand explicit delta de + pret/TVA/valuta (sectiunea 3) — nu doar pretul. +2. **Garda trebuie sa acopere pret + TVA + valuta impreuna**, nu doar pretul — confirmat la + sectiunea 3 ca vin din acelasi `SELECT`. O implementare care verifica doar pretul ar lasa + trecerea tacuta a unei schimbari de TVA sau valuta. +3. **Varianta (d) (ocolire) — nu o recomand**, din cauza pierderii permanente a legaturii + `VANZARI_DETALII.ID_CTR`. Daca Marius considera totusi ca merita costul (de ex. daca + trasabilitatea contract-linie pe documentele reemise nu conteaza in practica), e o decizie de + produs explicita, nu tehnica — semnalez aici, nu decid. +4. **Ramura de aviz (`ntip=4`) are acelasi tip de risc silentios** (sectiunea 6) — de decis daca + intra in perimetrul #13 etapa II acum sau se trateaza separat, cu propriul studiu de fezabilitate + pentru garda (sursa de comparat difera fata de contract). +5. **Frecventa reala in productie a divergentei `CTR_ARTICOLE.PRET_UNITAR`** ramane nemasurabila + din baza de dezvoltare (27 randuri total) — daca decizia (b) vs (c) depinde de cat de des s-ar + declansa garda, ar trebui repetata interogarea din sectiunea 2 pe o baza cu volum de productie + inainte de implementare. + +--- + +## STARE / CE RAMANE + +**Livrabil complet** — toate cele 6 puncte cerute in brief sunt acoperite (sectiunile 1-6), plus +recomandare argumentata (sectiunea 4, cu tabel comparativ) si lista de decizii deschise +(sectiunea 7). + +Nimic ramas in lucru; nicio verificare intrerupta. Singurele limitari explicite: +- esantionul de date (27 randuri `CTR_ARTICOLE`, baza de dezvoltare) nu se extrapoleaza la + productie (sectiunea 2); +- ramura de aviz (`ntip=4`) e semnalata ca avand acelasi tip de risc, dar nu a primit acelasi nivel + de cercetare (nu era in perimetrul cerut) — vezi sectiunea 6, ultimul paragraf inainte de + concluzie. diff --git a/docs/cercetare/s10_pret_rederivat.md b/docs/cercetare/s10_pret_rederivat.md new file mode 100644 index 0000000..1afd654 --- /dev/null +++ b/docs/cercetare/s10_pret_rederivat.md @@ -0,0 +1,342 @@ +# S10 — Pretul re-derivat in `adauga_articol_factura`, ramura cu ramura + +## Nota pe sursa folosita + +`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` **nu exista** (nici in `docs\`, nici urme in +istoricul git — `docs\` e integral netracked). Fisierul cu **acelasi nume** exista insa la +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si a fost folosit +ca sursa. Continutul `adauga_articol_factura` coincide structural cu ce descrie planul (aceeasi +ordine de ramuri, aceleasi coduri de eroare `FACT-012/013/018`, acelasi `DECODE(A.PRET_UNITAR, 0, +V_PRET_TEMP, A.PRET_UNITAR)`), dar liniile sunt deplasate cu **+17** fata de citatele din plan +(corp `4989-5284` aici vs. `4972-5268` in plan; ramura de contract `5146-5185` aici vs. `:5129-5168` +in plan) — probabil diferente de comentarii de antet intre cele doua exporturi. Toate citatele de mai +jos sunt pe fisierul din `SCRIPTURI_CLAR`, verificat direct. + +## Verdict (raspuns la intrebarea 4 — reemiterea) + +**Da, exista o ramura care suprascrie tacut pretul la orice apel, inclusiv la o eventuala +reemitere: liniile provenite dintr-un contract cu `OPT_FACTURARE = 3` (sau `NULL`, care se +implicit-eaza tot la 3).** Procedura nu are nicio notiune de "reemitere" — la fiecare apel cu acelasi +`V_ID_CTR`, daca articolul are in `CTR_ARTICOLE.PRET_UNITAR` o valoare diferita de 0, acea valoare +**inlocuieste** pretul trimis de VFP (`V_PRET_TEMP`), necontidionat de faptul ca linia a fost sau nu +atinsa de utilizator pe ecran. Riscul se materializeaza doar daca pretul din contract s-a schimbat +intre emiterea initiala si reemitere — daca n-a scazut, rezultatul e identic si nimeni nu observa +diferenta pana cand se schimba. Pe toate celelalte ramuri (implicita, restaurant), pretul trimis de +VFP e respectat ca atare. **Nu exista niciun parametru sau flag care sa opreasca re-derivarea** — +raspunsul la intrebarea 5 e "nu exista". + +## 1. Semnatura completa (corp, nu spec) + +`PACK_FACTURARE:4989-5015` (spec identica la `:537-563`, verificat doar ca prezenta, nu citat): + +``` +PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER, + V_ID_ARTICOL IN NUMBER, + V_SERIE IN VARCHAR2, + V_EXPLICATIE IN VARCHAR2, + V_ID_POL IN NUMBER, + V_ID_GESTIUNE IN NUMBER, + V_PRET_ACHIZITIE_TEMP IN NUMBER, + V_PRETD IN NUMBER, + V_ID_VALUTAD IN NUMBER, + V_PRET_TEMP IN NUMBER, + V_ID_VALUTA_TEMP IN NUMBER, + V_PRETURI_CU_TVA_TEMP IN NUMBER, + V_IN_STOC_TEMP IN NUMBER, + V_CANTITATE IN NUMBER, + V_DISCOUNT_UNITAR IN NUMBER, + V_CONT IN VARCHAR2, + V_CURS IN NUMBER, + V_MULTIPLICATOR IN NUMBER, + V_ID_JTVA_COLOANA IN NUMBER, + V_ID_PART_REZ IN NUMBER, + V_ID_LUCRARE_REZ IN NUMBER, + V_PRETV_ORIG IN NUMBER, + V_ID_VANZARE_SET IN NUMBER, + V_ID_CTR IN NUMBER, + V_ID_UTIL IN NUMBER, + V_TAXCODE IN NUMBER DEFAULT NULL, + V_LOT IN VARCHAR2 DEFAULT NULL) IS +``` + +Toti parametrii sunt **IN**, niciunul OUT/IN OUT — nu exista un canal de intoarcere a pretului +recalculat catre VFP la momentul apelului (linia se citeste ulterior, la re-interogarea grilei). + +**`V_OPT_FACTURARE` nu e parametru.** E o variabila locala (`:5029`), calculata **in interiorul +procedurii** din `CONTRACTE.OPT_FACTURARE`, folosind parametrul `V_ID_CTR` primit de la VFP +(`:5039-5050`): + +```sql +IF pack_facturare.ntip IN (2, 6, 26, 52) THEN + BEGIN + SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR; + EXCEPTION + WHEN NO_DATA_FOUND THEN + V_OPT_FACTURARE := 4; + END; +END IF; +``` + +VFP nu trimite si nu poate influenta `V_OPT_FACTURARE` altfel decat prin `V_ID_CTR` (`poArt.id_ctr`, +trimis `NULL` cand linia nu vine de pe contract). Pentru orice `pack_facturare.ntip` in afara lui +`(2, 6, 26, 52)`, `V_OPT_FACTURARE` **ramane neinitializat** (blocul IF nu ruleaza deloc) — echivalent +cu "nu conteaza", ramura de contract nu se poate nimeri. + +## 2. Ce inseamna `V_OPT_FACTURARE` si cum se coreleaza cu VFP + +Din `PACK_FACTURARE` (alte proceduri, aceeasi coloana `CONTRACTE.OPT_FACTURARE`) si din +`COMUN\clase\ofacturare.vc2` / `COMUN\programe\oproceduri_facturare.prg` (proprietatea locala +`poArt.opt_facturare`, populata la citirea contractului, in oglinda cu coloana Oracle): + +| Valoare | Tip facturare | Dovada | +|---|---|---| +| `0` (sau lipsa contract) | Articol normal, nelegat de contract | `ofacturare.vc2:13055` `If poArticol.opt_facturare = 0` | +| `1`, `2` | **Rata** (scadentar contract, `CTR_SCADENTAR`) — linia nu trece prin `adauga_articol_factura`, ci prin `adauga_rata_factura` (`ofacturare.vc2:14041-14050`) | `ofacturare.vc2:14041` `If Inlist(poArt.opt_facturare,1,2)`; `PACK_FACTURARE:2898,2915` (`CTR_SCADENTAR`, doar pt. `OPT_FACTURARE IN (1,2)`) | +| `3` | **Articol de pe contract**, pret din `CTR_ARTICOLE` | `PACK_FACTURARE:2699,2820` (`CTR_ARTICOLE` doar pt. `OPT_FACTURARE = 3`); `ofacturare.vc2:14750` (`poDate.tip = 2 And poArticol.opt_facturare = 3`) | +| `4` | Fallback intern cand contractul nu (mai) exista (`NO_DATA_FOUND`) | `PACK_FACTURARE:5047-5048` | + +VFP nu trimite `V_OPT_FACTURARE` ca parametru al `adauga_articol_factura` — coreleaza doar indirect, +prin faptul ca proprietatea locala `poArt.opt_facturare` (citita din acelasi `CONTRACTE.OPT_FACTURARE` +cand grila s-a populat) decide **ce RPC se apeleaza** (`adauga_rata_factura` pentru 1/2, +`adauga_articol_factura` pentru orice altceva, inclusiv 3), nu ce ramura Oracle se executa in interior +— aceea o recalculeaza serverul singur, din `V_ID_CTR`. + +## 3. Ramura cu ramura pe `V_OPT_FACTURARE` (si pe `pack_facturare.ntip`, care are prioritate) + +`CASE` la `PACK_FACTURARE:5052-5220`. Ramurile 1-3 sunt selectate dupa `pack_facturare.ntip`, nu +dupa `V_OPT_FACTURARE` — ramura 4 (contract) se testeaza **doar daca niciuna din primele trei nu s-a +potrivit**. + +| Selector | Tip facturare | Pretul | Sursa re-derivarii | Citat | +|---|---|---|---|---| +| `ntip IN (3,21,28,42,47)` | Facturare/aviz din **comenzi** | **Re-derivat, dar constrans sa se potriveasca cu ce a trimis VFP** — `SELECT A.PRET ... WHERE A.PRET = V_PRET_TEMP` | `COMENZI_ELEMENTE` + `CRM_POLITICI_PRETURI`/`PRET_ART`, filtrat pe `A.PRET = V_PRET_TEMP`; daca nu exista rand cu exact acel pret, `NO_DATA_FOUND` **nu e tratat** — procedura pica cu eroare Oracle netratata | `:5053-5078` | +| `ntip = 4` | Facturare din **avize** | **Re-derivat necondiționat, din documentul sursa** — `SELECT DISTINCT A.PRET ... INTO V_PRET` fara filtru pe pretul trimis | `VANZARI_DETALII` (linia din avizul sursa), filtrata pe articol/politica/gestiune/discount/cont, dar **nu** pe pret | `:5080-5103` | +| `ntip = 45` | Facturare **restaurant** | **Respectat** — `V_PRET := V_PRET_TEMP` setat **inainte** de SELECT (`:5109`); SELECT-ul recalculeaza doar `V_PROC_TVAV` si `V_PRET_ACHIZITIE` (cost, nu pret de vanzare) | n/a pentru pret; TVA din `JTVA_COLOANE`, cost din `CRM_POLITICI_PRET_ART.PRETFTVA` | `:5104-5145` | +| `V_OPT_FACTURARE = 3` (deci `ntip IN (2,6,26,52)` **si** contract cu `OPT_FACTURARE=3`/`NULL`) | Facturare/aviz **de pe contract** | **Re-derivat daca contractul are pret setat** — `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)`; daca `PRET_UNITAR = 0` sau nu exista rand `CTR_ARTICOLE`/politica potrivita (`NO_DATA_FOUND`), cade pe `V_PRET_TEMP` | `CTR_ARTICOLE.PRET_UNITAR` (prin `CRM_POLITICI_PRET_ART`, `ID_POL_ART`) | `:5146-5185` (branch), `:5181-5184` (fallback `V_PRET_TEMP` in exceptie) | +| `ELSE` (implicit — orice altceva, inclusiv linii normale fara contract) | Standard | **Respectat** — `V_PRET := V_PRET_TEMP` | n/a; TVA din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` trimis de VFP | `:5187-5203` | + +**Observatie de prioritate:** daca o linie e simultan "din comanda" (`ntip IN (3,21,28,42,47)`) si +are si `V_ID_CTR` completat, ramura de comenzi castiga — `V_OPT_FACTURARE` nici nu se calculeaza +(blocul de la `:5039` ruleaza doar pentru `ntip IN (2,6,26,52)`). Ramurile nu se pot suprapune in +productie curenta, dar asta inseamna ca **orice extindere viitoare a lui `ntip`** (de ex. daca #13 +introduce un tip nou de document care refoloseste `adauga_articol_factura` pentru reemitere) trebuie +verificata explicit fata de acest `CASE` — un `ntip` nou care nu intra in niciuna din listele +`(3,21,28,42,47)` / `4` / `45` cade automat fie pe ramura de contract (daca are `V_ID_CTR`), fie pe +`ELSE`. + +## 4. Reemiterea — detaliu (vezi si verdictul de mai sus) + +Procedura **nu primeste si nu poate primi** vreun semnal de tip "acesta e un apel de reemitere, nu +recalcula". Comportamentul e identic la emiterea initiala si la orice apel ulterior cu aceiasi +parametri. Riscul descris in plan (sectiunea H) se confirma integral pentru ramura de contract +(`V_OPT_FACTURARE = 3`): daca `CTR_ARTICOLE.PRET_UNITAR` s-a modificat intre emiterea initiala si +reemitere, factura reemisa va lua **pretul curent din contract**, nu pretul confirmat pe ecran de +utilizator inainte de reemitere — chiar daca utilizatorul n-a atins linia. + +Pentru ramurile "avize" (`ntip = 4`) si "comenzi" (`ntip IN (3,21,28,42,47)`), acelasi risc structural +exista (pretul se re-citeste din documentul sursa la fiecare apel), dar **nu sunt cazul confirmat de +#13** — planul S10 spune explicit "se verifica pe factura din contract, care e cazul confirmat" — nu +s-a cerut si nu s-a facut inventar suplimentar pentru daca fluxul de reemitere din #13 (etapa II, +stergere+regenerare cu aceleasi linii) ar trece vreodata prin aceste doua ramuri. **De retinut daca +#13 extinde reemiterea si la facturi/avize provenite din comanda sau din alt aviz** — pe ambele, +re-derivarea e chiar mai stricta decat pe contract (ramura de comenzi cade cu eroare netratata daca +pretul nu se potriveste exact; ramura de avize suprascrie necondiționat). + +## 5. Mecanism de "nu re-deriva" + +**Nu exista.** Niciun parametru boolean, nicio valoare santinela, nicio ramura in `CASE` +(`:5052-5220`) care sa respecte pretul primit *pentru ca i s-a cerut explicit*. Ramurile "implicita" +si "restaurant" respecta pretul doar pentru ca structural nu au de unde re-deriva altceva (nu exista +document sursa de recitit), nu pentru ca ar exista o comutare intentionata. Orice solutie de tip +"pretul vine din formular, nu se re-deriva" (cum sugereaza planul, sectiunea H) **ar trebui +implementata in VFP inainte de apel** (de exemplu, nu retrimite `V_ID_CTR` la reemitere daca vrei sa +eviti ramura de contract) — nu exista un parametru in pachet pe care VFP l-ar putea seta ca sa +dezactiveze re-derivarea, iar pachetul **nu se modifica** (decizia 27-bis, respectata — aceasta e o +constatare, nu o propunere). + +## 6. Discountul si TVA-ul + +- **Discountul (`V_DISCOUNT_UNITAR`) nu e niciodata re-derivat.** Parametrul intra direct in + `INSERT INTO VANZARI_DETALII_TEMP (..., DISCOUNT_UNITAR, ...) VALUES (..., V_DISCOUNT_UNITAR, ...)` + (`:5236`, `:5266`) — nicio ramura din `CASE` il citeste sau il modifica. Tratament **diferit** de + pret: discountul e mereu respectat, indiferent de ramura. +- **TVA-ul (`V_PROC_TVAV`) e mereu recalculat server-side, pe toate ramurile — dar din surse + diferite, nu dintr-un parametru brut.** Nu exista parametru `V_PROC_TVAV_TEMP` in semnatura; VFP + trimite doar `V_ID_JTVA_COLOANA` (un identificator de coloana de cota, nu cota insasi). Pe ramura + implicita si pe cea de contract-fara-potrivire, cota se cauta in `JTVA_COLOANE` dupa acel ID + (`:5188-5198`, `:5169-5179`); pe ramurile comenzi/avize/contract-cu-potrivire/restaurant, cota vine + din documentul sursa sau din politica de pret (`:5058`, `:5083`, `:5150`, `:5114`). Deci TVA-ul are + un tratament **mai strict** decat pretul: niciodata "pass-through" direct, intotdeauna o cautare, + doar sursa cautarii difera pe ramura. + +## 7. Cursul valutar si pretul in valuta + +- **Cursul (`V_CURS`) e mereu respectat ca valoare, pe toate ramurile.** Singura procesare e + `DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste 0 cu 1, altfel scrie exact ce a + trimis VFP. Procedura **nu interogheaza niciodata tabela `CURS`** pentru un curs "de azi" — nu are + de unde sa recalculeze cursul chiar daca ar vrea. +- **Identitatea valutei (`V_ID_VALUTA`, variabila locala, nu parametrul `V_ID_VALUTAD`) urmeaza + acelasi tipar ca pretul, ramura cu ramura:** re-derivata din sursa pe comenzi (`C.ID_VALUTA`, + `:5059`) si avize (`A.ID_VALUTA`, `:5084`), re-derivata din contract pe ramura `V_OPT_FACTURARE=3` + (`B.ID_VALUTA`, `:5151`, cu fallback `V_ID_VALUTA_TEMP` in exceptie, `:5182`), respectata pe + implicita si restaurant (`V_ID_VALUTA := V_ID_VALUTA_TEMP`, `:5110`, `:5201`). +- **Consecinta pentru reemiterea unui document in valuta pe contract:** daca politica de pret a + contractului (`CRM_POLITICI_PRET_ART`, prin `CTR_ARTICOLE.ID_POL_ART`) a fost schimbata intre timp + la o alta valuta, reemiterea ar scrie articolul in **noua** valuta a politicii, dar cu **cursul** + trimis de VFP (posibil cursul valutei vechi, daca VFP nu a fost actualizat sa retrimita cursul + corect pentru noua valuta) — o sursa suplimentara de neconcordanta, nesemnalata explicit in plan. + Nu s-a gasit nicio validare in procedura care sa verifice ca `V_CURS` corespunde valutei + re-derivate `V_ID_VALUTA`. + +## Completare: nota contabila a politicii + +Sursa: aceeasi, `contabilizeaza_articol` la `PACK_FACTURARE:7173-7547` (in fisierul din +`SCRIPTURI_CLAR`; corpul relevant pentru articol simplu — nu compus — e la `:7391-7544`), plus +`scrie_nota` la `:12329-12561`. + +### 1. Cum ajunge de la `id_pol` la nota contabila — ia toate randurile setului, fara distributie + +`cursor_articol` (`:7218-7271`): + +```sql +FROM CRM_POLITICI_PRET_ART A +LEFT JOIN CRM_POLITICI_PRETURI B ON A.ID_POL = B.ID_POL +LEFT JOIN CRM_NOTE_VANZARI C ON B.ID_NOTA = C.ID_NOTA +LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET +WHERE A.ID_POL = detalii_articol.id_pol AND A.ID_ARTICOL = detalii_articol.id_articol +``` + +E un `JOIN` simplu pe `ID_SET`, **fara `ROWNUM`, fara agregare, fara filtru suplimentar pe +`NOTE_CONTABILE`**. Daca setul are `N` randuri, cursorul intoarce `N` randuri pentru aceeasi +combinatie `id_pol`/`id_articol`. Bucla care il consuma (`:7393-7542`, +`OPEN cursor_articol; FETCH ...; WHILE cursor_articol%FOUND LOOP ... FETCH ...; END LOOP;`) executa +**intregul corp — `scrie_nota`, `descarca_gestiune`, `scrie_discount` — o data pentru fiecare rand +din set**, cu **aceeasi cantitate si acelasi pret intreg de fiecare data**, nu impartite intre +randuri. Nu exista nicio coloana `ORDINE` in tot pachetul (cautare `ORDINE` in fisier: zero +rezultate) si nicio logica de distributie procentuala intre randurile unui set. + +**Consecinta directa pentru reteta aprobata (J-quater):** daca politica tehnica pentru contul de +venit ar ajunge sa foloseasca o nota al carei `ID_SET` are mai mult de un rand in +`NOTE_CONTABILE` (cazul general — pana la 30 de randuri pe unele seturi din baza, per verificarea ta +pe date vii), `contabilizeaza_articol` ar scrie **de N ori** aceeasi suma in contabilitate si ar +apela `descarca_gestiune` de N ori pentru aceeasi linie de vanzare — dublare de venit si dublare de +descarcare de gestiune, nu doar zgomot. Pasul 2 al retetei ("cauta o politica a carei nota are deja +`SCC`-ul calculat") **trebuie sa garanteze un singur rand `NOTE_CONTABILE` per `ID_SET` ales**, nu +doar un rand cu `SCC`-ul potrivit — verificarea facuta de tine pe cele 7 note active (exact un rand +per set) e conditia care face reteta sigura azi, dar nu e impusa de cod, e o coincidenta a datelor +curente. + +### 2. `NOTE_CONTABILE.PTVA` — nu e citit deloc de `contabilizeaza_articol` + +Lista de coloane a `cursor_articol` (`:7230-7260`) selecteaza explicit +`ID_NOTA, PRETURI_CU_TVA, ID_VENCHELT, ID_SECTIE, ID_SET, EXPLICATIE, SCD, ASCD, SCC, ASCC, CU_TVA, +IN_VALUTA` din lantul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> +NOTE_CONTABILE` — **`D.PTVA` nu apare in acest `SELECT`**. Coloana exista pe tabel (confirmat de +datele tale vii — `21`, `5`, `0`), dar `contabilizeaza_articol` n-o citeste niciodata. + +Cota de TVA folosita efectiv in scriere e alta — vine din apelul catre `scrie_nota` (`:7444-7467`), +unde parametrul `V_PTVA` primeste `detalii_articol.proc_tvav * 100 - 100` (`:7464`), adica **cota +calculata deja in `adauga_articol_factura`** (din `JTVA_COLOANE` sau din documentul sursa, vezi +sectiunea 3 de mai sus), nu din nota. Deci **raspunsul direct la intrebarea ta: daca articolul are +21% dar nota politicii are `PTVA=5`, nu se intampla nimic — `PTVA` de pe nota e complet ignorat, +cota folosita e cea a articolului/documentului, nu a notei.** Coloana `NOTE_CONTABILE.PTVA` e moarta +din perspectiva acestei proceduri (posibil folosita in alt modul, neverificat aici). + +### 3. `CU_TVA` si `IN_VALUTA` de pe nota + +Ambele se citesc din `NOTE_CONTABILE` (`crs_rand_articol.cu_tva`, `crs_rand_articol.in_valuta`, +`:7253`, `:7254-7260`) si se transmit mai departe la `scrie_nota` ca parametri (`V_CU_TVA`, +`V_IN_VALUTA`), unde controleaza mecanic, nu semantic tot ce e legat de pret: + +- **`V_CU_TVA`** (`scrie_nota:12416-12422`): daca `= 0`, suma acumulata in `V_INCASAT_CALCUL` e + `V_SUMA_FARA_TVA`; daca `= 1`, e `V_SUMA_CU_TVA`. Separat, la `:12537-12558`, **doar cand + `V_CU_TVA = 1` se scrie si o linie separata de TVA** (`pack_facturare.scrie_tva(...)`, cont + `4427`/etc.). Deci `CU_TVA` decide daca acest rand din notă genereaza o inregistrare contabila + separata pentru TVA sau nu — nu suprascrie si nu recalculeaza cota, doar comuta daca linia de TVA + se scrie. +- **`V_IN_VALUTA`** (`scrie_nota:12391-12404`, `:12492-12507`): daca `= 1`, se calculeaza si + `V_SUMA_VAL`/`V_SUMA_FARA_TVA_VAL`/etc. (sumele in valuta straina) si randul `ACT_TEMP` scrie + `ID_VALUTA = V_ID_VALUTA` (valuta reala a articolului) si `CURS = V_CURS`; daca `= 0`, randul + scrie `ID_VALUTA = pack_facturare.nid_moneda_nationala` si `CURS = 0`, indiferent de valuta reala + a articolului. + **Raspuns la "ce se intampla daca `IN_VALUTA=1` pe nota dar documentul e in lei":** nu apare nicio + eroare si nicio validare incrucisata intre `IN_VALUTA` de pe nota si valuta reala a documentului — + procedura calculeaza pur si simplu `V_SUMA_VAL` folosind `V_ID_VALUTA` si `V_CURS` primite din + `detalii_articol` (care, pe un document in lei, sunt deja moneda nationala si curs implicit 1). + Rezultatul practic: `ACT_TEMP.SUMA_VAL` se populeaza redundant (cu aceeasi valoare ca `SUMA`, + scalata la curs 1) in loc sa ramana `0`, iar `ID_VALUTA` de pe randul contabil ramane oricum + moneda nationala (pentru ca asta e `detalii_articol.id_valuta` pe un document in lei) — o + inconsistenta cosmetica in `ACT_TEMP` (rand cu `SUMA_VAL` populat desi `CURS` real e 1), nu o + eroare de suma. Relevant pentru politicile `SERVICII VALUTA` / `COMISION INTERMEDIERE` + (`SCC=704`, candidate la pasul 2 al retetei): daca oricare document in lei ar ajunge sa foloseasca + una din ele, ar produce astfel de randuri cosmetic-inconsistente, fara sa strice suma facturata. + +### 4. `ASCD`/`ASCC` si `ID_PARTD`/`ID_PARTC` + +- **`ASCD`/`ASCC` nu sunt obligatorii — au fallback automat cand sunt nule**, exact pe ramura care + se aplica pe facturi (`:7409-7428`): + ``` + V_ASCD := NVL(crs_rand_articol.ascd, + PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD)); + ... + V_ASCC := NVL(crs_rand_articol.ascc, + PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCC)); + ``` + Cand nota nu are analitic explicit (cazul majoritatii notelor reale, per verificarea ta), se + deriva unul din grupul de utilizatori al celui care factureaza, functie de contul `SCD`/`SCC`. Nu + s-a verificat in aceasta sesiune ce intoarce `GetAnaliticByGrupUtilizatori` cand nici grupul de + utilizatori nu are un analitic configurat (posibil `NULL` mai departe, netratat explicit aici). +- **`ID_PARTD`/`ID_PARTC` nu sunt citite de la nota deloc.** `cursor_articol` nu le selecteaza (nu + exista `D.ID_PARTD`/`D.ID_PARTC` nicaieri in fisier — verificat). Cele doua coloane de pe + `ACT_TEMP` se calculeaza in `scrie_nota`/`scrie_tva` direct din **codul contului** (`V_SCD`, + `V_SCC`) si din variabile de sesiune ale pachetului (`pack_facturare.nid_part`, + `nid_part_rez`, `nid_partc`), nu din nota: `ID_PARTD` e un `CASE` pe prefixul lui `V_SCD` + (`41%`/`46%`/`45%`/`357`) la `:12510-12515`; `ID_PARTC` e un `DECODE` pe `V_SCC` + (`419`/`4111`/`357`) la `:12523-12530`. Daca `NOTE_CONTABILE` are coloane `ID_PARTD`/`ID_PARTC`, + ele sunt irelevante pentru `contabilizeaza_articol` — partenerul contabil vine intotdeauna din + contextul documentului (`pack_facturare.nid_part`), nu din configurarea notei. + +### 5. Politica fara `ID_NOTA` (`HOTEL TAXE`, `HOTEL CAZARE`) + +**Nu exista un cod `FACT-0xx` dedicat pentru acest caz — spre deosebire de articolul care lipseste +din politica (`FACT-024`, `:7278-7302`), aici procedura nu detecteaza si nu semnaleaza explicit +situatia.** Lantul de `LEFT JOIN` din `cursor_articol` (`:7261-7271`) e construit sa supravietuiasca +oricarui inel lipsa: `A` (politica-articol, gasita — altfel s-ar fi luat deja `FACT-024` mai +devreme) se pastreaza chiar daca `B.ID_NOTA` e `NULL` — `C` si `D` devin pur si simplu toate +`NULL` pe acel rand, cursorul tot intoarce **un rand** (`cursor_articol%FOUND = TRUE`), nu zero. + +In bucla (`:7396-7541`), asta inseamna: +- `crs_rand_articol.scd`, `.ascd`, `.scc`, `.ascc`, `.cu_tva`, `.in_valuta`, `.explicatie` — toate + `NULL`. +- Ramura de calcul `V_ASCD := NVL(NULL, GetAnaliticByGrupUtilizatori(nid_util, NULL))` — analitic + calculat pe un cont `NULL`, deci probabil tot `NULL` (netestat aici ce intoarce functia pe + argument `NULL`). +- `scrie_nota` e apelata cu `V_SCD = NULL`, `V_SCC = NULL` — insereaza in `ACT_TEMP` un rand cu + conturile de debit/credit **nule**. + +Daca `ACT_TEMP.SCD`/`ACT_TEMP.SCC` au constrangere `NOT NULL` pe schema, `INSERT`-ul ar pica cu o +eroare Oracle generica (`ORA-01400`), nu cu un mesaj `FACT-0xx` prietenos ca la celelalte cazuri +tratate explicit — **neverificat in aceasta sesiune** (DDL-ul `ACT_TEMP` nu e in exportul +`PACK_FACTURARE`). In orice caz, **comportamentul e cel putin la fel de riscant ca lipsa unui +articol din politica**, dar fara plasa de siguranta explicita din cod — de tratat cu atentie daca +reteta aprobata ajunge sa produca vreodata o politica fara `id_nota` (nu pare sa fie cazul retetei +J-quater, care cere explicit gasirea unei note existente, dar `HOTEL TAXE`/`HOTEL CAZARE` arata ca +starea "politica fara nota" chiar exista azi in baza vie, pe alte fluxuri). + +## Ce nu s-a putut stabili si de ce + +- **Comportamentul exact al viitorului flux de reemitere din #13** (etapa II) fata de aceasta + procedura — planul S10 cere verificarea "pe factura din contract", nu inventarul complet pentru + comenzi/avize; nu s-a extins cercetarea acolo (in afara observatiei de la punctul 4, care ramane o + constatare structurala, nu un verdict testat pe fluxul de reemitere, care **inca nu exista in + cod** — decizia 30, nimic implementat). +- **Ce se intampla la eroarea netratata din ramura de comenzi** (`NO_DATA_FOUND` cand + `A.PRET = V_PRET_TEMP` nu gaseste rand, `:5057-5078`) — daca propaga ca eroare Oracle bruta pana la + utilizator sau daca `goExecutor` o intercepteaza generic; n-a fost verificat (nu era in perimetrul + celor 7 intrebari, dar e un risc adiacent gasit din citirea codului). +- **Cate contracte reale au azi `CTR_ARTICOLE.PRET_UNITAR` diferit de pretul ultimei facturi emise** + — verificare pe date vii, nefacuta (cercetare read-only, fara acces la interogari live in aceasta + sesiune). +- **Discrepanta de numerotare fata de `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`** (fisierul + cerut in prompt nu exista pe disc) — semnalata la inceputul raportului, nu blocheaza verdictul + pentru ca fisierul gasit in `SCRIPTURI_CLAR` are acelasi nume/data si continut structural identic. diff --git a/docs/cercetare/s10_rederivare_pe_calea_reemiterii.md b/docs/cercetare/s10_rederivare_pe_calea_reemiterii.md new file mode 100644 index 0000000..43f8bce --- /dev/null +++ b/docs/cercetare/s10_rederivare_pe_calea_reemiterii.md @@ -0,0 +1,470 @@ +# S10 — Se re-deriva valorile liniei pe calea de REEMITERE? + +Stare: **TERMINAT.** + +Surse: +- `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii; + `versiune_db.txt` = `2026_08_09_02`) — prescurtat **`PF:`**; +- corpul viu din `all_source` (`MARIUSM_AUTO.PACK_FACTURARE`, `PACKAGE BODY`, **VALID**, + `last_ddl_time = 2026-08-09 20:03:50`) — prescurtat **`LIVE:`**; +- `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare_editare.prg`. + +**Exportul = ce ruleaza.** Verificat pe cinci ancore; in zona relevanta offsetul e constant, +`LIVE + 1240 = PF`: `LIVE:3749`↔`PF:4989`, `LIVE:3840`↔`PF:5080`, `LIVE:3909`↔`PF:5149`, +`LIVE:3982`↔`PF:5222`, `LIVE:4044`↔`PF:5284`, `LIVE:12466`↔`PF:13706`, `LIVE:13735`↔`PF:14975`. +**Nu generalizez offsetul in afara zonei masurate** — il dau doar ca dovada de identitate. + +--- + +## 1. Unde sta `SELECT`-ul de la `PACK_FACTURARE:5149-5166` si cine il cheama + +**Numarul de linie e corect**, verificat direct pe fisier. `PF:5149-5166`: + +``` +5149 SELECT DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR), +5150 B.PROC_TVAV, +5151 B.ID_VALUTA, +5152 A.PRET_CU_TVA, +5153 C.IN_STOC +5154 INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC +5159 FROM CTR_ARTICOLE A +5160 LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL_ART = B.ID_POL_ART +5162 LEFT JOIN NOM_ARTICOLE C ON B.ID_ARTICOL = C.ID_ARTICOL +5164 WHERE B.ID_ARTICOL = V_ID_ARTICOL AND A.ID_CTR = V_ID_CTR AND B.ID_POL = V_ID_POL; +``` + +**Procedura**: `adauga_articol_factura` — spec `PF:537`, corp **`PF:4989`**, +`END adauga_articol_factura;` **`PF:5284`**. **Deci DA, e `adauga_articol_factura`.** + +**CORECTIE fata de raportul rundei 9**: procedura **nu scrie in `VANZARI_DETALII`**. Se termina cu un +singur `INSERT`, `PF:5222`, in **`VANZARI_DETALII_TEMP`**: + +``` +5222 INSERT INTO VANZARI_DETALII_TEMP (... PRET, PRET_CU_TVA, PROC_TVAV, ... ID_VALUTA, ... IN_STOC ...) +5252 VALUES (... V_PRET, V_PRETURI_CU_TVA, V_PROC_TVAV, ... V_ID_VALUTA, ... V_IN_STOC ...) +``` + +Re-derivarea se produce deci **pe drumul catre temp**, nu dupa el. Afirmatia „`scrie_in_vanzari` +copiaza `PRET` neschimbat din temp" e adevarata (vezi 3) si totusi **irelevanta ca protectie**: +valoarea din formular e inlocuita **inainte** sa intre in temp. + +**Cine cheama procedura**: numai VFP-ul, din metoda de **salvare** a formularului: + +- `COMUN\clase\ofacturare.vc2:14069` — `frm_facturare_articole.do_scrie_articole` +- `COMUN\clase\ofacturare.vc2:18104` — `frm_facturare_articole2.do_scrie_articole` + +(`:14054` si `:18089` sunt variante comentate, cu bind `?`, ale aceluiasi apel.) +In pachet **nu exista apelant intern**: `grep 'adauga_articol_factura\b'` da doar `PF:537` (spec), +`PF:4989` (corp), `PF:5284` (`END`) si doua comentarii de antet (`PF:22`, `PF:1497`). + +**Traseul complet pe calea de scriere** (`frm_facturare_articole`): + +1. `ofacturare.vc2:13981` — `pack_facturare.initializeaza_date_factura(...)`, care primeste + `Alltrim(Str(poDate.Tip))` (`ofacturare.vc2:13994`) si il pune in variabila de pachet: + `PF:1882 pack_facturare.ntip := V_TIP;` (corp `PF:1808`, `END` la `PF:1917`). + **`ntip` = tipul documentului din formular** — acelasi care ajunge in `VANZARI.TIP` (`PF:13670`). + Tot aici, `PF:1835 DELETE FROM VANZARI_DETALII_TEMP;` si `PF:1836 nid_act := 0;`. +2. `ofacturare.vc2:14036-14115` — `SCAN` pe cursorul de grid `crsfactura`, cate un + `pack_facturare.adauga_articol_factura(...)` per linie (`:14069`), executat prin + `goExecutor.oExecute` (`:14105`). +3. `frm_facturare_articole.do_scrie_factura` — `scrie_factura2` (`ofacturare.vc2:14345`, `:14373`), + `scrie_factura_avize` (`:14318`) sau `scrie_factura_avize_retur` (`:14434`, `:14497`), care ajung + la `pack_facturare.scrie_in_vanzari` (`PF:13488`). + +**Raspuns Q1**: e `adauga_articol_factura`, si e pe calea de **scriere** a documentului (nu pe cea de +creare/incarcare din sursa) — dar tinta insertului e `VANZARI_DETALII_TEMP`, iar re-derivarea se +aplica **inainte** de insert, deci nimic de dupa temp nu o mai poate repara. + +### Poarta care decide re-derivarea + +`adauga_articol_factura` are un singur `CASE` (`PF:5052-5220`), pe **`pack_facturare.ntip`**: + +| `WHEN` | linii | conditie | +|---|---|---| +| comenzi | `PF:5053-5078` | `ntip IN (3, 21, 28, 42, 47)` | +| avize | `PF:5080-5103` | `ntip = 4` | +| restaurant | `PF:5104-5145` | `ntip = 45` | +| **contract** | `PF:5146-5185` | `V_OPT_FACTURARE = 3` | +| `ELSE` | `PF:5187-5218` | restul | + +`V_OPT_FACTURARE` se seteaza **numai** daca `ntip IN (2, 6, 26, 52)` (`PF:5039-5050`): +`SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;`, cu +`NO_DATA_FOUND -> V_OPT_FACTURARE := 4`. Pentru orice alt `ntip` ramane **NULL**, iar `WHEN NULL = 3` +e NULL -> fals -> se merge pe `ELSE`. **Implicitul e ramura care re-deriva**: `NVL(OPT_FACTURARE, 3)`. + +Tipurile (`COMUN\docs\tipuri_documente_facturare.md`): 2 = factura pe contract, 6 = contract in +valuta, 26 = aviz pe contract, 52 = contract factura valuta; 4 = factura din avize; +3/21/28/42/47 = din comenzi; 45 = restaurant. + +## 2. Cele cinci valori, una cate una + +Intai maparea parametrilor — ce **trimite** formularul (apel `ofacturare.vc2:14069-14091` confruntat +cu semnatura `PF:4989-5015`; 27 de parametri, pozitional, potrivire completa): + +| formular (`crsfactura` -> `poArt`) | parametru | +|---|---| +| `poArt.pretftva`/`pretctva`/`vpretftva`/`vpretctva` | `V_PRET_TEMP` | +| `poArt.id_valuta` | `V_ID_VALUTA_TEMP` | +| `poArt.cu_tva` | `V_PRETURI_CU_TVA_TEMP` | +| `poArt.gestionabil` | `V_IN_STOC_TEMP` | +| `poArt.id_jtva_coloana` | `V_ID_JTVA_COLOANA` | +| `poArt.id_pol` | `V_ID_POL` | +| `poArt.id_ctr` | `V_ID_CTR` | + +**`PROC_TVAV` nu e parametru.** Nu exista `V_PROC_TVAV_TEMP` in semnatura — cota de TVA a liniei se +calculeaza **intotdeauna** pe server, in **toate** ramurile. Formularul trimite `ID_JTVA_COLOANA`, din +care ramura `ELSE` deriva `PROC_TVAV`. Pentru `PROC_TVAV` intrebarea „vine din temp?" **nu are raspuns +„da" pe nicio ramura**; intrebarea reala e *din ce* se deriva. + +### Ramura contract (`V_OPT_FACTURARE = 3`, `PF:5146-5185`) + +- **`PRET`** — `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)` (`PF:5149`). + **Se re-deriva** din `CTR_ARTICOLE.PRET_UNITAR` ori de cate ori aceasta e `<> 0`, indiferent daca + linia a fost atinsa pe ecran. Numai cand contractul are `PRET_UNITAR = 0` trece valoarea din + formular. **Capcana NULL**: `CTR_ARTICOLE.PRET_UNITAR` e `nullable=Y` (verificat pe DB); un NULL + **nu** potriveste literalul `0` in `DECODE`, deci rezultatul e **NULL**, nu `V_PRET_TEMP` — pretul + din formular se pierde si linia intra in temp cu `PRET` NULL. *(dedus din semantica `DECODE`, nu + rulat.)* +- **`PROC_TVAV`** — `B.PROC_TVAV` din `CRM_POLITICI_PRET_ART` (`PF:5150`). **Se re-deriva** din + politica de preturi, **nu** din `ID_JTVA_COLOANA` trimis de formular. Diferit de `ELSE`. +- **`ID_VALUTA`** — `B.ID_VALUTA` din `CRM_POLITICI_PRET_ART` (`PF:5151`). **Se re-deriva**; + `V_ID_VALUTA_TEMP` e ignorat. +- **`PRET_CU_TVA`** — `A.PRET_CU_TVA` din `CTR_ARTICOLE` (`PF:5152`). **Se re-deriva**; + `V_PRETURI_CU_TVA_TEMP` (`poArt.cu_tva`) e ignorat. +- **`IN_STOC`** — `C.IN_STOC` din `NOM_ARTICOLE` (`PF:5153`). **Se re-deriva din nomenclatorul + curent**; `V_IN_STOC_TEMP` (`poArt.gestionabil`) e ignorat. + +Cele cinci **nu merg impreuna** nici macar aici: `PRET` are un `DECODE` care uneori lasa valoarea din +formular sa treaca, celelalte patru sunt inlocuite **neconditionat**, din **trei tabele diferite** +(`CTR_ARTICOLE`, `CRM_POLITICI_PRET_ART`, `NOM_ARTICOLE`). + +**Iesirea de siguranta**: `EXCEPTION WHEN NO_DATA_FOUND` (`PF:5167-5185`) deriva `PROC_TVAV` din +`JTVA_COLOANE` si pune **toate** celelalte patru pe valorile din formular (`PF:5181-5184`): +`V_PRET := V_PRET_TEMP; V_ID_VALUTA := V_ID_VALUTA_TEMP; V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP; +V_IN_STOC := V_IN_STOC_TEMP;`. Adica **daca linia nu mai are corespondent in contract, formularul +castiga** — exact comportamentul cerut de decizia 54, existent deja in cod, dar numai pe calea de +exceptie. + +### Ramura `ELSE` (`PF:5187-5218`) — cazul „bun" + +`PROC_TVAV` din `JTVA_COLOANE` pe `ID_JTVA_COLOANA` trimis de formular (`PF:5189-5192`), iar +`PF:5200-5203` pune explicit `V_PRET := V_PRET_TEMP`, `V_ID_VALUTA := V_ID_VALUTA_TEMP`, +`V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP`, `V_IN_STOC := V_IN_STOC_TEMP`. +**Patru din cinci vin din formular; `PROC_TVAV` se deriva, dar din date trimise de formular.** + +### Ramura comenzi (`ntip IN (3,21,28,42,47)`, `PF:5053-5078`) + +``` +5057 SELECT A.PRET, +5058 NVL2(A.PTVA, ROUND((A.PTVA + 100) / 100, 2), C.PROC_TVAV), +5059 C.ID_VALUTA, B.PRETURI_CU_TVA, D.IN_STOC +5075 WHERE A.ID_COMANDA = V_ID_COMANDA AND A.ID_ARTICOL = V_ID_ARTICOL +5077 AND A.PRET = V_PRET_TEMP AND A.STERS = 0; +``` + +- **`PRET`** — formal `A.PRET` din `COMENZI_ELEMENTE`, dar `WHERE ... A.PRET = V_PRET_TEMP` + (`PF:5077`) **fixeaza** rezultatul pe pretul din formular. Practic **nu se schimba**. +- **`PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`, `IN_STOC`** — **se re-deriva** din + `COMENZI_ELEMENTE.PTVA` / `CRM_POLITICI_PRET_ART` / `CRM_POLITICI_PRETURI` / `NOM_ARTICOLE`. +- **Ramura nu are bloc `EXCEPTION`.** Un `SELECT INTO` gol da `NO_DATA_FOUND` (ORA-01403) + **netratat**, care urca prin `goExecutor` in VFP. Deci daca la reemitere pretul liniei a fost + modificat pe ecran (sau linia nu mai e in comanda), scrierea **cade cu eroare** — nu se re-deriva + tacit. Este o **a doua ramura de re-derivare**, pe care raportul rundei 9 nu o numara. + +### Ramura restaurant (`ntip = 45`, `PF:5104-5145`) + +`PF:5109-5112` pune explicit `V_PRET := V_PRET_TEMP`, `V_ID_VALUTA := V_ID_VALUTA_TEMP`, +`V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP`, `V_IN_STOC := V_IN_STOC_TEMP`; `SELECT`-ul de la +`PF:5114-5139` deriva **doar** `V_PROC_TVAV` (din `JTVA_COLOANE`) si `V_PRET_ACHIZITIE`. +Patru din cinci vin din formular. + +### Ramura avize (`ntip = 4`) — la punctul 5 + +## 3. `UPDATE` / recalcul care suprascrie valorile DUPA insertul din temp + +**Nu exista, pentru cele cinci valori.** + +Scrierea temp -> definitiv, in `scrie_in_vanzari` (`PF:13488-13953`), e o copiere 1:1: + +``` +13705 INSERT /*+ APPEND */ +13706 INTO VANZARI_DETALII (... PRET, PROC_TVAV, ... ID_VALUTA, ... PRET_CU_TVA, DIFERENTA, ...) +13732 SELECT V_ID_VANZARE as ID_VANZARE, ... PRET, PROC_TVAV, ... ID_VALUTA, ... PRET_CU_TVA, DIFERENTA, ... +13757 FROM VANZARI_DETALII_TEMP; +``` + +> Capcana de cautare platita aici: `grep 'INSERT INTO VANZARI_DETALII'` da **zero** potriviri — +> hint-ul `/*+ APPEND */` rupe cuvintele pe doua randuri. Cautarea corecta e `INTO VANZARI_DETALII`. + +`IN_STOC` **nu apare** in lista de coloane (`PF:13707-13731`), si `VANZARI_DETALII` **nu are coloana +`IN_STOC`** (verificat pe DB: `all_tab_columns` -> 0 randuri). Traieste doar in +`VANZARI_DETALII_TEMP`, unde decide descarcarea de gestiune. Re-derivarea lui nu murdareste +documentul salvat, dar **schimba comportamentul de stoc** al reemiterii. + +Toate scrierile pe `VANZARI_DETALII` din pachet: + +| linie | procedura | ce face | +|---|---|---| +| `PF:13705` | `scrie_in_vanzari` (`PF:13488`) | `INSERT` 1:1 din temp | +| `PF:14974` | `finalizeaza_avize_lucrare` (`PF:14854`) | `INSERT` 1:1 din temp (cale avize de lucrare) | +| `PF:5560` | `sterge_factura` (`PF:5432`) | `SET STERS, ID_UTILS, DATAORAS` | +| `PF:5630` | `sterge_proforma` (`PF:5610`) | `SET STERS, ID_UTILS, DATAORAS` | +| `PF:14516` | `modifica_explicatie_articol` (`PF:14511`) | `SET EXPLICATIE, TAXCODE` | +| `PF:16017` | `actualizeaza_vanzari` (`PF:16012`) | `SET STERS = 0` | + +**Niciun `UPDATE` nu atinge `PRET`, `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`.** +Confirmat si pe DB: `all_source` are exact doua `INTO VANZARI_DETALII` (`LIVE:12466`, `LIVE:13735`). + +Mutatiile pe `VANZARI_DETALII_TEMP` **intre** `adauga_articol_factura` si `scrie_in_vanzari`: + +| linie | procedura | ce schimba | +|---|---|---| +| `PF:5297` | `adauga_diferente_pret` | numai `DIFERENTA` (`PF:5344`: `UPDATE SET DIFERENTA = B.DIFERENTA`) | +| `PF:6860`, `PF:6864` | `scrie_factura_avize` | numai `CANTITATE` (split pe custodie); in `MERGE`, `PRET`, `PRET_CU_TVA`, `PROC_TVAV`, `ID_VALUTA`, `IN_STOC` sunt in clauza `ON`, nu in `UPDATE SET` | +| `PF:14020` | `scrie_seturi` | numai `ID_VANZARE_SET` | +| `PF:14057` | `scrie_seturi_proforma` | numai `ID_VANZARE_SET` (cale proforma) | +| `PF:12266` | `transfera_articol` | cale de transfer, in afara scrierii de factura | + +**`pack_auto.actualizeaza_deviz`** (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`) +atinge **`DEV_ORDL.PROC_TVAV`, `RUL.ID_FACT`, `NOM_LUCRARI.ID_FACT`** — **nu atinge `VANZARI_DETALII`**. + +Recalculele de totaluri (`PF:13760+`, `recalculeaza_totaluri_vanzari` `PF:16021`) si rotunjirile +lucreaza pe **`VANZARI`**, agregat — nu rescriu linia. + +**Raspuns Q3**: nu. Dupa insertul in temp, cele cinci valori nu mai sunt suprascrise nicaieri. +`DIFERENTA` si `CANTITATE` da, restul nu. + +## 4. Verdict: ajung valorile din formular neatinse in `VANZARI_DETALII`? + +**NU, pentru trei familii de tipuri. DA, pentru restul.** + +Se pierd **intr-un singur loc**: `PACK_FACTURARE.adauga_articol_factura`, in `CASE`-ul de la +**`PF:5052-5220`**, adica **inainte** de `INSERT INTO VANZARI_DETALII_TEMP` (`PF:5222`). Precis: + +- **contract** (`ntip IN (2,6,26,52)` cu `CONTRACTE.OPT_FACTURARE` NULL sau 3) — `PF:5149-5153`: + se pierd `PRET` (cand `CTR_ARTICOLE.PRET_UNITAR <> 0`), `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`, + `IN_STOC`; +- **aviz** (`ntip = 4`) — `PF:5082-5086`: se pierd toate cinci, **inclusiv `PRET`, neconditionat**; +- **comenzi** (`ntip IN (3,21,28,42,47)`) — `PF:5057-5061`: se pierd patru; `PRET` e fixat de + `WHERE`, dar la nepotrivire scrierea **cade cu ORA-01403** in loc sa treaca. + +Pentru toate celelalte tipuri (1, 5, 10, 22, 45, ...) valorile din formular **ajung neatinse**: +ramurile `ELSE` (`PF:5200-5203`) si restaurant (`PF:5109-5112`) le copiaza explicit, iar traseul de +dupa (`PF:13705-13757`) e copiere 1:1. + +**Exceptie transversala, valabila pe TOATE tipurile**: `PROC_TVAV` **nu vine niciodata din formular** — +nu e parametru al procedurii. Pe calea „buna" se deriva din `JTVA_COLOANE` pe `ID_JTVA_COLOANA`-ul +trimis de formular, deci reproduce documentul **atat timp cat cota din `JTVA_COLOANE` nu s-a +schimbat**. La o modificare legala de cota, reemiterea schimba TVA-ul chiar si pe ramura `ELSE`. + +## 5. Sursa AVIZ (`ntip = 4`) — calea difera, si e mai grava + +Verbatim, `PF:5080-5103` (confirmat identic pe DB, `LIVE:3840-3863`): + +``` +5080 WHEN pack_facturare.ntip = 4 THEN +5081 -- facturare din avize +5082 SELECT DISTINCT A.PRET, +5083 A.PROC_TVAV, +5084 A.ID_VALUTA, +5085 A.PRET_CU_TVA, +5086 B.IN_STOC +5087 INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC +5092 FROM VANZARI_DETALII A +5093 LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL +5095 WHERE A.ID_ARTICOL = V_ID_ARTICOL +5096 AND A.ID_POL = V_ID_POL +5097 AND NVL(A.ID_GESTIUNE, -1000) = V_ID_GESTIUNE +5098 AND A.DISCOUNT_UNITAR = V_DISCOUNT_UNITAR +5099 AND NVL(A.CONT, 'XXXX') = V_CONT +5100 AND A.ID_VANZARE IN +5101 (SELECT X AS ID_VANZARE +5102 FROM table(cast(CHARN2COLLECTION(pack_facturare.clistaid, ',') AS num_tab))); +``` + +Diferentele fata de contract, toate in defavoarea deciziei 54: + +1. **`PRET` se re-deriva neconditionat**, direct din `VANZARI_DETALII` al **avizului sursa**. Nu exista + `DECODE(..., 0, V_PRET_TEMP, ...)` si **nu exista `AND A.PRET = V_PRET_TEMP`** in `WHERE` (spre + deosebire de ramura comenzi, `PF:5077`). Un pret modificat pe ecran la reemitere e **inlocuit + tacit** cu pretul din aviz. **Aceasta e ramura cea mai agresiva din tot `CASE`-ul.** +2. **Nu are bloc `EXCEPTION`.** Ramura contract are `NO_DATA_FOUND` care cade inapoi pe formular + (`PF:5167-5185`); aici, orice modificare pe ecran a `DISCOUNT_UNITAR`, `CONT`, `ID_GESTIUNE` sau + `ID_POL` scoate randul din `WHERE` -> **ORA-01403** urcata in VFP. `SELECT DISTINCT` peste mai + multe avize cu preturi diferite pe acelasi articol poate da si **ORA-01422** (`TOO_MANY_ROWS`). +3. **Nu filtreaza `A.STERS = 0`**, desi `VANZARI_DETALII` are coloana `STERS` (verificat pe DB) si + `sterge_factura` o foloseste (`PF:5560`). Linii sterse ale avizului sursa pot fi citite. +4. Depinde de `pack_facturare.clistaid` — lista de avize sursa, trimisa de VFP prin + `initializeaza_date_factura` (`poDate.listaid`, `ofacturare.vc2:13992`). La reemitere, `clistaid` + trebuie repopulata cu aceleasi avize, altfel `WHERE` nu potriveste nimic -> ORA-01403. + +**Ce nu difera**: dupa `CASE` traseul e identic (`INSERT` in temp `PF:5222`, apoi copiere 1:1 +`PF:13705`), iar `scrie_factura_avize` (`PF:6692`) modifica in temp **numai `CANTITATE`** +(`PF:6860`, `PF:6864`) — nu re-deriva nimic in plus. + +**Raspuns Q5**: calea difera, si e **mai stricta**. Pe contract exista o portita (`PRET_UNITAR = 0` +si `NO_DATA_FOUND`) prin care valorile din formular trec; pe aviz **nu exista niciuna** — ori se +re-deriva tot, ori pica cu eroare. + +## 6. Consecinta pentru decizia 54, la nivel de contract + +### 6.1 Se poate curat VFP? **Nu.** + +Confirm afirmatia rundei 9 — dar acum cu dovada, nu prin absenta: + +- **Semnatura nu are flag.** `PF:4989-5015`: 27 de parametri, toti date de linie; ultimii doi + (`V_TAXCODE`, `V_LOT`) sunt `DEFAULT NULL`, niciunul nu e comutator. +- **Globalele pachetului nu au flag.** `PF:126-212` — stare de sesiune (`ntip`, `clistaid`, + `nid_part`, ...), niciun comutator de comportament pe re-derivare. +- **Poarta nu e influentabila legitim din VFP.** `ntip` ajunge in `VANZARI.TIP` (`PF:13670`) — a-l + falsifica inseamna a schimba tipul documentului. `CONTRACTE.OPT_FACTURARE` e date de contract, nu + parametru de apel. +- **Singurele parghii VFP care ar devia pe calea `NO_DATA_FOUND` sunt, ambele, de aceeasi natura ca + varianta (d) deja respinsa:** + - `V_ID_CTR = NULL` — respinsa; `PF:5280` duce `V_ID_CTR` in `temp.ID_CTR`, iar `PF:13755` in + `VANZARI_DETALII.ID_CTR`, deci legatura se pierde permanent; + - `V_ID_POL = NULL` — **acelasi defect, alta coloana**: `PF:5258` duce `V_ID_POL` in `temp.ID_POL`, + `PF:13737` in `VANZARI_DETALII.ID_POL`; in plus ar rupe ramura aviz, care potriveste pe + `A.ID_POL = V_ID_POL` (`PF:5096`). **Nu o propun** — o mentionez ca sa nu fie redescoperita ca + „solutie" intr-o runda urmatoare. + +**Concluzie: decizia 54 cere obligatoriu modificare in `pack_facturare`.** + +### 6.2 Ce forma trebuie sa aiba semnalul + +Doua forme sunt inerte pentru apelantii de azi: + +- **(i) variabila noua de pachet** („regenerare in curs"), implicit `0` = comportamentul actual, scrisa + de VFP **dupa** `initializeaza_date_factura` si inainte de bucla de `adauga_articol_factura`; +- **(ii) parametru `DEFAULT 0`** adaugat **dupa** `V_LOT` in semnatura — apelantii de azi trimit 27 de + argumente pozitional si raman valizi. + +**Recomand (i)**, din trei motive concrete: se potriveste cu stilul existent al pachetului (care e deja +o punga de stare de sesiune); se scrie o data pe document, nu o data pe linie, in toate produsele; si +**punctul de resetare exista deja** — `initializeaza_date_factura` (`PF:1808-1917`) reinitializeaza +~40 de globale si face `DELETE FROM VANZARI_DETALII_TEMP` (`PF:1835`), deci flag-ul nu poate scapa in +documentul urmator din aceeasi sesiune. Atentie la ordine: VFP il seteaza **dupa** apelul de la +`ofacturare.vc2:13981`, nu inainte. Ambele variante modifica spec-ul, deci ambele forteaza +recompilarea dependentilor — nu e un criteriu de departajare. + +### 6.3 Ce face semnalul, cand e pornit + +**Nimic nou** — forteaza ramura care exista deja. Cea mai mica forma: un `WHEN` nou, **primul** in +`CASE`-ul de la `PF:5052`, cu **acelasi corp ca `ELSE`-ul de azi** (`PF:5189-5203`): `PROC_TVAV` din +`JTVA_COLOANE` pe `V_ID_JTVA_COLOANA`, si `V_PRET`/`V_ID_VALUTA`/`V_PRETURI_CU_TVA`/`V_IN_STOC` din +parametrii `*_TEMP`. `INSERT`-ul de la `PF:5222` ramane neatins. Acopera dintr-o data **toate trei** +ramurile problematice (contract, aviz, comenzi), pentru ca toate sunt in acelasi `CASE`. + +### 6.4 Trei consecinte de acceptat explicit, nu ocolite + +1. **`IN_STOC` ar veni din formular** (`poArt.gestionabil`) in loc de `NOM_ARTICOLE`. Nu e o problema + de fidelitate a documentului — `VANZARI_DETALII` **nu are coloana `IN_STOC`** (verificat pe DB) — + ci de **comportament de stoc** la reemitere. Ca reemiterea sa fie identica, incarcarea (S8) trebuie + sa dea valoarea cu care s-a scris documentul initial. **Azi nu o da**: loader-ul lui #6 o citeste + din nomenclatorul curent — `ofacturare_editare.prg:302-303`, + `left join nom_articole na on na.id_articol = v.id_articol`, cu comentariul care spune ca view-ul + n-o expune. **Flag-ul singur nu rezolva asta.** +2. **Se pierde o validare pe ramura comenzi.** `A.PRET = V_PRET_TEMP` (`PF:5077`) plus lipsa lui + `EXCEPTION` fac azi ca o linie care nu se mai potriveste cu comanda sa **opreasca** scrierea. + Cu flag-ul pornit, reemiterea unei „facturi din comanda" nu mai verifica linia fata de comanda. + Decizia 54 pare sa vrea exact asta, dar e o schimbare de comportament care trebuie spusa, nu + descoperita la S12. +3. **`PROC_TVAV` ramane derivat**, nu preluat. Reproducerea exacta a documentului cere ca S8 sa + pastreze `ID_JTVA_COLOANA` **si** ca `JTVA_COLOANE.COTA_TVA` sa nu se fi schimbat intre timp. Daca + se cere reproducere exacta si peste o modificare de cota, `PROC_TVAV` trebuie sa devina + **parametru** — schimbare mai mare decat flag-ul, de decis separat. + +### 6.5 Un obstacol de incarcare, gasit in treacat (pentru S8, nu pentru S10) + +View-ul pe care se sprijina incarcarea de azi, **`VVANZARI_ARTICOLE`**, expune (interogat pe DB): +`ID_VANZARE, ID_VANZARE_DET, ID_ARTICOL, CANTITATE, PRET, PRET_CU_TVA, PROC_TVAV, DISCOUNT_UNITAR, +ID_GESTIUNE, CONT, ID_VALUTA, ID_JTVA_COLOANA, SERIE, EXPLICATIE, TAXCODE, LOT, STERS, +ID_VANZARE_SET, PRET_ACHIZITIE, DENUMIRE, CODMAT, NUME_GESTIUNE, NUME_VAL`. + +**Nu expune `ID_POL` si nu expune `ID_CTR`** — exact cei doi pe care `adauga_articol_factura` ii cere +(`V_ID_POL`, `V_ID_CTR`) si pe care `VANZARI_DETALII` ii pastreaza. Reemiterea S9 **nu poate folosi +acest view ca atare**; ori se completeaza view-ul, ori loader-ul citeste direct din +`VANZARI_DETALII`. Nu e in perimetrul S10, dar blocheaza S9 daca nu e prins din timp. + +--- + +## Tabel sintetic — cele cinci valori pe ramura + +„temp" = valoarea trimisa de formular; „re-derivat" = citita din alta sursa si suprascrisa. + +| valoare | **contract** (2/6/26/52, `OPT_FACTURARE` NULL sau 3) | **aviz** (`ntip = 4`) | comenzi (3/21/28/42/47) | restaurant (45) | `ELSE` | +|---|---|---|---|---|---| +| `PRET_UNITAR` (`PRET`) | **re-derivat** din `CTR_ARTICOLE.PRET_UNITAR` daca `<> 0`; **temp** daca `= 0`; **NULL** daca e NULL (`PF:5149`) | **re-derivat** din `VANZARI_DETALII` al avizului, **neconditionat** (`PF:5082`) | **temp** de facto — `WHERE A.PRET = V_PRET_TEMP` (`PF:5077`); nepotrivire = ORA-01403 | **temp** (`PF:5109`) | **temp** (`PF:5200`) | +| `PROC_TVAV` | **re-derivat** din `CRM_POLITICI_PRET_ART` (`PF:5150`) | **re-derivat** din avizul sursa (`PF:5083`) | **re-derivat** din `COMENZI_ELEMENTE.PTVA` / politica (`PF:5058`) | **re-derivat** din `JTVA_COLOANE` (`PF:5114`) | **derivat** din `JTVA_COLOANE` pe `ID_JTVA_COLOANA` din formular (`PF:5189`) | +| `ID_VALUTA` | **re-derivat** din `CRM_POLITICI_PRET_ART` (`PF:5151`) | **re-derivat** din avizul sursa (`PF:5084`) | **re-derivat** din politica (`PF:5059`) | **temp** (`PF:5110`) | **temp** (`PF:5201`) | +| `PRET_CU_TVA` | **re-derivat** din `CTR_ARTICOLE` (`PF:5152`) | **re-derivat** din avizul sursa (`PF:5085`) | **re-derivat** din `CRM_POLITICI_PRETURI` (`PF:5060`) | **temp** (`PF:5111`) | **temp** (`PF:5202`) | +| `IN_STOC` | **re-derivat** din `NOM_ARTICOLE` (`PF:5153`) | **re-derivat** din `NOM_ARTICOLE` (`PF:5086`) | **re-derivat** din `NOM_ARTICOLE` (`PF:5061`) | **temp** (`PF:5112`) | **temp** (`PF:5203`) | + +`PROC_TVAV` **nu vine din temp pe nicio ramura** — nu e parametru al procedurii. +`IN_STOC` **nu ajunge in `VANZARI_DETALII`** (coloana nu exista) — traieste doar in temp, unde decide +descarcarea de gestiune. +Pe ramura contract, `EXCEPTION WHEN NO_DATA_FOUND` (`PF:5167-5185`) muta **toate cele cinci** pe +coloana „temp"; ramurile aviz si comenzi **nu au** un asemenea bloc. + +## Verificat direct vs. dedus + +**Verificat direct pe fisier SI confirmat pe DB** (`all_source`, `MARIUSM_AUTO.PACK_FACTURARE`, +`PACKAGE BODY`, VALID, `last_ddl_time = 2026-08-09 20:03:50`): +granitele lui `adauga_articol_factura`; toate cele cinci ramuri ale `CASE`-ului, ramura cu ramura; +textul integral al ramurii aviz; faptul ca insertul procedurii merge in `VANZARI_DETALII_TEMP`; ca +exista exact doua `INTO VANZARI_DETALII` in tot pachetul. + +**Verificat direct pe fisier** (nereconfirmat separat pe DB, dar in aceeasi zona cu offset masurat): +copierea 1:1 temp -> `VANZARI_DETALII` (`PF:13705-13757`); cele patru `UPDATE VANZARI_DETALII` si ce +coloane ating; mutatiile pe temp intre `adauga_articol_factura` si `scrie_in_vanzari`; corpul lui +`pack_auto.actualizeaza_deviz`. + +**Verificat pe metadatele DB**: `VANZARI_DETALII` nu are `IN_STOC`, are `STERS`; +`CTR_ARTICOLE.PRET_UNITAR` e `nullable=Y`; lista de coloane a lui `VVANZARI_ARTICOLE`. + +**Verificat direct in VFP**: apelantii lui `adauga_articol_factura`; maparea celor 27 de parametri; +`ntip := poDate.Tip`; loader-ul `IncarcaArticoleFactura` (`ofacturare_editare.prg:292-327`). + +**Dedus, nerulat**: comportamentul `DECODE` cu `PRET_UNITAR` NULL (semantica Oracle, nu test); +ORA-01403 / ORA-01422 pe ramurile fara `EXCEPTION` (din absenta blocului, nu din reproducere). + +**Din date de dev — NU e dovada, nu extrapolati**: `CTR_ARTICOLE` are **27** de randuri +(0 NULL, 10 cu `PRET_UNITAR = 0`, 17 cu `<> 0`) — deci **cazul NULL nu apare in dev**, ceea ce nu +spune nimic despre productie. `CONTRACTE.OPT_FACTURARE`: 176 NULL, 53 = 0, 9 = 1, 3 = 3, 1 = 4 — +adica pentru **176 + 3 din 242** de contracte `NVL(OPT_FACTURARE, 3)` da 3 si ramura re-deriva. + +**Neacoperit**: n-am rulat niciun flux real (nici VFP, nici o reemitere efectiva) — totul e analiza +statica plus interogari `SELECT` pe metadate. N-am citit corpurile `scrie_factura2` si +`finalizeaza_factura` integral, doar punctele in care ating `VANZARI_DETALII` / temp. + +## Corectii la rapoartele anterioare + +### `docs\cercetare\s10_pret_rederivat.md` (runda 9) + +1. **„exista exact o ramura care suprascrie pretul — contractul" — FALS.** Ramura **aviz** + (`ntip = 4`, `PF:5080-5103`) suprascrie `PRET` **neconditionat**, fara `DECODE` si fara filtru pe + pretul din formular — **mai agresiv decat contractul**. Ramura **comenzi** (`PF:5053-5078`) nu + suprascrie `PRET`, dar suprascrie celelalte patru si **arunca ORA-01403** la nepotrivire. + Ramurile care re-deriva sunt **trei**, nu una. +2. **„pe calea de scriere" — corect ca moment, gresit ca destinatie.** Se intampla la salvare, dar + insertul e in **`VANZARI_DETALII_TEMP`** (`PF:5222`), nu in `VANZARI_DETALII`. Diferenta conteaza: + explica de ce copierea „neschimbata" de mai tarziu nu protejeaza nimic. +3. **„nu exista niciun parametru sau flag care sa opreasca re-derivarea" — CONFIRMAT**, acum cu dovada + pozitiva (semnatura `PF:4989-5015`, globalele `PF:126-212`), nu prin absenta. + +### `docs\cercetare\s10_pret_contract_reemitere.md` (runda 13) + +1. **„acelasi `SELECT` alimenteaza cinci valori" — corect** (`PF:5149-5166`), dar de completat: + **nu merg impreuna** — `PRET` are `DECODE`, celelalte patru sunt neconditionate, si vin din trei + tabele diferite. +2. **„`IN_STOC` din nomenclatorul curent" — corect**, de completat cu faptul decisiv: + **`VANZARI_DETALII` nu are coloana `IN_STOC`** (verificat pe DB). Nu ajunge in document; efectul e + pe descarcarea de gestiune. +3. **„`scrie_in_vanzari` copiaza `PRET` neschimbat din temp" — corect** (`PF:13705-13757`), de marcat + insa ca **nu e o protectie**: dauna e amonte de temp. +4. Varianta **„avertizare + confirmare" ramane respinsa** (decizia 54) — nu am reargumentat-o. + +### `docs\plan_13_unificare_formular_facturare.md` + +- Trimiterea „`initializeaza_date_factura` (`PACK_FACTURARE:1808-1846`)" — corpul incepe la `PF:1808` + dar se termina la **`PF:1917`**. Citarile `:1835` / `:1836` din plan sunt corecte. diff --git a/docs/cercetare/s11_legaturi_id_vanzare.md b/docs/cercetare/s11_legaturi_id_vanzare.md new file mode 100644 index 0000000..8035429 --- /dev/null +++ b/docs/cercetare/s11_legaturi_id_vanzare.md @@ -0,0 +1,379 @@ +# S11 — Legaturile care raman pe `ID_VANZARE` la editarea prin regenerare + +Stare: **in lucru**. Vezi sectiunea STARE / CE RAMANE la final pentru progres curent. + +## Context (dat, nu se rediscuta) + +#13 etapa II: editarea unei facturi = regenerare. Documentul vechi e soft-sters (`STERS=1`) si +reemis in aceeasi tranzactie. `ID_FACT`, seria, numarul si data se pastreaza. **`ID_VANZARE` se +schimba** — documentul reemis primeste un `id_vanzare` nou din secventa. + +Sarcina: inventarul complet al legaturilor pe `VANZARI.ID_VANZARE` care se rup la aceasta +schimbare, clasificate (A) trebuie remigrat / (B) trebuie sters-refacut / (C) nu conteaza. + +## 0. Puncte de plecare (de verificat, nu de preluat pe incredere) + +- `docs\plan_13_unificare_formular_facturare.md` — sectiunea `#### S11` (linia ~3024) +- `docs\cercetare\idfact_refolosire_si_documente.md` +- `docs\cercetare\rec_cale_vanzari_detalii.md` +- `COMUN\docs\cercetare\rec_consumatori_vanzari.md` +- `docs\cercetare\cont_venit_corespondente.md` +- `docs\cercetare\legatura_linie_retur.md` +- `docs\cercetare\rec_d42_efactura.md` +- `docs\cercetare\s5c_factura_din_proforma.md` (sectiunea `VANZARI_CORESP`) +- PL/SQL: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` + +## 0-bis. Constatare centrala care schimba premisa din plan + +**Mecanismul S9 (planificat) NU e acelasi cu mecanismul deja existent de editare (#6).** Astea sunt +doua cai distincte, si asta conteaza pentru fiecare consumator de mai jos: + +- **#6, "editare directa"** (`frm_modific2024`/`omodificari.vc2`, `afisjurcom.do_modifica`, + `pack_contafin.finalizeaza_modificare_nota`): documentul vechi (identificat prin `cod`) primeste + `STERS=1` in `ACT`/`RUL`, se scrie un document nou cu `cod` nou, apoi + **`pack_facturare.actualizeaza_vanzari(V_COD_VECHI, V_COD_NOU)`** + (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16012-16022`) face + `UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI` — + **randul `VANZARI` e REFOLOSIT, `ID_VANZARE` NU SE SCHIMBA**, doar `COD`. In continuare, + `finalizeaza_modificare_nota` face si `UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod = + tnCod` (`rec_modific2024.md:126`, confirmat `fisier:linie`). Acesta e mecanismul deja **livrat in + productie** (git log: `#6 editare factura emisa`, changelog 2.11.15/2.11.16). +- **#13 S9 (planificat, subiectul acestei cercetari)**: reemiterea merge **pe drumul normal de + emitere**, `scrie_factura2` -> `finalizeaza_factura` -> `scrie_in_vanzari`, care face + `INSERT INTO VANZARI (...) VALUES (...) RETURNING ID_VANZARE INTO pack_facturare.nid_vanzare` + (`rec_cale_vanzari_detalii.md:99`, confirmat separat in planul S9: „reemiterea scrie prin + `pack_facturare`, pe acelasi drum ca emiterea", `plan_13...md:2936-2937`). Acesta e un rand **nou**, + cu **`ID_VANZARE` nou din secventa** — exact premisa data de team-lead. `oscrie_in_fisiere` + intervine in S9 **doar la stergerea** documentului vechi (`plan_13...md:2938`), nu si la scriere, + deci **`actualizeaza_vanzari`/sincronizarea prin `finalizeaza_modificare_nota` NU se declanseaza + pentru S9** — acel mecanism ramane specific caii #6, nu se mosteneste automat de S9. + +**Consecinta directa**: orice legatura tinuta azi prin `cod` (realiniata gratis de mecanismul #6) e +**expusa** la regenerarea din #13, pentru ca S9 nu trece prin `actualizeaza_vanzari`. Fiecare sectiune +de mai jos noteaza explicit daca protectia #6 s-ar fi aplicat sau nu. + +## 1. Inventarul exhaustiv al consumatorilor de ID_VANZARE + +| # | Tabela/mecanism | Cheie folosita | Scriitor(i) | Cititor(i) | Clasificare | +|---|---|---|---|---|---| +| 1 | `ATASAMENTE_VANZARI` | `ID_VANZARE` (FK real `FK_AT_VANZ001` -> `VANZARI.ID_VANZARE`) + coloana `COD` (legacy, vezi §2) | `ofacturare_comun.prg:466` (VFP INSERT direct), `pack_facturare.scrie_atasamente_factura` (PL/SQL, `ff_...:13955-13988`) | `VATASAMENTE_VANZARI` (view, JOIN pe `id_vanzare`), `ROAGEST`/`ROAIMOB` `oproceduri_atasamente.prg` (`citeste_atasament_vanzari`, `arata_meniu_at_vanz` — cauta pe `cod` via view) | **(A) trebuie remigrat** — vezi §2 | +| 2 | `marcheaza_facturat` -> `VANZARI.FACTURAT`/`ID_UTILFACT` | `VANZARI_CORESP.ID_VANZARE_FACT` (documentul nou) leaga la sursa | `pack_facturare.marcheaza_facturat`, apelata din `finalizeaza_factura` (`ff_...:14827,14831`) | citit la filtrarea avizelor/comenzilor facturabile | **(C) nu conteaza pentru discontinuitatea ID_VANZARE-ului documentului editat** — vezi §3 (releaga sursa, nu documentul editat insusi) | +| 3 | `VANZARI_CORESP` | `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ`, ambele pe `VANZARI.ID_VANZARE` | `pack_facturare.scrie_corespondente_vanzari`, singurul writer (`s5c_factura_din_proforma.md:92-96`) | `sterge_factura` (garda la stergere), afisare "provine din X" | **(A) trebuie remigrat daca documentul editat e parte a unui lant retur/aviz->factura** — vezi §3 | +| 4 | `VANZARI_CANTITATI` | `ID_VANZARE` (a avizului sursa, nu a facturii editate) | `scrie_cantitati_vanzari_avize`, apelata doar `ntip=4` | `marcheaza_facturat(V_VERIFICARE=1)` | **(C) nu conteaza** — vezi §3, tine de sursa, neatinsa de editarea facturii | +| 5 | `DOCUMENTE` | `ID_DOC` = `ID_FACT` (nu `ID_VANZARE`) | `SET_IDFACT`/INSERT in `SCRIE_IN_ACT` | `SET_IDFACT` cautare veche (inactiva) | **(C) confirmat fara legatura cu ID_VANZARE** — vezi §4, acoperit deja de `idfact_refolosire_si_documente.md`, nu se reface | +| 6 | eFactura (`ANAF_EFACTURA`, `EsteInEFactura`) | `VANZARI.ID_FACT` (nu `ID_VANZARE`) | — | `ofacturare_editare.prg`/`omodificari.vc2` | **(C) nu conteaza** — cheia e `ID_FACT`, care se pastreaza — vezi §5 | +| 7 | Listari/rapoarte facturi (`FACT_VFACTURI*`, grid cautare) | `ID_VANZARE` + `ID_FACT`/serie+numar, cautari mixte | — | multiple | **de verificat, vezi §5** — cele care cauta dupa serie+numar/ID_FACT raman corecte; cele care tin un `ID_VANZARE` stocat undeva (ex. favorite, ultima factura deschisa) s-ar rupe | +| 8 | Incasari/plati (`INCASARI`, `PLATI`, `IREG_PARTENERI`) | **de verificat** | — | — | **in lucru, vezi §6** | + +## 2. ATASAMENTE_VANZARI + +**Nu sunt documente incarcate de utilizator — sunt exporturi PDF AUTO-GENERATE ale documentului +tiparit** (factura/aviz/recapitulatie/invoice), salvate automat dupa listare. Dovada, pas cu pas: + +- Scrierea porneste din `frm_facturi.do_listeaza_formular`/fluxul de listare, la + `COMUN\programe\ofacturare.prg:2102-2105`: + ``` + If poDate.nRelistare = 0 And poDate.eProforma = 0 AND poDate.nEFactura = 0 And poDate.nSalveazaAtasamente = 1 + poDate.scrieAtasamente() + Endif + ``` + imediat dupa exportul PDF al recapitulatiei (`:2068`, `goExport.export2pdf('crsrecapitulatie', + 'recapitulatie', .F., poDate.cDocAtasate)`) — `poDate.cDocAtasate` **e** numele cursorului + `crsoDateDocAtasate`, populat de motorul de export PDF, nu de un dialog de upload. +- `crsoDateDocAtasate` se creeaza gol la `ofacturare.prg:233`: `Create Cursor crsoDateDocAtasate + (nume_frx c(50), fisier w)` — nicio referinta la `GETFILE()`/`GETPICT()`/dialog de fisier in tot + `ROAFACTURARE`/`COMUN` legata de acest cursor (cautat explicit, zero potriviri). +- `scrieAtasamente` (`ofacturare_comun.prg:429-475`) clasifica `TIP` dupa numele raportului + (`FACTURA_VAL*`->2, `FACTURA*`->1, `INVOICE*`->3, `RECAPITULATIE`->4, `AVIZ*`->5) si scrie: + `INSERT INTO ATASAMENTE_VANZARI(ID_VANZARE, TIP, FORMAT, DOCUMENT, ID_UTIL) VALUES (?pnId, ...)` + (`:466`) — `pnId = poDate.nid_vanzare` (sau `nid_vanzare_retur` pentru cazul aviz->factura). Deci + scriitorul **foloseste exclusiv `ID_VANZARE`**, niciodata `COD`. +- Cititorii confirmati: `ROAGEST\COMUN\programe\oproceduri_atasamente.prg` (identic in `ROAIMOB`) — + `citeste_atasament_vanzari` (`SELECT document FROM atasamente_vanzari WHERE id_at_vanz=...`) si + `arata_meniu_at_vanz(tnCod,...)` (`SELECT ... FROM vatasamente_vanzari a WHERE a.cod = ...`, + `:56`) — ambele read-only, mecanism de "vezi documentele salvate pentru aceasta factura", + disponibil din ROAGEST/ROAIMOB (alte produse ale suitei, confirmand ca S11 trebuia sa caute in + toata suita, nu doar ROAFACTURARE). + +**Structura reala (interogata pe schema vie `MARIUSM_AUTO`)**: `ATASAMENTE_VANZARI(ID_AT_VANZ PK +NOT NULL din secventa, ID_VANZARE NUMBER NULL, DOCUMENT BLOB, TIP, FORMAT, STERS NOT NULL, ID_UTIL, +DATAORA NOT NULL, ID_UTILS, DATAORAS, COD NUMBER NULL)`. FK real: `FK_AT_VANZ001` pe `ID_VANZARE -> +VANZARI.ID_VANZARE` (`PK_VANZARI`). Coloana `COD` **nu e scrisa de niciun cod curent** (scriitorii +gasiti folosesc doar `ID_VANZARE`) — e populata azi doar de mecanismul legacy #6 +(`finalizeaza_modificare_nota`'s `UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod = tnCod`, +care **nu poate seta `cod` pe un rand care nu-l are deja** — e un realiniere, nu o initializare). +Pe schema de test `MARIUSM_AUTO`, toate cele 20 de randuri existente au `COD` populat si +`ID_VANZARE` NULL (probabil date vechi, dinainte ca `ID_VANZARE` sa fie coloana activa) — cf. +memoriei de proiect, **zero cazuri in date nu e dovada**, dar dovada de cod (scriitorii folosesc +doar `ID_VANZARE`) e independenta de date si sta singura. + +**Cheia reala de legatura azi e `ID_VANZARE`, prin view**: `VATASAMENTE_VANZARI` (definitie +interogata direct din `ALL_VIEWS`): +```sql +select a.id_at_vanz, a.tip, decode(a.tip,1,'Factura',2,'Factura in valuta',3,'Invoice', + 4,'Recapitulatie',5,'Aviz','Alt document') as tip_doc, b.cod + from atasamente_vanzari a + left join vanzari b on a.id_vanzare = b.id_vanzare + where a.sters = 0 and b.sters = 0 +``` +`cod` in view **nu e coloana stocata pe `atasamente_vanzari`** — e `VANZARI.COD` curent, calculat +live prin JOIN pe `ID_VANZARE`. Consumatorii din alta suita (`ROACONT\Programe\orap_terti.prg:1854`, +`ROACONTRACTE\Programe\oparteneri_contracte.prg:190,229` — `LEFT JOIN vatasamente_vanzari ... ON +a.cod = b.cod`, pentru afisarea unui numar de atasamente pe rand de factura) folosesc de fapt tot +`ID_VANZARE`, indirect prin acest JOIN. + +**Ce se rupe la regenerare (S9, calea B din §0-bis)**: `atasamente_vanzari.id_vanzare` al randurilor +vechi ramane neschimbat, aratand spre `VANZARI` cu `STERS=1` dupa stergerea documentului vechi. +`WHERE b.sters = 0` din view **filtreaza acele randuri afara** — atasamentele **dispar tacit** din +orice interogare prin `VATASAMENTE_VANZARI` (inclusiv `arata_meniu_at_vanz` din ROAGEST/ROAIMOB), +desi BLOB-ul ramane fizic in tabel. Documentul nou (`ID_VANZARE` nou) nu are niciun atasament legat. +**Clasificare: (A) trebuie remigrat** — `UPDATE atasamente_vanzari SET id_vanzare = :id_nou WHERE +id_vanzare = :id_vechi AND sters = 0`, in aceeasi tranzactie ca restul regenerarii (acelasi tipar ca +`actualizeaza_vanzari` de la #6, dar pe `id_vanzare` in loc de `cod`, si trebuie scris nou — #6 nu +acopera acest caz, cf. §0-bis). + +**Corectie fata de premisa din briefing**: nu e "pierdere de date reala" in sensul de date +introduse de utilizator si irecuperabile — snapshot-ul se poate regenera prin relistare (acelasi +mecanism care l-a creat prima data). Ce s-ar pierde real e **istoricul exact al PDF-ului trimis +clientului la momentul emiterii initiale** (relevant daca factura a fost deja trimisa/tiparita +inainte de editare) — merita remigrare oricum, ca sa nu se piarda urma, dar motivatia corecta e +"pastrarea unui audit trail", nu "date introduse de utilizator". + +## 3. marcheaza_facturat / VANZARI.FACTURAT / VANZARI_CANTITATI / VANZARI_CORESP + +**Inventarul FK declarat pe `VANZARI.ID_VANZARE`** (interogare directa pe schema `ACN`, cea reala — +`ALL_CONS_COLUMNS`/`ALL_CONSTRAINTS`, `r_constraint_name = PK_VANZARI`), confirma si extinde lista +din briefing: + +| Tabela | Coloana | Constraint | +|---|---|---| +| `ATASAMENTE_VANZARI` | `ID_VANZARE` | `FK_AT_VANZ001` | +| `VANZARI_CORESP` | `ID_VANZARE_FACT` | `FK_VANZARI_CORESP_001` | +| `VANZARI_CORESP` | `ID_VANZARE_AVIZ` | `FK_VANZARI_CORESP_002` | +| `VANZARI_CURSURI` | `ID_VANZARE` | `FK_VANZARE_CURS_002` | +| `VANZARI_DETALII` | `ID_VANZARE` | `FK_VANZARE_DET001` | +| `REST_NOTE_PLATA` | `ID_VANZARE` | `FK_REST_NOTE_PLATA_004` | +| `IPS_VOYAGES_VANZARI` | `VZ_ID` | `FK_VOYAGES_VANZARI_2` | + +Primele cinci erau asteptate (vezi §1-2 si mai jos). **Ultimele doua nu erau in lista din plan** — +descoperite prin FK, nu prin cautare de text, exact motivul pentru care inventarul trebuia facut pe +schema, nu doar pe cod. Ambele sunt module de business **specifice unui singur tip de document** +(`pack_facturare.ntip`), nu cai generale: + +- **`REST_NOTE_PLATA`** — modul restaurant (`PACK_RESTAURANT`, `nTipFacturaRestaurant`). La + stergerea unei vanzari de tip restaurant, `sterge_factura` cheama deja + `pack_restaurant.sterge_vanzare(V_ID_VANZARE, V_ID_UTIL)` (`ff_...:5593`, in `CASE`-ul de la + finalul procedurii) — un hook dedicat, separat de logica generala. **Nu s-a gasit un hook simetric + „re-leaga la reemitere"** in codul citit (nu era in scop sa se citeasca tot `PACK_RESTAURANT` — + pachet separat, mii de linii). Clasificare: **(A) suspecta, needs follow-up dedicat** — afecteaza + doar documentele cu `ntip = nTipFacturaRestaurant`, deci doar daca #13 include si acest tip in + „regenerabile" (de confirmat in `COMUN\docs\tipuri_documente_facturare.md`, cf. S13). +- **`IPS_VOYAGES_VANZARI`** — modul specific schemei `ACN` (`PACK_ACN`, `nTipFacturaACN`), legat de + un tabel `IPS_VOYAGES_VANZARI`/`IPS_VOYAGE_MEMBERS_VANZARI`/`IPS_VVOYAGE_MEMBERS` (confirmat in + `ris_2024_04_09_01_ACN.sql:258-266`, JOIN pe `vz.id_vanzare = vv.vz_id`) — pare o extensie de + business pentru un client specific (calatorii/voiaje), nu parte din suita generica ROA. Acelasi + tipar: `sterge_factura` cheama `pack_acn.sterge_vanzare(...)` la stergere (`ff_...:5596-5600`, + `execute immediate` conditionat de `pack_migrare.ObjectExist('PACK_ACN')` — pachetul exista doar + pe schema ACN). Clasificare: **(A) suspecta, needs follow-up dedicat** — probabil in afara + perimetrului #13 (client unic, tip de factura special), dar trebuie confirmat, nu presupus. + +**`VANZARI_CURSURI`** — cursul valutar al documentului. Scris de `pack_facturare.scrie_cursuri(nid_vanzare)` +(`rec_cale_vanzari_detalii.md:102`), apelat **in interiorul aceluiasi `scrie_in_vanzari`** care +genereaza noul `ID_VANZARE` la reemitere (S9 foloseste exact acest drum, cf. §0-bis). Deci un rand +nou `VANZARI_CURSURI` se scrie automat pentru noul `ID_VANZARE`, fara nicio interventie separata. +**Clasificare: (B)** — se reface singur, ca parte a drumului normal de emitere, nimic de adaugat. + +**`VANZARI_DETALII`** — liniile facturii. E miezul a ceea ce regenerarea insasi scrie (INSERT direct +pe noul `ID_VANZARE`, cf. `rec_cale_vanzari_detalii.md:105-113`) — nu e un consumator "extern" care +sa se rupa, e obiectul regenerarii. **Clasificare: (B)**, deja acoperit de proiectarea S9/S10, nu se +reface aici. + +### VANZARI_CORESP — cazul netratat inca de plan: documentul editat e EL INSUSI parte a unui lant + +Planul (S11, `plan_13...md:3024-3027`) trateaza `VANZARI_CORESP` ca pe o lista simpla, dar +`sterge_factura` (apelata de S9 la pasul de stergere) arata ca situatia are **doua fete diferite**, +niciuna simpla: + +**(i) Garda de blocare — editarea unui document care e SURSA pentru altul e azi IMPOSIBILA, nu +riscanta.** `sterge_factura` (`ff_...:5450-5494`) verifica, INAINTE de orice stergere: +```sql +SELECT COUNT(*) INTO V_NR_FACT_RETUR FROM VANZARI_CORESP + WHERE STERS=0 AND ID_VANZARE_AVIZ = V_ID_VANZARE AND TIP = 3; +IF V_NR_FACT_RETUR > 0 THEN RAISE_APPLICATION_ERROR(-20000, 'Pentru acesta factura s-au emis facturi + de retur. Trebuie sa stergeti mai intai factura de retur!'); END IF; +``` +si analog pentru `TIP IN (1,2)` (aviz cu factura/aviz de retur emise pe el) si pentru avizele-sursa +ale unei „facturi din aviz" (subquery imbricat, `:5478-5494`). **Consecinta pentru S9**: daca +utilizatorul incearca sa editeze (regenereze) o factura care are deja o factura de retur emisa +pe ea, sau un aviz care are deja o factura/aviz de retur emis pe el, **pasul de stergere din +tranzactia S9 arunca `ORA-20000`, tranzactia face rollback, documentul vechi ramane intact** — nu +e coruptie silentioasa, e un esec curat, dar e o **limitare functionala reala**: aceste documente +nu pot fi editate prin regenerare deloc, cat timp garda ramane activa (si nu exista niciun motiv +funcțional sa fie dezactivata — ar contrazice exact protectia pe care garda o ofera azi). *De +verificat de S12*: ca mesajul de eroare Oracle ajunge inteligibil la utilizator prin formularul de +editare, nu ca o eroare tehnica generica. + +**(ii) Legaturile in care documentul editat e chiar `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ` — marcate +`STERS=1` la stergere, NU remigrate automat, dar potential rescrise de reemitere daca parametrii +sunt refurnizati corect.** La stergerea documentului vechi, `sterge_factura` executa neconditionat +(`:5582-5585`): +```sql +UPDATE VANZARI_CORESP SET STERS = V_STERS + WHERE ID_VANZARE_FACT = V_ID_VANZARE OR ID_VANZARE_AVIZ = V_ID_VANZARE; +``` +— orice legatura in care documentul vechi apare pe oricare parte devine `STERS=1`. **Nu exista cod +care sa recreeze automat legatura pentru noul `ID_VANZARE`.** Dar exista o cale prin care se +recreeaza **corect, de la sine**, daca S9 e proiectat sa refoloseasca drumul normal: `finalizeaza_factura` +(chemata de reemiterea normala) are un `CASE` pe `pack_facturare.ntip` care scrie din nou +corespondenta (`WHEN ntip=4: scrie_corespondente_vanzari(1)`; `WHEN ntip=24: +scrie_corespondente_vanzari(2)`; `WHEN ntip IN (8,9): scrie_corespondente_vanzari(3)`, +`ff_...:14823-14836`) — **daca** S9 seteaza `pack_facturare.ntip` la aceeasi valoare ca documentul +original SI re-populeaza `pack_facturare.clistaid`/`clistaid_avize` cu lista de `id_vanzare` sursa +originala **inainte** de reemitere, corespondenta se scrie natural, cu noul `ID_VANZARE_FACT`. +**Sursa lista originala e recuperabila** din randurile `VANZARI_CORESP` (acum `STERS=1`) ale +documentului vechi, citite INAINTE de stergere in aceeasi tranzactie — un pas suplimentar pe care +proiectarea S9 trebuie sa-l includa explicit, nu e „gratis". **Clasificare: (A) trebuie remigrat +— dar prin re-derivare + refolosirea mecanismului existent, nu prin UPDATE direct pe `VANZARI_CORESP`** +(un UPDATE direct ar trebui sa distinga cu grija cele doua roluri — FACT vs AVIZ — si ar risca sa +resusciteze legaturi catre documente inca sterse din alte motive; re-emiterea naturala prin `ntip`+ +`clistaid` e mai sigura, pentru ca refoloseste exact logica deja validata de emiterea normala). + +### marcheaza_facturat — pentru sursa documentului editat, nu pentru documentul insusi + +`marcheaza_facturat` (`ff_...:15381-15418`) actioneaza pe **sursa** (avizul din care s-a facturat, +sau avizul-parinte al unui aviz de retur), nu pe documentul care se editeaza. La stergerea +documentului vechi (S9, pasul de stergere), pentru `V_TIP=4`/`V_TIP=24`, `sterge_factura` reseteaza +deja `FACTURAT=0`/`ID_UTILFACT=NULL` pe sursa (`:5504-5510`, `:5525-5533`) si marcheaza +`VANZARI_CANTITATI.STERS=1` pentru cantitatile consumate (`:5517-5523`, `:5535-5540`) — sursa e deci +**eliberata** corect de mecanismul EXISTENT, neschimbat. La reemitere, daca `ntip`/`clistaid_avize` +sunt refurnizate corect (acelasi argument ca la VANZARI_CORESP mai sus), `finalizeaza_factura` +cheama din nou `scrie_cantitati_vanzari_avize` + `marcheaza_facturat` (`:14825-14827`,`:14831`), +**reconsumand** sursa sub noul `ID_VANZARE_FACT`. **Clasificare: (C) — mecanismul deja exista si +functioneaza simetric (elibereaza la stergere, reconsuma la scriere), CONDITIONAT de aceeasi +cerinta ca la VANZARI_CORESP: S9 trebuie sa refurnizeze `ntip` si `clistaid`/`clistaid_avize` +originale la reemitere.** Aceasta e exact conditia pe care S12 o testeaza explicit („sursa eliberata +si reconsumata", `plan_13...md:3037`) — S11 confirma DE CE mecanismul poate functiona (nu e nevoie +de cod nou pentru asta), dar nu inlocuieste testul S12. + +## 4. DOCUMENTE / ID_DOC — legatura cu ID_VANZARE + +**Confirmat: fara legatura.** `DOCUMENTE(ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT, +DATAACT, DATAIREG, ID_CTR, ID_SET, STERS, ...)` — INSERT-ul complet citat in +`idfact_refolosire_si_documente.md:143-148` (`PACK_CONTAFIN.pck:788-817`, cod activ) nu are nicio +coloana `ID_VANZARE`. Cheia `ID_DOC = ID_FACT`, care se PASTREAZA la regenerare (decizie E/F, deja +data). Acest raport nu reface analiza — deja acoperita exhaustiv de +`docs\cercetare\idfact_refolosire_si_documente.md` (S9, punctele A-C), care stabileste separat +conditiile pentru pastrarea `ID_FACT` (variabila noua de sesiune in `SET_IDFACT` + upsert real in +`DOCUMENTE`). **Clasificare: (C) — confirmat, fara actiune ceruta de S11.** + +## 5. Borderou eFactura si listari + +**eFactura: cheia e `ID_FACT`, nu `ID_VANZARE` — confirmat cu dovada proprie, independenta.** +`EsteInEFactura` (`COMUN\programe\ofacturare_editare.prg`, apelata din `ofacturare_comun.vc2:3764` +cu `lnIdFact = crsfacturi.id_fact`) verifica prezenta documentului in `ANAF_EFACTURA` **pe +`VANZARI.ID_FACT`**, nu pe `id_vanzare` — confirmat explicit ca eroare corectata in +`rec_d42_efactura.md:81-109` (implementarea initiala folosea gresit `id_vanzare`, corectata dupa +verificare pe date: `id_fact` si `id_vanzare` sunt spatii de ID complet diferite, 0 coincidente pe +142 facturi testate). **Cum `ID_FACT` se pastreaza la regenerare (decizie deja luata), `EsteInEFactura` +continua sa functioneze corect pe documentul reemis, fara nicio schimbare.** Aceeasi concluzie se +aplica probabil borderoului eFactura insusi (cautarea documentelor de trimis/trimise), dar +**construirea borderoului nu a fost citita in acest raport** (in afara scopului — cheia `ID_FACT` +fiind deja confirmata stabila, riscul e mic, dar afirmatia stricta „borderoul il gaseste corect" +ramane de verificat direct pe cod la implementare, nu doar dedusa). **Clasificare: (C), cu rezerva +de verificare directa a borderoului la implementare.** + +**Listari/grid cautare facturi**: cursoarele de grid (`crsFacturi`, `crsFacturiOrd`, +`ofacturare_comun.vc2:3982,4134-4145,4983`) se reconstruiesc live din Oracle la fiecare deschidere +a formularului de listare, filtrate pe `sters=0` — nu tin niciun `id_vanzare` persistat intre +sesiuni. Documentul reemis (nou `id_vanzare`, acelasi `id_fact`/serie/numar) apare normal la +urmatoarea reincarcare a gridului. **Singurul risc identificat, nu confirmat ca problema reala**: +daca undeva in cod exista un `id_vanzare` **retinut pe termen lung** in afara sesiunii curente a +formularului (ex. „ultima factura deschisa", favorite, shortcut) — nu s-a gasit niciun asemenea +mecanism in codul citit (`ofacturare_comun.vc2`, `ofacturare.prg`), dar nici nu a fost cautat +exhaustiv in tot `ROAFACTURARE` (in afara bugetului acestei cercetari). **Clasificare: (C), +neconfirmat ca risc real — verificare suplimentara recomandata daca timpul permite.** + +## 6. Incasari/plati si riscuri finale + +**Cautare directa (negativa, dar utila): niciun tabel de incasari/plati nu are FK declarat pe +`VANZARI.ID_VANZARE`.** Interogarea exhaustiva `ALL_CONS_COLUMNS`/`ALL_CONSTRAINTS` pe schema `ACN` +(tabelul din §3) e completa pentru FK-uri declarate — `INCASARI`, `PLATI`, `IREG_PARTENERI` nu apar +in acea lista. Cautare text suplimentara (`grep -rn "ID_VANZARE"` filtrat pe fisiere cu +"incasari"/"plati"/"chitant" in nume, in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR`) — **zero rezultate**. +Coerent cu arhitectura deja cunoscuta: incasarile/platile se leaga de facturi prin **`ID_FACT`** +(coloana pe `ACT`/`IREG_PARTENERI`, cf. `idfact_refolosire_si_documente.md`), nu prin `ID_VANZARE` +— `ID_FACT` se pastreaza la regenerare, deci **incasarile/platile nu sunt afectate structural**. +**Clasificare: (C), cu dovada dubla (schema + text), coerenta cu memoria de proiect „zero cazuri in +date nu e dovada" — aici insa e absenta de FK + absenta de text, nu absenta de date, deci e o +dovada structurala, nu doar un data point.** + +## Rezumat clasificari + +| Consumator | Clasificare | Actiune ceruta | +|---|---|---| +| `ATASAMENTE_VANZARI.ID_VANZARE` | **(A)** | `UPDATE ... SET id_vanzare=:nou WHERE id_vanzare=:vechi AND sters=0`, in tranzactia S9 | +| `VANZARI_CORESP` (documentul editat ca FACT/AVIZ al altui lant) | **(A)**, prin re-emitere naturala | S9 trebuie sa citeasca `ntip`+lista sursa originala INAINTE de stergere si sa le refurnizeze la reemitere | +| `marcheaza_facturat`/`VANZARI_CANTITATI` (sursa documentului editat) | **(C)**, conditionat | functioneaza automat DACA `ntip`/`clistaid` sunt refurnizate (acelasi mecanism ca mai sus) | +| `VANZARI_CURSURI` | **(B)** | se rescrie automat de `scrie_in_vanzari`, nimic de facut | +| `VANZARI_DETALII` | **(B)** | e obiectul regenerarii, acoperit de S9/S10 | +| `DOCUMENTE`/`ID_DOC` | **(C)** | confirmat fara legatura, acoperit de `idfact_refolosire_si_documente.md` | +| eFactura (`EsteInEFactura`, `ANAF_EFACTURA`) | **(C)** | cheie `ID_FACT`, pastrat — verificare directa a borderoului recomandata la implementare | +| Listari/grid facturi | **(C)** | cursoare live, fara stare persistata gasita | +| Incasari/plati | **(C)** | fara FK, fara referinta text — cheie e `ID_FACT` | +| `REST_NOTE_PLATA` (modul restaurant) | **(A)** suspecta | needs follow-up dedicat, `PACK_RESTAURANT` necitit integral | +| `IPS_VOYAGES_VANZARI` (modul ACN specific client) | **(A)** suspecta | needs follow-up dedicat, `PACK_ACN` necitit integral, posibil in afara perimetrului #13 | + +## Riscuri si ce ramane de decis de Marius + +1. **Editarea documentelor care sunt sursa unui lant retur/aviz e azi BLOCATA, nu doar riscanta.** + `sterge_factura` arunca `ORA-20000` daca documentul editat are deja facturi/avize de retur emise + pe el (§3.i). Nu e o eroare de proiectare — garda protejeaza integritatea lantului — dar + inseamna ca „editare prin regenerare" **nu va functiona deloc** pentru aceste documente, cat timp + utilizatorul nu sterge intai documentele-copil. **De decis**: acceptat ca limitare (cu mesaj + clar in UI, tradus din eroarea Oracle), sau se doreste alt comportament? +2. **Recuperarea `VANZARI_CORESP`/`marcheaza_facturat` la reemitere depinde de un pas pe care planul + nu-l mentioneaza explicit**: citirea listei sursa originale (`ID_VANZARE_AVIZ`/`ID_VANZARE_FACT` + din randurile `VANZARI_CORESP` ale documentului vechi) **inainte** de stergere, si refurnizarea + ei ca `pack_facturare.clistaid`/`clistaid_avize` la reemitere. Fara acest pas, corespondenta si + `marcheaza_facturat` **nu se scriu deloc** pentru documentul reemis (nu eroare, ci scriere lipsa + silentioasa) — de adaugat explicit in proiectarea S9, nu presupus „vine gratis" din drumul normal. +3. **`ATASAMENTE_VANZARI` nu e „date de utilizator pierdute"**, cf. §2 — e un audit trail de + PDF-uri auto-generate la listare. Remigrarea (`UPDATE ... SET id_vanzare`) e simpla si ieftina, + dar motivatia corecta pentru Marius e „pastrarea istoricului tiparit", nu „pierdere de date + introduse manual" — poate schimba prioritatea relativa fata de alte itemi din S11/S12. +4. **`REST_NOTE_PLATA` si `IPS_VOYAGES_VANZARI`** raman needs-investigation — descoperite prin FK, + nu prin text, deci nu erau in raza cautarilor anterioare (nici in planul S11 original). Daca + tipurile de document asociate (`nTipFacturaRestaurant`, `nTipFacturaACN`) nu sunt in domeniul + „regenerabile" al #13 (de confirmat in `tipuri_documente_facturare.md`, S13), riscul dispare de + la sine — dar asta trebuie confirmat, nu presupus. +5. **Borderoul eFactura insusi** (nu doar `EsteInEFactura`) nu a fost citit direct — cheia `ID_FACT` + fiind stabila, riscul e mic, dar afirmatia din *Gata cand* a S11 („documentul reemis se regaseste + corect in borderoul eFactura") merita o verificare punctuala pe cod la implementare, nu doar + dedusa din `EsteInEFactura`. + +## STARE / CE RAMANE + +**Cercetare incheiata pentru toate cele 7 puncte cerute de briefing**, cu dovada `fisier:linie` pe +fiecare afirmatie portanta: inventar exhaustiv (via FK Oracle + cod), `ATASAMENTE_VANZARI` (sectiune +proprie, cu corectia premisei „date de utilizator"), `marcheaza_facturat`/`VANZARI.FACTURAT`/ +`VANZARI_CANTITATI`, `DOCUMENTE`/`ID_DOC` (confirmat, fara redeschidere), borderou eFactura si +listari, riscuri finale. + +**Constatare centrala**: mecanismul S9 planificat (regenerare cu `ID_VANZARE` nou, pe drumul normal +de emitere) e **diferit** de mecanismul #6 deja livrat (`actualizeaza_vanzari`, reutilizeaza acelasi +`ID_VANZARE`, doar `COD` se schimba) — deci nicio protectie construita pentru #6 nu se mosteneste +automat de S9. Singurul consumator care chiar are nevoie de un `UPDATE` explicit nou este +`ATASAMENTE_VANZARI` (clasificare A simpla). `VANZARI_CORESP`/`marcheaza_facturat` se pot rezolva +**fara cod PL/SQL nou**, refolosind mecanismul existent (`finalizeaza_factura`'s `CASE` pe `ntip`), +**daca** S9 e proiectat sa citeasca si sa refurnizeze parametrii sursei originale — asta e o cerinta +de design care trebuie scrisa explicit in S9, nu deductibila implicit. + +**Neverificat, ramas pentru follow-up** (declarat, nu ascuns): corpul complet al `PACK_RESTAURANT`/ +`PACK_ACN` (pentru `REST_NOTE_PLATA`/`IPS_VOYAGES_VANZARI`), borderoul eFactura citit direct (nu doar +`EsteInEFactura`), o cautare exhaustiva de „id_vanzare retinut pe termen lung" in tot codul VFP +(favorite/shortcut-uri), si confirmarea in `tipuri_documente_facturare.md` a caror tipuri intra +efectiv in perimetrul „regenerabile" al #13 (ar elimina sau confirma riscurile 1 si 4). + +Zero modificari de cod, zero write-back, zero `git_sync.ps1`/`txt2vcx.ps1`, zero commit. Pe Oracle +doar `SELECT` (schema `MARIUSM_AUTO`, interogari pe `ALL_TAB_COLUMNS`/`ALL_CONSTRAINTS`/`ALL_VIEWS`/ +`ALL_SOURCE`, plus `SELECT` de numarare pe date de test — niciuna cu efect de scriere). diff --git a/docs/cercetare/s2_factureaza_unificare.md b/docs/cercetare/s2_factureaza_unificare.md new file mode 100644 index 0000000..67c7099 --- /dev/null +++ b/docs/cercetare/s2_factureaza_unificare.md @@ -0,0 +1,318 @@ +# S2 — Proiectare: o singura procedura `factureaza` + +Cercetare read-only pentru povestea **S2** din `docs\plan_13_unificare_formular_facturare.md`. +Niciun fisier de cod atins, `git_sync.ps1` nerulat, niciun write-back, niciun commit. + +## Verdict + +**Se poate unifica, si e mai simplu decat pare.** `factureaza2` nu e o a doua implementare +funcţională care trebuie fuzionata cu grija — e un fork din 08.06.2017 la care s-a **dezactivat +execuţia interogarii de articole** (`lnSucces = 1` hardcodat, `goExecutor.oExecute` niciodata +apelat) si mai multe blocuri intregi sunt inchise cu `If .F.`. Are **exact un singur apelant** in +tot codul (`ofacturare.prg:90`, din interiorul lui `factureaza` insusi, in spatele unui +`AMESSAGEBOX` de confirmare) si **zero utilizatori reali** dincolo de acel switch de test. Riscul de +regresie e mic pentru ca nu exista nimic funcţional de pierdut in `factureaza2` — obstacolul +principal nu e tehnic, e ca formularul spre care duce (`frm_facturare_articole2`) e tot un prototip +neterminat (`Init` de 92 de linii, nu completeaza antetul), deci unificarea proceduri lor **nu** +face formularul nou utilizabil — doar elimina duplicarea de cod. Asta ramane treaba lui S3. + +--- + +## 1. Inventarul celor doua proceduri + +Ambele sunt proceduri globale (nu metode de clasa) in **`COMUN\programe\ofacturare.prg`**, incarcat +via `SET PROCEDURE ... ADDITIVE` din `Programe\roafacturare.prg`. Nu exista omonime — verificat cu +`Get-ChildItem -Recurse *.prg | Select-String '^\s*(PROCEDURE|FUNCTION)\s+factureaza2?\b'` pe tot +`ROAFACTURARE` si cu Grep pe `.vc2` din `COMUN`: un singur rezultat pentru fiecare nume. + +| | `factureaza` | `factureaza2` | +|---|---|---| +| Locatie | `COMUN\programe\ofacturare.prg:81-577` (497 linii) | `COMUN\programe\ofacturare.prg:583-1081` (499 linii) | +| Parametri | `LPARAMETERS tnTip, toFactura` (`:82`) — `toFactura` = obiect, pentru copiere/modificare | `LPARAMETERS tnTip` (`:584`) — **fara** al doilea parametru | +| Apelanti | ~20+ puncte de intrare reale (sectiunea 3) | **unul singur**: `ofacturare.prg:90`, din `factureaza` | + +`ofacturare.prg` in sine e cod comun al **intregii suite**: exista o copie identica-la-origine in +`COMUN\programe\ofacturare.prg` a fiecarui produs ROA verificat (ROACONT, ROAGEST, ROACONTRACTE, +ROAAUTO, ROAACNPRO, ROAIMOB, plus alte ~30 produse) — fiecare cu propriile `factureaza`/`factureaza2` +la linii apropiate (de regula `:76-77` sau `:87-88` pentru comutatorul `gnFacturareNou`, dupa +versiune). Nu exista in `COMUNROA` (biblioteca partajata separata) — e sincronizat manual intre +produse ca fisier COMUN, nu ca livrabil COMUNROA. + +## 2. Diff-ul semantic + +Comparatie linie-cu-linie, cu citate `ofacturare.prg:linie`. **Sase din cele noua diferente listate +in sectiunea A a planului se confirma exact; una e imprecisa; una lipseste din plan.** + +### Ce face `factureaza` si nu face `factureaza2` + +| Ce | `factureaza` | `factureaza2` | +|---|---|---| +| Tipurile 51 (ROAACNPRO), 52 (contract factura fiscala valuta) rutate la FACTURA | `Inlist(tnTip,45,48,49,**51,52**)` la `:192` si `:222` | `Inlist(tnTip,45,48,49)` la `:674` si `:700` — **51/52 lipsesc**, ar cadea in `Otherwise` -> AVIZ | +| Ramura `llCopiere`/`toFactura` (copiere/modificare factura) | declarata `:102`, populata `:111`, folosita la completarea datelor (`:200-206`), la construirea interogarii (`Case m.llCopiere` -> `cursor_retur_document`, `:267-268`), si la adaugarea articolelor din lista de preturi peste cele copiate (`:454-474`) | **absenta complet** — nicio declarare a `llCopiere`, niciun tratament al lui `toFactura` | +| Deschiderea `frm_date_factura`/`frm_date_aviz` | activa, cu `toFactura` transmis constructorului (`:229-235`) | inchisa cu `If .F.` (`:707-711`) | +| Filtrul `RORTC` pe `jtva_coloane` | `AND !'RORTC'$UPPER(coloana_jv)` la ambele ramuri (`:243`, `:245`) | absent (`:715`, `:717`) | +| Executia interogarii de articole | `lnSucces = goExecutor.oExecute(lcSqlCursor, lcCursor)` (`:311`) | **inchisa cu `If .F.`** (`:823-826`); imediat dupa, `lnSucces = 1` hardcodat (`:827`) — cursorul SQL construit la `:748-822` nu ruleaza niciodata | +| Verificarea "nu exista articole" | activa (`:324-328`) | **inchisa cu `If .F.`** (`:838`) — combinata cu `lnSucces=1` de mai sus, ramura merge mereu pe calea "am articole", indiferent de continut | +| Zeroizarea `gestionabil` pe proforma | `IF poDate.eProforma = 1 ... UPDATE (lcCursor) SET gestionabil = 0` (`:333-336`) | **absenta** — `eProforma` nu apare nicaieri in `factureaza2` (verificat cu grep pe tot fisierul) | +| `crspolitici` / `crscontracte` (populare combo-uri) | necondiţionate (`:420-440`) | **ambele inchise cu `If .F.`** (`:956-968`, `:969-980`) | +| `GetInstitutiePublica` | `poDate.institutie_publica = GetInstitutiePublica(...)` (`:509`) | **absenta** | +| `codnc8`/`codcpv` pe fiecare linie | `Replace codnc8 WITH ..., codcpv WITH ...` in `Scan` (`:516-517`), pe langa `codmatc` | **doar `codmatc`** (`:1027`) — `codnc8`/`codcpv` lipsesc | +| `GetSoldClient` + `poDate.sold_lei`/`sold_valuta` inainte de listare | prezent, in ramura non-bon-fiscal (`:530-533`) | **absent** — ramura echivalenta (`:1040-1041`) sare direct la `listeaza_ofacturare()` | +| Formularul de articole | `frm_facturare_articole` (`:395`) | `frm_facturare_articole2` (`:929`) — **singura diferenta functionala reala** care justifica existenta lui `factureaza2` | + +### Corectii fata de sectiunea A a planului + +- **"`verifica_numar(16, ...)` pentru chitanta" — planul GRESESTE.** E prezent **identic** in ambele: + `factureaza:502-504` si `factureaza2:1018-1020` (`If poDate.incasat <> 0 ... + poGeneratorNumere.verifica_numar(16, poDate.nr_incasare) Endif`). Nu lipseste din `factureaza2`. +- **"`cursor_avize` cu tip 23" — planul e imprecis.** Nu exista nicio asociere `cursor_avize`/tip 23 + in niciuna din cele doua proceduri (`cursor_avize` trateaza doar `tnTip = 4`, identic in ambele, + `:294-295` / `:774-775`). Ce difera real pentru tipul 23: in `factureaza`, tipul 23 e prins in + `Case Inlist(tnTip,1,22,5,29,7,10,**23**)` -> `cursor_preturi` (`:279-282`); in `factureaza2`, 23 + **nu** e in acea lista (`:758`, doar `1,22,5,29,7,10`) si cade in schimb in + `Case Inlist(tnTip,**23**,41)` -> `cursor_gestiune` (`:776-778`). E o rutare complet diferita + pentru acelasi tip de document, nu o simpla lipsa — **de adaugat explicit la lista din sectiunea A**. +- **Nementionat in plan — tipul 52 lipseste si din `Do Case` de contract**: `factureaza` + `Case Inlist(tnTip,2,26,6,**52**)` (`:283`) vs `factureaza2` `Case Inlist(tnTip,2,26,6)` (`:762`) — + aceeasi lipsa a tipului 52 ca la nIdTipDoc, dar intr-un loc separat din cod; **tipul 24** (aviz de + retur) lipseste similar din `Case Inlist(tnTip,8,9,**24**)` (`:306`) vs `Case Inlist(tnTip,8,9)` + (`:819`). + +### Ce fac amandoua identic (verificat, nu presupus) + +Structura comentariilor `lnIdSet`/`pnTipFacturare`, bucla `Do While lnRaspuns = 6`, apelul +`actualizeaza_optiuni_program()`, crearea `poDate`/`poGeneratorNumere`, alegerea `frm_date_factura` / +`frm_date_aviz_lucrare` / `frm_date_aviz` pentru antet (desi in `factureaza2` deschiderea propriu-zisa +e dezactivata), tratarea `tnTip=30` (aviz din NIR, inclusiv bucla `Scatter`/`Insert Into crsfactura` +identica caracter-cu-caracter), `verifica_numar(16,...)`, dezalocarea celor trei numere +(`nIdTipDoc`, 16, 3/26) la abandon, si intreaga bucla finala de `AMESSAGEBOX` +"Doriti sa continuati" + `CloneObj`/`poDate.Reset()`. + +### Ce face `factureaza2` si nu face `factureaza` + +Un singur lucru: verificari defensive suplimentare de tip la iesirea timpurie (`pnButon = 2`): +``` +IF TYPE('poDate.nr_incasare') = 'N' + poDate.nr_incasare = 0 +ENDIF +IF TYPE('poDate.incasat') = 'N' + poDate.incasat = 0 +ENDIF +``` +(`factureaza2:727-732`) — `factureaza` face resetarea echivalenta necondiţionat, dar mai tarziu, in +ramura de succes (`:546-547`), nu pe calea de abandon. Diferenta e cosmetica, nu comportamentala pe +fluxul normal. + +## 3. Toti apelantii + +**Cautare in tot arborele `D:\ROA`** (fiecare produs + `COMUN`-ul lui + `COMUNROA`), cu capcana +"Grep nu vede COMUN" tratata explicit (path dat direct pe fiecare `COMUN`, nu doar pe radacina +produsului — altfel cautarea sare tacut peste el, per gitignore-ul fiecarui produs). + +**Niciun produs verificat (ROACONT, ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB) nu apeleaza `factureaza` +direct din cod propriu — nici in `COMUN`, nici in partea specifica produsului.** Singura urma in +`COMUN`-urile lor e propria copie a `ofacturare.prg`/`oproceduri_facturare.prg` (definitia, nu un +apel). Confirma independent observatia din `coresp_cont_venchelt.md` ca ROAACNPRO nu trece prin acest +flux (foloseste `pack_acn.salveaza_regdoc`, nu `contabilizeaza_articol`). **Exceptia: ROACONTRACTE**, +care are un apel real, specific produsului, la wrapper-ul `facturare_contracte`. + +### Apelanti directi ai `factureaza(N)` + +| Apelant | Produs | Parametri | Tip document | +|---|---|---|---| +| `Meniuri\politica.mn2:12` | ROAFACTURARE | `factureaza(1)` | lista de preturi, lei | +| `Meniuri\politica.mn2:15` | ROAFACTURARE | `factureaza(8)` | retur factura lei | +| `Meniuri\politica.mn2:18` | ROAFACTURARE | `factureaza(45)` | restaurant | +| `Meniuri\politica.mn2:26` | ROAFACTURARE | `factureaza(49)` | marfa custodie fara descarcare K | +| `Meniuri\politica.mn2:29` | ROAFACTURARE | `factureaza(48)` | marfa custodie cu descarcare K | +| `Meniuri\politica.mn2:37` | ROAFACTURARE | `factureaza(5)` | lista de preturi, valuta (invoice) | +| `Meniuri\politica.mn2:40` | ROAFACTURARE | `factureaza(7)` | credit note | +| `Meniuri\politica.mn2:43` | ROAFACTURARE | `factureaza(10)` | factura fiscala valuta | +| `Meniuri\politica.mn2:46` | ROAFACTURARE | `factureaza(9)` | retur factura valuta | +| `Meniuri\contracte.mn2:12` | ROAFACTURARE | `factureaza(2)` | contract, lei | +| `Meniuri\contracte.mn2:15` | ROAFACTURARE | `factureaza(6)` | contract, valuta | +| `Meniuri\contracte.mn2:18` | ROAFACTURARE | `factureaza(52)` | contract, factura fiscala valuta | + +### Apelanti prin `COMUN\programe\oproceduri_facturare.prg` (dispecer comun, un wrapper per grup de tipuri) + +| Wrapper (`oproceduri_facturare.prg`) | -> `factureaza(N)` | Apelat din | +|---|---|---| +| `facturare_contracte(tcTip)` (`:119-136`) | `factureaza(2)`/`factureaza(6)`/`factureaza(52)` dupa `tcTip` | `ROAFACTURARE\Clase\ofundal_facturare.vc2:890-894`, `ROAFACTURARE\Ferestre\fundal.sc2:924`, **`ROACONTRACTE\Clase\ferestre_contracte.vc2:1542-1546`** (produs diferit, apel real) | +| `facturare_comenzi()` (`:139-141`) | `factureaza(3)` | `COMUN\clase\ocomenzi.vc2:1580-1596`, metoda `do_factura` — `SCATTER NAME goComanda MEMO` inainte de apel | +| `facturare_avize()` (`:144-146`) | `factureaza(4)` | `ROAFACTURARE\Clase\ofundal_facturare.vc2:905`, `ROAFACTURARE\Ferestre\fundal.sc2:932` | +| `copiere_factura(toFactura)` (`:150-153`) | `factureaza(toFactura.Tip, toFactura)` | `COMUN\clase\ofacturare_comun.vc2:3710` — fisierul de perimetrul #6, doar citit, neatins | +| `facturare_lista_de_preturi()` (`:114-116`) | `Do politica.mpr` -> popup-ul din `politica.mn2` (vezi tabelul de mai sus) | `ROAFACTURARE\Clase\ofundal_facturare.vc2:883`, `ROAFACTURARE\Ferestre\fundal.sc2:920` | +| `emitere_aviz_clienti(tnTip)` (`:198-228`) | `factureaza(21/22/26/24)` dupa `tnTip=1/2/3/7`; `tnTip=4,5,6` -> `initializeaza_vanzare_din_stoc`, nu `factureaza` | `ROAFACTURARE\Meniuri\aviz_clienti.mn2:21,47,51,55,59,63,67` | +| `emitere_aviz_clienti_debitori(tnTip)` (`:230-243`) | `factureaza(28/29)` dupa `tnTip=1/2` | `ROAFACTURARE\Meniuri\aviz_clienti_debitori.mn2:22,26` | +| `emitere_aviz_clienti_custodie(tnTip)` (`:244-259`) | `factureaza(42/47)` dupa `tnTip=1/2`; `tnTip=3` -> `initializeaza_vanzare_din_stoc` | `ROAFACTURARE\Meniuri\aviz_clienti_custodie.mn2:30,34,38` | +| `emitere_aviz_transfer(tnTip)` (`:261-280`) | `factureaza(25/23/27/30/41)` dupa `tnTip=1..5` | `ROAFACTURARE\Meniuri\aviz_subunitati.mn2:36,40,44,48,52` | + +**Total: ~33 puncte de intrare reale in cod pentru `factureaza`, doar unul pentru `factureaza2`** +(`ofacturare.prg:90`, in interiorul lui `factureaza`, gatit de `gnFacturareNou=1` + confirmare DA la +`AMESSAGEBOX`). + +## 4. Rolul lui `gnFacturareNou` + +**Nu e un comutator de productie — e un switch de dezvoltator, fara nicio persistenta.** + +- **Declarat:** nicaieri. Cautare exhaustiva (`gnFacturareNou`, tot `D:\ROA`, extensii + `.prg/.vc2/.sc2/.mn2`) — singura aparitie e exact linia care il citeste (`ofacturare.prg:88`, sau + liniile echivalente in fiecare copie de produs). Nu exista intr-un ecran de optiuni, nu e coloana + intr-un tabel de configurare (spre deosebire de `gnScadereStoc`, `gl406` etc., citite din + `oinit_optiuni.prg`). +- **Citit:** o singura data, `ofacturare.prg:88` — `If Type('gnFacturareNou') = 'N' And + m.gnFacturareNou = 1`. +- **Valoare implicita:** inexistenta ca variabila -> `Type()` intoarce `'U'` (Undefined), deci + conditia e falsa implicit. Devine `.T.` doar daca cineva a facut manual, in sesiunea VFP curenta + (de regula din fereastra de comenzi a IDE-ului), `gnFacturareNou = 1`. +- **Cine il seteaza:** nimeni, in cod. E gandit sa fie setat manual de un dezvoltator care vrea sa + testeze prototipul. +- **Confirma sau infirma ca e comutator intre proceduri, nu intre formulare?** Azi, chiar intre + **proceduri** — `factureaza:88-93` face `Return factureaza2(m.tnTip)` dupa raspunsul DA la dialog, + adica sare complet in cealalta procedura, cu tot ce lipseste din ea (sectiunea 2). Planul (S2, + linia 1423) vrea sa-l transforme in comutator **intre formulare**, in interiorul unei singure + proceduri — schimbare corecta, pentru ca azi dubleaza cod, nu doar UI. + +## 5. Proiectarea unificarii + +### Semnatura + +**Neschimbata la aceasta poveste:** `factureaza(tnTip, toFactura)`. Parametrul de sursa explicit +(comanda/contract) e treaba lui **S3c**, care depinde de S2 si il trateaza separat — nu se amesteca +aici (planul insusi spune "Atinge cod comun intregii suite — nu se face impreuna cu alta +modificare"). + +### Ramificarea interna + +Nu e nevoie de o rescriere — `factureaza` de azi e deja versiunea completa si functionala. Interventia +e chirurgicala: + +1. **Muta verificarea `gnFacturareNou`** de la inceputul procedurii (azi `:88-93`, unde face + `Return factureaza2(...)`) la **punctul unde se alege `lcObject`** pentru formularul de articole + (azi `:395` in `factureaza`, ramura corespunzatoare celei din `factureaza2:929`): + ``` + Case tnTip = 27 + lcObject = [frm_avizare_lucrare] + Otherwise + lcObject = IIF(m.llFacturareNoua, [frm_facturare_articole2], [frm_facturare_articole]) + Endcase + ``` + unde `llFacturareNoua` e calculat **o singura data**, la fel ca azi (`Type('gnFacturareNou')='N' + And gnFacturareNou=1`, urmat de acelasi `AMESSAGEBOX` DA/NU), dar **fara** `Return` in alta + procedura — doar seteaza flagul si continua executia normala. +2. **Tot restul ramane identic** — cursorul de articole, `eProforma`, `codmatc`/`codnc8`/`codcpv`, + `GetInstitutiePublica`, `GetSoldClient`, filtrul `RORTC`, rutarea tipurilor 51/52/23/24, ramura + `llCopiere`/`toFactura` — pentru ca acestea nu au nicio legatura cu care formular de articole se + deschide. `factureaza2` nu avea o varianta "corecta, dar diferita" a acestor blocuri — pur si + simplu nu le avea, pentru ca fusesera dezactivate in graba la prototipare (`If .F.` peste tot), + nu pentru ca noul formular ar avea nevoie de alt comportament acolo. +3. **Sterge `factureaza2`** in intregime (`:583-1082`, inclusiv bannerul `INCEPUT`/`SFARSIT`). + +### Ordinea, ca suita sa nu fie stricata niciun moment + +Riscul de "moment in care suita e stricata" e mic aici, spre deosebire de S3c — pentru ca +`factureaza2` **nu are alt apelant decat el insusi prin `factureaza`**. Ordinea propusa: + +1. Muta logica de selectie a lui `lcObject` (pasul 1 de mai sus) **inainte** de a sterge + `factureaza2` — la acest punct, codul are temporar ambele cai active (comutatorul nou + + `factureaza2` inca existent, dar orfan). Se poate testa manual ca `gnFacturareNou=1` deschide + `frm_facturare_articole2` din interiorul lui `factureaza`, cu restul comportamentului corect + (spre deosebire de azi, unde deschiderea prin `factureaza2` sare peste `eProforma`, `codnc8` etc.) +2. Abia dupa verificare, **sterge `factureaza2`** — pasul e sigur pentru ca nu mai are niciun apelant + (linia 90, singurul, a fost deja inlocuita la pasul 1). +3. **Un singur fisier se schimba**: `COMUN\programe\ofacturare.prg`. Niciun apelant din tabelul de + la punctul 3 nu are nevoie de nicio modificare — toti cheama `factureaza(N)` cu aceeasi semnatura, + niciunul nu cheama `factureaza2` direct. +4. **ROACONTRACTE nu necesita nicio schimbare** la aceasta poveste — apelul lui la + `facturare_contracte` -> `factureaza(2/6/52)` nu atinge `factureaza2` deloc. + +## 6. Riscurile de regresie + +- **Cel mai probabil sa se rupa:** ramura `llCopiere`/`toFactura`, pentru ca e cea mai complexa + bucata de logica prezenta doar in `factureaza` (`cursor_retur_document`, apoi suprapunerea cu + `cursor_preturi` pentru a permite adaugarea de articole noi peste cele copiate, `:454-474`). Nu + are nicio acoperire azi in `factureaza2` de comparat — deci orice greseala de copy-paste la mutarea + liniei `lcObject` risca sa rupa exact aceasta ramura, nu partea "noua". **Testat manual din + `copiere_factura` (`ofacturare_comun.vc2:3710`) inainte si dupa.** +- **Al doilea cel mai probabil:** rutarea tipurilor 23/24/51/52, pentru ca azi cad in `Do Case`-uri + diferite intre cele doua proceduri (sectiunea 2) — o gresala de aliniere la stergerea lui + `factureaza2` nu ar afecta functional (pentru ca acele Do Case-uri raman neatinse, doar duplicate + disparute), dar merita o trecere vizuala pe fiecare tip listat la punctul 3 dupa modificare. +- **ROACONTRACTE** — singurul apelant real din alt produs. **Nu poate fi testat din arborele de lucru + ROAFACTURARE** — cere un build/rulare separata a ROACONTRACTE, cu propriul `ofacturare.prg` + sincronizat manual (nu e acelasi fisier fizic, e o copie). Verificarea "gata" trebuie sa includa + explicit macar un test manual pe ROACONTRACTE, tip contract-factura, nu doar pe ROAFACTURARE. +- **`frm_facturare_articole2` insusi ramane netestabil functional** la aceasta poveste — `Init`-ul + lui nu completeaza antetul (S1), deci deschiderea cu `gnFacturareNou=1` va arata un formular cu + campuri goale la fel ca azi. Nu e o regresie introdusa de S2, dar e o asteptare de gestionat: S2 + **nu** face `frm_facturare_articole2` utilizabil, doar elimina procedura duplicata din spatele lui. +- **Nimic din asta e testabil headless** — toate cele ~33 puncte de intrare sunt declansate din + meniuri/formulare UI (`ON SELECTION BAR`, `DO ... IN ...` din metode de clic), iar rezultatul se + verifica prin `ACT`/`VANZARI`/`RUL` in Oracle sau vizual pe formular. Testarea de dupa modificare + trebuie sa fie manuala, minim pe: o factura lista de preturi lei (tip 1), un aviz din comanda (tip + 21), o factura din contract lei (tip 2, din `ROACONTRACTE` daca fezabil), o copiere de factura + (`toFactura` populat), si — cu `gnFacturareNou=1` setat manual — confirmarea ca + `frm_facturare_articole2` se deschide fara eroare (nu neaparat ca arata corect, asta e S3). + +## 7. Criteriul de "gata", rescris si verificabil + +Planul spune azi: *"`factureaza2` nu mai exista, iar comutatorul alege formularul, nu procedura."* +Rescris, verificabil fara ambiguitate: + +1. **Structural** (verificabil cu o comanda, fara sa citesti codul): + ``` + Get-ChildItem -Recurse 'D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare.prg' | + Select-String -Pattern '^\s*(PROCEDURE|FUNCTION)\s+factureaza2\b' + ``` + trebuie sa intoarca **zero rezultate**. + ``` + Select-String -Path 'D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare.prg' -Pattern 'gnFacturareNou' + ``` + trebuie sa intoarca **exact o aparitie**, situata in interiorul lui `factureaza`, la punctul unde + se alege `lcObject` (nu inainte de constructia lui `poDate`, ca azi). +2. **Comportamental**, testat manual, fara `gnFacturareNou` setat (calea implicita, majoritatea + utilizatorilor): cele patru documente listate la sectiunea 6 (factura lista de preturi, aviz din + comanda, factura din contract, copiere factura) produc aceleasi randuri in `ACT`/`VANZARI`/`RUL` + ca inainte de modificare. +3. **Comportamental**, cu `gnFacturareNou = 1` setat manual si DA la dialog: se deschide + `frm_facturare_articole2` (nu `frm_facturare_articole`), fara eroare la deschidere, cu + `eProforma`/`codnc8`/`codcpv`/`GetInstitutiePublica`/`GetSoldClient`/filtrul RORTC/rutarea + 51-52-23-24 toate active identic cu calea implicita (spre deosebire de azi, cand + `gnFacturareNou=1` le sarea pe toate). +4. **ROACONTRACTE**: minim un test manual de facturare din contract, dupa sincronizarea + `ofacturare.prg` in acel produs, confirma acelasi rezultat ca inainte. + +## Ce nu s-a putut stabili si de ce + +- **Cine activeaza popup-urile `politica.mn2`/`contracte.mn2`** (nivelul chiar deasupra apelurilor + directe la `factureaza(N)`) nu a fost trasat pana la butonul/grid-ul exact — nu era necesar pentru + diff-ul procedurilor sau pentru tabelul de apelanti (punctul de interes e apelul la `factureaza`, + nu inca un nivel de indirectare deasupra), dar daca implementarea muta si aceste meniuri, merita + o trecere separata. +- **Daca alte produse (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB, ROACONT) au propriul lor + `oproceduri_facturare.prg`/`ocomenzi.vc2` invocat din cod specific produsului, dincolo de ce am + cautat** — cautarea a acoperit exact numele `factureaza`/`factureaza2` si cei noua wrapperi din + `oproceduri_facturare.prg`; nu a acoperit eventuale cai complet diferite catre acelasi fisier + (de exemplu un meniu `.mn2` specific unui alt produs care ar chema un wrapper cu alt nume). Pentru + aceste cinci produse, cautarea directa (`factureaza(`, cei noua wrapperi) a dat zero rezultate in + codul specific produsului — suficient pentru concluzia "nu apeleaza azi", dar nu e o dovada + negativa absoluta de "nu ar putea apela niciodata prin alt canal". +- **Testarea reala pe ROACONTRACTE** nu a fost efectuata (read-only, fara rulare de cod) — doar + confirmata existenta apelului in sursa (`ferestre_contracte.vc2:1542-1546`). +- **Diferenta minora de text** intre mesajele "Luna inchisa" ale celor doua proceduri + (`factureaza:105` vs `factureaza2:594`, un caracter diacritic afisat diferit la citire) nu a fost + investigata mai departe — pare artefact de encoding la citire (cp1250 in fisier), nu o diferenta + de continut intentionata; oricum dispare odata cu stergerea lui `factureaza2`. + +## Bug-uri semnalate, nereparate + +- **`factureaza2` e, in forma actuala, non-functional dincolo de deschiderea formularului de + antet dezactivata** — chiar daca cineva ar apela azi acest cod pe o cale ipotetica ocolind + `factureaza`, executia interogarii SQL e dezactivata (`lnSucces = 1` hardcodat la `:827`, fara + `goExecutor.oExecute`) si verificarea "nu exista articole" e dezactivata (`:838`) — codul ar merge + mereu pe ramura de succes cu un cursor `crsarticole` nepopulat de aceasta rulare (posibil ramas + dintr-un apel anterior, cu alt `tnTip`). Nu se repara aici — S2 il sterge oricum, deci bug-ul + dispare odata cu procedura, nu prin fix separat. +- Niciun bug nou gasit in `COMUN\clase\ofacturare_comun.vc2` sau `COMUN\programe\ofacturare_editare.prg` + (perimetrul #6) — singura atingere a acestor fisiere in aceasta cercetare a fost citirea liniei + `copiere_factura` din `ofacturare_comun.vc2:3710`, care nu are nimic suspect. diff --git a/docs/cercetare/s3_portare_antet.md b/docs/cercetare/s3_portare_antet.md new file mode 100644 index 0000000..4c5560a --- /dev/null +++ b/docs/cercetare/s3_portare_antet.md @@ -0,0 +1,478 @@ +# Cercetare — proiectare S3: portarea logicii de antet in formularul unificat + +Investigatie READ-ONLY pentru povestea **S3** din `docs\plan_13_unificare_formular_facturare.md:1428-1439`. +Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit. Constrangerile din briefing — +`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` doar citite (perimetrul +#6), `pack_facturare` si `pack_facturare_comun`-ul intern neatinse, analiticele read-only — respectate. + +## Verdict (5-10 randuri) + +S3 e **mai mare decat inventarul din plan sugereaza pe numar de metode** (29 `do_cauta_*` reale, nu +30 — vezi punctul 1), dar **mult mai mare decat sugereaza pe complexitate reala**, pentru doua motive +descoperite aici, nu presupuse: (a) `Init`, `inainte_de_do_termin` si — partial — `do_cauta_fdoc` sunt +**omonime cu semantica total diferita pe toate cele patru formulare-sursa** (antet vs. articole vs. +alte-date), deci portarea "o singura data" cere de fapt patru fuziuni de metoda, nu una singura cu +ramificare; (b) bug-ul **#16 e real, reprodus pe cod pana la linia exacta**, si **nu e un bug de focus** +— e o consecinta a faptului ca bucla de reincercare din `factureaza`/`factureaza2` +(`ofacturare.prg:174-571`) trateaza **orice esec SQL** (nu doar cursul lipsa) ca pe un "DA, continui cu +alt document", redeschizand din senin formularul de antet de la zero. Unificarea **nu-l reproduce +automat** — poate chiar sa-l elimine ca efect secundar, daca antetul nu se mai reconstruieste de la +Init dupa un esec Oracle in pasul urmator. Obstacolul principal pentru implementare nu e portarea +codului de validare (majoritatea e `Do Case`/`amessagebox` mecanic), ci **reconcilierea a patru cicluri +de viata `Init`/`inainte_de_do_termin` independente intr-unul singur**, cu ordinea de dependente de la +punctul 5. + +--- + +## 1. Inventarul metodelor de portat — numere corectate + +Sursa: `vfp_symbols.ps1 -Class frm_date_factura` / `-Class frm_date_aviz`, confirmat pe fisierul real +`COMUN\clase\ofacturare.vc2` (nu `.bak`). + +**`frm_date_factura`** (`ofacturare.vc2:8482-9869`) — **16** metode `do_cauta_*`, nu 17: + +| Metoda | Linii | Ce face | Echivalent in `frm_facturare_articole2` | +|---|---|---|---| +| `do_cauta_altele` | `8940-8961` (21) | cautare pe campul cu eticheta dinamica ("Altele"/contract/comanda/etc.) | nu | +| `do_cauta_avize` | `8963-8999` (36) | cautare aviz-sursa (tip=4) | nu | +| `do_cauta_client` | `9001-9060` (59) | cautare client, populeaza sold/cod fiscal | nu | +| `do_cauta_comanda` | `9062-9090` (28) | cautare comanda-sursa | nu | +| `do_cauta_contract` | `9092-9153` (61) | cautare contract-sursa | nu | +| `do_cauta_factura` | `9155-9171` (16) | cautare factura-sursa (credit note, tip 7) | nu | +| `do_cauta_facturi` | `9173-9212` (39) | cautare multi-factura (retur, tip 8/9) | nu | +| `do_cauta_fdoc` | `9214-9225` (11) | cautare generica fel-document, `caut_fdoc()` — **identica byte-cu-byte cu varianta din `frm_date_aviz`**, vezi punctul 4 | nu | +| `do_cauta_gestiune_init` | `9227-9242` (15) | cautare gestiune sursa | nu | +| `do_cauta_locatii` | `9244-9253` (9) | cautare locatie (tip 45) | nu | +| `do_cauta_lucrare` | `9255-9279` (24) | cautare lucrare | nu | +| `do_cauta_responsabil` | `9281-9301` (20) | cautare responsabil | nu | +| `do_cauta_sectie` | `9303-9342` (39) | cautare sectie | nu | +| `do_cauta_valuta` | `9344-9359` (15) | cautare valuta, muta focus pe `clb_zi_curs` daca exista, altfel pe serie (`:9351-9356`, deja conditionat `Type(...)<>'U'`) | nu | +| `do_cauta_venchelt` | `9361-9391` (30) | cautare venit/cheltuiala | nu | +| `do_cauta_venit` | `9393-9394` (1) | stub/alias, o linie — de verificat ce mai apeleaza | nu | +| `do_schimba_tipdoc` | `9396-9438` (42) | vezi punctul 6 — schimba `poDate.nIdTipDoc` si reinitializeaza serie+numar | **nu** — apelat orfan din prototip, vezi punctul 3 | +| `do_verifica` | `9440-9453` (13) | verificare ANAF pe cod fiscal client | nu (dar exista pe `frm_facturi`, aceeasi semantica) | +| `inainte_de_do_termin` | `9455-9561` (106) | validare completa antet inainte de trecerea la articole | **omonim cu semantica diferita** pe toate 4 formulare, vezi mai jos | +| `Init` | `9563-9796` (234) | vezi punctul 5 | **omonim cu semantica diferita**, vezi mai jos | +| `Clb_dataact...LostFocus` | `9798-9813` (15) | sincronizeaza `zi_curs=dataact` daca `clb_zi_curs` exista | nu | +| `Clb_dataireg...LostFocus` | `9815-9831` (16) | idem, pe `dataireg` | nu | +| `Clb_nract...LostFocus` | `9833-9835` (2) | — | nu | +| `Clb_serie_act...LostFocus` | `9837-9844` (7) | — | nu | +| `Ct_clb_fdoc._combobox1.Init` | `9846-9855` (9) | populeaza combo-ul FACTURA/PROFORMA/BON FISCAL | nu | +| `Ct_clb_fdoc._combobox1.LostFocus` | `9857-9859` (2) | `thisform.do_schimba_tipdoc()` — vezi punctul 6 | nu (frm_facturare_articole2 are un apel similar, dar **orfan**, vezi punctul 3) | +| `Ed_tx_simplu1._EDBASE1.KeyPress` | `9861-9867` (6) | — | nu | + +**`frm_date_aviz`** (`ofacturare.vc2:6566-7618`) — **13** metode `do_cauta_*`, numarul din plan e corect: + +| Metoda | Linii | Observatie | +|---|---|---| +| `do_cauta_altele` | `6882-6894` (12) | mai scurta decat pe factura (21) — set de tipuri mai mic | +| `do_cauta_avize` | `6896-6930` (34) | apropiata ca marime de factura (36) | +| `do_cauta_client` | `6932-7009` (77) | **mai lunga** decat pe factura (59) — trateaza eticheta variabila "Retur de la"/"Gestiune sursa" pe transfer | +| `do_cauta_comanda` | `7011-7034` (23) | | +| `do_cauta_contract` | `7036-7091` (55) | | +| `do_cauta_fdoc` | `7093-7104` (11) | **identica cu factura**, vezi punctul 4 | +| `do_cauta_gestiune_dest` | `7106-7131` (25) | **doar pe aviz** — gestiune destinatie la transfer subunitati | +| `do_cauta_gestiune_init` | `7133-7144` (11) | mai scurta decat pe factura (15) | +| `do_cauta_lucrare` | `7146-7165` (19) | | +| `do_cauta_politica` | `7167-7182` (15) | **doar pe aviz** — `Ct_clb_politici_preturi` | +| `do_cauta_responsabil` | `7184-7204` (20) | aceeasi lungime ca factura | +| `do_cauta_sectie` | `7206-7240` (34) | | +| `do_cauta_venchelt` | `7242-7275` (33) | | +| `inainte_de_do_termin` | `7277-7352` (75) | **omonim**, vezi mai jos — mai scurta decat factura (106): fara validare data-scadenta, fara validare valuta, fara validare client (!) | +| `Init` | `7354-7600` (247) | **omonim**, nu citit integral aici (doar `9563-9650`+`9733-9796` echivalentul pe factura) | +| `Clb_dataact...LostFocus` | `7602-7605` | | +| `Clb_dataireg...LostFocus` | `7607-7612` | | +| `Clb_nract...LostFocus` | `7614-7616` | | + +**`frm_date_aviz` nu are `do_schimba_tipdoc`** — confirmat, container-ul `Ct_clb_fdoc` de pe aviz e +o cautare simpla, fara combo si fara evenimentul `LostFocus` care declanseaza schimbarea de tip. + +**Total real: 16 + 13 = 29 metode `do_cauta_*`**, plus `do_schimba_tipdoc` (doar factura), `do_verifica` +(doar factura), `inainte_de_do_termin` si `Init` (omonime pe ambele) — planul le numara global corect +ca "17+13 ... plus restul", dar cifra "17" pe factura e gresita cu o unitate: **16**. + +--- + +## 2. Metodele omonime — capcana centrala a portarii + +**Nivelul care conteaza cel mai mult, necunoscut inca in plan**: `Init` si `inainte_de_do_termin` sunt +**acelasi nume de metoda pe toate cele patru formulare-sursa implicate in unificare** +(`frm_date_factura`, `frm_date_aviz`, `frm_facturare_articole`/`frm_facturare_articole2`, +`frm_alte_date`), fiecare cu corp complet diferit (antet vs. compunere articole vs. date suplimentare). +O singura clasa unificata nu poate avea patru metode `Init`. **Portarea "o singura data" a S3 inseamna +concret: patru corpuri de `Init` si patru corpuri de `inainte_de_do_termin` trebuie topite intr-un +singur `Init` si un singur `inainte_de_do_termin` (sau redenumite si inlantuite explicit)** — asta e +efortul real, nu doar mutarea codului de validare camp-cu-camp. + +**`inainte_de_do_termin`, factura vs. aviz — diferenta reala, cu citat** (comparate direct, ambele corpuri +citite integral, `ofacturare.vc2:9455-9561` si `:7277-7352`): + +- **Factura valideaza `id_client` obligatoriu; aviz nu valideaza deloc acest camp**: + ``` + ofacturare.vc2:9510-9513 (doar pe factura) + Case Empty(poDate.id_client) Or Isnull(poDate.id_client) + amessagebox("Nu ati ales clientul!",48,"Atentie") + This.ct_clb_nume_client.SetFocus() + ``` + Motiv plauzibil (neconfirmat mai departe): avizele de transfer intre subunitati (tip 23/25/30/41) nu + au neaparat un "client" — au o gestiune destinatie, validata separat. +- **Factura valideaza scadenta si valuta; aviz nu are deloc aceste `Case`-uri** — `Empty(poDate.datascad)`, + `poDate.datascad=21 in afara de 27/30). **Discriminatorul natural pentru ramificare exista deja**: + `poDate.nIdTipDoc` (5=FACTURA, 6=AVIZ — calculat de apelant la `ofacturare.prg:192-196`) sau simpla + apartenenta a lui `poDate.tip` la unul din cele doua seturi disjuncte, **nu** un flag nou. +- **Contul folosit la verificarea de facturi duplicate difera**: `facturi_duplicate('4111', 0, ...)` + (factura, `:9545`, cont clienti) vs. `facturi_duplicate('418', 0, ...)` (aviz, `:7341`, alt cont) — + un literal hardcodat care trebuie sa devina el insusi parte din ramificare, nu doar mesajul. + +**Alte omonime cunoscute, cu semantica diferita, relevante pentru zona din jurul lui S3** (nu introduse +aici — reconfirmate, vezi si `docs\handoff_13_formular_unificat.md:192-194`): +- `do_calculeaza_discount` — **la nivel de document** pe `frm_facturare_articole` (`:13405-13424`) si + `frm_facturare_articole2` (`:17670-17686`, verificat aici: identic ca semantica, scrie + `Thisform.ndiscfactron`/`ndiscfactval`); **la nivel de linie** pe `frm_articol_factura` (`:1874-1976`, + scrie `poArticol.discount_unitar*`). Nu intra direct in S3 (nu e printre metodele antetului), dar + orice cod nou care apeleaza `do_calculeaza_discount` prin nume pe formularul unificat trebuie sa + stie care semantica o vrea. +- `do_verifica` — pe `frm_date_factura` (`:9440-9453`, verificare ANAF) si pe `frm_facturi` + (verificare ANAF, aceeasi semantica) — omonim inofensiv, aceeasi actiune. +- `do_cauta_gestiune_init` / `do_cauta_responsabil` / `do_cauta_sectie` / `do_cauta_venchelt` / + `do_cauta_altele` / `do_cauta_comanda` / `do_cauta_contract` / `do_cauta_avize` / `do_cauta_lucrare` — + **acelasi nume pe factura si aviz, lungimi diferite** (vezi tabelele de la punctul 1) — nu omonime + periculoase (aceeasi intentie: cautare pe camp), dar **nu identice** — portarea trebuie sa ia corpul + fiecareia separat, nu sa presupuna ca sunt duplicate de sters. +- **Singura pereche confirmata identica byte-cu-byte**: `do_cauta_fdoc` (vezi punctul 4) — aceasta + chiar poate fi portata o singura data, fara ramificare. + +--- + +## 3. Ce e deja in prototip vs. ce lipseste + +`frm_facturare_articole2` (`ofacturare.vc2:15741-19355`) are controalele de antet montate direct pe +formular (confirmat in `inventar_controale_formulare.md`), dar **zero logica**: + +| Metoda/mecanism | Exista in `frm_facturare_articole2`? | Detaliu | +|---|---|---| +| Toate cele 29 `do_cauta_*` | **NU** | niciunul in lista de metode proprii a clasei (confirmat cu `vfp_symbols.ps1 -Class`) | +| `do_schimba_tipdoc` | **NU, dar e APELAT** — cod orfan | `clb_fdoc.cboFdoc.Valid` (`:19255-19257`) contine `thisform.do_schimba_tipdoc()`, dar clasa **nu are** aceasta metoda in lista proprie. Daca userul ar schimba azi combo-ul `clb_fdoc` pe acest formular mort, ar cadea cu eroare de metoda inexistenta. Confirma independent constatarea din plan ("controalele sunt acolo, logica nu") — de fapt e mai rau: e cod care ar crapa daca s-ar activa calea, nu doar cod lipsa | +| `inainte_de_do_termin` | **DA, dar cu alta semantica** | `:18809-18986` (178 linii) — valideaza ARTICOLE (stoc, discount, TVA), nu antetul; e omonimul de care vorbeste punctul 2, nu un candidat de reutilizare pentru validarea de antet | +| `Init` | **DA, dar cu alta semantica** | `:18988-19080` (92 linii, citit integral aici) — confirmat: doar grid/curs-label/total-mode/coloane in/afara valuta; **niciun** `poDate.xxx -> control.Value`. Nu populeaza antetul deloc | +| Serie/numar (`Clb_serie_act1`/`Clb_nract`) | Controale prezente, dar fara wiring de populare in `Init` | `Clb_serie_act1.TEXT_SIMPLU1.LostFocus` (`:19263-19270`) exista ca metoda proprie — nu verificat aici daca reproduce `genereazanumar` | + +**Concluzie punctul 3**: prototipul e o **coaja vizuala**, nu un schelet de 50% functional. Ce trebuie +scris pentru S3 e efectiv tot codul de comportament — controalele economisesc timpul de aranjare in +`.scx`/`.vcx`, nu timpul de portare a logicii. + +--- + +## 4. Ramificarea factura / aviz in interior + +**Discriminatorul de ramificare recomandat: `poDate.nIdTipDoc`** (5=FACTURA, 6=AVIZ), deja calculat de +apelant inainte de `Createobject` (`ofacturare.prg:187-196`) si deja disponibil pe `poDate` in +momentul in care formularul unificat ar porni `Init`. Alternativ, acelasi rezultat se obtine testand +direct apartenenta lui `poDate.tip` la unul din cele doua seturi disjuncte folosite azi in +`inainte_de_do_termin` (vezi punctul 2) — cele doua seturi nu se suprapun niciodata, deci nu exista +ambiguitate. **Nu e nevoie de o proprietate noua** gen `lEsteAviz` — `nIdTipDoc` face deja treaba, si +e deja pe obiectul `poDate` care trece prin tot lantul de apeluri. + +Ce trebuie sa ramifice concret, cu dovada: +1. **`inainte_de_do_termin`** — Cases specifice fiecarei parti (vezi citatele de la punctul 2), plus + constanta de cont (`'4111'` vs `'418'`) la `facturi_duplicate`. +2. **`Init`** — seturile `Do Case poDate.tip` care decid ce se elimina (`RemoveObject`) difera pe + fiecare parte (S1 documenteaza deja fiecare camp cu conditia lui de vizibilitate; portarea trebuie + sa pastreze fiecare conditie identic, nu sa le generalizeze pe ghicite). +3. **`do_schimba_tipdoc`** — **exista doar pe factura**; pe aviz nu exista mecanismul de schimbare a + tipului de document dupa deschiderea formularului (containerul `ct_clb_fdoc` de pe aviz e cautare + simpla, fara `_combobox1`). Ramificarea aici nu e "cod diferit pe aceeasi metoda" ci "metoda + prezenta doar pe o ramura" — formularul unificat trebuie sa decida daca aviz capata acest + comportament nou (schimbare tip dupa deschidere) sau ramane fara el, ca azi. +4. **`do_cauta_fdoc`** — **nu ramifica**, e identic (punctul 1) — poate fi portat o singura data, fara + `If nIdTipDoc=...`. +5. **`do_cauta_client`** — aviz e cu 18 linii mai lung (77 vs 59) pentru eticheta variabila + "Retur de la"/"Gestiune sursa" pe transfer (S1, randul "Client") — de citit integral la + implementare pentru ramificarea exacta, nu verificat linie-cu-linie aici. + +--- + +## 5. `Init`-urile — ce face fiecare, in ce ordine, ce depinde de ce + +**Secventa de azi, pe drumul normal (factura din lista de preturi, fara eroare Oracle):** + +1. **Apelantul** (`ofacturare.prg:factureaza`, inainte de orice `Createobject`): construieste + `poDate = Createobject("oDateFactura", ...)` si `poGeneratorNumere = Createobject("oGeneratorNumere")` + (`:184-185`), seteaza `poDate.nIdTipDoc` (`:187-196`), cheama + `poDate.completeaza_setari_document(...)` daca e copiere (`:200-206`), apoi + `poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)` + (`:208-211`) — **toate acestea ruleaza inainte ca vreun formular sa existe**. Orice `Init` de mai jos + presupune `poDate`/`poGeneratorNumere` deja populate. +2. **`frm_date_factura.Init`** (`:9563-9796`, 234 linii, citit integral aici) — `DoDefault()`, apoi + masoara inaltimile containerelor de antet intr-un array (`laPozitii`), decide pe `Do Case + gnScadereStoc/poDate.tip` ce containere elimina (`RemoveObject`) si reface layout-ul pe verticala, + initializeaza serie+numar (`.clb_serie_act.do_initializeaza(poDate.rezultat_serii)`, `:9737`), si + **la final** seteaza focus: pe `ct_clb_valuta` daca documentul e in valuta si campul valutei apare + deasupra tipului si nu are inca valuta aleasa, **altfel pe `ct_clb_fdoc`** (`:9788-9793`, citat + integral la punctul 6). Depinde de: `poDate` (tip, in_valuta, ...), `poGeneratorNumere` + (`creeaza_cursor_serii` deja rulat de apelant), variabile globale de firma (`gnScadereStoc`). +3. **`frm_facturare_articole2.Init`** (`:18988-19080`, 92 linii, citit integral aici) — confirmat: + **nu atinge niciun camp de antet**. Configureaza doar gridul (`nNrInregVizibile`), eticheta + cursurilor valutare, modul total (`gnModTotFact`), si elimina coloanele RON sau valuta din grid + dupa `poDate.in_valuta`. Depinde de: `poDate.zi_curs`/`in_valuta`/`tip`, `crscursuri` (populat de + apelant intre pasii 2 si 3, nu de acest `Init`). +4. **`frm_alte_date.Init`** (`ferestre_cere_date.vc2:3105-3207`, 103 linii, citit integral aici) — + `DoDefault()`, citeste `poDate.dataora_exp`; **daca nu e proforma**: cauta ultimul delegat/masina + folosite pentru client (apel Oracle `cauta_date_ultima_factura[_tip]`, `:3119-3136`), populeaza + combo-ul de casa dintr-un cursor Oracle nou (`v_nom_casa`, `:3156-3173`), seteaza `opt_incasat` + implicit daca `poDate.incasat<>0`; **daca e proforma**: elimina complet grupul delegat/masina/ + incasare (`RemoveObject` in cascada, `:3192-3201`) si redimensioneaza formularul la zona de text + aditional. **Risc gasit, neurmarit mai departe**: pe eroare la interogarea combo-ului de casa, + `Init` cheama `poGeneratorNumere.dezaloca_numar(5)` si `Return` (`:3159-3161`) — acelasi tipar de + "dezalocare numar pe eroare SQL neasteptata" ca la bug-ul #16 (punctul 6), dar intr-un `Init`, nu + intr-o bucla — **nu s-a verificat daca are aceeasi consecinta de recreare completa a formularului**; + semnalat, nu investigat suplimentar aici din motive de buget de context. + +**Ordinea de dependente, explicit**: `poDate`+`poGeneratorNumere` (apelant) -> `frm_date_factura`/ +`frm_date_aviz.Init` (foloseste serie/numar deja create) -> **Oracle: cursorul de articole** +(`ofacturare.prg:266-308`, aici poate pica bug #16) -> `frm_facturare_articole2.Init` (foloseste +`crscursuri` populat intre pasi, nu de propriul `Init`) -> `frm_alte_date.Init` (face **inca** un apel +Oracle nou, cu propriul risc de dezalocare). **Pentru formularul unificat, aceasta secventa de patru +`Init`-uri trebuie sa devina un singur `Init` care ruleaza fazat** (o singura data la deschidere) — +sau ramane o discutie deschisa daca vreo faza (Oracle-lookup-ul din `frm_alte_date.Init`) ramane +intarziata pana la deschiderea sectiunii pliate, ca sa nu incarce round-trip-uri Oracle inutile cand +utilizatorul nu ajunge niciodata la acea sectiune. + +--- + +## 6. Bug-ul #16 — mecanismul exact, verificat pe cod + +**Text original** (`COMUN\docs\todos.txt:45`): *"la revenire din formularul de curs valutar, focusul +revine inainte de numar document, cred ca pe TIP DOCUMENT, si la iesire din serie se regenereaza numar +act, ceea ce este periculos daca utilizatorul l-a schimbat".* + +**Verdict: NU e un bug de focus. E o bucla de reincercare care trateaza orice esec Oracle ca pe un "DA, +mai fac un document" si reconstruieste formularul de antet de la zero — focusul si regenerarea +numarului sunt doar simptomele vizibile ale acestei reconstructii.** + +Lantul complet, verificat linie cu linie: + +1. Utilizatorul completeaza antetul, apasa Termina — `inainte_de_do_termin` trece, formularul de antet + se inchide (`pnButon=1`). +2. Apelantul (`factureaza`, `ofacturare.prg:266-308`) construieste cursorul de articole din Oracle + (`cursor_preturi`/etc., functie de `poDate.tip`). **Daca cererea esueaza** (`lnSucces<0`, + `:313`) — inclusiv, dar nu numai, cu eroarea Oracle `-20005` "Nu este setat cursul..." (curs + valutar lipsa pentru o valuta din listele de preturi ale utilizatorului, verificata neconditionat + in `pack_facturare.verifica_cursuri_valute`, documentat deja in `zi_curs_validare.md`): + ``` + ofacturare.prg:313-322 + If lnSucces < 0 + AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare") + If goExecutor.nEroare = 20005 + vizualizeaza_curs(poDate.zi_curs) && deschide "formularul de curs valutar" (frm_curs, modal) + ENDIF + poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc) && numarul alocat se elibereaza + Else + ... [singurul loc unde se cere "Doriti sa continuati?" si se seteaza lnRaspuns] + Endif + ``` + `vizualizeaza_curs` (`oproceduri_curs.prg:8-42`) e confirmat: deschide `Createobject("frm_curs", + tdDataCurs).Show(1)` — chiar formularul din reclamatie. +3. **Punctul central, verificat prin numararea `If`/`Else`/`Endif` din fisier**: prompt-ul "Doriti sa + continuati cu operatii de acest fel?" care seteaza variabila de bucla `lnRaspuns` **exista doar in + ramura `Else` a lui `If lnSucces<0`** (`ofacturare.prg:555-564`, in interiorul aceluiasi bloc + `If/Else/Endif` care se inchide abia la `:568`). **Pe ramura de eroare (`lnSucces<0`), `lnRaspuns` + nu e niciodata atins.** Isi pastreaza valoarea din intrarea in aceasta iteratie a buclei — pe primul + document, `6` (initializat la `:174`, inainte de `Do While lnRaspuns = 6` de la `:175`). +4. Bucla externa (`Do While lnRaspuns = 6 ... Enddo`, `:175-571`) **reintra automat**, fara nicio + intrebare catre utilizator, pentru ca `lnRaspuns` e inca `6`. In aceasta noua iteratie: + `poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)` + ruleaza din nou (`:208-211`), apoi se creeaza **o instanta noua** de `frm_date_factura`/`frm_date_aviz` + (`Createobject`, `:230`) si se arata modal (`:235`) — `poDate` insusi **nu** e recreat (ramane + acelasi obiect, cu `nract`/`serie_act` deja setate de utilizator), dar **formularul da**, deci + `Init` ruleaza de la capat. +5. `frm_date_factura.Init` (`:9563-9796`) reface layout-ul si, **la final, seteaza focus necondiționat + pe `ct_clb_fdoc`** (tip document) in cazul normal: + ``` + ofacturare.vc2:9788-9793 + DO CASE + CASE poDate.in_valuta = 1 and this.ct_clb_valuta.Top < this.ct_clb_fdoc.Top AND EMPTY(NVL(poDate.nume_valuta,'')) + this.ct_clb_valuta.SetFocus() + OTHERWISE + this.ct_clb_fdoc.SetFocus() + ENDCASE + ``` + **Aceasta e "focusul care revine pe TIP DOCUMENT"** din reclamatie — nu un bug izolat de focus, ci + comportamentul normal de `Init` al unui formular nou, aparut unde utilizatorul nu se astepta la un + formular nou. +6. Cand focusul paraseste `ct_clb_fdoc` (Tab, click in alta parte — orice), se declanseaza + `Ct_clb_fdoc._combobox1.LostFocus -> thisform.do_schimba_tipdoc()` (`:9857-9859`). Prima linie a + acestei metode, **inainte de orice verificare "tipul chiar s-a schimbat?"**: + ``` + ofacturare.vc2:9417 (in do_schimba_tipdoc, INAINTE de guard-ul de la :9419-9421) + This.clb_serie_act._cbbase1.LostFocus() + ``` + — apeleaza **neconditionat** handler-ul de LostFocus al combo-ului de serie, indiferent daca tipul + documentului s-a schimbat sau nu. +7. Acel handler (`serii_numere.vc2:138-140`, clasa `clb_serie_act`) cheama `genereazanumar` (`:114-136`): + ``` + serii_numere.vc2:122-127 + If poGeneratorNumere.verifica_serie(This.nid_tipdoc) Or &lcValoare. = 0 + lcValoare = lcValoare+[=]+ALLTRIM(Str(poGeneratorNumere.aloca_numar(This.nid_tipdoc,Null),20,0)) + &lcValoare + This.Parent.Refresh() + Endif + ``` + — **aloca un numar nou** si il scrie peste `poDate.nract` prin macro, **necondiționat de ce numar + avea utilizatorul inainte**. **Asta e "iesirea din serie regenereaza numarul actului"** din + reclamatie — confirmat exact, pana la linia care face scrierea. + +**Verdict pe intrebarea planului ("se rezolva #16 in S3, sau se reproduce bug-ul?"): se poate rezolva +in S3, cu un cost mic, dar nu e o consecinta automata a unificarii — trebuie tratat explicit.** +Cauza reala nu e in `frm_date_factura`/`do_schimba_tipdoc`/`clb_serie_act` (cod care se comporta +"corect" fata de contractul lui local), ci in bucla de reincercare din apelant +(`ofacturare.prg:174-571`), care **nu distinge "utilizatorul a confirmat ca vrea alt document" de +"cererea Oracle a esuat"**. Doua directii posibile, ambele in afara perimetrului strict al portarii de +antet, dar declansate direct de ea: +- **(a) minimal**: in formularul unificat, dupa un esec Oracle la pasul de construire a cursorului de + articole (echivalentul liniei `:313`), **nu se distruge formularul de antet** — antetul e deja o + singura sectiune persistenta a aceluiasi formular, nu un obiect separat recreat de apelant; simpla + arhitectura unificata (un `Init` per sesiune de emitere, nu per formular) elimina pasul 4 de mai sus, + deci elimina intreg lantul 5-7. **Acesta e motivul pentru care unificarea are sansa reala sa rezolve + #16 ca efect secundar** — dar numai daca implementarea nu recreeaza formularul intreg la reincercare + dupa eroare Oracle (ceea ce ar reproduce bug-ul identic, doar mutat). +- **(b) daca (a) nu e suficient** (de ex. daca dupa fix la curs tot trebuie relansata interogarea de + articole): `lnRaspuns` (sau echivalentul lui in noua arhitectura) trebuie resetat explicit pe orice + cale care iese din ramura de eroare, ca sa nu mai fie confundat cu "utilizatorul a spus DA". + +**Coordonarea cu #6 (decizia 30 si constrangerea din briefing) — verificat direct pe diff-uri, nu doar +pe proza handoff-ului**: `docs\diff_s4_valuta_dialog*.patch` (trei fisiere) ating exclusiv +`COMUN\clase\omodificari.vc2`, `COMUN\programe\ofacturare_editare.prg` si un fisier de test — +**niciunul nu atinge `ofacturare.vc2` (unde traiesc `frm_date_factura`, `do_schimba_tipdoc`, +`clb_serie_act`) sau `ofacturare.prg` (unde traieste bucla `factureaza`)**. Confirmat si de continutul +lui handoff intermediar (sters): intreaga lui cercetare e despre `frm_articol_factura` +(dialogul de adaugare articol pe linie, alt fisier/clasa) si `frm_modific2024.cmdAdaugaArticol.Click` +— un mecanism de valuta **pe linie de articol**, fara nicio legatura cu antetul sau cu bucla de +emitere. **#6 nu a atins deloc lantul lui #16. Bug-ul e in intregime valabil si neschimbat.** + +--- + +## 7. Ordinea de lucru propusa pentru S3 + +Pasi care lasa suita functionala dupa fiecare (calea veche ramane in productie pe tot parcursul — +niciun pas nu sterge `frm_date_factura`/`frm_date_aviz`/`frm_alte_date` originale): + +1. **Fuzioneaza `Init`-urile de antet** (`frm_date_factura` + `frm_date_aviz`) intr-o metoda unica pe + formularul unificat, ramificata pe `poDate.nIdTipDoc` (punctul 4). Verificare: formularul unificat + se deschide pe fiecare din cele ~15 tipuri principale de `poDate.tip` folosite azi in cele doua + `Init`-uri, si arata exact aceleasi campuri vizibile/eliminate ca varianta veche — comparatie + camp-cu-camp fata de tabelul din `docs\S1_inventar_campuri_formular_unificat.md`. +2. **Porteaza cele 29 `do_cauta_*`** (fara ramificare unde sunt identice — `do_cauta_fdoc` — cu + ramificare pe `nIdTipDoc` unde difera). Verificare: fiecare cautare deschide acelasi dialog, scrie + aceleasi proprietati pe `poDate`, muta focusul identic cu azi. +3. **Fuzioneaza `inainte_de_do_termin`**, cu ramificarea documentata la punctul 2. Verificare: aceleasi + mesaje de eroare, in acelasi ordine, pentru aceleasi campuri goale, pe fiecare tip. +4. **Porteaza `do_schimba_tipdoc`** — decide explicit daca ramane doar-factura sau se extinde si pe + aviz (punctul 4, pct. 3). **Aici se rezolva sau nu #16** (punctul 6) — de facut ca parte a acestui + pas, nu separat, pentru ca schimbarea de arhitectura care il rezolva (un singur `Init` persistent) + e chiar cea care se construieste la pasul 1. +5. **Porteaza `but_modifica`/blocarea antetului** (decizia 9, deja proiectata in plan sectiunea I) — + depinde de pasii 1-4 fiind stabili, pentru ca reutilizeaza aceleasi controale. +6. **Integreaza bucla de emitere** (`factureaza`/`factureaza2`) cu noul formular unificat, tratand + explicit esecul Oracle de la cursorul de articole (punctul 6, directia a/b). + +**Criteriul de "gata", rescris verificabil.** Azi planul spune "un document se emite integral din +formularul unificat, pe tip 1, cu acelasi rezultat in `vanzari`/`act`/`rul` ca pe calea veche" — fara +sa spuna cum se compara. Propunere concreta: +- Se emite **acelasi document** (acelasi client, articole, cantitati, preturi) o data pe calea veche + (`frm_date_factura` -> `frm_facturare_articole` -> `frm_alte_date`) si o data pe formularul unificat, + pe date de test identice, in aceeasi zi contabila. +- Se compara, randuri-cu-randuri, rezultatul in `VANZARI` (toate coloanele, nu doar sumele), `ACT`, + `RUL`, `DOCUMENTE`, `JV2007` (nota contabila) — cel mai simplu cu doua interogari identice filtrate + pe cei doi `ID_FACT`/`ID_VANZARE` rezultati, exportate si diff-uite text-cu-text (unealta: acelasi + export SQL folosit deja pentru `PACK_FACTURARE` in `docs\ff_*.sql`, adaptat pe `SELECT * FROM VANZARI + WHERE ID_FACT=...`). +- Diferentele **asteptate** (marcate ca OK, nu ca esec): `ID_VANZARE`/`ID_FACT`/timestamp-uri de + creare — orice altceva trebuie sa fie identic. +- Se repeta pentru **cel putin un tip de factura in valuta** (S3 atinge direct campurile de valuta) si + **un tip de aviz** (ramificarea de la punctul 4), nu doar tip 1. + +--- + +## 8. Ce nu se poate testa headless din S3 + +Capcana cunoscuta (`COMUN\docs\depanare_testare_vfp.md`, memorie de proiect): sub harness `-A -T`, +coloanele de grid nu se materializeaza (`ColumnCount=0`, `RecordSource` raman artefacte necitite) — +relevant direct pentru `frm_facturare_articole2` (grid-ul de compunere), dar S3 propriu-zis nu atinge +gridul. + +Specific pentru S3 (antet): +- **Toate cele 29 `do_cauta_*`** deschid un dialog modal de cautare (`caut_ora.vcx`/similare) — + interactiunea reala de selectare dintr-o lista si `Show(1)` modal nu se poate simula headless; se + pot testa doar efectele **dupa** ce `poCauta`/rezultatul e construit manual (tiparul deja folosit in + `test_pret_cu_tva_dialog.prg`/`test_adauga_linie_valuta.prg` mentionat in + handoff intermediar (sters), punctul 7) — apel direct al metodei cu un obiect + simulat, fara `Show()`. +- **`Init`-ul complet** (redimensionare, `RemoveObject` in cascada, repozitionare containere) e + verificabil pe proprietati (`.Visible`, `.Height`, existenta obiectului dupa `Type(...)`) fara UI + vizibil, pentru ca `Createobject` ruleaza `Init` fara `Show()` — tiparul e deja validat in codebase. +- **Focusul si secventa reala de `LostFocus`** (exact ce a produs bug #16) **nu se poate reproduce + headless** — necesita fie `vfp_ui_harness.ps1` cu UI vizibil si input simulat pe masina, fie testare + manuala de Marius; simularea unui `SetFocus()`/`LostFocus()` apelat direct din cod ocoleste tocmai + secventa evenimentelor native care a cauzat bugul. +- **Eroarea Oracle -20005 si redeschiderea `vizualizeaza_curs`** — reproductibila headless doar daca + se poate forta controlat lipsa unui curs pentru o data de test (manipulare de date, nu de UI) — nu + s-a verificat aici daca exista deja o retetare de date de test pentru asta. + +--- + +## Ce nu s-a putut stabili si de ce + +- **Corpul complet al `frm_date_aviz.Init`** (`:7354-7600`, 247 linii) — citit doar partial in sesiuni + anterioare (pana la `~7533`, conform `inventar_controale_formulare.md:166-167`); nu re-citit integral + aici din motive de buget de context. Structura generala (Do Case pe tip, RemoveObject, focus final) + e foarte probabil simetrica cu `frm_date_factura.Init`, dar nu verificata linie-cu-linie. +- **`do_cauta_venit`** (`frm_date_factura`, `:9393-9394`, o singura linie) — nu s-a citit continutul; + posibil alias/stub mort, de verificat la implementare. +- **Riscul semnalat la punctul 5** (`frm_alte_date.Init:3159`, `dezaloca_numar` pe eroare la + interogarea combo-ului de casa) — **doar semnalat, neurmarit**: nu s-a verificat daca apelantul care + cheama `frm_alte_date` are aceeasi bucla "retry silentios" ca `factureaza`, sau daca eroarea aici + chiar opreste fluxul curat (`Return` explicit la `:3161`, spre deosebire de bug #16 unde nu exista + `Return` echivalent). Merita o cercetare separata, de marimea celei de la punctul 6, inainte de + implementare. +- **`Clb_serie_act1.TEXT_SIMPLU1.LostFocus`** din `frm_facturare_articole2` (`:19263-19270`) — nu + citit; nu s-a verificat daca reproduce corect `genereazanumar` sau e alt cod orfan ca cel de la + punctul 3. +- **Ramificarea exacta linie-cu-linie pentru restul celor 27 de perechi `do_cauta_*`** (dincolo de + `do_cauta_client` si `do_cauta_fdoc`, comparate direct aici) — s-au comparat doar lungimile (indiciu + de diferenta), nu continutul; de citit la implementare, nu presupus din lungime. +- **`frm_modifica_factura`** — clasa reala traieste in `ofacturare_comun.vc2` (perimetrul #6, doar + citire permisa), dar `vfp_symbols.ps1` a indexat-o din `.pre_s4butoane.bak.vc2` (linii duplicate, + nesigure) — nu s-au recitit liniile reale, pentru ca `frm_modifica_factura` **nu intra in portarea + S3** (e inlocuita de `but_modifica`, deja proiectat separat in plan, sectiunea I). + +## Bug-uri semnalate, nereparate + +1. **Cod orfan in `frm_facturare_articole2`**: `clb_fdoc.cboFdoc.Valid` (`ofacturare.vc2:19256`) cheama + `thisform.do_schimba_tipdoc()`, metoda **inexistenta** pe aceasta clasa — ar arunca eroare VFP daca + userul ar interactiona cu acest combo pe formularul mort. Fara consecinte azi (formularul nu e + accesibil in productie, cf. plan sectiunea A), dar de curatat sau completat cand prototipul devine + baza formularului unificat. +2. **Bug #16, confirmat si localizat complet** (punctul 6) — in `ofacturare.prg:555-564` (si simetric + in `factureaza2`, liniile ~1061 dupa numerotarea echivalenta, nu verificate linie-cu-linie aici): + `lnRaspuns` nu se reseteaza pe ramura de eroare Oracle a buclei de emitere, cauzand recrearea + completa si nesolicitata a formularului de antet. In afara perimetrului `ofacturare_comun.vc2`/ + `ofacturare_editare.prg` (deci nu ciocneste cu #6), dar in `ofacturare.vc2`/`ofacturare.prg`, cod + comun suitei (afecteaza si ROACONT/ROAGEST/etc. daca folosesc acelasi `factureaza`/`factureaza2` + din `COMUN` — neverificat aici daca alte produse il apeleaza, dar fisierul e in `COMUN\programe`, + deci probabil da). +3. **Risc structural similar, neconfirmat**: `frm_alte_date.Init:3159` — acelasi tipar + "`dezaloca_numar` pe eroare SQL neasteptata", posibil fara aceeasi consecinta de reincercare + silentioasa, dar nu verificat (vezi "Ce nu s-a putut stabili"). diff --git a/docs/cercetare/s3b_alte_date_analitice.md b/docs/cercetare/s3b_alte_date_analitice.md new file mode 100644 index 0000000..5666d10 --- /dev/null +++ b/docs/cercetare/s3b_alte_date_analitice.md @@ -0,0 +1,602 @@ +# S3b — Proiectare: sectiunea pliata "alte date + analitice" + +Livrabilul povestii **S3b** din `docs\plan_13_unificare_formular_facturare.md:1838-1854` (deciziile 7, +8, 25, 26). Proiectare pe cod, fara nicio modificare — zero editari, zero +`git_sync.ps1`/`txt2vcx.ps1`, zero commit. `COMUN\clase\ofacturare_comun.vc2` si +`COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar citite, neatinse. + +## Verdict (rezumat) + +S3b nu e o simpla "mutare de controale" cum sugereaza textul din plan. **Descoperirea centrala, +negasita in materialele existente**: mecanismul de alocare a numarului de chitanta nu porneste doar +din clicul utilizatorului pe `opt_incasat` — porneste din **orice** atribuire programatica a +proprietatii `.Value`, pentru ca `opt_incasat.ProgrammaticChange` (`ferestre_cere_date.vc2:3351-3352`) +cheama exact acelasi `actualizeaza_tipincasare()` ca si `.Click` (`:3250-3251`). `Init`-ul de azi +(`:3150`) seteaza chiar el `Thisform.opt_incasat.Value = 2` cand documentul soseste cu +`poDate.incasat<>0` (caz real: copiere, confirmat la `ofacturare_stoc.prg:535`, +`oproceduri_facturare.prg:1186`, `ofacturare_comun.vc2:4066,4260`) — deci **azi, deschiderea +dialogului aloca deja un numar de chitanta fara ca userul sa atinga nimic**, de fiecare data cand +documentul soseste cu o incasare presetata. In formularul unificat, unde zona nu se mai deschide o +singura data modal ci se pliaza/depliaza repetat pe acelasi obiect `poDate` persistent, acelasi cod +rulat la fiecare depliere ar realoca/dealoca la fiecare toggle — exact opusul criteriului cerut de +decizia 25. Sectiunea 4 trateaza asta ca risc central, cu recomandare concreta. + +A doua descoperire: **niciun mecanism de pliere reutilizabil nu exista** in prototipul +`frm_facturare_articole2` (cautare completa, zero potriviri pe "plia/colaps/accordion/expand" in +`ofacturare.vc2`) — cel mai apropiat tipar din suita e `frm_modific2024.afiseaza_rulaje` +(`omodificari.vc2:13169-13199`, perimetrul #6, citit dar neatins), care refoloseste **acelasi idiom +sus/jos** deja validat pentru `but_modifica`/`but_salveaza` (decizia 9), dar aplicat unui show/hide, +nu unei blocari. Sectiunea 6 detaliaza. + +A treia: pentru analiticele read-only (decizia 26), mecanismul existent `ct_clb_cautare.do_dezactiveaza()` +(`caut_ora.vc2:800-806`) **ascunde doar lupa de cautare**, nu blocheaza si textbox-ul de afisare — +cineva tot poate scrie liber in camp. Sectiunea 2 detaliaza gap-ul si completarea minima necesara. + +--- + +## 1. Inventarul exact al celor patru grupuri din `frm_alte_date` + +Sursa primara a controalelor: `docs\cercetare\inventar_controale_formulare.md` §4 si +`docs\S1_inventar_campuri_formular_unificat.md` (randurile "Pliat — alte date"), reconfirmate aici +direct pe `COMUN\clase\ferestre_cere_date.vc2` (clasa `frm_alte_date`, `2219-3353`; garda de validare +`inainte_de_do_termin` la `3044-3103`, `Init` la `3105-3208`). + +### 1.1 Delegat / transport + +| Control | Caption real | `fisier:linie` (ADD OBJECT) | Vizibilitate | Ruta de scriere (din rute_scriere_antet.md / S1) | +|---|---|---|---|---| +| `Ct_clb_delegat` | "Delegat" | `ferestre_cere_date.vc2:2553` (container `ct_clb_cautare`-like, `caution=do_cauta_delegat`) | pliat, mereu editabil pe document nou; **eliminat cu totul** cand `poDate.eProforma=1` (`:3192`) | pe loc, `modifica_date_factura` (`V_ID_DELEGAT`), neconditionat — `modifica_date_factura_parametri.md:17` | +| `Ct_clb_masina` | "Masina" | `:2569` | idem, eliminat pe proforma (`:3193`) | `V_ID_MASINA`, neconditionat — `:18` | +| `Ct_clb_agent` | "Agent" | `:2537` | idem, **nu e eliminat pe proforma** (nu apare in cascada `RemoveObject` `:3192-3201`) | `V_ID_AGENT`, neconditionat — `:19` | +| `Clb_dataora_exp` | "Data si ora expedierii" | `:2443` | idem, nu eliminat pe proforma; **validat obligatoriu** in `inainte_de_do_termin` (`:3062-3067`, `Case Empty(Nvl(poDate.dataora_exp,{})) And poDate.eProforma = 0`) | `V_DATAORA_EXP`, neconditionat — `:20` | +| `But_modifica1` | (icon, `caction` implicit -> `do_modifica`) | `:2343` | actiune, nu camp — deschide `nom_parteneri_modifica` pe delegatul curent (`ferestre_cere_date.vc2:2925-2935`) | — | + +**Validarea de grup** (`inainte_de_do_termin`, `:3055-3060`): delegatul e obligatoriu doar cand +`gcNumeProgram = [ROAFACTURARE]` **si** documentul nu e proforma sau bon fiscal +(`!(poDate.eProforma = 1 OR poDate.eBonFiscal = 1)`) — nuanta care trebuie pastrata identic in +formularul unificat, nu doar copiata "delegat obligatoriu". + +### 1.2 Incasare (grupul cel mai mare, vezi si sectiunile 3-4) + +| Control | Caption real / eticheta dinamica | `fisier:linie` | Vizibil cand | +|---|---|---|---| +| `opt_incasat` (radiogrup 4 optiuni) | "Fara incasare" / "Chitanta" / "Bon fiscal" / "POS Card" | `:2638-2685` (default `Value=1`) | mereu, dar **intreg containerul incasare** dispare pe proforma (`:3140-3143`, `Thisform.opt_incasat.Visible = .F.`, doar pe ramura non-proforma exista oricum) si pe `gcNumeProgram=[ROAGEST]` sau `!Between(poDate.tip,1,4)` | +| `Cb_casa` | "Casa" -> "Banca POS" pe POS (`:2967` `_lbbase1.Caption='Banca POS'`) | `:2362` | ascuns doar la `opt_incasat=1` | +| `Clb_serie_chit` | "Serie chitanta" | `:2503` | vizibil **doar** la Chitanta (`Value=2`) | +| `Clb_nrchit` | "Nr. chitanta" -> "Nr. bon" pe Bon fiscal/POS | `:2480` | ascuns doar la `Value=1` | +| `Clb_incasat` | "Incasat" | `:2461` | ascuns doar la `Value=1` | +| `cmdModificaBon` | icon, `caction=do_modifica_bon` | `:2526` | vizibil **doar** la Bon fiscal (`Value=3`) | +| `chkPOS` | "POS" | `:2409` | vizibil la Bon fiscal (`Value=3`, bifabil) **si** la POS (`Value=4`, fortat `.T.` si ascuns — `:2845` `thisform.chkPOS.Value=1` apoi `.Visible=.F.`) | +| `chkDetaliat` | "Detaliat" | `:2396` | vizibil doar la Bon fiscal | +| `cboTipFactura` | combo, langa "Tip factura" | `:2378` | mereu (langa incasare), scrie `poDate.tip_saft` | + +Masina de stari completa e la sectiunea 3-4. **Eticheta "Casa"/"Banca POS" si vizibilitatea lui +`chkPOS`/`chkDetaliat` fac parte din aceeasi metoda unica** (`actualizeaza_tipincasare`) — nu sunt +conditii separate de refacut, sunt randuri din acelasi `Do Case`. + +### 1.3 Adresa de facturare + +| Control | Caption | `fisier:linie` | Vizibilitate | +|---|---|---|---| +| `clb_adresa_facturare` | "Adresa facturare" | `:2422` | pliat, mereu editabil; **nu** eliminat pe proforma (absent din cascada `:3192-3201`) | +| `do_cauta_adresa` (metoda, nu control separat) | — | `:2911-2926` | cauta pe `poDate.id_client`, seteaza `poDate.adresa_facturare`/`poDate.id_facturare` | + +Ruta de scriere: `V_ID_FACTURARE`, neconditionat — `modifica_date_factura_parametri.md:21`. + +### 1.4 Text aditional + +| Control | Caption | `fisier:linie` | Vizibilitate | +|---|---|---|---| +| `Ed_tx_simplu1` | "Text aditional (max. 1000 caractere)" | `:2585` | pliat, mereu editabil; nu eliminat pe proforma — dimpotriva, **pe proforma ramane singurul grup activ**, formularul se redimensioneaza in jurul lui (`:3184-3207`) | +| `_checkbox1` ("Listare detaliata") | "Listare detaliata" | `:3002-3011` | ADD OBJECT separat, `ControlSource=poDate.nListareDetaliata` — **nu e in cascada celor patru grupuri din B**, e propriul lui control, langa text aditional in layout, dar conceptual apartine grupului de raportare (I-bis) alaturi de `tip_saft`/`efactura`, nu grupului "text aditional" | + +Ruta: `V_TEXT_ADITIONAL`, neconditionat, **fara** `NVL` — `modifica_date_factura_parametri.md:23`. + +**Observatie structurala pentru mockup**: `_checkbox1` (listare detaliata) e cablat separat de +`chkDetaliat` din grupul incasare (control diferit, aceeasi semantica probabila — ambiguitate deja +semnalata in S1, randul 57, neinchisa aici). Formularul unificat trebuie sa aleaga **unul singur** +dintre cele doua controale existente, nu sa le porteze pe amandoua. + +--- + +## 2. Analiticele coborate din antet (decizia 8, nuantata de decizia 26) + +Controale: `Ct_clb_venchelt` ("Venit / cheltuiala"), `Ct_clb_sectie` ("Sectie"), `Ct_clb_responsabil` +("Responsabil"), `Ct_clb_lucrare` ("Lucrare") — toate patru container `ct_clb_cautare` (subclasa +`caut_ora.vcx`), azi pe `frm_date_factura`/`frm_date_aviz`, **nu** pe `frm_alte_date` +(`inventar_controale_formulare.md` §3; S1 randurile 39-42). Live in sectiunea I a planului ca grup +"Pliat — analitice", distinct de cele patru grupuri ale lui `frm_alte_date` — dar aceeasi sectiune +pliata a formularului unificat, conform textului S3b ("peste ele coboara analiticele din antet"). + +### 2.1 Ce inseamna "afisare read-only" mecanic + +Sursa: `docs\cercetare\rute_scriere_antet.md` §1 — verdict deja stabilit, **DA** pentru +`ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE` printr-o cale noua (`frm_facturi.do_editare_factura` -> +`frm_modific2024`, perimetrul #6), **neconfirmat** pentru `ID_VENCHELT`. Pentru #13, indiferent de +raspunsul acelei rezerve, decizia 26 e explicita: **zero editare din formularul unificat**, oricare ar +fi ruta reala din Oracle. + +**Mecanismul de blocare vizuala, verificat pe cod, cu un gap real**: +`ct_clb_cautare.do_dezactiveaza()` (`caut_ora.vc2:800-806`): +``` +PROCEDURE do_dezactiveaza + This.lactiv = .F. + This.img_cautare.Visible = .F. + This.ctooltip = This.clb_tx_cautare.text_simplu1.ToolTipText + This.clb_tx_cautare.text_simplu1.ToolTipText = [] + This.clb_tx_cautare.text_simplu1.Refresh() +ENDPROC +``` +`This.lactiv` gateaza **doar** deschiderea dialogului de cautare — `DblClick` (`:822-826`), +`KeyPress` pe Enter/Tab/F7 (`:829-836`), `img_cautare.Click` (`:840-843`) — toate testeaza +`This.Parent(.Parent).lactiv` inainte sa cheme `do_apeleaza()`. **Nu seteaza `ReadOnly` pe +`clb_tx_cautare.text_simplu1`** — textbox-ul bindat (`ControlSource=This.cvar_afisata`, setat in +`Init`, `caut_ora.vc2:814-817`) ramane direct tastabil. Pentru un camp cu adevarat read-only (decizia +26 cere explicit "nu se editeaza"), `do_dezactiveaza()` trebuie **completat**, nu doar apelat: se +adauga `This.clb_tx_cautare.text_simplu1.ReadOnly = .T.` (si eventual `.TabStop = .F.`, ca sa nu +primeasca focus deloc) langa apelul existent — o linie noua, in tiparul deja recomandat de plan +(sectiunea I: "singura reteta completa e `clb_tx_data.dezactiveaza()`... se generalizeaza de la ea"). + +**Consecinta pentru "un singur buton deschide tot" (decizia 9)**: `do_dezactiveaza()` se apeleaza **o +singura data**, la constructia sectiunii, **si nu se mai cheama niciodata `do_activeaza()`** pentru +aceste patru controale — nici cand `but_modifica` deblocheaza restul antetului. E o exceptie +deliberata de la "un singur control comanda toata protectia antetului" (plan I:843-853), simetrica cu +exceptia deja acceptata pentru grupurile B/C la decizia 25 (sectiunea 5 de mai jos). + +### 2.2 Eticheta / tooltip + +Nicio propunere de text nu exista inca in materialele citite — de scris la implementare. Recomandare +minima, in stilul deja folosit in codebase (ex. `Ct_clb_gestiune_init.ToolTipText`, un citat complet +la `inventar_controale_formulare.md:93-95`, deci precedent de lungime/ton acceptat): eticheta ramane +neschimbata ("Venit / cheltuiala"/"Sectie"/"Responsabil"/"Lucrare"), `ToolTipText` nou de tipul +*"Se editeaza din Editare factura (articole, cantitati, preturi) — buton disponibil pe fiecare linie +a notei contabile"*, ca sa nu para camp stricat. Text exact — **de decis de Marius**, vezi sectiunea 9. + +### 2.3 Nivel antet vs. nivel linie — de retinut la afisare + +`rute_scriere_antet.md` §1.4: editarea reala prin #6 e **pe linie de nota**, nu pe antet — un +document poate ajunge cu sectii diferite pe linii diferite dupa editare (imposibil la emitere). Cand +formularul unificat afiseaza analiticele "cu valoarea de antet" (cum spune decizia 26), afiseaza de +fapt **o singura valoare reprezentativa** (probabil prima linie sau variabila de sesiune retinuta la +emitere), nu o agregare a liniilor — daca liniile diverg dupa o editare #6, afisarea din #13 devine +"aproximativa, nu autoritara". Nu e o contradictie de rezolvat aici (decizia 26 exista tocmai ca sa +evite ca #13 sa scrie peste diferentierea de linie), dar merita un rand explicit in criteriul de +"gata" (sectiunea 10): afisarea trebuie sa spuna clar ce valoare arata, nu sa pretinda ca e valoarea +unica a documentului daca liniile difera. + +--- + +## 3. `actualizeaza_tipincasare` + +Definita la `COMUN\clase\ferestre_cere_date.vc2:2698-2856`, metoda proprie a clasei `frm_alte_date`. +Comuta vizibilitatea intregului subgrup de incasare pe `This.opt_incasat.Value` (patru ramuri, tabel +complet la sectiunea 1.2 si in `inventar_controale_formulare.md:130-135`) **si**, in aceeasi metoda, +aloca/dezaloca numerele — cele doua responsabilitati (UI si alocare) sunt **impletite in acelasi +`Do Case`**, nu separate. Fiecare ramura incepe prin a dezaloca defensiv celelalte trei tipuri de +numar, apoi aloca pe cel curent daca e cazul: + +| `opt_incasat.Value` | Dezaloca la intrare | Aloca | Alte efecte | +|---|---|---|---| +| 1 (Fara incasare) | 16, 3, 26 | — | `poDate.incasat=0`, `poDate.nr_incasare=0` | +| 2 (Chitanta) | 3, 16, 26 | `creeaza_cursor_serii(16)` + `clb_serie_chit.genereazaNumar()` (`:2783`) — doar daca `nRezultatSerii=0` (nu realoca daca seria era deja creata) | `poDate.incasat=poDate.totalctva` | +| 3 (Bon fiscal) | 3, 16, 26 | `do_aloca_nr_bon([CLICK])` (`:2807`) -> cod 3 | idem, plus `tip_saft` poate deveni 751 daca `gnEFactura_tip_saft_bf` e setat | +| 4 (POS/Card) | 3, 16, 26 | `do_aloca_nr_pos([CLICK])` (`:2844`) -> cod 26 | idem, `chkPOS.Value` fortat 1 si ascuns | + +**Ce depinde de ea**: `opt_incasat.Click` (`:3250-3251`) **si** `opt_incasat.ProgrammaticChange` +(`:3351-3352`) — vezi sectiunea 4, e disjunctia care conteaza. Nu are alti apelanti directi in +`ferestre_cere_date.vc2` (cautare `vfp_symbols.ps1 -Grep actualizeaza_tipincasare -CodeOnly` +recomandata la implementare pentru confirmare exhaustiva pe tot proiectul — nu rulata aici din motive +de buget, dar apelantul extern deja cunoscut e citat mai jos, la 3.1). + +### 3.1 Apel extern, cu context important + +`COMUN\clase\ofacturare.vc2:14919-14926` (`frm_facturare_articole(2).inainte_de_do_termin`, inainte +de `ofrmdatesupl.Show(1)`): +``` +ofrmdatesupl = Createobject("frm_alte_date") +ofrmdatesupl.nnrbon = poDate.nract +If (poDate.eBonFiscal = 1) + With ofrmdatesupl + .opt_incasat.Value = 3 && INCASARE CU BON FISCAL + .actualizeaza_tipincasare() && apel EXPLICIT, dupa .Value= + .opt_incasat.option1.TabStop = .F. + ... +``` +Apelantul seteaza `.Value=3` **si** cheama explicit `actualizeaza_tipincasare()` imediat dupa — ceea +ce, coroborat cu descoperirea de la sectiunea 4 (`.Value=` deja declanseaza `ProgrammaticChange` -> +aceeasi metoda), inseamna ca metoda ruleaza de doua ori la acest apel. Nu produce o eroare vizibila +(fiecare ramura e idempotenta: dezaloca ce era alocat, realoca acelasi tip), dar confirma ca autorul +codului nu s-a bazat exclusiv pe evenimentul `ProgrammaticChange` — semn ca mecanismul lui exact +(cand se declanseaza, cand nu) n-a fost niciodata documentat explicit, doar "acoperit din ambele +parti". **Portarea "ca atare" trebuie sa pastreze acest apel dublu identic**, nu sa-l "curete" ca +redundant — eliminarea unuia dintre cele doua declansatoare ar putea rupe o presupunere neverificata +in alta parte a codului. + +### 3.2 "Se muta ca atare" — ce inseamna concret + +Corpul metodei (`:2698-2856`) nu are nicio dependenta de fereastra-container (`Thisform.*` peste +tot, nu `This.Parent.*`) — se poate muta **byte-cu-byte** pe formularul unificat, cu conditia ca +toate cele noua controale referite (`opt_incasat`, `cb_casa`, `clb_serie_chit`, `clb_nrchit`, +`clb_incasat`, `_shape3`, `lb_simplu1`, `cmdModificaBon`, `chkPOS`, `chkDetaliat`, `cboTipFactura`) +sa existe cu **exact aceleasi nume** pe noul formular, in acelasi container logic (`Thisform`, nu un +subpanou cu alt scope) — altfel referintele nekvalificate `Thisform.xxx` pica silentios pe obiect +inexistent. Aceasta e o constrangere de implementare directa: **sectiunea pliata trebuie sa fie +container-transparenta** pentru aceasta metoda (fie pusa direct pe `Thisform`, fie metoda insasi +adaptata sa foloseasca `This.Parent`-ul corect) — nu poate fi mutata intr-un obiect-container separat +fara o trecere explicita de `Thisform.xxx -> This.Parent.xxx` pe toate liniile. + +--- + +## 4. Alocarea si dezalocarea numerelor — evenimentele exacte + +### 4.1 Evenimente de alocare (azi) + +1. **`opt_incasat.Click`** (`:3250-3251`) — interactiune reala a utilizatorului pe radiogrup. +2. **`opt_incasat.ProgrammaticChange`** (`:3351-3352`) — **descoperire centrala a acestui raport**, + negasita in materialul de plan sau in cele patru rapoarte de referinta: in VFP, orice atribuire + `.Value = n` facuta din cod (nu de utilizator) declanseaza `ProgrammaticChange`, nu `Click`. Clasa + are ambele metode cablate pe **acelasi** apel (`actualizeaza_tipincasare()`), deci **orice loc din + cod care scrie `opt_incasat.Value = ...` aloca/dealoca numere**, indiferent de intentie. +3. **Chiar `Init`-ul clasei** foloseste acest canal: `:3150`, `Thisform.opt_incasat.Value = 2` — ruleaza + **doar daca** `poDate.incasat <> 0` la momentul deschiderii. Verificat pe cod (nu presupus) ca + `poDate.incasat` poate fi nenul **inainte** ca `frm_alte_date` sa se deschida, prin cel putin patru + cai reale: `ofacturare_stoc.prg:535`, `oproceduri_facturare.prg:1186`, + `ofacturare_comun.vc2:4066`, `ofacturare_comun.vc2:4260` (toate patru scriu `poDate.incasat = + incasat`, parametru primit de la apelant — flux de copiere/reluare a unui document cu incasare deja + stabilita). **Concluzie verificata**: azi, deschiderea dialogului `frm_alte_date` pe un astfel de + document **aloca deja un numar de chitanta la Init, inainte ca userul sa apese orice**. Nu e o + ipoteza — e mecanismul descris la sectiunea 3, declansat de linia 3150. +4. `do_modifica_bon`/`do_modifica_pos` (`:3001-3022`) — dezaloca explicit + realoca, la apasarea + butonului "Modifica bon"/echivalent POS. +5. `do_aloca_nr_bon`/`do_aloca_nr_pos` (`:2864-2907`) — chemate din interiorul lui + `actualizeaza_tipincasare` (ramurile 3 si 4), nu independent. + +### 4.2 Evenimente de dezalocare (azi) + +1. **In interiorul `actualizeaza_tipincasare`** — fiecare ramura dezaloca defensiv celelalte trei + tipuri **inainte** de a (re)aloca pe cel ales (tabelul de la sectiunea 3). Efect: comutarea intre + optiuni, in cadrul aceleiasi sesiuni de dialog, e curata — nu ramane niciun numar orfan din + optiunea anterioara. +2. **`inainte_de_do_renunt`** (`:3023-3043`) — la anulare (buton Renunt / ESC pe dialogul modal): + ``` + inainte_de_do_renunt (ferestre_cere_date.vc2:3025-3030) + If Type('poGeneratorNumere') = 'O' + poGeneratorNumere.dezaloca_numar(16) && chitanta + poGeneratorNumere.dezaloca_numar(3) && bon fiscal + ... + ``` + **Gap real, preexistent, verificat pe cod**: acest bloc dezaloca 16 (chitanta) si 3 (bon fiscal), + dar **nu 26 (POS)** — nici direct, nici in blocul urmator (`:3033-3035`, care dezaloca doar 16 din + nou, conditionat). Daca utilizatorul alege POS/Card si apoi anuleaza dialogul, numarul POS alocat + **ramane alocat**, nedezalocat. Nu e introdus de S3b — exista deja in codul de azi — dar merita + semnalat explicit ca risc mostenit (sectiunea 9), pentru ca S3b il "muta ca atare" (plan, textul + povestii), deci il si multiplica pe orice cale noua prin care sectiunea pliata s-ar putea + "renunta" fara sa fie un dialog modal cu propriul buton Renunt. +3. **`Init` pe eroare Oracle** (`:3159`) — `poGeneratorNumere.dezaloca_numar(5)` (factura) daca + interogarea `v_nom_casa` esueaza, cu `Return` imediat dupa (linie explicita, spre deosebire de + bug-ul #16 din `s3_portare_antet.md` §6, unde nu exista `Return` echivalent). Semnalat, neurmarit + mai departe aici — acelasi risc de arhitectura ca #16, dar pe alt fisier. + +### 4.3 Riscul central pentru formularul unificat, si recomandarea + +**Problema mecanica exacta**: in fluxul de azi, `frm_alte_date` e un dialog modal, instantiat **o +singura data** per emitere (`ofacturare.vc2:14921`), asa ca `Init`-ul lui (si declansarea eventuala de +la linia 3150) ruleaza **o singura data**. In formularul unificat, grupul de incasare devine o +sub-sectiune a unui panou pliabil, pe **acelasi obiect de formular persistent** — daca implementarea +naiva re-populeaza `opt_incasat.Value` din `poDate` de fiecare data cand sectiunea se depliaza (ex. +"la fiecare `Show`/`Visible=.T.` al panoului, resincronizeaza controalele cu starea curenta a lui +`poDate`"), **fiecare depliere ar re-declansa `ProgrammaticChange` -> `actualizeaza_tipincasare()` -> +dezaloca tot + realoca pe optiunea curenta** — incalcand direct criteriul din S3b ("deschiderea si +inchiderea formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar"). + +**Recomandare concreta**: populeaza `opt_incasat.Value` din `poDate` **o singura data**, la +constructia sectiunii de antet a documentului (echivalentul lui `Init` de azi, care ruleaza o data per +sesiune de emitere/editare) — nu la fiecare toggle de pliere/depliere. Plierea/deplierea trebuie sa +fie **strict** un toggle de `Visible`/`Height` pe containerul vizual, fara nicio atingere a +proprietatii `.Value` a lui `opt_incasat` sau a oricarui alt control cu `ProgrammaticChange` cablat pe +alocare. Aceasta e exact genul de regula care trebuie verificata explicit la implementare (criteriu de +"gata" concret la sectiunea 10), nu presupusa. + +--- + +## 5. Grupul de incasare pe document deja emis (decizia 25) + +**Ce spune decizia**: orice camp schimbat din grupul C (incasare) pe un document deja emis +"marcheaza documentul pentru regenerare" — dar regenerarea nu exista in etapa I, deci **planul tine +campurile blocate**, cu explicatie la hover, pana la etapa II (plan `:192-201`, `:1847-1848`). +`rute_scriere_antet.md` §3 confirma independent ca nu exista nicio cale directa de scriere pentru +grupul C dupa emitere (verdict "NU" pentru toate controalele, "PARTIAL neconfirmat" doar pentru o +cale indirecta prin editarea notei — vezi §3.2 acolo, ramane deschisa). + +### 5.1 Mecanic: pe ce proprietate, pe ce conditie + +Decizia 9 stabileste ca `but_modifica` deblocheaza **tot** antetul, fara exceptie de camp — dar +decizia 25 taie o exceptie explicita peste asta pentru grupurile B si C. Nu exista inca (verificat, +`rute_scriere_antet.md` §2-3) un mecanism de blocare per-grup separat de `but_modifica`; trebuie +construit nou, dar dupa un tipar deja folosit in codebase pentru exact intrebarea "acest document are +deja un numar, sau e nou": `chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))` +(`ofacturare_comun.vc2:5736-5750`, citat deja in plan I:839 si S1 randul 60). Decizia 9 **abandoneaza** +acest test ca poarta pe cele patru bife de identitate (serie/numar/data/scadenta) — dar testul insusi +ramane exact instrumentul potrivit pentru o alta intrebare: "documentul e nou sau deja emis?", care e +tocmai conditia ceruta de decizia 25 pentru grupurile B/C. + +**Recomandare mecanica**: la construirea antetului, se calculeaza o singura data un flag (ex. +`lDocumentExistent = !EMPTY(NVL(poRec.numar_act,0))` sau echivalentul lui pe obiectul `poDate`/`poRec` +folosit de formularul unificat — de confirmat la implementare care obiect poarta `numar_act` in noua +arhitectura, pentru ca `poRec` era specific lui `frm_modifica_factura`, perimetrul #6). Controalele +grupului C (`opt_incasat` si tot ce atarna de el din tabelul sectiunii 1.2) primesc `Enabled = .F.` +**neconditionat de starea lui `but_modifica`** cand `lDocumentExistent = .T.`, in etapa I. Cand +`but_modifica` deblocheaza restul antetului, acest grup **ramane** dezactivat — e o exceptie fixa, nu +una care se recalculeaza la fiecare click pe `but_modifica`. + +**Grupul B** (tip document, valuta, zi curs, client, sursa, gestiune sursa, politica de preturi) are +aceeasi tratare — dar acele controale traiesc azi in `frm_date_factura`/`frm_date_aviz` +(sectiunea I a planului, nu sectiunea B de aici), deci blocarea lor nu e in perimetrul S3b propriu-zis; +se semnaleaza aici doar pentru consecventa mecanismului (acelasi flag, acelasi tipar de `Enabled=.F.` +peste orice stare a lui `but_modifica`). + +### 5.2 La emitere, se comporta ca azi + +Pentru un document **nou** (nu inca emis), `lDocumentExistent = .F.`, grupul C e activ normal — se +comporta identic cu `frm_alte_date` de azi, cu masina de stari intreaga din sectiunile 3-4. Nimic nu +se schimba pe calea de emitere initiala; blocarea se aplica **doar** cand formularul unificat deschide +un document deja scris in `VANZARI` (calea de editare, nu de creare). + +### 5.3 Tooltip / explicatie la hover + +Ca la sectiunea 2.2, textul exact nu exista inca — de propus la implementare, in tonul deja folosit in +codebase. Continut minim necesar: sa spuna ca modificarea incasarii pe un document emis nu e inca +posibila din acest formular ("disponibil dupa ce regenerarea documentului e implementata" sau +echivalent), nu doar "camp dezactivat" fara explicatie — plan `:198-200` cere explicit "cu explicatie +la hover". + +--- + +## 6. Mecanismul de pliere — nu exista, se construieste nou dupa un tipar existent + +**Cautare directa in prototip**: `vfp_symbols.ps1 -Grep` (si grep text simplu) pe "plia|colaps| +collapse|expand|acordeon|accordion" in `COMUN\clase\ofacturare.vc2` (unde traieste +`frm_facturare_articole2`, `:15741-19355`) — **zero potriviri**. Prototipul are controalele de antet +montate direct pe formular (confirmat deja de `s3_portare_antet.md` §3 si de +`inventar_controale_formulare.md` §2), dar **niciun mecanism de show/hide de sectiune** — nu exista +nici macar un buton candidat. + +**Cel mai apropiat tipar real din suita**: `frm_modific2024.afiseaza_rulaje` +(`COMUN\clase\omodificari.vc2:13169-13199`, perimetrul #6, doar citit aici). Cod complet: +``` +PROCEDURE afiseaza_rulaje + * COLAPSEAZA SAU MARESTE PAGEFRAME-UL RULAJE + lcPicture = this.but_afiseaza_rulaje.Picture + ... + llAfiseazaRulaje = 'sus'$m.lcPicture + IF m.llAfiseazaRulaje + lcPicture2 = STRTRAN(m.lcPicture,'sus','jos') ... + ELSE + lcPicture2 = STRTRAN(m.lcPicture,'jos','sus') ... + ENDIF + this.but_afiseaza_rulaje.Picture = m.lcPicture2 + ... + this.pgfArticole.Visible = m.llAfiseazaRulaje + this.but_copiazaR.Visible = m.llAfiseazaRulaje + this.but_modificaR.Visible = m.llAfiseazaRulaje + this.but_stergeR.Visible = m.llAfiseazaRulaje + thisform.resize_grid1() +ENDPROC +``` +Trei observatii directe: +1. **Foloseste exact acelasi idiom sus/jos** deja validat si documentat pentru `but_modifica`/ + `but_salveaza` (decizia 9, `docs\cercetare\buton_comutator_picture.md`, citat in plan `:63-75`): + citeste starea curenta din numele fisierului `Picture` (contine "sus" sau "jos"), calculeaza + perechea opusa prin `STRTRAN`, rescrie **`Picture` + `cPictureDown` + `cPictureUp` impreuna** — + aceeasi precautie semnalata in plan ("numai `.Picture` nu ajunge: hover-ul standard din + `buton.MouseEnter`/`MouseLeave` rescrie `Picture` din `cPictureUp`/`cPictureDown`"). +2. **Nu e o clasa reutilizabila, e cod ad-hoc per caz** — toggle direct pe `.Visible` al unei liste + fixe de controale, plus un apel manual de reflow (`resize_grid1()`, metoda proprie a formularului, + nu un mecanism generic). Nu exista in nicio biblioteca de clase din `COMUN` (`_baza.vcx`, etc., + cautare separata, zero potriviri pentru o clasa "panou pliabil"/"expander"/"accordion") un + container gata facut de pliere. **S3b trebuie sa scrie cod nou**, dupa acest tipar, nu sa + instantieze o clasa existenta. +3. **E in perimetrul #6, doar citire** — poate fi copiat ca *idee*/tipar la implementare, dar + `omodificari.vc2` insusi nu se atinge cat timp #6 e in lucru (decizia 30). + +### 6.1 Ce trebuie sa scrie S3b, concret + +- **Un buton comutator per sectiune pliata** (sau unul singur pentru toata zona "alte date + + analitice", de decis la implementare — plan I nu specifica granularitatea), cu imaginile + sus/jos deja existente in `COMUN\grafice` (aceleasi perechi folosite pentru `but_afiseaza_rulaje`, + de identificat exact numele fisierelor la implementare — nu verificate aici, doar tiparul de + mecanism). +- **Toggle de `.Visible`/`.Height`** pe grupul de controale al sectiunii (delegat/transport, incasare, + adresa, text aditional, analitice), plus un apel de reflow echivalent lui `resize_grid1()` — pentru + ca restul formularului (butoane, alte sectiuni de mai jos) sa nu ramana cu goluri sau suprapuneri + cand sectiunea se pliaza/depliaza. VFP nu reflow-eaza automat un layout absolut-pozitionat (`Left`/ + `Top` fixe pe fiecare control) — de asta si tiparul din `frm_date_factura.Init` (citat in + `s3_portare_antet.md` §5, "masoara inaltimile containerelor de antet intr-un array `laPozitii`") cat + si `afiseaza_rulaje` fac manual acest calcul; nu exista un layout manager automat de reutilizat. +- **Populare o singura data** a starii initiale (sectiunea 4.3) — separat de toggle-ul de vizibilitate, + ca sa nu retrigger-eze `ProgrammaticChange`. +- **Intrebare deschisa, semnalata deja in `s3_portare_antet.md` §5** (nu redusa aici): daca lookup-urile + Oracle ale lui `frm_alte_date.Init` (delegat/masina ultima factura, cursorul `v_nom_casa`) raman + **eager** (rulate la construirea formularului, ca azi) sau devin **lazy** (amanate pana la prima + depliere a sectiunii, ca sa nu incarce round-trip-uri Oracle inutile cand userul nu ajunge niciodata + acolo). Ambele optiuni sunt compatibile cu criteriul "deschiderea/inchiderea nu aloca numere" **doar + daca** populate keeping in mind sectiunea 4.3 (populare o singura data, indiferent de varianta + aleasa) — **de decis de Marius**, vezi sectiunea 9. + +--- + +## 7. Ordinea de executie + +Pasi care lasa suita functionala dupa fiecare — calea veche (`frm_alte_date` ca dialog separat) **nu +se sterge** in etapa I, cf. textul povestii ("`frm_alte_date` nu se sterge cat timp calea veche mai e +in uz"). + +1. **Construieste sectiunea vizuala pliata** (patru grupuri din sectiunea 1 + analiticele din + sectiunea 2), fara nicio logica de alocare/validare inca — doar controale + toggle de + Visible/Height (sectiunea 6). *Criteriu:* fiecare control existent in `frm_alte_date`/antetul de + analitice apare o data, cu aceeasi eticheta, in sectiunea corecta a formularului unificat — + verificare camp-cu-camp fata de tabelele din sectiunile 1-2 de aici. +2. **Porteaza `actualizeaza_tipincasare` ca atare** (sectiunea 3), cu toate cele noua referinte + `Thisform.xxx` rezolvate corect in noul container (sectiunea 3.2). *Criteriu:* alegerea fiecarei + optiuni din `opt_incasat` produce exact aceleasi `Visible`/etichete pe controalele dependente ca pe + `frm_alte_date` de azi, verificabil headless (sectiunea 8). +3. **Rezolva explicit riscul de la sectiunea 4.3** — populare unica a starii, toggle de pliere fara + atingere de `.Value`. *Criteriu, verificabil headless:* pe un document de test cu `poDate.incasat<>0` + la construirea formularului, se depliaza si se plie + aza sectiunea de trei ori consecutiv fara a atinge + niciun control din grup — `poGeneratorNumere` (cursorul lui de numere alocate) arata **acelasi** + numar de chitanta la finalul celor trei toggle-uri ca la inceput, nu unul nou de fiecare data. +4. **Porteaza delegat/transport si adresa de facturare** (`do_cauta_delegat`/`do_cauta_masina`/ + `do_cauta_agent`/`do_cauta_adresa`/`do_modifica`), cu validarea din `inainte_de_do_termin` + (sectiunea 1.1). *Criteriu:* fiecare cautare deschide acelasi dialog si scrie aceleasi proprietati + pe `poDate` ca azi (comparatie directa, cod identic mutat). +5. **Adauga analiticele read-only** (sectiunea 2), inclusiv completarea `do_dezactiveaza()` cu + `ReadOnly=.T.` explicit. *Criteriu:* tastarea directa in oricare din cele patru campuri nu modifica + valoarea afisata (verificare pe proprietate, headless). +6. **Implementeaza blocarea grupului C pe document deja emis** (decizia 25, sectiunea 5), cu flag-ul + de "document existent" si exceptia peste `but_modifica`. *Criteriu:* pe un document nou, + `but_modifica` (cand exista, dupa S3/decizia 9) nu afecteaza grupul C (mereu activ); pe un document + deja emis, grupul C ramane `Enabled=.F.` inainte **si** dupa apasarea lui `but_modifica`. +7. **Verificare de regresie end-to-end**: emite acelasi document (numerar, chitanta, bon fiscal, POS — + toate patru, nu doar unul) o data pe calea veche (`frm_alte_date` dialog) si o data pe formularul + unificat, compara `poDate`-ul rezultat inainte de scriere in Oracle (aceleasi campuri de incasare, + acelasi numar alocat) — tipar deja folosit in `s3_portare_antet.md` §7 pentru S3, reutilizat aici + pe grupul mai mic al lui S3b. + +*Depinde de S3* (planul o spune explicit) — pasii 4 si 6 presupun ca `but_modifica`/blocarea antetului +(proiectata in S3, sectiunea I a planului) exista deja ca mecanism, nu doar ca design pe hartie. + +--- + +## 8. Ce nu se poate testa headless + +Precedent direct: `s3_portare_antet.md` §8, si memoria de proiect +(`grid-coloane-nu-se-materializeaza-headless` — coloanele de grid nu se materializeaza sub `-A -T`, +exista harness UI vizibil separat care le citeste corect). + +**Se POATE testa headless** (contrar primei intuitii, pentru ca e logica pe proprietati, nu pictura pe +ecran): +- Toata masina de stari `actualizeaza_tipincasare` — `Createobject()` fara `.Show()` tot ruleaza + `Init` si tot cabl eaza evenimentele; `thisform.opt_incasat.Value = n` declanseaza + `ProgrammaticChange` identic cu UI vizibil sau nu. Verificarile de alocare/dezalocare de la pasul 3 + al sectiunii 7 sunt testabile 100% headless, pe proprietatile lui `poGeneratorNumere`. +- Vizibilitatea controalelor dupa toggle (`.Visible`, `.Height`, existenta obiectului via `Type(...)`) + — acelasi motiv, confirmat deja de `s3_portare_antet.md` §8 pentru mecanismul similar din `Init`-urile + de antet. + +**NU se poate testa headless**: +- **Aspectul vizual real dupa pliere/depliere** (suprapuneri, goluri, pozitionarea corecta a + controalelor de sub sectiune dupa reflow-ul manual, sectiunea 6.1) — layout absolut-pozitionat, fara + reflow automat; verificarea ceruta e "arata bine pe ecran", nu o proprietate booleana — necesita + harness UI vizibil (memoria de proiect citata mai sus) sau verificare manuala de Marius. +- **Cele patru dialoguri modale** (`do_cauta_delegat`/`do_cauta_masina`/`do_cauta_agent`/ + `do_cauta_adresa`) — interactiunea reala de alegere dintr-o lista nu se simuleaza headless; se poate + testa doar efectul **dupa** ce rezultatul cautarii e construit manual (tiparul deja folosit in + proiect, citat in `s3_portare_antet.md` §8, pct. 1). +- **Lookup-urile Oracle din `Init` (delegat/masina ultima factura, `v_nom_casa`)** — necesita conexiune + Oracle reala (harnessul suporta asta prin `MARIUSM_AUTO`, dar tot nu e "headless" in sensul de + "fara dependinte externe") si date de test care sa existe pentru clientul folosit. +- **Butonul `cmdModificaBon`/`do_modifica_bon`** — deschide `viz_config_serii_complet WITH 3` + (`oserii_numere.prg`), alt formular modal, aceeasi limitare ca dialogurile de cautare. + +--- + +## 9. Riscuri si capcane + +1. **Riscul central** (sectiunea 4.3): re-populare a starii `opt_incasat` la fiecare toggle de pliere + ar re-declansa alocare/dezalocare, incalcand direct criteriul cerut de decizia 25/S3b. Tratat + explicit ca pas 3 in ordinea de executie — **nu** de lasat "se rezolva de la sine prin arhitectura", + pentru ca mecanismul de declansare (`ProgrammaticChange`) e usor de lovit accidental de orice cod + ulterior care "resincronizeaza UI-ul cu modelul" intr-un loc gresit. +2. **Gap preexistent, nu introdus de S3b, dar mostenit prin "muta ca atare"**: `inainte_de_do_renunt` + (`:3025-3030`) nu dezaloca numarul POS (26) la anulare — doar chitanta (16) si bon fiscal (3). Daca + formularul unificat introduce vreo cale noua de "renunta la document" care nu mapeaza exact pe acest + cod, riscul se poate agrava (numar POS ramas alocat orfan, de fiecare data cand utilizatorul incepe + un document cu POS si renunta). Recomandare: fie se corecteaza in trecere (nu e in perimetrul strict + al portarii "ca atare", dar e o linie), fie se semnaleaza explicit ca bug cunoscut de preluat separat. +3. **`_checkbox1`/"Listare detaliata" vs. `chkDetaliat`** (sectiunea 1.4) — doua controale cu semantica + probabil identica, ambiguitate deja semnalata in S1 (randul 57), neinchisa aici. Formularul unificat + trebuie sa aleaga unul singur; alegerea gresita (portarea amandurora ca doua campuri distincte) + ar introduce un camp duplicat fara sens pentru utilizator. +4. **`do_dezactiveaza()` insuficient pentru read-only real** (sectiunea 2.1) — daca implementarea + apeleaza doar metoda existenta fara completarea de `ReadOnly`, decizia 26 nu e respectata mecanic: + campul arata blocat (fara lupa) dar tot accepta taste. +5. **Referinte `Thisform.xxx` nekvalificate** in `actualizeaza_tipincasare` (sectiunea 3.2) — orice + decizie de a pune sectiunea pliata intr-un container/obiect separat (nu direct pe `Thisform`) rupe + tacit aceste referinte, cu eroare de "obiect inexistent" la runtime, nu la compilare. +6. **Dependinta pe S3**: `but_modifica` si testul "document existent" (sectiunea 5.1) nu exista inca + nicaieri in cod — proiectate doar pe hartie in plan (sectiunea I). Pasii 4 si 6 din sectiunea 7 nu + pot fi *implementati* complet inainte ca S3 sa livreze acel mecanism, doar proiectati. +7. **Bug-ul de `sectie`** (`omodificari.vc2:13941`, semnalat deja in `rute_scriere_antet.md` §1.3 si + in plan `:761-764`) — nu afecteaza direct S3b (analiticele sunt read-only aici, deci S3b nu scrie + niciodata `id_sectie`), dar afecteaza increderea in ce se **afiseaza**: daca bug-ul e real, coloana + `id_sectie` din Oracle poate sa nu reflecte eticheta aratata dupa o editare din #6. In afara + perimetrului S3b de reparat (e in #6), dar relevant pentru cine citeste afisarea din #13 dupa o + editare de la #6. + +--- + +## 10. Criteriul de "gata", rescris verificabil + +Textul din plan (`:1849-1852`) spune: *"un document cu incasare prin bon fiscal emis din formularul +unificat produce aceleasi randuri si acelasi numar de bon ca pe calea veche; deschiderea si inchiderea +formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar; iar pe un document +emis analiticele se vad dar nu se pot edita."* Precizat mai jos, pe fiecare bucata: + +1. **Paritate de emitere, pe toate patru tipurile de incasare, nu doar bon fiscal**: se emite acelasi + document de test (client, articole, cantitati, preturi identice) o data pe calea veche + (`frm_date_factura`/`frm_date_aviz` -> `frm_facturare_articole(2)` -> `frm_alte_date`) si o data pe + formularul unificat, pentru fiecare din **Fara incasare / Chitanta / Bon fiscal / POS-Card**. Se + compara `lcListaIncasare` (stringul asamblat inainte de `scrie_factura2`, sectiunea 3.1 al + `rute_scriere_antet.md`) si numarul alocat (`poDate.nr_incasare`) — trebuie sa fie identice pe + fiecare din cele patru cazuri, nu doar pe bon fiscal. +2. **Non-alocare la toggle de pliere, testabila headless** (sectiunea 7, pasul 3, deja formulat ca + assert concret pe `poGeneratorNumere`) — pe **doua** scenarii de start, nu unul: document nou + (`poDate.incasat=0` la construire) **si** document venit din copiere cu incasare presetata + (`poDate.incasat<>0`, sectiunea 4.1 punctul 3) — al doilea caz e cel care azi *deja* aloca la + deschidere, exact scenariul pe care criteriul trebuie sa-l acopere explicit. +3. **Blocarea grupului C, pe ambele stari ale antetului**: pe un document deja emis, campurile din + grupul de incasare raman `Enabled=.F.` atat inainte cat si dupa apasarea lui `but_modifica` — nu + doar "la deschidere" (ce ar lasa loc unei regresii daca `but_modifica` le-ar debloca din greseala + odata cu restul). +4. **Analiticele, pe ambele stari**: pe un document emis, cele patru campuri (venit/cheltuiala, sectie, + responsabil, lucrare) afiseaza valoarea curenta din sursa lor (sectiunea 2.3 — de precizat exact + care sursa la implementare) si resping orice tentativa de tastare directa sau de deschidere a + dialogului de cautare — verificat pe proprietatea `ReadOnly`/`lactiv`, nu doar vizual. +5. **Delegat/transport/adresa/text aditional** — paritate camp-cu-camp cu `frm_alte_date` de azi, pe + cel putin un document proforma (grup delegat/incasare eliminat, sectiunea 1.4) si unul non-proforma + (grup complet), ca sa acopere ambele ramuri ale `Init`-ului de azi (`:3109-3207`). + +*Depinde de:* S3 (but_modifica, mecanismul de blocare a antetului). *Nu depinde de:* etapa II +(regenerarea) — grupurile B/C raman blocate prin design in etapa I, nu prin absenta temporara a unei +functionalitati care ar trebui testata aici. + +--- + +## Ce ramane de decis de Marius + +1. **Textul exact al tooltip-urilor** pentru analiticele read-only (sectiunea 2.2) si pentru grupul de + incasare blocat pe document emis (sectiunea 5.3) — doar continutul minim necesar e propus aici, nu + formularea finala. +2. **Granularitatea butonului/butoanelor de pliere**: un singur comutator pentru toata zona "alte date + + analitice", sau unul separat per subgrup (delegat/transport, incasare, adresa, text, analitice)? + Plan sectiunea I nu specifica, iar tiparul gasit (`afiseaza_rulaje`) e per-sectiune unica, nu + per-formular-intreg — precedentul nu decide singur granularitatea (sectiunea 6.1). +3. **Eager vs. lazy pentru lookup-urile Oracle din `Init`** (delegat/masina ultima factura, casa) — + semnalat si in `s3_portare_antet.md` §5 ca discutie deschisa, reconfirmat aici pentru acelasi cod + (sectiunea 6.1, ultimul punct). +4. **Corectarea gap-ului de dezalocare POS la anulare** (sectiunea 9, punctul 2) — se repara in trecere + ca parte a S3b, sau se lasa exact ca azi si se semnaleaza separat ca bug de preluat ulterior? +5. **`_checkbox1` vs. `chkDetaliat`** (sectiunea 1.4, sectiunea 9 punctul 3) — care dintre cele doua + controale de "listare detaliata" devine campul unic in formularul unificat; ambiguitatea ramane + deschisa din S1 si nu s-a inchis aici. + +## Handoff + +Cercetare incheiata, nu intrerupta la mijloc — toate cele zece sectiuni cerute in briefing sunt +complete, cu citate `fisier:linie` verificate direct pe fisierele reale (nu `.bak`). Niciun cod +atins, nicio interogare Oracle rulata (nu a fost nevoie — tot ce trebuia era in codul VFP deja citit +sau in cele patru rapoarte de referinta). Fisierul e complet la aceasta versiune; nu e nevoie de o +sesiune de continuare pentru S3b ca atare. Urmatorul pas natural (nu al acestei sarcini) ar fi S3 +insusi (blocarea antetului, `but_modifica`) — S3b depinde de el pentru pasii 4 si 6 din sectiunea 7, +dar proiectarea de aici nu asteapta acel livrabil, doar implementarea o va astepta. diff --git a/docs/cercetare/s3c_sursa_ca_parametru.md b/docs/cercetare/s3c_sursa_ca_parametru.md new file mode 100644 index 0000000..d9b2c96 --- /dev/null +++ b/docs/cercetare/s3c_sursa_ca_parametru.md @@ -0,0 +1,426 @@ +# S3c — Proiectare: sursa ca parametru, nu ca global + +Cercetare read-only pentru povestea **S3c** din `docs\plan_13_unificare_formular_facturare.md` +(`#### S3c`, linia 1855; decizia 12, linia 473; L.3, linia 1733; „Canalul de precompletare”, +liniile 465-474). Zero modificari de cod, zero write-back, zero `git_sync.ps1`, zero commit — in +niciun produs din `D:\ROA`. Depinde de S2 (`docs\cercetare\s2_factureaza_unificare.md`), a carui +proiectare a lasat explicit semnatura lui `factureaza` neschimbata „pentru ca e treaba lui S3c” +(sectiunea 5 a acelui raport). + +Status: **complet**. + +--- + +## Verdict + +**Se poate face, e o interventie mica pe cod, dar textul planului supraestimeaza cat de „globala" +e problema azi.** `goContract` nu e niciodata scris in `ROAFACTURARE` — nici in codul specific +produsului, nici in `COMUN`-ul lui — deci ramura de precompletare din contract +(`ofacturare_comun.prg:261-297`) e **cod mort in ROAFACTURARE azi**, nu doar teoretic riscant. +Asimetria de resetare semnalata la L.3 (`ofundal_facturare.vc2:886-902`) exista textual, dar +**nu produce niciun efect observabil azi**, pentru ca nu exista niciun scriitor al lui `goContract` +in acest produs care sa lase ceva de resetat. Ea devine un risc real abia daca cineva adauga in +viitor, in `ROAFACTURARE`, un cod care scrie `goContract` — situatie in care lipsa resetarii ar +deveni activa dintr-o data, tacut. `goComanda`, in schimb, chiar e viu in `ROAFACTURARE`: are doi +scriitori (`ocomenzi.vc2:1583` la clic pe „Factureaza”, **si** `ocomenzi.vc2:2199`, ca efect +colateral al navigarii in grid), iar reset-ul explicit de la `ofundal_facturare.vc2:900` e singura +plasa de siguranta reala azi. + +**Suprafata de regresie e mica si masurata exact**: doi scriitori de convertit +(`ocomenzi.vc2:1580-1596` in familia ROAFACTURARE, `ferestre_contracte.vc2:1538-1549`+`:1598-1606` +in ROACONTRACTE), trei fisiere comune de atins o singura data fiecare +(`ofacturare.prg`, `oproceduri_facturare.prg`, `ofacturare_comun.prg`), si **zero schimbari** la +celelalte ~31 puncte de intrare din inventarul S2 — toate cheama `factureaza(N)` cu un singur +parametru, deci un al treilea parametru opozitional cu implicit `.F.`/`NULL` nu le atinge. + +**Corectie de scop fata de formularea planului**: „pana se convertesc toti apelantii" nu poate +insemna, pentru `goContract`, „pana dispare global-ul" — `goContract` e bufferul de editare al +intregului ecran de contracte din ROACONTRACTE (peste 100 de `ControlSource` legate de el, +sectiunea 1), populat continuu de navigarea in grid, independent de facturare. Nu va disparea +niciodata din ROACONTRACTE la aceasta poveste sau la vreuna viitoare rezonabila — S3c schimba doar +**canalul prin care valoarea ajunge la `oDateFactura.Init`**, nu existenta globalei in ROACONTRACTE. +Criteriul de „gata” de mai jos (sectiunea 8) e rescris sa reflecte asta. + +--- + +## 1. Inventarul scrierilor si citirilor `goComanda` / `goContract` in toata suita + +Cautare in `D:\ROA\ROAFACTURARE`, `D:\ROA\ROACONT`, `D:\ROA\ROAGEST`, `D:\ROA\ROAAUTO`, +`D:\ROA\ROAACNPRO`, `D:\ROA\ROAIMOB`, `D:\ROA\ROACONTRACTE` — radacina fiecarui produs **si** +`COMUN\`-ul lui explicit (capcana „Grep nu vede COMUN”). `D:\ROA\COMUNROA` nu contine niciuna din +cele doua variabile (cautare separata, zero rezultate) — confirma ca sursa nu e livrata prin +biblioteca partajata, ci prin fisierele COMUN duplicate per produs. + +### `goComanda` + +| Fisier | Linie | Rol | Produse | +|---|---|---|---| +| `Programe\roafacturare.prg` | `:473-474` | **Declarare**: `PRIVATE goComanda` / `goComanda = null`, la pornirea aplicatiei | doar ROAFACTURARE (fiecare produs are propriul `roafacturare.prg`/echivalent, nu verificat identic — irelevant, doar initializeaza la null) | +| `COMUN\clase\ocomenzi.vc2:1580-1596` (`ct_comenzi.do_factura`) | `:1583` | **SCRIE**: `SELECT crsComenzi` / `SCATTER NAME goComanda MEMO`, apoi cheama `facturare_comenzi` | ROAFACTURARE, ROACONT, ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB (fisier identic pe MD5, sectiunea 6) | +| `COMUN\clase\ocomenzi.vc2:2191-2207` (`_grdrow1.AfterRowColChange`) | `:2199` | **SCRIE** ca efect colateral: la fiecare schimbare de rand in grid-ul de comenzi, `SCATTER NAME goComanda MEMO`, folosit doar pentru `but_factura1.Visible = (goComanda.facturat = 0)` (`:2202`) — **nu are nicio legatura cu intentia de facturare**, dar suprascrie global-ul de fiecare data cand utilizatorul navigheaza in grid | idem, acelasi fisier identic | +| `COMUN\programe\ofacturare_comun.prg:301-328` (`oDateFactura.Init`) | `:301` | **CITESTE**: `If tnTip = 3 And Type('goComanda') = 'O'` -> `.id_client`, `.nume_client`, `.cod_fiscal`, `.listaid`, `.descriere`, `.id_sectie`, plus un `SELECT sectie FROM nom_sectii` pe `goComanda.id_sectie` | identic pe 6/7 produse (sectiunea 6; ROAIMOB diverge cu o linie, dar nu pe acest bloc) | +| `Clase\ofundal_facturare.vc2:899-902` (`Page2.Cw3.do_actiune`) | `:900` | **RESETEAZA** explicit: `goComanda = ''` inainte de `DO facturare_comenzi` | doar ROAFACTURARE (fisierul e specific produsului, nu COMUN) | + +Niciun alt fisier, in niciun produs verificat, nu scrie sau citeste `goComanda`. + +### `goContract` + +| Fisier | Linie | Rol | Produse | +|---|---|---|---| +| `ROACONTRACTE\Programe\roacontracte.prg:559-560` | `:559-560` | **Declarare**: `Public poCtr, goContract` / `Store '' To poCtr, goContract`, la pornirea aplicatiei | doar ROACONTRACTE | +| `ROACONTRACTE\Clase\ferestre_contracte.vc2` | ~200 aparitii (liniile 977-11005, tabel complet in sectiunea de cautare) | **SCRIE si CITESTE** ca buffer de editare al intregului formular de contracte: `Scatter Name goContract Memo Blank` la creare (`:1026`, `:1186`), `SCATTER NAME goContract MEMO` la fiecare schimbare de rand in grid (`grid_contracte.AfterRowColChange`, `:1598-1606`), **peste 20** `ControlSource = "goContract."` pe controale (combo-uri, textbox-uri), `Gather Name goContract Memo` la salvare (`:1053`, `:2406-2472` etc.) | doar ROACONTRACTE | +| `ROACONTRACTE\Clase\ferestre_contracte.vc2:1538-1549` (`but_factura.Click`) | — | **CITESTE implicit** (nu re-scrie): cheama `facturare_contracte` fara sa re-populeze `goContract` — se bazeaza pe scrierea facuta deja de `AfterRowColChange` la selectia randului curent | doar ROACONTRACTE | +| `ROACONTRACTE\Clase\outlook2003bar.vc2`, `ofundal_roaclienti.vc2` | multiple | **CITESC** `goContract.id_ctr`/`.id_part` pentru navigare in bara laterala si ecrane de parteneri | doar ROACONTRACTE | +| `ROACONTRACTE\Programe\roacontracte.prg`, `oparteneri_contracte.prg`, `oproceduri_roacontracte.prg` | multiple | **CITESC/SCRIU** `goContract` in fluxuri proprii ROACONTRACTE (incasari, plati, rate, garantii) — complet independente de facturare | doar ROACONTRACTE | +| `COMUN\programe\ofacturare_comun.prg:261-297` (`oDateFactura.Init`) | `:261` | **CITESTE**: `If INLIST(m.tnTip, 2, 6, 52) And Type('goContract') <> 'U'` -> `.id_client`, `.nume_client`, `.cod_fiscal`, `.listaid`, `.descriere`, `.id_sectie`, `.sectie`, `.id_responsabil`, `.responsabil`, `.id_valuta`, `.nume_valuta`, plus interogare `fact_vcontracte` pentru scadenta | identic pe 6/7 produse (ROAIMOB diverge cu exact aceasta linie lipsa — sectiunea 6) | +| `COMUN\clase\ferestre_atasamente.vc2`, `COMUN\programe\oproceduri_atasamente.prg` | 5 aparitii | **CITESC** `goContract.id_ctr`, dar **numai** in ramura `Case Upper(Alltrim(gcNumeProgram)) = "ROACONTRACTE"` — cod prezent in fiecare copie COMUN (deci si in ROAFACTURARE), dar mort acolo pentru ca `gcNumeProgram` nu e niciodata `"ROACONTRACTE"` in afara procesului ROACONTRACTE | identic pe toate produsele verificate, dar activ doar in ROACONTRACTE | + +**Confirmare directa: `goContract` nu e scris niciunde in `ROAFACTURARE`** — nici in `Programe\`, +`Clase\`, `Ferestre\`, `Meniuri\`, nici in `COMUN\` (cautare pe `PUBLIC goContract`/`Public +goContract` in tot arborele: zero rezultate, tabelul complet cu toate declaratiile `PUBLIC go*` +gasite e in sectiunea 2). Singura mentiune a lui `goContract` in ROAFACTURARE e citirea garda de +`Type()` din `ofacturare_comun.prg:261`, care ramane mereu falsa (`Type('goContract') = 'U'`) intr-o +sesiune ROAFACTURARE. + +## 2. Verdict pe L.3 — asimetria de resetare + +**Textul din cod e adevarat, dar efectul practic e altul decat sugereaza formularea „de verificat” +din plan.** + +- **Asimetria exista, literal**: `ofundal_facturare.vc2:899-902` (`Page2.Cw3`, ruta genericaa de + comanda) face `goComanda = ''` inainte de `DO facturare_comenzi`; `ofundal_facturare.vc2:886-897` + (`Page2.Cw2`, ruta generica de contract) **nu** face echivalentul pentru `goContract` — cheama + direct `DO facturare_contracte WITH 'FACTURA LEI'/'INVOICE'/'FACTURA VALUTA'`. +- **Dar `facturare_contracte` (`oproceduri_facturare.prg:119-136`) nu citeste si nu scrie + `goContract` deloc** — primeste un string de tip (`'FACTURA LEI'` etc.) si cheama direct + `factureaza(2)`/`factureaza(6)`/`factureaza(52)`. Precompletarea din contract se intampla + **exclusiv** in `oDateFactura.Init`, la citirea globalei — nu exista niciun pas intermediar care + ar putea fi „resetat”. + In ROAFACTURARE, calea genericaa (Cw2) las utilizatorul sa aleaga contractul **in formular**, + printr-un combo populat din `crscontracte` legat de `poDate.listaid` + (`ofacturare.prg:283-291,433-440`) — un canal complet diferit, care nu trece prin `goContract` + deloc (confirmat citind codul, sectiunea 3). +- **Deci, in ROAFACTURARE azi, nu exista nicio secventa executabila in care `goContract` sa fie + citit cu o valoare veche.** Pentru ca asta sa se intample ar trebui ca (a) ceva sa fi scris + `goContract` mai devreme in aceeasi sesiune ROAFACTURARE — **nu exista niciun asemenea cod azi** — + si (b) urmatorul apel sa fie cu `tnTip` in `(2, 6, 52)`. Fara (a), (b) singur nu ajunge nicaieri. +- **Verdictul pe L.3**: asimetria de resetare **e reala ca defect de simetrie in cod** (o ruta isi + curata globala inainte de apel, cealalta nu), dar **inert azi in ROAFACTURARE** — nu exista niciun + document gresit care se poate emite azi din cauza ei, pentru ca variabila pe care ar trebui sa o + resetezi nu e niciodata populata in acest produs. Devine un risc real **doar** daca un cod viitor + (in ROAFACTURARE) incepe sa scrie `goContract` fara sa adauge simetric si resetul — exact genul de + capcana pe care „parametru explicit, fara global implicit” o elimina structural, nu prin inca o + linie de reset de tinut minte. +- **In ROACONTRACTE, unde `goContract` chiar e viu, nu exista o ruta „generica fara precompletare” + analoaga lui Cw2** — `but_factura.Click` (`:1538-1549`) e singurul punct de intrare si presupune + intotdeauna un contract selectat in grid (populat de `AfterRowColChange`, `:1598-1606`, la fiecare + schimbare de rand). Riscul teoretic acolo nu e „global ramas din alta sesiune de facturare”, ci + „utilizatorul apasa Factureaza fara sa fi selectat explicit un rand dupa un refresh programatic” — + un caz marginal, netratat de L.3 si nelegat de asimetria semnalata in plan. + +## 3. Doar doua globale, sau mai sunt? + +**Doar doua.** Verificat prin citirea completa a `oDateFactura.Init` +(`ofacturare_comun.prg:223-332`) si a rutarii cursoarelor de articole din `ofacturare.prg:260-308`: + +- **`goDate`, `gnIdSet` ca surse de precompletare — nu exista.** `goDate` nu apare niciunde in + `ofacturare.prg`/`ofacturare_comun.prg` (cautare directa, zero rezultate). `gnIdSet` nu exista ca + atare — parametrul se numeste `tnIdSet`/`lnIdSet`, calculat **local** in `factureaza` + (`lnIdSet = 25000 + tnTip - 1 + gnScadereStoc * 10`, `ofacturare.prg:123`) din `tnTip` si un + optiune de configurare (`gnScadereStoc`), nu un canal de sursa. +- **Singurele doua verificari `Type('go...')` din `oDateFactura.Init` sunt exact `goContract` + (`:261`) si `goComanda` (`:301`)** — cautare `Type\('go` pe tot `ofacturare.prg` + + `ofacturare_comun.prg`: zero alte rezultate in afara comutatorului de dezvoltator + `gnFacturareNou` (irelevant aici, documentat in S2). +- **`poDate.listaid` pentru avize (tip 4, 21, 28, 42, 47) NU trece printr-un global** — se scrie + direct din cursorul de selectie al formularului: `poDate.listaid = Iif(Inlist(poDate.tip, 3, 21, + 28, 42, 47), Alltrim(Str(id_comanda)), [])` (`ofacturare_comun.vc2:4255`, si varianta similara la + `:4062`, `:7271`) — populat dintr-un `Scan`/`Locate` peste cursorul cu avizele bifate de utilizator + in acelasi apel, nu dintr-o variabila globala persistenta intre apeluri. **Avizele nu intra in + S3c** — nu au canal implicit de eliminat. +- **Copierea (`toFactura`) e deja parametru, nu global** — `copiere_factura(toFactura)` + (`oproceduri_facturare.prg:150-153`) -> `factureaza(toFactura.Tip, toFactura)`, si in + `oDateFactura.Init`/logica de copiere valorile vin din `toDateAnterior` (parametrul), de exemplu + `.listaid = toDateAnterior.id_vanzare` (`ofacturare_comun.vc2:387`) — **e exact precedentul pe + care S3c il extinde la comanda/contract**, nu un al treilea canal implicit de adaugat la lista. +- **`poDate` insusi nu e un canal de scurgere intre apeluri** — se creeaza cu `Createobject` la + fiecare intrare in bucla lui `factureaza` (`ofacturare.prg:184`, doar `If Type('poDate') <> 'O'`, + ceea ce e adevarat prima data si ramane fals doar in interiorul aceluiasi apel, la reintrarile + bucla `Do While lnRaspuns = 6`) — deci nu poate purta stare intre doua clicuri distincte pe + „Factureaza”. + +**Concluzie**: `goComanda` si `goContract` sunt singurele doua canale implicite relevante pentru +decizia 12. Nu mai exista o a treia variabila de convertit odata cu ele. + +## 4. Semnatura propusa + +**`factureaza`** (`COMUN\programe\ofacturare.prg:81-82`), azi `Lparameters tnTip, toFactura`: + +``` +Lparameters tnTip, toFactura, toSursa +``` + +- **`toSursa`** — obiect, implicit `NULL` (nepasat de niciun apelant existent). Poarta **fie** un + obiect cu forma lui `goComanda` (cand `tnTip = 3`), **fie** un obiect cu forma lui `goContract` + (cand `tnTip IN (2, 6, 52)`) — exact aceeasi dualitate pe care codul de azi o rezolva deja prin + ramificare pe `tnTip` in `oDateFactura.Init` (`:261` vs `:301`), deci nu introduce un tip nou de + decizie, doar muta sursa valorii. +- **Pozitia**: al treilea parametru, dupa `toFactura`, niciodata inaintea lui — la fel ca precedentul + `V_TAXCODE`/`V_LOT` citat de tine: apelurile VFP sunt pozitionale, iar singurul mod sa nu rupi + apelantii existenti e sa adaugi la coada, cu implicit. Toate cele ~31 de apeluri din inventarul S2 + care pasesc doar `tnTip` raman **neschimbate** — VFP completeaza automat parametrii nepasati la + coada cu `.F.` (verificat comportamental: `Type()` pe un parametru nepasat intoarce `'L'` cu + valoarea `.F.`, nu `'U'` — de tratat explicit in garda, vezi sectiunea 5). +- **De ce nu inlocuieste `toFactura`**: `toFactura` inseamna „copiaza factura asta” (un document deja + emis), `toSursa` inseamna „precompleteaza din documentul asta” (o comanda sau un contract inca + nefacturat) — semantic distincte, si `copiere_factura` (`oproceduri_facturare.prg:150-153`) ar + putea teoretic avea nevoie de amandoua simultan in viitor (copiere + realocare pe alt contract) — + motiv suplimentar sa nu le contopesti intr-un singur parametru. + +**Constructorul `oDateFactura.Init`** (`ofacturare_comun.prg:223-224`), azi +`Lparameters tnIdSet, tnTip`: + +``` +Lparameters tnIdSet, tnTip, toSursa +``` + +- Aceeasi regula de pozitionare: la coada. Singurul apelant azi e + `Createobject("oDateFactura", lnIdSet, tnTip)` din `factureaza` (`ofacturare.prg:184`) — devine + `Createobject("oDateFactura", lnIdSet, tnTip, toSursa)`. Nu exista alt loc din cod care + instantiaza `oDateFactura` (cautare `Createobject("oDateFactura"` pe tot `COMUN`: un singur + rezultat). + +## 5. Mecanismul de sursa de rezerva + +**Regula**: la fiecare din cele doua ramuri, se prefera `toSursa` daca a fost pasat ca obiect; daca +nu, se cade pe globala, exact ca azi. Nu se schimba forma datelor citite (aceleasi campuri), doar +sursa lor. + +```foxpro +* ofacturare_comun.prg, oDateFactura.Init — inlocuieste liniile 261 si 301 + +Local loSursa +loSursa = Iif(Type('toSursa') = 'O', toSursa, Null) + +If INLIST(m.tnTip, 2, 6, 52) And (Type('loSursa') = 'O' Or Type('goContract') <> 'U') + If Type('loSursa') <> 'O' + loSursa = goContract + Endif + .id_client = loSursa.id_part + .nume_client = loSursa.denumire + * ... restul neschimbat, doar goContract -> loSursa +Endif + +loSursa = Iif(Type('toSursa') = 'O', toSursa, Null) && re-evaluat, nu se refoloseste variabila de mai sus intre ramuri + +If tnTip = 3 And (Type('loSursa') = 'O' Or Type('goComanda') = 'O') + If Type('loSursa') <> 'O' + loSursa = goComanda + Endif + .id_client = loSursa.id_part + * ... restul neschimbat, doar goComanda -> loSursa +Endif +``` + +De ce doua evaluari separate ale lui `loSursa` (nu una singura la inceput): `toSursa` poarta o +singura forma per apel (fie comanda, fie contract, niciodata amandoua — `tnTip` decide exclusiv +care), dar garda trebuie sa verifice `tnTip` inainte sa presupuna forma, la fel ca azi. O variabila +locala unica evita sa scrii `toSursa` de doua ori in tot blocul, fara sa schimbe logica. + +**Cum se stie cand se poate scoate globala** — criteriu verificabil, nu „cand se convertesc toti”: + +- **Pentru `goComanda`**: nu se poate scoate niciodata complet din `ROAFACTURARE`, pentru ca al + doilea ei scriitor (`ocomenzi.vc2:2191-2207`, `AfterRowColChange`) nu are nicio legatura cu + `factureaza` — exista doar pentru `but_factura1.Visible`. Criteriul realist: **linia de citire in + `oDateFactura.Init` (`:301`) poate deveni neconditionata de fallback abia cand se confirma, prin + `git_sync.ps1` + Grep, ca niciun apel la `factureaza(3, ...)` mai lasa `toSursa` nepasat** — adica + se verifica apelantii lui `factureaza`, nu scriitorii globalei (care raman, pentru alt scop). +- **Pentru `goContract`**: acelasi lucru, dar cu o observatie in plus — global-ul insusi + (`Public goContract` in `roacontracte.prg:559-560`) **nu va disparea niciodata** cat timp exista + formularul de contracte din ROACONTRACTE, care il foloseste ca buffer de editare, nu doar ca + transport spre facturare. Criteriul de scos fallback-ul: **cand toate apelurile catre + `factureaza(2/6/52, ...)`, in toate produsele, pasesc `toSursa` explicit** — verificabil cu + aceeasi comanda `Select-String` ca in S2, aplicata pe `factureaza(2` / `factureaza(6` / + `factureaza(52` in loc de `factureaza2`. +- Pana atunci, fallback-ul ramane — el nu costa nimic in productie (o verificare `Type()` in plus), + si e singura plasa de siguranta pentru cele ~31 puncte de intrare care nu au fost, nu vor fi, si + nu trebuie sa fie convertite (nu au sursa de precompletat). + +## 6. Cele N copii ale fisierelor atinse — identice sau divergente? + +MD5 pe fiecare fisier, in cele 7 produse care au copie (`ROAFACTURARE`, `ROACONT`, `ROAGEST`, +`ROAAUTO`, `ROAACNPRO`, `ROAIMOB`, `ROACONTRACTE`): + +| Fisier | Rezultat | +|---|---| +| `COMUN\programe\ofacturare.prg` (2662 linii) | **Identic pe toate cele 7** — un singur MD5 | +| `COMUN\programe\oproceduri_facturare.prg` (2393 linii) | **Identic pe toate cele 7** — un singur MD5 | +| `COMUN\clase\ocomenzi.vc2` (8301 linii) | **Identic pe cele 6** care il au (nu verificat separat in ROACONTRACTE, care nu are comenzi) | +| `COMUN\programe\ofacturare_comun.prg` (2224 linii) | **Identic pe 6 din 7** — `ROAIMOB` diverge cu **exact o linie lipsa**: `:264`, `.cod_fiscal = ALLTRIM(NVL(goContract.cod_fiscal, ''))`, absenta din copia ROAIMOB (2223 linii). Restul fisierului, inclusiv blocul `goComanda`/`goContract` de la `:261-328`, e identic caracter cu caracter. | + +**Spre deosebire de `ofacturare.vc2` (7 copii, 4 variante MD5 distincte, citat ca precedent de +comparat)**, fisierele atinse de S3c sunt aproape perfect sincronizate — un singur punct de drift, +minor si izolat. Concluzie pentru propagare: modificarea din `ofacturare.prg` si +`oproceduri_facturare.prg` se poate copia **caracter cu caracter** in toate cele 7 produse fara nicio +adaptare. Modificarea din `ofacturare_comun.prg` se poate copia identic in 6 produse, dar **in +ROAIMOB trebuie aplicata pe baza divergentei existente** (linia `cod_fiscal` lipseste deja acolo — +o copiere oarba a diff-ului ar putea sa nu se aplice curat sau sa reintroduca acea linie fara sa fie +intentionat; de verificat manual la sincronizare, nu doar copiat). + +`ferestre_contracte.vc2` (ROACONTRACTE) e specific produsului, nu are copii de sincronizat. + +## 7. Suprafata de regresie masurata + +**Fisiere care chiar se modifica** (5, plus 1 verificare de sincronizare): + +| # | Fisier | Ce se schimba | Cate copii de propagat | +|---|---|---|---| +| 1 | `COMUN\programe\ofacturare.prg:81-82` | semnatura `factureaza`, +`toSursa` | 7 (identice, copiere directa) | +| 2 | `COMUN\programe\ofacturare_comun.prg:223-224, 261-328` | semnatura `Init`, +`toSursa`; ramurile de citire cu fallback | 7 (6 identice + ROAIMOB cu drift de reconciliat) | +| 3 | `COMUN\programe\oproceduri_facturare.prg:119-141` | `facturare_contracte(tcTip, toSursa)`, `facturare_comenzi(toSursa)`, relay catre `factureaza(N, NULL, toSursa)` | 7 (identice, copiere directa) | +| 4 | `COMUN\clase\ocomenzi.vc2:1580-1596` (`do_factura`) | `SCATTER NAME loComanda MEMO` (local, nu global) + `DO facturare_comenzi WITH loComanda IN oproceduri_facturare.prg` | 6 (identice, copiere directa) | +| 5 | `ROACONTRACTE\Clase\ferestre_contracte.vc2:1538-1549` (`but_factura.Click`) | trimite `goContract` (sau o copie locala scatter-uita din randul curent) explicit ca al doilea parametru la `DO facturare_contracte WITH lcTip, goContract IN ...` | 1 (specific ROACONTRACTE) | + +**Apelanti care NU se modifica** (confirmat, nu presupus): toate cele ~31 de puncte de intrare din +inventarul S2 (`Meniuri\politica.mn2`, `Meniuri\contracte.mn2`, `Meniuri\aviz_*.mn2`, +`ofundal_facturare.vc2:883-905`) cheama `factureaza(N)` cu un singur argument sau +`facturare_contracte`/`facturare_comenzi` fara sursa — al treilea parametru ramane implicit `NULL`, +fallback-ul pe global preia (pentru cele care oricum nu aveau sursa reala, fallback-ul da acelasi +rezultat ca azi: nimic de precompletat). **Inclusiv `ofundal_facturare.vc2:886-902` (Page2.Cw2/Cw3) +raman neschimbate** — `Cw3` isi pastreaza `goComanda = ''` (devine redundant, dar inofensiv, odata ce +`do_factura` trimite explicit; se poate curata separat, nu la aceasta poveste), `Cw2` ramane cum e. + +**Total: 5 fisiere reale de editat, in 4 continuturi distincte de modificare** (fisierele #1-#3 au +acelasi continut in toate copiile lor), propagate in pana la 7 produse — mult sub suprafata unei +modificari „de suita” tipice, pentru ca `ofacturare.prg`/`oproceduri_facturare.prg` sunt deja +perfect sincronizate (sectiunea 6). + +## 8. Ordinea de executie, ca suita sa nu fie stricata niciun moment + +Spre deosebire de S2 (unde `factureaza2` nu avea alt apelant decat el insusi, deci risc de stricare +aproape nul), aici **exista o fereastra in care semnaturile trebuie sa fie compatibile intre fisiere +diferite** (`ofacturare.prg` cheama `oDateFactura::Init`, `oproceduri_facturare.prg` cheama +`factureaza`) — ordinea trebuie sa garanteze ca niciun punct intermediar nu are un apelant cu +semnatura veche impotriva unui apelat cu semnatura noua incompatibila. Pentru ca **toti parametrii +noi sunt optionali, la coada**, riscul e de fapt mic — o semnatura veche care cheama o rutina noua +functioneaza (parametrul lipsa devine implicit), problema ar aparea doar invers (rutina veche +chemata cu un parametru in plus, care s-ar ignora silentios in VFP — tot fara eroare, dar fara +efect). Deci ordinea de mai jos e despre corectitudine, nu despre a evita crash-uri: + +1. **`oDateFactura.Init`** (`ofacturare_comun.prg`) — adauga `toSursa`, muta logica de citire pe + modelul din sectiunea 5. Se poate face si testa izolat: fara niciun apelant care sa paseze + `toSursa` inca, comportamentul ramane identic cu azi (fallback pe global, mereu). +2. **`factureaza`** (`ofacturare.prg`) — adauga `toSursa`, il paseaza la `Createobject("oDateFactura", + lnIdSet, tnTip, toSursa)`. Inca niciun apelant nu paseaza `toSursa` — comportament neschimbat. +3. **`oproceduri_facturare.prg`** — `facturare_contracte`/`facturare_comenzi` primesc `toSursa` si il + relaeaza. Inca niciun apelant real nu il paseaza — comportament neschimbat. +4. **Abia acum, apelantii reali**: `ocomenzi.vc2:do_factura` trimite `loComanda` explicit; + `ferestre_contracte.vc2:but_factura.Click` trimite `goContract` explicit (in ROACONTRACTE). Din + acest punct, comportamentul chiar se schimba (sursa vine din parametru, nu din citirea globalei), + dar rezultatul trebuie sa fie identic — parametrul poarta exact aceleasi campuri pe care globala + le avea. +5. **Sincronizare in celelalte 6 produse**: pasii 1-3 se copiaza caracter cu caracter (identice, + sectiunea 6), cu atentia speciala la ROAIMOB (drift de o linie). Pasul 4 pe partea de comenzi se + copiaza si el (fisierul e identic in toate). Nu exista pas 4 de propagat pe partea de contract in + afara de ROACONTRACTE (nu exista alt produs cu formular de contracte care sa scrie `goContract`). +6. **Un singur produs se poate testa complet integrat de la sine**: ROAFACTURARE (pasii 1-4, partea + de comanda). ROACONTRACTE cere o rulare separata (build propriu, sincronizat manual) pentru + pasul 5 pe partea de contract — acelasi tip de limitare semnalata deja in S2 pentru + `factureaza2`. + +## 9. Ce nu se poate testa headless + +- **Toate cele ~31+2 puncte de intrare sunt declansate din UI** (clic pe buton, `ON SELECTION BAR` + in meniu) — niciunul nu are un test headless existent, la fel ca in S2. +- **Testarea „parametrul castiga peste global” cere manipulare de sesiune**: singurul mod sa verifici + ca `toSursa` are prioritate e sa populezi manual `goComanda`/`goContract` cu o valoare **diferita** + de cea din parametru si sa confirmi ca documentul rezultat foloseste parametrul — asta cere fie + cod de test care seteaza global-ul inainte de apel (posibil headless, dar artificial fata de + fluxul real), fie o sesiune interactiva VFP cu comenzi tastate manual in fereastra de comenzi. +- **Efectul colateral al `AfterRowColChange` (`ocomenzi.vc2:2199`) nu se poate simula headless** — + harnessul `-A -T` nu declanseaza fiabil evenimente de grid (confirmat de precedentul din memorie + „Coloanele de grid nu se materializeaza headless”), deci nu se poate verifica automat ca navigarea + in grid tot suprascrie `goComanda` dupa modificare (comportament care ramane neschimbat, dar + trebuie confirmat vizual, nu presupus). +- **ROACONTRACTE nu poate fi testat din arborele de lucru ROAFACTURARE** — cere build si rulare + separata, cu propriul executabil si propria sincronizare a `ofacturare.prg`/`ofacturare_comun.prg` + /`oproceduri_facturare.prg` (nu sunt acelasi fisier fizic, sunt copii). +- **Rezultatul final** (facturare emisa cu antetul corect precompletat din comanda/contract) se + verifica doar prin continutul lui `crsfactura`/`VANZARI`/formularul de antet, vizual sau prin + interogare Oracle read-only dupa emitere — nu exista assert automat de facut. + +## 10. Riscuri si ce ramane de decis de Marius + +**Riscuri**: + +- **Cel mai probabil sa se strice**: garda `Type('loSursa') = 'O' Or Type('goContract') <> 'U'` din + sectiunea 5 — daca se scrie gresit (de exemplu `And` in loc de `Or`), fallback-ul pe global s-ar + rupe silentios pentru toti apelantii care inca nu au fost convertiti, fara nicio eroare vizibila + (`oDateFactura` ar porni pur si simplu fara precompletare). Se testeaza explicit pe cel putin un + apel neconvertit (orice `factureaza(N)` cu un singur argument, tip 1) dupa modificare. +- **Al doilea cel mai probabil**: parametrul nou nepasat trebuie verificat `Type('toSursa') = 'O'`, + nu `Type('toSursa') <> 'U'` — un parametru VFP nepasat nu e `'U'` (asta e pentru variabile + nedeclarate), ci `'L'` cu valoarea `.F.` implicita a lui `LPARAMETERS`. O garda gresita + (`<> 'U'`) ar trece mereu adevarat si ar incerca sa citeasca `.id_part` de pe `.F.`, eroare de + rulare imediata la primul apel neconvertit. **De verificat cu un test minimal inainte de a atinge + fisierele reale** — nu presupune, verifica comportamentul `Type()` pe un parametru nepasat intr-un + `.prg` de proba. +- **ROACONTRACTE, singurul apelant real din alt produs** — acelasi risc semnalat in S2: netestabil + din acest arbore de lucru, cere confirmare manuala separata. +- **Drift-ul de o linie din `ofacturare_comun.prg` in ROAIMOB** — o copiere mecanica a diff-ului ar + putea esua silentios sau reintroduce linia lipsa fara sa fie intentia — de aplicat manual acolo, + nu prin copy-paste orb. +- **`Cw3.do_actiune` (`ofundal_facturare.vc2:900`) ramane cu `goComanda = ''` redundant** dupa + conversia lui `do_factura` — nu e o problema (ramane inofensiv), dar merita mentionat explicit ca + „lasat asa, intentionat” in codul livrat, ca sa nu para o omisiune la revizuirea diff-ului. + +**Ce ramane de decis de Marius**: + +1. **Forma lui `toSursa`**: ramane duck-typing pe obiect scatter (ca azi), sau se formalizeaza o + clasa cu doua forme (`oSursaComanda`/`oSursaContract`)? Recomandare: ramane duck-typing — nu + schimba nimic functional, adauga doar o clasa noua de intretinut pentru un beneficiu marginal la + aceasta poveste. +2. **Se face si conversia partii de comanda si cea de contract in acelasi commit, sau separat?** + Ambele ating exact aceleasi trei fisiere comune (`ofacturare.prg`, `ofacturare_comun.prg`, + `oproceduri_facturare.prg`) — separarea nu reduce suprafata de regresie pe fisierele comune, doar + amana testarea reala pe ROACONTRACTE. Recomandare: **un singur commit**, cu testare manuala pe + ambele produse inainte de a-l considera gata. +3. **Se curata acum redundanta `goComanda = ''` de la `Cw3` (sectiunea „Riscuri”), sau se lasa pentru + o poveste ulterioara de curatenie?** Nu afecteaza corectitudinea — decizie de stil, nu de + comportament. +4. **Criteriul de „gata” rescris** (inlocuieste formularea din plan, linia 1861-1862): + - *Structural*: `Select-String -Path ofacturare.prg,ofacturare_comun.prg,oproceduri_facturare.prg + -Pattern 'toSursa'` gaseste parametrul in toate cele trei fisiere, in toate cele 7 produse care + au copii, cu semnatura identica. + - *Comportamental, cale convertita*: facturarea pornita din `ocomenzi.vc2:do_factura` cu + `goComanda` populat manual cu **alta** comanda decat cea trimisa prin `toSursa` produce + documentul corespunzator parametrului, nu globalei — testat manual, cu global-ul deliberat + „murdar” dintr-o navigare anterioara in grid. + - *Comportamental, cale neconvertita*: oricare din cele ~31 de apeluri care trimit doar `tnTip` + produce acelasi document ca inainte de modificare (fallback pe global, sau fara precompletare + acolo unde nu exista global de citit). + - *ROACONTRACTE*: facturarea din `but_factura.Click` cu contractul trimis explicit prin + `toSursa` produce acelasi rezultat ca azi (precompletare identica), verificat manual pe build + separat. + +--- + +## Ce nu s-a putut stabili si de ce + +- **Continutul exact al obiectului `loComanda`/`goContract` scaturat** nu a fost comparat camp cu + camp intre ce citeste azi `oDateFactura.Init` si ce ar contine un `SCATTER` facut in afara + contextului `crsComenzi`/`cContracte` curent — presupunerea (rezonabila, dar neverificata pe date) + e ca structura cursorului nu se schimba intre cele doua puncte de scatter. +- **Daca exista alte produse din suita (dincolo de cele 7 verificate) care au propriile copii ale + `ofacturare.prg`/`ofacturare_comun.prg`/`oproceduri_facturare.prg`** — cautarea s-a limitat la + produsele numite explicit in sarcina; alte ~30 de produse mentionate in S2 ca avand copii ale + `ofacturare.prg` (ROARETAIL etc.) nu au fost verificate pentru `goComanda`/`goContract` la aceasta + poveste. +- **Testarea reala pe ROACONTRACTE** nu a fost efectuata (read-only, fara rulare de cod) — doar + confirmata structura codului sursa. + +## Bug-uri semnalate, nereparate + +Niciunul nou — cercetarea a confirmat un defect de simetrie deja semnalat in plan (L.3), l-a +verificat pe cod pana la verdictul „inert azi in ROAFACTURARE” (sectiunea 2), si nu a gasit alte +probleme in fisierele atinse. diff --git a/docs/cercetare/s4_cautare_articole_server.md b/docs/cercetare/s4_cautare_articole_server.md new file mode 100644 index 0000000..55e8887 --- /dev/null +++ b/docs/cercetare/s4_cautare_articole_server.md @@ -0,0 +1,482 @@ +# Cercetare — proiectare S4: cautarea articolelor pe server, in linie + +Investigatie READ-ONLY pentru povestea **S4** din `docs\plan_13_unificare_formular_facturare.md:1865-1874`, +plus sectiunea `### C. Incarcarea articolelor — de ce dureaza si ce inlocuieste` (`:589-614`). +Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit. `COMUN\clase\ofacturare_comun.vc2` +si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar citite, nu atinse. Pe Oracle doar +`SELECT`, prin exportul `PACK_FACTURARE` de pe disc — nu s-a rulat nimic pe server. + +## Verdict (rezumat) + +Mecanismul de inlocuire propus in plan **exista deja, dar e un prototip la jumatate**: +`grd_factura.cCodMat.cboCodmat` / `cCboDenumire` (`ofacturare.vc2:16751-16797`, wiring la +`:19288-19334`) cauta pe server prin `combosql` si scriu `codmat`/`denumire`/`id_articol` in +`crsfactura` — dar **nu scriu nimic altceva**, pentru ca sursa lor (`vnom_articole`) nu are pret, +TVA, valuta, `id_pol`, `gestionabil`. Asta confirma exact ce zice planul la punctul C: partea Oracle +de facut e o **varianta filtrata pe articol a celor cinci cursoare de facturare**, nu o cautare noua. + +Descoperirea care schimba proiectarea fata de textul din plan: **`crsarticole` nu e doar sursa de +populare a gridului — e un registru al cantitatii ramase de facturat**, citit si scris de +`do_adauga_tot`, `do_sterge` si `do_scrie_factura` pentru toate tipurile cu document sursa (comanda, +aviz, contract-lista-de-preturi). Stergerea unei linii **reface** cantitatea in `crsarticole` +(`:14640-14669`), iar la scriere se face `Calculate Sum(cantitate) To lnCantitateRamasa` peste +`crsarticole` ca sa se decida daca se inchide automat comanda/avizul (`:14303-14311`, `:14334-14338`). +Asta inseamna ca **incarcarea in masa nu poate disparea pentru aceste tipuri**, indiferent de S4 — +nu doar pentru ca planul a decis sa pastreze "adauga tot", ci pentru ca bookkeeping-ul de cantitate +ramasa e cablat direct pe cursorul incarcat. S4 se aplica deci curat doar pe ramurile de **lista de +preturi** (`cursor_preturi`, plus jumatate din `cursor_contract` si `cursor_gestiune`) — vezi punctul 7. + +A doua descoperire: calea de scriere a pretului la nivel de linie (`adauga_articol_factura`, +verificata separat in S10, `docs\cercetare\s10_pret_rederivat.md`) **primeste deja pretul ca +parametru** in loc sa-l re-deriveze — exact precedentul pe care trebuie sa-l urmeze si varianta +filtrata: cauta pretul o singura data, la alegerea liniei, si il transmite mai departe neschimbat. + +## 1. Inventarul celor cinci cursoare Oracle + +Sursa: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (corpul +pachetului; liniile de mai jos sunt pe corp, `:2138+`, nu pe spec `:335+` — offset **+17** fata de +citatele vechi din plan, confirmat si in S10). + +### 1.1 `cursor_preturi` — lista de preturi, ramura principala (`:2138-2644`) + +``` +PROCEDURE cursor_preturi(V_DATA_CURS IN DATE, V_TIP IN NUMBER, V_ID_VALUTA IN NUMBER, + V_ID_GESTIUNE_INIT IN NUMBER, V_LUNA IN NUMBER, V_AN IN NUMBER, + V_ID_UTIL IN NUMBER, V_ID_SUCURSALA IN NUMBER, + V_CURSOR OUT cursor_facturare) +``` + +**Fara filtru pe articol** — semnatura n-are niciun parametru de cod/denumire. Ramuri pe `V_TIP` +(`CASE`, `:2159-2643`): + +| Ramura | Cand | Sursa randurilor | Observatie | +|---|---|---|---| +| `V_TIP = 45` | restaurant | `utilizatori_rol_intern` -> `politici_grupuri` -> `crm_politici_preturi` -> `crm_politici_pret_art` -> `nom_articole`, plus `curs`/`nom_valute` | fara filtru de stoc, `cantitate` fixa la 1 (`:2193`) | +| `V_TIP IN (1,2)` | factura in lei | acelasi lant de politici, plus `LEFT JOIN` pe `STOC` agregat pe gestiunile utilizatorului | `WHERE` final filtreaza pe stoc > 0 sau `RF_FACTURARE_FARA_STOC` (`:2387-2388`) | +| `V_TIP IN (5,6,10,52)` | factura in valuta | `FACT_VPRETURI_UTILIZATOR` (view precalculat, nu politici brute) + `STOC` + `CURS` | `WHERE A.ID_UTIL = V_ID_UTIL AND ((A.ID_VALUTA = V_ID_VALUTA AND A.IN_VALUTA=1) OR A.ID_POL = politica_stoc)` (`:2461-2462`) | +| `V_TIP = 7` | credit note | `FACT_VPRETURI_UTILIZATOR`, filtrat suplimentar `NVL(A.nota_discount,0)=1` (`:2538`) | doar articole de discount | +| `ELSE` (aviz, tip implicit) | orice alt tip | `FACT_VPRETURI_UTILIZATOR`, fara filtrul de valuta din ramura 5/6/10/52 | ramura cea mai generala | + +Coloane comune returnate (uniforme pe toate ramurile, ceea ce conteaza pentru mapare — punctul 6): +`id_c, id_articol, lot, serie, id_pol, id_valuta, nume_lista_preturi, discount_unitar, +discount_unitar_val, codmat, codbare, denumire, um, gestionabil, cantitate, proc_tvav, +preturi_cu_tva, curs, multiplicator, pret, pret_val, tip_valuta, nume_val` (+ `modificabil`, +`id_gestiune`, `cont` doar pe ramura restaurant). + +Apeleaza intai `initializeaza_facturare`, `completare_politica_stoc` si +**`verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)`** (`:2149-2153`) — validare **neconditionata** +a cursurilor pentru toate valutele din listele de preturi ale utilizatorului, indiferent de articolul +cautat. Relevant pentru S4d (`-20005`). + +### 1.2 `cursor_contract` — factura/aviz pe contract (`:2646-2950`) + +``` +PROCEDURE cursor_contract(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_LISTAID, V_ID_GESTIUNE_INIT, + V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA, + V_ID_AGENT OUT, V_NUME_AGENT OUT, + V_CURSOR OUT cursor_facturare, V_CURSOR2 OUT cursor_facturare) +``` + +**Doua cursoare de iesire.** `V_CURSOR2` (`:2718-2938`) e specific contractului: `UNION ALL` intre +(a) articole cu `OPT_FACTURARE = 3` din `CTR_ARTICOLE` si (b) rate din `CTR_SCADENTAR` pentru +`OPT_FACTURARE IN (1,2)`, cu `id_articol = NULL` pe randurile de rata — filtrate pe +`V_LISTAID` (lista de `id_ctr`) prin `charn2collection`. La final (`:2940-2948`) **cheama +`cursor_preturi` cu aceiasi parametri** si scrie rezultatul in `V_CURSOR` — deci jumatate din +`cursor_contract` e literalmente `cursor_preturi`. + +In VFP, cele doua cursoare de iesire ajung (observat pe cod, nu documentat explicit in `goExecutor`) +in `crsarticole` (=`V_CURSOR`, lista de preturi) si `crsarticole1` (=`V_CURSOR2`, liniile contract + +rate) — vezi `ofacturare.vc2:13778` (`lcCursorStoc = Iif(poArticol.opt_facturare = 0, [crsarticole], +[crsarticole1])`) si `Destroy` (`:12804-12811`) care inchide ambele. + +**Randurile de rata nu au `id_articol`** — nu pot fi gasite printr-o cautare cod/denumire; raman +legate de gridul `crsarticole1` existent (`grd_contracte`), bounded de numarul de rate ale +contractului, nu de catalog. Vezi punctul 7. + +Valideaza cursurile valutare **doar** pentru valutele prezente in `CTR_SCADENTAR`/`CTR_ARTICOLE` ale +contractelor din `V_LISTAID` (`:2679-2716`) — spre deosebire de `cursor_preturi`, care valideaza tot +ce are utilizatorul in politici. Filtru deja ingust pe sursa, dar tot pe tot contractul, nu pe +articol. + +### 1.3 `cursor_comanda` — factura/aviz pe comanda (`:2952-3171`) + +``` +PROCEDURE cursor_comanda(V_DATA_CURS, V_TIP, V_LISTAID, V_ID_UTIL, V_CURSOR OUT cursor_facturare) +``` + +`V_LISTAID` e **un singur `id_comanda`** (`TO_NUMBER(V_LISTAID)`, `:2963` — nu o lista, desi +parametrul se numeste la fel ca la contract/avize). Doua ramuri identice ca forma (`V_TIP <= 20` = +factura, altfel aviz, `:2994-3170`), ambele pe `COMENZI_ELEMENTE` filtrat `WHERE A.ID_COMANDA = +V_ID_COMANDA` (`:3078`, `:3166`) — **deja filtrat pe un singur document sursa**, nu pe tot catalogul. +`LEFT JOIN` cu suma cantitatilor deja facturate din `VANZARI_DETALII` pe acelasi `ID_COMANDA` +(`:3060-3068`) calculeaza `cantitate` ca **ramas de facturat**, nu cantitatea comandata bruta — +exact sursa pentru bookkeeping-ul de "adauga tot" / stergere de la punctul urmator. + +### 1.4 `cursor_avize` — factura din avize (`:3703-3874`) + +``` +PROCEDURE cursor_avize(V_LISTAID, V_ID_UTIL, V_DISCOUNT OUT NUMBER, V_CURSOR OUT cursor_facturare) +``` + +`V_LISTAID` = lista de `id_vanzare` (avize sursa), separate prin virgula. O singura interogare, +fara `CASE` pe tip — agrega `VANZARI_DETALII` pe cheie compusa (articol, pol, lot, serie, discount, +TVA, gestiune, cont, pret...) si scade ce a fost deja facturat din `VANZARI_CANTITATI` +(`WHERE A.CANTITATE <> NVL(B.CANTITATE, 0)`, `:3871`) — acelasi tipar "ramas de facturat" ca la +comanda. **Nu are ramuri pe `V_TIP`** — planul o citeaza ca exemplu de "aceleasi ramuri pe tip", dar +real e cea mai simpla dintre cele cinci: un singur `SELECT`. + +### 1.5 `cursor_gestiune` — transfer intre subunitati pe lista de preturi (`:4158-4310+`) + +``` +PROCEDURE cursor_gestiune(V_DATA_CURS, V_ID_POL, V_ID_GESTIUNE, V_LUNA, V_AN, + V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR OUT cursor_facturare) +``` + +Foloseste `V_ID_POL` **fix** (nu politica derivata din utilizator ca la `cursor_preturi`), pe +`STOC` filtrat `ID_GESTIUNE = V_ID_GESTIUNE` `JOIN` `CRM_POLITICI_PRET_ART WHERE ID_POL = V_ID_POL` +(`:4266-4280`) — deja restrans la o singura gestiune si o singura politica, dar tot fara filtru pe +articol; `GROUP BY` pe articol agrega stocul. + +## 2. De unde sunt executate; cine consuma `crsarticole` + +**Executia:** un singur loc, `ofacturare.prg:266-311` (`factureaza`), rutare pe `tnTip` (punct de +plecare confirmat, `Do Case` la `:266-308`) -> `goExecutor.oExecute(lcSqlCursor, [crsarticole])` +(`:310-311`). Acelasi tipar apare a doua oara in `factureaza2` (`ofacturare.prg:824+`, ramura +`frm_facturare_articole2`, prototipul). + +**Consumatorii `crsarticole` in perimetrul S4** (`frm_facturare_articole`, `ofacturare.vc2:11xxx-15300`; +lista completa via `vfp_symbols -Grep 'crsarticole\b' -CodeOnly`): + +| Metoda | Linii | Ce face cu `crsarticole` | Ramane dupa S4? | +|---|---|---|---| +| `Destroy` | `12804-12811` | inchide cursorul | da, neschimbat | +| `do_adauga_articol` | `12813-13167` | citeste **un rand ales** (`Scatter Name poArticol`), il scrie in `crsfactura` | **inlocuit** de randul intors de cautarea pe server, pe ramurile de lista de preturi — vezi punctul 7 | +| `do_adauga_tot` | `13169-13198` | `Scan` peste tot cursorul, cheama `do_adauga_articol` pe fiecare rand | **pastrat neschimbat**, dar numai pe tipurile cu document sursa | +| `do_cauta` | `13566-13593` | filtru client-side (`Set Filter`) peste cursorul deja incarcat, dupa text tastat in `txtArticole`/`txtCodmat` si politica aleasa | **dispare** pe ramurile de lista de preturi — inlocuit de `combosql` | +| `do_scrie_factura` | `14301-14338` | `Calculate Sum(cantitate) To lnCantitateRamasa` peste `crsarticole`, decide daca se inchide comanda/avizul sursa (`pnParametruAditional`) | **pastrat neschimbat** — vezi verdictul | +| `do_sterge` | `14608-14669` | la stergerea unei linii din `crsfactura`, **reface** cantitatea in `crsarticole`/`crsarticole1` (`Replace cantitate With cantitate + poArticol.cantitate`) | **pastrat neschimbat** pe tipurile cu bookkeeping; pe lista de preturi nu exista azi replace-back real (cantitatea nu scade la adaugare pe acele tipuri — stocul se verifica separat, prin `cursor_gestiuni_articol*`) | +| `do_modifica` | `13778` | alege `crsarticole` sau `crsarticole1` dupa `opt_facturare` pentru verificare stoc | pastrat, tine de gestiunea aleasa la editarea unei linii deja adaugate | +| `cb_politici_preturi.InteractiveChange` | `15647-15658` | cheama `do_cauta()` (filtru client-side) | **dispare** — pe varianta noua, schimbarea politicii devine parametru al cautarii pe server (`id_pol` in `WHERE`), nu filtru local | +| `KeyPress` (navigare grid) | `15463-15486` | navigare in cursor la taste sageata | **dispare** pe ramurile fara grid de sus | + +`frm_facturare_articole2` (prototipul, `:17xxx-18xxx`) are exact aceeasi structura pe +`do_adauga_articol` / `do_adauga_tot` / `do_scrie_factura` / `do_sterge` — nu difera in aceasta +privinta. + +**Concluzie pentru cat se poate scoate:** `do_cauta` si filtrul din `cb_politici_preturi` dispar +integral (inlocuite de cautarea pe server). `do_adauga_articol` isi schimba sursa randului (de la +`Scan` in cursorul local la randul ales de `combosql`), dar restul lui (verificare stoc, alegere +gestiune via `cursor_gestiuni_articol*`, scriere in `crsfactura`) **nu se schimba** — vezi punctul 6. +`do_adauga_tot`, `do_sterge` (partea de bookkeeping) si `do_scrie_factura` (`lnCantitateRamasa`) +**nu pot fi scoase** cat timp `crsarticole` ramane sursa lor de adevar pentru "cat a mai ramas" — +motiv suplimentar, nu doar UX, pentru care "adauga tot" ramane cablat pe incarcare in masa acolo +unde exista document sursa. + +## 3. Cat costa azi incarcarea, pe forma interogarii (nu pe timpi masurati — decizia 31) + +**`cursor_preturi` (ramurile 1/2 si generala) nu are niciun filtru pe articol** — semnatura n-are +parametru de cod/denumire (`:2138-2146`). Rezultatul e **intreg catalogul accesibil utilizatorului**: +`utilizatori_rol_intern` -> `politici_grupuri` -> `crm_politici_preturi` (toate politicile active la +data cursului) -> `crm_politici_pret_art` (toate liniile de pret ale acelor politici) -> `nom_articole`. +Numarul de randuri creste cu produsul dintre "cate politici de pret vede utilizatorul" si "cate +articole are fiecare politica" — nu cu numarul de articole cautate, care e de regula 1. + +Trei surse structurale de cost, vizibile direct in forma interogarii, nu masurate: + +1. **`LEFT JOIN` pe `STOC` agregat** (`:2359-2378`, ramura 1/2) — subquery cu `GROUP BY ID_ARTICOL` + peste tot stocul lunii curente, pe toate gestiunile la care utilizatorul are drept, recalculat la + fiecare deschidere de formular, indiferent daca utilizatorul cauta un singur articol. +2. **`FACT_VPRETURI_UTILIZATOR`** (ramurile 5/6/10/52 si 7 si aviz) e un view, nu un tabel — planul + nu detaliaza corpul lui aici (ar fi o cercetare separata), dar fiind sursa pentru "toate preturile + vizibile utilizatorului", are aceeasi forma: cost proportional cu marimea catalogului, nu cu + cautarea. +3. **`verifica_cursuri_valute`** (`:2153`, chemata necondiționat la fiecare apel) valideaza cursul + pentru **toate** valutele din politicile utilizatorului, nu doar valuta articolului cautat — + cost fix per deschidere, independent de ce se cauta. + +Concluzia structurala: interogarea de azi calculeaza raspunsul pentru "orice articol ar putea alege +utilizatorul", cand formularul are nevoie doar de "articolul pe care tocmai l-a tastat". Filtrarea pe +`id_articol`/`codmat`/`denumire` reduce fiecare din cele trei surse la un numar de randuri marginit +de cate politici de pret contin acel articol (de regula 1, rar cateva), nu de marimea catalogului. + +## 4. Proiectarea variantei filtrate, cursor cu cursor + +Toate cinci sunt proceduri PL/SQL in `PACK_FACTURARE`, apelate direct din VFP prin +`{call pack.proc(...)}` + `goExecutor.oExecute`, fara view intermediar si fara strat ORM — deci +minimul de schimbare e **acelasi tipar**: o procedura noua (sau o supraincarcare cu parametru +suplimentar) in acelasi pachet, apelata din acelasi loc (`ofacturare.vc2`, metoda nou-introdusa pe +`combosql`, nu din `ofacturare.prg`, care ramane neschimbat — el tot incarca varianta "in masa" +acolo unde ramane necesara). + +**Alegere de proiectare, nu fapt verificat:** supraincarcare (aceeasi denumire, parametru nou +opțional `V_FILTRU_COD IN VARCHAR2 DEFAULT NULL` / `V_FILTRU_DEN IN VARCHAR2 DEFAULT NULL`) e mai +sigura decat o procedura noua, pentru ca **garanteaza** aceleasi ramuri de `CASE`, acelasi `JOIN`, +aceeasi logica de rotunjire — orice divergenta viitoare intre "cursor complet" si "cursor filtrat" ar +fi un bug de sincronizare greu de prins. Cu supraincarcare, filtrul se adauga o singura data, in +`WHERE`-ul final al fiecarei ramuri, nu in logica de business. + +| Cursor | Ce se adauga | Unde (linia `WHERE`/`CASE` care primeste filtrul) | +|---|---|---| +| `cursor_preturi` | `AND (V_FILTRU_COD IS NULL OR C.CODMAT LIKE V_FILTRU_COD) AND (V_FILTRU_DEN IS NULL OR UPPER(C.DENUMIRE) LIKE UPPER(V_FILTRU_DEN))` — pe alias-ul articolului, `C` pe patru din cinci ramuri, `A` pe ramurile cu `FACT_VPRETURI_UTILIZATOR` | dupa `WHERE` existent, pe fiecare din cele 5 ramuri (`:2264`, `:2387-2388`, `:2464-2465`, `:2540-2541`, `:2640-2641`) — 5 locuri, nu unul | +| `cursor_contract` | acelasi filtru pe `V_CURSOR` (delegat catre `cursor_preturi`, gratuit); pe `V_CURSOR2` filtrul se adauga in `UNION ALL`-ul de la `:2752` (partea cu `id_articol`), **nu** pe partea de rate (`id_articol IS NULL` — nu se pot filtra pe cod, raman needitate de filtru) | `:2825` (join articol) + propagare in `WHERE`-ul de la `:2819` | +| `cursor_comanda` | filtru pe `C.CODMAT`/`C.DENUMIRE` in `WHERE A.ID_COMANDA = V_ID_COMANDA` (`:3078`, `:3166`) — **dar nu are sens sa se filtreze**: cf. punctul 7, comanda ramane pe calea "adauga tot" | de proiectat doar daca decizia de la punctul 7 se schimba | +| `cursor_avize` | idem — nu are sens, acelasi motiv | idem | +| `cursor_gestiune` | filtru pe `C.CODMAT`/`C.DENUMIRE` in interogarea de la `:4234-4237`, inainte de `GROUP BY` | `:4266-4293` | + +**Parametrii care raman identici** pe varianta filtrata: `V_DATA_CURS`, `V_TIP`, `V_ID_VALUTA`, +`V_ID_GESTIUNE_INIT`, `V_LUNA`, `V_AN`, `V_ID_UTIL`, `V_ID_SUCURSALA` — tot ce vine din `poDate` / +sesiune la deschiderea formularului, nu se recalculeaza per cautare. Doar filtrul de text e nou, plus +(pentru `cursor_preturi` in noua utilizare) eventual `V_ID_POL` daca utilizatorul a ales explicit o +lista de preturi in combo-ul `cb_politici_preturi` — azi acel combo doar filtreaza local +(`ofacturare.vc2:15647-15658`, comentat inlocuit cu `do_cauta()`); pe varianta noua devine parametru +real al interogarii. + +**Ce nu se poate verifica din cod, ramane de decis de Marius:** daca filtrul pe `codmat` foloseste +`LIKE` "incepe cu" (ca `combosql` de azi, `nCharCountBegin`/cautare incrementala) sau match exact la +alegerea din lista — combosql de azi face amandoua (tastare = "incepe cu" pe server, alegere din +lista = valoare exacta), deci varianta cea mai apropiata de comportamentul actual e sa pastreze +acelasi tipar: interogarea filtrata se cheama la fiecare tastare (ca azi, `RefreshData`), iar +valoarea finala aleasa vine din randul deja adus, nu dintr-o interogare separata "exact match". + +## 5. `combosql` — contract si exemple reale + +**Clasa:** `combosql AS combobox`, `COMUN\clase\_cb_base.vc2:519-843`. Nu e specifica facturarii — +traieste in biblioteca comuna a suitei, dar cautarea nu a gasit nicio alta utilizare, nici in +`ROAFACTURARE`, nici in `COMUNROA`/`ROAGEST` (`grep -rn "AS combosql WITH"` — zero potriviri in afara +`ofacturare.vc2:16751/16785/16861`, cele trei coloane ale gridului prototip). **Singurul exemplu real +de folosire e chiar prototipul din plan** — nu exista alt loc in suita de copiat. + +**Proprietati relevante** (`_memberdata`, `:560-575`): + +| Proprietate | Rol | +|---|---| +| `csourcesql` | `SELECT` fara `WHERE`/`ORDER BY` — baza interogarii | +| `csourcewhere` | conditia `WHERE` fixa (ex. `inactiv = 0`) | +| `csourceorder` | `ORDER BY`, implicit `pfieldactiv` | +| `pcursorname` | numele cursorului cu rezultatele | +| `pfieldactiv` | campul dupa care se cauta si se afiseaza | +| `psecondfield` | al doilea camp de cautare (optional) | +| `ncharcountbegin` | cate caractere minim inainte sa porneasca interogarea pe server | +| `llimittolist` | daca valoarea trebuie sa existe in lista | + +**Mecanismul** (`refreshdata`, `:796-830`): construieste +`SELECT ... FROM (cSourceSql) WHERE (cSourceWhere) AND (pFieldActiv LIKE ?pcValue [OR psecondfield +LIKE ?pcValue]) ORDER BY ...`, cu `?pcValue` = `cSearchString + '%'` (incepe cu) sau +`'%'+cSearchString+'%'` (contine, la Ctrl+Enter) si il executa prin +**`goExecutor.oExecuta`** (`selectdata`, `:832-840`) — acelasi executor folosit peste tot in suita +pentru apeluri Oracle, deci **niciun mecanism nou de transport**, doar un SQL nou de trimis. +`KeyPress` (`:610-776`) gestioneaza incremental tastarea: la fiecare caracter tastat re-executa +`RefreshData(1)` daca lungimea depaseste `nCharCountBegin`. + +**Contractul de legare la o coloana de grid**, dedus din prototip (`ofacturare.vc2:16751-16762` + +`:19288-19306`): + +1. se adauga ca `CurrentControl` al coloanei (`Column1.CurrentControl = "cCboDenumire"`, `:16621`); +2. `ControlSource` leaga afisarea de campul din cursorul de linii (`crsFactura.codmat`, `:16754`); +3. **evenimentul de reactie e `LostFocus`, nu `Valid` sau `InteractiveChange`** — abia la parasirea + celulei se scriu campurile in cursorul de linii, cu paza `If this.Value <> thisform.cOldValue` + (setat in `When`, `:19308-19310`) ca sa nu se rescrie fara motiv; +4. `LostFocus` de azi scrie **doar** campurile pe care le are `vnom_articole`: `codmat`, `denumire`, + `id_articol` (`:19293`) — nimic despre pret, TVA, valuta. **Aici se opreste prototipul azi**; tot + ce trebuie adaugat pentru S4 e sa inlocuiasca `crsCodmat`/`crsDenumire` (rezultatul din + `vnom_articole`) cu rezultatul cursorului Oracle filtrat de la punctul 4, care are toate coloanele, + si sa extinda `REPLACE`-ul de la `:19293`/`:19317` cu restul campurilor din tabelul de la punctul 6. + +**Ce nu ofera azi `combosql` si trebuie adaugat, nu doar copiat:** `csourcesql` e un `SELECT` +static definit in `.vcx` la design-time, nu poate primi parametrii dinamici ai documentului curent +(`poDate.zi_curs`, `poDate.tip`, `poDate.id_valuta`...) direct in proprietate. Pentru cursorul +Oracle cu 8 parametri, populate din `poDate`/sesiune, `csourcesql`/`csourcewhere` trebuie construite +dinamic in `Init` sau la schimbarea documentului (nu sunt string-uri fixe ca azi), sau — alternativa +mai simpla — `refreshdata`/`selectdata` se suprascriu punctual pe instanta din grid, ca sa apeleze +`{call pack_facturare.cursor_preturi_filtrat(...)}` in loc de `SELECT ... FROM (cSourceSql)`. A doua +varianta pastreaza restul mecanismului (`KeyPress`, incremental, `LostFocus`) neschimbat si e +schimbarea minima — de confirmat cu Marius, nu o certitudine de cod. + +## 6. Maparea camp-cu-camp + +Sursa cea mai fiabila pentru "ce completeaza azi `crsarticole` in linie" nu e cursorul Oracle brut, ci +`Gather Name poArticol Fields Like ...` din `do_adauga_articol` (`ofacturare.vc2:12945-12957`, +identic pe `frm_facturare_articole2` la `:17226+`) — acolo se vede exact ce trece din randul scanat +in `crsfactura`. + +| Camp in `crsfactura` | Vine din `crsarticole` (coloana cursorului Oracle) | Pe varianta filtrata | +|---|---|---| +| `id_articol`, `codmat`, `codbare`, `denumire`, `um` | direct din cursor | identic — filtrul e chiar pe aceste coloane | +| `pret_achizitie` | **nu** din `crsarticole` — vine din `poArtLista`/`crsartselectate` (rezultatul lui `cursor_gestiuni_articol*`, ales dupa `crsarticole`), nu din cursorul de lista de preturi | **neschimbat** — pasul e deja separat azi, ramane separat | +| `id_pol` | direct (`crsarticole.id_pol`) | identic | +| `Cont` | direct (`crsarticole.cont`, doar ramura restaurant o are explicit; pe restul vine `'371'` implicit sau din `cursor_gestiuni_articol*`) | identic pe ramurile care il au; **de verificat** pe ramurile 1/2/5/6/7/10/52/aviz — cursorul nu are `CONT` explicit in `SELECT` (`:2268-2329` etc.), deci provine din `do_alege_stoc`, nu din `crsarticole` | +| `id_gestiune` | direct doar pe ramura restaurant (`A.ID_GESTIUNE`, `:2220`); pe rest vine din alegerea de gestiune (`cursor_gestiuni_articol*`) | identic — pasul de alegere gestiune ramane neschimbat | +| `Proc_Tvav`, `pretftva`, `Pretctva`, `discountftva`(`discount_unitar`), `discountctva` | direct din cursor, plus `do_initializeaza_articol` (`:13618-13659`) care deriva `pretftva`/`pretctva`/`tva` reciproc dupa `preturi_cu_tva` | identic — logica de derivare e in VFP, nu se schimba | +| `id_valuta`, `tip_valuta`, `nume_val`, `Curs`, `multiplicator` | direct din cursor | identic | +| `gestionabil` | direct din cursor | identic (inclusiv regula de proforma, `ofacturare.prg:333-336`, care suprascrie `gestionabil=0` — ramane in `ofacturare.prg`, neschimbata) | +| `id_jtva_coloana` | direct doar pe ramurile care il au explicit in `SELECT` (aviz/contract/retur); pe `cursor_preturi` (1/2/5/6/7/10/52) **nu apare in lista de coloane** returnate (`:2264-2329`) | **de verificat cu Marius** — daca lipseste azi, `Gather ... id_jtva_coloana` scrie `NULL`/valoare implicita; filtrarea nu schimba nimic aici, dar merita clarificat inainte de implementare, nu presupus | +| `pretv_orig`, `pretd`, `id_valuta_d` | nu apar in `cursor_preturi`; vin din `poArtLista`/`crsartselectate` (alegerea de gestiune) | neschimbat | +| `taxcode`, `explicatie`, `id_lucrare_rez`, `id_part_rez`, `id_ctr`, `opt_facturare` | nu din `crsarticole` — din `poArticol`/context (`do_initializeaza_articol`, `do_alege_stoc`) | neschimbat | +| `pret_cu_tva` (flag `preturi_cu_tva`) | direct din cursor (`A.PRETURI_CU_TVA` / `D.PRETURI_CU_TVA`) | identic | + +**Concluzie pentru punctul 6 al cerintei:** nicio coloana din cele cerute explicit (pret, cota TVA, +valuta, `id_pol`, `gestionabil`, `pret_cu_tva`) nu ridica probleme — toate vin direct din cursorul +Oracle si filtrarea pe articol nu le atinge. Singurul semnal de atentie e `id_jtva_coloana`, absent +din `cursor_preturi` insusi (posibil completat in alt pas, neverificat aici — iese din perimetrul +"cele cinci cursoare", ar cere citirea intregului `do_adauga_articol`/`calculeaza_totaluri`). + +## 7. „Adauga tot" — ce ramane si de ce (nu doar decizia din plan) + +Planul spune "se pastreaza adauga tot pentru tipurile cu document sursa (comanda, aviz, contract) — +acolo setul e marginit". Cercetarea (punctul 2) arata un motiv suplimentar, mai tare decat marimea +setului: **bookkeeping-ul de cantitate ramasa** (`do_sterge`, `do_scrie_factura`) citeste si scrie +direct in `crsarticole`, deci acel cursor **trebuie sa existe incarcat in memorie** cat timp +formularul e deschis, indiferent cum alege utilizatorul liniile. + +**Vizibilitatea de azi a butonului `but_urmator_tot1`** ("adauga tot"), confirmata pe cod +(`ofacturare.vc2:15108-15248`, `Do Case poDate.tip`): + +| Tip | Vizibil "adauga tot" azi | Sursa cursorului | +|---|---|---| +| proforma (`eProforma=1`) | da (`:15113`) | `cursor_preturi` sau alt cursor dupa tip, marcat neges­tionabil | +| copiere (`lCopiere`) | da (`:15120`) | `cursor_preturi` (varianta copiere, `ofacturare.prg:464-473`) | +| `1, 5, 7, 10` — lista de preturi | **nu** | `cursor_preturi` | +| `2, 6` — contract | **nu** | `cursor_contract` | +| `3` — comanda | da (`:15150`) | `cursor_comanda` | +| `4` — avize | da (`:15166`) | `cursor_avize` | +| `21, 28, 42, 47` — aviz din comanda | da (`:15177`) | `cursor_comanda` | +| `22, 29` — aviz din lista de preturi | **nu** | `cursor_preturi` | +| `23, 41` — transfer subunitati | **nu** menționat explicit vizibil | `cursor_gestiune` | +| `25` — transfer din comanda | da (`:15215`) | `cursor_comanda` | +| `26` — aviz din contract | **nu** | `cursor_contract` | +| `8, 9` — retur | da (`:15240`) | `cursor_retur` (nu e in cele 5, iese din perimetru) | +| `24` — aviz retur | da (`:15245`) | `cursor_retur` | + +**Coincide exact** cu impartirea utila pentru S4: butonul e vizibil azi pe tipurile unde +`do_adauga_tot` are sens pentru ca setul e mic **si** bookkeeping-ul de ramas il cere +(comanda/aviz-din-comanda/retur), si e ascuns pe tipurile de lista de preturi (1/5/7/10, 22/29) si pe +contract-lista-de-preturi (2/6/26) — exact ramurile pe care cautarea filtrata inlocuieste incarcarea +in masa. **Contractul insa nu are azi "adauga tot" vizibil deloc** (nici pe partea de rate, nici pe +partea de articole) — planul semnaleaza asta separat, la S4b, ca gol de acoperit (tipurile 2, 6, 26, +52 lipsesc din conditiile de vizibilitate), nu ca ceva de pastrat. + +**Concret, ce se schimba per tip:** +- **Lista de preturi (1,5,7,10,22,29) si transfer pe lista (23,41,45,48,49):** `crsarticole` nu se + mai incarca la deschidere; `combosql` cauta filtrat; "adauga tot" ramane ascuns (ca azi — n-are + sens pe catalog intreg). +- **Comanda (3,21,25,28,42,47) si avize (4):** `crsarticole` **ramane incarcat in masa**, neschimbat; + `combosql` **nu se activeaza** pe aceste tipuri (sau, daca se activeaza pentru UX, cauta *in* + cursorul deja incarcat, nu pe server — cautare locala, nu Oracle) pentru ca bookkeeping-ul de + cantitate ramasa il cere oricum incarcat. +- **Contract (2,6,26,52):** dublu — `crsarticole` (jumatatea `cursor_preturi`) trece pe cautare + filtrata ca orice lista de preturi; `crsarticole1` (rate + articole `OPT_FACTURARE=3`) **ramane + incarcat in masa**, bounded de contract, neschimbat de S4 (randurile de rata n-au `id_articol`, + nu pot fi cautate). +- **Retur (8,9,24):** in afara celor cinci cursoare cerute (`cursor_retur`/`cursor_retur_document`), + proiectat separat la S4f — nesemnalat aici ca problema, doar exclus din perimetru. + +## 8. Interactiunea cu S4b si S10 + +**Cu S4b (bara de butoane / meniul de adaugare):** S4b proiecteaza meniul `xmenu()` "Adauga +articole" cu optiunile pe sursa (tot / alege), inclusiv acoperirea golului de pe contract (tipurile +2/6/26/52). S4 ii da continutul concret pentru ramura "cauta in linie": pe tipurile de lista de +preturi (unde S4b nu are nevoie de optiunea "tot" azi), linia de grid cu `combosql` **este** metoda +de adaugare — nu exista un dialog separat de ales. Pe tipurile cu document sursa, S4 nu schimba +nimic din ce proiecteaza S4b: meniul ramane cablat pe `do_adauga_tot`/alegere din `crsarticole` +existent. Punctul de atingere real: daca S4b decide sa afiseze si pe contract un "adauga tot" (gol +semnalat la punctul 7), acel "tot" trebuie sa opereze pe `crsarticole1` (rate+articole), nu pe +`crsarticole` (lista de preturi) — cele doua cursoare raman distincte si dupa unificare. + +**Cu S10 (pretul care nu trebuie re-derivat, `docs\cercetare\s10_pret_rederivat.md`):** verificarea +S10 arata ca `adauga_articol_factura` **primeste pretul ca parametru** (`V_PRET_ACHIZITIE_TEMP` si +restul) si nu-l recalculeaza, cu o singura exceptie tacuta: liniile de contract cu +`OPT_FACTURARE = 3`, unde `CTR_ARTICOLE.PRET_UNITAR` **suprascrie** pretul trimis, necondiționat de +sursa. Pentru S4, asta inseamna doua lucruri concrete: +1. cursorul filtrat (`cursor_preturi`/`cursor_gestiune` cu filtru pe articol) se cheama **o singura + data**, la selectarea liniei in `combosql` (`LostFocus`) — pretul obtinut atunci se scrie in + `crsfactura` si **nu se re-interogheaza** la scriere, exact ca azi pe calea `crsarticole` completa; +2. pe **contract**, indiferent daca linia a fost aleasa prin `crsarticole1` (needitat de S4) sau, + ipotetic, printr-o cautare filtrata viitoare, riscul de suprascriere tacuta descris in S10 exista + deja si S4 nu-l introduce si nu-l rezolva — il mosteneste neschimbat, pentru ca punctul de + suprascriere e in `adauga_articol_factura` (scriere), nu in cursorul de cautare (citire). + +## 9. Ordinea de executie in pasi + +1. **Pas 1 — Oracle, `cursor_preturi` filtrat.** Supraincarcare cu `V_FILTRU_COD`/`V_FILTRU_DEN` + opționale, aceleasi 5 ramuri, filtru adaugat in `WHERE`-ul fiecareia (punctul 4). *Gata cand:* + apelat cu filtru gol, cursorul e identic randuri-cu-randuri (aceleasi coloane, aceleasi valori, pe + fiecare din cele 5 ramuri de `V_TIP`) cu varianta actuala pe acelasi set de parametri — comparatie + directa pe SQL, nu pe timp. +2. **Pas 2 — Oracle, `cursor_gestiune` filtrat.** Acelasi tipar, un singur `SELECT`, mai simplu. + *Gata cand:* idem, comparatie randuri-cu-randuri cu filtru gol. +3. **Pas 3 — Oracle, `cursor_contract`, doar partea `V_CURSOR`.** Filtrul se propaga gratuit prin + apelul catre `cursor_preturi` de la `:2940-2948`; nicio schimbare suplimentara necesara daca Pasul + 1 e facut corect. *Gata cand:* `cursor_contract` cu filtru gol produce acelasi `V_CURSOR` ca + inainte de Pasul 1 (regresie, nu functie noua). +4. **Pas 4 — VFP, `combosql` pe `cCodMat`/`cCboDenumire`.** Inlocuieste `csourcesql`/`SelectData` cu + apelul la cursorul filtrat (punctul 5); extinde `LostFocus` (`:19288-19330`) sa scrie toate + campurile din tabelul de la punctul 6, nu doar `codmat`/`denumire`/`id_articol`. *Gata cand:* + alegerea unei linii in grid, pe un document de tip lista-de-preturi, produce in `crsfactura` + aceleasi valori ca alegerea aceluiasi articol azi din `crsarticole` incarcat complet — comparatie + pe date, punctul 10. +5. **Pas 5 — VFP, oprirea incarcarii in masa pe tipurile vizate.** In `ofacturare.prg:266-311`, pentru + `tnTip` din ramurile de lista de preturi (1,5,7,10,22,29,23,41,45,48,49) si jumatatea `cursor_preturi` + a contractului, `goExecutor.oExecute` nu se mai cheama la deschidere — grid-ul porneste gol, + `combosql` populeaza la cerere. *Gata cand:* deschiderea formularului pe aceste tipuri nu executa + niciun apel `cursor_preturi`/`cursor_gestiune`/`cursor_contract` (verificabil in log-ul + `goExecutor`/`WAIT WINDOW ... NOWAIT` din `combosql.selectdata`, care ramane singurul loc care + cheama Oracle pentru articole). +6. **Pas 6 — verificare "adauga tot" neatins.** Pe comanda/aviz/contract-rate, `do_adauga_tot`, + `do_sterge`, `do_scrie_factura` raman neschimbate (Pasii 1-5 nu le ating codul). *Gata cand:* + regresie manuala pe un document din comanda si unul din aviz — comportament identic cu azi. + +*Depinde de:* S3 (planul o marcheaza asa; portarea antetului trebuie sa existe inainte, ca sa aiba +`poDate` populat corect la deschiderea gridului). + +## 10. Cum se verifica „aceleasi valori ca azi" + +**Testabil pe date (nu doar pe cod), propunere concreta:** pentru fiecare tip reprezentativ (1 sau 5 +pentru lista de preturi, 2 pentru contract, 41 pentru transfer-gestiune), se alege un articol prezent +in cursorul complet de azi si se compara randul din `crsarticole` (deschidere veche, neschimbata) cu +randul intors de cursorul filtrat pe acelasi articol, aceiasi parametri (`zi_curs`, `id_valuta`, +`id_gestiune_init`, utilizator) — comparatie coloana cu coloana, in Oracle direct (doua `SELECT`-uri, +`MINUS` intre ele pe coloanele comune) sau in VFP prin doua cursoare deschise simultan. Se repeta pe +un articol cu politica de discount, unul in valuta si unul fara stoc (`RF_FACTURARE_FARA_STOC`), ca +sa acopere ramurile de `CASE` din punctul 1. + +**Ce nu se poate testa headless — capcana cunoscuta** (`grid-coloane-nu-se-materializeaza-headless`, +memorie de proiect): sub `-A -T`, `ColumnCount`/`RecordSource` ale gridului sunt artefacte, nu +reflecta ce vede operatorul. Deci **partea VFP** (alegerea din `combosql`, `LostFocus`-ul care scrie +in `crsfactura`) nu se poate verifica prin harnessul headless obisnuit — cere fie harnessul UI +vizibil existent, fie verificare directa pe cursorul `crsfactura` dupa un apel programatic la metoda +`LostFocus` (posibil, pentru ca metoda insasi nu depinde de randare, doar de `This.Value` — de +confirmat la implementare). Partea Oracle (comparatia de cursoare de mai sus) **e** testabila +headless, prin `SELECT`, fara nicio dependenta de VFP. + +## 11. Riscuri si ce ramane de decis de Marius + +- **`id_jtva_coloana` lipsa din `cursor_preturi`** (punctul 6) — de clarificat inainte de + implementare daca e completat in alt pas (nevazut aici) sau ramane `NULL` si azi; filtrarea nu + schimba comportamentul, dar nota trebuie inchisa ca sa nu para un bug nou introdus de S4. +- **Mecanismul de legare `combosql` -> cursor Oracle parametrizat dinamic** (punctul 5, ultimul + paragraf) e o alegere de implementare, nu un fapt de cod — `csourcesql` static din `.vcx` nu poate + primi parametrii de sesiune direct; solutia propusa (suprascriere punctuala `refreshdata`) e + rezonabila, dar nu e singura posibila si nu exista alt exemplu in suita de comparat. +- **Contractul ramane cu doua surse de date pe acelasi grid conceptual** (`crsarticole` filtrat + + `crsarticole1` needitat) — de confirmat ca UX-ul (doua zone: cautare + grid rate) e acceptabil, nu + doar tehnic corect. +- **`cursor_gestiune` (transfer subunitati) nu are azi buton "adauga tot" vizibil** (tabelul din + punctul 7 nu-l gaseste in `Do Case`) — de verificat separat daca tipurile 23/41 au alt mecanism de + adaugare in masa, neacoperit de cautarea in `crsarticole`/`crsarticole1` directa; nu s-a citit codul + suplimentar necesar pentru raspuns cert aici. +- **Validarea de curs valutar (`verifica_cursuri_valute`, punctul 1.1) ramane neconditionata** chiar + si pe varianta filtrata, daca se pastreaza in procedura supraincarcata — poate produce `-20005` la + prima cautare a unui articol, inainte ca utilizatorul sa fi vazut vreun rand, pe o valuta pe care + n-o foloseste azi doar pentru ca incarcarea in masa "o gasea" oricum. De discutat cu Marius daca + validarea trebuie restransa la valuta articolului cautat sau ramane globala (comportament + identic cu azi, doar declansat mai des — la fiecare cautare, nu o data la deschidere). +- **Numarul exact de apeluri Oracle pe sesiune de cautare** (un apel per tastare, cu debounce prin + `nCharCountBegin`) nu a fost masurat si nu trebuie masurat aici (decizia 31) — dar merita setat + explicit `nCharCountBegin` >= 2-3 la implementare, ca sa nu se piarda avantajul filtrarii intr-un + numar mare de interogari de o litera. + +## Handoff + +Cercetare incheiata in aceasta sesiune, fara nevoie de predare — toate cele 11 puncte cerute sunt +acoperite mai sus, cu citate `fisier:linie` verificate pe fisierul real (nu `.bak`). Niciun fisier de +cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle. diff --git a/docs/cercetare/s4_punct2_registru_cantitate_ramasa.md b/docs/cercetare/s4_punct2_registru_cantitate_ramasa.md new file mode 100644 index 0000000..18ef086 --- /dev/null +++ b/docs/cercetare/s4_punct2_registru_cantitate_ramasa.md @@ -0,0 +1,615 @@ +# 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: tip `4`). Cursoarele Oracle (`cursor_comanda`, `cursor_avize`) o + **calculeaza deja live** la deschidere ca `cantitate_document - deja_facturat`. `do_scrie_factura` + o re-suma din `crsarticole` (`Calculate Sum`) **doar ca sa decida ce sa trimita mai departe** + (`pnParametruAditional`) — dar Oracle **recalculeaza acelasi lucru independent, din tabele reale**, + in `inchide_comanda()` si `marcheaza_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; + transfer `23,41`; retur `8,9,24`). Decrementat/incrementat in `do_adauga_articol`/`do_modifica`/ + `do_sterge` ca sa nu lase operatorul sa adauge mai mult decat vede pe ecran, in aceeasi sesiune. + **Nu alimenteaza nicio decizie Oracle** — nu apare in niciun `Calculate Sum` in 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 tipurile `23,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 pe `23,41` fara 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 din + `do_scrie_factura` (dupa `do_scrie_articole()`, cand `VANZARI_DETALII_TEMP` e deja populat), care + reface exact interogarea pe care `inchide_comanda`/`marcheaza_facturat` o repeta oricum. Pentru + Rolul B, plafonul devine un apel Oracle la cerere (aceeasi interogare care alimenteaza azi + `cursor_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-3171` per raportul punctului 1; verificat aici direct la sectiunea + "aviz"/comanda a interogarii, `:3060-3081`): coloana `cantitate` e `A.CANTITATE - NVL(D.CANTITATE, 0)` + unde `D` e `SUM(cantitate)` deja facturat din `VANZARI_DETALII` pe acelasi `id_comanda` + (`:3060-3068`), filtrat `WHERE ... 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`): coloana `A.CANTITATE - NVL(D.CANTITATE, 0) AS CANTITATE` + (analog la `:3118`), plus agregarea finala (`:3820-3872`) care scade `VANZARI_CANTITATI` deja + consumat din `VANZARI_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,41` transfer, `8,9,24` retur)**: `cantitate` + vine din `cursor_preturi`/`cursor_gestiune`, care e **stoc disponibil** (`LEFT JOIN` pe `STOC`, + 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): + +```sql +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`) + +```sql +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 vazut `lnCantitateRamasa = 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 caror + `ramas = 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: + +```sql +-- 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 + +1. **Pas 1 — Oracle: extrage functiile de recalcul remainder** (`cantitate_ramasa_comanda`, + `exista_ramas_avize`) din logica deja scrisa in `inchide_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 care `Calculate Sum(cantitate)` din VFP o + calculeaza azi peste `crsarticole`, pe acelasi document, in aceeasi stare (comparatie directa, + inainte de orice alta schimbare). +2. **Pas 2 — VFP: `do_scrie_factura` cheama functiile noi in loc de `Calculate 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. +3. **Pas 3 — Oracle: functie de plafon Rol B filtrata pe articol**, reutilizand `WHERE`-ul din + `cursor_preturi`/`cursor_gestiune` (deja proiectat la S4 punctul 1, sectiunea 4), minus ce e deja in + `crsfactura` pentru acelasi articol. *Gata cand:* pe un articol gestionabil cunoscut, valoarea + returnata coincide cu `crsarticole.cantitate` de azi, la aceeasi stare a sesiunii (inainte de orice + adaugare). +4. **Pas 4 — VFP: `do_adauga_articol`/`do_sterge`/`do_modifica` folosesc plafonul cerut pe server** + pentru grupul Rol B, in loc de `Replace cantitate` pe `crsarticole` (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. +5. **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 lui `crsarticole`. *Gata cand:* deschiderea formularului pe tip + `41` (si `23` pe prototip) nu mai executa `cursor_gestiune`/`cursor_preturi` la deschidere, dar + adaugarea unui articol tot respecta plafonul de stoc (verificat manual). +6. **Pas 6 — curatare: `crsarticole` ramane incarcat doar unde e nevoie de enumerare** (comanda, aviz, + contract-rate — pentru "adauga tot" existent), fara bookkeeping legat de el. *Gata cand:* grep pe + `ofacturare.vc2` pentru `Replace cantitate.*crsarticole` (in afara populare) nu mai gaseste + potriviri in `do_adauga_articol`/`do_sterge`/`do_modifica`. +7. **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`): + +```sql +-- 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`): + +```sql +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`): +1. reseteaza datele de test la aceeasi stare (aceeasi comanda/aviz, aceleasi cantitati ramase); +2. factureaza partial pe fluxul **vechi** (cod actual), noteaza rezultatul interogarilor de mai sus; +3. reseteaza din nou la aceeasi stare initiala; +4. factureaza identic (aceleasi linii, aceleasi cantitati) pe fluxul **nou** (dupa Pasii 1-2 din + sectiunea 7), noteaza acelasi rezultat; +5. 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 ca `23,41` n-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 pe `23,41` asteapta finalizarea punctului 2** (asa cum recomand la Pasul 5, sectiunea 7). +- **Asimetria `do_modifica` fata de `do_adauga_articol`/`do_sterge`** (sectiunea 1.3, grupul + `1,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 `crsarticole` local 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 grupul `2,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 a `JOIN`-urilor din `inchide_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). diff --git a/docs/cercetare/s4_puncte_deschise.md b/docs/cercetare/s4_puncte_deschise.md new file mode 100644 index 0000000..7faacd5 --- /dev/null +++ b/docs/cercetare/s4_puncte_deschise.md @@ -0,0 +1,246 @@ +# Cercetare — doua puncte deschise inainte de implementarea S4 + +Investigatie READ-ONLY, doua intrebari inchise din `docs\plan_13_unificare_formular_facturare.md`, +`#### S4`, sectiunea "De inchis inainte de implementare" (`:2092-2095`). Fara editari de cod, fara +`git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara scriere pe Oracle (numai `SELECT`). + +Status: **GATA**. Ambele intrebari au raspuns confirmat pe cod, cu `fisier:linie`. Amandoua +raspunsurile de baza confirma "nu schimba nimic", dar fiecare are o rezerva concreta, nu triviala, +de pus in proiectarea S4 (nu doar de bifat). + +Sursa Oracle: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` +(17217 linii — liniile citate mai jos sunt verificate direct pe acest fisier, corpul pachetului). +Sursa VFP: `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare.prg` (perimetrul `frm_facturare_articole` +/ `factureaza`; `frm_facturare_articole2` / `factureaza2` citite doar pentru comparatie, nu atinse). + +--- + +## Intrebarea 1 — `id_jtva_coloana` lipseste din `cursor_preturi`? + +### 1.1 Ce e si cine o consuma + +`id_jtva_coloana` identifica randul din `JTVA_COLOANE` (explicatia/coloana de TVA folosita la +raportare si SAFT) asociat unei linii de factura. E scris pe fiecare linie in `VANZARI_DETALII_TEMP` +(Oracle, prin `adauga_articol_factura`) si citit inapoi de UI-ul VFP pentru dialogul de "explicatie +TVA" (`Cb_explicatie_Tva`, `frm_articol_factura`). + +### 1.2 Chiar lipseste din `cursor_preturi`? — confirmat pe SQL + +Toate cele **cinci ramuri** ale `cursor_preturi` (`WHEN V_TIP = 45`, `WHEN V_TIP IN (1,2)`, +`WHEN V_TIP IN (5,6,10,52)`, `WHEN V_TIP = 7`, `ELSE` aviz — corpul la +`ff_...PACK_FACTURARE.sql:2138-2644`) au liste de coloane complete verificate direct — **niciuna nu +returneaza `id_jtva_coloana`** (`:2163-2221`, `:2268-2329`, `:2394-2427`, `:2470-2504`, `:2545-2603`). +`cursor_facturare` e un `REF CURSOR` slab tipat (`:28`), deci lista de coloane a fiecarei ramuri e +literal ce se vede in `SELECT`, fara camp implicit. + +Acelasi lucru e adevarat si pentru **`cursor_gestiune`** (`:4158-4347`, coloanele la `:4207-4343`) — +relevant pentru intrebarea 2, tipul 41. `cursor_contract` (`:2646-2950`) delega `V_CURSOR` la +`cursor_preturi` (deci mosteneste lipsa), iar `V_CURSOR2` (rate + articole `OPT_FACTURARE=3`, +`:2718-2938`) **de asemenea nu are `id_jtva_coloana`** in niciuna din cele doua ramuri `UNION ALL`. + +Prin contrast, **`cursor_avize`** (`:3703-3874`) **are** `A.ID_JTVA_COLOANA` explicit (`:3748`, +plus `GROUP BY`-urile de la `:3781`, `:3804`, `:3822`, `:3843`) si `cursor_comanda` (`:2952-3171`) +**nu are**, pe niciuna din cele doua ramuri (factura/aviz, `:2994-3170`). Deci impartirea reala e: +*are* — doar `cursor_avize`; *nu are* — `cursor_preturi`, `cursor_gestiune`, `cursor_contract` +(ambele cursoare), `cursor_comanda`. + +### 1.3 E completat in alt pas, sau ramane gol? — DA, e completat, printr-un mecanism in doi pasi + +Pasul 1 — **valoare implicita, nu `NULL`**. `do_initializeaza_articol` +(`ofacturare.vc2:13681-13683`, identic la `:17896-17897` pe prototip): +``` +If Type('toArticol.id_jtva_coloana') = 'U' + AddProperty(toArticol,'id_jtva_coloana',0) +Endif +``` +Daca `Scatter Name poArticol` dintr-un cursor fara coloana `id_jtva_coloana` produce un obiect fara +acea proprietate (`Type(...) = 'U'`), se adauga cu valoarea **`0`** (numeric, nu `NULL`). + +Pasul 2 — **derivare reala din `proc_tvav`, neconditionata**. `frm_articol_factura.Init` +(`ofacturare.vc2:2344-2371`) suprascrie *intotdeauna* `id_jtva_coloana`, indiferent de ce a venit din +cursor: +``` +Select jtva_coloane +If !Isnull(poArticol.proc_tvav) + Set Filter To cota_tva = poArticol.proc_tvav * 100 - 100 + ... + poArticol.id_jtva_coloana = id_jtva_coloana +Else + ... + poArticol.proc_tvav = (cota_tva + 100)/100 + poArticol.id_jtva_coloana = id_jtva_coloana +EndIf +``` +`proc_tvav` **este** returnat de toate cele cinci ramuri ale `cursor_preturi` (coloana comuna, +confirmata la 1.2), deci lookup-ul are mereu ce derivea, indiferent daca `id_jtva_coloana` a lipsit +din cursorul sursa. + +Acest `Init` ruleaza pentru **orice** linie adaugata prin `do_adauga_articol` +(`ofacturare.vc2:12813-13167`), pe ambele ramuri ale `Do Case` de la `:12870-12896`: +- articol negestionabil / `gnScadereStoc=0` / restaurant (`tip=45`) → + `Createobject("frm_articol_factura", ...)` direct (`:12873`); +- articol gestionabil (`Otherwise`, `:12881-12895`) → `do_alege_stoc` → + `Createobject("frm_articol_gest_factura", ...)` (`:13353`/`:13361`/`:13375`) — clasa care + **mosteneste** `frm_articol_factura` (`vfp_symbols -Class`: `frm_articol_gest_factura -> + frm_articol_factura -> _frmbase -> _form`) si al carei `Init` (`:3986-3989`) cheama + `DoDefault(tnCantitate,tlAscunde)` — deci ruleaza acelasi `Init` de la 2315-2371, cu aceeasi + derivare. + +**Confirmare ca derivarea chiar functioneaza si nu doar exista in cod:** `do_alege_stoc` +(`:13330-13340`) avea, intr-o versiune anterioara (comentata, `v 2.2.18`), un scurtcircuit care +seta direct `id_jtva_coloana` fara sa mai arate formularul cand exista o singura optiune de TVA; +azi acel scurtcircuit e dezactivat si se creeaza intotdeauna obiectul `frm_articol_gest_factura` +(deci `Init` ruleaza mereu, nu doar in cazuri rare). + +Safety-net-ul `Case Empty(poArticol.id_jtva_coloana)` din `inainte_de_do_termin` +(`:2205-2208`, mostenit si de `frm_articol_gest_factura`) e o verificare reziduala pentru cazul in +care nici `proc_tvav` n-are match in `jtva_coloane` — nu dovada ca `id_jtva_coloana` ramane gol azi +pe fluxul normal. + +### 1.4 Concluzia care conteaza — cu o rezerva reala, nu ipotetica + +**Pe fluxul de azi (`do_adauga_articol`), lipsa lui `id_jtva_coloana` din `cursor_preturi`/ +`cursor_gestiune` NU e un bug** — e completat corect, de doua ori (default 0, apoi derivat real din +`proc_tvav`), pe ambele ramuri (gestionabil/negestionabil), inainte ca linia sa ajunga in +`crsfactura` (`Gather ... id_jtva_coloana ...`, `:12945-12950`/`:12992-12995`) si, de acolo, in +Oracle (`do_scrie_articole` → `adauga_articol_factura(...)`, `Alltrim(Str(poArt.id_jtva_coloana))`, +`:14085`). + +**Rezerva: raspunsul "filtrarea nu schimba nimic" e adevarat DOAR daca implementarea S4 pastreaza +trecerea prin `do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa.** Cercetarea +anterioara (`docs\cercetare\s4_cautare_articole_server.md`, sectiunea 5) arata ca **singurul exemplu +real existent de cautare-pe-server** (`combosql`, `grd_factura.cCodMat.cboCodmat.LostFocus`, +`ofacturare.vc2:19288-19306`) **nu trece prin `do_adauga_articol` deloc** — face `REPLACE` direct in +`crsFactura`: +``` +REPLACE codmat WITH crsCodMat.codmat, denumire WITH crsCodmat.denumire, id_articol WITH crsCodmat.id_articol IN crsFactura +``` +Acelasi tipar identic pe coloana `cDenumire` (`:19312-19317`). Daca S4 extinde acest `REPLACE` cu +restul campurilor din cursorul filtrat — asa cum recomanda raportul anterior la sectiunea 5, punctul +4 ("sa extinda REPLACE-ul... cu restul campurilor") — **`id_jtva_coloana` nu mai trece prin niciun +`do_initializeaza_articol`/`frm_articol_factura.Init`**, pentru ca acel `REPLACE` nu creeaza obiectul +`poArticol` si nu instantiaza formularul. Cursorul filtrat tot n-are `id_jtva_coloana` (varianta +filtrata a `cursor_preturi` pastreaza aceeasi lista de coloane — vezi +`s4_cautare_articole_server.md:220`), deci randul din `crsFactura` ar ramane cu valoarea implicita +de camp (`Append Blank`, nu exista un `REPLACE` explicit pe acea coloana) — **fara nicio derivare din +`proc_tvav`**. + +La scriere, `adauga_articol_factura` (Oracle) cauta `ID_JTVA_COLOANA = V_ID_JTVA_COLOANA` in +`JTVA_COLOANE` pe ramura `ELSE` a `CASE`-ului sau (`ff_...PACK_FACTURARE.sql:5187-5198` — ramura care +se aplica exact tipurilor de lista de preturi 1/5/7/10/22/29, tipurile de transfer 23/45/48/49 si +altele care nu intra in ramurile speciale comanda/aviz/restaurant/contract-cu-pret-fix) si, daca nu +gaseste o potrivire, **arunca `RAISE_APPLICATION_ERROR(-20000, 'Nu a fost gasita cota de TVA! +(FACT-013 : ...)')`**. O valoare implicita de camp (tipic `0`, posibil `NULL` dupa `Append Blank`, +de verificat pe structura reala a `crsfactura`) ar produce fie acest -20000, fie (daca exista un rand +`JTVA_COLOANE.ID_JTVA_COLOANA=0`) o cota de TVA gresita scrisa tacut — amandoua ar fi un **bug nou, +introdus de S4**, nu unul preexistent. + +**Recomandare concreta pentru proiectarea S4 (nu doar constatare):** varianta filtrata a cautarii +(oricare ar fi mecanismul concret — `combosql` extins sau altceva) trebuie sa continue sa treaca prin +`do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa (sau sa reproduca explicit +derivarea din `proc_tvav` daca se alege un `REPLACE` direct), nu doar sa extinda `REPLACE`-ul de azi +cu campurile brute din cursor. De pus explicit ca cerinta in proiectarea S4, nu ca detaliu care se +rezolva de la sine. + +--- + +## Intrebarea 2 — `cursor_gestiune` (tipurile 23/41) fara buton "adauga tot"? + +### 2.1 Vizibilitatea butonului pe cod — confirmata + +`but_urmator_tot1` are **`Visible = .F.` la design-time** +(`ofacturare.vc2:11257-11266`, `ADD OBJECT 'But_urmator_tot1' AS but_urmator_tot WITH ... Visible = +.F. ...`). Devine vizibil doar prin `This.but_urmator_tot1.Visible = .T.` explicit, in 8 locuri din +`Do Case poDate.tip` din `Init` (`:15108-15248`): + +| Tip | Vizibil? | Linia care il seteaza | +|---|---|---| +| `eProforma=1` | da | `:15113` | +| `lCopiere` | da | `:15120` | +| `1,5,7,10` (lista de preturi) | **nu** (fara linie) | — | +| `2,6` (contract) | **nu** | — | +| `3` (comanda) | da | `:15150` | +| `4` (avize) | da | `:15166` | +| `21,28,42,47` (aviz din comanda) | da | `:15177` | +| `22,29` (aviz din lista de preturi) | **nu** | — | +| **`23`** (transfer subunitati) | **nu** — are propriul `Case` (`:15187-15195`), dar niciun `Visible=.T.` in el | — | +| **`41`** (retur transfer) | **nu** — are propriul `Case` (`:15197-15205`), dar niciun `Visible=.T.` in el | — | +| `25` (transfer din comanda) | da | `:15215` | +| `26` (aviz din contract) | **nu** | — | +| `8,9` (retur) | da | `:15240` | +| `24` (aviz retur) | da | `:15245` | + +**Confirmat: 23 si 41 au fiecare `Case` propriu in `Do Case`** (nu lipsesc din el, spre deosebire de +tipul 52, verificat separat intr-o cercetare anterioara ca "nu intra in niciun `Case`") — dar niciunul +din cele doua `Case`-uri nu seteaza `Visible = .T.`, deci butonul ramane la valoarea implicita +ascunsa. Concluzie identica cu lista de preturi (1/5/7/10): "adauga tot" e ascuns azi, nu doar +"neconfirmat". + +### 2.2 Exista alt mecanism de adaugare in masa pe 23/41? + +**Nu unul dedicat — dar exista mecanismul de baza, "adauga rand cu rand", disponibil pe orice tip.** +`But_urmator1` (singular, nu "tot") e `ADD OBJECT`-at neconditionat in definitia clasei +(`ofacturare.vc2:11237`), fara vreun `Visible=.F.` la design-time si fara sa fie atins de `Do Case` +de la 15108-15248 (acolo se atinge doar `but_urmator2`, aferent `grd_contracte`/contract, scos prin +`RemoveObject` pentru tipurile non-contract, `:15322-15323` — nu are legatura cu 23/41). Lantul e +`But_urmator1.Click` → `do_urmator()` → `Thisform.do_adauga_articol()` (`:14715`) — exact metoda cu +derivarea de TVA confirmata la intrebarea 1. Deci pe 23/41, ca si pe lista de preturi, operatorul +adauga **o linie o data**, prin acelasi buton/metoda folosit si azi pe tipurile de lista de preturi — +nu exista un "adauga tot" separat de recreat, pentru ca nu exista nici azi. + +### 2.3 Cine mai foloseste `cursor_gestiune` — CORECTIE fata de premisa din plan + +Rutarea reala e in `ofacturare.prg`, procedura `factureaza` (formularul **standard**, +`frm_facturare_articole` — cel lansat implicit), `Do Case` de la `:266-308`: + +``` +Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi -> cursor_preturi (:279-282) +Case Inlist(tnTip, 2, 26, 6, 52) && contract -> cursor_contract (:283-290) +Case Inlist(tnTip, 3, 21, 25, 28, 42, 47) && comenzi -> cursor_comanda (:292-293) +Case tnTip = 4 && din avize -> cursor_avize (:294-295) +Case Inlist(tnTip, 41) && transfer/retur -> cursor_gestiune (:296-298) +Case tnTip = 30 -> cursor_aviz_nir (:299-300) +Case tnTip = 27 -> cursor_lucrare (:302-304) +Case Inlist(tnTip, 8, 9, 24) -> cursor_retur (:306-307) +``` +plus, mai sus in acelasi `Do Case` (`:271-278`): `Case Inlist(tnTip, 48, 49)` → **`cursor_articole_k`** +(procedura complet diferita, nu una din cele cinci) si `Case tnTip = 45` → **`cursor_preturi`**. + +**Pe formularul standard: doar tipul 41 foloseste `cursor_gestiune`.** Tipul **23 foloseste de fapt +`cursor_preturi`**, grupat direct cu tipurile de lista de preturi (1,22,5,29,7,10) — asta explica de +ce, la 2.1, tipul 23 se comporta identic cu lista de preturi (acelasi cursor sursa). Tipurile **45 si +48/49 nu ating deloc `cursor_gestiune`**: 45 → `cursor_preturi`, 48/49 → `cursor_articole_k` (iese +din perimetrul celor cinci cursoare din S4). Premisa din plan ("`cursor_gestiune` — tipurile 23/41") +si mentiunea "45, 48, 49" (`s4_cautare_articole_server.md:344`, tabelul de vizibilitate) **descriu +corect vizibilitatea butonului** (toate aceste tipuri au "adauga tot" ascuns), dar **nu si sursa lor +de cursor** — nu toate trec prin `cursor_gestiune`. + +**Pe formularul prototip** (`frm_facturare_articole2`/`factureaza2`, accesibil live doar cu optiunea +`gnFacturareNou=1` **si** alegerea explicita a operatorului la un dialog de confirmare, +`ofacturare.prg:88-93` — deci reachable in productie, nu cod mort, dar opt-in), rutarea **difera**: +`Case Inlist(tnTip, 23, 41)` (`ofacturare.prg:776-778`) cheama **impreuna** `cursor_gestiune` pentru +**ambele** tipuri. Pe acest formular, premisa din plan e exacta. Divergenta intre cele doua formulare +(23 pe `cursor_preturi` in cel standard, pe `cursor_gestiune` in prototip) nu pare intentionata — nu +exista niciun comentariu care s-o justifice — dar e in afara perimetrului acestei cercetari (read-only, +fara editare) si nu afecteaza concluzia de mai jos, valabila pe oricare din cei doi cursori (niciunul +nu are `id_jtva_coloana`, ambii sunt tratati identic de `do_adauga_articol`). + +### 2.4 Concluzia care conteaza + +**Dupa S4, pe tipurile 23/41 nu se pierde nimic functional.** "Adauga tot" e deja absent azi pe +ambele (confirmat pe cod, 2.1), iar adaugarea se face deja rand-cu-rand prin acelasi mecanism folosit +si pe lista de preturi (`but_urmator1`/`do_urmator`/`do_adauga_articol`, 2.2) — mecanism pe care S4 nu +il atinge (S4 schimba doar sursa cursorului incarcat la deschidere, nu calea de adaugare a unei linii +individuale). Aceeasi rezerva de la intrebarea 1 se aplica identic aici: valabil doar daca varianta +filtrata a cautarii trece prin `do_adauga_articol`, nu daca reproduce `REPLACE`-ul direct din +`combosql`. + +--- + +## Handoff + +Nu e necesara predare — ambele intrebari sunt inchise, cu dovada pe cod. Rezervele de la 1.4 si 2.4 +(derivarea `id_jtva_coloana` trebuie sa treaca prin `do_adauga_articol`/`frm_articol_factura.Init`, +nu prin `REPLACE` direct tip `combosql`) sunt de dus mai departe in proiectarea S4, nu doar de +arhivat aici. Corectia de la 2.3 (doar tipul 41, nu si 23, foloseste `cursor_gestiune` pe formularul +standard) merita reflectata daca planul S4 mai citeaza undeva "cursor_gestiune (23/41)" ca sursa unica. diff --git a/docs/cercetare/s4b_bara_butoane_meniu.md b/docs/cercetare/s4b_bara_butoane_meniu.md new file mode 100644 index 0000000..34543d2 --- /dev/null +++ b/docs/cercetare/s4b_bara_butoane_meniu.md @@ -0,0 +1,606 @@ +# S4b — Bara de butoane si meniul de adaugare + +Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit), pentru +povestea **S4b** din `docs\plan_13_unificare_formular_facturare.md:1890-1899` (textul decis in +sectiunea J, `:911-1101`). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si +`COMUN\programe\ofacturare_editare.prg` (perimetrul #6) — doar citite, cand au aparut in cautari. + +## Verdict (esenta, 10 randuri) + +Cele trei bucati din decizia 13 sunt implementabile fara cod nou major, dar cu **trei corectii fata +de textul din plan**, toate in favoarea utilizatorului: (1) golul de contract nu e doar +`but_urmator_tot1.Visible` lipsa pe tip 2/6/26 — pe **tip 52 formularul nu intra deloc in ramura +lui**, `Do Case` nu are niciun `Case` care sa-l prinda, deci pierde si titlul, si eliminarea coloanei +`cSerie`, nu doar butonul "tot"; (2) tiparul RORIS (`frm_tranzit`, dialog bespoke) **nu e cel mai +ieftin de refolosit** — suita are deja, folosit chiar in acest formular pentru retur +(`do_cauta_facturi`), un mecanism generic de selectie multipla cu bifare, `cauta_alfa(..., +tnTipReturn=1)`, mai aproape de cerinta („dialog modal cu coloana de bifat si criterii de cautare") +decat un formular nou; (3) „unitatea de selectie e rata" pe contract **nu e valabil pentru toate +contractele** — cursorul `crsarticole1` are doua ramuri disjuncte, `OPT_FACTURARE=3` (articole reale) +si `OPT_FACTURARE IN (1,2)` (rate de scadentar), iar un contract e mereu pe una singura; eticheta +„Alege ratele de facturat…" e corecta doar pe ramura a doua. Echivalenta „tot" = „alege total" tine +azi pe o singura rutina comuna, `do_adauga_articol`, dar `do_adauga_tot` **nu parcurge `crsarticole1` +deloc** — pe contract, „adauga tot" ar trebui sa fie scris, nu doar facut vizibil. Detaliile, cu +`fisier:linie`, mai jos. + +--- + +## 1. Inventarul butoanelor de azi + +### 1.1 `frm_facturare_articole` (formularul de compunere in productie, `ofacturare.vc2:10968-15739`) + +Grid sursa (comanda/lista preturi) = `grd_articole` (`RecordSource=crsarticole`, +`ofacturare.vc2:11570`). Grid sursa-contract = `grd_contracte` (`RecordSource=crsarticole1`, +`:11891`). Grid destinatie (linii factura) = `grd_factura` (`RecordSource=crsfactura`, `:12263`). + +| Control | Clasa | Caption/Picture | L/T/W/H | ToolTipText | `fisier:linie` | +|---|---|---|---|---|---| +| `But_modifica1` | `but_modifica` | (fara caption) `modific_sus.bmp` | 773/61/30/27 | "Modificare (CTRL+M)" | `:11192` | +| `But_sterge1` | `but_sterge` | (fara caption) `sterg_sus.bmp` | 811/61/30/27 | "Stergere (CTRL+D)" | `:11229` | +| `But_renunt1` | `but_renunt` | (fara caption) `renunt_sus.bmp` | 792/1/30/27 | "Renuntare (ESC)" | `:11202` | +| `But_reset1` | `but_reset` | (fara caption) `reset_sus.bmp` | 303/295/30/27 | "Reseteaza" | `:11213` | +| `But_urmator1` | `but_urmator`, `caction=do_urmator` | `urmator1.bmp` | 343/348/30/27 | "Verificare" | `:11237` | +| `But_urmator2` | `but_urmator`, `caction=do_urmator2` | `urmator1.bmp` | 343/143/30/27 | "Verificare" | `:11247` | +| **`But_urmator_tot1`** | `but_urmator_tot`, `caction=do_adauga_tot` | `urmator1_tot.bmp`, **fara Caption, fara ToolTipText** (clasa insasi nu are niciuna din ele — `cmd_butoane.vc2:414-425`) | 343/378/30/27, `Visible=.F.` implicit | — | `:11257` | +| `But_retur` | `but_retur`, `caction=do_retur` | `retur1.bmp`, `Visible=.F.` implicit | 343/407/30/27 | "Retur" | `:11221` | + +Niciun buton "linie noua" (`but_nou`) — o linie se adauga alegand un rand din `grd_articole` / +`grd_contracte` si apeland `do_adauga_articol` (dublu-click / Enter, mecanism de grid standard, nu +citit exhaustiv aici), nu prin `APPEND BLANK` pe `crsfactura`. "Detalii linie" nu exista ca buton +separat — `But_modifica1` **e** azi echivalentul: deschide `frm_articol_factura` pe randul curent din +`crsfactura` (`do_modifica`, `:13746-13914`), reface plafonul de cantitate din cursorul sursa +(`crsarticole` sau `crsarticole1`, ales prin `poArticol.opt_facturare`, `:13778`) si reconstruieste +proprietatile de discount/pret in valuta. + +**"Adauga tot" — `But_urmator_tot1.Click -> do_adauga_tot`** (`:13169-13198`): + +``` +PROCEDURE do_adauga_tot + If Used('crsarticole') And Reccount('crsarticole') > 0 + Select crsarticole + Scan + ... + Thisform.do_adauga_articol(.T.) && tlContract = omis => .F. implicit + ... + Endscan + Else + aMessageBox("Nu exista articole de adaugat!", 48, "Atentie") + Endif +ENDPROC +``` + +**Parcurge exclusiv `crsarticole`.** Nu exista nicio ramura care sa parcurga `crsarticole1` — deci +chiar daca s-ar face vizibil pe tip 2/6/26/52, "adauga tot" **nu ar aduce nimic** pe un contract al +carui `grd_contracte`/`crsarticole1` are randuri (rate sau articole de contract), pentru ca butonul +citeste doar cursorul celalalt. E un gol de **cod**, nu doar de vizibilitate — vezi sectiunea 5. + +**Conventia de clase de butoane** (confirmat, `inventar_controale_formulare.md`): clasa de baza +`buton` (`_cmd_base.vc2:16`) are `Caption=""` implicit — butoanele sunt doar-imagine 30x27px prin +design. `but_nou` (`cmd_butoane.vc2:214-227`, `caption=do_adauga`, `ToolTipText="Adaugare +(CTRL+N)"`) si `but_sterge` (`:354-366`, `caption=inainte_de_do_sterge`, tot fara Caption propriu la +nivel de clasa) au deja `ToolTipText`, dar niciodata `Caption` — "eticheta, nu iconita muta" ceruta de +decizia 13 e o schimbare reala, nu o simpla refolosire a clasei: fie se seteaza `Caption` pe instanta +(clasa `buton` are proprietatea, pur si simplu nefolosita azi), fie se creeaza o varianta de clasa cu +`Caption` implicit. + +### 1.2 `frm_facturare_articole2` (prototip, `ofacturare.vc2:15741-19355`) + +Arhitectura diferita: **un singur grid** `grd_factura` cu editare inline prin combo-uri in celule +(`cCodMat.cboCodmat`, `cDenumire.cCboDenumire`) — nu exista `grd_articole`/`crsarticole` separat. + +| Control | Clasa | Caption/Picture | L/T/W/H | ToolTipText | `fisier:linie` | +|---|---|---|---|---|---| +| **`But_nou1`** | `but_nou` (caption suprascris pe instanta, vezi mai jos) | `nou_sus.bmp` | 719/175/30/27 | "Adaugare (CTRL+N)" | `:15936` | +| `But_sterge1` | `but_sterge` | `sterg_sus.bmp` | 747/175/30/27 | "Stergere (CTRL+D)" | `:15955` | +| `But_renunt1` | `but_renunt` | `renunt_sus.bmp` | 716/1/30/27 | "Renuntare (ESC)" | `:15944` | + +**Sunt deja unde trebuie**: `Top=175`, gridul la `Top=204` (`:15936`, `:15955`) — **deasupra +tabelului**, exact pozitionarea ceruta de decizia 13. + +**`But_nou1.Click -> do_adauga`** (`:17118-17122`, override propriu pe instanta, nu `caction` +mostenit de la clasa `but_nou`): + +``` +PROCEDURE do_adauga + Select crsFactura + APPEND BLANK + this.grd_factura.SetFocus() +ENDPROC +``` + +Adauga direct un rand gol si da focus in grid — **nu** cheama `do_adauga_articol`, nu verifica stoc, +nu deschide niciun dialog. Completarea articolului se face **dupa**, prin editare inline in celule +(`cboCodmat`/`cCboDenumire`) — acesta e tiparul care se leaga direct de S4 (cautare pe server, vezi +sectiunea 6), nu de meniul de adaugare in masa. + +Nu exista `But_modifica`/`detalii linie` in acest prototip — editarea se face inline, in celula. + +### 1.3 Tabel comparativ + +| | `frm_facturare_articole` (productie) | `frm_facturare_articole2` (prototip) | +|---|---|---| +| Pozitia butoanelor de linie | lateral, intre cele doua griduri (`Left=773+`) | deasupra gridului (`Top=175`, gridul la `Top=204`) — cerinta decizie 13 | +| "Linie noua" | nu exista — se alege dintr-un grid sursa | `But_nou1` -> `APPEND BLANK` + focus | +| "Sterge linie" | `But_sterge1`, lateral | `But_sterge1`, deasupra | +| "Detalii linie" | `But_modifica1` -> `frm_articol_factura` (dialog complet) | nu exista — editare inline | +| Sursa de articole | 2 griduri separate, `crsarticole`/`crsarticole1` | niciunul — combo pe cod/denumire in celula | +| Caption pe butoane | fara, peste tot (doar iconite) | fara, peste tot (doar iconite) — nici prototipul nu rezolva "eticheta" | + +Concluzie pentru implementare: **pozitionarea** se ia din prototip, **comportamentul de adaugare in +masa** (`do_adauga_tot`/dialoage de alegere) se ia din formularul de productie (prototipul nu are deloc +aceasta logica — n-a fost construit pentru documente cu sursa), iar **eticheta pe buton** nu exista +azi in niciunul din cele doua, e de adaugat explicit in ambele cazuri. + +--- + +## 2. `xmenu()` — contract si exemple + +**Definitie**: `COMUN\programe\proceduri_comune.prg:755-822` (identica, byte-cu-byte in structura, cu +`COMUN\programe\oproceduri_comune.prg:1550-1617` — a doua e cea incarcata efectiv, SET PROCEDURE +ADDITIVE peste prima, dar comportamentul e acelasi). + +``` +Procedure XMENU + Lparameters TCITEMS, TNBAR + && TCITEMS = optiuni separate prin ";" ; "\-" = separator vizual (bara, nu optiune selectabila, + && nu se trece prin ALLTRIM); "\ metoda unica -> dialog cu bifare si criterii -> populare aditiva prin +`INSERT`, fara scriere Oracle pana la salvare) — dar `frm_tranzit` insusi e un formular **specific +ROAACNPRO**, care nu exista in ROAFACTURARE si n-ar trebui portat ca formular. + +**ROAFACTURARE are deja, in productie, in acelasi `frm_facturare_articole` care e subiectul acestei +povesti, mecanismul cerut** — `cauta_alfa()` (`COMUN\programe\cauta_alfa.prg:16-260`), functia de +cautare generica folosita de zeci de `do_cauta_*` din toata suita. Are deja tot ce cere decizia 13: + +- **coloana de bifat**: cand `tnTipReturn=1`, `cauta_alfa` adauga singura o coloana `ales` peste + cursorul de rezultate (`cauta_alfa.prg:124`: `Select *, 0 As ales From &lcCursort ... Into Cursor + &lcCursor`) si titlul devine explicit "Alegeti ... (mouse-click pe numar sau apasati SPACE)" + (`oproceduri_facturare.prg:2104`); +- **criterii de cautare**: parametrul `tcStringCriterii` deschide `cauta_alfa_form_plus`, varianta cu + bara de criterii (`cauta_alfa.prg:22`); fara el, cautarea alfa/browse standard tot filtreaza + interactiv pe coloanele afisate; +- **populare aditiva, fara scriere in baza**: returul (`tnTipReturn=1`) e un XML cu randurile bifate, + convertit local prin `Xmltocursor(...)` (exemplu direct, `ofacturare.vc2:9196`) — nimic nu ajunge la + Oracle in acest pas; +- **e deja folosit in acest exact formular**, pentru un caz de "alege documentul sursa": vezi 4.2. + +**Recomandare de proiectare**: dialoagele "Alege..." din meniul S4b se construiesc pe reteta +`cauta_alfa(..., tnTipReturn=1)`, cu `tcselect`/`tcfiltru` diferite pe sursa (comanda/contract/avize/ +retur), **nu** pe un formular nou stilizat dupa `frm_tranzit`. Ramane de portat din RORIS doar +**ideea**, nu codul: buton unic condiționat, metoda de intrare unica, populare aditiva — toate trei +deja adevarate si pentru `cauta_alfa`. + +### 4.2 Precedentul direct: retur multi-factura + +`frm_date_factura.do_cauta_facturi` (`ofacturare.vc2:9173-9212`) foloseste deja exact acest tipar, +pentru cazul "alege mai multe facturi sursa de retur": + +``` +lcXMLFacturi = caut_facturi_multiple_client(poDate.id_client, poDate.in_valuta, poDate.id_valuta, .T.) +If !Empty(lcXMLFacturi) and gnButon = 1 + Xmltocursor(lcXMLFacturi, "crsFacturiTemp") + ... + poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",") +``` + +si `caut_facturi_multiple_client` (`oproceduri_facturare.prg:2091-2121`) cheama direct +`cauta_alfa(..., lnTipReturn = Iif(tlFacturiMultiple, 1, 0))`. **Important pentru sectiunea 9**: acest +pas ruleaza azi la **antet** (`frm_date_factura`), inainte ca formularul de articole (si deci bara de +butoane S4b) sa existe — `poDate.listaid` e fixat o singura data, cursorul `crsarticole` se incarca +o singura data din el (`cursor_retur_document`, cu `V_LISTAID`). Optiunea "Adauga tot din facturile +alese" din meniul S4b **poate** refolosi direct `do_adauga_tot`/`crsarticole` asa cum e azi (sursa e +deja bounded). O optiune noua "Alege facturile de returnat…" **la nivelul barei de butoane** ar insemna +insa a permite schimbarea setului de facturi sursa **dupa** ce formularul de articole s-a deschis — +azi nu exista acest flux (odata setat, `poDate.listaid` nu se rescrie din articole). E o decizie de +proiectare noua, nu o simpla mutare a codului existent (vezi 9.3). + +### 4.3 Ce cursor-sursa pe fiecare tip, si ce inseamna "linie" acolo + +| Sursa | Cursor(oare) populat(e) la intrarea in `factureaza`/`factureaza2` | Ce e o "linie" pentru dialogul de alegere | `fisier:linie` | +|---|---|---|---| +| **comanda** (3, 21, 25, 28, 42, 47) | `crsarticole` <- `pack_facturare.cursor_comanda` | un articol de pe `COMENZI_ELEMENTE`, cu `id_pol` mostenit direct de pe linia comenzii | `ofacturare.prg:292-293` | +| **contract, `OPT_FACTURARE=3`** (2,6,26,52) | `crsarticole1` <- `pack_facturare.cursor_contract`, ramura `CTR_ARTICOLE` | un articol al contractului, `id_pol` derivat prin `CTR_ARTICOLE.ID_POL_ART -> CRM_POLITICI_PRET_ART` | `ff_...COMUN_PACK_FACTURARE.sql:2752-2836` | +| **contract, `OPT_FACTURARE IN (1,2)`** (2,6,26,52) | `crsarticole1` <- acelasi cursor, ramura `CTR_SCADENTAR` (`UNION ALL`) | o **rata** de scadentar (`ID_RATA`, `NR_RATA`, `DATA_RATA`, `DATA_SCADENTA`, `DEN_RATA`); `id_articol` e **NULL**, `cantitate` e mereu 1, `gestionabil=0` | `ff_...COMUN_PACK_FACTURARE.sql:2836-2924` | +| **contract, oricare din cele doua** — cautare libera | `crsarticole` <- acelasi apel, dar cu `pack_facturare.cursor_preturi` legat in interior | lista de preturi normala a operatorului (nu e "libera de politica", vezi `idpol_comanda_contract.md` E.3) | `ff_...COMUN_PACK_FACTURARE.sql:2940-2948` | +| **avize** (4) | `crsarticole` <- `pack_facturare.cursor_avize` | un rand de pe avizul sursa | `ofacturare.prg:294-295` | +| **retur** (8, 9, 24) | `crsarticole` <- `pack_facturare.cursor_retur_document`, filtrat pe `V_LISTAID` (facturile alese la antet, 4.2) | un articol al facturii/facturilor deja alese; **nicio coloana `id_vanzare`/`id_vanzare_det` in cursor** — legatura cu factura sursa se pierde inainte sa ajunga in VFP (`docs\cercetare\legatura_linie_retur.md:8-20`) | `ff_...COMUN_PACK_FACTURARE.sql:3949-4062` | + +**Pe contract, `crsarticole1` e populat printr-un singur `UNION ALL`, dar cele doua ramuri sunt +disjuncte pe `OPT_FACTURARE`**: pentru un contract cu `OPT_FACTURARE=3`, `WHERE A.OPT_FACTURARE = 3` +elimina ramura de rate; pentru `OPT_FACTURARE IN (1,2)`, `WHERE ... IN (1,2)` elimina ramura de +articole. **Un contract nu produce niciodata ambele tipuri de rand in acelasi `crsarticole1`.** Deci +eticheta din meniu ("Adauga tot din contract" vs. "Adauga toate ratele") **trebuie sa depinda de +`crsarticole1.opt_facturare`** (coloana exista in cursor, `:2751,2893` — poate fi citita din primul +rand dupa incarcare), nu poate fi un text static ca in tabelul din plan. + +**Daca `crsarticole1` iese gol** (contract fara articole/rate configurate), formularul de azi +**elimina complet** `grd_contracte`/`But_urmator2` si redistribuie inaltimea catre `grd_articole` +(`ofacturare.vc2:15294-15324`, `If !Used('crsarticole1') ... Thisform.RemoveObject('grd_contracte')`) +— documentul se comporta ca o factura "libera" din lista de preturi. Meniul S4b trebuie sa verifice +aceeasi conditie inainte sa ofere "Adauga tot din contract"/"Alege ratele…": daca cursorul nu exista +sau e gol, optiunea nu apare deloc (nu apare goala). + +### 4.4 Populare aditiva si "nicio scriere pana la salvare" + +Deja adevarat prin constructie, pentru toata familia `do_adauga_articol`: fiecare rand ales (fie prin +`do_adauga_tot`, fie prin rezultatul unui `cauta_alfa`) trece prin **`Thisform.do_adauga_articol(...)`**, +care termina cu `Insert Into crsfactura(...)` (cursor local, `.T. Buffering`) — niciun apel Oracle in +acest pas (Oracle e atins abia la salvarea documentului, `finalizeaza_factura`/`scrie_factura2`, in +afara acestei povesti). Un dialog nou de alegere trebuie doar sa **produca lista de randuri bifate** +(cursor XML din `cauta_alfa`) si sa **cheme aceeasi rutina** rand cu rand — vezi sectiunea 5, e +punctul care garanteaza si echivalenta cu "tot". + +### 4.5 Zero rezultate — mesaj, nu tacere + +RORIS nu spune nimic la zero rezultate (`import_roris_roaacnpro.md`, punctul 5: butonul "pare ca nu +face nimic"). `cauta_alfa` insasi nu are un mesaj dedicat de "zero rezultate" (e un browse generic — +daca cursorul de rezultate e gol, grila apare goala, fara alt semnal, cf. structurii ei standard). +**Reteta pentru S4b**: verificarea "zero rezultate" se face **inainte** de a deschide `cauta_alfa` +(similar cu `do_cauta_facturi`, care verifica intai `Empty(poDate.id_client)` inainte de a cauta), +printr-un `SELECT COUNT(*)` (sau verificare pe cursorul deja incarcat, `Reccount(...)=0`) urmat de +`AMESSAGEBOX` care spune **de ce**: "Contractul nu are rate de facturat neincasate" / "Comanda nu are +articole ramase de facturat" / etc., in loc sa lase dialogul sa se deschida gol. Exact tiparul deja +folosit de `do_adauga_tot` insusi pe ramura fara date (`:13195-13197`, +`"Nu exista articole de adaugat!"`) — se extinde acelasi obicei la dialogul de alegere. + +--- + +## 5. Echivalenta „tot" vs. „alege" + +**Garantia structurala exista, dar e incompleta azi.** Toate cele trei cai de populare a +`crsfactura` — `do_adauga_tot` (SCAN + apel), un viitor dialog de alegere (bifare + apel pe fiecare +rand bifat), si adaugarea manuala rand cu rand — converg spre **aceeasi rutina**, +`Thisform.do_adauga_articol(tlImplicit, tlContract, tlRetur)` (`ofacturare.vc2:12813-...`). Cata vreme +toate trei cheama exact aceasta rutina, cu acelasi cursor sursa pozitionat pe randul corect, rezultatul +per linie in `crsfactura` e identic prin constructie — nu exista o a doua cale de `INSERT` in +`crsfactura` in afara acestei rutine (confirmat prin cautarea `Insert Into crsfactura` — singurul alt +loc e ramura specifica `tip=30`/aviz din NIR, `ofacturare.prg:357-376`, care nu intra in perimetrul +S4b). + +**Ce lipseste, concret, ca sa fie adevarat si pe contract**: `do_adauga_tot` (`:13169-13198`) parcurge +**doar `crsarticole`**. Pentru ca "Adauga tot din contract" / "Adauga toate ratele" sa functioneze, +`do_adauga_tot` are nevoie de o ramura noua (sau un al doilea parametru `tlContract`, simetric cu +`do_adauga_articol`) care sa faca `SCAN` peste `crsarticole1` si sa cheme +`Thisform.do_adauga_articol(.T., .T.)`. Fara aceasta completare, "tot" pe contract fie nu exista, fie +(daca cineva ar face butonul vizibil fara sa atinga metoda) ar aduce tacut liniile gresite din +`crsarticole` (lista de preturi) in loc de `crsarticole1` (articolele/ratele contractului) — o eroare +mai grava decat lipsa vizibilitatii, pentru ca n-ar da nicio eroare, doar rezultat gresit. + +**Alegerea selectiva** trebuie sa respecte aceeasi regula: dupa ce `cauta_alfa` intoarce XML-ul cu +randurile bifate, bucla care le proceseaza trebuie sa pozitioneze cursorul sursa (`crsarticole` sau +`crsarticole1`, dupa caz) pe `id_c`-ul corespunzator inainte de fiecare apel `do_adauga_articol`, **nu** +sa reconstruiasca linia din campurile XML direct — altfel cele doua cai ("tot" vs. "alege") ar avea +doua implementari diferite ale "ce inseamna sa adaug randul X", exact riscul pe care criteriul de gata +din plan il exclude explicit ("rezultatul in `crsfactura` e identic pe cele doua cai cand selectia e +totala"). + +**Pe comanda/avize/retur, criteriul e deja adevarat prin constructie** — `do_adauga_tot` foloseste azi +`crsarticole`, care e cursorul lor unic; singurul de completat e contractul (cele doua cazuri de mai +sus). + +--- + +## 6. Interactiunea cu S4 (cautarea articolelor pe server) + +Decizia din S4 (`plan_13...md:1879-1888`) e explicita: `crsarticole` **nu se mai incarca in masa** +pentru facturarea libera (lista de preturi/nomenclator), inlocuita cu `combosql` pe cod/denumire care +completeaza randul pe loc — **dar** "se pastreaza adauga tot pentru tipurile care au document sursa +(comanda, aviz, contract) — acolo setul e marginit si incarcarea lui e legitima". Consecinta directa +pentru meniul S4b: + +- **Optiunile "Cauta in lista de preturi…" si "Alege din nomenclator…"** (constante peste tot, cf. + deciziilor 16/18/20) **nu deschid un dialog de tip `cauta_alfa`** — ele sunt fatada meniului pentru + mecanismul S4: `combosql` pe o singura linie, populata pe loc, fara incarcare in masa. In termeni de + UI, alegerea uneia din aceste doua optiuni echivaleaza cu ce face azi `But_nou1.do_adauga` din + prototip (`APPEND BLANK` + focus pe celula de cod/denumire) — deci **"linie noua" (sectiunea 1) si + aceste doua optiuni de meniu ajung la acelasi gest UI**, doar ca "linie noua" il porneste direct + (fara meniu), iar cele doua optiuni de meniu il pornesc din context (dupa ce operatorul a vazut si + restul optiunilor sursei). Nu sunt cai de cod diferite — sunt doua puncte de intrare in acelasi + `APPEND BLANK` + editare inline. +- **Toate optiunile "Adauga tot din X…" / "Alege X…"** (comanda, contract, avize, retur) opereaza pe + cursoarele bounded (`crsarticole`/`crsarticole1`) pe care **S4 le pastreaza neschimbate** — pentru + aceste tipuri, nimic din mecanismul de cautare pe server nu se aplica; secțiunile 3-5 de mai sus + raman valabile ca atare. +- **Punct de atingere real intre cele doua povesti**: campul `id_pol` pe randul adaugat prin + `combosql` (S4, cautare din nomenclator liber) — subiectul blocajului `FACT-024` documentat pe larg + in plan (J-ter/J-quater) si in `idpol_comanda_contract.md`. S4b **nu rezolva** acest blocaj (e + decizia de produs deschisa la J-quater, intre reteta in 4 pasi / nota pe antet / nota pe antet ca + fallback) — doar il mosteneste: optiunea "Alege din nomenclator…" din meniul S4b va ajunge la aceeasi + eroare `FACT-024` la salvare, indiferent de cum arata butonul, pana cand acea decizie separata se ia. + De mentionat explicit in criteriul de gata al S4b, ca sa nu se creada ca butonul rezolva problema. + +--- + +## 7. Ordinea de executie in pasi + +1. **Bara de linie (`but_nou`, `but_sterge`, "detalii linie")**, mutata deasupra gridului, cu + `Caption` explicit pe fiecare instanta (clasele `but_nou`/`but_sterge` au deja `ToolTipText`, nu au + niciodata `Caption` — de setat pe instanta, nu de asteptat de la clasa). + *Verificabil*: cele trei butoane sunt vizibile deasupra gridului de linii, fiecare cu text vizibil + (nu doar iconita), pe orice tip de document deschis. +2. **Corectarea `Do Case` din `Init`** (`ofacturare.vc2:15109-15248`): adauga `52` la ramura + `Inlist(poDate.tip, 2, 6)` (linia 15129) astfel incat sa primeasca titlu, eliminare `cSerie`, si sa + fie tratat identic cu 2/6 in tot restul metodei — corecteaza golul mai mare decat cel semnalat in + plan (sectiunea 3 de mai sus). Adauga `26` la lista tipurilor care primesc `but_urmator_tot1.Visible + = .T.` candva in pasul 3, nu aici (26 ramane azi cu titlu corect, doar fara butonul "tot"). + *Verificabil*: un document nou de tip 52 arata acelasi titlu si acelasi grid (fara `cSerie`) ca un + document de tip 2/6. +3. **Extinderea `do_adauga_tot`** cu ramura pe `crsarticole1` (parametru sau ramura separata dupa + `poDate.tip`/`crsarticole1.opt_facturare`), plus vizibilitatea butonului/optiunii pe 2/6/26/52 — + **cei doi pasi impreuna**, nu separat (vizibilitate fara continut ar aduce un buton mut; continut + fara vizibilitate nu s-ar vedea niciodata). + *Verificabil*: pe un contract cu `OPT_FACTURARE=3` si articole configurate, "adauga tot" produce in + `crsfactura` exact liniile din `crsarticole1`; pe un contract cu `OPT_FACTURARE IN (1,2)`, produce + cate o linie per rata neincasata. +4. **Butonul unic "Adauga articole" cu `xmenu()`**, inlocuind `But_urmator_tot1` (fara caption) si + orice buton separat de alegere — construit dinamic dupa tabelul din sectiunea 3, cu eticheta pe + ramura de contract aleasa in functie de `crsarticole1.opt_facturare` (sectiunea 4.3). + *Verificabil*: pe fiecare sursa, meniul deschis contine exact optiunile din tabelul sectiunii 3, in + ordinea specificata, iar alegerea `ESC` (`xmenu()` intoarce 0) nu declanseaza nimic. +5. **Dialogul de alegere selectiva**, pe reteta `cauta_alfa(..., tnTipReturn=1)` (sectiunea 4), + cu mesaj explicit la zero rezultate (4.5) si populare prin `do_adauga_articol` rand cu rand (5) — + cate o instantiere per sursa (comanda/contract-articole/contract-rate/avize/retur-linii), cu + `tcselect`/`tcfiltru` proprii. + *Verificabil*: pe fiecare sursa, bifarea a N randuri din M disponibile adauga exact acele N randuri + in `crsfactura`, cu aceleasi valori ca daca ar fi fost adaugate individual; bifarea tuturor produce + acelasi rezultat ca "adauga tot" (criteriul de gata al plan-ului, rescris verificabil). + *Depinde de*: pasul 3 (aceeasi rutina `do_adauga_articol` trebuie sa functioneze deja pe + `crsarticole1` inainte ca dialogul de alegere sa se poata baza pe ea). +6. **Reconcilierea cu selectia de facturi la antet** (sectiunea 4.2, 9.3) — decizie separata, + nu blocanta pentru pasii 1-5: daca "Alege facturile de returnat…" ramane doar la antet (cum e azi) + sau devine posibila si din bara de butoane. + +*Gata cand (rescris)*: pe fiecare din cele sase surse (lista de preturi, contract-articole, +contract-rate, comanda, avize, retur), meniul "Adauga articole" ofera exact optiunile tabelate in +sectiunea 3; pentru fiecare sursa cu "tot"/"alege" ambele prezente, o selectie totala prin dialogul de +alegere produce in `crsfactura` acelasi set de randuri (aceleasi valori, nu doar acelasi numar) ca +"adauga tot"; zero rezultate in orice dialog de alegere produce un mesaj care spune sursa si motivul, +nu un dialog gol; tip 52 se comporta identic cu tip 2/6 in titlu si structura de grid. + +--- + +## 8. Ce NU se poate testa headless + +- **Continutul si comportamentul meniului `xmenu()`** — `Activate Screen` + `Define Popup ... Bar` e + UI nativa Windows/VFP (shortcut popup la pozitia mouse-ului); nu exista API de citire a itemilor unui + popup activ prin automatizare headless. Verificarea "meniul contine optiunile N" se poate face doar + **indirect**, citind stringul `lcMeniu` construit inainte de apelul `xmenu()` (verificabil headless, + ca text), nu popup-ul afisat efectiv. +- **Coloanele griduri (`grd_articole`, `grd_contracte`, `grd_factura`, si viitorul grid al dialogului + de alegere)** — capcana deja cunoscuta si confirmata pe acest proiect: sub `-A -T` + (rulare headless) `ColumnCount=0` si `RecordSource` raman artefacte, coloanele nu se materializeaza. + Exista un harness UI **vizibil** (nu headless) care le citeste corect — orice verificare vizuala a + meniului/dialogului de alegere trebuie sa treaca prin acel harness, nu prin rulare `-A -T`. +- **`cauta_alfa`/`cauta_alfa_form_plus`** — dialog modal cu `.Show(1)`, blocheaza thread-ul UI pana la + interactiune; testarea automata ar necesita fie injectare de input (interzisa pe aceasta masina, + partajata cu Marius — vezi memoria `masina-partajata-fara-input-real`), fie apelarea directa a + functiilor Oracle/cursor din spatele dialogului, ocolind UI-ul (verifica *datele*, nu *dialogul*). +- **`AMESSAGEBOX` la zero rezultate** — verificabil ca text al mesajului in cod (string literal), nu ca + aparitie reala pe ecran, din acelasi motiv. +- **Pozitionarea vizuala "deasupra gridului"** (`Top`/`Height` relative) — se poate verifica numeric + (comparand `Top`-ul butoanelor cu `Top`-ul gridului in `.vc2`), dar nu si "arata bine"/aliniere + vizuala — necesita captura de ecran prin harness-ul UI vizibil. + +Ce **se poate** verifica headless, direct: continutul cursoarelor (`crsarticole`, `crsarticole1`, +`crsfactura`) dupa apelul rutinelor de adaugare, apelate direct (nu prin click) cu parametri de test; +stringul `lcMeniu` construit inainte de `xmenu()`; valorile `Visible`/`Caption`/`ToolTipText` setate pe +obiectele din `.vc2` (proprietati statice, nu comportament de runtime). + +--- + +## 9. Riscuri, capcane de UX, si ce ramane de decis de Marius + +### 9.1 Regulile de formulare/griduri (`COMUN\docs\reguli_lucru.md`, punctul 7) + +Aplicabile direct aici (citite, respectate in proiectarea de mai sus): +- **`GO` pe `Recno()`** — orice bucla de tip `do_adauga_tot`/dialog de alegere care itereaza cursorul + sursa (`crsarticole`/`crsarticole1`) trebuie sa retina si sa refaca pozitia (`Recno()`) dupa fiecare + apel `do_adauga_articol`, exact cum face deja `do_adauga_tot` azi (`lnNrInregistrare = Recno()` ... + `Go lnNrInregistrare`, `:13175,13185,13193`) — pastrat si in ramura noua pe `crsarticole1`. +- **UX formulare/griduri** — butoanele fara `Caption` sunt azi conforme cu restul suitei (toate + butoanele suita sunt icon-only); a le adauga `Caption` e o schimbare vizibila la nivelul intregii + bare, nu doar a celor trei butoane noi — de discutat daca `But_renunt1`/`But_reset1` raman mute langa + butoane cu text (inconsistenta vizuala posibila, nu doar tehnica). + +### 9.2 Contractul are doua meniuri, nu unul (sectiunea 4.3) + +Tabelul din plan (J) trateaza "contract" ca o singura sursa cu un singur set de optiuni. Pe cod, un +contract e **fie** OPT_FACTURARE=3 (articole), **fie** 1/2 (rate) — niciodata ambele. Eticheta +"Alege ratele de facturat…" din decizia explicita a lui Marius e corecta doar pe ramura a doua; pe +prima, echivalentul e "Alege articolele din contract…". **De decis**: se pastreaza o singura eticheta +generica ("Alege din contract…") care acopera ambele cazuri fara sa distinga in text, sau se +diferentiaza explicit (mai clar pentru operator, dar mai mult cod de verificare a lui +`crsarticole1.opt_facturare` inainte de a construi meniul)? + +### 9.3 "Alege facturile de returnat…" — la antet sau la bara de butoane? + +Azi (sectiunea 4.2), selectia facturilor sursa pentru retur se face **o singura data**, la deschiderea +formularului de antet (`frm_date_factura.do_cauta_facturi`), inainte ca formularul de articole sa +existe. Meniul S4b, asa cum e cerut de decizia 13, ar pune "Alege facturile de returnat…" **in bara de +butoane a formularului de articole** — adica dupa ce `poDate.listaid` a fost deja fixat. Doua optiuni, +de decis de Marius: +1. Optiunea din meniul S4b **nu schimba** setul de facturi sursa — e doar un rascolitor spre inapoi, + catre acelasi dialog de la antet, dar utilizatorul trebuie sa iasa din pasul de articole ca sa-l + foloseasca (comportament posibil confuz: optiunea apare in meniul de articole, dar actioneaza pe alt + ecran). +2. Se adauga un mecanism nou: alegerea de facturi suplimentare **din formularul de articole**, care + extinde `poDate.listaid` si reincarca aditiv `crsarticole` (`cursor_retur_document` apelat a doua + oara, cu filtru pe facturile noi, `INSERT INTO crsarticole` in loc de `SELECT INTO`) — cere cod nou + in VFP, dar nu in `pack_facturare` (procedura Oracle ramane aceeasi, doar apelata de mai multe ori). + +"Alege liniile de returnat…" (per articol, din facturile deja alese) e diferita si **nu are conflict** +cu fluxul de azi — e un dialog de alegere clasic peste `crsarticole`, ca pe comanda/avize. + +### 9.4 `id_pol` pe rata de contract — `NULL` prin constructie, dar validat corect + +Rata de scadentar (`crsarticole1`, ramura `OPT_FACTURARE IN (1,2)`) are **`id_pol` NULL** prin +constructia SQL insasi (`NULL AS ID_POL`, `:2840`) — dar asta nu ajunge niciodata la `FACT-024`, +pentru ca ratele nu trec prin `contabilizeaza_articol`, ci prin `contabilizeaza_rata` (functie separata +in `pack_facturare`, `idpol_comanda_contract.md` §0c), care citeste nota din `CONTRACTE.ID_NOTA`. **De +verificat inainte de implementare**: `do_adauga_articol`/`do_scrie_articole` scriu randul de rata in +`crsfactura` cu ce anume in coloana `id_pol` (probabil `NULL`, mostenit din `poArticol`) — daca +salvarea foloseste ramura corecta (`contabilizeaza_rata`, nu `contabilizeaza_articol`) pe baza altui +semnal decat `id_pol` (probabil `id_ctr`/`opt_facturare` pe linie), nu pe baza lui `id_pol` fiind gol. +Nu s-a verificat cine alege intre cele doua functii la salvare — e in afara perimetrului S4b (tine de +scriere, nu de bara de butoane), dar merita un pointer explicit ca sa nu se presupuna gresit ca "rata +fara `id_pol`" e acelasi caz cu "articol din nomenclator fara `id_pol`" (J-quater) — sunt cai de cod +diferite, cu tratament diferit. + +### 9.5 Ce ramane strict de decis de Marius + +1. Eticheta pe contract (9.2) — generica sau diferentiata pe `OPT_FACTURARE`. +2. Fluxul "Alege facturile de returnat…" (9.3) — raman la antet sau se adauga incarcare aditiva. +3. Daca `But_renunt1`/`But_reset1` primesc si ele `Caption`, pentru consistenta vizuala cu bara nou- + etichetata, sau raman icon-only (9.1). +4. Ordinea si formularea exacta a optiunilor in `xmenu()` per sursa (tabelul din sectiunea 3 e o + propunere, nu o decizie finala de text). + +--- + +## Handoff + +Cercetare incheiata, fara nicio problema de context intalnita in aceasta runda — nu a fost necesara +predarea. Toate cele noua sectiuni cerute de brief sunt completate mai sus, cu `fisier:linie` verificat +direct pe fisierele text reale (nu `.bak`), plus SQL-ul din `PACK_FACTURARE` exportat curent +(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`). Nicio editare de +cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit. diff --git a/docs/cercetare/s4c_discount_in_grid.md b/docs/cercetare/s4c_discount_in_grid.md new file mode 100644 index 0000000..522dd36 --- /dev/null +++ b/docs/cercetare/s4c_discount_in_grid.md @@ -0,0 +1,489 @@ +# S4c — Discountul pe linie, mutat din dialog in grid — proiectare implementabila + +Cercetare + proiectare READ-ONLY pentru `docs\plan_13_unificare_formular_facturare.md`, `#### S4c` +(`:2156-2190`). Nu s-a modificat niciun fisier, nu s-a rulat `git_sync.ps1`/`txt2vcx.ps1`, nu s-a dat +commit, nu s-a scris nimic pe Oracle (numai `SELECT`, niciunul rulat de fapt — toata cercetarea a +fost pe cod VFP). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`. + +**Surse de adevar deja stabilite, citate ca atare** (nu se re-verifica aici): +`docs\cercetare\discount_in_rapoarte_si_efactura.md` (verdictul principal — niciun raport/eFactura nu +tipareste discountul, dar amandoua citesc `valdiminuatftva`/`discountftva` din cursorul de tiparire), +`docs\cercetare\discount_verificare2.md` (structura reala a coloanelor din `grd_factura` si a +`VANZARI_DETALII`). **`docs\cercetare\discount_pe_articol.md` NU e citat ca sursa** — task-ul mi-a +semnalat ca are doua afirmatii gresite, corectate deja in `discount_verificare2.md` sectiunea finala +("Ce era gresit in afirmatiile de mai sus"). + +## Verdict (10 randuri) + +S4c e implementabila cu cod nou moderat, nu cu redesign. Contractul de calcul exista deja, complet si +reciproc, in `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`) si +`frm_articol_factura.do_calculeaza_totaluri` (`:2068-2179`, delegand la functia partajata +`calculeaza_totaluri()` din `oproceduri_facturare.prg:2258-2381`) — aceasta din urma **e deja scrisa +generic, pe orice obiect cu proprietatile potrivite**, deci se poate rula direct pe un rand din +`crsfactura` (via `Scatter`/`Gather Name`), fara sa mai treaca prin dialog. Tiparul de eveniment +pentru coloane editabile de grid **exista deja in acelasi fisier**, pe alt formular din aceeasi +familie de clase (`frm_avizare_lucrare.grd_articole.cCantitate/cPret.Text1.LostFocus`, +`:6549-6562`) — `LostFocus` care cheama `Thisform.do_calculeaza_totaluri()`, nu `InteractiveChange`. +**Capcana reala e alta decat pare din titlul poveste**: exista **doua cai de scriere independente** +catre destinatii diferite, si doar una e azi corect legata. `do_scrie_articole` trimite spre Oracle +(`pack_facturare.adauga_articol_factura`, `ofacturare.vc2:14081-14083`) discountul citit **direct +din `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva`** — deci `VANZARI_DETALII.DISCOUNT_UNITAR` +**e mereu corect**, indiferent de bug, pentru ca aceste campuri sunt chiar campurile editate in grid. +Riscul e strict local, in sesiunea VFP curenta: `prelucreaza_factura` (apelata pentru tiparire/eFactura, +`ofacturare.prg:1887`) construieste cursorul de tiparire **din acelasi `crsfactura` deja in memorie**, +citind `valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` — campuri **agregate** +(pret-discount)*cantitate, care **nu se recalculeaza singure** cand se editeaza discountul brut. Deci +Oracle poate avea `DISCOUNT_UNITAR` corect si totusi factura tiparita/eFactura sa arate valoarea veche, +in aceeasi sesiune, pana la reincarcare. Solutia: coloanele noi de discount trebuie sa scrie, pe +`LostFocus`, atat campurile brute cat si campurile agregate — vezi sectiunea 4. + +--- + +## 1. Lantul complet al campurilor de discount, cu `fisier:linie` + +### 1.1 Structura cursorului local `crsfactura` + +Definit in `creeaza_facturacrs` (`COMUN\programe\ofacturare_comun.prg:1779-1782`): +``` +1779 valdiscountctva N(20,max(gnPc,4)),valdiminuatftva N(20,max(gnPc,4)),valdiminuattva N(20,max(gnPc,4)),valdiminuatctva N(20,max(gnPc,4)),proc_Tvav N(20,4),cu_tva N(1), lot C(20) null, serie c(100) Null,; +1782 vvaldiscountctva N(20,max(gnPVal,4)),vvaldiminuatftva N(20,max(gnPVal,4)),vvaldiminuattva N(20,max(gnPVal,4)),vvaldiminuatctva N(20,max(gnPVal,4)),id_set_fact N(20) Null,explicatie M Null,; +``` +(`discountftva`, `discountctva`, `vdiscountftva`, `vdiscountctva`, `valdiscountftva`, `valdiscountctva`, +`vvaldiscountftva`, `vvaldiscountctva` sunt definite pe liniile adiacente, aceeasi procedura — +nu recitate individual, tiparul `N(20,...)` e identic). + +**Nomenclatura confirmata pe cod** (nu doar dedusa din nume) — opt campuri distincte, trei niveluri: +| Camp | Nivel | Moneda | Cu/fara TVA | Sens | +|---|---|---|---|---| +| `discountftva` | pe unitate | lei | fara TVA | discount unitar, sursa in lei | +| `discountctva` | pe unitate | lei | cu TVA | discount unitar, derivat | +| `vdiscountftva` | pe unitate | valuta | fara TVA | discount unitar, sursa in valuta | +| `vdiscountctva` | pe unitate | valuta | cu TVA | discount unitar, derivat | +| `valdiscountftva`/`valdiscountctva` | pe linie (x cantitate) | lei | ambele | discount agregat lei | +| `vvaldiscountftva`/`vvaldiscountctva` | pe linie (x cantitate) | valuta | ambele | discount agregat valuta | +| `valdiminuatftva`/`valdiminuatctva` | pe linie (x cantitate) | lei | ambele | **valoare neta** (pret-discount)*cant — cea tiparita/eFactura | +| `vvaldiminuatftva`/`vvaldiminuatctva` | pe linie (x cantitate) | valuta | ambele | valoare neta in valuta | + +### 1.2 De la tastare la `crsfactura` — calea de azi (prin dialog) + +1. Operator tasteaza in dialogul `frm_articol_factura` (`ofacturare.vc2:1108-2659`), pe unul din 3 + perechi de campuri: `Clb_procent_discount.Text_simplu1` (`InteractiveChange`, `:2646`), + `Clb_discount_unitar.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2614/:2620`), + `Clb_discountctva.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2602/:2608`). +2. Toate cheama `Thisform.do_calculeaza_discount(valoare, tip)` — **`frm_articol_factura`** + (`:1874-1976`, contract detaliat in sectiunea 2) — scrie in `poArticol`: + `discount_unitar`, `discount_unitar_val`, `discount_unitar_ctva`, `discount_unitar_ctva_val`. +3. `do_calculeaza_discount` cheama la final `Thisform.do_calculeaza_totaluri()` (`:1974`, + `frm_articol_factura`, `:2068-2179`) care delega la **`calculeaza_totaluri(poArticol)`** + (`oproceduri_facturare.prg:2258-2381`, functie globala) — aceasta scrie in `poArticol`: + `valdiminuatftva`, `valdiminuattva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuattva`, + `vvaldiminuatctva`, plus `valdiscountftva/ctva`, `vvaldiscountftva/ctva`, `valftva/ctva/tva` etc. +4. La inchiderea dialogului, `do_adauga_articol` (`ofacturare.vc2:12813-13167`) scrie **tot obiectul + deja calculat** in `crsfactura`, in doua pasi: + - `Gather Name poArticol Fields Like ... valdiminuatftva, valdiminuattva, valdiminuatctva, + vvaldiminuatftva, vvaldiminuattva, vvaldiminuatctva ...` (`:12945-12950`) — campurile agregate, + **deja calculate de `calculeaza_totaluri`**, nu recalculate aici. + - `Replace ... discountftva With poArticol.discount_unitar, discountctva With + poArticol.discount_unitar_ctva, vdiscountftva With Nvl(poArticol.discount_unitar_val,0), + vdiscountctva With Nvl(poArticol.discount_unitar_ctva_val,0)` (`:12952-12957`) — campurile pe + unitate, separat, pentru ca nu sunt in lista `Gather` (doar variantele `val*` agregate sunt). +5. `do_modifica` (`:13746-13914`, editarea unei linii existente) urmeaza acelasi tipar — + `Gather`/`Replace` cu aceleasi campuri (`:13837-13881`), dupa ce redeschide dialogul pe rand. +6. **Cale suplimentara, deja existenta, cu bug documentat**: pe factura in valuta, coloana + `cVdiscountftva` (`ControlSource="vdiscountftva"`, `ofacturare.vc2:12340-12345`) e editabila + direct in grid, **fara niciun handler** — scrie `vdiscountftva` direct in `crsfactura` prin + binding-ul standard de grid, dar **nu recalculeaza** `vvaldiminuatftva`/`vvaldiminuatctva` + (confirmat cautat explicit, `discount_verificare2.md` punctul 3). + +### 1.3 De la `crsfactura` la Oracle (`VANZARI_DETALII.DISCOUNT_UNITAR`) + +`do_scrie_articole` (`ofacturare.vc2:13916-...`), la finalizarea facturii, trimite spre +`pack_facturare.adauga_articol_factura` un singur parametru de discount, ales **direct din campurile +pe unitate** ale randului curent din `crsfactura` (`:14081-14083`): +``` +14081 IIF(poArt.cu_tva = 0,; +14082 Iif(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountftva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountftva,18,gnPVal))), ; +14083 IIF(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountctva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountctva,18,gnPVal)))) +``` +**Nu foloseste `valdiminuatftva`.** Deci Oracle primeste intotdeauna valoarea curenta din +`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` — cele patru campuri pe care coloanele +noi de grid le-ar edita direct. `VANZARI_DETALII.DISCOUNT_UNITAR NUMBER(22,6)` e singura coloana +Oracle de discount (confirmat `discount_verificare2.md` punctul 4) — nu exista coloana de procent pe +Oracle. + +### 1.4 Inapoi, pentru tiparire/eFactura — `crsfacttemp` + +`prelucreaza_factura` (`ofacturare_comun.prg:1055-1059`, apelata din `ofacturare.prg:1887`) **NU +reincarca din Oracle** — primeste ca parametru **acelasi `crsfactura`** deja in memorie (cel scris la +pasii 1.2/1.3), si construieste cursorul de tiparire prin agregare SQL locala: +``` +1182 Sum(cantitate) As cantitate,pretftva-discountftva As pretftva,; +1186 Sum(valdiminuatftva) As valftva,Sum(valdiminuattva) As valtva,; +``` +(`ofacturare_comun.prg:1182-1186`, cazul comun `discount_evidentiat=0`). eFactura foloseste **acelasi +cursor de iesire** (`xmlefactura.prg:231`, `LineExtensionAmount = valftva`, `PriceAmount = pretftva`, +`xmlefactura.prg:935-937`, `:1043-1046`). + +**Consecinta directa**: `pretftva-discountftva` de la 1182 citeste `discountftva` (mereu proaspat, +pentru ca e campul editat direct), dar `Sum(valdiminuatftva)` de la 1186 citeste campul **agregat**, +care ramane vechi daca nu a fost recalculat explicit dupa editare. Rezultat: **randul tiparit poate +avea `pretftva` corect (net, recalculat corect din `discountftva`) dar `valftva` (valoarea liniei) +gresit** — o discrepanta pret x cantitate ≠ valoare, vizibila chiar pe hartie, nu doar o valoare +veche uniforma. Asta e mai grav decat "arata vechi" — arata **inconsistent**. + +--- + +## 2. Contractul `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`) + +**Semnatura**: `Lparameters tnValoare, tnTip` — `tnTip`: `1` = s-a modificat procentul, `2` = +discount in lei (fara TVA daca `preturi_cu_tva=0`, cu TVA altfel), `3` = discount in valuta. + +**Ramura principala** — `If poArticol.preturi_cu_tva = 0` (pretul de referinta e fara TVA, cazul +uzual): sursa de adevar e `discount_unitar`(_val). +- `tnTip=1` (procent -> valoare): daca `tip_valuta=0`, + `discount_unitar = Round(pretftva * procent/100, gnPPretV)`, apoi **procentul se re-normalizeaza** + din valoarea rotunjita (`:1888-1889`) — nu se pastreaza procentul brut tastat, ci cel rezultat din + rotunjire. Daca `tip_valuta=1`: `discount_unitar_val` se calculeaza intai (`gnPVal`), apoi + `discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`. +- `tnTip=2` (lei -> procent): `discount_unitar = tnValoare` direct, procentul se deriva + (`Round(discount_unitar/pretftva*100, 2)`). +- `tnTip=3` (valuta -> procent + lei): `discount_unitar_val = tnValoare`, procentul deriva din + valuta, apoi `discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`. +- **Dupa `Do Case`, neconditionat**: `discount_unitar_ctva = discount_unitar + Round(discount_unitar + * (proc_tvav-1), gnPPretV)` (`:1913-1914`) — varianta cu TVA e **mereu derivata** din cea fara TVA + in aceasta ramura, niciodata sursa. +- Daca `tip_valuta=1`, simetric: `discount_unitar_ctva_val` derivat din `discount_unitar_val`. + +**Ramura alternativa** — `Else` (`preturi_cu_tva=1`, pretul de referinta e cu TVA): rolurile se +inverseaza complet — `discount_unitar_ctva`(_val) e sursa (calculata direct din `tnValoare`/`pretctva` +in cele 3 cazuri, simetric cu ramura de mai sus), iar `discount_unitar`(_val) **fara TVA** e derivat +la final (`:1958`, `Round(discount_unitar_ctva / proc_tvav, gnPPretV)`). + +**Rotunjiri**: `gnPPretV` pentru valorile unitare in lei, `gnPVal` pentru cele in valuta, `2` fix +pentru procent — niciodata `gnPc` (precizia de linie/document) in aceasta metoda. + +**La final, neconditionat** (`:1972-1974`): `Thisform.clb_tva_discount.Refresh()`, +`Thisform.clb_pret_diminuat.Refresh()`, **`Thisform.do_calculeaza_totaluri()`** — deci orice apel +recalculeaza si campurile agregate de linie, nu doar cele pe unitate. + +**In valuta, ce ramane needitat de aceasta metoda**: campurile agregate (`valdiminuatftva` etc.) NU +sunt scrise aici — `do_calculeaza_totaluri` (sectiunea urmatoare) le calculeaza separat din +`discount_unitar`/`cantitate`. + +--- + +## 3. Starea de azi in grid — tabel + +| Coloana | `ControlSource` | Formular | `ReadOnly` | Editabil azi | Recalculeaza la editare | +|---|---|---|---|---|---| +| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole` (productie), `:12311-12317` | `.T.` explicit | Nu | — | +| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole`, `:12340-12345` | nesetat (implicit `.F.`) | **Da, pe factura in valuta** | **Nu** — niciun `Valid`/`LostFocus`/`InteractiveChange` propriu, cautat explicit in `10968-15739` | +| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole2` (prototip, neinstantiat in productie) | `.F.` explicit, `:16657` | Da (prototip) | Nesigur — prototip, nu s-a cautat handler dedicat | +| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole2` | `.F.` explicit, `:16688` | Da (prototip) | idem | +| `Column14`/`procdisc` | `procdisc` | `frm_facturare_articole2` numai, `:16721` | nesetat | scaffold, fara scriere | **Nu are corespondent Oracle, nu are `Gather`/`Replace` nicaieri in fisier** (`discount_verificare2.md` sectiunea 5) | + +**Excludere pe valuta** (`ofacturare.vc2:15269-15278`, in `frm_facturare_articole.Init`): cand +`poDate.in_valuta = 0` se elimina `cVpretFtva`, `cVdiscountftva`, `cVvaldiminuatftva`; cand +`in_valuta <> 0` se elimina `cPretFtva`, `cDiscountctva` (si simetricele lor). Deci azi, pe orice +factura, **doar una din cele doua coloane de discount e vizibila** — cea in lei, needitabila, sau cea +in valuta, editabila-dar-fara-recalcul. Nu exista azi nicio coloana de **procent** de discount in +`grd_factura` in productie (doar `discountctva`/`vdiscountftva`, valori, nu procent) — pentru procent, +azi operatorul trebuie sa deschida dialogul. + +--- + +## 4. Proiectarea + +### 4.1 Ce coloane se adauga/deschid + +Nu se sterge nimic din `crsfactura` (modelul de date ramane neschimbat, conform cerintei). Se +lucreaza cu campurile deja existente: + +- **Coloana procent discount** (noua in grid, in ambele monede) — `ControlSource` pe un camp + calculat, nu direct pe un camp Oracle (nu exista coloana Oracle de procent) — vezi 4.4 pentru + optiunea recomandata. +- **`cDiscountCTva`** (`discountctva`, lei): `Column5.ReadOnly` trece din `.T.` in `.F.` — devine + editabila, simetric cu ce azi doar prototipul `frm_facturare_articole2` face. +- **`cVdiscountftva`** (`vdiscountftva`, valuta): ramane editabila ca azi, dar castiga handler-ul + care azi lipseste. + +Se pastreaza `RemoveObject` pe valuta (`:15269-15278`) neschimbat — excluderea reciproca deja +implementeaza cerinta "se pastreaza excluderea pe `in_valuta`". + +### 4.2 Pe ce eveniment se cableaza calculul reciproc + +**Recomandare: `Text1.LostFocus`, dupa tiparul deja folosit in acest fisier pentru coloane +editabile de grid** — `frm_avizare_lucrare.grd_articole.cCantitate.Text1.LostFocus` si +`.cPret.Text1.LostFocus` (`ofacturare.vc2:6549-6562`), ambele in aceeasi clasa de baza de grid +(`_grdrow`, `_grd_base.vc2:445`) folosita si de `grd_factura`. Motivele, nu teoretice ci din cod: +- **`InteractiveChange` fireste pe fiecare tasta** — ar recalcula la fiecare caracter tastat intr-un + numar cu zecimale (comportament vazut la coloana `procent` in dialog, `:2646`, dar acolo controlul + e un camp simplu de dialog, nu o celula de grid cu re-randare de coloane vecine la fiecare tasta — + in grid ar fi vizibil costisitor si ar zgaltai focusul). + `Valid`-urile din dialog (`:2602-2624`) folosesc explicit un guard `<> nSumaNatOld`/`nSumaValOld` + ca sa nu recalculeze cand valoarea nu s-a schimbat efectiv — semnaleaza ca autorii au evitat + deliberat recalculul pe fiecare tasta chiar si la `Valid`. +- **`LostFocus` e tiparul deja validat pentru grid-uri de articole in acest fisier**, pe doua coloane + numerice diferite (cantitate, pret), amandoua cu acelasi tip de nevoie (schimbarea unei valori + declanseaza recalculul liniei si al totalului documentului). +- Grid-ul VFP nu are `Valid` pe coloana insasi in mod uzual folosit aici — handlerele existente sunt + pe `.Text1.LostFocus`, deci noile handlere trebuie sa fie + `grd_factura.cDiscountCTva.Text1.LostFocus` si `grd_factura.cVdiscountftva.Text1.LostFocus` + (plus coloana noua de procent, daca implementata ca `Text1` editabil). + +### 4.3 Rutina de recalcul — reutilizare, nu reimplementare + +`calculeaza_totaluri()` (`oproceduri_facturare.prg:2258-2381`) **e deja generica**: primeste orice +obiect (`toArticol`) cu proprietatile `preturi_cu_tva`/`discount_unitar`/`pretftva`/`cantitate`/... +(le adauga singura, prin `AddProperty`, daca lipsesc — `:2264-2272`, mapand numele de camp +`crsfactura`-stil, `discountftva`/`vdiscountftva`, pe numele `poArticol`-stil, +`discount_unitar`/`discount_unitar_val`) si scrie inapoi campurile agregate. **Poate rula direct pe +un `Scatter Name` al randului curent din `crsfactura`**, fara sa deschida dialogul: +``` +Select crsfactura +Scatter Name loArt Memo +loArt = calculeaza_totaluri(loArt) +Gather Name loArt Memo +``` +Aceasta acopera pasul "recalculeaza campurile agregate din discountul unitar deja stabilit" +(`valdiminuatftva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuatctva`, +`valdiscountftva/ctva`, `vvaldiscountftva/ctva`), **exact campurile care azi raman vechi** +(sectiunea 1.4). + +**Ce `calculeaza_totaluri()` NU face**: conversia reciproca procent<->valoare — aceea e logica din +`do_calculeaza_discount` (sectiunea 2), care azi scrie in `poArticol` si actualizeaza controale de +dialog (`Thisform.clb_*.Refresh()`) care nu exista in grid. **Recomandare**: se extrage o functie noua, +fara referinte la `Thisform.clb_*` (partea de calcul pur, liniile `:1878-1970` minus liniile de +`Refresh`), reutilizabila atat din dialog (daca dialogul de articol individual mai exista undeva — +nu la S4c, dialogul dispare) cat si din handler-ul de grid — parametrizata pe rand +(`toArticol`/`Scatter Name`) in loc de `poArticol` global. Aceasta functie noua intra la fel ca +`calculeaza_totaluri()`, in `oproceduri_facturare.prg`, ca sa fie apelabila din handler-ul de coloana +fara sa depinda de `poArticol`-ul dialogului disparut. + +### 4.4 Coloana de procent — implementare recomandata + +Nu exista coloana Oracle de procent (sectiunea 1.3) si nici coloana persistenta pe `crsfactura` +pentru asta (spre deosebire de `discountftva` etc, care sunt in `creeaza_facturacrs`). Doua optiuni: +1. **Adauga camp calculat, needitabil, alaturi de coloana editabila de valoare** — afiseaza procentul + derivat (`Round(discountftva/pretftva*100,2)`), needitabil direct — evita sa se mai adauge o + coloana Oracle noua si o cale de editare in plus (mai putin cod nou, mai putina suprafata de bug). +2. **Adauga coloana editabila de procent, needitabila-pe-model** — ca in `frm_articol_factura` + (`Clb_procent_discount`), scrie tot in `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` + prin acelasi calcul reciproc, dar camp de UI, nu de Oracle. Mai aproape de comportamentul de azi + din dialog (operatorul poate tasta fie procentul, fie valoarea), dar cere si al treilea handler de + `LostFocus` si inca un camp de lucru in cursorul local (`AddProperty` la `do_initializeaza_articol`, + dupa tiparul `id_jtva_coloana`/`valdiminuatftva`, `:13666-13681`). + +Cerinta din plan ("cele doua campuri de discount ale lui — procent si valoare unitara — devin coloane +in grid") cere explicit **ambele campuri editabile** — deci optiunea 2 e cea care respecta litera +cerintei; optiunea 1 e o simplificare de discutat cu Marius (vezi sectiunea 10). + +### 4.5 Unde se cheama exact recalculul, pas cu pas (handler propus) + +Pentru coloana `cDiscountCTva` (`discountctva`, lei, `tip=2` in nomenclatura `do_calculeaza_discount`): +``` +PROCEDURE grd_factura.cDiscountCTva.Text1.LostFocus + Select crsfactura + Scatter Name loArt Memo + * recalcul reciproc procent<->valoare, tip=2 -- functia noua din 4.3, nu do_calculeaza_discount + loArt = recalc_discount_linie(loArt, This.Value, 2) && scrie discountftva/discountctva(_val) + loArt = calculeaza_totaluri(loArt) && scrie valdiminuat*/vvaldiminuat* + Gather Name loArt Memo + Thisform.do_calculeaza_totaluri() && resumeaza totalurile documentului +ENDPROC +``` +Simetric pentru `cVdiscountftva` (`tip=3`) si pentru coloana de procent, daca implementata editabil +(`tip=1`). Guard-ul `<> valoare veche` (ca la `Valid`-urile din dialog, `:2602-2624`) se pastreaza ca +sa nu se recalculeze la simplu tab-through fara modificare. + +--- + +## 5. Interactiunea cu discountul din politica de pret + +**Azi**: `do_initializeaza_articol` (`ofacturare.vc2:13618` si urm.) preia +`toArticol.discount_unitar = Nvl(toArticol.discount_unitar, 0)` (`:13630`) **din `crsarticole`** +(cursorul de stoc/oferta, populat inainte de deschiderea dialogului — sursa SQL exacta, in afara +`ofacturare.vc2`, ramasa necercetata si in raportul precedent). Daca politica de pret a populat deja +un discount, acela apare **preincarcat** in campurile dialogului (`Clb_discount_unitar` etc.), +operatorul il poate suprascrie tastand — suprascrierea intra prin acelasi `do_calculeaza_discount` +ca orice alta tastare, fara distinctie intre "valoare din politica" si "valoare tastata manual". + +**Dupa S4c**: nu se schimba nimic in mecanismul de preincarcare — `do_adauga_articol` inca scrie +`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` in `crsfactura` la adaugarea liniei +(sectiunea 1.2, pasul 4), inainte ca operatorul sa apuce sa editeze coloana de grid. Valoarea din +politica **ramane vizibila in celula**, exact ca azi in dialog, iar editarea manuala in grid o +suprascrie la fel — singura diferenta e ca suprascrierea se intampla acum pe `LostFocus` de celula, +nu pe `Valid` de camp de dialog. **Nu exista azi o distinctie de tip "flag discount din politica vs. +discount manual"** in `crsfactura` (nu am gasit un camp `discount_din_politica` sau similar) — deci +S4c nu pierde nicio informatie care exista deja, dar nici nu castiga vreo trasabilitate noua. + +--- + +## 6. Refacerea totalurilor documentului + +Totalurile documentului (`Thisform.nbazaron`, `ntotalron`, `ndiscron`, variantele `*val`) se +recalculeaza in `frm_facturare_articole.do_calculeaza_totaluri` (`ofacturare.vc2:13423-13520`) — +**re-sumeaza direct din `crsfactura`** (si `crsfacturaset` daca exista), citind exact campurile +agregate din sectiunea 1.1/1.4: +``` +13456 Select Sum(Nvl(valdiminuatctva,0)) As Total,Sum(Nvl(valdiminuatftva,0)) As Baza,; +13457 Sum(Nvl(valdiminuattva,0)) As Tva,; +13458 Sum(Nvl(valdiscountftva,0)) As discount From crsfactura Into Cursor crstotalurifact +``` +Simetric pentru valuta (`vvaldiminuat*`) mai jos in aceeasi procedura. **Consecinta directa pentru +proiectare**: daca handler-ul de coloana (sectiunea 4.5) actualizeaza corect `valdiminuatftva`/ +`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva`/`valdiscountftva`/`vvaldiscountftva` pe randul +editat **inainte** de a chema `Thisform.do_calculeaza_totaluri()`, totalurile documentului se refac +automat, corect, din acelasi mecanism care azi refece totalurile la adaugare/stergere de linie +(`do_adauga_articol:13160`, `do_sterge:14676`) — **nu trebuie cod nou pentru pasul de resumare**, doar +apelul, la finalul handler-ului de coloana. `Thisform.do_calculeaza_totaluri` e deja legat prin +`Bindevent` de `actualizeaza_total_mod` (`:15265`) — orice apel al lui declanseaza si actualizarea de +ecran a etichetelor de total, fara cablaj suplimentar. + +--- + +## 7. Pasi de implementare, ordonati, cu criteriu de "gata" + +1. **Extrage functia de calcul reciproc** din `frm_articol_factura.do_calculeaza_discount` + (`:1874-1976`), fara liniile de `Thisform.clb_*`/`Thisform.nprocent`, ca functie noua in + `oproceduri_facturare.prg`, parametrizata pe obiect + valoare + tip (semnatura similara cu + `calculeaza_totaluri(toArticol)`). + *Gata cand*: pentru fiecare din cele 3 `tnTip` si ambele ramuri `preturi_cu_tva`, functia noua + produce exact aceleasi `discount_unitar`/`discount_unitar_ctva`/`_val` ca metoda originala, testat + pe acelasi set de intrari (procent, lei, valuta) — comparatie directa, nu doar citire de cod. +2. **Deschide `Column5`/`cDiscountCTva` la editare** (`ReadOnly = .F.`, + `ofacturare.vc2:12311-12317`), pastrand `RemoveObject` pe valuta neschimbat. + *Gata cand*: pe factura in lei, celula de discount unitar cu TVA e editabila din grid (nu doar + afisata). +3. **Adauga handler `Text1.LostFocus` pe `cDiscountCTva` si pe `cVdiscountftva`**, dupa modelul din + sectiunea 4.5: recalcul reciproc (pasul 1) + `calculeaza_totaluri()` (deja existent, + `oproceduri_facturare.prg:2258`) + `Gather` + `Thisform.do_calculeaza_totaluri()`. + *Gata cand*: editarea oricareia din cele doua coloane schimba, in acelasi moment, si + `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` **si** + `valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` pe randul curent, + verificat prin `Browse`/inspectie cursor, nu doar pe ecran. +4. **Adauga coloana de procent** (decizie 4.4 de confirmat cu Marius — recomandare: optiunea 2, + editabila, ca sa respecte litera cerintei din plan), cu acelasi handler, `tip=1`. + *Gata cand*: tastarea unui procent produce aceeasi valoare de discount ca tastarea valorii + echivalente, pe ambele monede. +5. **Verifica totalurile documentului** dupa editare in grid — nu ar trebui sa fie nevoie de cod nou + (sectiunea 6), doar de apelul din pasul 3. + *Gata cand*: dupa editarea discountului pe o linie, `Thisform.nbazaron`/`ntotalron` (si variantele + `val`) se schimba imediat, fara sa fie nevoie de o alta actiune (adaugare/stergere de linie) care + sa le forteze. +6. **Verifica scrierea in Oracle** (`do_scrie_articole`, `:14081-14083`) — nu ar trebui sa fie nevoie + de nicio schimbare, pentru ca citeste deja campurile pe unitate direct (sectiunea 1.3). + *Gata cand*: `VANZARI_DETALII.DISCOUNT_UNITAR` dupa salvare = valoarea tastata in grid, pe o + factura testata cu discount editat exclusiv din grid (fara sa fi trecut prin dialog, care oricum + dispare la unificare). +7. **Verifica tiparirea si eFactura** — cea mai importanta proba, cea ceruta explicit de plan. + *Gata cand*: vezi sectiunea 8. + +--- + +## 8. Cum se verifica — concret + +**Pasii**, pe o factura de test (lei si separat valuta): +1. Adauga o linie in grid (fara discount). +2. Editeaza direct in grid coloana de discount (valoare, apoi pe alt rand procent) — verifica pe + ecran ca celelalte coloane afisate (`cPretFtva`/`cVpretftva`, `cValdiminuatctva`/`cVvaldiminuatftva` + daca ramase vizibile) se actualizeaza imediat. +3. **Interogheaza direct cursorul** (nu doar ecranul) — din Command Window/breakpoint, pe randul + editat: `? discountftva, discountctva, vdiscountftva, vdiscountctva, valdiminuatftva, + valdiminuatctva, vvaldiminuatftva, vvaldiminuatctva` — toate opt trebuie sa reflecte editarea, nu + doar cele patru "brute". +4. **Finalizeaza factura** (`do_scrie_articole`) si interogheaza Oracle (schema `MARIUSM_AUTO`, + conventia din `COMUN\docs\scripturi-migrare-db.md:36-40`, prin `goExecutor`, doar `SELECT`): + ```sql + SELECT discount_unitar FROM vanzari_detalii WHERE id_vanzare = :id_factura AND id_articol = :id_articol; + ``` + trebuie sa fie egal cu valoarea tastata in grid. +5. **Retipareste factura** (nu doar priveste pe ecranul de compunere — deschide efectiv raportul, + `factura.fr2` sau echivalentul in lei/valuta) si verifica pe pagina tiparita ca `pretftva`/`valftva` + pe linia editata reflecta discountul nou (pretul unitar net si valoarea liniei trebuie sa fie + consistente intre ele: `valftva = pretftva * cantitate`, altfel exact bug-ul din sectiunea 1.4). +6. **Genereaza XML-ul de eFactura** pentru aceeasi factura si verifica in fisier + `cbc:LineExtensionAmount`/`cbc:PriceAmount` pe linia editata — trebuie sa corespunda cu pretul net + nou, nu cu cel dinaintea editarii. +7. Repeta 1-6 pe **factura in valuta**, ca sa acoperi ramura `vdiscountftva`/`vvaldiminuatftva` + (ramura azi cu bug-ul confirmat). + +--- + +## 9. Ce nu se poate testa headless + +- **Editarea propriu-zisa a celulei de grid** (tastare in `Text1` al unei coloane, tab/enter pentru + `LostFocus`) — headless-ul VFP (`-A -T`) nu materializeaza interactiunea de tastatura intr-un grid; + cf. `docs\cercetare\...grid-coloane-nu-se-materializeaza-headless` (memorie de proiect) — coloanele + de grid sunt artefacte needitabile sub `-A -T`; verificarea reala a comportamentului UI cere + harness-ul cu UI vizibil sau un test manual asistat. +- **Tiparirea efectiva a raportului** (`.frx`) — generarea unui PDF/preview real, nu doar constructia + cursorului `crsfacttemp` in memorie, cere motorul de raportare VFP, care nu ruleaza util headless + pentru verificare vizuala (se poate verifica campurile cursorului sursa, dar nu pagina tiparita). +- **Trimiterea efectiva catre ANAF** (SPV) — se poate genera si inspecta XML-ul local, dar validarea + reala (acceptare/respingere) cere mediul de test ANAF, in afara acestei sarcini. +- **Comportamentul focus/tab-order al noilor coloane** in grid (ordinea de tab intre celule, daca + `LostFocus` se declanseaza corect la navigare cu tastatura vs. mouse) — cere sesiune interactiva. + +--- + +## 10. Riscuri si ce ramane de decis de Marius + +- **Coloana de procent — editabila sau doar afisata** (sectiunea 4.4). Recomandare: editabila + (optiunea 2), pentru ca respecta litera deciziei din plan ("cele doua campuri... devin coloane"), dar + costa un handler si un camp de lucru in plus. Daca simplitatea conteaza mai mult decat paritatea cu + dialogul vechi, optiunea 1 (doar afisaj) reduce suprafata de cod fara sa piarda functionalitate reala + (operatorul tot poate obtine orice discount tastand valoarea). +- **Riscul de rotunjire in lant**: `do_calculeaza_discount` are cel putin 3 rotunjiri succesive pe + drumul procent->valoare->procent (sectiunea 2) — comportament deja existent, nu introdus de S4c, dar + mutarea in grid (editare mai frecventa, rand cu rand, fara sa mai treaca prin "confirmare" de + dialog) ar putea face vizibile discrepante mici de rotunjire care azi treceau neobservate. Nu e un + motiv sa se schimbe rotunjirile (ar strica alte fluxuri), doar un risc de UX de semnalat. + **Zero cazuri gasite in cod care sa demonstreze deja o problema** — semnalat preventiv, nu confirmat. +- **`Gather`/`Scatter` pe rand cu campuri MEMO**: `do_adauga_articol` foloseste `Gather ... MEMO` + (`:12945-12950`) — handler-ul nou de coloana (sectiunea 4.5) trebuie sa faca la fel + (`Scatter Name ... Memo` / `Gather Name ... Memo`), altfel campul `explicatie` (M) s-ar putea goli + la fiecare editare de discount — **de verificat explicit la implementare**, nu doar presupus. + Recomand un test dedicat: editeaza discountul pe o linie cu `explicatie` populata, verifica ca + `explicatie` ramane neschimbata dupa `LostFocus`. +- **Coloana `procdisc` din prototip** (`frm_facturare_articole2`, `:16721`) — scaffold mort, fara + scriere (sectiunea 3). Recomandare: nu se reutilizeaza ca atare pentru coloana noua de procent — + se porneste curat, cu functia noua din sectiunea 4.3, nu cu acest camp neconectat. +- **Formularul `frm_facturare_articole2`** ramane prototip separat, ne-instantiat in productie — S4c + nu are nevoie sa il atinga, dar daca exista intentia sa devina formularul unificat, coloanele lui + de discount (deja editabile, `:16657`/`:16688`) ar trebui auditate separat pentru acelasi bug de + recalcul (nu verificat aici — in afara perimetrului cerut). + +--- + +## 11. Punct deschis din S1 — `_checkbox1` vs `chkDetaliat` + +Nu apartine povestii S4c (nu am gasit nicio legatura cu discountul pe linie sau `grd_factura`), dar +am dat peste ambele controale pe cale laterala, in `frm_alte_date` (`ferestre_cere_date.vc2`), asa ca +inchid ieftin: **sunt doua controale distincte, nu o duplicare de nume**. +- `_checkbox1` e un control generic (mostenit din clasa de baza), folosit in `frm_alte_date.Init` + (`ferestre_cere_date.vc2:3188-3202`) doar ca **reper de layout** — pozitia lui determina inaltimea + ferestrei si offset-ul altor controale, fara logica de business proprie vizibila in acest formular. +- `chkDetaliat` e un checkbox cu nume propriu, legat de fluxul de incasare (`actualizeaza_tipincasare`, + `:2728-2853`) — vizibil doar cand `opt_incasat` indica un anumit tip de incasare (POS/detaliat), + ascuns/zero altfel; pe ramura `Else` a lui `Init` (`:3187-3205`, cand alt tip de context nu implica + incasare deloc) e **eliminat explicit** din formular (`Thisform.RemoveObject('chkDetaliat')`, + `:3201`), spre deosebire de `_checkbox1`, care ramane (e folosit chiar pe linia urmatoare pentru + calculul inaltimii finale, `:3202`). + +**Concluzie**: nu e o inconsecventa de cod, sunt doua controale cu roluri diferite pe acelasi +formular — punctul se poate inchide ca "nu e bug", cu rezerva ca n-am cercetat *de ce* `chkDetaliat` +exista ca si `checkbox` separat de `opt_incasat` (ce reprezinta exact "detaliat") — nu era in +perimetrul S4c si nu am aprofundat. + +--- + +## Necunoscute ramase (mostenite din rapoartele-sursa, nu re-investigate aici) + +- Interogarea SQL exacta care populeaza `crsarticole` cu discountul din politica de pret + (`CRM_POLITICI_PRET_ART`) — cod in afara `ofacturare.vc2`, netrasat (mostenit din + `discount_verificare2.md`, sectiunea "Necunoscute ramase"). +- Valorile implicite ale flag-urilor `gnEFACTURA_XML_DISC_PLISTA_LINIE`/`_ART` (mostenit din + `discount_in_rapoarte_si_efactura.md`). +- Comportamentul `crsfacturafinalaval` (varianta valuta a cursorului final de tiparire) — presupus + simetric cu varianta lei, nu reverificat linie cu linie separat pentru S4c. diff --git a/docs/cercetare/s4d_zi_curs_reactiv.md b/docs/cercetare/s4d_zi_curs_reactiv.md new file mode 100644 index 0000000..848b15e --- /dev/null +++ b/docs/cercetare/s4d_zi_curs_reactiv.md @@ -0,0 +1,429 @@ +# Proiectare S4d — Data cursului valutar, numai cand are sens (reactiv) + +Poveste: `docs\plan_13_unificare_formular_facturare.md`, `#### S4d` (decizia 15, proiectata in +sectiunea M). Cercetare preliminara deja facuta si citata ca sursa de adevar: +`docs\cercetare\zi_curs_validare.md`, `COMUN\docs\cercetare\valuta_si_curs.md`, +`docs\cercetare\rec_dec42_proiectare.md`/`rec_d42_efactura.md` (decizia 42), +`docs\cercetare\s3_portare_antet.md` (bug #16), `docs\cercetare\s4_cautare_articole_server.md`/ +`s4_puncte_deschise.md` (decizia 42, mecanismul ei exact). Read-only: nicio editare de cod, niciun +`git_sync.ps1`/`txt2vcx.ps1`, nicio scriere pe Oracle. + +--- + +## Verdict (rezumat) + +Regula reactiva se poate implementa fara sa strice nimic din ce e deja demonstrat sigur: implicitul +`poDate.zi_curs` ramane neconditionat (nu se schimba), iar cheia reactivitatii e un camp deja prezent +in cursorul de grid — `crsfactura.tip_valuta` — verificabil cu exact acelasi tipar SQL folosit deja in +cod (`ofacturare.prg:1656`, `:1730`: `Select Distinct ... From crsfactura Where tip_valuta = 1`). +Formularul-prototip `frm_facturare_articole2` are deja, azi, un `Clb_zi_curs` propriu, editabil, legat +la `poDate.zi_curs` (`ofacturare.vc2:16285-16305`) — nu trebuie inventat un control nou, ci reevaluata +vizibilitatea celui care exista deja. Mesajul Oracle `-20005` **contine deja** data si numele valutei +lipsa (`STRINGAGG` peste toate valutele fara curs) — decizia 42 nu schimba *continutul* mesajului, ii +schimba **domeniul**: il restrange de la "toate valutele din listele de preturi ale utilizatorului" la +"valuta articolului cautat", dar **doar pe varianta filtrata a cautarii** (S4 punctul 1); pe caile cu +document sursa (comanda/aviz/contract) incarcarea in masa ramane neconditionata. Plan sectiunea M +(`:2635-2638`) cere explicit ca la aceasta eroare campul sa **revina vizibil** — asta intra ca pas de +implementare, nu ca optiune. Bug-ul #16 **nu e in perimetrul S4d** — e deja cercetat si legat de S2 +(bucla de reincercare din `ofacturare.prg`), cu verdict separat in `s3_portare_antet.md`; S4d doar +**confirma** ca noua arhitectura (antet persistent, nu recreat) ii inlatura mecanismul, daca S2/S3 nu +recreeaza formularul la eroare. + +--- + +## 1. Inventarul controalelor de valuta si curs (`fisier:linie`) + +Patru definitii `Clb_zi_curs` in tot `ofacturare.vc2` (control compus `clb_tx_data`, camp text legat +la `poDate.zi_curs`), confirmate exhaustiv (`grep "ADD OBJECT 'Clb_zi_curs'"`): + +| Formular | Definitie | Eliminat azi? | Validare la Termina | Sincronizare cu data | +|---|---|---|---|---| +| `frm_date_aviz` | `ofacturare.vc2:6727-6740` | Niciodata | Nu exista (fara `inainte_de_do_termin` care sa o ceara) | `Clb_dataact...LostFocus` (`:7603-7604`), `Clb_dataireg...LostFocus` (`:7610-7611`) — `Thisform.clb_zi_curs.Refresh()`, neconditionat | +| `frm_date_aviz_lucrare` | `:7787-7801` | Niciodata | **Neconditionata**: `:8076` `Case Empty(Nvl(poDate.zi_curs,{}))` -> `:8078 This.clb_zi_curs.SetFocus()`, `plReturn=.F.` | `:8186-8187`, `:8193-8194`, neconditionat | +| `frm_date_factura` | `:8701-8714` | **Doar** tip 8/9 (retur), `Init` `:9717-9722` (`RemoveObject`) | Conditionata: `:9484` `Case poDate.in_valuta=1 And Empty(...) And Type('thisform.clb_zi_curs.visible')<>'U'` -> `:9488 SetFocus` | `:9805-9808`, `:9824-9827`, ambele cu garda `Type(...)<>'U'` | +| `frm_facturare_articole2` (prototip) | `:16285-16305`, camp text legat la `poDate.zi_curs` (`TEXT_SIMPLU1.ControlSource`) | **Niciodata** — `Init` (`:18988-19080`) nu contine niciun `RemoveObject('clb_zi_curs')` | Nu exista (formularul de articole nu are `inainte_de_do_termin` propriu de tip antet) | `Clb_dataact.TEXT_SIMPLU1.LostFocus` (`:19219-19222`) — acelasi tipar cu garda `Type(...)<>'U'` | + +Alte controale de valuta/curs, relevante ca sa nu fie confundate cu `clb_zi_curs`: + +- **`Ct_clb_valuta`** (selector de valuta document) — eliminat pe `frm_date_factura` cand + `poDate.in_valuta=0` (`:9725-9728`), **neconditionat de tip**. Confirma ca "ascunde cand nu are sens" + e deja un tipar folosit in aceeasi metoda, la doar 3 linii distanta de blocul retur — dovada directa + ca autorul stia sa faca exact acest lucru, doar nu l-a aplicat si campului de curs. +- **`lb_cursuri` + `grd_cursuri`** (eticheta si grid read-only "Curs valutar (data)") — + `frm_facturare_articole.Init` (`:15092-15100`) si `frm_facturare_articole2.Init` (`:19000-19012`): + ``` + ofacturare.vc2:19000-19012 + If !Used('crscursuri') Or Reccount('crscursuri') = 0 + Thisform.RemoveObject('lb_cursuri') + Thisform.RemoveObject('grd_cursuri') + Else + If !Empty(Nvl(poDate.zi_curs, {})) + Thisform.lb_cursuri.Caption = "Curs valutar " + Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "") + ... + ``` + Acesta e **cel mai apropiat precedent de "arata/ascunde in functie de continut"** din codul existent + — dar evalueaza `crscursuri` (cursurile incarcate pentru toate valutele din listele de preturi ale + utilizatorului), **o singura data, la `Init`**, inainte ca userul sa fi adaugat vreo linie pe grid. + **Nu e reactiv** la adaugare/stergere de articole — e evaluat o singura data pe un cursor diferit de + `crsfactura`. Nu se poate copia ca atare pentru S4d; poate fi reutilizat doar ca **idiom** (verificare + `Reccount(...) > 0` care decide `RemoveObject`/reafisare), mutat pe alt cursor si alt eveniment + (punctul 2). +- **`poArticol.tip_valuta`** — proprietate a politicii de pret a articolului (nu a documentului), + populata la `do_initializeaza_articol`/cautare, citita masiv in calculul de pret/discount + (`ofacturare.vc2:1857-2288`, `frm_articol_factura`). Cand articolul e adaugat pe grid, valoarea + ajunge in coloana `crsfactura.tip_valuta` (vezi punctul 2) — acesta e semnalul pe care se construieste + conditia reactiva, nu `poArticol` (obiect temporar, mort dupa `Release`). + +**Cine citeste `poDate.zi_curs` dupa formular** (relevant ca sa nu se rupa nimic la ascundere) — deja +documentat exhaustiv in `zi_curs_validare.md` §4 si `valuta_si_curs.md` §4: cursoarele de articole +(`cursor_preturi`/`cursor_articole_k`/`cursor_gestiune`/`cursor_lucrare`, `ofacturare.prg:266-308`), +eticheta `lb_cursuri`, si validarile de mai sus. Nimic din acest tabel se schimba prin S4d. + +--- + +## 2. Conditia reactiva, exact + +**Camp folosit:** `crsfactura.tip_valuta` (N(1)), definit la creearea cursorului de grid, +`creeaza_facturacrs` (`COMUN\programe\ofacturare_comun.prg:1776`), populat la fiecare linie adaugata +prin `do_adauga_articol` (coloana e in lista de `Insert`/`Scatter`, `ofacturare.vc2:12950`, +`:17258` pentru `frm_facturare_articole2`) direct din `poArticol.tip_valuta`. + +**Verificare, cu exact acelasi tipar deja folosit in productie** (nu inventat): + +``` +ofacturare.prg:1656, :1730 [listeaza_ofacturare] +Select Distinct nume_val, Curs, multiplicator From crsfactura Where tip_valuta = 1 +``` + +Pentru S4d, verificarea de vizibilitate se reduce la: + +```foxpro +lVizibil = !Inlist(poDate.tip, 8, 9) And ; + (poDate.in_valuta = 1 Or (Used('crsfactura') And Reccount('crsfactura', 1) > 0) ) +``` + +unde `Reccount('crsfactura', 1)` inseamna "exista macar un rand cu `tip_valuta=1`" — in practica se +scrie ca `Calculate Cnt() To lnLinii For tip_valuta=1` sau `Select Count(*) From crsfactura Where +tip_valuta=1 Into Array laCnt`, ca sa nu depinda de pozitia recordului curent din grid. + +**Cand se reevalueaza — pe evenimentul deja existent de recalcul, nu pe un timer nou.** Atat +`do_adauga_articol` (`:17124-17393`) cat si `do_sterge` (`:18619-18704`) se termina cu apelul +**`Thisform.do_calculeaza_totaluri()`** (`:17360`, `:18687`) — acesta e deja punctul unic prin care +formularul "stie" ca s-a schimbat compozitia liniilor (totaluri, discount pe articole etc). E locul +natural unde se adauga si reevaluarea `clb_zi_curs`: dupa fiecare adaugare de linie **si** dupa fiecare +stergere, `do_calculeaza_totaluri` (sau un apel adaugat imediat dupa el in cele doua metode) reface +`lVizibil` de mai sus si seteaza `Thisform.clb_zi_curs.Visible = lVizibil`. + +**Modificarea unei linii existente** (schimbare cantitate/pret pe o linie deja adaugata) nu schimba +`tip_valuta` — acesta e o proprietate a politicii de pret, fixata la adaugare, nu editabila pe grid. +Deci nu exista eveniment suplimentar de "editare linie" care sa afecteze conditia — doar adaugare si +stergere pot muta numarul de linii cu `tip_valuta=1` de la 0 la >0 sau invers. (Verificat: nu exista +`do_modifica` separat pe `frm_facturare_articole2` in indexul de simboluri — editarea unei linii merge +prin acelasi `do_adauga_articol`/dialog, care oricum recheama `do_calculeaza_totaluri`.) + +**De ce nu `poDate.in_valuta`-style (o singura evaluare la Init):** documentul poate incepe fara nicio +linie in valuta si poate primi una la mijlocul sesiunii de facturare — exact cazul pe care unificarea +il face posibil de rezolvat (azi antetul se inchide inainte sa existe `crsfactura`). Evaluarea trebuie +sa fie pe eveniment, nu pe `Init`. + +--- + +## 3. Trecerea inapoi — ultimul articol in valuta e sters + +**Recomandare: ascunde din nou (simetric), dar NU goli `poDate.zi_curs`.** + +Argumente pe tiparul deja folosit, nu pe teorie: + +- `lb_cursuri`/`grd_cursuri` (punctul 1) folosesc deja idiomul "vizibil doar cand `Reccount(...) > 0`" + — o conditie booleana simpla, reevaluabila oricand fara efecte laterale, pentru ca `RemoveObject`/ + re-adaugare (sau `Visible=`) nu ating proprietatea de date din spate (`poDate.zi_curs`). Simetria + (ascunde la 0, arata la >0) e deja tratata ca stare normala pentru acel control, nu ca o exceptie de + construit. +- `poDate.zi_curs` **nu se goleste niciodata** in tot codul existent (confirmat exhaustiv in + `zi_curs_validare.md` §5-6) — nici la `RemoveObject` (tip 8/9), nici la ascunderea `lb_cursuri`. Deci + ascunderea campului de curs cand ultima linie in valuta dispare **nu pierde valoarea**: daca userul + adauga din nou o linie in valuta in aceeasi sesiune, campul reapare cu **aceeasi data** pe care o avea + inainte de ascundere (fie cea introdusa manual, fie implicitul de la `Init`), nu resetata la azi. +- Alternativa ("ramane vizibil o data aratat") ar introduce un tip nou de stare per-sesiune + (`lFostVizibilCandva`) fara niciun precedent in cod si fara beneficiu clar — userul care sterge + singura linie in valuta de pe o factura in lei nu mai are, de fapt, niciun motiv sa vada/editeze + cursul; a lasa campul vizibil ar fi exact inconsistenta pe care decizia 15 vrea sa o elimine. + +**Exceptie de la simetrie, ceruta de plan (sectiunea M, `:2635-2638`):** cand vine eroarea Oracle +`-20005` cu campul ascuns, campul **trebuie adus inapoi vizibil**, indiferent de `Reccount`-ul curent — +vezi punctul 5. Acesta e singurul caz in care regula reactiva simetrica se suspenda explicit. + +--- + +## 4. Interactiunea cu `in_valuta` la nivel de document + +`poDate.in_valuta` e stabilit o singura data, in `Init`, exclusiv din `tnTip`/`tnIdSet` +(`ofacturare_comun.prg:248-250`, confirmat exhaustiv in `valuta_si_curs.md` §1) — **nu exista cale de +cod care sa-l schimbe dupa Init**, iar controlul de alegere a valutei (`Ct_clb_valuta`) e chiar +eliminat din formular cand `in_valuta=0` (`ofacturare.vc2:9725-9728`), deci operatorul nu are de unde +sa-l aleaga manual. Prin urmare **intrebarea "ce se intampla daca operatorul schimba valuta documentului +dupa ce a adaugat linii" nu se poate pune in arhitectura actuala** — nu exista control care sa permita +acea schimbare. Documentul e in valuta sau nu inca de la alegerea tipului (`tnTip`), inainte ca vreo +linie sa existe. + +Pentru S4d, consecinta e simpla: `poDate.in_valuta=1` e o conditie **statica** pe toata durata sesiunii +de facturare — cand e adevarata, `clb_zi_curs` ramane vizibil necontenit (ca azi), indiferent de ce se +intampla pe grid; conditia reactiva descrisa la punctul 2 conteaza **doar** pentru ramura +`in_valuta=0`. Nu exista tranzitie `in_valuta 0->1` sau `1->0` de tratat. + +--- + +## 5. Mesajul de eroare `-20005` + +### 5.1 Unde se ridica azi + +`pack_facturare.verifica_cursuri_valute` (Oracle, `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16247-16274`): + +```sql +16268 IF V_NUME_VALUTE IS NOT NULL THEN +16269 RAISE_APPLICATION_ERROR(-20005, +16270 'Nu este setat cursul din data de ' || +16271 to_char(V_DATA_CURS, 'DD/MM/YYYY') || +16272 ' pentru ' || V_NUME_VALUTE || '!'); +16273 END IF; +``` + +`V_NUME_VALUTE` e un `STRINGAGG` (`:16252-16266`) peste **toate** valutele din +`FACT_VPRETURI_UTILIZATOR` ale utilizatorului curent care nu au curs valabil la `V_DATA_CURS` — exclude +doar moneda nationala. **Mesajul contine deja data si numele valutei/valutelor** — nu e un cod generic. +Apelata neconditionat din `cursor_preturi` (`:2153`), si delegat din `cursor_contract` (jumatatea +`crsarticole`, `s4_cautare_articole_server.md:83-85`). + +### 5.2 Traseul pana la operator, azi + +``` +ofacturare.prg:313-317 (identic ofacturare.prg:828-832, factureaza2) +If lnSucces < 0 + AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare") + If goExecutor.nEroare = 20005 + vizualizeaza_curs(poDate.zi_curs) + ENDIF +``` + +`goExecutor.oPrelucrareEroare()` (`COMUN\programe\oproceduri_comune.prg:626-639`) extrage **textul +brut** dintre `ORA-20xxx:` si urmatorul `ORA` din mesajul Oracle — pentru erori in intervalul +20000-20999 (cazul de aici), operatorul vede **exact** textul PL/SQL de mai sus, netrunchiat, nemodificat. +Deci raspunsul la "mesajul spune care valuta si ce zi lipsesc" e: **da, deja o face**, azi, inainte de +orice modificare S4d. + +### 5.3 Ce schimba decizia 42 + +Decizia 42 (plan `:512-516`) **nu schimba textul** mesajului — schimba **domeniul de valute verificate**, +si **doar pe varianta filtrata a cautarii** (S4 punctul 1, `cursor_preturi` chemat per-articol-cautat, +nu la incarcarea in masa). Azi (fara decizia 42), pe orice apel al lui `cursor_preturi`, +`V_NUME_VALUTE` poate include valute complet nelegate de articolul pe care userul tocmai il cauta — +de exemplu userul cauta un articol in RON, dar mesajul ii spune ca lipseste cursul pentru EUR, pentru +ca EUR apare undeva in politicile lui de pret. Dupa decizia 42, pe cautarea filtrata, verificarea se +restrange la valuta randului adus — deci mesajul (acelasi format text) va numi, natural, **doar +valuta relevanta pentru cautarea curenta**, nu un agregat strain. + +**Rezerva importanta, de citit impreuna cu S4d:** decizia 42 se aplica explicit "pe varianta filtrata +a cursoarelor" — adica pe calea noua de cautare per-articol introdusa de S4 punctul 1, folosita azi +doar pe **ramura de lista de preturi** (`s4_cautare_articole_server.md:68-69`: "S4 se aplica curat doar +pe ramurile de lista de preturi"). Pe **comanda / aviz / contract**, incarcarea in masa a articolelor +(bookkeeping obligatoriu, `crsarticole` citit si scris de `do_adauga_tot`/`do_sterge`/`do_scrie_factura`) +**ramane neschimbata**, deci `verifica_cursuri_valute` tot ruleaza neconditionat (toate valutele din +politicile utilizatorului), la incarcarea initiala a grilei — nu doar pe articolul cautat. **Pe aceste +tipuri, mesajul poate inca numi o valuta care n-are legatura cu documentul curent**, indiferent de +decizia 42 — asta ramane o diferenta reala fata de "campul e ascuns pentru ca documentul nu are nevoie +de curs", si intra la riscuri (punctul 10). + +### 5.4 Ce se schimba prin S4d — nu textul, ci vizibilitatea campului dupa eroare + +Cerinta explicita din plan, sectiunea M (`:2635-2638`): + +> Ascunderea datei de curs poate lasa un document fara curs corectabil [...] utilizatorul nu mai are +> unde sa corecteze data daca i-am ascuns campul. **Regula din M trebuie sa aduca inapoi campul in +> exact acest caz.** + +Implementare propusa, la punctul de interceptare deja existent: + +```foxpro +* ofacturare.prg:313-317 (si simetric la :828-832) +If lnSucces < 0 + AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare") + If goExecutor.nEroare = 20005 + If Type('thisform.clb_zi_curs.visible')<>'U' And !thisform.clb_zi_curs.Visible + thisform.clb_zi_curs.Visible = .T. && aduce campul inapoi, indiferent de conditia reactiva + Endif + vizualizeaza_curs(poDate.zi_curs) + ENDIF +``` + +Nota: `thisform` aici nu e formularul de antet (deja inchis la acest punct in arhitectura veche) — e +motivul pentru care acest fix e legat de rezultatul S3/S2 asupra bug-ului #16 (punctul 6): daca +antetul unificat ramane deschis/persistent (nu recreat), `thisform.clb_zi_curs` exista si poate fi +readus vizibil direct; daca arhitectura tot recreaza un formular nou dupa eroare, readucerea vizibila +trebuie facuta in `Init`-ul noii instante, verificand acelasi semnal (`goExecutor.nEroare=20005` din +iteratia anterioara, sau un flag explicit propagat). + +**Textul mesajului propus de pastrat neschimbat** (deja corect): *"Nu este setat cursul din data de +DD/MM/YYYY pentru !"*. Nu se propune inlocuirea lui — se propune doar sa nu mai fie surprinzator +faptul ca operatorul nu are unde sa corecteze, prin readucerea campului. + +--- + +## 6. Verificarea daca #16 dispare + +**Ce e #16:** `COMUN\docs\todos.txt:45` — "la revenire din formularul de curs valutar, focusul revine +[...] pe TIP DOCUMENT, si la iesire din serie se regenereaza numar act". Citat si in plan +`:1926-1927`, `:2665-2668`. + +**Deja cercetat, cu verdict**, in `docs\cercetare\s3_portare_antet.md` §6 (runda 9), **inainte** de +aceasta sesiune — S4d nu redeschide cercetarea, o **confirma si o leaga** de domeniul propriu (eroarea +`-20005`, singurul declansator relevant pentru S4d): + +- **#16 nu e un bug de focus.** E o bucla de reincercare (`ofacturare.prg:174-571`, `Do While + lnRaspuns=6`) care trateaza **orice** esec Oracle la incarcarea cursorului de articole ca pe un "DA, + utilizatorul vrea alt document" — `lnRaspuns` nu se reseteaza pe ramura de eroare + (`ofacturare.prg:555-564`, in `Else`, niciodata atinsa cand `lnSucces<0`), deci bucla externa reintra + automat si **recreeaza formularul de antet de la zero** (`Createobject`, `:230`). `Init`-ul noii + instante seteaza focus neconditionat pe `ct_clb_fdoc` (`:9788-9793`) — asta e "focusul care revine pe + tip document" — si `LostFocus`-ul care urmeaza cheama neconditionat `clb_serie_act`, care aloca un + numar nou (`serii_numere.vc2:122-127`) — asta e "regenerarea numarului". +- **Cauza reala e in `ofacturare.prg` (bucla de emitere, cod comun suitei), nu in formularul de antet.** + `s3_portare_antet.md:337-350` e explicit: portarea antetului (S3) NU rezolva automat #16 — arhitectura + unificata (un singur `Init` persistent, antetul nu mai e un obiect separat distrus/recreat de apelant) + **are sansa reala** sa-l elimine ca efect secundar, **dar numai daca implementarea nu recreaza + formularul intreg la reincercare dupa o eroare Oracle** — ceea ce reproduce bugul identic, doar mutat. + Verdictul e legat explicit de **S2**, nu de S3/S4d: bucla traieste in procedura de emitere, nu in + formular. + +**Raspunsul specific S4d, cerut de plan** ("Verifica in acelasi timp daca #16 dispare — vezi M"): +**da, S4d confirma exact scenariul care declanseaza #16** — eroarea `-20005` de la +`verifica_cursuri_valute` e unul dintre cazurile `lnSucces<0` care intra pe ramura problematica a +buclei (`vizualizeaza_curs(poDate.zi_curs)` la `:315`, chiar linia care deschide `frm_curs`, exact +recuperarea citata in reclamatia originala). Deci daca S2/S3 rezolva #16 (antet persistent, fara +recreare), **S4d beneficiaza direct** — dupa `-20005`, userul revine in acelasi antet unificat +(nu unul nou), campul `clb_zi_curs` readus vizibil (punctul 5.4) ramane exact acolo unde a fost adus +inapoi, fara sarituri de focus si fara renumerotare. Daca S2/S3 NU rezolva #16 (recreare inca prezenta), +S4d **nu il agraveaza si nu il repara** — comportamentul ramane identic cu azi pe acest punct, dar +readucerea campului vizibil (5.4) trebuie facuta in `Init`-ul noii instante, nu presupusa mostenita. + +**Concluzie:** #16 **nu e in perimetrul de implementare al S4d** (e S2, per plan `:1944`), dar S4d +**depinde de rezultatul lui** pentru UX-ul complet al recuperarii de eroare (5.4) — de marcat explicit +ca dependenta la implementare, nu de reinvestigat. + +--- + +## 7. Pasi de implementare, ordonati + +Fiecare pas lasa suita functionala; calea veche (`frm_date_factura`/`frm_facturare_articole2` cum sunt +azi) nu se sterge in etapa I. + +1. **Adauga functia de evaluare a conditiei reactive** pe formularul unificat (metoda noua, de ex. + `do_actualizeaza_vizibilitate_curs`), care calculeaza `lVizibil` conform formulei din punctul 2 si + seteaza `Visible` pe controlul `clb_zi_curs` mostenit din prototipul `frm_facturare_articole2` + (`ofacturare.vc2:16285-16305`) — **nu un control nou**, cel existent, ale carui evenimente de + sincronizare (`Clb_dataact...LostFocus`, `:19219-19222`) raman neschimbate (deja garda pe + `Type(...)<>'U'`, deci tolereaza si eliminare, nu doar `Visible=.F.`). + *Gata cand:* metoda exista, se poate apela manual (fara UI), si intoarce `.T.`/`.F.` corect pe cele + patru combinatii din punctul 8 (tip retur / nu, `in_valuta` 0/1, cu/fara linie `tip_valuta=1`). +2. **Cheama metoda din pasul 1 la sfarsitul `do_adauga_articol` si `do_sterge`**, imediat dupa (sau in) + `Thisform.do_calculeaza_totaluri()` (`:17360`, `:18687` in prototip — liniile echivalente in + formularul unificat, dupa portarea din S3/S4). + *Gata cand:* adaugarea unei linii cu `tip_valuta=1` pe o factura in lei fara alte linii de acest fel + face campul vizibil imediat, fara Refresh manual; stergerea ultimei asemenea linii il ascunde din nou + (simetric, punctul 3), fara sa goleasca `poDate.zi_curs`. +3. **Verifica initializarea la deschiderea formularului** (cazul `in_valuta=1` sau document reincarcat + cu linii deja existente, ex. la editare factura emisa) — `Init`-ul unificat trebuie sa apeleze aceeasi + metoda o data, dupa ce `crsfactura` e populat, nu doar sa se bazeze pe evenimentele de adaugare/ + stergere (care nu ruleaza la incarcarea initiala a unui document existent). + *Gata cand:* deschiderea unei facturi existente (in lei, cu linii in valuta deja salvate) arata + campul corect de la primul `Show()`, fara sa fie nevoie de o adaugare/stergere care sa-l declanseze. +4. **Elimina campul neconditionat pe retur (tip 8/9)**, ca azi — verifica ca formula din pasul 1 include + deja `!Inlist(poDate.tip,8,9)` inaintea oricarei alte conditii, ca sa nu-l readuca vizibil din greseala + printr-o linie in valuta pe un retur (desi returul nu foloseste `zi_curs`, vezi `zi_curs_validare.md` + §4c/§6 — comportamentul trebuie sa ramana identic azi, nu doar "fara efect"). + *Gata cand:* pe orice tip 8/9, campul e absent indiferent de continutul grilei. +5. **Readu campul vizibil la eroarea `-20005`** (punctul 5.4) — modifica ramura `goExecutor.nEroare=20005` + din bucla de emitere (`ofacturare.prg:313-317`/`:828-832`, sau echivalentul ei in noua arhitectura + integrata cu formularul unificat, cf. S3 pasul 6) ca sa forteze `Visible=.T.` pe `clb_zi_curs` inainte + de a deschide `frm_curs`. + *Gata cand:* pe o factura in lei cu campul ascuns, o eroare `-20005` simulata (curs lipsa de test) + face campul vizibil imediat dupa mesaj, inainte sau odata cu deschiderea `frm_curs`. +6. **Coordoneaza cu decizia 42 (S4 punctul 1)**: cand acea poveste implementeaza restrangerea + `verifica_cursuri_valute` la valuta articolului cautat, verifica ca textul afisat prin + `oPrelucrareEroare()` (punctul 5.1-5.2, neschimbat) tot numeste corect valuta si data — nu necesita + modificare pe partea VFP, doar confirmare ca noul parametru Oracle e transmis corect din calea de + cautare filtrata. + *Gata cand:* pe cautarea filtrata (dupa decizia 42), mesajul `-20005` numeste doar valuta cautata, nu + un agregat. +7. **Regresie pe formularul vechi**: confirma ca niciuna din modificarile 1-6 nu atinge + `frm_date_factura`/`frm_facturare_articole` (calea veche, ramasa in productie) — toate modificarile + se fac pe clasele formularului unificat / prototip, nu pe cele originale. + *Gata cand:* `svn diff`/`git diff` arata modificari doar in clasele noii cai. + +--- + +## 8. Cum se verifica + +Pe probele din plan (`:2243-2246`), plus cazurile suplimentare care rezulta din punctele 2-6: + +1. **Factura in lei, fara articole in valuta** — `clb_zi_curs` nu apare la deschidere; documentul se + emite corect (acelasi rezultat `VANZARI`/`ACT`/`RUL` ca azi, criteriul din S3 §7). +2. **Aceeasi factura, dupa adaugarea unui articol cu pret in valuta** — campul apare imediat, cu data + implicita deja completata (data documentului, de la `Init`/`Reset`, nemodificata). +3. **Stergerea articolului din pasul 2** (singurul cu `tip_valuta=1`) — campul dispare din nou; valoarea + din `poDate.zi_curs` ramane cea introdusa/implicita (verificabil citind proprietatea direct, nu doar + vizual). +4. **Readaugarea unui alt articol in valuta, in aceeasi sesiune** — campul reapare cu **aceeasi** data + ca la pasul 2, nu resetata. +5. **Factura in valuta (`in_valuta=1`)** — campul apare mereu, indiferent de continutul grilei, exact ca + azi (fara regresie pe cazul deja functional). +6. **Retur (tip 8/9)** — campul absent indiferent de linii; document salvat corect (cf. precedent deja + existent). +7. **`-20005` cu campul ascuns** (factura in lei, curs de test lipsa pentru o valuta din politicile + utilizatorului) — mesajul numeste valuta si data lipsa (deja azi); campul devine vizibil dupa eroare. +8. **Editare factura emisa** (cf. #6, formular deja in lucru separat) — deschiderea unei facturi in lei + cu linii in valuta deja salvate arata campul corect de la primul `Show()` (pasul 3 de implementare). +9. **Dupa decizia 42** — cautarea filtrata a unui articol cu curs lipsa arata mesaj cu o singura valuta + (a articolului cautat), nu un agregat. + +--- + +## 9. Ce nu se poate testa headless + +- **Focusul si secventa reala `SetFocus`/`LostFocus`/`Visible=`** pe controale UI reale — capcana deja + cunoscuta (`grid-coloane-nu-se-materializeaza-headless`, memorie de proiect): sub harness `-A -T`, + proprietatile de grid/vizibilitate nu se materializeaza identic cu UI vizibil. Verificarea + vizibilitatii reactive (punctele 2-3) trebuie facuta fie prin verificare directa a proprietatii + `.Visible` dupa apelul metodei (fara `Show()`), fie cu `vfp_ui_harness.ps1`, UI vizibil. +- **Eroarea Oracle `-20005` reprodusa real** — necesita fie date de test cu un curs lipsa garantat pe o + fereastra de date controlata (manipulare de date, nu de UI), fie mock pe `goExecutor` care simuleaza + `nEroare=20005` fara conexiune reala — niciuna verificata ca exista deja in suita de teste. +- **Deschiderea modala a `frm_curs`** (`vizualizeaza_curs`) — `Show(1)` modal, aceeasi limitare generala + a testarii headless pe formulare modale. +- **Bug #16 propriu-zis** (secventa reala de focus dupa recreare de formular) — deja marcat netestabil + headless in `s3_portare_antet.md` §8; S4d nu adauga o cale noua de testare, doar depinde de acelasi + rezultat. + +--- + +## 10. Riscuri si de decis de Marius + +1. **Incarcarea in masa pe comanda/aviz/contract ramane neconditionata dupa decizia 42** (punctul 5.3). + Pe aceste tipuri, `-20005` poate inca numi o valuta nelegata de documentul curent, chiar daca `S4d` + ascunde corect campul pe baza continutului grilei. **Recomandare:** de discutat daca extinderea + restrangerii decizia-42-style merita si pe incarcarea in masa (poveste separata, posibil in S4 sau + intr-o continuare a deciziei 42), sau se accepta ca diferenta cunoscuta, documentata aici. +2. **Cine seteaza `Visible=.T.` la `-20005` cand antetul ar fi fost recreat** (daca #16 nu se rezolva + complet in S2/S3 pana la implementarea S4d) — punctul 5.4/6 presupune `thisform` = acelasi formular + persistent; daca arhitectura finala tot recreaza o instanta, logica trebuie mutata in `Init`, cu un + semnal explicit propagat (ex. proprietate `poDate.lCursLipsa` sau echivalent) ca sa stie noua instanta + sa arate campul. **Recomandare:** de tratat ca parte a pasului 6 din `s3_portare_antet.md` (integrarea + buclei de emitere), nu izolat in S4d. +3. **`frm_date_aviz`/`frm_date_aviz_lucrare` (tipurile 27, 30) nu intra in perimetrul imediat** — daca + formularul unificat le preia mai tarziu, validarea neconditionata de la `:8076` trebuie aliniata la + aceeasi regula reactiva (azi ramane necondiționata, per `zi_curs_validare.md` §6). **Recomandare:** nu + se atinge acum; se trateaza cand/daca acele tipuri intra in unificare. +4. **Simetria "ascunde la 0 linii" (punctul 3) e o recomandare, nu o certitudine ceruta explicit de + plan** — planul cere doar "apare cand...", nu specifica explicit comportamentul la disparitia ultimei + linii. **Recomandare:** simetria propusa aici (argumentata pe tiparul `lb_cursuri`), dar de confirmat + cu Marius inainte de implementare, pentru ca schimba usor experienta (campul "clipeste" la + adaugare/stergere repetata a aceleiasi linii). diff --git a/docs/cercetare/s4e_lista_preturi_pe_sursa.md b/docs/cercetare/s4e_lista_preturi_pe_sursa.md new file mode 100644 index 0000000..a79e294 --- /dev/null +++ b/docs/cercetare/s4e_lista_preturi_pe_sursa.md @@ -0,0 +1,444 @@ +# S4e — Lista de preturi disponibila si pe factura din comanda + +Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara +scriere Oracle — numai `SELECT`), pentru povestea **S4e** din +`docs\plan_13_unificare_formular_facturare.md:2249-2267` (decizia 16, text integral in sectiunea J +a planului). Nu se ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` +(perimetrul altei sarcini in lucru; `COMUN\programe\ofacturare_comun.prg` si +`COMUN\programe\ofacturare.prg` sunt citite, nu editate). Decizia 29 (stergerea unei linii din comanda +ramane fara protectie) nu se reargumenteaza. + +**Status: cercetare incheiata.** + +--- + +## Verdict (esenta, pentru cine nu citeste tot) + +**Reteta literal citata in decizia 16 — `APPEND FROM` la `ofacturare.prg:454-473`, care lipeste +lista de preturi peste `crsarticole` — nu trebuie generalizata pe comanda ca atare. E nesigura acolo, +pe cod, nu doar teoretic.** `crsarticole` e simultan grid-sursa **si** registrul de cantitate ramasa +citit/scris de `do_sterge` si `do_scrie_factura` (decizia 39, S4 punctul 2). Doua motive concrete, +verificate pe cod si pe SQL exportat: + +1. **Coliziune de `id_c`.** Fiecare procedura `cursor_*` din `PACK_FACTURARE` numeroteaza `id_c` cu + `ROWNUM`, pornind de la 1, **independent** de orice alta executie. `cursor_comanda` (liniile + comenzii) si `cursor_preturi` (lista de preturi) ar produce, executate separat si apoi lipite + prin `APPEND FROM`, doua seturi de `id_c` care se suprapun (1, 2, 3…). `do_sterge` potriveste + randul de sters in `crsarticole` **prin `id_c`** (`ofacturare.vc2:14652-14655`, + `:14658-14659`) — o coliziune ar face ca stergerea unei linii libere sa modifice cantitatea + ramasa a unei linii de comanda complet diferite (sau invers), tacut, fara eroare. + Codebase-ul insusi cunoaste acest risc: `cursor_contract` (`PACK_FACTURARE:2722`, + `SELECT rownum - 10000 as id_c ...`) foloseste deliberat un offset ca sa nu se suprapuna cu + spatiul de `id_c` al celuilalt cursor din acelasi apel. +2. **Poluarea registrului.** `do_scrie_factura` face `Calculate Sum(cantitate) To lnCantitateRamasa` + peste **tot** `crsarticole` ca sa decida daca se ofera inchiderea automata a comenzii + (`ofacturare.vc2:14332-14338`). Coloana `cantitate` din `cursor_preturi` **nu inseamna "ramas de + facturat"** — inseamna **stoc disponibil** (`NVL(C.CANTITATE,0)`, derivat din miscari de stoc, + `PACK_FACTURARE:2276-2280` si similar pe fiecare ramura). Amestecarea celor doua ar face ca suma + folosita pentru decizia de inchidere sa includa cantitati de stoc fara nicio legatura cu ce a mai + ramas de facturat din comanda — decizia de inchidere automata ar deveni gresita, tacut. + +**De ce merge pe contract fara aceste probleme**: pe contract, lista de preturi **nu intra niciodata +in acelasi cursor** cu articolele contractului. `cursor_contract` intoarce **doua** cursoare Oracle +separate (`V_CURSOR` = `cursor_preturi`, mapat pe `crsarticole`; `V_CURSOR2` = articolele/ratele +contractului, mapat pe `crsarticole1`, cu `id_c` offset `-10000`) si **niciun `do_scrie_factura` +nu calculeaza `Sum(cantitate)` pe tipurile de contract** (2,6,26,52) — acel `Do Case` nu are ramura +pentru ele, cad in `Otherwise`, fara nicio logica de inchidere automata. Contractul "merge" pentru ca +lista de preturi traieste azi acolo unde nu exista niciun registru de citit. + +**Solutia curata pentru comanda, verificata ca fezabila pe cod**: liniile libere **nu intra deloc in +`crsarticole`**. Mecanismul de adaugare a lor e cel construit de **S4** — cautare filtrata pe server +(`cursor_preturi` cu parametru de filtru, `docs\cercetare\s4_cautare_articole_server.md`, punctul 4) +legata printr-un `combosql`/`APPEND BLANK` **direct pe `crsfactura`** (exact tiparul deja existent in +`frm_facturare_articole2.But_nou1.do_adauga`, `ofacturare.vc2:17118-17122`: `Select crsFactura / +APPEND BLANK`), nu prin `do_adauga_articol`/`Scatter` dintr-un cursor sursa. Fiindca linia nu vine +niciodata dintr-un rand real al lui `crsarticole`, `crsfactura.id_c` ramane la valoarea implicita a +campului (`N(20)`, fara `Null`, deci `0` la `APPEND BLANK`) — **valoare pe care Oracle nu o produce +niciodata** (`ROWNUM` porneste de la 1), deci `do_sterge`-ul de azi (`For id_c = poArticol.id_c`) e +deja, prin constructie, un no-op sigur pe o linie libera, **fara nicio modificare de cod in +`do_sterge`**. Precedentul exista deja in acelasi fisier: linia de discount adaugata la +`ofacturare.vc2:14531-14537` (`Append Blank` pe `crsfactura`, fara `id_c`) foloseste exact acest +tipar azi, in productie. + +Ramane un gol real de proiectat, nu de presupus rezolvat: **validarea de cantitate/stoc pe o linie +libera** nu se poate sprijini pe `do_verifica_articol` (cuplat de `crsarticole`) — vezi sectiunea 6. + +--- + +## 0. Ce e deja stabilit (nu se reinvestigheaza) + +- Optiunea "Cauta in lista de preturi…" intra in meniul de adaugare proiectat la S4b + (`docs\cercetare\s4b_bara_butoane_meniu.md`) — S4e ii da un element de meniu, nu un buton propriu. +- **Decizia 29**: stergerea unei linii venite din comanda ramane FARA protectie — se sterge ca + oricare alta, comanda ramane facturata partial. Nu se reia. +- **Legatura cu S4 punctul 2 (decizia 39)**: `crsarticole` e si registru al cantitatii ramase de + facturat. La momentul scrierii acestui raport, `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md` + e **in lucru** (sectiuni "IN LUCRU", fara concluzie de proiectare inca). Acest raport **nu asteapta** + acea proiectare: dovezile de mai jos (id_c, `Calculate Sum`) sunt citite direct pe codul actual, nu + presupuse din raportul in lucru. Presupunerea facuta explicit aici: **designul propus (liniile + libere nu intra in `crsarticole`) e compatibil cu orice varianta de decuplare a registrului pe care + S4 punctul 2 ar alege-o** — pentru ca liniile libere nu ating deloc cursorul/mecanismul pe care + punctul 2 il decupleaza. Daca punctul 2 alege sa mute registrul intr-un cursor complet nou (nu + `crsarticole`), concluzia ramane aceeasi cu o singura schimbare de nume. +- **S4 (`docs\cercetare\s4_cautare_articole_server.md`) a decis deja**, pe cod, ca pentru + comanda/aviz (3,4,21,25,28,42,47) `crsarticole` **ramane incarcat in masa**, neschimbat — punctul 7 + al acelui raport. Aceasta decizie priveste **selectia articolelor comenzii insesi** (raman pe + `grd_articole`/`crsarticole`/`do_adauga_tot`, neschimbate). **Nu exclude** mecanismul S4e: cautarea + filtrata pe server construita de S4 (`cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`) e exact + ce S4e reutilizeaza pentru liniile **libere**, un canal separat, care nu inlocuieste si nu atinge + `grd_articole`. + +## 1. Ce face azi calea de copiere (`ofacturare.prg:454-473`) + +Cod complet (`COMUN\programe\ofacturare.prg:454-473`): + +``` +ELSE + IF m.llCopiere + * Modificare sau copiere factura + * Nu adaug automat articolele din factura originala, ca sa dau posibilitate de modificare a cantitatilor, pretului, explicatiei + * ofrmdetaliifactura.do_adauga_tot() + + * Sterg din cursorul cu articole inregistrarile adaugate in factura + *DELETE FROM crsArticole + + * Adaug in lista articolelor cursorul cu lista de preturi ca sa pot adauga si alte articole in factura copiata/modificata + lcSqlCursor = [{call pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta,] + ; + [?poDate.id_gestiune_init,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala)}] + + lcCursorTemp = [crsArticoleTemp] + llSucces = goExecutor.oExecuta(lcSqlCursor, lcCursorTemp) + IF m.llSucces + SELECT crsArticole + APPEND FROM DBF(m.lcCursorTemp) + ENDIF + USE IN (SELECT(m.lcCursorTemp)) + ENDIF && m.llCopiere +``` + +Pas cu pas: + +1. Se executa `pack_facturare.cursor_preturi(...)` intr-un cursor temporar separat, `crsArticoleTemp` + (nu direct in `crsarticole`). +2. `SELECT crsArticole` + `APPEND FROM DBF(lcCursorTemp)` — VFP potriveste coloanele **dupa nume**; + coloanele care exista in `crsArticoleTemp` dar nu in `crsArticole` (sau invers) sunt ignorate + tacut de `APPEND FROM` (nu da eroare pentru coloane lipsa pe o parte sau alta — completeaza cu + valoarea implicita a campului pe partea destinatie). Cele doua cursoare au aceeasi structura de + baza (`creeaza_facturacrs`/coloanele standard `cursor_facturare`), deci in practica maparea e + completa. +3. `id_c` din `crsArticoleTemp` (numerotat `ROWNUM` de Oracle, incepand de la 1, independent de + executia anterioara) se copiaza **ca atare** in `crsArticole` — **nu se renumeroteaza**. +4. `crsArticoleTemp` se inchide (`USE IN`); rezultatul ramane doar in `crsArticole`, care de acum + contine doua seturi de randuri provenite din doua interogari Oracle diferite, cu spatii de `id_c` + care se pot suprapune. + +**De ce e sigur aici, dar nu neaparat pe comanda**: aceasta ramura ruleaza **doar** cand +`m.llCopiere` e adevarat, si cand `llCopiere` e adevarat, cursorul de baza `crsArticole` **nu mai +vine din `cursor_comanda`** — Do Case-ul de la `ofacturare.prg:266-308` verifica `Case m.llCopiere` +**primul**, inaintea oricarei ramuri pe `tnTip`, deci pentru copiere cursorul de baza e intotdeauna +`cursor_retur_document` (documentul copiat insusi), indiferent de tipul original. Un document copiat +nu (mai) e o "comanda" (`poDate.tip` dupa copiere nu e niciodata `3`), deci nici `do_scrie_factura` +(care cere `poDate.Tip = 4` sau `Inlist(poDate.Tip,3,21,25,28,42,47)` pentru logica de inchidere +automata), nici `do_sterge` (aceleasi conditii de tip) nu citesc/scriu `crsarticole` ca registru pe +calea de copiere — **coliziunea de `id_c` exista tehnic si aici, dar nu are efect observabil**, +pentru ca nimic nu mai citeste cursorul ca registru pe acest `poDate.tip`. Aplicarea aceleiasi tehnici +pe un document de tip 3 (unde registrul chiar e citit) ar expune exact coliziunea descrisa in verdict. + +## 2. De ce merge azi pe contract si nu pe comanda + +**Pe contract, lista de preturi si articolele contractului sunt doua cursoare separate de la bun +inceput, populate de o singura procedura Oracle cu doua `OUT REF CURSOR`-uri** +(`PACK_FACTURARE:2646-2950`, `cursor_contract`): + +``` +PROCEDURE cursor_contract(..., V_CURSOR OUT cursor_facturare, V_CURSOR2 OUT cursor_facturare) IS +... +OPEN V_CURSOR2 FOR + ... + SELECT rownum - 10000 as id_c, id_ctr, id_articol, id_rata, ... , opt_facturare + FROM (... CTR_ARTICOLE OPT_FACTURARE=3 UNION ALL ... CTR_SCADENTAR OPT_FACTURARE IN (1,2) ...) + ORDER BY data, numar, data_rata, denumire; + +pack_facturare.cursor_preturi(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_ID_GESTIUNE_INIT, V_LUNA, V_AN, + V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR); +END cursor_contract; +``` + +`V_CURSOR` (= `crsarticole`, mapat in VFP) **este** rezultatul unui apel intern la `cursor_preturi` — +lista de preturi completa, exact ca pe un document liber de tip 1/5/7/10. `V_CURSOR2` (= `crsarticole1`) +e interogarea proprie contractului, cu `id_c` deliberat decalat cu `-10000` fata de spatiul lui +`ROWNUM` simplu — dovada directa ca autorii codului au tratat coliziunea de `id_c` intre cele doua +cursoare ca pe un risc real de evitat, nu ca pe ceva neglijabil. + +**Consecinta pentru UI**: pe un document de contract, `grd_articole` (RecordSource `crsarticole`) +afiseaza azi **lista de preturi**, nu articolele contractului — de aceea capul lui de coloana e deja +"Cantitate in stoc" si mesajul e deja "Acest articol nu este pe stoc!" +(`ofacturare.vc2:15129-15143`, citat integral): + +``` +Case Inlist(poDate.tip, 2, 6) + && facturare pe baza de contract + This.lb_titlu_alb_b121.Caption = [FACTURA PE CTR. ] + Alltrim(poDate.descriere) + This.grd_articole.RemoveObject('cSerie') + && articole din lista de preturi + This.grd_articole.cCantitate.header1.Caption = [Cantitate in stoc] + This.cmesaj_cantitate = [Acest articol nu este pe stoc!] +``` + +`grd_contracte` (RecordSource `crsarticole1`) e gridul secundar, cu articolele/ratele contractului. +Contractul "are deja lista de preturi" pentru ca **asa e populat de la inceput** — nu exista niciun +`APPEND`/merge, doar doua interogari separate aratate in doua griduri diferite. + +**Confirmarea ca registrul nu se aplica pe contract**: `do_scrie_factura`'s `Do Case` +(`ofacturare.vc2:14282-14389`) are exact patru ramuri — `poDate.eProforma=1`, `poDate.Tip=4`, +`Inlist(poDate.Tip,3,21,25,28,42,47)`, `Otherwise`. Tipurile de contract (2,6,26,52) nu apar in +niciuna din primele trei, deci cad pe `Otherwise` — `pack_facturare.scrie_factura2` se cheama direct, +**fara** `Select crsarticole / Calculate Sum(cantitate) ...` si fara `pnParametruAditional` derivat +din vreo suma. Contractul nu are, azi, nicio decizie de "inchidere automata" bazata pe +`Sum(crsarticole.cantitate)` — deci amestecul de continut din `crsarticole` (lista de preturi) nu are +cum sa strice o logica ce nu exista pentru acest tip. **Pe comanda, aceeasi ramura a Do Case-ului +(`Inlist(poDate.Tip,3,21,25,28,42,47)`) exista si citeste exact `crsarticole`** +(`ofacturare.vc2:14332-14338`, citat in verdict) — de aici diferenta reala. + +## 3. Proiectarea adaugarii + +**Liniile libere NU intra in `crsarticole`.** Mecanismul recomandat: + +1. **Sursa de date**: cursorul filtrat construit de S4 — + `pack_facturare.cursor_preturi` supraincarcat cu `V_FILTRU_COD`/`V_FILTRU_DEN` + (`docs\cercetare\s4_cautare_articole_server.md`, punctul 4/9, pasul 1). Nu se cere nimic nou + in Oracle pentru S4e insusi — se reutilizeaza mecanismul deja proiectat pentru S4 (S4e devine + inca un consumator al aceluiasi cursor filtrat, nu un cursor nou). "Alege din nomenclator…" + (S4g, decizia 27/34) e un cursor separat, in afara acestei povesti. +2. **Legarea in UI**: `APPEND BLANK` direct pe `crsfactura`, urmat de editare inline prin + `combosql` pe celula de cod/denumire — exact tiparul deja existent, azi, in productie (chiar + daca in celalalt formular) la `frm_facturare_articole2.But_nou1.do_adauga` + (`ofacturare.vc2:17118-17122`): + ``` + PROCEDURE do_adauga + Select crsFactura + APPEND BLANK + this.grd_factura.SetFocus() + ENDPROC + ``` + Meniul S4b ("Cauta in lista de preturi…") declanseaza acest gest, cu focus direct pe celula de + cautare — S4b insusi a documentat aceasta echivalenta (sectiunea 6 a raportului S4b): "aceste + doua optiuni de meniu ajung la acelasi gest UI". +3. **`crsfactura.id_c` ramane la valoarea implicita a campului** (`N(20)`, fara `Null`, + `creeaza_facturacrs`, `COMUN\programe\ofacturare_comun.prg:1775-1785`) — adica `0` la + `APPEND BLANK`, valoare pe care Oracle nu o produce niciodata pentru `id_c` (`ROWNUM` incepe de + la 1). Consecinta: `do_sterge`-ul de azi (`For id_c = poArticol.id_c`, vezi sectiunea 4) nu + gaseste niciodata un rand de potrivit in `crsarticole` pentru o linie libera — comportament + corect prin constructie, **fara nicio modificare de cod**. + *Precedent in acelasi fisier, deja in productie*: linia de discount adaugata la finalul + documentului (`ofacturare.vc2:14531-14537`, `Select crsfactura / Append Blank / Replace + denumire With Replicate('Z',20), ... , id_temp With 99999`) — nu seteaza `id_c` deloc, foloseste + deja acelasi tipar de "rand sintetic fara sursa in `crsarticole`". +4. **`crsfactura.opt_facturare`** (numeric, implicit `0`) ramane la valoarea implicita si pentru o + linie libera — coincide cu valoarea pe care o are deja o linie normala din lista de preturi + (`opt_facturare=0` inseamna azi "nu vine din contract", conventia citita de `do_sterge` la + `ofacturare.vc2:14615-14619`). Nu e nevoie de o valoare noua pe acest camp pentru distinctia + "libera vs. comanda" — distinctia se face deja prin `id_c` (punctul 3). +5. **Completarea campurilor de pret/TVA/valuta**: identic cu maparea deja proiectata de S4 + (`s4_cautare_articole_server.md`, punctul 6 — tabelul camp-cu-camp) — S4e nu adauga camp nou, + preia mecanismul S4 ca atare pentru randul ales din cautarea filtrata. + +**Ce ramane de verificat la implementare, nu de presupus**: daca `combosql`-ul legat pe `grd_factura` +(construit pentru S4) trebuie sa apara pe **toate** coloanele relevante (cod, denumire) ale unei +linii nou-adaugate prin acest gest, sau doar pe coloana pe care s-a facut click — comportamentul +exact al `LostFocus`-ului de completare in masa a celorlalte campuri (`s4_cautare_articole_server.md`, +punctul 5, pasul 4) ramane in perimetrul S4, S4e doar il consuma. + +## 4. Stergerea + +**(a) O linie venita din comanda** — comportament **neschimbat**, decizia 29 se aplica ca azi: +`do_sterge` (`ofacturare.vc2:14608-14693`) gaseste `poArticol.opt_facturare = 0` (linie din +`crsarticole`, comanda), potriveste pe `id_c` (`ofacturare.vc2:14652-14655`, +`Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip,3,4,21,25,28,42,47) => Select +crsarticole / Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c`), +reface cantitatea ramasa in `crsarticole`. La `do_scrie_factura`, `Calculate Sum(cantitate)` peste +`crsarticole` (neschimbat, fara linii libere in el) vede corect cantitatea marita — comanda ramane +"facturata partial", fara nicio interventie noua. Nicio confirmare, niciun marcaj "refuzat" — +exact decizia 29. + +**(b) O linie libera adaugata** — `poArticol.opt_facturare = 0` (aceeasi valoare ca (a): `0` nu +distinge intre "din lista de preturi ca supliment pe comanda" si "din lista de preturi ca document +liber" — nu trebuie sa distinga, pentru ca in ambele cazuri `lcCursor = crsarticole` +la `ofacturare.vc2:14615-14619`). Executia lui `Replace cantitate With cantitate + poArticol.cantitate +For id_c = poArticol.id_c` gaseste **zero randuri** in `crsarticole` cu `id_c = 0` (Oracle nu produce +niciodata `id_c = 0`), deci `REPLACE ... FOR` e un no-op — nimic nu se schimba in registru. Corect: o +linie libera nu a fost niciodata scazuta din vreun "ramas de facturat", deci nu are ce sa refaca la +stergere. **Nicio schimbare de cod necesara in `do_sterge` pentru acest caz.** + +**Verificare de facut la implementare, nu presupunere**: confirmarea exacta a valorii implicite +(`0` vs. posibil `.NULL.` daca proprietatile campului se schimba fata de ce arata azi +`creeaza_facturacrs`) — ambele valori produc acelasi rezultat (`REPLACE ... FOR` fara potrivire), +dar merita un test explicit inainte de a considera punctul inchis, nu doar citirea definitiei +campului. + +## 5. Capul de coloana si mesajul de cantitate + +**Corectie fata de ipoteza initiala a task-ului**: capul de coloana "Cantitate comandata" si mesajul +"A fost facturata intreaga cantitate comandata pentru acest articol!" (`ofacturare.vc2:15144-15150`) +apartin lui `grd_articole`/`crsarticole` — gridul care, cu proiectarea din sectiunea 3, **continua sa +arate exclusiv liniile comenzii** (liniile libere nu intra niciodata acolo). Deci acest cap de coloana +si acest mesaj **raman corecte, neschimbate**, pentru ca nu se mai aplica vreodata unei linii libere. + +**Unde apare totusi problema semnalata de task**: nu in `grd_articole`, ci in **validarea de +cantitate/stoc a liniei libere insesi**, care trebuie sa arate un mesaj propriu, nu mostenit de la +`Thisform.cmesaj_cantitate` (proprietate unica per formular, setata o singura data in `Init` dupa +`poDate.tip` — daca `poDate.tip = 3`, ea contine azi tocmai textul de comanda). Daca validarea unei +linii libere ar reutiliza `Thisform.cmesaj_cantitate` fara sa o schimbe pe moment, un articol liber +epuizat din stoc ar afisa gresit "A fost facturata intreaga cantitate comandata" — mesajul cerut +de plan la punctul de atentie e real, doar localizat gresit initial (nu pe `grd_articole`, ci pe +mecanismul de validare a liniei libere, vezi sectiunea 6). + +**Propunere concreta de text** (romana, de validat cu Marius la implementare): +- `grd_articole` (comanda), neschimbat: cap coloana **"Cantitate comandata"**, mesaj + **"A fost facturata intreaga cantitate comandata pentru acest articol!"**. +- Linie libera (validare separata, sectiunea 6), text propus, calchiat dupa formularea deja + folosita pe lista de preturi simpla (`ofacturare.vc2:15122-15127`, tipurile 1/5/7/10, unde + `cmesaj_cantitate` ramane la valoarea implicita a formularului): **"Nu mai exista stoc disponibil + pentru acest articol!"** — coerent cu formularea deja folosita pe contract + (`[Acest articol nu este pe stoc!]`, `ofacturare.vc2:15136`), fara sa foloseasca acelasi text + identic (ca sa nu para o dublare accidentala a mesajului de contract). + +## 6. Validarea de cantitate + +**Ce se pastreaza neschimbat**: pentru liniile din comanda, alegerea prin `grd_articole` ramane pe +calea de azi — `do_adauga_articol` -> `do_verifica_articol(tlContract=.F., tnCantitate=poArticol.cantitate)` +(`ofacturare.vc2:12813-12869`, `:14731-14787`). `poArticol.cantitate` provine din +`crsarticole.cantitate`, care e "ramas de facturat" (`A.CANTITATE - NVL(D.CANTITATE,0)`, +`PACK_FACTURARE:3030`) — cererea din plan ("liniile din comanda sa nu-si piarda validarea de +cantitate") e satisfacuta **prin constructie**: acest drum nu e atins de nimic din proiectarea S4e, +pentru ca liniile libere nu intra niciodata in `crsarticole` si nu modifica randurile lui. + +**Ce nu se poate reutiliza ca atare pentru o linie libera**: `do_verifica_articol` cere un +`poArticol` scatter-uit dintr-un cursor sursa deja incarcat (`crsarticole`/`crsarticole1`) — o linie +libera adaugata prin `APPEND BLANK` + `combosql` (sectiunea 3) nu are un asemenea rand sursa. Capacul +de cantitate pentru un articol gestionabil ales liber trebuie sa vina din **stocul curent al +articolului**, nu din "ramas de facturat" — informatie pe care randul intors de cautarea filtrata +(`cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`) o are deja in coloana `cantitate` (aceeasi +semantica de stoc descrisa in verdict), fara sa fie nevoie de un apel Oracle suplimentar. + +**Gol real de proiectare, necesar S4e, neacoperit de niciun raport anterior (S4/S4b)**: nici +`s4_cautare_articole_server.md`, nici `s4b_bara_butoane_meniu.md` nu descriu un mecanism de validare +a cantitatii/stocului pentru randul ales prin `combosql` pe `grd_factura` — ambele rapoarte se opresc +la "randul se completeaza cu valorile din cursorul filtrat" (mapare de campuri), fara sa acopere +"ce se intampla daca utilizatorul introduce o cantitate mai mare decat stocul". **De proiectat la +implementare**: fie (a) o verificare in `LostFocus`/`Valid` a celulei de cantitate din `grd_factura`, +similara ca forma cu `do_verifica_articol` dar alimentata din randul `combosql` (nu din +`crsarticole`), fie (b) reutilizarea `do_verifica_articol` insasi, cu un `poArticol` construit ad-hoc +din randul `combosql` in loc de `Scatter` dintr-un cursor — a doua varianta pastreaza un singur loc +de validare in cod, dar cere ca `do_verifica_articol` sa primeasca `poArticol` ca parametru explicit +in loc sa citeasca variabila `Private poArticol` deja populata de apelant (schimbare de contract a +metodei, nu doar de continut). Ambele variante trebuie sa aleaga mesajul din sectiunea 5, nu +`Thisform.cmesaj_cantitate` motenit din `Init`. + +## 7. Aceeasi problema pe alte surse + +| Sursa | `crsarticole` = registru citit de `do_scrie_factura`? | `do_sterge` ajusteaza `crsarticole` pe `id_c`? | Concluzie pentru "APPEND FROM literal" | +|---|---|---|---| +| **comanda** (3,21,25,28,42,47) | **Da** — `Inlist(poDate.Tip,3,21,25,28,42,47)`, `:14332-14338` | **Da** — aceleasi tipuri, `:14652-14655` | **Nesigur** — subiectul acestui raport; solutia e "linii libere in afara `crsarticole`" (sectiunea 3) | +| **avize** (4, "factura din avize") | **Da** — `poDate.Tip = 4`, `:14301-14311` | **Da** — `Inlist(poDate.tip,3,4,21,25,28,42,47)` include 4, `:14652-14655` | **Acelasi risc ca pe comanda** — `cursor_avize` are aceeasi semantica "ramas de facturat" (`docs\cercetare\s4_cautare_articole_server.md`, punctul 1.4); solutia din sectiunea 3 se aplica identic | +| **contract** (2,6,26,52) | **Nu** — tipurile nu apar in `Do Case`-ul din `do_scrie_factura`, cad pe `Otherwise` | Da, dar pe `crsarticole1` (`opt_facturare<>0`), niciodata pe `crsarticole` | **Sigur** — lista de preturi traieste deja intr-un cursor separat (`crsarticole`), fara sa fie citita ca registru; nimic de schimbat | +| **retur ca document** (8,9,24) | **Nu** — cad pe `Otherwise`, fara `Sum(cantitate)`/`pnParametruAditional` | **Da, partial** — `Inlist(poDate.tip,8,9,24) => Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c`, `:14658-14659` (semn opus, urmareste maximul returnabil, nu inchiderea) | **Risc de coliziune `id_c` ramane, dar fara poluarea `Sum`**: o linie libera adaugata literal prin `APPEND FROM` in `crsarticole` pe un document de retur ar putea, la stergere, atinge tacut cantitatea maxima returnabila a unei linii reale (aceeasi problema de `id_c` din verdict, punctul 1) — **de tratat cu aceeasi solutie (linii libere in afara `crsarticole`) si in S4f**, nu doar aici. Decizia 22 (retur fara factura originala e permis) nu schimba aceasta concluzie — priveste alt aspect (mostenirea gestiunii/pretului) | +| **transfer subunitati** (23, 41) | Nu (cad pe `Otherwise`) | Partial, doar tip 41 (`Case gnScadereStoc = 1 And poDate.tip = 41`, `:14647-14649`, cu semn opus) | In afara perimetrului S4e (surse via `cursor_gestiune`, nu `cursor_comanda`/`cursor_preturi`); acelasi principiu de precautie se aplica daca se cere vreodata "lista de preturi si pe transfer" | + +**Concluzie de tabel**: comanda si avize sunt genuin identice ca risc si ca solutie — **orice +implementare a S4e trebuie sa acopere ambele tipuri de sursa cu acelasi mecanism** ("linii libere in +afara `crsarticole`"), nu doar comanda cum sugereaza titlul povestii. Contractul ramane cum e azi +(nimic de schimbat). Returul ca document (S4f) trebuie avertizat explicit sa nu foloseasca reteta +`APPEND FROM` literal, desi riscul lui e mai mic (fara poluarea sumei de inchidere). + +## 8. Pasi de implementare + +1. **Extinde meniul S4b cu "Cauta in lista de preturi…" pe comanda/aviz** (deja in tabelul propus de + S4b, sectiunea 3 a acelui raport) — leaga optiunea de gestul `APPEND BLANK` pe `crsfactura` + descris in sectiunea 3 de mai sus, nu de un al doilea cursor bulk. + *Verificabil*: pe un document de tip 3 (comanda) sau 4/21/25/28/42/47 (avize), alegerea optiunii + adauga un rand gol in `grd_factura` cu focus pe celula de cautare, fara niciun apel catre + `cursor_comanda`/`cursor_avize` in plus (verificabil in logul `goExecutor`). +2. **Confirma valoarea implicita a `crsfactura.id_c` la `APPEND BLANK`** (sectiunea 4) — test direct, + nu citire de definitie. *Verificabil*: dupa `APPEND BLANK` pe un `crsfactura` gol, `id_c` al + noului rand e o valoare pe care Oracle n-o produce niciodata pentru `id_c` (azi: `0`). +3. **Adauga validarea de cantitate/stoc pentru linia libera** (sectiunea 6, varianta aleasa la + implementare) — mesaj propriu, nu `Thisform.cmesaj_cantitate` mostenit. + *Verificabil*: pe un articol gestionabil cu stoc 0, adaugarea lui ca linie libera pe un document + de comanda arata mesajul de stoc propus in sectiunea 5, nu mesajul de "cantitate comandata". +4. **Verifica prin regresie ca `do_sterge`/`do_scrie_factura` raman neatinse** — niciun cod nou in + aceste doua metode pentru cazul liniei libere (confirmat teoretic in sectiunea 4, dar de validat + pe date reale). *Verificabil*: pe un document de comanda cu o linie a comenzii si o linie libera + adaugate, stergerea liniei libere nu modifica `Calculate Sum(cantitate)` peste `crsarticole`; + stergerea liniei din comanda reface cantitatea ei, neschimbat fata de azi. +5. **Extinde acelasi mecanism pe avize (tip 4/21/25/28/42/47)** — nu doar comanda (tabelul din + sectiunea 7). *Verificabil*: identic cu pasul 4, pe un document "factura din avize". +6. **Avertizeaza explicit S4f (retur ca document)** sa nu foloseasca `APPEND FROM` literal pentru + lista de preturi pe documentele de retur (8,9,24) — risc de coliziune `id_c` cu maximul returnabil, + chiar daca fara poluarea sumei de inchidere. Nu e un pas de implementat aici, e o nota de predare + catre acea poveste (deja referentiata la decizia 22/S4f in plan). + +## 9. Ce nu se poate testa headless + +- **Comportamentul `combosql`/`APPEND BLANK` in `grd_factura`** — capcana deja confirmata pe acest + proiect (`grid-coloane-nu-se-materializeaza-headless`): sub `-A -T`, `ColumnCount`/`RecordSource` + raman artefacte. Verificarea vizuala a mesajului/capul de coloana (sectiunea 5) si a gestului de + adaugare (sectiunea 3) cere harnessul UI vizibil, nu rulare headless. +- **`LostFocus`/`Valid` pe celula de cantitate** (sectiunea 6) — daca implementarea alege varianta + (a) din sectiunea 6 (verificare directa in eveniment de celula), comportamentul depinde de randare + de grid, deci nu e testabil direct headless; varianta (b) (reutilizare `do_verifica_articol` cu + `poArticol` construit ad-hoc) **este** apelabila programatic, fara UI, daca semnatura metodei + primeste parametrul explicit. +- **`AMESSAGEBOX` la mesajul de stoc** (sectiunea 5) — verificabil ca text literal in cod, nu ca + aparitie reala pe ecran, din acelasi motiv structural ca oriunde altundeva in suita. +- **Ce se poate verifica headless, direct**: continutul `crsfactura`/`crsarticole` dupa apeluri + directe (nu prin click) la `do_sterge`/`do_scrie_factura`/gestul de `APPEND BLANK`, cu date de + test pregatite manual (un `crsarticole` cu 2-3 randuri de comanda, un `crsfactura` cu o linie de + comanda si una libera inserate programatic) — confirma `id_c`, `Sum(cantitate)`, si comportamentul + `REPLACE ... FOR` fara sa deschida formularul. + +## 10. Riscuri si decizii ramase lui Marius + +1. **Metoda de validare a cantitatii pe linia libera** (sectiunea 6) — varianta (a) sau (b); (b) + cere schimbarea contractului lui `do_verifica_articol` (parametru nou `poArticol`), (a) dubleaza + logica de validare in doua locuri diferite ale codului. **Recomandare**: (b), pentru un singur + loc de intretinut — coerent cu principiul deja aplicat in S4b sectiunea 5 ("echivalenta tot vs. + alege" tine pe convergenta catre o singura rutina). +2. **Textul exact al mesajului de stoc pentru linia libera** (sectiunea 5) — propunerea e o + recomandare, nu o decizie finala; de confirmat impreuna cu textele deja stabilite la S3b + (punctul marunt `a` din plan, `:524`). +3. **S4f trebuie avertizat explicit sa nu repete reteta `APPEND FROM`** pentru retur (sectiunea 7, + pasul 6) — nu e o decizie de luat aici, dar cineva trebuie sa o duca mai departe cand S4f intra + in lucru, altfel riscul de coliziune `id_c` pe retur se reintroduce pe alta poveste, fara sa mai + fie legat de acest raport. +4. **Interactiunea cu S4 punctul 2, in lucru la momentul acestui raport** — proiectarea de aici + presupune ca liniile libere raman complet in afara oricarui mecanism de registru, indiferent cum + arata forma lui finala dupa decuplare. Daca punctul 2 decide sa scoata bookkeeping-ul din + `crsarticole` intr-un cursor nou (cea mai probabila directie, judecand dupa decizia 39), concluzia + sectiunilor 3-4 de aici ramane valabila fara nicio schimbare — liniile libere nu ating niciun + cursor de registru, indiferent de numele lui. Daca punctul 2 alege in schimb sa pastreze + `crsarticole` ca sursa unica dar sa filtreze `Sum(cantitate)`/`do_sterge` printr-un camp nou + (de exemplu un flag `liber`), acel camp ar trebui setat pe liniile libere **daca** ele ar intra + totusi in `crsarticole` — proiectare pe care acest raport o considera inutila (liniile libere nu + trebuie sa intre acolo deloc), dar merita re-confirmat cu punctul 2 cand acel raport se incheie, + ca sa nu ramana doua solutii partial suprapuse pentru aceeasi problema. +5. **Nu s-a masurat, doar dedus din forma interogarii** (decizia 31 a planului, aplicata si aici): + nimic din acest raport se sprijina pe volume de date masurate — toate concluziile despre `id_c`/ + `Sum(cantitate)` vin din citirea directa a SQL-ului si a codului VFP, nu din executii de test. + +## Handoff + +Cercetare incheiata, fara nicio problema de context intalnita in aceasta runda — nu a fost necesara +predarea. Toate cele zece sectiuni cerute de brief sunt completate mai sus, cu `fisier:linie` +verificat direct pe fisierele text reale (nu `.bak`) si pe SQL-ul exportat curent +(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`). Nicio editare +de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit, nicio scriere pe Oracle. + +**Corectie fata de premisa initiala a task-ului**, de retinut explicit pentru cine citeste doar +rezumatul: reteta citata in decizia 16 (`APPEND FROM`, `ofacturare.prg:454-473`) **nu** trebuie +generalizata literal pe comanda — e sigura doar in contextul ei actual (copiere, fara registru activ +pe cursorul respectiv). Solutia corecta, verificata ca fezabila si ca deja partial sprijinita de +mecanisme existente (S4's `cursor_preturi` filtrat, `APPEND BLANK`-ul din `frm_facturare_articole2`, +valoarea implicita `0` a lui `id_c`), e ca liniile libere sa **nu intre niciodata in `crsarticole`** +— exact ipoteza pe care brief-ul o semnala ca posibila si cerea sa fie confirmata sau infirmata pe +cod, nu presupusa. diff --git a/docs/cercetare/s4f_retur_formular_unificat.md b/docs/cercetare/s4f_retur_formular_unificat.md new file mode 100644 index 0000000..620a63f --- /dev/null +++ b/docs/cercetare/s4f_retur_formular_unificat.md @@ -0,0 +1,727 @@ +# S4f — Returul in formularul unificat + +Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara +scriere Oracle — doar `SELECT`), pentru povestea **S4f** din +`docs\plan_13_unificare_formular_facturare.md:2287-2330` (decizia 17, proiectata in sectiunea N, +`:1574-1655`). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si +`COMUN\programe\ofacturare_editare.prg` — doar citite, cand au aparut in cautari. + +Surse de adevar deja stabilite, citate nu reinvestigate: `docs\cercetare\legatura_linie_retur.md`, +`docs\cercetare\factura_retur_document.md`, `docs\cercetare\retur_si_lista_preturi.md`, +`docs\cercetare\s4b_bara_butoane_meniu.md`. + +## Verdict (esenta, 10 randuri) + +**Corectat dupa avertismentul S4e** (`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si +7): mecanismul `APPEND FROM` din decizia 16 (`ofacturare.prg:454-473`) **e respins pentru orice +cursor care serveste si drept registru citit de `do_sterge`**, `crsarticole` fiind exact acel cursor +pe documentele de retur (`Inlist(poDate.tip,8,9,24) => Replace cantitate With cantitate - +poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) — riscul e coliziune de +`id_c` intre `cursor_retur_document` si `cursor_preturi`, ambele numerotate `ROWNUM` independent. +Sectiunea 5 de mai jos e rescrisa pe solutia validata de S4e: **liniile libere nu intra deloc in +`crsarticole`**, se adauga direct in `crsfactura` prin `APPEND BLANK` + `combosql` (tiparul deja in +productie la `frm_facturare_articole2.But_nou1.do_adauga`), iar campul de distinctie e +**`crsfactura.id_c`** (`0` implicit pe o linie libera, valoare pe care Oracle n-o produce niciodata +pentru `id_c`), nu o coloana lipsa in `crsarticole`. + +Partea grea nu e ce parea: N.1 (documentul de retur, tip 8/9/24) merge deja cap-coada — alegere +multipla de facturi, populare din `cursor_retur`, gestiune si pret de achizitie mostenite, stergere +si retur partial. Munca reala e in trei bucati inguste. **(1) Nu se muta dialogul de alegere a +facturilor** in bara de butoane a gridului — s-a verificat pe cod ca alegerea ramane la nivelul +antetului (sectiunea pliata), exact cum recomanda deja `s4b_bara_butoane_meniu.md` §9.3/tabelul +"puncte marunte", randul h; ce se muta in meniul "Adauga articole" e **rezultatul** alegerii (liniile +deja aduse), ca "Adauga tot din facturile alese" / "Alege liniile de returnat…", simetric cu +comanda/contract/avize. **(2)** Ridicarea lui `But_retur` la nivel de document e cod nou real, nu o +mutare de buton — azi alegerea e per-articol si niciodata scrisa ca legatura persistata. +**(3)** Liniile libere pe retur folosesc reteta S4e ("`APPEND BLANK` pe `crsfactura`, niciodata pe +`crsarticole`"), **plus** un gol nou de proiectat, gasit in aceasta sesiune si necunoscut de S4e (care +nu are dialog de gestiune pe comanda/avize): mecanismul de alegere a gestiunii +(`frm_articol_gest_factura`, `.lRetur=.T.`) **suprascrie pretul de vanzare cu pretul din stoc** +(`ofacturare.vc2:4094-4103`) — corect pentru o linie mostenita dintr-o factura sursa, dar gresit +pentru o linie libera, a carei pret de vanzare trebuie sa vina din lista de preturi (decizia 16), nu +din costul de stoc; in plus, acel dialog se intra azi doar dintr-un `poArticol` scatter-uit din +`crsarticole` (`do_adauga_articol`/`do_alege_stoc`), pe care o linie libera din `crsfactura` nu-l are +— e nevoie de un punct de intrare nou in dialog, nu doar de reparat pretul. Vezi sectiunea 5 si +riscurile R1/R7. + +--- + +## 1. Inventarul celor doua cai de retur de azi + +### 1.1 N.1 — factura de retur ca document (tip 8, 9, aviz 24) + +| Pas | Ce face | `fisier:linie` | +|---|---|---| +| Intrare | Tile `Page2.Cw1` -> `politica.mpr` -> `factureaza(8)`/`factureaza(9)` din meniul shortcut | `COMUN\clase\ofundal_facturare.vc2:882-884`, `Meniuri\politica.mn2:14-15,45-46` | +| Antet | `nIdTipDoc=5`, formular `frm_date_factura`, aviz retur (24) foloseste `frm_date_aviz` cu `nIdTipDoc=6` | `COMUN\programe\ofacturare.prg:192-196,222-226` | +| Alegere facturi sursa | `frm_date_factura.do_cauta_facturi` -> `caut_facturi_multiple_client(id_client, in_valuta, id_valuta, .T.)` -> `cauta_alfa(..., tnTipReturn=1)`, selectie multipla, exclude tipurile de retur din sursa | `COMUN\clase\ofacturare.vc2:9173-9212`; `COMUN\programe\oproceduri_facturare.prg:2091-2121` | +| Validare obligatorie | Fara alegere, antetul nu se poate inchide (`inainte_de_do_termin`) | `ofacturare.vc2:9523-9526` | +| Populare linii | `pack_facturare.cursor_retur(V_IN_VALUTA,V_LISTAID,V_ID_UTIL)` -> `cursor_retur_document(...,V_COPIERE=0,...)`, `SELECT` direct din `VANZARI_DETALII`, filtrat pe `ID_VANZARE IN (V_LISTAID)` | `ofacturare.prg:306-307`; `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3943-3956,3958-4071` | +| Gestiune + pret achizitie | `ID_GESTIUNE`/`PRET_ACHIZITIE` copiate neschimbate din linia originala; `GESTIONABIL = NVL2(A1.ID_GESTIUNE,1,0)` | `ff_...sql:4034-4035,4055-4056,4048` | +| Stergere linie | Permisa, cantitatea reintra in cursorul sursa | `ofacturare.vc2:14608-14693`, ramura `:14658-14659` | +| Retur partial | Permis, validat fata de maximul = `CANTITATE` a liniei originale, fara agregare cu retururi anterioare | `ofacturare.vc2:14743-14754`, `ff_...sql:4036` | +| Adaugare libera | **Nu se poate azi** — `do_adauga_articol` ia articolul mereu din `crsarticole`, care pe tip 8/9/24 **este** rezultatul lui `cursor_retur` | `ofacturare.vc2:12843-12851` | + +### 1.2 N.2 — `But_retur` per-articol, in factura normala (tip 1, 5, 7, 10) + +| Pas | Ce face | `fisier:linie` | +|---|---|---| +| Vizibilitate | Doar pe tip 1/5/7/10; pe documentele de retur (8/9/24) butonul nu exista in UI | `ofacturare.vc2:15122-15127` | +| Declansare | `caction=do_retur` -> `do_adauga_articol(.F.,.F.,.T.)` | `cmd_butoane.vc2:324-338`; `ofacturare.vc2:13963-13965` | +| Alegere factura sursa | **Per articol**, la fiecare adaugare: `caut_facturi_multiple_client_articol(id_client,in_valuta,id_valuta,.F.,id_articol)` -> `cauta_alfa` cu titlu simplu "Alegeti factura" (nu selectie multipla) | `oproceduri_facturare.prg:2124-2159` | +| Acumulare | `thisform.cListaIdArticoleRetur` acumuleaza CSV de perechi `id_articol:id_vanzare`, cate una per articol adaugat; `poDate.listaid` e folosit **transient** (salvat in `lnListaIdOld` si restaurat imediat dupa) doar cat dureaza dialogul per-articol | `ofacturare.vc2:12882-12892` | +| Gestiune | **Se alege**, nu se mosteneste — `pack_facturare.cursor_gestiuni_articol_retur`, prin `do_alege_stoc(...,tlRetur=.T.)` -> `frm_articol_gest_factura` | `ofacturare.vc2:13230,13353-13385` | +| Pret | **Suprascris cu pretul din stoc** (`pretv`), nu cel din lista de preturi — vezi sectiunea 5 | `ofacturare.vc2:4094-4103` | +| Semn cantitate | Explicit `poArticol.cantitate = (-1) * cantitate` cand `Thisform.lRetur` | `ofacturare.vc2:4103` | +| Trimitere Oracle | `poDate.listaid = thisform.cListaIdArticoleRetur` la scriere finala | `ofacturare.vc2:13977-13979` | +| Legatura persistata | **Nicio legatura scrisa** — `listaid` e folosit doar ca filtru in calculul de disponibil pe rulaj (`RUL`), nu scrie nimic in `VANZARI_CORESP` sau pe linia noua | `ff_...sql:8142-8212` | + +### 1.3 Ce au in comun si unde diverg + +Comun: excluderea returului dintr-un retur (acelasi filtru `tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)` +in ambele functii de cautare), conventia de semn negativ pe cantitate, `frm_articol_gest_factura` +ca formular de alegere a gestiunii pentru linia gestionabila. + +Diverg pe tot restul: N.1 alege facturile **o singura data, la nivel de document, inainte** ca +articolele sa existe; N.2 alege **per articol, in orice moment**, fara limita de cate ori. N.1 +mosteneste gestiune+pret_achizitie fara alegere; N.2 le cere explicit. N.1 scrie +`VANZARI_CORESP(TIP=3)`; N.2 nu scrie nimic persistat. Zero cod comun intre cele doua cai — +`ofacturare.vc2:12813-12900` (N.2, `do_adauga_articol`) si `ofacturare.prg:306-311` (N.1, +`cursor_retur`) nu se ating. + +--- + +## 2. `do_cauta_facturi` / `cauta_alfa(..., tnTipReturn=1)` — contractul exact + +### 2.1 Cine il cheama si cand + +Pe `frm_date_factura` exista un control generic de cautare, `Ct_clb_altele` (clasa +`ct_clb_cautare`), a carui iconita de lupa e legata de `cprocedura = thisform.do_cauta_altele` +(`ofacturare.vc2:8721-8729`). `do_cauta_altele` e un dispecer pe `nid_tip`: + +``` +Case Inlist(Thisform.nid_tip,8,9) + Thisform.do_cauta_facturi() +``` +(`ofacturare.vc2:8953-8954`) + +**Secventa confirmata pe cod** (`factureaza()`, `ofacturare.prg:187-311`): `frm_date_factura` se +creeaza si se afiseaza (`.Show()`, `:230-235`) **inainte** ca `Do Case tnTip` de la `:266-308` sa +ruleze `cursor_retur`, si mult inainte ca `lcObject = [frm_facturare_articole]` sa fie ales +(`:395`). Executia continua abia dupa ce `ofrmceredate` (antetul) se elibereaza (`:248`), iar +`pnButon` (setat la inchiderea antetului) e verificat imediat dupa. Deci **azi dialogul de alegere +a facturilor ruleaza pe formularul de antet, complet inainte ca formularul de articole sa existe** — +`crsarticole` se incarca o singura data, cu `poDate.listaid` deja fixat. + +Control observat, semnificativ pentru sectiunea 3: `Ct_clb_altele` are +`cconditie = EMPTY(NVL(poDate.listaid,''))` (`ofacturare.vc2:8722`) — sugereaza ca iconita de +cautare e activa doar cat timp nimic nu a fost inca ales; **neverificat** insa exact ce proprietate +citeste `cconditie` (clasa `ct_clb_cautare` nu a fost gasita in text in acest repo, posibil intr-un +`.vcx` neconvertit sau in `COMUNROA`), deci nu se poate confirma daca astazi re-alegerea e blocata +explicit sau doar descurajata vizual. De tratat ca ipoteza, nu ca fapt confirmat. + +### 2.2 Contractul functiei + +`caut_facturi_multiple_client(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple)` +(`oproceduri_facturare.prg:2091-2121`): +- **Intrare**: client (obligatoriu — dialogul nu porneste fara el), valuta (doar daca `tnInValuta`), + `tlFacturiMultiple=.T.` la acest apel -> `lnTipReturn=1` in `cauta_alfa`. +- **Sursa**: `select serie_act,numar_act,data_act,dataora,id_vanzare from fact_vfacturi where + sters=0 and tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) [+ conditie sucursala] [+ client] [+ valuta]` + — fara filtru SQL pe perioada/serie (filtrarea pe acele coloane e comportament generic al + `cauta_alfa`, nu parametru dedicat). +- **Iesire**: XML cu randurile bifate (`ales=1`), convertit local prin `Xmltocursor(..., + "crsFacturiTemp")`; **nimic nu ajunge la Oracle in acest pas**. +- **Populare aditiva a rezultatului in VFP**: `poDate.listaid = cursor2lista("crsFacturiTemp", + "id_vanzare", ",")` — **inlocuieste** intreg continutul (`=`, nu `+=`); daca dialogul se apeleaza a + doua oara azi, a doua rulare suprascrie complet prima (dar azi asta nu se intampla niciodata pentru + ca dialogul ruleaza o singura data, inainte de orice alta actiune pe document — vezi 2.1). +- **`cauta_alfa(...,tnTipReturn=1)`** (`COMUN\programe\cauta_alfa.prg:16-260`, mecanismul generic + folosit peste tot in suita): adauga singur coloana `ales` (`cauta_alfa.prg:124`), titlu explicit + "Alegeti ... (mouse-click pe numar sau apasati SPACE)", filtrare interactiva pe coloanele afisate. + Nu are mecanism de dezactivare a unei optiuni individuale, nici submeniu — doar bifare/debifare. + +### 2.3 Varianta per-articol + +`caut_facturi_multiple_client_articol(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple, tnIdArticol)` +(`oproceduri_facturare.prg:2124-2159`) — **aceeasi functie de baza**, cu doua diferente: filtru +suplimentar `b.id_articol = ?pnIdArticol` (join pe `vanzari_detalii`) si `tlFacturiMultiple=.F.` la +apelul din `do_adauga_articol` (`:12884`), deci `lnTipReturn=0` -> selectie **simpla**, nu multipla. +Titlu simplu "Alegeti factura". Rezultatul e un obiect cu un singur `id_vanzare`, nu un cursor. + +--- + +## 3. Proiectarea punctului 1 — alegerea facturilor sursa ca sursa in meniul de adaugare + +**Ce NU se schimba**: alegerea propriu-zisa a facturilor sursa (dialogul cu bifare multipla) ramane +la nivelul antetului — in formularul unificat, sectiunea pliata (decizia 7). Verificat impotriva +tabelului "puncte marunte" din plan (`plan_13...md`, randul h): *„Alege facturile de returnat…" +ramane doar la antet in etapa I; mutarea (in bara de butoane) e o poveste separata* — si confirmat +independent de tabelul de meniu al lui `s4b_bara_butoane_meniu.md` §3: pe randul "retur (8,9,24)", +meniul "Adauga articole" propus contine **"Adauga tot din facturile alese" · "Alege liniile de +returnat…" · "Cauta in lista de preturi…" · "Alege din nomenclator…"** — nu si "Alege facturile de +returnat…". **Punctul 1 al lui S4f nu contrazice asta**: ce se muta in meniul de adaugare e +rezultatul alegerii deja facute la antet (liniile din facturile alese), simetric cu comanda/ +contract/avize, nu dialogul de alegere a facturilor insusi. + +**Ce SE schimba, si e proiectarea reala**: azi secventa e rigida — antet modal -> `cursor_retur` -> +formular de articole (sectiunea 2.1). In formularul unificat exista un singur formular, mereu +deschis; sectiunea de antet (pliata) si gridul de articole coexista. Trei consecinte de proiectat: + +1. **Cand se declanseaza `cursor_retur_document`?** Nu mai poate fi legat de "inchiderea antetului" + (nu mai exista acel eveniment). Trebuie legat de **confirmarea dialogului de alegere** (butonul de + cautare din sectiunea pliata, echivalentul lui `Ct_clb_altele`/`do_cauta_facturi`): imediat ce + utilizatorul bifeaza facturile si confirma, `poDate.listaid` se seteaza si `cursor_retur_document` + ruleaza pe loc, populand/repopuland `crsarticole`. Documentul poate porni cu tipul deja pe 8/9/24 + (ales din combo-ul de tip document) inainte ca vreo factura sa fie aleasa — grid-ul de articole + ramane gol pana la prima alegere, exact ca azi cand `Reccount(crsarticole)=0` da mesajul "Nu exista + articole pentru optiunile alese!" (`ofacturare.prg:325`). +2. **Poate opera la orice moment, nu doar o data.** Spre deosebire de azi (dialogul ruleaza o singura + data, inainte ca gridul sa existe), in formularul unificat butonul din sectiunea pliata e + apasabil oricand documentul e deschis. Trebuie decis explicit comportamentul la a doua apasare — + vezi punctul urmator. +3. **Alegere aditiva sau inlocuitoare, la a doua apasare.** Azi `poDate.listaid = ...` **inlocuieste** + (nu `+=`), dar azi nu conteaza pentru ca nu se poate apasa a doua oara dupa ce gridul exista. In + formularul unificat, daca utilizatorul a ales deja factura A, a adaugat cateva linii din ea in + `crsfactura`, si apoi vrea sa adauge si din factura B, doua variante: + - **(a) Inlocuire** — a doua alegere reseteaza `poDate.listaid`/`crsarticole` la noul set ales; + liniile deja mutate in `crsfactura` raman (crsfactura e independent de crsarticole dupa mutare), + dar orice linie inca needitata din factura A dispare din `crsarticole` fara avertisment. Simplu + de implementat (identic cu azi), dar surprinzator pentru operator. + - **(b) Aditiv** — a doua alegere **extinde** `poDate.listaid` cu facturile noi (evitand + duplicate) si ruleaza `cursor_retur_document` filtrat doar pe facturile noi, cu + `INSERT INTO crsarticole` (nu `SELECT INTO`), exact reteta deja propusa de + `s4b_bara_butoane_meniu.md` §9.3, optiunea 2 — cere cod VFP nou (bucla de filtrare + + `INSERT`), dar zero cod nou in `pack_facturare` (aceeasi procedura, apelata de mai multe ori). + Consistent cu filozofia deciziei 16 ("sursa umple documentul, nu il inchide"). + + **Recomandare**: varianta (b), aditiv — e coerenta cu tratamentul lista-de-preturi (S4e) si cu + comanda/contract (unde alegerea suplimentara nu sterge ce exista deja), si evita pierderea tacuta + de linii needitate. **Ramane decizie de confirmat de Marius** — nici plan-ul, nici + `s4b_bara_butoane_meniu.md` nu au tranzat-o explicit (§9.5, punctul 2 acolo o listeaza tot ca + deschisa). + +**Consecinta pentru validarea de antet**: garda de azi (`inainte_de_do_termin`, `descriere` obligatoriu +pentru tip 8/9, `ofacturare.vc2:9523-9526`) nu mai are un moment natural "inainte de a inchide +antetul" — se muta la garda echivalenta din `Termina` (aceeasi verificare, alt declansator). + +--- + +## 4. Proiectarea punctului 2 — ridicarea `But_retur` la nivel de document + +**Ce se pastreaza din calea per-articol**: alegerea gestiunii ramane per-articol, prin +`cursor_gestiuni_articol_retur` si `frm_articol_gest_factura` — decizia 22/N.3 spune explicit ca +gestiunea si pretul de achizitie **se aleg**, nu se mostenesc, pentru orice linie fara factura +sursa unica precisa (si N.2 nu are niciodata o factura "sursa unica a documentului", are cel mult +factura aleasa pentru articolul curent). Punctul 2 nu schimba asta. + +**Ce se schimba**: alegerea facturii sursa nu mai e per-articol (un dialog `cauta_alfa` cu selectie +simpla la fiecare `do_retur`), ci **la nivel de document**, dupa modelul N.1 — un singur dialog cu +selectie multipla (`caut_facturi_multiple_client`, acelasi ca la N.1, dar filtrat pe articolul +curent doar daca ramane necesar; de decis daca filtrul pe articol dispare complet odata cu ridicarea +la document). Consecinta directa: `poDate.listaid` (sau un camp nou, vezi mai jos) ar fi populat o +singura data pentru intreg documentul, nu reconstruit per articol prin `caut_facturi_multiple_client_articol`. + +**Capcana de proiectare, gasita pe cod**: `poDate.listaid` e deja **partajat** intre N.1 si N.2 azi — +N.2 il suprascrie transient (`lnListaIdOld = poDate.listaid` ... `poDate.listaid = m.lnListaIdOld`, +`ofacturare.vc2:12882,12892`) doar cat dureaza alegerea per-articol, apoi il restaureaza. Daca N.2 +ridicat la document incepe sa scrie **permanent** in `poDate.listaid` (ca N.1), cele doua mecanisme +intra in conflict pe acelasi camp — un document normal (tip 1/5/7/10) cu retur ridicat la nivel de +document ar avea `poDate.listaid` populat cu facturile de retur alese, camp care pe alte tipuri +(comanda/contract/avize) e deja folosit cu alt sens (id-uri de comanda/contract/aviz sursa, vezi +`ofacturare.prg:266-308`). **Nu e conflict pe tip 1/5/7/10** — acele tipuri nu folosesc azi +`poDate.listaid` pentru altceva (sunt populate cu `cursor_preturi`, care nu ia `V_LISTAID` ca +parametru) — deci reutilizarea e sigura tehnic, dar merita un camp cu nume propriu +(`poDate.listaid_retur` sau similar) ca sa nu se ambiguizeze semantic ce inseamna `listaid` pe un +document normal. **De decis la implementare**, nu blocant. + +**Ce arata in bara de butoane / meniu (S4b)**: tabelul din `s4b_bara_butoane_meniu.md` §3 nu are +azi un rand pentru "retur intr-o factura normala" — doar pentru documentele de retur propriu-zise +(8,9,24). Ridicarea lui N.2 la nivel de document introduce o optiune noua in meniul "Adauga +articole" **pentru tipurile 1,5,7,10**: "Retur de articole…" (deja anticipata in tabelul §3 al +raportului S4b, coloana lista de preturi: "Cauta in lista de preturi… · Alege din nomenclator… · +**Retur de articole…**"). Aceasta optiune deschide dialogul de alegere multipla la nivel de document +(prima data), apoi pe alegerile ulterioare acelasi comportament aditiv/inlocuitor discutat la +sectiunea 3 se aplica identic. + +**Ce dispare**: `caut_facturi_multiple_client_articol` (filtrul per-articol) ramane cod mort daca +alegerea trece integral la nivel de document — sau se pastreaza ca alegere secundara optionala +("mai am nevoie de o factura sursa doar pentru articolul X", nefolosita azi in acest scenariu). Nu +e clar din decizia 17/N.3 daca trebuie eliminata explicit sau doar nu mai e calea principala — **de +decis de Marius** (risc R3). + +--- + +## 5. Proiectarea punctului 3 — liniile libere pe document de retur + +**Corectie fata de premisa initiala a brief-ului** ("prin acelasi `APPEND FROM` ca la S4e"): S4e a +gasit ca reteta literala a deciziei 16 (`ofacturare.prg:454-473`) **nu e sigura** pe niciun tip unde +`crsarticole` e citit ca registru de `do_sterge`/`do_scrie_factura`, si a documentat asta explicit +pentru retur, in tabelul sau de la §7: + +> "**retur ca document (8,9,24)** | `crsarticole` = registru citit de `do_scrie_factura`? **Nu** — +> cad pe `Otherwise`, fara `Sum(cantitate)`/`pnParametruAditional` | `do_sterge` ajusteaza +> `crsarticole` pe `id_c`? **Da, partial** — `Inlist(poDate.tip,8,9,24) => Replace cantitate With +> cantitate - poArticol.cantitate For id_c = poArticol.id_c` (`:14658-14659`, semn opus, urmareste +> maximul returnabil, nu inchiderea) | Concluzie: **risc de coliziune `id_c` ramane, dar fara +> poluarea `Sum`**: o linie libera adaugata literal prin `APPEND FROM` in `crsarticole` pe un +> document de retur ar putea, la stergere, atinge tacut cantitatea maxima returnabila a unei linii +> reale — de tratat cu aceeasi solutie (linii libere in afara `crsarticole`) si in S4f." +> (`s4e_lista_preturi_pe_sursa.md`, §7, randul "retur ca document") + +Solutia S4e (verificata acolo pe comanda/avize) se aplica identic aici — sectiunile de mai jos sunt +rescrise pe ea, nu pe `APPEND FROM`. + +### 5.1 Mecanismul corect: `APPEND BLANK` pe `crsfactura`, niciodata pe `crsarticole` + +**Liniile libere nu intra in `crsarticole`.** Se adauga direct in `crsfactura`, prin acelasi gest deja +in productie la `frm_facturare_articole2.But_nou1.do_adauga` (`ofacturare.vc2:17118-17122`): + +``` +PROCEDURE do_adauga + Select crsFactura + APPEND BLANK + this.grd_factura.SetFocus() +ENDPROC +``` + +urmat de editare inline prin `combosql` pe celula de cod/denumire (mecanismul construit de S4/S4e +pentru cautarea filtrata pe server, `pack_facturare.cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`). +Optiunea de meniu "Cauta in lista de preturi…" (deja in tabelul S4b §3, pe randul retur) declanseaza +exact acest gest — **nu** un `cursor_retur_document` suplimentar si **nu** un `APPEND FROM` peste +`crsarticole`. + +### 5.2 Cum se distinge o linie libera de una din factura sursa + +**Campul corect e `crsfactura.id_c`, nu o coloana pe `crsarticole`.** O linie mostenita ajunge in +`crsfactura` prin `do_adauga_articol`, care face `Select (lcCursor) / Scatter Name poArticol` pe +randul ales din `crsarticole` (populat de `cursor_retur_document`, cu `id_c = ROWNUM`, pornind de la +1) si copiaza acel `id_c` in randul nou din `crsfactura`. O linie libera, adaugata prin `APPEND BLANK` +direct pe `crsfactura` (5.1), **nu trece niciodata prin acest `Scatter`** — `id_c` ramane la valoarea +implicita a campului (`N(20)`, fara `Null`, deci `0`, `creeaza_facturacrs`, +`COMUN\programe\ofacturare_comun.prg:1775-1785`, citat si verificat de S4e §3 punctul 3). **Oracle nu +produce niciodata `id_c = 0`** (`ROWNUM` incepe la 1) — deci `crsfactura.id_c = 0` e semnalul curat, +fara camp nou, care distinge o linie libera de una mostenita. Acelasi tipar e deja in productie pentru +linia de discount (`ofacturare.vc2:14531-14537`, `Append Blank` fara `id_c` setat). + +**Consecinta directa, fara cod suplimentar**: `do_sterge`, ramura de retur +(`Case Inlist(poDate.tip,8,9,24) => Select crsarticole / Replace cantitate With cantitate - +poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) cauta in `crsarticole` +un rand cu `id_c = poArticol.id_c`. Pentru o linie libera, `poArticol.id_c = 0`, iar `crsarticole` nu +are niciodata un rand cu acel `id_c` — `REPLACE ... FOR` devine **no-op prin constructie**. Stergerea +unei linii libere de pe un document de retur **nu atinge** maximul returnabil al nici unei linii +mostenite — exact ce cerea brief-ul la punctul 5, si exact acelasi mecanism (nu doar aceeasi idee) ca +solutia S4e pentru comanda. + +### 5.3 Pretul de vanzare — riscul gasit (R1, ramane valabil) + +**Nu e doar despre gestiune/pret de achizitie, si nu se rezolva prin mutarea in `crsfactura`.** Cand +o linie ajunge la `do_alege_stoc` cu `Inlist(poDate.tip,8,9,24)` adevarat (orice linie pe un document +de retur, indiferent de origine — conditia e pe `poDate.tip`, nu pe un flag de linie), se deschide +`frm_articol_gest_factura`. In acel formular, la schimbarea coloanei de gestiune +(`grd_gestiuni.RowColChange`), daca `Thisform.lRetur` e adevarat: + +``` +If Thisform.lRetur + poArticol.pretftva = pretv && pret DIN STOC, nu din lista de preturi + ... + poArticol.cantitate = (-1) * cantitate +Endif +``` +(`ofacturare.vc2:4094-4103`) + +Pentru o linie **mostenita** (N.1), asta e corect prin constructie — returnezi marfa la pretul cu +care a fost vanduta. Pentru o linie **libera** (decizia 22 — articol niciodata vandut clientului, deci +fara "pret de retur" de mostenit), suprascrierea cu pretul de stoc e o **eroare de pret**: linia +libera ar iesi cu costul de stoc in loc de pretul din lista de preturi ales prin `combosql` la 5.1. + +**Solutie propusa, actualizata**: `frm_articol_gest_factura` primeste un al doilea semnal, distinct de +`tlRetur` ("esti pe flux de retur"), care sa insemne "aceasta linie are factura sursa" — derivat acum +din `crsfactura.id_c <> 0` (5.2), nu dintr-o coloana lipsa in `crsarticole`. Ramura de suprascriere +pret (`:4094-4103`) se activeaza doar cand acest al doilea semnal e adevarat. + +### 5.4 Golul nou, nu tratat de S4e: punctul de intrare in dialogul de gestiune + +**S4e nu are acest gol pentru ca nu are dialog de gestiune pe comanda/avize** — liniile libere de +acolo se completeaza integral prin `combosql` (pret, TVA, valuta) fara sa mai treaca prin niciun +dialog de alegere a gestiunii. Pe retur, decizia 22 cere explicit ca gestiunea si pretul de achizitie +sa **se aleaga** pentru o linie libera — mecanismul care stie sa faca asta e +`cursor_gestiuni_articol_retur` + `frm_articol_gest_factura`, dar acel dialog se intra azi **doar** +din `do_alege_stoc`, apelat de `do_adauga_articol` pe baza unui `poArticol` scatter-uit dintr-un rand +**deja incarcat in `crsarticole`** (`ofacturare.vc2:12843-12851,12891`). O linie libera adaugata prin +`APPEND BLANK` pe `crsfactura` (5.1) **nu are** un asemenea rand sursa — exact acelasi gol pe care +S4e l-a semnalat, netratat, pentru validarea de cantitate pe comanda (`s4e_lista_preturi_pe_sursa.md` +§6: *"`do_verifica_articol` cere un `poArticol` scatter-uit dintr-un cursor sursa deja incarcat... o +linie libera... nu are un asemenea rand sursa"*). + +**Consecinta pentru S4f, mai stricta decat pe comanda**: pe comanda/avize (S4e), lipsa unui `poArticol` +de sursa afecteaza doar validarea de cantitate (un gol de acoperit, dar linia se poate scrie si fara +gestiune aleasa — comanda/avize nu cer gestiune diferita de cea din lista de preturi). Pe retur, lipsa +aceluiasi `poArticol` blocheaza **si** alegerea gestiunii, care e obligatorie prin decizia 22. Trebuie +proiectat un punct de intrare nou in `frm_articol_gest_factura`/`do_alege_stoc`, care sa porneasca de +la randul **deja inserat in `crsfactura`** (dupa `combosql`, cu `id_articol` si cantitate deja +completate) in loc de la un rand din `crsarticole` — practic o a doua cale de apel, cu acelasi dialog +la capat, dar `poArticol` construit ad-hoc din `crsfactura` in loc de `Scatter` din `crsarticole`. +Aceeasi problema structurala, aceeasi solutie propusa (poArticol construit ad-hoc) ca varianta (b) de +la S4e §6 pentru `do_verifica_articol` — de unificat la implementare daca se poate, nu de rezolvat de +doua ori independent. + +### 5.5 Maximul returnabil — nu se aplica, si acum e clar de ce + +Confirmat structural: azi maximul returnabil pe N.1 **nu e un calcul de "cat s-a mai returnat"**, e +pur si simplu `CANTITATE` a liniei originale (`ff_...sql:4036`, `ofacturare.vc2:15236-15239`, +`do_verifica_articol:14743-14754`) — validarea respinge doar semnul gresit (`tnCantitate >= 0` in mod +retur), nu o depasire fata de un istoric. **Pentru o linie libera, intrebarea nici nu se pune in +termenii de azi**: `do_verifica_articol`, ca si dialogul de gestiune (5.4), asteapta un `poArticol` +din `crsarticole` — o linie libera nu are unul, deci nu poate trece prin comparatia de maxim existenta +nici macar din greseala. Validarea proprie a liniei libere (cantitate/stoc, construita ad-hoc, 5.4) +nu are nimic de comparat cu un "maxim returnabil" — verifica doar disponibilul de stoc curent, ca pe +o linie normala de lista de preturi (acelasi gol deschis de S4e §6 pentru comanda, aceeasi solutie). + +--- + +## 6. Efectul asupra `VANZARI_CORESP` + +**Scrierea nu depinde de continutul liniilor, ci de alegerea facuta la antet.** +`scrie_corespondente_vanzari(3)` (`ff_...sql:14834-14836`, apelata din `finalizeaza_factura`) scrie +direct din `pack_facturare.clistaid` (variabila de sesiune, = `poDate.listaid` trimis la +`initializeaza_date_factura`), **independent** de ce a ajuns efectiv in `VANZARI_DETALII` la +finalizare: + +```sql +INSERT INTO VANZARI_CORESP (ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP) + SELECT pack_facturare.nid_vanzare, ID_VANZARE, 3 + FROM VANZARI WHERE ID_VANZARE IN (SELECT X FROM table(charn2collection(V_LISTAID,','))); +``` +(`ff_...sql:15481-15516`, citat integral in `legatura_linie_retur.md:45-53`) + +**Consecinta directa pentru liniile libere**: daca documentul de retur contine si linii libere (din +lista de preturi), corespondenta **tot se scrie**, si scrie exact aceleasi facturi alese la antet — +liniile libere nu adauga si nu scot randuri din `VANZARI_CORESP`, pentru ca sursa e `poDate.listaid` +(fixat la antet), nu continutul `crsfactura`/`VANZARI_DETALII`. Un document de retur cu 3 linii +mostenite din factura A si 2 linii libere scrie tot un singur rand in `VANZARI_CORESP` +(`ID_VANZARE_FACT`=documentul nou, `ID_VANZARE_AVIZ`=factura A, `TIP=3`) — corespondenta descrie +"acest document a folosit ca sursa factura A", nu "aceste linii vin din factura A". + +**Ce se scrie cand nu s-a ales nicio factura sursa**: nu e un caz posibil pentru documentul de retur +insusi — validarea de antet (`inainte_de_do_termin`, `descriere` obligatoriu, sectiunea 3) impune cel +putin o factura aleasa inainte ca documentul sa poata continua. Deci pentru N.1/S4f (documente 8/9/24) +`clistaid` nu e niciodata gol la scriere. **Pentru N.2 ridicat la document (punctul 2)** raspunsul e +diferit: acolo documentul e de tip 1/5/7/10, iar `poDate.listaid`/campul nou echivalent (sectiunea 4) +nu e obligatoriu — un document normal fara nicio linie de retur nu are ce sa scrie. **Intrebare +deschisa, verificata partial**: daca `scrie_corespondente_vanzari(3)` e apelata azi doar cand +`ntip in (8,9)` (confirmat de `legatura_linie_retur.md:113`: *"TIP=3: factura de retur +(scrie_corespondente_vanzari(3), :14834-14836, la ntip in (8,9))"*), atunci **ridicarea lui N.2 la +nivel de document NU va scrie automat `VANZARI_CORESP`** — documentul ramane tip 1/5/7/10, gardat de +`ntip`, nu de continutul `listaid`. Linia exacta a acestui `IF`/`CASE` in exportul curent +(`ff_2026_08_09_01...sql`) **nu a fost re-verificata caracter cu caracter** in aceasta sesiune (citata +din raportul sursa, care a folosit un export anterior cu alta numerotare) — de reverificat la +implementare, dar concluzia structurala (gating pe `ntip`, nu pe `listaid`) e solida. + +**Decizie de proiectare care rezulta**: ridicarea lui N.2 la nivel de document (punctul 2) **nu +capata**, prin simpla ridicare, o corespondenta persistata in `VANZARI_CORESP` — comportamentul +raman identic cu azi (nicio legatura scrisa), *cu exceptia* cazului in care Marius decide explicit ca +merita cod Oracle nou (extinderea gardei `ntip in (8,9)` la un al treilea semnal, ex. "are `listaid` +populat"). Decizia 35 (un singur cod de scriere contabila, cod Oracle nou doar cand strict necesar) +face ca varianta "fara schimbare in pack_facturare" sa fie implicita — **de confirmat cu Marius** +(risc R4), nu de presupus. + +--- + +## 7. Semnul cantitatii / al valorii pe retur + +**Doua mecanisme diferite, verificate separat pe cod:** + +- **N.1 (documente 8/9/24)**: cantitatea vine deja cu semnul corect direct din + `cursor_retur_document` — `CANTITATE` a liniei originale (pozitiva ca valoare de referinta, dar + interpretata cu semn de retur prin `poDate.tip`); semnul efectiv de scriere in `VANZARI_DETALII` + e controlat de codul Oracle care insereaza documentul de retur (nu urmarit aici, in afara + perimetrului VFP), iar in UI validarea `do_verifica_articol` (`llRetur = Inlist(poDate.tip,8,9,24)`) + respinge `tnCantitate >= 0` in mod retur — deci utilizatorul introduce cantitatea ca valoare + pozitiva ("cat returnez"), iar semnul negativ e o conventie aplicata la nivel de tip de document, + nu per linie. +- **N.2 (`But_retur`)**: semnul negativ **e aplicat explicit in cod VFP**, in + `frm_articol_gest_factura` — `poArticol.cantitate = (-1) * cantitate` (`ofacturare.vc2:4103`), + declansat de `Thisform.lRetur` (adevarat cand `tlRetur` a fost trimis la `Init`, indiferent de + `poDate.tip`). Aici semnul **nu** vine din `poDate.tip` (documentul e tip 1/5/7/10, pozitiv prin + design), ci e o negatie explicita facuta pentru ca acea linie anume e un retur intr-un document + altfel normal. + +**Implicatie pentru liniile libere pe document de retur (S4f punctul 3)**: pentru ca documentul +intreg e tip 8/9/24, orice linie — libera sau mostenita — trece prin acelasi tratament de tip de +document (semnul negativ e o proprietate a **documentului**, nu a liniei, in acest caz). Nu exista +risc de "linie libera cu semn pozitiv gresit" atata timp cat validarea ramane gatata pe `poDate.tip` +si nu pe originea liniei — de verificat explicit ca ramura noua a validarii (5.4, sarirea peste +maximul returnabil) **nu** atinge si sare peste verificarea de semn, care trebuie sa ramana activa +identic pe linii libere si mostenite. + +**Implicatie pentru N.2 ridicat la document (punctul 2)**: aici semnul **ramane** o proprietate de +linie (nu de document, pentru ca documentul e tip normal 1/5/7/10) — mecanismul de la +`ofacturare.vc2:4103` se pastreaza neschimbat, declansat de acelasi `tlRetur`/`Thisform.lRetur`, +indiferent daca alegerea facturii sursa se face per-articol (ca azi) sau la nivel de document +(punctul 2). Nimic de schimbat aici — doar de confirmat ca noul flux (alegere la document) tot +seteaza `tlRetur=.T.` la apelul catre `do_alege_stoc`/`frm_articol_gest_factura` pentru fiecare linie +de retur adaugata. + +--- + +## 8. Unde se aseaza informatia de provenienta in UI + +**Inchis pe cod, cf. `legatura_linie_retur.md`**: legatura linie-la-linie nu se poate afisa in niciun +caz — nici `cursor_retur_document` nu aduce `ID_VANZARE`/`ID_VANZARE_DET` in `crsarticole` +(`ff_...sql:3965-4028`, coloanele lipsesc din SELECT), nici `INSERT`-ul final in `VANZARI_DETALII` +nu are coloana de sursa (`PACK_FACTURARE:13705-13757`, listata explicit, fara camp de provenienta). + +**Ce se poate afisa, verificat**: la nivel de **document**, cand exact o singura factura sursa a fost +aleasa, `VANZARI_CORESP(TIP=3)` da fara ambiguitate factura sursa: + +```sql +SELECT v.serie_act, v.numar_act + FROM VANZARI_CORESP c JOIN VANZARI v ON v.ID_VANZARE = c.ID_VANZARE_AVIZ + WHERE c.ID_VANZARE_FACT = :id_document_retur AND c.TIP = 3 AND c.STERS = 0; +``` +(`legatura_linie_retur.md:174-178`, interogare formulata, neverificata pe date live in acel raport) + +**Propunere concreta de text si conditie** (in romana, pentru sectiunea pliata / antetul +formularului unificat): + +- **O singura factura sursa aleasa**: eticheta fixa de tip "Provine din factura: **{serie} {numar}**" + langa campul de descriere existent (`poDate.text_aditional`, deja populat azi cu + "RETUR FACTURA {numere}" — `factura_retur_document.md:65`), afisata read-only, imediat sub sau langa + campul de tip document. +- **Mai multe facturi sursa alese**: eticheta devine "Provine din facturile: **{lista serie+numar}**" + — aceeasi sursa de date (`poDate.descriere`, deja populata cu lista, nu doar `VANZARI_CORESP`), + **fara** sa se sugereze vreo distributie pe linii. +- **Niciodata pe linie** — nici coloana in grid, nici tooltip per rand care sa pretinda o sursa + exacta. Pentru liniile libere in particular, nu exista nimic de afisat (nu au sursa) — nu se + introduce o valoare "N/A" vizibila care ar sugera ca alte linii AU o sursa unica atunci cand de + fapt (cu selectie multipla) nici ele nu o au cu certitudine. +- **Conditie tehnica**: textul se construieste din `poDate.descriere` (deja in memorie, fara interogare + suplimentara) — nu necesita interogarea `VANZARI_CORESP` de mai sus decat daca se doreste + reafisarea la redeschiderea unui document deja emis (regenerare, etapa II) — caz in care + interogarea de mai sus e necesara pentru ca `poDate.descriere` nu mai exista in memorie. + +--- + +## 9. Pasi de implementare ordonati + +1. **Confirmarea comportamentului aditiv/inlocuitor la alegerea facturilor sursa** (sectiunea 3, + punctul 3) — decizie de produs, nu cod. *Gata cand:* Marius a ales (a) sau (b); fara asta pasii 2-3 + nu se pot implementa fara risc de refacere. + *Verificabil:* niciunul (decizie, nu cod). +2. **Butonul/actiunea de alegere a facturilor sursa, mutat in sectiunea pliata a formularului + unificat**, pastrand `do_cauta_facturi`/`caut_facturi_multiple_client` neschimbate — doar + punctul de declansare se muta din antetul modal in sectiunea pliata a formularului unic, si + `cursor_retur_document` se declanseaza la confirmarea dialogului, nu la inchiderea unui formular + separat (sectiunea 3, punctele 1-2). + *Gata cand:* pe un document nou de tip 8/9/24, deschis in formularul unificat, alegerea facturilor + sursa din sectiunea pliata populeaza `crsarticole` cu exact aceleasi randuri ca azi (gestiune, pret + achizitie, pret vanzare identice), verificabil prin comparatie directa a cursorului. +3. **Meniul "Adauga articole" pe tipurile 8/9/24**, cu optiunile "Adauga tot din facturile alese" / + "Alege liniile de returnat…" peste `crsarticole` deja populat (reteta comuna S4b, sectiunile 4-5 + ale acelui raport) — fara alegere de facturi in acest meniu (sectiunea 3). + *Gata cand:* meniul contine exact aceste doua optiuni pe tip 8/9/24, plus "Cauta in lista de + preturi…"/"Alege din nomenclator…" (punctul urmator). +4. **Liniile libere — `APPEND BLANK` pe `crsfactura`, semnalul de distinctie pe `id_c`** (sectiunile + 5.1-5.2): optiunea de meniu "Cauta in lista de preturi…" pe tip 8/9/24 declanseaza + `Select crsFactura / APPEND BLANK` + `combosql`, exact tiparul S4e (`ofacturare.vc2:17118-17122`) + — **nu** `APPEND FROM` peste `crsarticole`. `id_c` ramane implicit (`0`) pe randul nou. + *Gata cand:* dupa adaugarea unei linii libere pe un document de test, `crsfactura.id_c = 0` pe acel + rand si `crsarticole` ramane neschimbat (acelasi `Reccount`/continut ca inainte de adaugare). +5. **Punct de intrare nou in dialogul de gestiune, pornind de la randul din `crsfactura`** (sectiunea + 5.4) — `frm_articol_gest_factura`/`do_alege_stoc` primesc o a doua cale de apel, cu `poArticol` + construit ad-hoc din randul deja inserat in `crsfactura` (dupa `combosql`), nu din `Scatter` pe un + rand din `crsarticole`. Fara acest pas, o linie libera nu poate ajunge deloc la alegerea gestiunii + ceruta de decizia 22. + *Gata cand:* pe o linie libera adaugata pe un document de retur, dialogul de gestiune se deschide si + returneaza `ID_GESTIUNE`/`PRET_ACHIZITIE` alese de operator, fara sa fi trecut prin `crsarticole`. +6. **Fixarea pretului de vanzare pe liniile libere** (sectiunea 5.3, riscul R1) — ramura de + suprascriere din `frm_articol_gest_factura` (`ofacturare.vc2:4094-4103`) primeste al doilea semnal + ("are sursa" vs. "e libera"), derivat din `crsfactura.id_c <> 0` (pasul 4), si nu suprascrie + `pretftva` cand linia e libera. + *Gata cand:* o linie libera adaugata pe un document de retur pastreaza pretul din lista de preturi + dupa alegerea gestiunii, verificabil comparand `poArticol.pretftva` inainte si dupa + `RowColChange` in dialogul de gestiune. +7. **Validarea de cantitate/stoc pe liniile libere** (sectiunea 5.5) — mecanism nou, nu + `do_verifica_articol` ca atare (care cere un `poArticol` din `crsarticole`); verifica disponibilul + de stoc curent, fara nicio comparatie cu un maxim returnabil. Unificabil cu golul echivalent + deschis de S4e §6 pentru comanda/avize, daca implementarea alege aceeasi varianta (`poArticol` + construit ad-hoc, parametru explicit). + *Gata cand:* pe o linie libera, orice cantitate <= stocul curent trece validarea, cu mesaj propriu + (nu "cantitate maxima de returnat"); pe o linie mostenita, comportamentul de azi ramane neschimbat. +8. **Ridicarea `But_retur` la nivel de document (punctul 2)** — dialog de alegere multipla la + deschiderea primei linii de retur pe un document normal (1/5/7/10), populare cu un camp nou sau + reutilizarea controlata a lui `poDate.listaid` (sectiunea 4), pastrand calea per-articol existenta + ca fallback sau eliminand-o explicit (decizie separata, risc R3). + *Gata cand:* pe o factura normala, alegerea facturilor sursa se face o singura data, iar liniile de + retur adaugate ulterior (mai multe articole) folosesc acel set fara sa redeschida dialogul de + cautare la fiecare articol. +9. **`VANZARI_CORESP` pentru N.2 ridicat la document** (sectiunea 6) — de decis daca se adauga cod + Oracle nou sau ramane fara corespondenta persistata (risc R4); implementat doar dupa decizia lui + Marius. + *Gata cand:* comportamentul ales (cu sau fara corespondenta) e implementat consistent si testat pe + un document cu retur ridicat la nivel de document si mai multe facturi sursa. +10. **Textul de provenienta la antet** (sectiunea 8) — afisare read-only din `poDate.descriere`, + condusa de numarul de facturi alese (0/1/multiple). + *Gata cand:* eticheta arata corect in toate trei cazurile (nicio factura inca, o factura, mai + multe), fara sa apara pe grid/linie. + +*Depinde de:* S2 (formular unificat de baza), S4e (mecanismul `APPEND BLANK` + `combosql` pentru +liniile libere pe document cu sursa, NU `APPEND FROM`) — exact ca in plan (`plan_13...md:2330`), +corectat fata de reteta literala a deciziei 16. + +--- + +## 10. Cum se verifica + +**Proba din plan** (`plan_13...md:2327-2329`): *"un document de retur deschis in formularul unificat +aduce liniile facturilor alese cu aceleasi valori ca azi — inclusiv gestiunea si pretul de +achizitie —, permite stergere si cantitate partiala, iar pe o factura normala se poate face retur +alegand facturile o singura data."* + +Pasi concreti: + +1. **Paritate N.1**: pe date de test, emite un document de retur (tip 8) pe calea veche (formularul + actual) si retine `crsarticole` rezultat (gestiune, pret achizitie, pret vanzare, cantitate per + linie). Repeta aceeasi alegere de facturi sursa in formularul unificat si compara `crsarticole` + camp cu camp — trebuie sa fie identic, pentru ca sursa Oracle (`cursor_retur_document`) nu se + schimba, doar punctul VFP care o declanseaza. +2. **Stergere si retur partial**: pe formularul unificat, sterge o linie mostenita si verifica ca + cantitatea reintra in `crsarticole` (acelasi test ca azi, `do_sterge` neschimbat); introdu o + cantitate mai mica decat maximul pe o linie mostenita si verifica validarea. +3. **Linie libera**: adauga o linie din lista de preturi pe documentul de retur deschis (prin + `APPEND BLANK` + `combosql`, nu `APPEND FROM`); verifica (a) `crsfactura.id_c = 0` pe acel rand si + `crsarticole` neschimbat; (b) pretul de vanzare = pretul din lista de preturi, nu din stoc; (c) + gestiunea/pretul de achizitie sunt cerute prin dialogul de gestiune (intrat pe calea noua, 5.4), nu + completate automat; (d) cantitatea nu e limitata de niciun maxim istoric, doar de stocul curent; + (e) semnul cantitatii ramane negativ (consistent cu documentul); (f) stergerea liniei libere nu + modifica `crsarticole` (verifica direct: `Reccount`/continut identic inainte si dupa stergere). +4. **`VANZARI_CORESP` neschimbat de liniile libere**: dupa emiterea documentului din pasul 3, + interogheaza: + ```sql + SELECT ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP FROM VANZARI_CORESP + WHERE ID_VANZARE_FACT = :id_document_nou AND TIP = 3; + ``` + — trebuie sa arate exact facturile alese la antet, indiferent de cate linii libere s-au adaugat. +5. **N.2 ridicat la document**: pe o factura normala, adauga doua linii de retur pentru articole + diferite folosind acelasi set de facturi sursa ales o singura data (nu redeschide dialogul la + fiecare articol); verifica semnul cantitatii (negativ) si absenta oricarei scrieri in + `VANZARI_CORESP` (daca decizia de la punctul 8/risc R4 pastreaza comportamentul de azi). +6. **Provenienta**: deschide un document de retur cu o singura factura sursa — verifica textul de + antet; alege doua facturi sursa — verifica ca textul listeaza ambele si nu implica o sursa unica. + +## 11. Ce nu se poate testa headless + +Reluat din capcanele deja confirmate pe acest proiect (`s4b_bara_butoane_meniu.md` §8), plus +specificul retur: + +- **`cauta_alfa` cu `tnTipReturn=1`** — dialog modal (`.Show(1)`), blocheaza thread-ul UI; testarea + automata a bifarii multiple nu se poate face prin injectare de input pe aceasta masina (partajata, + memoria `masina-partajata-fara-input-real`). Se verifica indirect: apeland direct + `caut_facturi_multiple_client`/`cursor_retur_document` cu parametri de test si citind cursorul + rezultat, ocolind UI-ul. +- **`frm_articol_gest_factura`** — acelasi tip de dialog modal; verificarea suprascrierii de pret + (risc R1, sectiunea 5.3) si a noului punct de intrare pornit din `crsfactura` (sectiunea 5.4) se + poate face doar citind codul metodei `RowColChange`/noua metoda de intrare si, separat, apeland + direct logica de calcul cu date de test simulate — nu prin click real in grid. +- **Coloanele griduri** (`grd_articole`, gridul dialogului de alegere) — capcana deja cunoscuta: + `ColumnCount=0` sub `-A -T`; verificare vizuala necesita harness UI vizibil, nu headless. + (memoria `grid-coloane-nu-se-materializeaza-headless`) +- **Textul de provenienta din sectiunea pliata** (sectiunea 8) — verificabil headless ca **continut + al proprietatii** (`Caption`/`Value` a controlului), nu ca "arata bine" vizual. +- **Secventa reala de deschidere a dialogului din sectiunea pliata** (declansarea la click, nu doar + codul din spatele ei) — necesita harness UI vizibil pentru captura de interactiune. + +**Ce se poate verifica headless, direct**: continutul `crsarticole`/`crsfactura` dupa apeluri directe +ale rutinelor (`cursor_retur_document`, `do_adauga_articol`, `do_verifica_articol`, `APPEND BLANK` pe +`crsfactura`) cu parametri de test; valoarea `crsfactura.id_c` pe randurile noi (semnalul de la 5.2, +`0` pe liniile libere, valoare reala pe cele mostenite); continutul real din +`VANZARI_CORESP`/`VANZARI_DETALII` dupa emiterea unui document de test pe schema Oracle (doar +`SELECT`, verificare post-factum). + +--- + +## 12. Riscuri si ce ramane de decis de Marius + +**R1 — Pretul de vanzare pe liniile libere se suprascrie cu pretul de stoc, daca nu se trateaza +explicit** (sectiunea 5.3). Cel mai concret risc gasit in aceasta cercetare, netratat in plan pana +acum. **Recomandare**: al doilea semnal ("linie cu sursa" vs. "linie libera") pe langa `tlRetur`, +derivat din `crsfactura.id_c <> 0` (5.2), care sa dezactiveze suprascrierea de pret doar pentru +liniile libere. Necesita o mica modificare in `frm_articol_gest_factura` (nu in `pack_facturare`). + +**R7 — Punctul de intrare in dialogul de gestiune pentru o linie libera nu exista azi** (sectiunea +5.4). Gol nou, mai strict decat echivalentul lui pe comanda/avize (S4e §6, doar validare de +cantitate) — pe retur blocheaza si alegerea gestiunii/pretului de achizitie, obligatorie prin decizia +22. `do_alege_stoc`/`frm_articol_gest_factura` se intra azi doar dintr-un `poArticol` scatter-uit din +`crsarticole`; o linie libera (`APPEND BLANK` pe `crsfactura`, 5.1) nu are asa ceva. **Recomandare**: +a doua cale de apel, cu `poArticol` construit ad-hoc din randul `crsfactura` deja completat prin +`combosql`, unificata daca se poate cu solutia aleasa pentru golul echivalent de validare (R7 aici, +S4e §6/varianta b acolo) — un singur mecanism de "construieste `poArticol` fara sursa in `crsarticole`", +nu doua independente. + +**R2 — Alegere aditiva sau inlocuitoare la a doua apasare a dialogului de facturi sursa** (sectiunea +3, punctul 3). Nu e tratat de nicio decizie existenta. **Recomandare**: aditiv, consistent cu +filozofia deciziei 16, dar cere cod VFP nou (bucla de filtrare pe facturi noi + `INSERT INTO +crsarticole`). + +**R3 — Ce se intampla cu `caut_facturi_multiple_client_articol` (alegerea per-articol) dupa ridicarea +lui `But_retur` la nivel de document.** Ramane ca optiune secundara sau se elimina? Nefolosit oriunde +altundeva in cod (verificat implicit — apelul e doar din `do_adauga_articol`, ramura `tlRetur`). +**Recomandare**: se elimina, pentru ca pastrarea ambelor cai ar insemna doua UX-uri pentru aceeasi +actiune, exact riscul pe care unificarea vrea sa il elimine. + +**R4 — `VANZARI_CORESP` pentru N.2 ridicat la document.** Gardat azi pe `ntip in (8,9)`, nu pe +continutul `listaid` — **reconfirmat independent** in aceasta sesiune, pe exact acelasi export +(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:14834-14836`): `WHEN pack_facturare.ntip in (8, 9) THEN +pack_facturare.scrie_corespondente_vanzari(3);`, in interiorul unui `CASE` fara alta conditie pe +`listaid`. Daca ramane asa, un document normal +cu retur ridicat la document nu va avea nicio legatura persistata catre facturile sursa alese — exact +ca azi cu `But_retur` per-articol, deci nu e o regresie, dar nici un castig. **Recomandare**: se lasa +neschimbat (fara cod Oracle nou), consistent cu decizia 35 (economie de cod de intretinut) — de +confirmat explicit cu Marius, pentru ca punctul 2 al S4f ("ridicarea la nivel de document") ar putea +crea asteptarea implicita ca acum exista si o legatura persistata, cand de fapt nu exista. + +**R5 — `Ct_clb_altele.cconditie` — semantica exacta neconfirmata** (sectiunea 2.1). Daca acest camp +chiar dezactiveaza azi re-alegerea dupa prima selectie, design-ul nou (sectiunea 3) trebuie sa decida +explicit sa **elimine** aceasta restrictie (pentru varianta aditiva) — altfel comportamentul nou ar +fi mai permisiv decat parea sa fie azi, fara sa fi fost o decizie constienta. **De verificat la +implementare**, cand clasa `ct_clb_cautare` devine accesibila (posibil in `COMUNROA`, in afara acestui +repo). + +**R6 — Semnul cantitatii pe liniile libere, verificare explicita ceruta de brief** (sectiunea 7): nu +e un risc nou — analiza arata ca semnul e o proprietate a documentului (tip 8/9/24), nu a liniei, +deci liniile libere il mostenesc automat corect. Se listeaza aici doar ca sa fie explicit ca punctul +a fost verificat, nu omis. + +--- + +## Handoff + +Cercetare incheiata fara sa fie nevoie de predare de context — un subagent de verificare +(`ab0be8e27f0fb97f3`, Explore/general-purpose) a esuat de doua ori sa returneze rezultate, apoi a +raspuns complet la a treia incercare (reluat prin `SendMessage`), confirmand independent 4 puncte deja +scrise pe cod propriu in raport: (1) `do_cauta_facturi` e declansat explicit de user, prin +`Ct_clb_altele`/`caut_ora.vc2:787-798,839-844` -> `do_cauta_altele` -> `do_cauta_facturi` +(`ofacturare.vc2:8940-8961,9173`), inainte de orice deschidere a `frm_facturare_articole`; (2) +`scrie_corespondente_vanzari(3)` e gardat exact pe `ntip in (8,9)` +(`ff_...sql:14834-14836`) — folosit sa inchid definitiv R4; (3) +`thisform.cListaIdArticoleRetur` poate acumula `ID_VANZARE` diferit per articol +(`ofacturare.vc2:12882-12892`); (4) `poDate.listaid` e acelasi camp la N.1 si N.2, dar cu rol +tranzitoriu la N.2 (salvat/restaurat) si definitiv doar la `do_scrie_articole` +(`ofacturare.vc2:13977-13979`). Niciuna din aceste confirmari nu a schimbat vreo concluzie a +raportului, doar le-a intarit sursa. + +**Corectie aplicata dupa livrare**, la avertismentul `team-lead` (bazat pe +`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si 7): sectiunea 5 (liniile libere) a +fost **rescrisa integral** — reteta `APPEND FROM` peste `crsarticole` (premisa initiala a brief-ului) +e inlocuita cu mecanismul validat de S4e (`APPEND BLANK` pe `crsfactura` + `combosql`, niciodata pe +`crsarticole`), campul de distinctie devine `crsfactura.id_c = 0` (nu absenta `PRET_ACHIZITIE` pe +`crsarticole`), si s-a adaugat un gol nou, negasit de S4e (care nu are dialog de gestiune pe +comanda/avize): punctul de intrare in `frm_articol_gest_factura` pentru o linie fara rand sursa in +`crsarticole` (risc R7, nou). Verdictul de la inceputul raportului, pasii de implementare (9), +verificarea (10) si sectiunile headless (11) au fost actualizate in consecinta. Restul raportului +(sectiunile 1-4, 6-8, 12 minus R1/R7) ramane neschimbat fata de livrarea initiala. + +Fisierul SQL folosit: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` +(cel mai recent din director). Nicio editare de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun +commit, nicio scriere Oracle. diff --git a/docs/cercetare/s4g_adaugare_articole_modificare.md b/docs/cercetare/s4g_adaugare_articole_modificare.md new file mode 100644 index 0000000..45ea33c --- /dev/null +++ b/docs/cercetare/s4g_adaugare_articole_modificare.md @@ -0,0 +1,437 @@ +# S4g — Adaugarea de articole la modificarea oricarui document (proiectare) + +Proiectare, nu implementare. Zero fisiere de cod atinse, zero write-back, zero commit. Pe Oracle +doar `SELECT`. Continua planul `docs\plan_13_unificare_formular_facturare.md` liniile 2494-2530 si +cercetarile `canal_cont_venit_fara_politica.md` (sectiunea "Proiectarea parametrului de cont +contabil"), `parametru_cont_contabilizeaza_articol.md`, `coresp_cont_venchelt.md`, +`optiune_firma_cont_debit.md`. + +**STARE: cercetare/proiectare incheiata.** + +Sursa PL/SQL verificata direct in aceasta runda: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` +(17217 linii, confirmat `wc -l`). Cod VFP citit: `COMUN\clase\ofacturare.vc2`, +`COMUN\programe\ofacturare_comun.prg` (ambele permise). Neatinse: `COMUN\clase\ofacturare_comun.vc2`, +`COMUN\programe\ofacturare_editare.prg` (perimetrul sesiunii #6). + +--- + +## 0. Premisele deja decise (nu se redeschid) + +- Povestea: a doua sursa de articole in meniu, "Alege din nomenclator...", disponibila si la + modificarea oricarui document deja emis, inclusiv auto (tip = -12, dar livrat separat, dupa). +- Decizia 20: din nomenclator se ofera si gestionabile, si negestionabile — nu se copiaza filtrul + `in_stoc = 0` al lui ROAAUTO. +- Decizia 21: contul de gestiune are fallback, nu NULL, nu refuz — mecanism separat de cel de aici + (priveste `id_gestiune`/`Cont` de gestiune al liniei, nu contul de venit), presupus deja rezolvat + cand derivarea din sectiunea 3 porneste. +- Decizia 27 (transportul inlocuit de decizia 34): contul de venit se **deriva** in VFP — + `CORESP_CONT_VENCHELT` pentru gestionabile (pe contul de gestiune al liniei), `NOM_ARTICOLE.CONT` + daca e 6xx/7xx pentru negestionabile, altfel `704`. +- Decizia 34: transportul e parametru nou, direct — nu prin `id_pol`/politica tehnica. Ocolul prin + `pack_preturi.adauga_politica_pret_art` e abandonat. +- Decizia 35: acelasi drum serveste si editarea prin regenerare — nu se proiecteaza a doua ruta de + contare. +- Decizia 36 (noua, 10.08.2026): `SCD` pe ramura fara politica nu e hardcodat `'4111'` literal — + vine dintr-o optiune de firma (`getoptiunefirma`), cu `4111` ca implicit dublu (rand in `OPTIUNI` + + fallback hardcodat in PL/SQL), tiparul deja folosit de `RF_CONT_INCASARE_*` in acelasi pachet. +- "Alte servicii" ROAAUTO ocoleste complet `contabilizeaza_articol` — exceptie, nu jumatate de + mecanism. +- Regula deja adoptata in #13: liniile libere nu intra deloc in `crsarticole` — `APPEND BLANK` + + `combosql` direct in `crsfactura`, ca `id_c` sa ramana `0` si `do_sterge` sa fie no-op prin + constructie (verificat aici ca acelasi tipar se aplica si campului `id_pol`, sectiunea 4). +- Ordine: intai ROAFACTURARE, apoi tip = -12. + +--- + +## 1. Punctul central: parametru vs politica, cine castiga + +### 1.1 Ce face azi codul, verificat direct pe sursa (nu preluat din rapoarte) + +`contabilizeaza_articol` (`ff_...:7173-7547`) primeste un singur parametru, +`detalii_articol VANZARI_DETALII_TEMP%ROWTYPE`. Primul lucru pe care il face (`:7275-7302`): + +```sql +BEGIN + SELECT COMPUS, ID_POL_ART + INTO V_COMPUS, V_ID_POL_ART + FROM VCRM_POLITICI_PRET_ART + WHERE ID_ARTICOL = detalii_articol.id_articol + AND ID_POL = detalii_articol.id_pol; +EXCEPTION + WHEN NO_DATA_FOUND THEN + ... + RAISE_APPLICATION_ERROR(-20000, 'Articolul ... nu este definit in politica de preturi ... (FACT-024)'); +END; +``` + +Daca `detalii_articol.id_pol` e `NULL`, comparatia `ID_POL = NULL` nu se potriveste niciodata — +`NO_DATA_FOUND` garantat, FACT-024 garantat. Acelasi rezultat daca `id_pol` e populat dar articolul +nu e in acea politica. **`cursor_articol`** (`:7218-7271`), care face tot lucrul real (`scrie_nota`, +`descarca_gestiune`, discount), e filtrat **pe exact aceeasi cheie** (`A.ID_POL = detalii_articol.id_pol +AND A.ID_ARTICOL = detalii_articol.id_articol`, `:7270-7271`) — daca `SELECT INTO` de mai sus a picat +in `NO_DATA_FOUND`, cursorul ar gasi tot zero randuri (aceeasi cauza). Deci punctul de intrare in +politica e unic si dublu-verificat (o data prin exceptie explicita, o data structural prin cursor). + +Design-ul deja facut (`canal_cont_venit_fara_politica.md:132-138`) propune sa infasoare **toata +functia** intr-un switch la nivel de metoda: + +``` +IF detalii_articol.cont_venit IS NOT NULL THEN + +ELSE + +END IF; +``` + +**Aceasta e deja, prin constructie, varianta "parametrul castiga necondiionat cand e populat"** — +cand `cont_venit` nu e `NULL`, executia nu se mai uita deloc la `id_pol`/politica, indiferent daca +acestea ar fi rezolvat sau nu. Team-lead-ul cere explicit sa se decida asta ca punct de proiectare, +nu sa ramana o consecinta implicita a formei codului — mai jos analiza celor 3 variante realiste. + +### 1.2 Cele 3 variante + +**A. Parametrul castiga mereu cand e populat** (= design-ul existent, ca atare). +- *Ce se strica*: daca, dintr-un motiv oarecare (bug VFP, sau — mai realist — decizia 35: la + **regenerare**, daca logica de derivare a lui `cont_venit` ar rula necondiionat pe toate liniile + documentului, inclusiv cele scrise initial prin "Cauta in lista de preturi..." cu `id_pol` real), + o linie ajunge la Oracle cu **ambele** populate, `SCC`-ul politicii reale e inlocuit tacut cu + contul derivat generic, `SCD` cu optiunea de firma (posibil diferita de `NOTE_CONTABILE.SCD` al + notei reale), `ASCD`/`ASCC`/`EXPLICATIE`/`CU_TVA` cu variantele generice ale ramurii noi. **Fara + nicio eroare** — factura se emite, dar cu alta contare decat cea configurata prin politica. Cel + mai periculos tip de defect: tace si trece verificarea vizuala (suma corecta, cont plauzibil). + +**B. Politica castiga mereu cand `id_pol` e populat** (indiferent daca rezolva sau nu). +- Ar cere schimbarea conditiei de switch din `cont_venit IS NOT NULL` in ceva de forma + `id_pol IS NULL AND cont_venit IS NOT NULL` — adica: daca exista `id_pol` pe linie, mergi pe + ramura veche necondiionat, chiar daca acel `id_pol` nu rezolva (caz FACT-024 de azi). +- *Ce se strica*: exact scenariul in care mecanismul nou ar fi cel mai util — o linie cu `id_pol` + populat gresit/stale (nu neaparat imposibil, doar improbabil in fluxul normal, vezi 1.3) — + ar continua sa primeasca FACT-024 desi VFP a trimis explicit un cont de rezerva. Varianta cea mai + fragila: defineste "castigatorul" pe **prezenta** unui camp, nu pe **rezolvarea** lui. + +**C. Parametrul e folosit doar cand politica nu rezolva** (fallback real, nu switch pe camp). +- Cere ca `SELECT INTO ... FROM VCRM_POLITICI_PRET_ART` (1.1) sa ramana **necondiionat**, exact ca + azi (deja rulat pe fiecare linie, cost zero suplimentar), dar `EXCEPTION WHEN NO_DATA_FOUND` sa + verifice `cont_venit`: daca e populat, ramura noua; daca nu, `RAISE FACT-024` ca azi. Semantic + corect — politica, cand exista si rezolva, nu e niciodata inlocuita tacut; parametrul e strict ce + a fost gandit sa fie: o plasa pentru cazul in care politica lipseste. +- *Cost real*: restructurare interna a functiei, nu doar un `IF` la inceput. Codul de dupa blocul + `EXCEPTION` verifica azi `IF V_COMPUS = 1 THEN ... ELSE END IF` + (`:7305,7391`) — `V_COMPUS` ramane `NULL` daca s-a intrat pe `EXCEPTION`, deci "cade" oricum pe + ramura `ELSE` care deschide `cursor_articol` (gaseste zero randuri, nu declanseaza nimic, dar nici + ramura noua). Trebuie introdus un flag explicit (`V_ARE_POLITICA`/similar) propagat din interiorul + `EXCEPTION` pana la punctul de decizie, marind suprafata modificata fata de varianta A (care + atinge doar granita functiei, cu restul intact). + +### 1.3 Ar putea sa apara vreodata, in fluxul normal, ambele populate? + +Verificat direct in aceasta runda (nu presupus): `crsfactura` (cursorul VFP din care se scrie orice +linie) are `id_pol N(20) Null` (`COMUN\programe\ofacturare_comun.prg:1775`) — camp nullable, deci la +`APPEND BLANK` (mecanismul liniilor libere, confirmat de S4e ca acelasi gest se foloseste si pentru +"Alege din nomenclator...") ramane `.NULL.` implicit, nu `0`. La scriere, `poArt.id_pol` (scatter +din randul respectiv) ajunge in apelul RPC ca `Nvl(Alltrim(Str(poArt.id_pol)),[NULL])` +(`ofacturare.vc2:14072`, identic la `:18107`) — literal SQL `NULL` cand campul e `.NULL.`. **O linie +"din nomenclator" trimite deci `id_pol = NULL` la Oracle prin constructie, nu doar prin conventie de +utilizare** — cursorul de cautare al nomenclatorului (`caut_articol`, `ocautare.prg`, deja confirmat +de `coresp_cont_venchelt.md` sectiunea 9d) nu are coloana `id_pol` in output, deci nu exista niciun +punct in care combosql-ul ar putea popula acest camp cu o valoare reala pentru o astfel de linie. + +Ramane un singur scenariu real de ambiguitate: **regenerarea** (decizia 35). La regenerare, toate +liniile documentului (cele scrise initial prin "Cauta in lista de preturi...", cu `id_pol` real, **si** +cele adaugate prin "Alege din nomenclator...", fara `id_pol`) trec din nou prin acelasi drum de +scriere. Daca logica VFP care calculeaza `cont_venit` (sectiunea 3) ar rula **necondiionat** pe toate +liniile din grid, in loc sa fie conditionata explicit de "linia nu are `id_pol`", ar produce exact +combinatia periculoasa din varianta A. **Asta nu e un risc Oracle — e un risc de implementare VFP**, +dar exact tipul de risc pe care design-ul Oracle trebuie sa nu-l agraveze printr-un switch care il +face invizibil. + +### 1.4 Recomandare + +**Niciuna dintre cele 3 variante pure, ci varianta A (deja proiectata, cea mai simpla) plus o garda +explicita pe combinatia ambigua**, nu o rezolvare tacita in orice directie: + +```sql +IF detalii_articol.cont_venit IS NOT NULL THEN + IF detalii_articol.id_pol IS NOT NULL THEN + RAISE_APPLICATION_ERROR(-20000, + 'Articolul ' || detalii_articol.id_articol || + ' are simultan politica de pret (' || detalii_articol.id_pol || + ') si cont de venit calculat — conflict netratat (FACT-0xx)'); + END IF; + +ELSE + +END IF; +``` + +Motivare, in ordine: +1. **Combinatia nu trebuie sa apara niciodata in fluxul normal** (1.3) — `id_pol` ramane `NULL` prin + constructie pentru orice linie scrisa prin gestul "linie libera". Daca totusi apare, e un semnal + ca ceva e stricat in populate-ul liniei (VFP a trimis ambele campuri, posibil din cauza logicii + de regenerare descrisa la 1.3) — situatie de bug de investigat, nu de "rezolvat" silentios. +2. **Variantele B si C rezolva combinatia in cate o directie fixa** — ambele ascund o eroare de date + in loc sa o semnaleze: B ar putea reintroduce FACT-024 pe o linie unde VFP a oferit deja o + solutie; A fara garda ar inlocui tacut o politica reala. O garda explicita e singurul comportament + care nu presupune ca stie mai bine decat datele ce s-a intamplat. +3. **Cost de implementare minim**: un singur `IF` suplimentar, la intrarea in ramura deja proiectata + — nu restructureaza funcia (spre deosebire de varianta C), nu schimba conditia switch-ului + principal (spre deosebire de B). +4. **Compatibilitate/regresie zero pe apelantii de azi**: cand `cont_venit` e `NULL` (toti apelantii + existenti, care nu cunosc inca acest parametru), executia intra direct pe `ELSE` — garda nu se + evalueaza niciodata, comportamentul e identic bit-cu-bit cu azi. Cand `cont_venit` e populat si + `id_pol` e `NULL` (cazul intentionat, "din nomenclator"), garda trece nevazuta, ramura noua + ruleaza normal. + +**Numele codului de eroare** (`FACT-0xx` in schita de mai sus) ramane de ales de Marius, distinct de +`FACT-024` (care ramane, neschimbat, pentru cazul "niciuna din cele doua" — linie fara politica si +fara cont). + +--- + +## 2. Suprafata de schimbare pe Oracle + +Deja proiectata si verificata adversarial in `canal_cont_venit_fara_politica.md` si +`parametru_cont_contabilizeaza_articol.md`; rezumat + completarea cu decizia 36 si garda de la +sectiunea 1.4: + +1. **Coloana noua**: `VANZARI_DETALII_TEMP.CONT_VENIT VARCHAR2(4) NULL` (fara `NOT NULL`, fara + `CHECK` — acelasi tipar ca `ACT_TEMP.SCC`). Optional, recomandat pentru trasabilitate: + `VANZARI_DETALII.CONT_VENIT`, plus extinderea listei explicite de coloane din + `scrie_in_vanzari` (`PACK_FACTURARE:13705-13757`, confirmat ca listeaza 24 coloane explicit, nu + `SELECT *` — de extins cu o a 25-a daca se alege trasabilitatea). +2. **Parametru nou pe `adauga_articol_factura`** (`ff_...:4989-5015`), la coada, dupa `V_LOT`: + `V_CONT_VENIT IN VARCHAR2 DEFAULT NULL` — apelul VFP existent (pozitional, se opreste la + `V_LOT`, confirmat la doua locuri, `ofacturare.vc2:14069-14091` si `:18089-18114`, doua clase + distincte cu aceeasi metoda, nu o duplicare) ramane neschimbat, echivalent cu "trimite NULL". + Plumbing identic cu `V_CONT`/`V_CONT2`, o coloana in plus in `INSERT INTO VANZARI_DETALII_TEMP` + (`ff_...:5222-5282`). +3. **`contabilizeaza_articol` insasi NU primeste parametru nou** — ramane `VANZARI_DETALII_TEMP%ROWTYPE`; + coloana noua ajunge automat prin `SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP` + (`:6063`), fara nicio schimbare la cei 3 apelanti interni (`scrie_factura2`, + `scrie_factura_avize_retur`, `scrie_aviz_retur`). +4. **Ramura noua in `contabilizeaza_articol`**, cu garda de la sectiunea 1.4: + - `SCC := detalii_articol.cont_venit`. + - `SCD` (decizia 36): `PACK_SESIUNE.getoptiunefirma('FACT_SCD_ARTFPRET')`, cu fallback hardcodat + `'4111'` cand optiunea lipseste/e goala — optiune noua in tabelul `OPTIUNI` + (`VARTYPE='CHARACTER'`, script de migrare idempotent, tiparul exact al `RF_CONT_INCASARE_*`, + deja folosit in acelasi pachet, `optiune_firma_cont_debit.md` sectiunea 3.4). Ramurile de aviz + raman `'418'`/`'461'`, neschimbate. + - `ASCD`/`ASCC` din `GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD/V_SCC)` — acelasi + fallback deja folosit necondiionat pe ramurile de aviz azi. + - `EXPLICATIE` din `detalii_articol.explicatia` (parametru deja existent, `V_EXPLICATIE`). + - `ID_VENCHELT`/`ID_SECTIE` din `pack_facturare.nid_venchelt`/`nid_sectie_stoc` (fallback de + sesiune deja folosit ca prioritate azi). + - `IN_VALUTA` din `pack_facturare.nin_valuta` (parametru obligatoriu, fara `DEFAULT`, mereu + curent — nu o presupunere, sursa solida). + - `CU_TVA`: hardcodat `1`, **cu efect colateral real si masurat, nu "inofensiv"** — + `parametru_cont_contabilizeaza_articol.md` sectiunea 2b arata ca pe combinatia specifica "linie + fallback cu TVA 0% + discount global pe factura + acea linie e prima/singura vazuta" valoarea + forteaza `nproc_tva_max`/`nid_jtva_coloana` pentru toata linia de discount a facturii. Ramane + "de confirmat cu Marius", cu argumentul mai tare decat in propunerea initiala. + - Apeluri o singura data (nu in bucla): `scrie_nota(...)`, `descarca_gestiune(...)` (garda + identica, `nscadere_stoc=1 AND id_gestiune<>-1000 AND in_stoc=1`), discount (garda identica). + - Articole compuse (`V_COMPUS=1`) exclus structural — o linie fara `id_pol` nu poate avea + `ID_POL_ART`, deci intrebarea "e compus" nu se poate pune pe aceasta ramura (confirmat pe + schema view-ului, nu presupus). +5. **Ordinea de deploy**: DB inainte de EXE. Parametrul nou are `DEFAULT NULL`, deci EXE-ul vechi + ruland pe DB-ul nou functioneaza neschimbat (nu trimite parametrul). EXE-ul nou ruland pe DB-ul + vechi (fara coloana/parametru) ar esua la primul apel cu al 27-lea argument — deci EXE-ul nou + **nu** poate merge inaintea migrarii DB. Ordine standard, fara surpriza. +6. **Regenerarea (decizia 35)**: `initializeaza_date_factura` reseteaza starea de sesiune + (`DELETE FROM VANZARI_DETALII_TEMP`, `nid_act := 0`) la fiecare emitere — nimic din ramura noua + citeste vreo stare presupunand "documentul e nou". Calea Oracle e identica la regenerare, **cu o + exceptie separata, in alt pachet**: reemiterea cu acelasi `ID_FACT` ar da azi `ORA-00001` pe + `PK_DOCUMENTE` in `PACK_CONTAFIN.SET_IDFACT` — problema deja proiectata separat + (`idfact_refolosire_si_documente.md`), nu intersecteaza si nu invalideaza design-ul de aici, dar + ramane o bucata de lucru distincta, necesara pentru ca regenerarea sa fie completa. +7. **Gol neadresat inca**: ramura `ntip = 4` ("factura din avize", `:7520-7537`) apeleaza + `scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount — design-ul nu specifica ce se + intampla pe fallback aici. In practica improbabil (o linie de aviz sursa fara politica n-ar fi + ajuns la factura pe acest drum, `adauga_articol_factura:5096` cere deja `A.ID_POL = V_ID_POL` pe + avizul sursa), dar de exclus explicit inainte de implementare, nu de presupus tacit. + +--- + +## 3. Derivarea contului in VFP + +### 3.1 Sursele, in ordine (decizia 27) + +1. **Articol gestionabil** (are `id_gestiune` valid, `in_stoc=1`): `CORESP_CONT_VENCHELT.CONT_VENIT`, + cautat pe **contul de gestiune al liniei** (`poArt.Cont`, camp deja gathered pe `poArticol` in + ambele metode `do_scrie_articole`, confirmat la `ofacturare.vc2:12945,13835` si echivalentele in + `frm_facturare_articole2`). Cheia de join e mereu `CONT` (nu `ID_GESTIUNE`, nu `TIP_DOC`) — + confirmat pe 4+ consumatori Oracle reali ai tabelului (`pack_vin`, `pack_devize`, + `gestiune_pack_gest_import`, un raport din 2026), niciodata pentru `CONT_VENIT` insa — aceasta + propunere ar fi **primul consumator real** al coloanei, desi populata din 2023 (20 conturi de + gestiune uzuale: 301-303/3021-3028, 331/332, 341, 345/346/348, 361, 371, 381). View-ul VFP-safe e + `VCORESP_CONT_VENCHELT` (`STERS=0` deja filtrat in view). +2. **Articol negestionabil**, sau gestionabil dar corespondenta nu rezolva (rand lipsa pentru acel + `CONT`): `NOM_ARTICOLE.CONT`, **doar daca** primul caracter e `6` sau `7` (`verific_cont` nu + restrange campul la o clasa, deci poate contine legitim orice cont valid, inclusiv 6xx/7xx pe un + articol negestionabil). +3. **Fallback final**: literal `'704'`. Fara reteta de cod deja scrisa in `pack_facturare` pentru + aceasta valoare, dar cu un precedent independent de "704 = cont implicit client" in alt subsistem + (`anaf_efactura.vc2:8376`, `cconte = 704`) — nu o inventie, dar cod nou. + +### 3.2 Unde sta codul si cand ruleaza + +Locul natural: chiar in `do_scrie_articole` (ambele clase, `frm_facturare_articole` si +`frm_facturare_articole2`, confirmat ca 2 locuri distincte de atins, nu 1), imediat inainte de +construirea textului RPC pentru fiecare linie, **conditionat explicit de `Empty(Nvl(poArt.id_pol,0))`** +— nu la momentul adaugarii liniei in grid. Doua motive: +- **Regenerarea (decizia 35)** re-parcurge toate liniile la fiecare scriere; derivarea la momentul + scrierii (nu la adaugare) garanteaza ca valoarea reflecta starea curenta a corespondentelor/ + nomenclatorului, nu una inghetata la momentul in care linia a fost adaugata initial in grid. +- **Conditionarea explicita pe `id_pol` gol** (nu pe "linia vine din nomenclator" ca marcaj separat) + e chiar garda ceruta la sectiunea 1.3-1.4: o linie cu `id_pol` populat nu trebuie sa primeasca + niciodata o valoare pe `cont_venit`, indiferent de sursa ei — un singur punct de decizie, in + oglinda exacta cu conditia pe care Oracle o va verifica la randul lui (sectiunea 1.4). + +### 3.3 Ce se intampla cand derivarea esueaza + +- **Corespondenta nu are rand pentru acel `CONT`** (gestiune fara corespondenta configurata): cade pe + pasul 2 (`NOM_ARTICOLE.CONT`), nu pe eroare. +- **`NOM_ARTICOLE.CONT` nu incepe cu 6/7** (cont de gestiune sau alt tip pe un articol negestionabil, + configurare atipica): cade pe pasul 3 (`704`), nu pe eroare. +- **Niciodata `NULL`/gol trimis la Oracle pentru o linie "din nomenclator"** — ultimul pas e un + literal, nu o interogare care poate esua. Aceasta e proprietatea care face garda de la 1.4 + inofensiva pe fluxul normal: `cont_venit` e *intotdeauna* populat cand `id_pol` e gol, deci + ramura noua din Oracle ruleaza mereu cand ar trebui, iar FACT-024 (ramura veche) nu mai poate fi + atinsa de o linie din nomenclator dupa implementare — dispare exact problema pe care povestea o + rezolva. +- **Nu e proiectata aici** (ramane de decis la implementare, in afara acestei povesti): daca vreo + validare suplimentara ar trebui sa verifice ca valorile derivate (`CONT_VENIT` din corespondenta, + `NOM_ARTICOLE.CONT`) sunt conturi valide in planul de conturi al anului curent — azi + `verific_cont` exista ca mecanism (`oproceduri_comune.prg:2389-2415`) dar nu e cablat automat pe + acest drum nou. + +--- + +## 4. Fluxul in formularul unificat + +1. Utilizatorul deschide un document deja emis la modificare (`frm_modific2024`/formularul unificat, + in afara perimetrului #6/omodificari.vc2, care nu se atinge aici) si alege din meniu "Alege din + nomenclator..." (a doua sursa, langa "Cauta in lista de preturi...", ambele reduse la acelasi + gest UI conform S4b/S4e). +2. Gestul UI: `Select crsfactura / APPEND BLANK` (tiparul deja in productie la + `frm_facturare_articole2.But_nou1.do_adauga`, `ofacturare.vc2:17118-17122`), focus pe celula de + cautare, `combosql` legat pe cursorul filtrat pe nomenclator (`caut_articol`/echivalent, fara + coloana `id_pol` in output — confirmat, sectiunea 1.3). +3. **`crsfactura.id_c` ramane `0`** (valoarea implicita a campului `N(20)` fara `Null`, la + `APPEND BLANK`) — valoare pe care Oracle nu o produce niciodata pentru `id_c`. `do_sterge` + (potriveste pe `id_c`) e no-op prin constructie pe aceasta linie, fara nicio modificare de cod — + exact regula deja adoptata in #13, reconfirmata aici pentru sursa "nomenclator" (S4e o stabilise + pentru sursa "lista de preturi pe comanda"; acelasi cursor de scriere `crsfactura`, acelasi + mecanism, doar alt cursor de cautare in fata). +4. **`crsfactura.id_pol` ramane `.NULL.`** (camp `N(20) Null`, `ofacturare_comun.prg:1775`), pentru + ca sursa de cautare (nomenclator) nu are aceasta coloana in output — confirmat direct in aceasta + runda (sectiunea 1.3), nu presupus. +5. Utilizatorul completeaza cantitate/pret (campuri editabile inline pe grid, tipar deja existent). +6. La `do_scrie_articole` (salvarea documentului), pentru fiecare linie cu `Empty(Nvl(poArt.id_pol,0))`, + se ruleaza derivarea din sectiunea 3 si se populeaza al 27-lea argument pozitional al apelului RPC + catre `adauga_articol_factura` cu valoarea calculata; pentru restul liniilor (cele cu `id_pol` + populat, "din lista de preturi"), argumentul ramane `NULL` — comportament identic cu azi. +7. Documentul se salveaza; pe Oracle, `contabilizeaza_articol` ruleaza ramura noua (sectiunea 1-2) + pentru liniile fara politica, ramura veche neschimbata pentru restul. +8. **Editarea** (regenerare, decizia 35): acelasi `do_scrie_articole`, aceeasi conditie pe `id_pol`, + acelasi rezultat — nu exista o a doua cale de contare pentru liniile deja existente pe document. + +--- + +## 5. Ce ramane in afara (partea auto) + +- `tip = -12` (facturare auto, ROAAUTO) — livrat separat, dupa ce mecanismul de mai sus e stabil pe + documentele ROAFACTURARE (ordinea deja decisa in plan). +- "Alte servicii" din ROAAUTO ramane exceptia ei — ocoleste complet `contabilizeaza_articol`, nu + foloseste si nu va fi migrata sa foloseasca acest mecanism ca parte a acestei povesti; daca se + decide vreodata unificarea, e o poveste separata. +- Gridul read-only din `frm_modific2024` (al #6) — S4g incepe dupa ce #6 se termina (decizia 30), + nicio proiectare de aici nu presupune sau modifica acel perimetru. +- Validarea de cantitate/stoc pentru o linie liberă la modificare (analogul sectiunii 6 din + `s4e_lista_preturi_pe_sursa.md`, dar pentru nomenclator, nu lista de preturi) — nu re-proiectata + aici, acelasi gol semnalat deja de S4e se aplica identic si pe sursa "nomenclator"; de rezolvat cu + acelasi mecanism (varianta (b), reutilizare `do_verifica_articol` cu `poArticol` explicit). + +--- + +## 6. Teste minime + +Pe langa cele deja listate in `canal_cont_venit_fara_politica.md` sectiunea 6 si +`nota_contabila_fara_politica.md`, specifice acestei povesti: + +1. **Factura normala cu politica, parametru NULL** — verifica ACT identic cu azi (regresie de baza). +2. **Aviz cu articol adaugat din nomenclator (fara politica)** — `SCD` ramane `'461'`/`'418'` + (ramurile de aviz raman hardcodate independent de ramura noua/veche), nu `getoptiunefirma`. +3. **`ntip = 46`** (daca exista pe fluxul de modificare vizat) — `scrie_nota` nu se cheama pe acea + ramura; de confirmat ca ramura noua nu e atinsa deloc pentru acest tip. +4. **Articol gestionabil din nomenclator, cu corespondenta configurata** — un singur rand `ACT`, + `SCC` = `CORESP_CONT_VENCHELT.CONT_VENIT` pentru contul de gestiune al liniei, `SCD` = optiunea + de firma (sau `4111` daca optiunea lipseste), linie TVA scrisa separat. +5. **Articol negestionabil din nomenclator, fara corespondenta aplicabila** — `SCC` = `NOM_ARTICOLE.CONT` + daca incepe cu 6/7, altfel `'704'`. +6. **Articol negestionabil** (`id_gestiune=-1000`) cu `cont_venit` populat — `descarca_gestiune` NU + ruleaza. +7. **Discount pe o linie din nomenclator** — foloseste `V_ASCD`/`V_CU_TVA` calculate in ramura noua. +8. **Document in valuta** (`nin_valuta=1`) cu linie din nomenclator — `IN_VALUTA=1` pe nota, + `SUMA_VAL` completat. +9. **Linie fara politica si fara cont derivat** (nu ar trebui sa se poata construi prin UI, dar de + testat direct pe Oracle) — FACT-024 tot apare, regresie negativa: garda nu s-a slabit. +10. **Linie cu ambele populate** (construita direct la nivel de apel Oracle, nu prin UI — testeaza + garda de la sectiunea 1.4, nu fluxul normal) — noua eroare explicita apare, nu o rezolvare + tacita in nicio directie. +11. **Regenerare pe un document mixt** (o linie din lista de preturi cu `id_pol`, o linie din + nomenclator fara `id_pol`) — dupa regenerare, prima linie tot cu `SCC` din politica, a doua tot + cu `SCC` derivat; niciuna nu trece pe ramura celeilalte. +12. **`id_c` si `do_sterge`** (mostenit din tiparul S4e, de re-verificat pentru sursa nomenclator): + o linie din nomenclator adaugata la modificare, apoi stearsa inainte de salvare — no-op pe + `crsarticole`, fara efect asupra vreunei linii reale a documentului. + +--- + +## 7. Riscuri si de decis de Marius + +1. **Garda explicita pe combinatia `id_pol` + `cont_venit` ambele populate** (sectiunea 1.4) — e o + recomandare de proiectare a acestui raport, nu o decizie deja luata de Marius; codul exact al + erorii si numarul `FACT-0xx` raman de ales. +2. **`CU_TVA` hardcodat `1`** — are un efect colateral masurat (nu doar teoretic), prin + `nproc_tva_max`, pe combinatia specifica linie-scutita + discount global de factura. De confirmat + explicit, nu de presupus inofensiv. +3. **Decizia 36 (`SCD` prin optiune de firma)** — numele exact al cheii (`FACT_SCD_ARTFPRET` propus), + daca se adauga validare de cont la citire (niciun precedent existent nu valideaza), si + `PROGRAME` (restrans la ROAFACTURARE sau extins ca `RF_CONT_INCASARE_*`) raman decizii deschise + in `optiune_firma_cont_debit.md` sectiunea 4. +4. **Ramura `ntip=4`/"factura din avize" pe fallback** (sectiunea 2 punctul 7) — probabil imposibil + de atins prin acest drum (avizul sursa cere deja `id_pol`), dar nu exclus explicit prin cod sau + test — de confirmat inainte de implementare. +5. **Suprafata de regresie in restul suitei** (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB) pentru + `adauga_articol_factura` — pe baza cercetarilor existente, aceste produse folosesc proceduri + separate (`_deviz`/`_stoc`) sau alt pachet complet (`pack_acn`), deci risc asteptat zero, dar + cautarea directa in `COMUN`-urile lor pentru un apel simplu la `adauga_articol_factura(` nu s-a + terminat in nicio runda anterioara — de re-rulat inainte de implementare, nu de presupus incheiata. +6. **Validarea de cantitate/stoc pentru linia libera la modificare** (sectiunea 5) — gol mostenit + de la S4e, nu inchis aici, de rezolvat cu acelasi mecanism pe ambele surse (lista de preturi si + nomenclator). +7. **Trasabilitatea `CONT_VENIT` pe `VANZARI_DETALII`** (sectiunea 2 punctul 1) — optionala pentru + ca mecanismul sa functioneze, dar fara ea coloana nu se pastreaza dupa fapt; de decis daca merita + extinderea listei explicite de coloane din `scrie_in_vanzari`. +8. **`idfact_refolosire_si_documente.md`** — obstacol real pentru ca regenerarea (decizia 35) sa fie + completa (reemitere cu acelasi `ID_FACT`), in `PACK_CONTAFIN`, nu in `pack_facturare` — nu + blocheaza design-ul de aici, dar e o bucata de lucru separata, necesara inainte ca "editare = + regenerare" sa functioneze end-to-end. + +--- + +## STARE / CE RAMANE + +Cercetare/proiectare incheiata pentru toate cele 6 puncte cerute in brief, cu punctul central +(sectiunea 1) tratat explicit ca decizie de proiectare (nu doar consecinta implicita a codului +existent deja schitat in rundele anterioare). Nicio editare de cod, niciun `git_sync.ps1`/ +`txt2vcx.ps1`, niciun commit, nicio scriere pe Oracle. Context consumat moderat in aceasta runda — +nu a fost necesara predarea de mijloc de sesiune. + +**Ce nu s-a putut inchide complet, de reluat separat**: +- Cautarea exhaustiva a apelantilor `adauga_articol_factura` simplu in restul suitei (risc 5, + sectiunea 7) — de re-rulat, nu s-a terminat in nicio runda anterioara din lipsa de timp, nu din + cauza unei erori. +- Confirmarea explicita a lui Marius pe hardcodarile/optiunile ramase deschise (`CU_TVA=1`, numele + cheii de optiune, garda de la sectiunea 1.4) — sunt recomandari argumentate, nu decizii finale. diff --git a/docs/cercetare/s5_acoperire_tipuri.md b/docs/cercetare/s5_acoperire_tipuri.md new file mode 100644 index 0000000..d64afd1 --- /dev/null +++ b/docs/cercetare/s5_acoperire_tipuri.md @@ -0,0 +1,502 @@ +# Cercetare + proiectare — S5: acoperirea tuturor tipurilor de document + +Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara +scriere pe Oracle — numai `SELECT`), pentru povestea **S5** din +`docs\plan_13_unificare_formular_facturare.md` (`#### S5`). Nu s-a atins +`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul altei +sarcini in lucru) — doar citite cand au aparut in cautari (n-a fost cazul). + +Sursa VFP: `COMUN\clase\ofacturare.vc2` (`frm_facturare_articole` = formularul de productie, +`:10968-15739`; `frm_facturare_articole2` = prototipul, `:15741-19355` — **nu e subclasa** a +primului, mostenesc separat din `_frmbase`, au `Init` propriu fiecare). Sursa de rutare: +`COMUN\programe\ofacturare.prg` (`factureaza` = standard, `:81-...`; `factureaza2` = prototip, +`:660-...`). Referinta de tipuri: `COMUN\docs\tipuri_documente_facturare.md`. + +## Verdict (rezumat, citeste asta primul) + +1. **`Do Case`-ul din `frm_facturare_articole.Init` (`ofacturare.vc2:15109-15248`) acopera 21 de + valori de `tip`** (grupate in 14 ramuri): `1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,28,29,41,42,47`. +2. **Patru tipuri sunt reale, reachable prin `factureaza()`, dar nu apar in niciun `Case`** — + pierd titlu, cap de coloana, mesaj de stoc, vizibilitate discount, eliminare `cSerie`: **`45` + (factura restaurant), `48`/`49` (custodie cu/fara descarcare K), `52` (contract, factura fiscala + valuta)**. Confirmat pe meniuri (`Meniuri\politica.mn2:18,26,29`, `Meniuri\contracte.mn2:18`) si + pe rutarea cursorului (`ofacturare.prg:271-282`), care **le recunoaste** — doar Init-ul + formularului de articole nu le-a "prins" niciodata. **Nu doar `52`**, cum semnalase raportul S4b — + sunt patru, nu unul. +3. **Cinci tipuri din referinta (`43,44,46,50,51`) nu sunt niciodata pasate lui `factureaza()` + in tot arborele `D:\ROA`** (cautare exhaustiva, zero potriviri) — nu ajung la acest formular deloc + azi. `50` e marcat explicit "in lucru" in sursa; `51` (ROAACNPRO) foloseste `id_set=50100`, un + interval separat de restul (`25000+`), semn ca provine dintr-un flux Oracle direct al altui produs, + nu din `factureaza()` local. +4. **Descoperire centrala, dincolo de ce cerea misiunea**: prototipul (`frm_facturare_articole2.Init`, + `:18988-19080`) **nu e o versiune partiala a Do Case-ului standard — e aproape gol**. Singurul + lucru pe care-l face pe tip e sa aleaga cuvantul `lcTipDoc` ("factura" vs "aviz"), pe o lista + **mai scurta** (lipseste `24`). Nu seteaza titlu (nu exista `lb_titlu_alb_b121` in tot fisierul + prototipului), nu schimba capul coloanei de cantitate, nu schimba mesajul de stoc, nu ascunde + discountul, **nu are deloc conceptul de coloana `cSerie`** (gridul prototipului, `grd_factura`, + n-are niciodata `RemoveObject('cSerie')` — cautare pe tot fisierul, zero potriviri in intervalul + `15741-19355`). Daca formularul unificat porneste de la prototip (cum indica decizia de baza a + planului), **toata diferentierea pe tip trebuie reconstruita de la zero**, nu doar completata. +5. **Rutarea cursorului diverge intre standard si prototip pe trei tipuri, nu doua**: `23` + (confirmat deja de S4/S4b), plus **`52` si `24`, gasite aici** — pe prototip, `Case Inlist(tnTip, + 2, 26, 6)` (`ofacturare.prg:762`) **omite `52`** fata de standard (`Inlist(tnTip, 2, 26, 6, 52)`, + `:283`), si `Case Inlist(tnTip, 8, 9)` (`:819`) **omite `24`** fata de standard (`Inlist(tnTip, 8, + 9, 24)`, `:307`). Daca cineva ar factura tip `52` sau `24` prin prototip azi (`gnFacturareNou=1`), + `lcSqlCursor` ar ramane nedefinit — eroare, nu doar diferenta de comportament. +6. **Tipurile `26` si `52` n-au niciun bookkeeping `crsarticole`** (nici Rol A, nici Rol B) — + inchis aici punctul lasat deschis de raportul S4 punctul 2: excluderea lor din toate cele patru + `Case`-uri de bookkeeping din `do_adauga_articol`/`do_sterge` e totala (Do Case exhaustiv, fara + ramura implicita), nu doar "neconfirmata". +7. **`27` si `30` raman pe calea lor** — confirmat pe cod, cu o nuanta importanta pentru `30`: nu + e un formular separat, ci **acelasi `frm_facturare_articole`, instantiat si trecut prin acelasi + `Init`/`Do Case`, dar niciodata aratat** (`ofacturare.prg:444-453`: calculeaza totalurile, apasa + programatic `but_termin1.Click()`, apoi `Release()`, fara `Show()`). Tip `30` **e afectat de + golurile din `Do Case`** exact ca oricare alt tip needitat — doar ca defectele (titlu, cap de + coloana) nu se vad niciodata pe ecran. + +--- + +## 0. Metoda de verificare — lista de referinta + +Lista completa de tipuri vine din `COMUN\docs\tipuri_documente_facturare.md` (sursa unica, deja +verificata pe cod de acea cercetare). Tipuri incluse in tabelul de mai jos: toate cele din sectiunile +"Facturi" si "Avize de expeditie" (documentele care intra prin `frm_facturare_articole`/`2`). +Sectiunea "Tipuri negative" (`-1..-13`) **nu intra in acest formular** — sunt scrise de alte produse +(ROAGEST, ROAAUTO) prin propriile lor fluxuri, niciodata prin `factureaza()` din ROAFACTURARE +(cautare exhaustiva `factureaza(-` in tot `D:\ROA`, zero potriviri) — declarate aici explicit **ramase +pe calea altui produs**, nu "neacoperite". + +--- + +## 1. Tabelul complet, tip cu tip + +Coloane: `tip` = `VANZARI.TIP` · **Case propriu** = are ramura proprie in `frm_facturare_articole.Init` +(`ofacturare.vc2:15109-15248`)? · **titlu** = ce seteaza pe `lb_titlu_alb_b121.Caption` · **cap +cantitate** = ce seteaza pe `grd_articole.cCantitate.header1.Caption` (implicit ramane cel din +design, `[Cantitate in stoc]`, daca nu e suprascris) · **mesaj stoc** = `This.cmesaj_cantitate` · +**discount** = `clb_discount.Visible` · **`cSerie`** = coloana ramane (`Da`) sau se scoate (`Nu`) · +**butoane** = ce se face vizibil (`but_urmator_tot1`/`but_retur`, ambele `.F.` la design) · **cursor +standard** = ramura din `factureaza` (`ofacturare.prg:266-308`) · **cursor prototip** = ramura din +`factureaza2` (`:748-822`, gol daca lipseste) · **Rol crsarticole** = A (cantitate ramasa de +facturat) / B (plafon de sesiune) / — (fara bookkeeping), din `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md` +· **stare S5** = acoperit azi / gol de completat / ramas pe calea veche / neatins. + +### Facturi + +| tip | denumire | Case propriu | titlu | cap cantitate | mesaj stoc | discount | `cSerie` | butoane vizibile | cursor standard | cursor prototip | Rol | stare S5 | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | lista de preturi (lei) | **Da** `:15122` (grup 1,5,7,10) | — (implicit) | "Cantitate in stoc" | "nu e pe stoc!" | vizibil (implicit) | **Nu** (scoasa) | `but_retur` | `cursor_preturi` (grup 1,22,5,29,7,10,23), `:279-282` | `cursor_preturi` (grup 1,22,5,29,7,10), `:758-761` | B (gestionabil, `1,22,29`) | acoperit azi, de portat | +| 2 | contract (lei) | **Da** `:15129` (grup 2,6) | "FACTURA PE CTR. …" | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | — | `cursor_contract` (grup 2,26,6,52), `:283-291` | `cursor_contract` (grup 2,26,6 — **fara 52**), `:762-769` | B doar pt. `opt_facturare=0`; — pe rest | acoperit azi, de portat | +| 3 | comanda | **Da** `:15144` | "FACTURA LA COMANDA …" | "Cantitate comandata" | "cantitate comandata facturata" | vizibil | **Nu** | `but_urmator_tot1` | `cursor_comanda` (grup 3,21,25,28,42,47), `:292-293` | `cursor_comanda` (acelasi grup), `:771-773` | **A** | acoperit azi, de portat | +| 4 | din avize | **Da** `:15151` | "FACTURA DIN AVIZE" | — (implicit) | "cantitate de pe aviz facturata" | **ascuns** (`.F.`, `:15154`) | Da (nu se scoate) | `but_urmator_tot1` | `cursor_avize`, `:294-295` | `cursor_avize`, `:774-775` | **A** | acoperit azi, de portat | +| 5 | lista de preturi valuta | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat | +| 6 | contract valuta | **Da** `:15129` (grup 2,6) | "FACTURA PE CTR. …" | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | — | `cursor_contract` | `cursor_contract` | B partial (ca 2) | acoperit azi, de portat | +| 7 | credit note | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat | +| 8 | retur factura lei | **Da** `:15236` (grup 8,9) | — | "Cant. max. de returnat" | "nu se mai poate returna" | vizibil | Da (nu se scoate) | `but_urmator_tot1` | `cursor_retur`, `:306-307` | `cursor_retur` (grup 8,9), `:819-820` | B (invers) | acoperit azi, de portat | +| 9 | retur factura valuta | **Da** `:15236` (grup 8,9) | — | "Cant. max. de returnat" | "nu se mai poate returna" | vizibil | Da | `but_urmator_tot1` | `cursor_retur` | `cursor_retur` | B (invers) | acoperit azi, de portat | +| 10 | factura fiscala valuta | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat | +| **43** | bon fiscal magazine (ROARETAIL) | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — niciodata pasat lui `factureaza()` (cautat in tot `D:\ROA`); colectat de la magazine prin alt flux | +| **44** | factura hotel | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — la fel, zero apeluri `factureaza(44` | +| **45** | factura restaurant | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** (implicit) | **nesetat** (ramane "nu e pe stoc!" default, `:15108`) | **nesetat** (ramane vizibil) | **Da, ramane** (nescoasa) | **nesetat** | `cursor_preturi`, `:275-278` | `cursor_preturi`, `:754-757` | — (exclus explicit din bookkeeping, `:12871,17178`) | **gol real de completat** — reachable din `Meniuri\politica.mn2:18` | +| **46** | nota de plata restaurant | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — zero apeluri `factureaza(46`; zero documente in date de test (`tipuri_documente_facturare.md`, capcana 2) | +| **48** | custodie cu descarcare K | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_articole_k`, `:271-273` | `cursor_articole_k`, `:750-752` | — | **gol real de completat** — reachable din `Meniuri\politica.mn2:29` (submeniu `Marfaincus`) | +| **49** | custodie fara descarcare K | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_articole_k` | `cursor_articole_k` | — | **gol real de completat** — reachable din `Meniuri\politica.mn2:26` | +| **50** | *(in lucru)* retur custodie | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins**, marcat explicit "in lucru" in `tipuri_documente_facturare.md` | +| **51** | factura ROAACNPRO | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — `id_set=50100`, interval separat; probabil scris direct de ROAACNPRO, nu prin `factureaza()` local | +| **52** | contract, factura fiscala valuta | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_contract` (grup 2,26,6,52) | ***lipseste*** din grupul contract (`:762`) — `lcSqlCursor` nedefinit pe prototip | — (confirmat, vezi §2) | **gol real de completat** — reachable din `Meniuri\contracte.mn2:18` | + +### Avize de expeditie + +| tip | denumire | Case propriu | titlu | cap cantitate | mesaj stoc | discount | `cSerie` | butoane vizibile | cursor standard | cursor prototip | Rol | stare S5 | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 21 | catre clienti, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — (implicit) | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat | +| 22 | catre clienti, din lista | **Da** `:15178` (grup 22,29) | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_preturi` | `cursor_preturi` | B | acoperit azi, de portat | +| **23** | transfer subunitati, din lista | **Da** `:15187` | "TRANSFER INTRE SUBUNITATI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | **`cursor_preturi`** (grup 1,22,5,29,7,10,**23**), `:279` | **`cursor_gestiune`** (grup **23**,41), `:776-778` | **B** (cod comun, indiferent de sursa) | acoperit azi, **dar sursa de cursor diverge intre forme — vezi §3** | +| 24 | aviz retur | **Da** `:15242` | "RETUR AVIZ DE EXPEDITIE" | "Cant. max. de returnat" | "nu se mai poate returna" | (nemodificat aici) | **Nu** | `but_urmator_tot1` | `cursor_retur` (grup 8,9,**24**), `:306-307` | ***lipseste*** din grupul retur (`:819`, doar 8,9) — `lcSqlCursor` nedefinit pe prototip | B (invers) | acoperit azi, **dar prototipul n-are ramura de cursor — vezi §3** | +| 25 | transfer subunitati, din comanda | **Da** `:15206` | "TRANSFER INTRE SUBUNITATI PE BAZA DE COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat | +| 26 | catre clienti, din contract | **Da** `:15216` | "AVIZ DE EXPEDITIE DIN CONTRACTUL …" | "Cantitate in stoc" | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_contract` (grup 2,26,6,52) | `cursor_contract` (grup 2,26,6 — fara 52, dar 26 e prezent) | — (confirmat, §2) | acoperit azi, de portat | +| **27** | transfer subunitati, pe lucrare | n/a — **ramane pe calea lui** | n/a | n/a | n/a | n/a | n/a | n/a | `cursor_lucrare`, `:302-304` | `cursor_lucrare`, `:815-817` | n/a | **ramas pe calea veche** — `frm_avizare_lucrare`, confirmat §4 | +| 28 | catre clienti debitori, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat | +| 29 | catre clienti debitori, din lista | **Da** `:15178` (grup 22,29) | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_preturi` | `cursor_preturi` | B | acoperit azi, de portat | +| **30** | transfer subunitati, pe NIR | n/a — **ramane pe calea lui, dar prin acelasi formular** | (irelevant — formular niciodata aratat) | (irelevant) | (irelevant) | (irelevant) | (irelevant) | (irelevant) | `cursor_aviz_nir`, `:299-300` | `cursor_aviz_nir`, `:813` | n/a | **ramas pe calea veche, cu nuanta** — vezi §4 | +| 41 | retur transfer, lista pret | **Da** `:15197` | "RETUR TRANSFER" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_gestiune`, `:296-298` | `cursor_gestiune` (grup 23,41) | **B** | acoperit azi, de portat | +| 42 | catre clienti custodie, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat | +| 47 | catre clienti custodie K, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat | +| **50** | *(in lucru)* retur clienti custodie | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins**, "in lucru" | + +**Tipuri negative** (`-1..-13`, ROAGEST/ROAAUTO): **ramase pe calea altui produs** — nu trec +niciodata prin `factureaza()`/`factureaza2` din ROAFACTURARE (cautare exhaustiva, zero potriviri), +deci nu intra in perimetrul Do Case-ului acestui formular. Declarate aici explicit, nu omise. + +--- + +## 2. Tipurile care nu intra in niciun `Case` — inventar complet + +Cerinta explicita a misiunii: "nu doar 52". Lista completa, verificata pe intreg `Do Case`-ul +(`ofacturare.vc2:15109-15248`, citit integral, nu esantion) fata de lista de referinta: + +**Nu apar in niciun `Case` al `frm_facturare_articole.Init`:** + +| tip | reachable prin `factureaza()`? | ce pierde concret | +|---|---|---| +| 43 | Nu (0 apeluri in tot `D:\ROA`) | irelevant — nu ajunge la acest formular | +| 44 | Nu | irelevant | +| **45** | **Da** (`Meniuri\politica.mn2:18`) | titlu, cap coloana cantitate, mesaj de stoc, `cSerie` nescoasa (ramane vizibila, desi tip 45 e explicit exclus din bookkeeping-ul de cantitate — cele doua lucruri nu sunt legate) | +| 46 | Nu | irelevant — zero documente si in datele de test | +| **48** | **Da** (`Meniuri\politica.mn2:29`, submeniu `Marfaincus`) | idem 45 | +| **49** | **Da** (`Meniuri\politica.mn2:26`) | idem 45 | +| 50 | Nu, "in lucru" | irelevant azi | +| 51 | Nu (interval `id_set` separat, alt produs) | irelevant pentru acest formular | +| **52** | **Da** (`Meniuri\contracte.mn2:18`) | titlu (ramane cel implicit al formularului), `cSerie` nescoasa, cap coloana cantitate implicit — **cel mai vizibil defect, pentru ca 2/6 (acelasi grup logic) au titlu corect** | + +**Concluzie**: din cele noua tipuri fara `Case`, **patru sunt reale si vizibile utilizatorului azi** +(`45, 48, 49, 52`) — acestea sunt golul de completat cu valoare, nu doar `52`. Celelalte cinci +(`43,44,46,50,51`) nu ajung niciodata la acest formular in fluxul curent — nu au nevoie de ramura in +`Do Case` **pana cand** ceva le conecteaza la `factureaza()` (posibil, dar in afara perimetrului +observabil aici; de tratat ca risc, nu ca bug, la sectiunea 10). + +**De ce raman "invizibile" azi cu `cSerie`/titlu implicit, nu cu eroare**: `Do Case ... Endcase` fara +ramura `Otherwise` in VFP nu genereaza nicio eroare cand nimic nu se potriveste — pur si simplu sare +peste tot blocul. De-asta tip 45/48/49/52 "merg" (formularul se deschide, factureaza cu succes), doar +cu UI-ul netratat pentru cazul lor specific — un defect tacut, nu un crash, motiv probabil pentru care +n-a fost observat/raportat pana acum. + +--- + +## 3. Divergentele standard vs. prototip + +| Aspect | Standard (`factureaza`) | Prototip (`factureaza2`) | Comportament corect de pastrat | +|---|---|---|---| +| **Cursor pe tip 23** | `cursor_preturi` (grup `1,22,5,29,7,10,23`, `:279`) — tratat ca lista de preturi | `cursor_gestiune` (grup `23,41`, `:776-778`) — tratat ca transfer | **`cursor_gestiune`**, impreuna cu 41 (deja stabilit de S4/S4b: transferul e o singura familie de tip, indiferent daca porneste "din lista" sau "retur"; tratarea ca lista de preturi pe standard e inconsistenta cu propriul titlu "TRANSFER INTRE SUBUNITATI" pe care tot standardul il afiseaza pentru tip 23) | +| **Cursor pe tip 52** | prezent, grupat cu `2,26,6` (`:283`) | **absent** din grupul contract (`:762`, doar `2,26,6`) — `lcSqlCursor` ramane nedefinit daca cineva factureaza tip 52 prin prototip | **prezent**, grupat cu `2,6,26` — lipsa lui pe prototip e o eroare de portare, nu o alegere deliberata (nimic in cod sugereaza ca 52 trebuia tratat diferit de 2/6/26 la nivel de cursor) | +| **Cursor pe tip 24** | prezent, grupat cu `8,9` (`:306-307`) | **absent** din grupul retur (`:819`, doar `8,9`) — `lcSqlCursor` nedefinit | **prezent**, grupat cu `8,9` — acelasi tip de omisiune ca la 52 | +| **Init: diferentiere pe tip** | 14 ramuri, seteaza titlu/cap coloana/mesaj/discount/`cSerie`/butoane | practic nimic — doar `lcTipDoc` ("factura"/"aviz"), pe o lista **fara tip 24** | **toata logica standardului**, portata — prototipul nu are nimic de pastrat aici in afara de pozitia `lcTipDoc` | +| **Coloana `cSerie`** | exista in grid prin design, se scoate condiționat (10 din 21 tipuri acoperite) | **nu exista deloc** ca si coloana in `grd_factura` (gridul unic al prototipului) | de decis explicit la proiectare (§5) — nu e o simpla portare, gridul insusi trebuie sa capete coloana | +| **`but_urmator_tot1` (sau echivalentul lui)** | vizibil pe 9 din 21 de tipuri (§1) | nu exista conceptul in Init — prototipul nu are nimic care sa corespunda azi | de portat lista completa de vizibilitate din standard | +| **`clb_discount.Visible`** | ascuns explicit pe toate tipurile de aviz (`21,28,42,47,22,29,23,41,25,26`) | niciodata atins in Init | de portat integral | + +**De ce conteaza asta pentru S5**: planul spune ca formularul unificat se bazeaza pe prototip +(arhitectura lui: grid unic, editare inline, cautare pe server — deja deciziile S1-S4). Dar +**diferentierea pe tip nu vine "aproape gata" din prototip** — vine aproape in intregime din +standard, si trebuie portata, nu doar completata cu cele patru tipuri lipsa. Cele doua liste (tipuri +lipsa din standard: 45/48/49/52; tot ce lipseste din prototip: aproape totul) sunt probleme +**diferite**, care se rezolva **in aceeasi miscare** daca proiectarea de la §5 porneste de la o +sursa unica de configurare portata integral din standard, cu cele patru completari incluse de la +inceput (nu adaugate separat, dupa portare). + +--- + +## 4. Tipurile speciale (27, 30) — confirmate pe cod + +### Tip 27 — transfer pe baza de lucrare + +Confirmat la trei niveluri, toate in `ofacturare.prg`: +- `Do Case tnTip = 27 -> poDate.nIdTipDoc = 6` (`:188-189`, tip document AVIZ); +- `Do Case tnTip = 27 -> lcObiect = [frm_date_aviz_lucrare]` (`:218-219`) — **formular de antet + diferit**, nu `frm_date_aviz`/`frm_date_factura`; +- `Do Case tnTip = 27 -> lcObject = [frm_avizare_lucrare]` (`:386-387`) — **formular de articole + diferit**, nu `frm_facturare_articole`. Cursorul sursa e si el propriu: `cursor_lucrare` + (`:302-304`), populat pe `poDate.id_lucrare`, un camp pe care restul tipurilor nu-l au. + +**Ce il tine pe calea lui**: `id_lucrare` — o legatura pe care niciun alt tip de document n-o are +(lucrare de service/executie, nu comanda/aviz/contract). `frm_avizare_lucrare` grupeaza gestiunile +destinatie diferit (`crsgestiunidest`, `:388-393`, cu optiunea ``), o structura pe care +`frm_facturare_articole`/`2` n-o au. **Formularul unificat n-ar avea `id_lucrare` si n-ar avea +gruparea pe gestiuni destinatie** — motiv suficient sa ramana separat, confirmat pe cod, nu +presupus. + +### Tip 30 — transfer pe baza de NIR + +**Nuanta importanta, gasita aici**: tip 30 **nu ocoleste** `frm_facturare_articole` — il +instantiaza, exact ca orice alt tip din grupul "otherwise" (`ofacturare.prg:395`, `lcObject = +[frm_facturare_articole]`, ramura `Else` a lui `If tnTip = 27`). Trece prin acelasi `Init`, acelasi +`Do Case` de la `:15109-15248` (unde `30` nu are ramura proprie — ar avea aceleasi goluri ca 45/48/49 +daca ar fi vreodata aratat). Diferenta reala: **formularul nu e niciodata aratat** +(`ofacturare.prg:444-453`): + +``` +IF tnTip = 30 && AVIZ DIN NIR + ofrmdetaliifactura.do_calculeaza_totaluri() + ofrmdetaliifactura.but_termin1.Click() + plVizibil = .F. + ... +ELSE + ... + If plVizibil + ofrmdetaliifactura.Show() + Else + ofrmdetaliifactura.Release() + pnButon = 2 + Endif +ENDIF +``` + +**Ce il tine pe calea lui**: nu structura formularului (e acelasi obiect), ci **automatizarea +completa a fluxului** — cursorul sursa (`cursor_aviz_nir`, populat din `VRUL`/tranzactii de receptie, +nu din comanda/lista de preturi) vine deja complet, iar codul apeleaza direct metodele de finalizare +fara interactiune. **Pentru formularul unificat**: daca arhitectura noua pastreaza acelasi tipar +("creeaza obiectul, populeaza, cheama finalizarea, `Release()` fara `Show()`"), tip 30 continua sa +functioneze neschimbat — nu are nevoie de ramura in configurarea vizuala (§5), pentru ca vizualul nu +se vede niciodata. **Singurul risc real**: daca `do_calculeaza_totaluri()`/`but_termin1.Click()` ale +formularului unificat ajung sa citeasca vreo proprietate pe care doar `Do Case`-ul vizual o seteaza +azi (de exemplu, un cod care ar verifica `This.cmesaj_cantitate` sau `lcTipDoc` in logica de calcul, +nu doar in UI) — **de verificat explicit la implementare**, nu presupus ca "nu conteaza pentru ca nu +se vede". + +--- + +## 5. Ce structura inlocuieste `Do Case`-ul de 140 de linii + +### Optiunile comparate + +**(a) Pastreaza `Do Case`, doar completeaza-l** (adauga ramuri pentru 45/48/49/52, porteaza restul in +prototip). Cost minim imediat, dar **nu rezolva problema de fond**: un `Do Case` fara `Otherwise` nu +semnaleaza niciodata un tip lipsa — exact mecanismul care a produs golul de azi (patru tipuri reale +pierdute, ani la rand, fara nicio eroare). Orice tip nou de document adaugat in viitor (suita are deja +`46,50` "in lucru", `43,44,51` din alte fluxuri) risca aceeasi soarta. + +**(b) Metoda separata per grup de tipuri** (`configureaza_lista_preturi()`, `configureaza_comanda()`, +...). Mai clar decat un `Do Case` unic, dar tot **implicit** — un tip nou tot nu declanseaza nicio +eroare daca nimeni nu-l adauga in metoda corecta; doar muta problema din 140 de linii intr-un fisier +cu mai multe metode mici, fara sa adauge un mecanism de detectie. + +**(c) Tabel de configurare per tip (RECOMANDAT)**. Un cursor/tabel cu **un rand per `tip`**, coloanele +fiind exact proprietatile pe care `Do Case`-ul le seteaza azi: `titlu`, `cap_cantitate`, `mesaj_stoc`, +`discount_vizibil` (`L`), `are_serie` (`L`), `tip_doc` (`factura`/`aviz`), `buton_tot_vizibil` (`L`), +`buton_retur_vizibil` (`L`), `grup_sursa` (pentru meniul S4b: `lista/comanda/aviz-comanda/transfer/ +retur/contract-articole/contract-rate`). Populat printr-un singur bloc de `INSERT INTO` (sau un DBF +static, `configuratie_tip_document.dbf`, editabil fara compilare) — **un rand per tip din +`tipuri_documente_facturare.md`**, inclusiv cele patru azi lipsa. + +`Init` devine: +``` +SELECT * FROM configuratie_tip_document WHERE tip = poDate.tip INTO CURSOR crscfgtip +If Reccount('crscfgtip') = 0 + * tip necunoscut -- eroare explicita, nu formular netratat tacut + AMESSAGEBOX("Tip de document necunoscut in configurare: " + Transform(poDate.tip), 16, "Eroare configurare") + Thisform.Release() + Return +Endif +This.lb_titlu_alb_b121.Caption = crscfgtip.titlu +This.grd_articole.cCantitate.header1.Caption = crscfgtip.cap_cantitate +This.cmesaj_cantitate = crscfgtip.mesaj_stoc +This.clb_discount.Visible = crscfgtip.discount_vizibil +If !crscfgtip.are_serie + This.grd_articole.RemoveObject('cSerie') +Endif +This.but_urmator_tot1.Visible = crscfgtip.buton_tot_vizibil +This.but_retur.Visible = crscfgtip.buton_retur_vizibil +``` + +**De ce e mai bun decat (a)/(b) pe cost de intretinere**: +- **Un tip lipsa devine o eroare vizibila la deschidere**, nu un formular netratat tacut — exact + defectul care a permis golul de azi sa treaca neobservat ani la rand. +- **"Cat de usor se vede un tip lipsa" e mecanic, nu vizual** — vezi §8, o interogare simpla compara + lista de tipuri din configurare cu lista de referinta din `tipuri_documente_facturare.md`, fara sa + ruleze formularul. +- **Grupurile identice raman explicite, nu implicite** — azi, "tipurile 21,28,42,47 au acelasi titlu" + se vede doar citind `Inlist(...)`; intr-un tabel, acelasi lucru se vede ca patru randuri cu aceeasi + valoare in coloana `titlu` — usor de generat cu un singur `INSERT` per grup (`FOR EACH tip IN + (21,28,42,47) ... INSERT ...`), nu mai putin explicit, dar auditabil cu `SELECT titlu, COUNT(*) + GROUP BY titlu`. +- **Coloana `are_serie` rezolva si divergenta standard/prototip de la §3** — prototipul nu are azi + conceptul deloc; cu configurarea noua, adaugarea coloanei `cSerie` in gridul unificat devine + conditionata de aceeasi sursa unica, indiferent de forma de baza. + +**Cost**: portarea initiala a ~21 de randuri (14 ramuri distincte de azi + 4 completari + grupare +explicita), plus un nou tabel/cursor de intretinut. Nu e cod nou complex — e date, nu logica; riscul +de regresie e in acuratetea portarii (fiecare valoare trebuie sa corespunda exact cu ce face azi +`Do Case`-ul), verificabil linie cu linie fata de tabelul din §1. + +**Recomandare finala**: **(c)**, cu tabelul de configurare implementat ca `DBF` static (nu cursor +generat in cod) — editabil de oricine fara sa recompileze, si direct verificabil cu `SELECT` fara sa +porneasca formularul (vezi §8). + +--- + +## 6. Grupuri naturale de tipuri + +Din tabelul §1, grupurile care au azi (sau ar trebui sa aiba) valori identice pe toate coloanele: + +| Grup | Tipuri | Ce difera **in interiorul** grupului | +|---|---|---| +| **Lista de preturi, fara stoc special** | 1, 5, 7, 10 | Nimic in Init — difera doar valuta/tip document la nivel de antet (`poDate.in_valuta`, `nIdTipDoc`), nu in acest formular | +| **Contract, factura** | 2, 6 (+ **52** de adaugat) | Nimic in Init dupa completare — `52` e valuta, ca `6`, dar cu alt `id_set`; titlul/coloanele sunt identice | +| **Aviz din comanda (clienti/debitori/custodie)** | 21, 28, 42, 47 | Nimic in Init — difera doar destinatia comerciala (client normal/debitor/custodie), invizibila la acest nivel | +| **Aviz din lista de preturi** | 22, 29 | Nimic — difera doar client normal/debitor | +| **Retur facturi** | 8, 9 | Nimic — lei/valuta | +| **Transfer subunitati** | 23, 41 | Sens (din lista vs. retur) — titlu diferit ("TRANSFER..." vs "RETUR TRANSFER"), restul identic; **trebuie unificate pe cursor** (§3) inainte de unificare vizuala | +| **Comanda proprie** | 3 | Singur — cap de coloana propriu ("Cantitate comandata") | +| **Avize proprii** | 4 | Singur — discount vizibil (spre deosebire de toate celelalte avize) | +| **Aviz din contract** | 26 | Singur — titlu propriu, dar cursor comun cu grupul contract | +| **Aviz retur** | 24 | Singur — cursor comun cu 8/9, dar titlu si `but_urmator_tot1` proprii | +| **Custodie K** | 48, 49 | Identice ca structura vizuala (ambele lipsesc azi) — difera doar `cu`/`fara` descarcare K, invizibil la acest nivel | +| **Restaurant** | 45 | Singur, azi lipsa | + +Observatie de proiectare: grupurile "aviz din comanda" (21/28/42/47) si "comanda" (3) au **acelasi** +cap de coloana si mesaj de stoc conceptual ("cantitate comandata"), dar text usor diferit +("facturata"/"avizata") — pastrate distincte in tabelul de configurare (nu fortate identice), pentru +ca diferenta e deliberata in codul de azi (`:15149` vs `:15176`, verb diferit). + +--- + +## 7. Pasi de implementare, ordonati + +1. **Extrage tabelul de configurare din `Do Case`-ul standard, exhaustiv** — un rand per tip din + `tipuri_documente_facturare.md` care intra prin acest formular (Facturi + Avize, exclus 27/30/ + negative), valorile copiate exact din §1. *Gata cand*: `SELECT DISTINCT tip FROM + configuratie_tip_document` produce exact multimea `{1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,28,29, + 41,42,45,47,48,49,52}` (23 de tipuri) — nici unul in minus, nici unul in plus fata de lista + calculata in §8. +2. **Completeaza cele patru randuri azi lipsa (45,48,49,52)** cu valori coerente cu grupul lor logic + (45 langa lista de preturi cu stoc dezactivat conceptual; 48/49 langa custodie K; 52 langa 2/6) — + decizie de continut, nu doar de structura; **de validat cu Marius inainte de a le considera + "gata"**, pentru ca azi nu exista niciun titlu/mesaj de referinta pentru ele (nimeni nu l-a vazut + pe ecran). *Gata cand*: cele patru randuri au valori nenule pe toate coloanele obligatorii + (`titlu`, `cap_cantitate`, `mesaj_stoc`). +3. **Corecteaza rutarea cursorului**: `23` trece pe `cursor_gestiune` (unificat cu `41`, nu mai divide + standard/prototip); `52` si `24` primesc ramura de cursor pe orice cale ramane vie din prototip + (daca formularul unificat pastreaza `factureaza2`-stil, sau devine parte din calea unica daca + `factureaza`/`factureaza2` se unesc — decizie separata, in afara acestei povesti). *Gata cand*: + deschiderea formularului pe tip `23`, `24` si `52` prin calea noua produce acelasi continut in + `crsarticole` ca varianta care functiona deja (`23`→prototip vechi pentru comparatie de continut, + `24`/`52`→standard). +4. **Adauga coloana `cSerie` in gridul unificat, condiționata pe `are_serie`** — azi absenta din + gridul prototipului; adaugata o singura data, aratata/ascunsa din configurare, nu prin + `RemoveObject` scris de mana pe fiecare tip. *Gata cand*: pe un tip cu `are_serie=.T.` (ex. 4) + coloana e vizibila; pe un tip cu `are_serie=.F.` (ex. 3) nu e. +5. **Inlocuieste `Do Case`-ul din `Init` cu citirea din configurare** (structura din §5), inclusiv + ramura de eroare explicita pe tip necunoscut. *Gata cand*: pentru fiecare din cele 23 de tipuri, + deschiderea formularului seteaza exact valorile din tabelul §1/pasul 2 (comparatie automata, nu + vizuala — proprietatile sunt citibile headless). +6. **Verifica tipurile speciale raman neatinse**: 27 (cale total separata, neschimbata), 30 (acelasi + formular, dar `do_calculeaza_totaluri`/`but_termin1.Click()` nu citesc nimic setat doar de vechiul + `Do Case` vizual — verificare explicita, §4). *Gata cand*: un document tip 30 de test se + finalizeaza cu acelasi rezultat in `VANZARI`/`VANZARI_DETALII` inainte si dupa migrare. +7. **Documenteaza tipurile neatinse (43,44,46,50,51) ca decizie explicita**, nu ca omisiune — un + comentariu in tabelul de configurare (`* 43,44,46,50,51: neconectate la factureaza() in + ROAFACTURARE, verificat `) ca viitorii cititori sa nu presupuna ca lipsesc din greseala. + *Gata cand*: comentariul exista si linkeaza spre acest raport. + +*Depinde de*: S4 (cautarea articolelor pe server) pentru arhitectura gridului unic — pasul 4 de aici +presupune ca gridul unificat exista deja in forma stabilita de S4; S4b (bara de butoane) pentru +`buton_tot_vizibil`/`buton_retur_vizibil`, care alimenteaza si meniul `xmenu()` de acolo — coloanele +`grup_sursa` din tabelul de configurare (§5) sunt exact ce cere S4b sectiunea 3. + +--- + +## 8. Cum se verifica ca acoperirea e completa — proba mecanica + +**Nu o citire — o interogare care compara doua liste.** Doua surse de adevar: + +1. **Lista de referinta**: tipurile din `COMUN\docs\tipuri_documente_facturare.md`, sectiunile + "Facturi" si "Avize de expeditie", **minus** cele confirmate neatinse azi de acest formular (27, 30 + raman — vezi nuanta §4 — dar 43,44,46,50,51 se exclud daca raman neconectate; de recalculat lista + la fiecare rulare, nu de la o constanta inghetata). +2. **Lista din configurare** (dupa implementarea §5): `SELECT DISTINCT tip FROM + configuratie_tip_document`. + +**Script de verificare** (headless, fara UI, rulabil oricand): + +```foxpro +* verifica_acoperire_tip.prg — proba mecanica pentru S5 +LOCAL lnLipsa, lnInPlus +* 1. lista de referinta -- tinuta manual sincron cu tipuri_documente_facturare.md +* (facturi + avize, exclus negative; 27/30 raman in lista, tratate separat la pasul 3) +DIMENSION laReferinta[23] +laReferinta = [1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,27,28,29,30,41,42,47,52] && + 45,48,49 dupa Pasul 2 + +* 2. lista din configurare +SELECT DISTINCT tip FROM configuratie_tip_document INTO CURSOR crsCfg + +* 3. tipuri de referinta fara configurare (exclus 27, 30 -- cale separata confirmata) +* 4. tipuri in configurare fara corespondent in referinta (config "orfana") +* -- ambele liste trebuie sa fie goale la "gata" +``` + +Alternativ, **fara sa astepte implementarea §5**: acelasi principiu se aplica azi direct pe +`Do Case`-ul din `ofacturare.vc2:15109-15248`, extragand tipurile din fiecare `Case ... Inlist/=` prin +`vfp_symbols.ps1 -Grep 'Case (poDate\.tip|Inlist\(poDate\.tip'` si comparand rezultatul cu lista de +referinta — exact tehnica folosita in aceasta cercetare pentru a produce tabelul din §1 (nu o +citire vizuala, o extractie sistematica). + +**Pentru cursor (rutare, §3)**: acelasi principiu, pe `ofacturare.prg`, comparand tipurile din fiecare +`Do Case`/`Inlist` de la `:266-308` (standard) cu cele de la `:748-822` (prototip) — orice tip prezent +intr-una si absent in cealalta e o divergenta de raportat (asa au fost gasite `52` si `24` in aceasta +sesiune). + +--- + +## 9. Ce nu se poate testa headless + +- **Titlul, capul de coloana, mesajul de stoc ca text afisat efectiv pe ecran** — verificabile + headless doar ca *proprietati setate* (`This.lb_titlu_alb_b121.Caption` dupa `Init()`, apelat direct + cu un `poDate` de test), nu ca randare vizuala. Diferenta conteaza: o proprietate corect setata pe + un control care nu exista (`lb_titlu_alb_b121` lipsa din prototip azi) ar trece "headless" fara sa + arate nimic real — de confirmat mai intai ca ambele controale exista in formularul unificat. +- **Coloanele de grid (`grd_articole`/`grd_factura`, coloana `cSerie` noua)** — capcana deja cunoscuta + pe acest proiect: sub `-A -T` (rulare headless), `ColumnCount=0`/`RecordSource` raman artefacte, + coloanele nu se materializeaza. Exista un harness UI **vizibil** care le citeste corect — orice + verificare a `are_serie`/coloanelor trebuie sa treaca prin acela, nu prin `-A -T`. +- **Tip 30 — fluxul complet fara `Show()`** — se poate rula headless (nu deschide nicio fereastra prin + constructie), dar verificarea "rezultatul in Oracle e identic inainte/dupa" cere date de test reale + (un NIR cu articole), nu doar apelul metodei. +- **Popup-ul/dialogul viitor din S4b (`xmenu()`, `cauta_alfa`)**, daca `grup_sursa` din tabelul de + configurare (§5/§6) ajunge sa-l alimenteze direct — mostenit ca limitare de la raportul S4b (`§8` + de acolo): continutul `lcMeniu` verificabil ca text, popup-ul afisat nu. +- **Comportamentul real al meniurilor `politica.mn2`/`contracte.mn2`** pentru tipurile 45/48/49/52 — + se poate confirma ca exista intrarea de meniu (citire `.mn2`, facuta in aceasta cercetare), dar nu + ca apasarea ei in productie deschide exact formularul asteptat, fara o rulare UI vizibila. + +--- + +## 10. Riscuri si ce ramane de decis de Marius + +1. **Continutul exact (titlu/mesaj) pentru cele patru tipuri azi netratate (45,48,49,52)** — codul nu + ofera niciun precedent vizual (n-au fost vazute niciodata corect pe ecran), deci textele propuse in + Pasul 2 (§7) sunt o **propunere**, nu o recuperare a unui text existent. **Recomandare**: 45 langa + grupul "lista de preturi fara stoc" (e exclus explicit din bookkeeping de stoc, `:12871`); 48/49 cu + titlu care mentioneaza "custodie" (singurul lucru care le distinge conceptual azi, in afara de + coeficientul K, invizibil la acest nivel); 52 **identic cu 2/6** ("FACTURA PE CTR. ..."), pentru + coerenta cu gruparea deja facuta corect in `frm_date_factura.Init` (`:9530,9632,9670`, grupul + `2,6,52`). +2. **Daca 23 trece pe `cursor_gestiune` pe calea unificata, comportamentul vizibil pentru operator se + schimba** (sursa de articole devine stocul din gestiune, nu lista de preturi) — **decizie de produs, + nu doar tehnica**, deja semnalata de S4/S4b, dar repetata aici pentru ca afecteaza direct §5/§7 + (tabelul de configurare trebuie sa reflecte alegerea finala, nu ambele variante). **Recomandare**: + `cursor_gestiune`, argumentat de coerenta cu titlul "TRANSFER INTRE SUBUNITATI" pe care insusi + standardul il afiseaza azi pentru 23 (titlul spune "transfer", cursorul azi spune "lista de + preturi" — inconsistenta interna de rezolvat, nu de pastrat). +3. **Daca 43,44,46,50,51 raman permanent neconectate la `factureaza()`, sau exista planuri sa fie + activate** — nu s-a gasit nicio dovada de cod ca ar fi in curs, dar nici o confirmare ca sunt + abandonate definitiv (in afara de `50`, marcat explicit "in lucru" in sursa). **De intrebat pe + Marius direct**, pentru ca raspunsul schimba daca tabelul de configurare (§5) trebuie sa le includa + preventiv sau poate ramane cu 23 de randuri. +4. **Coloana `cSerie` pe gridul unificat — cost de adaugare** — prototipul n-are azi conceptul deloc; + adaugarea ei presupune modificari de grid (nu doar de cod), posibil in afara perimetrului text-only + (`.vc2`/`.sc2` prin `txt2vcx.ps1`) daca structura de coloane a gridului cere editare in IDE — **de + confirmat la implementare**, nu presupus ca e o simpla proprietate. +5. **Tabel de configurare ca `DBF` static vs. `INSERT`-uri in cod** — recomandarea (§5) e DBF, pentru + editabilitate fara recompilare, dar suita foloseste azi predominant cod pentru acest fel de + configurare (niciun precedent de DBF static de configurare gasit in `frm_facturare_articole`/ + `frm_date_factura`) — **de validat cu Marius daca abaterea de la conventia existenta merita + beneficiul**, sau daca un cursor generat in cod (mai aproape de stilul actual, dar mai putin + editabil) e preferat. + +## Handoff + +Cercetare + proiectare incheiata in aceasta sesiune, fara sa fie nevoie de predare de context. +Toate cele zece sectiuni cerute de misiune sunt completate, cu `fisier:linie` verificat direct pe +fisierele text reale (`ofacturare.vc2`, `ofacturare.prg`, nu `.bak`), plus meniurile `.mn2` si +raportul S4/S4b/S4_punct2 citate ca sursa pentru punctele deja stabilite (nereinvestigate). + +Descoperiri dincolo de cerinta explicita a misiunii, semnalate clar in text: (1) patru tipuri lipsa +din `Do Case`, nu unul (45/48/49/52, sectiunea 2); (2) prototipul (`frm_facturare_articole2.Init`) +e aproape complet gol pe diferentiere de tip, nu doar incomplet (sectiunea 3) — cea mai mare +descoperire a acestei sesiuni, cu impact direct asupra efortului de implementare estimat pentru S5; +(3) doua divergente noi de rutare a cursorului (52, 24), pe langa cea deja cunoscuta (23) (sectiunea +3); (4) tipurile 26 si 52 confirmate fara niciun bookkeeping `crsarticole`, inchizand punctul lasat +deschis de raportul S4 punctul 2 (sectiunea 1, nota de subsol pe tabel). + +Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle (numai +`Read`/`Grep`/`vfp_symbols.ps1` pe fisiere de pe disc). diff --git a/docs/cercetare/s5b_proforma_descarcare_gestiune.md b/docs/cercetare/s5b_proforma_descarcare_gestiune.md new file mode 100644 index 0000000..f773c17 --- /dev/null +++ b/docs/cercetare/s5b_proforma_descarcare_gestiune.md @@ -0,0 +1,321 @@ +# 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');` diff --git a/docs/cercetare/s5b_proiectare_proforma_copiere.md b/docs/cercetare/s5b_proiectare_proforma_copiere.md new file mode 100644 index 0000000..cc39bd6 --- /dev/null +++ b/docs/cercetare/s5b_proiectare_proforma_copiere.md @@ -0,0 +1,610 @@ +# Proiectare S5b — proforma si copierea pe formularul unificat + +Cercetare + proiectare READ-ONLY (fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara +commit, fara scriere Oracle — numai `SELECT`), pentru povestea **S5b** din +`docs\plan_13_unificare_formular_facturare.md` (sectiunea `#### S5b`, liniile 2507-2530), deciziile +10 si 11. Continua, fara sa reia, `docs\cercetare\s5b_proforma_descarcare_gestiune.md` (sursa de +adevar pentru mecanismul `gestionabil=0` / `id_gestiune=-1000`) si foloseste rezultatele din +`docs\cercetare\s4e_lista_preturi_pe_sursa.md` (coliziunea `id_c`) si +`docs\cercetare\s4_punct2_registru_cantitate_ramasa.md` (Rol A/B ale `crsarticole`). + +**Status: cercetare + proiectare incheiate.** + +Nu se ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` +(perimetrul altei sarcini). `COMUN\programe\ofacturare.prg` si `ofacturare_comun.prg` sunt citite, +nu editate — la fel `COMUN\clase\ofacturare.vc2` si `COMUN\clase\ofacturare_comun.vc2` (citire, nu +editare; scriere interzisa doar pe al doilea). + +--- + +## Descoperire centrala, care rescrie premisa de risc a lui S5b + +Sursa de adevar anterioara (`s5b_proforma_descarcare_gestiune.md`) a stabilit ca gate-ul pentru +proforma e sentinela `id_gestiune = -1000`, verificata **in interiorul** lui +`contabilizeaza_articol` (`PACK:7472-7476`). Cercetarea de fata a gasit un strat **mai devreme si +mai tare**: la `Termina`, `do_scrie_factura` **alege intre doua proceduri Oracle diferite** dupa +`poDate.eProforma`, inainte sa se uite la `poDate.tip` (`COMUN\clase\ofacturare.vc2:14282-14300`): + +``` +Do Case + Case poDate.eProforma = 1 + * scrie_proforma are aceiasi parametri ca scrie_factura2 + * salveaza doar in vanzari, nu si in contabilitate + lcSql = [{call pack_facturare.scrie_proforma(...)}] + Case poDate.Tip = 4 + ... lcSql = [{call pack_facturare.scrie_factura_avize(...)}] + Case Inlist(poDate.Tip,3,21,25,28,42,47) + ... lcSql = [{call pack_facturare.scrie_factura2(...)}] + Otherwise + ... lcSql = [{call pack_facturare.scrie_factura2(...)}] +Endcase +``` + +Comentariul din cod ("salveaza doar in vanzari, nu si in contabilitate") e literal adevarat, verificat +pe corpul Oracle: `pack_facturare.scrie_proforma` (`PACK:5637-5671`) cheama **doar** +`scrie_in_vanzari` (`PACK:13488-13760`) — care face `INSERT INTO VANZARI` (antet) **si** +`INSERT INTO VANZARI_DETALII ... SELECT FROM VANZARI_DETALII_TEMP` (liniile, cu `ID_GESTIUNE` asa +cum a ajuns in `TEMP`) — apoi marcheaza `VANZARI.EPROFORMA=1`. **`scrie_proforma` nu cheama +niciodata `contabilizeaza_articol`.** `scrie_factura2`/`scrie_factura_avize`/`scrie_aviz_retur` sunt +singurele trei proceduri care cheama `contabilizeaza_articol` (confirmat si in +`docs\plan_13_unificare_formular_facturare.md:298-310`, runda 11) — si niciuna din ele nu ruleaza +pentru `eProforma=1`. + +**Consecinta:** pentru o proforma, `scrie_nota` (care scrie `NOTE_CONTABILE`) si `descarca_gestiune` +nu ruleaza **deloc** — nu pentru ca sentinela `-1000` le blocheaza pe fiecare linie (desi si asta +ramane adevarat, ca plasa suplimentara), ci pentru ca **intreaga functie care le cheama nu se +executa**. Sentinela `-1000` din `contabilizeaza_articol` conteaza doar cand `contabilizeaza_articol` +chiar ruleaza — adica exact pe drumul invers (vezi sectiunea 5), nu pe proforma insasi. + +**De ce conteaza pentru S5b**: alegerea `scrie_proforma` vs. `scrie_factura2` se face **o singura +data, la Termina**, citind `poDate.eProforma` **in acel moment** — nu depinde de cand/cum a fost +comutat combo-ul, nici de valorile `gestionabil`/`id_gestiune` de pe liniile individuale. Asta e o +veste buna pentru un sens al comutarii (proforma -> ramane proforma la salvare: corect, indiferent ce +au liniile), dar **nu acopera** sensul opus (liniile marcate negestionabil sub proforma, apoi +documentul comutat inapoi la factura inainte de Termina) — acolo `scrie_factura2` **chiar** ruleaza, +si atunci sentinela `-1000` de pe liniile ramase de la proforma **chiar blocheaza** `descarca_gestiune` +pe un document care ar trebui sa descarce. Vezi sectiunea 5. + +--- + +## 1. Fluxul de azi al proformei, cap-coada + +**1.1 Alegerea tipului.** Proforma nu e o valoare in `pack_facturare.ntip` (tipul de business, +1-52) — e un atribut ortogonal, `poDate.nIdTipDoc` (5=FACTURA, 23=PROFORMA, 3=BON FISCAL, +6=AVIZ), setat din combo-ul "Tip document" (`Ct_clb_fdoc._combobox1`, +`RowSource = "FACTURA,PROFORMA,BON FISCAL"`, `ofacturare.vc2:8745-8754`). Setter-ul +`nIdTipDoc_Assign` (`COMUN\programe\ofacturare_comun.prg:593-600`) deriva boolean-ul in acelasi +moment: `This.eProforma = IIF(m.tnIdTipDoc = This.nIdTipDocProforma, 1, 0)`. + +**1.2 Marcarea in masa la incarcarea cursorului.** In `factureaza()` (`COMUN\programe\ofacturare.prg`), +dupa ce cursorul sursa (indiferent care — lista de preturi, comanda, contract, aviz, retur) e adus in +`crsarticole` (Do Case pe `tnTip`, `:266-311`), **daca** `poDate.eProforma = 1`, se face un `UPDATE` +de masa peste tot cursorul (`:330-334`): +``` +* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc +IF poDate.eProforma = 1 + UPDATE (m.lcCursor) SET gestionabil = 0 + GO TOP IN (m.lcCursor) +ENDIF +``` +Acest pas ruleaza **o singura data**, la incarcare, **inainte** ca formularul de linii sa se +deschida — pentru ca la momentul lui `poDate.eProforma` e deja finala: combo-ul traieste in +`frm_date_factura`/`frm_date_aviz`, un dialog modal care se **inchide** (`ofrmceredate.Show()` la +`:235`, urmat de `Release ofrmceredate` la `:248`) **inainte** ca `Do Case`-ul de incarcare a +cursorului sa ruleze (`:266` e dupa `:248`). Tipul nu se mai poate schimba dupa acest punct, azi. + +**1.3 Ce face `gestionabil=0` la nivel de linie.** Cand operatorul adauga o linie, ramura care alege +dialogul citeste exact acest camp (`ofacturare.vc2:13803-13809`): +``` +Do Case + Case (poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45) + ofrmadarticol = Createobject("frm_articol_factura", lnCantitateMax, .F.) + Otherwise + Thisform.do_alege_stoc(...) +Endcase +``` +`frm_articol_factura` e dialogul generic pentru orice articol negestionabil din toata aplicatia +(nu unul specific proformei) — ocoleste `do_alege_stoc` (alegerea lotului din stoc). +`do_initializeaza_articol` (`ofacturare.vc2:13618-13623`) seteaza `id_gestiune=-1000` **doar daca +proprietatea lipseste** pe obiectul articol (`Type(...) = "U"`): +``` +If Type('toArticol.id_gestiune') = "U" + AddProperty(toArticol,'id_gestiune',-1000) +Endif +``` +La scriere, `poArt.id_gestiune` merge direct ca `V_ID_GESTIUNE` catre +`pack_facturare.adauga_articol_factura` (`ofacturare.vc2:14069-14073`), care il traduce +(`PACK:5032-5034`: `IF V_ID_GESTIUNE <> -1000 THEN V_ID_GESTIUNE2 := V_ID_GESTIUNE; END IF;` — +altfel ramane `NULL`), scris in `VANZARI_DETALII_TEMP.ID_GESTIUNE`. + +**Nota de incertitudine, mostenita din raportul-sursa**: linia exacta unde proprietatea +`id_gestiune` "lipseste" (`Type='U'`) pentru o linie de proforma scatter-uita din `crsfactura` nu a +fost confirmata direct pe date vii — `crsfactura` are `id_gestiune` ca si camp cu valoare implicita, +nu absent. Ramane argument indirect (fara `FACT-007` raportat in productie de ani), nu dovada de +executie. **De verificat cu prioritate la implementare**, pentru ca proiectarea de mai jos (sectiunea +4) muta acest mecanism dintr-un `UPDATE` de masa intr-o marcare per-linie, si trebuie sa stie exact +ce camp seteaza. + +**1.4 Routingul la Termina.** Vezi "Descoperirea centrala" de mai sus: `scrie_proforma`, nu +`scrie_factura2`/`scrie_factura_avize` — fara nota contabila, fara descarcare de gestiune, indiferent +de tipul de business original. + +**1.5 Raportul propriu.** `listeaza_ofacturare` alege raportul dupa `poDate.eProforma` +(`ofacturare.prg:1638-1643`): +``` +Case (Between(poDate.tip, 1, 20) Or Inlist(poDate.tip, -1,-2,-3,-4,-8,-11,44,45,48,49,50,51,52)) And poDate.eProforma = 1 + lcRaport = [PROFORMA] + lcRaportVal = [PROFORMA_VAL] + lcSetare = [PROFORMA] +``` +Independent de forma formularului (unificat sau nu) — declansat de `poDate.eProforma` la momentul +listarii, care e populata corect din `nIdTipDoc_Assign` daca antetul reflecta starea finala. + +**1.6 Garzile `eProforma = 0` pe atasamente (nu pe nota contabila).** Cinci guarde separate in +`listeaza_ofacturare`, toate de forma `poDate.nRelistare = 0 And poDate.eProforma = 0 ...`, controland +salvarea PDF-ului ca atasament arhivat (`export2pdf(..., poDate.cDocAtasate)` si +`poDate.scrieAtasamente()`), **nu** scrierea notei contabile (care e blocata la alt nivel, sectiunea +precedenta): `ofacturare.prg:1960` (factura in lei), `:1996` (aviz retur), `:2052` (factura in +valuta), `:2063` (recapitulatie), `:2103` (`scrieAtasamente()`, arhivarea propriu-zisa). **Corectie +fata de formularea din plan** (`plan_13_unificare_formular_facturare.md:548-549`, "fara nota +contabila si fara atasamente, prin garzile `eProforma=0`"): cele doua efecte sunt reale amandoua, dar +prin **mecanisme diferite** — nota contabila prin routing-ul `scrie_proforma`/`scrie_factura2` +(sectiunea "Descoperire centrala"), atasamentele prin aceste cinci guarde explicite. Nu schimba nimic +pentru proiectare, dar conteaza pentru cine cauta "garda de nota contabila" in cod si nu o gaseste la +liniile citate de plan. + +**1.7 Relistarea pe cale separata.** `frm_facturi.do_listare` (`COMUN\clase\ofacturare_comun.vc2:7233-`) +reconstruieste `poDate` de la zero pentru un document deja emis, cu `poDate.eProforma = 1` setat +explicit (`:7262`) cand se relisteaza o proforma din grid — nu refoloseste obiectul `poDate` din +sesiunea de facturare curenta. + +--- + +## 2. Fluxul de azi al copierii, cap-coada + +**2.1 Degradarea de tip in `do_copiaza`.** `frm_facturi.do_copiaza` +(`COMUN\clase\ofacturare_comun.vc2:3628-3713`) mapeaza **orice** tip de business catre unul din +cinci "simple" — `T1` (lista de preturi), `T10` (lista de preturi valuta), `T5` (invoice), +`T22` (aviz din lista de preturi), sau `T1`/`T10` pentru transfer/altele — pe un `Do Case` explicit +peste `loFactura.tip` (`:3693-3708`). Factura/avizul din contract sau din comanda **nu se copiaza ca +atare** — devine o factura simpla din lista de preturi. Apoi `DO copiere_factura WITH loFactura IN +oproceduri_facturare.prg` (`:3710`). + +**2.2 `copiere_factura` -> `factureaza(tip, toFactura)`.** `copiere_factura` +(`COMUN\programe\oproceduri_facturare.prg:150-153`) cheama `factureaza(loFactura.tip, loFactura)` — +`toFactura` (obiectul scatter-uit din `crsFacturi`) devine parametrul opțional al lui `factureaza` +(`COMUN\programe\ofacturare.prg:81-82`), care seteaza `llCopiere = (Type('toFactura') = 'O')` +(`:111`). + +**2.3 Antetul precompletat, dar `nIdTipDoc` NU se propaga.** +`poDate.completeaza_setari_document(toFactura, .T.)` (`ofacturare.prg:204`, apelat cand `m.llCopiere`) +cheama `oDateFactura::completeaza_setari_document` +(`COMUN\programe\ofacturare_comun.prg:362-412`), ramura `tlFactura=.T.` (`:368-390`): copiaza delegat, +masina, sectie, agent, client, referinta la documentul sursa (`listaid = toDateAnterior.id_vanzare`) — +dar **linia `.nIdTipDoc = toDateAnterior.nIdTipDoc` e comentata** (`:370`, +`*.nIdTipDoc = toDateAnterior.nIdTipDoc`), deliberat. Documentul nou primeste `nIdTipDoc` implicit +dupa tipul degradat (`Do Case` la `ofacturare.prg:187-196`: `tnTip < 21` sau in +`{45,48,49,51,52}` => `5` FACTURA, altfel `6` AVIZ) — pentru tipurile degradate ale copierii (`T1=1`, +`T5=5`, `T10=10`, `T22=22`), toate `<21`, deci **`nIdTipDoc=5` (FACTURA) intotdeauna**, ceea ce prin +`nIdTipDoc_Assign` face **`poDate.eProforma = 0` pe documentul nou, indiferent daca sursa era +proforma**. Confirma direct pe cod ce raportul-sursa dedusese din trasare (`s5b_proforma_descarcare_gestiune.md` §3). + +**2.4 Numar nou, intotdeauna.** `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` (`:211`) +ruleaza neconditionat, indiferent de copiere — copierea nu mosteneste numarul documentului sursa, +alege intotdeauna un numar nou din seria tipului nou (`FACTURA`). + +**2.5 Cursorul de linii candidate — intotdeauna `cursor_retur_document`.** `Do Case`-ul care alege +cursorul Oracle de incarcat in `crsarticole` (`ofacturare.prg:266-308`) verifica **`Case m.llCopiere` +primul**, inaintea oricarei ramuri pe `tnTip`: +``` +Case m.llCopiere + lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}] +``` +al treilea parametru pozitional `1` e `V_COPIERE`. Documentul sursa (proforma sau nu) intra prin +`?poDate.listaid` (setat la `toDateAnterior.id_vanzare` in §2.3). `poDate.eProforma` trimis e cel al +**documentului nou** (`0`, per §2.3), nu al sursei. + +**2.6 Gestionabilitatea se restaureaza la copiere.** `cursor_retur_document` +(`PACK_FACTURARE:3949-4000`) calculeaza `GESTIONABIL` cu exact aceasta expresie (`:3993-4000`): +```sql +(case + when V_PROFORMA = 1 then 0 + when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE.IN_STOC, articolul real + else A.GESTIONABIL + end) AS GESTIONABIL, +``` +Cu `V_PROFORMA=0` (documentul nou nu e proforma) si `V_COPIERE=1`, ramura activa e +`GESTIONABIL = B.IN_STOC` — valoarea reala din nomenclator, **indiferent daca documentul sursa era o +proforma cu toate liniile fortate `gestionabil=0`**. Liniile copiate dintr-o proforma redevin +gestionabile normal (daca articolul chiar e in stoc), trec prin `do_alege_stoc` la adaugare, primesc +`id_gestiune` real, si descarca gestiune normal la emiterea facturii rezultate. + +**2.7 Nimic auto-adaugat.** Comentariu explicit in cod (`ofacturare.prg:97-99`, in ramura +`m.llCopiere` de mai jos in aceeasi functie): +``` +* Nu adaug automat articolele din factura originala, ca sa dau posibilitate de modificare a cantitatilor, pretului, explicatiei +* ofrmdetaliifactura.do_adauga_tot() +``` +Liniile documentului sursa raman doar **candidate** in `crsarticole` (populat de `cursor_retur_document` +la §2.5) — operatorul le adauga manual (sau cu "adauga tot"), la fel ca la orice document nou. + +**2.8 Lista de preturi se adauga peste, doar la copiere.** Imediat dupa, tot in `factureaza()` +(`ofacturare.prg:454-473`, citat integral in `docs\cercetare\s4e_lista_preturi_pe_sursa.md` §1): +`pack_facturare.cursor_preturi(...)` intr-un cursor separat `crsArticoleTemp`, apoi +`SELECT crsArticole / APPEND FROM DBF(lcCursorTemp)` — lipeste lista de preturi completa peste +candidatii din documentul sursa, ca operatorul sa poata adauga si articole noi la copiere/modificare. +Vezi sectiunea 6 pentru siguranta acestui pas. + +--- + +## 3. Combo-ul `Ct_clb_fdoc` si realocarea de serie/numar la comutare + +**Azi, combo-ul traieste exclusiv in dialogul separat `frm_date_factura`/`frm_date_aviz`** +(clasa continuta in acelasi fisier text `ofacturare.vc2`, dar formular distinct de +`frm_facturare_articole`/`frm_facturare_articole2`, care contin gridul de linii). Handler-ul e legat +pe `LostFocus`, nu pe `InteractiveChange`: +``` +PROCEDURE Ct_clb_fdoc._combobox1.LostFocus && ofacturare.vc2:9857-9859 + thisform.do_schimba_tipdoc() +ENDPROC +``` +`do_schimba_tipdoc` (`ofacturare.vc2:9396-9438`), pas cu pas: +1. Determina `lnIdTipDoc` din textul combo-ului (`FACTURA`/`PROFORMA`/`BON FISCAL`/altfel `FACTURA`). +2. Cheama neconditionat `poDate.initializeaza_setari_document(...)` cu un cod special (`-101` bon + fiscal, `-102` proforma, sau `poDate.Tip` altfel) — realoca setarile de document (serie/numar) + pentru noul tip, inainte sa stie daca tipul chiar s-a schimbat. +3. **Iesire timpurie daca tipul nu s-a schimbat**: `IF poDate.nIdTipDoc = m.lnIdTipDoc THEN RETURN`. +4. Daca s-a schimbat: `poDate.nract = 0`, `poDate.serie_act = ""` (curata selectia locala, nesalvata + nicaieri inca), `poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc)` (**elibereaza in pool numarul + vechi**, rezervat pe tipul vechi — nu se pierde, devine disponibil pentru alt document/alta sesiune; + documentul nu are inca niciun numar scris in baza, fiind pre-Termina), apoi + `poDate.nIdTipDoc = m.lnIdTipDoc` (declanseaza `nIdTipDoc_Assign`, care actualizeaza + `eProforma`/`eBonFiscal`), si `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` reincarca + seriile disponibile pentru noul tip, populand `clb_serie_act`. +5. **Numarul vechi nu se "reia" automat** — utilizatorul trebuie sa aleaga din nou serie/numar pentru + noul tip din controlul `clb_serie_act`, care s-a reincarcat la pasul 4. + +**Ce NU atinge `do_schimba_tipdoc` azi**: `crsarticole`, `crsfactura`, `gestionabil`, `id_gestiune` — +nimic legat de linii. **Motivul e structural, nu o omisiune**: azi comutarea e posibila **doar** +inaintea oricarei linii, pentru ca `frm_date_factura` (unde traieste combo-ul) e un dialog modal +care se inchide (`Release ofrmceredate`, `ofacturare.prg:248`) **inainte** ca Do Case-ul de incarcare +a cursorului de linii sa ruleze (`:266`) — comutarea si incarcarea liniilor nu pot fi simultane azi. + +**Ce se schimba cand combo-ul traieste in formularul unificat, cu gridul deja prezent**: exact +premisa care dispare — combo-ul devine comutabil **si dupa** ce linii exista deja in `crsfactura`. +`do_schimba_tipdoc` ramane corect pentru partea de serie/numar (pasii 1-5 de mai sus se aplica +identic, indiferent daca gridul are linii sau nu — realocarea de serie e independenta de continutul +documentului). **Ce lipseste e pasul 1.2/1.3 de mai sus (marcarea `gestionabil=0`/`id_gestiune=-1000`), +care azi nu are nevoie sa fie legat de comutare pentru ca nu poate fi comutare cu linii deja +prezente.** Vezi sectiunea 4. + +--- + +## 4. Ce se rupe la unificare + +**4.1 Marcarea negestionabil nu mai are un singur moment de executie.** Azi exista un singur loc +(`ofacturare.prg:330-334`, dupa incarcarea cursorului, inaintea deschiderii gridului) unde +`eProforma=1` implica `gestionabil=0` in masa. In formularul unificat, cu combo-ul viu in acelasi +ecran cu gridul (decizia 10), sunt **trei momente distincte** care trebuie sa garanteze acelasi +invariant, nu unul: + a. **La deschiderea initiala**, daca formularul porneste direct pe tip Proforma (echivalent cu azi + — se poate pastra `UPDATE` de masa pe cursorul candidat, daca inca exista un cursor de masa + pentru sursa respectiva dupa S4/S4e; pe sursele deja mutate pe cautare filtrata/`APPEND BLANK` + — S4e — nu mai exista cursor de masa de actualizat, marcarea trebuie facuta la construirea + fiecarui rand). + b. **La adaugarea unei linii noi cat timp `poDate.eProforma=1`** — deja identificat ca gol de + proiectare in `s5b_proforma_descarcare_gestiune.md` §7 ("trebuie reimplementat linie-cu-linie, + la momentul in care fiecare linie e adaugata"), confirmat aici ca valabil si pentru cazul in + care operatorul a **pornit** documentul ca proforma inainte de a adauga prima linie (nu doar + dupa un switch). + c. **La comutarea combo-ului DUPA ce linii exista deja in `crsfactura`** (cazul explicit cerut de + brief) — scenariu care azi nu poate exista, deci nu are cod de reutilizat. `do_schimba_tipdoc` + trebuie extins (sau o metoda noua chemata din el) sa parcurga liniile deja prezente in + `crsfactura` si sa aplice acelasi tratament ca la 1.2-1.3: `gestionabil=0`, + `id_gestiune=-1000`, pe fiecare linie existenta, **cand comutarea intra pe Proforma**. + *Corolarul necesar, netratat de nicio decizie/raport anterior*: pentru fiecare linie deja + adaugata prin `do_alege_stoc` (gestionabila, cu `id_gestiune` real ales de operator), acea + alegere de gestiune/lot devine irelevanta odata marcata negestionabil — de decis daca se + **pastreaza tacit** valoarea veche (inofensiv, pentru ca `id_gestiune=-1000` o inlocuieste + oricum in parametrul trimis la scriere) sau se **sterge explicit** din obiectul liniei, ca sa nu + induca in eroare un ecran care ar afisa gestiunea aleasa pe o linie acum negestionabila. + +**4.2 Combo-ul viu tot timpul inseamna ca `eProforma` poate flutura de mai multe ori inainte de +Termina.** Azi tipul se alege o singura data, ireversibil (dialogul se inchide). In formularul +unificat, operatorul poate comuta Factura -> Proforma -> Factura de mai multe ori inainte de a apasa +`Termina`. Routing-ul de la Termina (sectiunea "Descoperire centrala") citeste `poDate.eProforma` +**doar la momentul apasarii** — corect pentru starea finala — dar **starea liniilor** (marcate sau nu +negestionabil, dupa istoricul comutarilor) nu se "reseteaza" singura la fiecare comutare, daca 4.1.c +nu implementeaza si drumul invers. Vezi sectiunea 5. + +**4.3 Realocarea de serie/numar (sectiunea 3) ramane corecta ca atare**, dar UX-ul ei (curatarea +`clb_serie_act`, cererea catre operator sa aleaga din nou seria) trebuie sa functioneze si cu gridul +de linii vizibil pe acelasi ecran — nu identificat niciun cod care sa presupuna ca gridul e ascuns in +timpul realocarii; e o verificare vizuala, nu o problema de proiectare gasita in cod. + +--- + +## 5. Drumul invers: proforma comutata inapoi in factura + +**Acesta e riscul cel mai serios gasit in aceasta cercetare, nesemnalat explicit in rapoartele +anterioare.** + +Scenariu: operatorul porneste documentul ca Proforma (sau comuta pe Proforma la un moment dat), +adauga linii — care, daca 4.1 e implementat corect, ajung marcate `gestionabil=0`/`id_gestiune=-1000` +pe `crsfactura`. Apoi, **inainte de Termina**, comuta combo-ul inapoi pe Factura. + +- **`poDate.eProforma` devine `0`** prin `nIdTipDoc_Assign`, corect. +- **La Termina, routing-ul alege `scrie_factura2`/`scrie_factura_avize`** (sectiunea "Descoperire + centrala") — care **chiar cheama** `contabilizeaza_articol` pentru fiecare linie. +- **`contabilizeaza_articol` verifica exact sentinela `id_gestiune <> -1000`** (`PACK:7472-7476`) + inainte de a chema `descarca_gestiune`. Liniile ramase marcate `-1000` de cand documentul era + proforma **nu vor descarca gestiune**, desi documentul final e o factura reala, nu o proforma. +- **Nu exista azi niciun mecanism care sa refaca `gestionabil`/`id_gestiune`** cand tipul revine la + Factura in aceeasi sesiune. Singurul loc din tot codul citit unde gestionabilitatea se + "restaureaza" e `cursor_retur_document` la **copiere** (`GESTIONABIL = B.IN_STOC` cand + `V_COPIERE=1`, sectiunea 2.6) — mecanism legat de incarcarea unui cursor **nou**, la deschiderea + unui document **nou**, nu de o comutare in acelasi document, in aceeasi sesiune, pe linii deja + existente in `crsfactura`. + +**Consecinta pe date**: o factura reala, emisa din formularul unificat dupa un du-te-vino prin +Proforma, poate iesi din sesiune cu stocul nedescarcat pentru articole care ar fi trebuit sa-l +descarce — silentios, fara eroare Oracle (sentinela `-1000` e o valoare valida pentru +`contabilizeaza_articol`, nu declanseaza nicio exceptie). + +**Ce trebuie proiectat, obligatoriu, pentru ca decizia 10 sa fie sigura**: la comutarea **dinspre** +Proforma **catre** orice alt tip, liniile care au fost marcate negestionabil **exclusiv din cauza +proformei** (nu articole real negestionabile in nomenclator) trebuie sa-si recapete +`gestionabil`/`id_gestiune` reale, inainte de a ajunge la `Termina`. Doua variante, niciuna aleasa +inca: + a. **Re-interogare per linie** la comutare (analog cu `cursor_retur_document.GESTIONABIL`, dar + aplicat pe liniile deja din `crsfactura`, nu la incarcarea unui cursor nou) — cere un apel + Oracle (sau citire din nomenclator local, daca exista deja incarcat) per linie afectata, si + re-deschiderea alegerii de gestiune/lot pentru cele gestionabile (echivalentul lui + `do_alege_stoc`, care nu a rulat cand linia a fost adaugata sub Proforma). + b. **Blocarea comutarii inapoi** cand exista deja linii adaugate sub Proforma — cere confirmare + sau refuza tranzitia, obligand operatorul sa stearga liniile si sa le re-adauge sub noul tip. + Mai simplu de implementat, cu cost UX (utilizatorul pierde munca de introducere). + +**Nu e o decizie de proiectare pe care raportul o ia in locul lui Marius** — vezi sectiunea 11, +punctul 1. + +--- + +## 6. `id_c` la copiere — raspuns cu dovada + +**Nu exista coliziune cu efect observabil pe drumul de copiere, azi.** Motivul, verificat direct pe +cod (nu presupus, extras din `s4e_lista_preturi_pe_sursa.md` §1, reconfirmat aici pentru S5b): + +`ofacturare.prg:454-473` face `APPEND FROM` peste `crsArticole`, lipind lista de preturi +(`crsArticoleTemp`, numerotata `id_c` independent, cu `ROWNUM` propriu Oracle) peste continutul deja +prezent (documentul copiat, incarcat la §2.5 din `cursor_retur_document`, cu propriul `id_c` +independent). Cele doua seturi de `id_c` **se pot suprapune la nivel de valoare** — asta e adevarat, +tehnic. Dar coliziunea **nu are efect**, pentru doua motive independente, ambele verificate: + +1. **`Case m.llCopiere` e prima ramura verificata** in Do Case-ul de incarcare a cursorului + (`ofacturare.prg:266-268`) — pentru orice document copiat, cursorul de baza e **intotdeauna** + `cursor_retur_document`, indiferent de tipul original. Un document copiat nu (mai) e niciodata de + tip `3` (comanda) dupa degradarea din `do_copiaza` (sectiunea 2.1: degradare catre + `1,5,7,10,22,23` — **CORECTIE runda 13**, cifra veche `1,5,10,22` era gresita; setul real e citit + din primul `CASE`, `ofacturare_comun.vc2:3693-3694`) — + deci **nu poate ajunge** in ramura `Inlist(poDate.Tip,3,21,25,28,42,47)` a lui `do_scrie_factura` + (`ofacturare.vc2:14332-14338`), care e singurul loc unde `id_c` conteaza pentru "cantitate + ramasa" (Rol A, per `s4_punct2_registru_cantitate_ramasa.md`). +2. **`do_sterge` potriveste pe `id_c`** (`ofacturare.vc2:14652-14655`), dar aceasta potrivire conteaza + doar daca ceva citeste `crsarticole` ca registru dupa aceea — ceea ce nu se intampla pe tipurile + rezultate din copiere (`1,5,7,10,22,23`, toate in ramura `Otherwise` a Do Case-ului de scriere, fara + `Calculate Sum(cantitate)`). + +**Concluzie, cu aceeasi rezerva ca in raportul-sursa**: coliziunea de `id_c` exista **tehnic** in +datele din `crsArticole` dupa copiere, dar **nu are cale prin care sa produca un efect gresit**, atata +timp cat tipul rezultat din copiere ramane in setul degradat (`1,5,7,10,22,23` — niciodata +`3,21,25,28,42,47`). +**Aceasta concluzie ramane valabila neschimbata si in formularul unificat**, pentru ca nu depinde de +forma formularului — depinde doar de faptul ca `do_copiaza` degradeaza tipul inaintea oricarei +incarcari de cursor, mecanism care nu se propune sa fie schimbat de S5b (sectiunea 7). + +**O singura conditie de pastrat, explicit**: daca vreo poveste viitoare schimba `do_copiaza` sa NU +mai degradeze tipul catre grupul "simplu" (de exemplu, sa permita copierea unei comenzi ca o comanda +noua, nu ca factura din lista de preturi), concluzia de mai sus **trebuie re-verificata** — riscul de +coliziune `id_c` descris in `s4e_lista_preturi_pe_sursa.md` verdict/punctul 1 ar deveni real. + +--- + +## 7. Ce NU se atinge — lista explicita + +Confirmate ca deja corecte si suficiente, fara nicio schimbare necesara pentru S5b: + +1. **Sentinela `id_gestiune = -1000`** in `contabilizeaza_articol` (`PACK:7472-7476`) — ramane gate-ul + de aparare pentru orice linie care ajunge totusi la contabilizare cu aceasta valoare (drumul + invers, sectiunea 5, o foloseste ca sa NU descarce gestiune — ceea ce e exact problema de acolo, + nu ceva de "reparat" in sentinela insasi). +2. **Routing-ul `scrie_proforma` vs. `scrie_factura2`/`scrie_factura_avize`** + (`ofacturare.vc2:14282-14300`) — corect prin constructie, citeste `poDate.eProforma` la momentul + potrivit (Termina), nu are nevoie de nicio schimbare. +3. **Cele cinci garzi `eProforma=0` pe atasamente** (`ofacturare.prg:1960,1996,2052,2063,2103`) — + raman neschimbate, nu au legatura cu formularul (grid vs. dialog separat), doar cu momentul + listarii/salvarii PDF, care ramane acelasi apel indiferent de forma UI. +4. **Raportul propriu** (`PROFORMA`/`PROFORMA_VAL`, `ofacturare.prg:1638-1643`) — alegerea ramane pe + `poDate.eProforma`, neschimbata. +5. **`do_copiaza` (degradarea de tip)** — ramane exact cum e, decizia S5b (D) o cere explicit + ("degradarea de tip din `do_copiaza` ramane pentru copiere si nu se aplica la regenerare"). +6. **`copiere_factura` -> `factureaza(tip, toFactura)`** — punctul de intrare ramane neschimbat. +7. **`completeaza_setari_document`** — comentariul de la `:370` (`nIdTipDoc` necopiat) e deliberat si + ramane corect: documentul nou pleaca mereu ca Factura, indiferent de sursa, exact ce cere decizia + S5b (copierea deschide "document nou", cu antetul editabil, nu proforma mostenita). +8. **`cursor_retur_document` cu `V_COPIERE=1`** (`PACK:3949-4000`) — restaurarea `GESTIONABIL = B.IN_STOC` + la copiere ramane corecta si suficienta pentru cazul "copiere", fara nicio schimbare (nu e acelasi + mecanism cu comutarea in-sesiune de la sectiunea 5, care ramane un gol real). +9. **Numarul alocat la copiere e intotdeauna nou** (`ofacturare.prg:208,211`, neconditionat de + `llCopiere`) — nimic de schimbat. +10. **Alocarea de serie/numar a lui `do_schimba_tipdoc`** (sectiunea 3) — mecanismul de dealocare + + realocare ramane corect si reutilizabil ca atare in formularul unificat; doar absenta lui legata + de linii (sectiunile 4-5) e golul de acoperit. + +--- + +## 8. Pasi de implementare, ordonati + +1. **Confirma pe date reale linia exacta unde `id_gestiune` devine `-1000`** pentru o linie de + proforma (incertitudinea de la sectiunea 1.3) — headless, cu un `crsfactura` construit manual din + `Scatter` pe o linie de proforma reala, inainte de orice alta schimbare de cod. + *Gata cand:* se confirma cu certitudine (nu argument indirect) fie linia din + `do_initializeaza_articol`, fie alt punct, unde proprietatea `id_gestiune` a liniei devine + efectiv `-1000`. +2. **Extrage marcarea "linie negestionabila pentru proforma" intr-o metoda proprie**, reutilizabila + din trei locuri (sectiunea 4.1: incarcare initiala, adaugare linie noua, comutare combo pe linii + existente) — un singur loc de intretinut pentru regula `eProforma=1 => gestionabil=0, + id_gestiune=-1000`, nu trei copii. + *Gata cand:* toate cele trei puncte de intrare cheama aceeasi metoda, verificat prin grep pe + codul nou. +3. **Leaga metoda de la pasul 2 de `do_schimba_tipdoc`**, pe ramura care duce spre Proforma, aplicata + pe toate liniile deja prezente in `crsfactura` la momentul comutarii (sectiunea 4.1.c). + *Gata cand:* pe un document cu linii deja adaugate ca Factura, comutarea combo-ului pe Proforma + marcheaza `gestionabil=0`/`id_gestiune=-1000` pe fiecare linie existenta, verificabil prin citirea + directa a `crsfactura` dupa comutare. +4. **Proiecteaza si implementeaza drumul invers** (sectiunea 5) — varianta (a) sau (b), decizie a lui + Marius (sectiunea 11, punctul 1). *Gata cand:* pe un document cu linii adaugate sub Proforma, + comutat inapoi pe Factura inainte de Termina, fie liniile isi recapata `gestionabil`/`id_gestiune` + reale (varianta a, verificabil prin `descarca_gestiune` apelat corect la emitere — vezi sectiunea + 9), fie comutarea inapoi e refuzata/necesita confirmare explicita (varianta b). +5. **Verifica riscul de la sectiunea 4.1, corolarul**: decide daca `id_gestiune`/lotul ales anterior pe + o linie acum negestionabila se pastreaza tacit sau se sterge din obiectul liniei — implementeaza + alegerea si documenteaz-o in cod (un rand de comentariu, per conventia proiectului). +6. **Regresie pe copiere**, fara nicio schimbare de cod asteptata (sectiunile 2, 6, 7) — pas de + verificare, nu de implementare: confirma ca formularul unificat, rulat pe drumul de copiere, produce + acelasi rezultat ca azi (sectiunea 9). +7. **Regresie pe proforma simpla** (fara comutare, document pornit direct ca Proforma) — confirma ca + pasii 2-3 nu au schimbat comportamentul cazului deja corect azi. + +*Depinde de:* S5 (acoperirea tipurilor), S4/S4e (forma finala a incarcarii liniilor — pasul 2 trebuie +sa stie daca opereaza pe un cursor de masa sau pe randuri individuale, dupa ce alte povesti decid +asta pentru fiecare sursa). + +--- + +## 9. Cum se verifica + +**Proba ceruta de plan**: o proforma emisa din formularul unificat nu produce nota contabila si se +listeaza pe raportul ei; o copie produce un document nou cu numar nou si acelasi continut. + +**Interogari SQL concrete** (numai `SELECT`, rulate cu unealta PowerShell + `sqlplus.exe`, per +mediul de proiect): + +```sql +-- 1. Proforma emisa din formularul unificat: fara nota contabila, fara descarcare de gestiune +SELECT id_vanzare, tip, eproforma, sters FROM vanzari WHERE id_vanzare = :ID_TEST; +-- eproforma trebuie sa fie 1 + +SELECT COUNT(*) FROM vanzari_detalii WHERE id_vanzare = :ID_TEST; +-- trebuie sa coincida cu numarul de linii adaugate (liniile SE scriu, doar nu se contabilizeaza) + +SELECT id_vanzare_det, id_articol, id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST; +-- id_gestiune trebuie sa fie NULL pe fiecare linie (sentinela -1000 tradusa de adauga_articol_factura) + +SELECT COUNT(*) FROM note_contabile nc + JOIN act a ON a.id_fact = nc.id_fact -- sau echivalentul legaturii act/nota folosite in schema +WHERE a.cod = (SELECT cod FROM vanzari WHERE id_vanzare = :ID_TEST); +-- trebuie sa fie 0 -- nicio nota contabila generata + +-- 2. Fara descarcare de gestiune: RUL nu are miscare noua dupa emiterea proformei +SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE AND id_articol IN (:articolele_testate); +-- trebuie sa fie identic cu inainte de emitere (0 randuri noi legate de acest document) + +-- 3. Copie: document nou, numar nou, acelasi continut +SELECT id_vanzare, numar_act, serie_act, eproforma FROM vanzari WHERE id_vanzare = :ID_COPIE; +-- eproforma = 0; numar_act/serie_act diferite de documentul sursa + +SELECT vd.id_articol, vd.cantitate, vd.pret, vd.id_gestiune + FROM vanzari_detalii vd WHERE vd.id_vanzare = :ID_COPIE + ORDER BY vd.id_articol; +-- comparat linie cu linie cu vanzari_detalii al documentului sursa (:ID_TEST) -- acelasi articol/cantitate/pret; +-- id_gestiune insa REAL (nu NULL), daca articolul e gestionabil -- confirma restaurarea de la sectiunea 2.6 + +-- 4. Drumul invers (sectiunea 5, dupa ce varianta e implementata): factura emisa dupa un du-te-vino prin Proforma +-- descarca gestiune normal, ca orice factura +SELECT id_vanzare, eproforma FROM vanzari WHERE id_vanzare = :ID_TEST_INVERS; +-- eproforma = 0 +SELECT id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST_INVERS; +-- NU mai e NULL pe liniile gestionabile -- confirma ca varianta aleasa la pasul 4 (sectiunea 8) functioneaza +SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE_INVERS AND id_articol IN (:articolele_testate); +-- trebuie sa arate miscare noua -- gestiunea s-a descarcat +``` + +**Protocol**: fiecare interogare rulata **inainte si dupa** implementare, pe date de test resetate +identic, per memoria de proiect "zero cazuri in date nu e dovada" — minim un caz per scenariu descris +mai sus (proforma simpla, proforma cu switch la comutare, copiere din proforma, drumul invers), +nu doar "a mers o data". + +--- + +## 10. Ce nu se poate testa headless + +- **Comutarea combo-ului `Ct_clb_fdoc` cu gridul de linii vizibil in acelasi ecran** — capcana deja + confirmata pe acest proiect (`grid-coloane-nu-se-materializeaza-headless`, memorie de proiect): + sub `-A -T`, `ColumnCount`/`RecordSource` raman artefacte, comportamentul real al `LostFocus` + pe combo si al reincarcarii `clb_serie_act` cere harnessul UI vizibil. +- **Dialogul `frm_articol_factura` vs. `do_alege_stoc`** (sectiunea 1.3, decizia de dialog dupa + `gestionabil`) — depinde de randare de formular modal, nu apelabil direct fara UI. +- **Verificarea vizuala ca liniile deja adaugate isi schimba starea la comutare** (sectiunea 4.1.c) — + daca implementarea alege sa arate un indicator vizual (de exemplu, o coloana/culoare care marcheaza + "negestionabil"), acel indicator nu se verifica headless; **continutul** `crsfactura` (valorile + `gestionabil`/`id_gestiune`) **se poate** verifica headless, cu apeluri directe la metoda noua din + pasul 2 (sectiunea 8), fara sa deschida formularul. +- **Realocarea vizuala a seriei/numarului in `clb_serie_act`** — control de UI, comportamentul lui de + reincarcare (`do_initializeaza`) nu se verifica fara randare. +- **Ce se poate verifica headless, direct**: continutul `crsfactura`/`poDate` dupa apeluri directe + (nu prin click) la metoda de marcare (pasul 2), la `do_schimba_tipdoc`, si la rutina drumului + invers (pasul 4) — cu date de test pregatite manual (un `crsfactura` cu 2-3 linii, `poDate.eProforma` + comutat programatic inainte si dupa) — confirma `gestionabil`, `id_gestiune`, `poDate.eProforma`, + fara sa deschida formularul. La fel, toate interogarile SQL din sectiunea 9 sunt verificabile + headless (SQL direct, fara UI). + +--- + +## 11. Riscuri si decizii ramase lui Marius + +1. **Drumul invers (sectiunea 5) — varianta (a) sau (b).** Cel mai important punct deschis al acestui + raport: fara o decizie explicita aici, decizia 10 din plan ("proforma foloseste acelasi formular") + ramane incompleta — un document care trece prin Proforma si revine la Factura in aceeasi sesiune + poate iesi cu stocul nedescarcat, silentios. **Recomandare**: varianta (a) (re-interogare si + re-deschidere a alegerii de gestiune la comutarea inapoi), pentru ca varianta (b) (blocarea + comutarii) contrazice explicit decizia 10 ("combo-ul ramane in antetul unificat", implicit + liber de folosit in ambele sensuri) si ar surprinde operatorul cu o pierdere de munca fara + avertisment prealabil la momentul comutarii spre Proforma. +2. **Ce se intampla cu `id_gestiune`/lotul ales anterior pe o linie marcata ulterior negestionabil** + (sectiunea 4.1, corolarul) — pastrare tacita (mai simplu, fara cod nou) sau stergere explicita + (mai clar pentru un ecran viitor care ar afisa gestiunea). **Recomandare**: pastrare tacita — nu + ajunge niciodata la server oricum (sentinela `-1000` trimisa explicit ignora orice valoare veche), + iar stergerea ar cere cod suplimentar fara beneficiu functional, doar cosmetic. +3. **Incertitudinea ramasa din raportul-sursa** (sectiunea 1.3: linia exacta unde `id_gestiune` devine + `-1000`) — necesara inainte de a scrie codul pasului 2 (sectiunea 8), nu doar o curiozitate. + Nu e o decizie de produs, e o verificare tehnica obligatorie inaintea implementarii. +4. **Indicator vizual pentru liniile marcate negestionabil la comutare** (mentionat la sectiunea 10) — + optional, nu cerut explicit de plan; de decis daca merita un semnal in grid (culoare/iconita) sau + ramane invizibil pana la emitere. Nu blocheaza nimic, dar afecteaza UX-ul comutarii repetate. +5. **Testarea pe date reale a `FACT-007`** (mentionata in raportul-sursa ca argument indirect, + nu dovada) — daca Marius vrea certitudine completa inainte de implementare, un test manual pe + mediu de dezvoltare (proforma cu articol gestionabil din comanda, verificare directa + `VANZARI_DETALII.ID_GESTIUNE IS NULL`) inchide definitiv acest gol, in afara perimetrului + read-only al acestei cercetari. + +--- + +## Handoff + +Cercetare + proiectare incheiate intr-o singura sesiune, fara sa fie nevoie de predare de context. +Toate cele 11 sectiuni cerute de brief sunt complete, cu `fisier:linie` verificat direct pe fisierele +text reale (`ofacturare.vc2`, `ofacturare_comun.vc2`, `ofacturare.prg`, `ofacturare_comun.prg` — nu +`.bak`) si pe corpul `PACK_FACTURARE` de pe `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`. + +**Descoperirea centrala** (sectiunea "Descoperire centrala"): mecanismul care blocheaza nota +contabila pe proforma nu e (doar) sentinela `-1000` din `contabilizeaza_articol`, ci alegerea +`scrie_proforma` (fara contabilizare deloc) vs. `scrie_factura2`/`scrie_factura_avize` (cu +contabilizare) in `do_scrie_factura`, facuta dupa `poDate.eProforma` la Termina. Aceasta descoperire +schimba unde trebuie sa se concentreze grija de proiectare: nu pe "cum ajunge `-1000` la server" +(deja acoperit corect de codul existent), ci pe **ce se intampla cu liniile deja marcate `-1000` cand +documentul nu mai e proforma la Termina** — exact riscul din sectiunea 5, singurul loc unde sentinela +chiar conteaza si azi nu exista nicio plasa. + +Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe +Oracle (doar `SELECT`/`Read`/`Grep` pe fisiere de pe disc). diff --git a/docs/cercetare/s5c_factura_din_proforma.md b/docs/cercetare/s5c_factura_din_proforma.md new file mode 100644 index 0000000..d6e6dcd --- /dev/null +++ b/docs/cercetare/s5c_factura_din_proforma.md @@ -0,0 +1,374 @@ +# S5C — Factura din proforma (proiectare) + +Status: INCHEIAT + +## Sarcina +Cerinta noua (decizia 43, runda 12): dintr-o proforma emisa sa se poata genera o +FACTURA ca document NOU (nu prin comutarea tipului pe acelasi document — comutarea +PROFORMA -> FACTURA cu linii e deja blocata cu mesaj). Mecanismul pare sa existe deja +pe calea de copiere (`do_copiaza`). + +## (a) Poate fi azi o proforma aleasa ca sursa de copiere? + +**Da, fara nicio excludere.** Trei dovezi convergente: + +1. `COMUN\clase\ofacturare_comun.vc2:4964-4974` — `frm_facturi.IsCopy(tnTip)`: + ``` + RETURN .T. && POT SA COPIEZ ORICE, TRATEZ TIPURILE IN DO_COPIAZA + *!* RETURN INLIST(m.lnTip, 1,5,7,10,22,23,43) && pot sa copii doar avize si facturi pret de lista, ... + ``` + Varianta veche (restrictiva, comentata) verifica doar `tip` (1-52) — niciodata `eproforma`. Varianta + activa returneaza necondiționat `.T.`. Nu exista, si n-a existat vreodata in acest cod, o excludere + pe `eproforma`. +2. `COMUN\clase\ofacturare_comun.vc2:5110` — vizibilitatea butonului de copiere pe randul selectat: + `Thisform.but_copiaza1.Visible = Thisform.IsCopy(crsFacturi.tip)` — acelasi apel, acelasi rezultat + necondiționat. +3. `COMUN\clase\ofacturare_comun.vc2:5082-5083` — gridul de facturi are un filtru dedicat pe + `eproforma`, cu proforma tratata ca subset normal, nu ascuns: + ``` + "Facturi&Avize\nofiled\E\(eproforma = 0)\" + crlf + ; + "Proforme\nofiled\E\(eproforma=1)\" + crlf + ; + ``` + Chiar filtrul "Proforme" arata explicit ca proformele sunt navigabile si selectabile normal in + acelasi grid — butonul de copiere ramane vizibil identic pe orice rand selectat din acest filtru. + +**Concluzie**: azi orice utilizator poate selecta o proforma in grid si apasa "Copiere (CTRL+K)" +(tooltip-ul insusi anunta explicit acest caz de folosire — vezi citatul din +`s5b_proiectare_proforma_copiere.md` §"1.4"/`proforma_copiere_puncte_intrare.md:41-47`). Nu exista +nicio garda de blocat, la niciun nivel (`IsCopy`, vizibilitate buton, filtru grid). + +## (b) Ce tip rezulta din copierea unei proforme? + +**Rezultatul e intotdeauna `nIdTipDoc=5` (FACTURA)** — corect pentru o factura fiscala — dar tipul de +business (`VANZARI.TIP`, 1-52) degradeaza dupa tabelul deja existent din `do_copiaza`, **nemodificat +pentru cazul proforma**: nu exista nicio ramura speciala pe `eproforma` in `do_copiaza` +(`COMUN\clase\ofacturare_comun.vc2:3628-3713`) — degradarea se face **strict dupa `loFactura.tip`**, +indiferent daca documentul sursa era proforma sau nu. + +**Pasul 1 — degradarea de tip business** (`:3693-3708`, tabel deja confirmat in +`s5b_proiectare_proforma_copiere.md` §2.1): +- Proforma poate exista doar pe tipuri "de factura" (combo-ul `Ct_clb_fdoc` traieste in + `frm_date_factura`, nu si in `frm_date_aviz` — cf. `proforma_copiere_puncte_intrare.md` §1). Setul + posibil de `loFactura.tip` pentru o proforma e deci printre `{1,2,3,4,5,6,7,8,9,10,43,44,45,47,48,49,51}` + (grupul "Facturi" din `COMUN\docs\tipuri_documente_facturare.md`). +- Din acest set: `{1,5,7,10}` raman neschimbate (`CASE INLIST(loFactura.tip,T1,T5,T7,T10,T22,T23)`, + `:3694`); `{2,3,4,8,43,44,45,47,48,49,51}` degradeaza la `T1` (`:3696-3697`); `{6}` la `T10` + (`:3698-3699`); `{9}` la `T5` (`:3700-3701`). +- Deci, pentru orice proforma emisa astazi (indiferent de tipul de business original), documentul + copiat va avea `TIP` in `{1,5,7,10}` — nucleul "lista de preturi" (lei/valuta) sau credit note. + +**Pasul 2 — `nIdTipDoc` NU se propaga** (confirmat deja in +`s5b_proiectare_proforma_copiere.md` §2.3, `COMUN\programe\ofacturare_comun.prg:370`: +`*.nIdTipDoc = toDateAnterior.nIdTipDoc` — comentata). Documentul nou primeste `nIdTipDoc` implicit +dupa tipul degradat: `Do Case` la `COMUN\programe\ofacturare.prg:187-196`, toate valorile din +`{1,5,7,10}` cad sub `5` (FACTURA) → `poDate.eProforma = 0` pe documentul nou, **indiferent ca sursa +era proforma**. + +**Concluzie pentru (b)**: mecanismul de copiere transforma azi orice proforma intr-o **FACTURA reala +(`nIdTipDoc=5`, `eproforma=0`)**, de tip business `1` (lista de preturi, cel mai frecvent caz — orice +tip din `{2,3,4,8,43,44,45,47,48,49,51}` cade tot pe `T1`), `5`/`10` (daca sursa era deja pe lista de +preturi in valuta/factura valuta), sau `7` (credit note, ramas neschimbat). Tipul rezultat **e cel bun +pentru o factura fiscala** din perspectiva `nIdTipDoc`/`eproforma` (nu ramane proforma), dar tipul de +business e intotdeauna **degradat catre "lista de preturi"** — proforma emisa dintr-o comanda (`tip=3`) +sau dintr-un aviz (`tip=4`) devine, dupa copiere, o factura "banala" `tip=1`, **la fel ca la orice alta +copiere non-proforma** (comportament deja documentat si acceptat in S5b §7 punctul 5: "degradarea de +tip din `do_copiaza` ramane pentru copiere si nu se aplica la regenerare" — nicio schimbare ceruta aici +fata de acel raport). + +## (c) Legatura proforma -> factura (VANZARI_CORESP.TIP) + +### Structura tabelei (confirmata pe schema vie, `MARIUSM_AUTO`) +``` +ID_VANZARE_CORESP NUMBER NOT NULL (PK, populat de trigger BEFORE INSERT din SEQ_VANZARI_CORESP) +ID_VANZARE_FACT NUMBER NOT NULL +ID_VANZARE_AVIZ NUMBER NOT NULL (nume generic mostenit — nu neaparat un aviz, vezi mai jos) +STERS NUMBER NULL +TIP NUMBER NOT NULL +``` +Singurul trigger gasit pe tabela (`TRG_VANZARI_CORESP_BEFOINS`, prezent identic in schemele `ACN` si +`MARIUSM_AUTO`) doar genereaza PK-ul din secventa — nu atinge/valideaza `TIP`. + +### Cine scrie in tabela — un singur punct, in tot codul Oracle +`grep` pe `all_source` (schema vie) dupa `VANZARI_CORESP` gaseste **un singur writer**: +`pack_facturare.scrie_corespondente_vanzari` (`PACK_FACTURARE:15481-15516`, singurul `INSERT INTO +VANZARI_CORESP` din toata baza). Nu exista alt pachet, trigger sau produs ROA (ROAGEST, ROAIMOB, +ROACONTRACTE, ROAACNPRO etc.) care sa scrie in aceasta tabela — `PACK_FACTURARE` traieste in `COMUN`, +partajat de toata suita, deci **orice** consumator ar trece prin acelasi punct. + +### Valorile de `TIP` deja folosite — confirmate cod + date +**Cod** (singurele 3 apeluri la `scrie_corespondente_vanzari` din tot `PACK_FACTURARE`, +`:14818-14839`, in `finalizeaza_factura`): +- `TIP=1`: `WHEN pack_facturare.ntip = 4 THEN ... scrie_corespondente_vanzari(1)` — factura scrisa + dintr-un aviz (`ntip=4` = "FACT. DIN AVIZ"). Foloseste `pack_facturare.clistaid_avize` (lista + separata, populata pentru facturare-din-aviz cu selectie multipla). +- `TIP=2`: `WHEN pack_facturare.ntip = 24 THEN ... scrie_corespondente_vanzari(2)` — aviz de retur + (`ntip=24`). +- `TIP=3`: `WHEN pack_facturare.ntip IN (8, 9) THEN ... scrie_corespondente_vanzari(3)` — factura de + retur. +Toate trei folosesc `pack_facturare.nid_vanzare` (documentul curent, tocmai scris) ca +`ID_VANZARE_FACT`; pentru `TIP=1` sursa vine din `clistaid_avize`, pentru `TIP=2`/`TIP=3` (ramura +`ELSE` din `scrie_corespondente_vanzari`, `:15490-15491`) din `pack_facturare.clistaid` generic. + +**Date** (schema `MARIUSM_AUTO`, interogare directa `SELECT tip, COUNT(*) FROM vanzari_coresp GROUP BY +tip`): `TIP=1` → 6 randuri, `TIP=2` → 1, `TIP=3` → 1. **Zero randuri cu alt `TIP`.** Coerent cu codul +(nu exista alt loc care sa scrie alte valori) — dovada dubla (cod + date), nu doar una din ele (per +memoria de proiect "zero cazuri in date nu e dovada": aici avem *si* absenta din date, *si* absenta +structurala in cod, ceea ce e o dovada tare, nu doar un data point izolat). + +### Valoare noua libera +**`TIP=4` e liber**, confirmat pe ambele fronturi (cod: niciun apel existent la +`scrie_corespondente_vanzari(4)`; date: zero randuri `TIP=4` in schema testata). Recomandare: **`TIP=4` += "factura scrisa dintr-o proforma"**, urmatorul numar disponibil in secventa deja folosita (1,2,3), +fara conflict cu niciun consumator existent (nu exista alt produs ROA care sa scrie sau sa citeasca +`VANZARI_CORESP` in afara de `PACK_FACTURARE`). Structura tabelei **nu se schimba** — doar o noua +valoare de enum in coloana `TIP`, exact cum a cerut misiunea. + +### Unde s-ar scrie corespondenta — punct de intrare, cu rezerva importanta +Tiparul de azi (`TIP=1/2/3`) scrie corespondenta **din interiorul** `finalizeaza_factura`, intr-un +`CASE` cheie **`pack_facturare.ntip`** — semnalul de business-type al documentului nou scris. Aceasta +cheie **nu poate distinge** "factura rezultata dintr-o copiere de proforma" de "orice alta factura +obisnuita cu acelasi tip degradat" (vezi (b): rezultatul copierii unei proforme cade intotdeauna in +`{1,5,7,10}`, niciodata `4`/`24`/`8`/`9` — deci nu exista azi nicio ramura `WHEN` pe care s-o extinda, +si niciuna nu s-ar putea scrie corect doar din `ntip`, pentru ca acelasi `ntip=1` rezulta si dintr-o +copiere obisnuita de factura, nelegata de nicio proforma). Vezi Capcana (i) pentru raspunsul complet la +"de unde stie Oracle ca sursa a fost o proforma" si propunerea de proiectare pentru rezolvare. + +## Capcana (i): supravietuieste id_fact/id_vanzare al sursei pana la scrierea corespondentei? + +**Da, `id_vanzare` al proformei supravietuieste — dovedit pas cu pas — dar faptul ca sursa "era o +proforma" (nu doar "era un document oarecare") NU supravietuieste nicaieri azi. Asta e golul real.** + +**Partea care functioneaza deja, neschimbata:** +1. `do_copiaza` (`COMUN\clase\ofacturare_comun.vc2:3690-3691`): `SELECT crsFacturi / SCATTER NAME + loFactura MEMO` — `loFactura` e o copie completa a randului sursa din grid, **inclusiv + `loFactura.id_vanzare`** (campul cheie) si `loFactura.eproforma` (coloana de grid confirmata la + `ofacturare_comun.vc2:2494`, `Column37.ControlSource = "eproforma"`) — **ambele disponibile in + acest moment**, inainte ca degradarea de tip (`:3693-3708`) sa ruleze. +2. `copiere_factura` (`COMUN\programe\oproceduri_facturare.prg:150-153`) → `factureaza(loFactura.tip, + loFactura)` — `loFactura` intreg (cu `id_vanzare` si `eproforma` inca pe el) devine `toFactura`, + parametrul opțional. +3. `completeaza_setari_document(toDateAnterior, .T.)` + (`COMUN\programe\ofacturare_comun.prg:362-412`), ramura de copiere: **`.listaid = + toDateAnterior.id_vanzare`** (`:387`) — **aici** `id_vanzare` al proformei ajunge in `poDate`, + proprietate care **nu e atinsa de degradarea de tip** (aceea opereaza doar pe `loFactura.tip`, + variabila locala din `do_copiaza`, complet separata de `poDate`). +4. `poDate.listaid` calatoreste neschimbat prin toata sesiunea (folosit si la §2.5 din + `s5b_proiectare_proforma_copiere.md` pentru `cursor_retur_document`), pana la scriere: + `do_scrie_articole` (`COMUN\clase\ofacturare.vc2:6104`): `Alltrim(Nvl(poDate.listaid,''))` e trimis + ca parametru `V_LISTAID` catre `pack_facturare.initializeaza_date_factura`, care il pune in + `pack_facturare.clistaid := V_LISTAID` (`PACK_FACTURARE:1883`) — **inainte** ca `do_scrie_factura` + sa aleaga `scrie_factura2`/`finalizeaza_factura`. La momentul in care `scrie_corespondente_vanzari` + ar rula, `pack_facturare.clistaid` **este deja** `id_vanzare`-ul proformei (ca text), exact valoarea + pe care ramura `ELSE` a lui `scrie_corespondente_vanzari` (`:15490-15491`, folosita azi de `TIP=2` si + `TIP=3`) o citeste. + +**Partea care NU exista azi — golul real:** `completeaza_setari_document` (pasul 3 de mai sus) copiaza +explicit `id_lucrare`, `nrord`, `id_sectie`, `sectie`, `id_agent`, `nume_agent`, `id_delegat`, +`nume_delegat`, `BIdelegat`, `CNPdelegat`, `nrinmat`, `id_masina`, `listaid`, `descriere`, `id_client`, +`nume_client` de pe `toDateAnterior` — **dar niciodata `.eproforma`**. Documentul nou stie "din ce +`id_vanzare` a fost copiat" (`.listaid`), dar **nu stie daca acel document sursa era o proforma sau o +factura obisnuita** — informatia se pierde exact la acest pas, inainte sa ajunga la Oracle. + +**De ce conteaza**: `finalizeaza_factura` (Oracle) decide azi ce `TIP` de corespondenta sa scrie +uitandu-se **doar** la `pack_facturare.ntip` (business-type-ul documentului nou). Cum am stabilit la +(b), rezultatul unei copieri de proforma cade intotdeauna in `{1,5,7,10}` — **acelasi interval** in +care cade si o copiere obisnuita (proforma sau nu). Oracle **nu poate reconstitui** din `ntip` singur +daca documentul curent a fost copiat dintr-o proforma sau dintr-o factura normala — nu exista niciun +semnal in `pack_facturare` care sa poarte aceasta distinctie. + +**Ce trebuie adaugat, minimal** (propunere, nu implementare): +1. **VFP**: la `completeaza_setari_document:387`, langa `.listaid = toDateAnterior.id_vanzare`, adauga + o proprietate noua, de exemplu `.lProformaSursa = (toDateAnterior.eproforma = 1)` — un simplu boolean + client-side, capturat in acelasi moment si din acelasi obiect sursa unde `listaid` e deja capturat + (zero risc suplimentar de "se pierde intre timp", pentru ca foloseste exact acelasi tipar dovedit la + pasul 3-4 de mai sus). +2. **Scrierea corespondentei, NU prin `finalizeaza_factura`/`ntip`**: pentru ca `ntip` nu poate purta + distinctia (vezi mai sus), cea mai sigura ruta e un apel Oracle **explicit, separat**, facut din VFP + imediat dupa ce `do_scrie_factura` confirma succesul scrierii documentului nou, gardat de + `poDate.lProformaSursa`: + ``` + IF poDate.lCopiere AND poDate.lProformaSursa + lcSql = [{call pack_facturare.scrie_corespondente_vanzari(4)}] + * ... goExecutor.oExecute(lcSql) ... + ENDIF + ``` + Aceasta reutilizeaza **exact** procedura Oracle existenta, neschimbata (ramura `ELSE`, + `pack_facturare.clistaid`/`pack_facturare.nid_vanzare` inca valide in sesiune la acel moment, per + pasul 4 de mai sus) — **zero cod PL/SQL nou**, doar un nou punct de apel VFP si o noua valoare de + `TIP`. Alternativa (adaugarea unei ramuri noi in `CASE`-ul din `finalizeaza_factura`, cheie pe un + parametru nou trimis prin `V_PARAMETRU_ADITIONAL` sau similar) ar fi mai fragila: acel `CASE` e + punctul comun al **oricarei** facturi/aviz/retur scrise in tot codul, folosit de toate produsele ROA + prin `PACK_FACTURARE` partajat — o ramura noua acolo, keyed pe o combinatie de `ntip` + un flag nou, + ar creste suprafata unui switch deja incarcat, pentru un caz care oricum nu poate fi exprimat corect + doar din `ntip`. Apelul explicit izolat e mai simplu si nu atinge deloc `finalizeaza_factura`. +3. **Ordinea conteaza**: apelul explicit trebuie sa ruleze **inainte** ca orice alt cod Oracle din + aceeasi sesiune sa resetteze `pack_facturare.nid_vanzare`/`clistaid` (de exemplu, o alta scriere de + document in acelasi batch) — de plasat imediat dupa `do_scrie_factura`, inainte de listare/atasamente + (care oricum nu ating Oracle pentru partea de scriere). Daca se prefera robustete completa (fara + dependenta de stare de sesiune Oracle), varianta alternativa e sa se paseze explicit + `V_ID_VANZARE_FACT` (= `poDate.id_vanzare`, cunoscut client-side dupa scriere) si + `V_ID_VANZARE_PROFORMA` (= `poDate.listaid`, deja cunoscut) direct ca parametri, printr-o mica + procedura Oracle noua care ocoleste `clistaid`/`nid_vanzare` cu totul — cost: un nou obiect PL/SQL in + loc de zero, beneficiu: independenta de ordinea altor apeluri din sesiune. **Alegere ramasa lui + Marius** (vezi propunerea finala). + +## Capcana (ii): `marcheaza_facturat` pe proforma — corect sau nu? + +**Nu trebuie chemat pe proforma. Confirmat cu dovada, pe patru argumente convergente.** + +**Ce face, exact** (`PACK_FACTURARE:15381-15418`): +```sql +PROCEDURE marcheaza_facturat(V_VERIFICARE IN NUMBER) IS +BEGIN + IF V_VERIFICARE = 0 THEN + UPDATE VANZARI SET FACTURAT = 1, ID_UTILFACT = pack_facturare.nid_util + WHERE ID_VANZARE IN (SELECT id_vanzare_aviz FROM vanzari_coresp + WHERE id_vanzare_Fact = pack_facturare.nid_vanzare + AND sters = 0 AND tip <> 3) + AND FACTURAT = 0; + ELSE + -- varianta "verificare cantitate ramasa": marcheaza FACTURAT=1 doar daca toata cantitatea + -- din VANZARI_DETALII a documentului sursa a fost deja consumata in VANZARI_CANTITATI + ... + END IF; +END; +``` +Flipeaza `VANZARI.FACTURAT=1` (+`ID_UTILFACT`) pe **documentul(ele) sursa** legate prin +`VANZARI_CORESP` de documentul tocmai scris — fie neconditionat (`V_VERIFICARE=0`), fie doar cand +cantitatea documentului sursa a fost epuizata (`V_VERIFICARE=1`, mecanismul de **facturare partiala** a +avizelor, bazat pe `VANZARI_CANTITATI`). + +**Argumentul 1 — nu exista un `TIP` cu care sa se cupleze corect azi.** `marcheaza_facturat` se cheama +azi **doar** din `finalizeaza_factura`, in aceleasi doua ramuri `WHEN pack_facturare.ntip = 4` si +`WHEN pack_facturare.ntip = 24` care scriu si corespondenta (`:14823-14831`) — cuplate mereu impreuna cu +`scrie_corespondente_vanzari(1)`/`(2)`, niciodata separat. Cum am stabilit la Capcana (i), calea propusa +pentru `TIP=4` (factura din proforma) e un **apel explicit separat**, in afara acestui `CASE` — deci +n-ar exista niciun loc "natural" unde `marcheaza_facturat` sa se agate fara sa introduca exact acelasi +tip de cod nou-scris ca la corespondenta insasi. + +**Argumentul 2 — mecanismul de "cantitate ramasa" (`V_VERIFICARE=1`) nu are pe ce sa opereze pentru o +proforma.** Verificarea citeste `VANZARI_CANTITATI` (populat **doar** de +`scrie_cantitati_vanzari_avize`, apelata **doar** in ramura `ntip=4` a lui `finalizeaza_factura`). +Proforma nu trece niciodata prin `finalizeaza_factura` (confirmat in +`s5b_proiectare_proforma_copiere.md`, "Descoperire centrala": `scrie_proforma` cheama doar +`scrie_in_vanzari`) — deci **nu exista niciodata randuri `VANZARI_CANTITATI` pentru o proforma**. +Varianta `V_VERIFICARE=1` a lui `marcheaza_facturat` ar gasi mereu "cantitate ramasa = cantitate totala" +(nimic consumat), fie nu ar marca niciodata `FACTURAT=1` (comportament inutil), fie (daca s-ar folosi +gresit `V_VERIFICARE=0`) ar marca neconditionat, ca la punctul urmator. + +**Argumentul 3 — asimetrie la stergere: proforma ar ramane blocata `FACTURAT=1` definitiv.** +`sterge_factura` (`PACK_FACTURARE:5501-5533`) reface `FACTURAT=0` pe documentele sursa **doar** pentru +`V_TIP=24` (aviz retur, `:5502-5510`) si `V_TIP=4` (factura din aviz, `:5525-5533`) — tipurile care azi +chiar cheama `marcheaza_facturat`. Rezultatul copierii unei proforme cade intotdeauna in `{1,5,7,10}` +(per (b)) — **niciuna din aceste valori nu are ramura in `CASE`-ul de stergere**. Daca s-ar chema +`marcheaza_facturat` la scrierea facturii din proforma, iar utilizatorul ar sterge ulterior acea +factura, **proforma sursa ar ramane cu `FACTURAT=1` pentru totdeauna** — o stare orfana, fara niciun +cod care s-o repare, introdusa exact de acest apel. Corespondenta `VANZARI_CORESP` insasi nu are aceeasi +problema (nimic n-o citeste ca sa se strice daca ramane "orfana" dupa stergerea facturii — cel mult +devine o legatura catre un document sters, inofensiv). + +**Argumentul 4 — nimic nu filtreaza azi proformele dupa `FACTURAT`, deci n-ar exista niciun beneficiu +de blocat.** Spre deosebire de avize (`cursor_avize`/candidatii de facturat, filtrati implicit prin +fluxul dedicat "Factura din aviz") si comenzi (`VCOMENZI.FACTURAT=0`, folosit explicit in +`cauta_date_comanda`/`cauta_date_comanda_gest`, `PACK_FACTURARE:15659,15685`, ca sa nu ofere din nou o +comanda deja facturata), **nimic in codul citit** filtreaza dupa `FACTURAT` cand se alege o proforma ca +sursa de copiere — confirmat la (a): `IsCopy`/vizibilitatea butonului/filtrul de grid nu se uita +niciodata la `facturat`. Marcarea n-ar preveni nicio re-copiere accidentala a aceleiasi proforme (care +oricum ramane posibila, vezi propunerea finala, punctul de decizie 2). + +**Concluzie**: `marcheaza_facturat` **nu trebuie chemat** pentru "factura din proforma". Se scrie +**doar** corespondenta (`VANZARI_CORESP`, `TIP=4`), fara actualizare de `VANZARI.FACTURAT` pe proforma. +Confirma explicit suspiciunea din misiune ("proforma nu e document de livrare") cu evidenta concreta: +proforma n-are urma in `VANZARI_CANTITATI` (Argumentul 2), n-are ramura de reversare la stergere +(Argumentul 3), si n-are niciun consumator care sa citeasca `FACTURAT` pe ea (Argumentul 4). + +## Propunere de proiectare + +Mecanismul de baza **ramane copierea existenta** (`do_copiaza`/`copiere_factura`/`factureaza`, +neschimbata in structura ei) — cerinta "document nou, nu comutare pe acelasi document" e deja +satisfacuta de calea de azi (confirmat la (a)/(b)). Ce lipseste e strict **legatura persistata** +proforma → factura si excluderea explicita a lui `marcheaza_facturat`. Pasi, in ordine: + +1. **Captureaza `eproforma` al sursei la copiere.** In `completeaza_setari_document` + (`COMUN\programe\ofacturare_comun.prg:387`, ramura `tlFactura=.T.`), langa + `.listaid = toDateAnterior.id_vanzare`, adauga o proprietate noua pe `poDate` + (ex. `.lProformaSursa`), citind `toDateAnterior.eproforma` — singurul loc unde informatia mai e + disponibila, inainte sa se piarda (Capcana (i)). *Gata cand:* dupa copierea unei proforme, + `poDate.lProformaSursa = .T.`; dupa copierea oricarui alt document, `.F.`. +2. **Rezerva `TIP=4`** in `VANZARI_CORESP` pentru "factura scrisa dintr-o proforma" — doar o conventie + documentata (ca la 1/2/3), zero schimbare de schema (confirmat la (c): tabela ramane + `ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP, STERS`). +3. **Scrie corespondenta cu un apel Oracle explicit, separat de `finalizeaza_factura`.** Imediat dupa + ce `do_scrie_factura` confirma succesul (acelasi punct unde azi se decid listarea/atasamentele), + daca `poDate.lCopiere AND poDate.lProformaSursa`: `{call + pack_facturare.scrie_corespondente_vanzari(4)}` — **reutilizeaza procedura Oracle existenta, + neschimbata** (ramura `ELSE`, deja scrisa pentru `TIP=2`/`3`). Zero cod PL/SQL nou pentru scriere. + *Alternativa mai robusta, cu cost:* o mica procedura Oracle noua care primeste explicit + `V_ID_VANZARE_FACT`/`V_ID_VANZARE_PROFORMA` ca parametri (nu se bazeaza pe + `pack_facturare.clistaid`/`nid_vanzare` inca valide in sesiune) — de ales intre simplitate (varianta + de mai sus) si robustete fata de ordinea apelurilor (varianta cu parametri expliciti); vezi punctul + de decizie 1 mai jos. +4. **NU cheama `marcheaza_facturat`.** Confirmat cu 4 argumente independente la Capcana (ii) — proforma + nu are `VANZARI_CANTITATI`, nu are ramura de reversare la stergere, nimic n-o filtreaza dupa + `FACTURAT`, si oricum n-ar exista un `WHEN pack_facturare.ntip=...` natural de unde s-o cheme (calea + aleasa la pasul 3 e explicit separata de `finalizeaza_factura`). +5. **Afisare optionala "provine din proforma X"** pe factura noua — se poate construi din + `VANZARI_CORESP` (`TIP=4`) la fel ca tiparul deja folosit pentru retur (`legatura_linie_retur.md` + §5): la nivel de **document**, nu de linie (nicio schimbare fata de tiparul deja acceptat pentru + retur — nu exista niciun tabel de legatura la nivel de linie in tot codul citit, per acelasi raport). + Nu e cerut explicit de misiune, dar e disponibil "gratis" din legatura scrisa la pasul 3. + +**Ce NU se schimba** (confirmat, fara nevoie de atingere): +- `IsCopy`, vizibilitatea `But_copiaza1`, filtrul de grid "Facturi&Avize / Proforme" — proforma ramane + selectabila ca sursa exact ca azi (a). +- Degradarea de tip din `do_copiaza` (`{1,5,7,10}` pentru orice proforma) si alocarea de numar nou — + raman neschimbate, deja produc `nIdTipDoc=5`/`eproforma=0` corect pentru documentul nou (b). +- `cursor_retur_document`, restaurarea `GESTIONABIL=B.IN_STOC` la copiere — neschimbat, mecanism deja + corect pentru orice copiere (mostenit din `s5b_proiectare_proforma_copiere.md` §7). +- `scrie_corespondente_vanzari` insasi — zero cod PL/SQL nou (varianta recomandata la pasul 3). + +### Riscuri si ce ramane de decis de Marius + +1. **Apel explicit pe stare de sesiune (`clistaid`/`nid_vanzare`) vs. procedura noua cu parametri + expliciti** (pasul 3) — simplitate (zero cod Oracle nou, dar depinde ca nimic altceva sa nu resetteze + starea pachetului intre scrierea facturii si apelul de corespondenta) vs. robustete (un obiect + PL/SQL nou, insensibil la ordine). Recomandare: varianta simpla, **daca** apelul se plaseaza imediat + dupa `do_scrie_factura`, inainte de orice alt apel Oracle din acelasi flux (listare/atasamente nu + ating Oracle pentru scriere) — de verificat pe cod exact la implementare ca nu exista un apel Oracle + intercalat care ar reseta `pack_facturare.nid_vanzare`. +2. **Poate fi copiata de mai multe ori aceeasi proforma?** Azi nu exista nicio garda (FACTURAT nu se + marcheaza — decizia de la Capcana (ii)), deci un utilizator poate genera N facturi din aceeasi + proforma, fiecare cu propriul rand `TIP=4` in `VANZARI_CORESP` catre aceeasi proforma sursa. E + comportamentul implicit al oricarei copieri azi (nimic n-o limiteaza nici pentru facturi/avize + normale) — de confirmat daca e acceptabil sau daca se doreste un avertisment (nu o blocare, pentru + ca ar contrazice tiparul existent de copiere liber-repetabila). +3. **Guard simetric la stergere?** `sterge_factura` blocheaza azi stergerea unui aviz/unei facturi care + are deja o factura de retur legata (`TIP=3`) sau facturi/avize-retur legate (`TIP IN (1,2)`, + `PACK_FACTURARE:5450-5476`). Nu exista cerinta explicita in misiune pentru un guard simetric pe + `TIP=4` (ex. "nu poti sterge o proforma care are deja o factura generata din ea") — de decis daca se + doreste, sau daca proforma ramane liber stersa oricand (comportament actual, neschimbat daca nu se + adauga nimic). +4. **Afisarea "provine din proforma X"** (pasul 5) — optionala, nu ceruta explicit; de decis daca + merita implementata acum sau ramane pentru o poveste ulterioara (costul e mic, data fiind legatura + deja scrisa la pasul 3). +5. **`TIP=4` ca valoare aleasa** — libera si fara conflict azi (confirmat cod+date), dar e o alocare + ireversibila din punct de vedere al datelor odata folosita in productie; de confirmat explicit de + Marius inainte de implementare (nu doar acceptata tacit). + +## STARE / CE RAMANE + +**Cercetare + proiectare incheiate.** Toate cele trei intrebari (a/b/c) si ambele capcane (i/ii) au +raspuns cu dovada `fisier:linie`, verificat pe fisierele text reale (`ofacturare_comun.vc2`, +`ofacturare.vc2`, `ofacturare_comun.prg`, `ofacturare.prg` — nu `.bak`) si pe `PACK_FACTURARE` +(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`), plus verificare directa pe schema Oracle vie +(`MARIUSM_AUTO`: structura `VANZARI_CORESP`, distributia `TIP` din date, singurul trigger existent). + +Descoperirea centrala a acestei cercetari: mecanismul de copiere de azi rezolva deja "document nou" si +"tip corect" (a, b) fara nicio schimbare, dar **pierde tacit** informatia "sursa era o proforma" chiar +in metoda care ar trebui s-o pastreze (`completeaza_setari_document:387`, care copiaza `listaid` dar nu +si `eproforma`) — motiv pentru care legatura `VANZARI_CORESP` nu se poate agata de `CASE`-ul existent +din `finalizeaza_factura` (cheie doar pe `ntip`, care nu poarta aceasta distinctie) si are nevoie de un +semnal nou, client-side, plus un apel Oracle explicit separat (pasii 1 si 3 din propunere). + +Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe +Oracle (doar `SELECT`, rulat cu `sqlplus.exe` pe schema `MARIUSM_AUTO`). diff --git a/docs/cercetare/s8_incarcare_document.md b/docs/cercetare/s8_incarcare_document.md new file mode 100644 index 0000000..2f09502 --- /dev/null +++ b/docs/cercetare/s8_incarcare_document.md @@ -0,0 +1,720 @@ +# S8 — Proiectare: incarcarea documentului existent in formularul unificat + +Livrabilul povestii **S8** din `docs\plan_13_unificare_formular_facturare.md:2944-2962` (etapa II, +deciziile 5, 8, 9, 19, 35, 50). Cercetare **READ-ONLY** — zero editari de cod, zero write-back, zero +`git_sync.ps1` / `txt2vcx.ps1`, zero commit. Pe Oracle **numai `SELECT`** pe dictionar +(`all_tab_columns` pentru `VANZARI`, `VANZARI_DETALII`, `FACT_VFACTURI`, `FACT_VFACTURI_DETALII`). +`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar +citite, neatinse. + +**Conventie de marcare**, folosita peste tot in raport: +- **[V]** = verificat direct pe cod / pe dictionarul Oracle, cu `fisier:linie` sau nume de coloana; +- **[D]** = dedus din cod verificat, dar concluzia insasi n-a fost executata / observata; +- **[N]** = nestabilit, cu motivul scris. + +--- + +## 0. Verdict (rezumat) + +Schita din plan — *„`completeaza_setari_document` pentru antet + `cursor_retur_document(V_COPIERE = 1)` +pentru linii, fara degradarea de tip din `do_copiaza`"* — e corecta ca directie, dar **niciuna dintre +cele doua piese nu incarca documentul**, si asta nu e o nuanta de implementare: + +1. **`completeaza_setari_document(toFactura, .T.)` copiaza 16 proprietati din ~120 [V]** + (`COMUN\programe\ofacturare_comun.prg:361-411`). Nu copiaza **niciunul** dintre cei 14 parametri de + identitate/antet ai lui `modifica_date_factura`, in afara de `id_delegat` / `id_masina` / `id_agent`. + Serie, numar, data, scadenta, `text_aditional`, `id_facturare`, `id_ruta`, `tip_saft`, `efactura`, + `listare_detaliata`, `dataora_exp`, discountul de document, `discount_evidentiat`, `eproforma`, + valuta, cursul — **toate lipsesc**. Sectiunea 1.2 le da pe toate, cu sursa. +2. **`cursor_retur_document(V_COPIERE = 1)` nu umple gridul documentului, ci gridul-sursa.** Pe calea de + copiere, rezultatul lui intra in `crsarticole` (selectorul din stanga), iar `crsfactura` (documentul) + se creeaza **gol** si ramane gol — comentariul e explicit: *„Nu adaug automat articolele din factura + originala, ca sa dau posibilitate de modificare"* (`COMUN\programe\ofacturare.prg:455-457`, + `:338` `creeaza_facturacrs([crsfactura])`) **[V]**. Transferul in document se face **numai** prin + `do_adauga_tot` → `do_adauga_articol`, care pentru articolele gestionabile trece prin + **dialogul de alegere din stoc** (`do_alege_stoc`). Sectiunea 4.3. +3. **`cursor_retur_document` nu intoarce `ID_VANZARE_DET` si nu intoarce `TAXCODE` [V]** + (lista completa de coloane: `PACK_FACTURARE:3949-4054`). Fara `ID_VANZARE_DET`: + - **cheia de linie presupusa de S8b (§1.2 al raportului S8b) nu exista** pe calea propusa; + - **ruta ieftina `modifica_explicatie_articol` devine neapelabila** — primul ei parametru **este** + `V_ID_VANZARE_DET` (`PACK_FACTURARE:14511-14519`) **[V]**. + Fara `TAXCODE`, cerinta explicita a planului („explicatia si `taxcode` pe fiecare linie") nu e + acoperita. + +**Recomandarea centrala a acestei proiectari:** S8 **nu** se construieste pe perechea din schita, ci pe +**precedentul care exista deja si face exact acest lucru** — `relisteaza_ofacturare_stoc` +(`COMUN\programe\ofacturare_stoc.prg:456-742`) **[V]**, care reconstituie `poDate` + cursorul de linii +dintr-un document salvat, prin `FACT_VFACTURI` (antet) + `FACT_VFACTURI_DETALII` (linii), pentru +relistare. `FACT_VFACTURI_DETALII` **are** `ID_VANZARE_DET` si `TAXCODE` **[V]**. Detaliile, +compromisurile si ce ramane totusi de completat — sectiunea 1.3 si 4. + +--- + +## 1. Inventarul complet a ce se incarca, camp cu camp + +### 1.1 Cele doua canale de citire, comparate pe coloane [V] + +Sursa: `PACK_FACTURARE:3949-4054` (`cursor_retur_document`) si `all_tab_columns` pentru +`FACT_VFACTURI_DETALII` / `VANZARI_DETALII`. + +| Ce trebuie pe linie | `cursor_retur_document(V_COPIERE=1)` | `FACT_VFACTURI_DETALII` | Exista pe `VANZARI_DETALII`? | +|---|---|---|---| +| `ID_VANZARE_DET` (cheia liniei) | **NU** | **DA** | DA | +| `TAXCODE` | **NU** | **DA** | DA | +| `EXPLICATIE` | DA | DA | DA | +| `ID_POL` (politica de pret pe linie) | DA | **NU** (doar `NUME_LISTA_PRETURI`) | DA | +| `PRETD` / `ID_VALUTAD` | DA (`PRETD`) | **NU** | DA | +| `GESTIONABIL` (flag calculat) | DA (calculat, vezi 1.4) | **NU** | — (derivat din `ID_GESTIUNE`) | +| `DIFERENTA` | pliata in `PRET` (vezi 1.4) | **NU** | DA | +| `ID_GESTIUNE`, `LOT`, `SERIE`, `CANTITATE`, `PRET`, `PRET_CU_TVA`, `PROC_TVAV`, `ID_JTVA_COLOANA`, `ID_JTVA_COLOANA_EX`, `PRET_ACHIZITIE`, `DISCOUNT_UNITAR`, `ID_VALUTA`, `CURS`, `MULTIPLICATOR` | DA | DA | DA | +| `ID_CTR` / `NUMAR_CONTRACT` | **NU** | **DA** | DA (`ID_CTR`) | + +**Concluzia operationala: niciunul dintre cele doua canale nu e suficient singur [V].** Cele doua +coloane care lipsesc din `cursor_retur_document` (`ID_VANZARE_DET`, `TAXCODE`) exista **amandoua pe +`VANZARI_DETALII`**, deci sunt disponibile fara nicio schimbare de structura — lipseste doar din lista +`SELECT`. + +**Trei variante, cu recomandare:** + +| Varianta | Ce cere | Risc | +|---|---|---| +| **(A)** adaugarea celor doua coloane in `SELECT`-ul lui `cursor_retur_document` | doua randuri in PL/SQL, plus `A1.ID_VANZARE_DET, A1.TAXCODE` in subselectul intern | Procedura e folosita si de **copiere** si (prin `cursor_retur`) de **retur**. Coloanele in plus ajung in `crsarticole` → `Scatter Name poArticol` → si `taxcode` **s-ar gathera** in `crsfactura` (campul exista deja acolo, `creeaza_facturacrs`, `ofacturare_comun.prg:1785`) **[V]**. Adica **copierea ar incepe sa transporte `taxcode`** — probabil o corectie, dar e o **schimbare de comportament pe o cale existenta**, nu una inerta. | +| **(B) procedura noua**, `cursor_editare_document`, clona cu cele doua coloane in plus — **RECOMANDAT** | o procedura noua in `pack_facturare`, zero atingere pe cele existente | Duplicare de SQL (~100 randuri). Costul e intretinerea, nu regresia. Garantie de regresie zero **prin constructie**, in acelasi spirit ca solutia `SET_IDFACT` din S9. | +| **(C)** citire din `FACT_VFACTURI_DETALII` (calea `relisteaza_ofacturare_stoc`) | zero cod Oracle nou | Pierde `ID_POL`, `PRETD`/`ID_VALUTAD`, `GESTIONABIL` si tratamentul `DIFERENTA`/valuta din `cursor_retur_document` — adica exact partea grea. Ar cere refacerea ei in VFP. | + +*Recomandare:* **(B)**, cu `SELECT`-ul copiat identic din `cursor_retur_document` plus +`A1.ID_VANZARE_DET` si `A1.TAXCODE`, si cu ramura `GESTIONABIL` decisa explicit (1.4). + +### 1.2 Antetul — ce se incarca, si de unde [V] + +`poDate` e `oDateFactura` (`COMUN\programe\ofacturare_comun.prg:99-320`). Coloana „sursa" e coloana din +`FACT_VFACTURI` (view-ul din spatele `crsFacturi`, folosit deja de `do_modifica` +`ofacturare_comun.vc2:4560-4568` si de `relisteaza_ofacturare_stoc` `ofacturare_stoc.prg:504-509`), +sau `VANZARI` direct. + +Legenda coloanei „azi": **CSD** = acoperit de `completeaza_setari_document(…, .T.)`; **GOL** = nimeni +nu-l incarca azi pe calea de copiere, deci e **gol de umplut in S8**. + +#### (a) Identitatea documentului — antetul vizibil + +| `poDate.` | Sursa | Azi | Nota | +|---|---|---|---| +| `nIdTipDoc` (FACTURA/PROFORMA/BON) | derivat din `tip` + `eproforma` | **GOL** — linia e **comentata explicit** in CSD (`ofacturare_comun.prg:366`) | vezi 3.2 | +| `tip` | `FACT_VFACTURI.TIP` | **GOL** (si azi **degradat** de `do_copiaza`) | S8 il pastreaza, per plan | +| `eProforma` | `FACT_VFACTURI.EPROFORMA` | **GOL** — `eproforma` nu apare deloc in `ofacturare_comun.prg` (rezultatul 1 al rundei 13) | | +| `serie_act` | `FACT_VFACTURI.SERIE_ACT` | **GOL** (CSD il pune doar in `.descriere`, ca text) | vezi 4.2 — conflict cu `poGeneratorNumere` | +| `nract` | `FACT_VFACTURI.NUMAR_ACT` | **GOL** (idem) | idem | +| `dataact` | `FACT_VFACTURI.DATA_ACT` | **GOL** (`Init` pune **azi**) | | +| `datascad` | `FACT_VFACTURI.DATA_SCAD` | **GOL** (`Init` calculeaza din `gnZileScadentaFact`) | | +| `zi_curs` | **NU EXISTA COLOANA** — vezi 1.5 | **GOL, si nerecuperabil** | raspuns la intrebarea deschisa din S8b §5.2 | +| `in_valuta` / `id_valuta` / `nume_valuta` | `FACT_VFACTURI.IN_VALUTA` / `ID_VALUTA` / `NUME_VAL` | **GOL** (`Init` deriva `in_valuta` din `tip`) | | +| `Curs` / `multiplicator` | `FACT_VFACTURI.CURS` / `MULTIPLICATOR` | **GOL** | pe linii, din `VANZARI_CURSURI` | + +#### (b) Partener si sursa + +| `poDate.` | Sursa | Azi | +|---|---|---| +| `id_client` / `nume_client` | `ID_PART` / `CLIENT` | **CSD** | +| `cod_fiscal` | `FACT_VFACTURI.COD_FISCAL` | **GOL** | +| `sold_lei` / `sold_valuta` | recalculate la deschidere (derivate, read-only) | **GOL** — se recalculeaza, nu se restaureaza | +| `listaid` (id comanda / contract / lista avize) | vezi 1.6 | **CSD, dar cu valoare GRESITA pentru editare** — CSD scrie `.listaid = toDateAnterior.id_vanzare` (`ofacturare_comun.prg:383`) **[V]** | +| `descriere` (nr. comanda/contract/avize) | `FACT_VFACTURI.ALTELE` / `COMANDA` / `CONTRACT` / `AVIZE` | **CSD, dar cu serie+numarul documentului insusi**, nu al sursei (`:384`) | +| `id_gestiune_init` | `FACT_VFACTURI.ID_GESTIUNE` | **GOL** | +| `id_pol` / `nume_politica` | **NU EXISTA pe `VANZARI`** — doar `VANZARI_DETALII.ID_POL` | **GOL** — vezi 1.5 | +| `id_ctr` / `contract` | `VANZARI.ID_CTR` / `VANZARI.CONTRACT` | **GOL** | + +#### (c) Pliat — analitice (grupul A, read-only in #13, decizia 26) + +| `poDate.` | Sursa | Azi | +|---|---|---| +| `id_sectie` / `sectie` | `FACT_VFACTURI.ID_SECTIE` / `SECTIE` | **CSD** | +| `id_lucrare` / `nrord` | `ID_LUCRARE` / `LUCRARE` | **CSD** | +| `id_responsabil` / `responsabil` | **NU EXISTA pe `VANZARI`** | **GOL, si nerecuperabil din `VANZARI`** — liniile sunt **comentate** in CSD (`:369-370`) **[V]** | +| `id_venchelt` / `venchelt` | **NU EXISTA pe `VANZARI`** | **GOL** — idem, comentate (`:373-374`) **[V]** | + +> Faptul ca `ID_RESPONSABIL` si `ID_VENCHELT` **nu sunt coloane pe `VANZARI`** (verificat pe +> `all_tab_columns`) e **dovada structurala** ca grupul A traieste la nivel de **linie de nota**, nu de +> antet — exact ce spunea `rute_scriere_antet.md`, dar acum cu argument de schema, nu de cautare. **[V]** + +#### (d) Pliat — alte date (`frm_alte_date`) si cei 14 parametri + +| `poDate.` | Sursa | Azi | +|---|---|---| +| `id_delegat` / `nume_delegat` / `BIdelegat` / `CNPdelegat` | `ID_DELEGAT` / `DELEGAT` / `BIDELEGAT` / `CNPDELEGAT` | **CSD** | +| `id_masina` / `nrinmat` | `ID_MASINA` / `NRINMAT` | **CSD** | +| `id_agent` / `nume_agent` | `ID_AGENT` / `NUME_AGENT` | **CSD** | +| `id_facturare` / `adresa_facturare` | `ID_FACTURARE` / `ADRESA_FACTURARE` | **GOL** | +| `dataora_exp` | `DATAORA_EXP` | **GOL** | +| `text_aditional` | `TEXT_ADITIONAL` | **GOL** — plus capcana de la 1.7 | +| `nListareDetaliata` | `LISTARE_DETALIATA` | **GOL** | +| `tip_saft` | `TIP_SAFT` | **GOL** (`Init` pune `380`) | +| `eFactura` | `EFACTURA` | **GOL** (`Init` pune `0`) | +| *`id_ruta`* | `ID_RUTA` | **GOL, si nu exista proprietate pe `oDateFactura`** — vezi 1.5 | +| `afisare_scadenta` | `AFISARE_SCADENTA` | **GOL** (`Init` pune `1`; ROACONTRACTE il recalculeaza) | +| `tva_incasare` | `TVA_INCASARE` | **GOL** (`Init` ia `goCalendar.tva_incasare`) | +| `institutie_publica` | `FACT_VFACTURI.INSTITUTIE_PUBLICA` | **GOL** | +| `nTipFactura` | `TIP_FACTURA` | **GOL** | +| `nIdBeneficiar` | `ID_BENEFICIAR` | **GOL** | +| `id_ordl` | `ID_ORDL` | **GOL** | +| `coeficient_k` | `VANZARI.COEFICIENT_K` | **GOL** | +| grupul C (`ntip_incasare`, `serie_chit`, `nr_incasare`, `incasat`, `id_casa`) | `TIP_INCASAT`, `SERIE_INCASAT`, `NR_INCASAT`, `SUMA_INCASAT` | **GOL** — blocat in etapa I, dar vezi 2.3 | + +#### (e) Discountul de document — nu e in `poDate` + +**[V]** Discountul de document **nu are proprietate pe `oDateFactura`**: traieste in proprietatile +formularului `frm_facturare_articole.ndiscfactron` / `.ndiscfactval` +(`ofacturare.vc2:5286-5287` declaratie `*p:`, `:5317-5318` initializare la `0`, +`:11325`/`:11337` `ControlSource` pe cele doua textbox-uri, `:14272-14274` citirea la scriere). + +| Ce | Sursa | Cum se incarca | +|---|---|---| +| discount de document, lei | `VANZARI.DISCOUNT` (in lei cand `IN_VALUTA=0`) | `Thisform.ndiscfactron` | +| discount de document, valuta | `VANZARI.DISCOUNT` (in valuta cand `IN_VALUTA=1`) | `Thisform.ndiscfactval`, plus `ndiscfactron = Round(ndiscfactval * Curs / multiplicator, …)` | +| `discount_evidentiat` | `VANZARI.DISCOUNT_EVIDENTIAT` | `poDate.discount_evidentiat` (`ControlSource` la `ofacturare.vc2:11305` si `:15968`) | + +**Precedentul exact exista** si trateaza deja bifurcatia lei/valuta: +`ofacturare_stoc.prg:553-561` **[V]** — `lnDiscountVal = discount` cand `in_valuta = 1`, altfel +`lnDiscount = discount`. S8 copiaza acest tipar. + +**Atentie [V]:** `oDateFactura.Init` pune `.discount_evidentiat = IIF(TYPE('gnDiscountEvidentiat')='N', +gnDiscountEvidentiat, 1)` (`ofacturare_comun.prg:238`) — **optiunea de firma, nu valoarea documentului**. +Daca S8 nu suprascrie, un document emis pe alta setare apare cu discountul evidentiat altfel decat a +fost emis, si — pentru ca `discount_evidentiat` **atinge sumele** (e parametru al lui `scrie_factura2`, +`ofacturare.vc2:14358`) — ar declansa **regenerare la simpla deschidere**. Fals pozitiv de acelasi tip +cu lookup-ul delegatului, dar cu efect mai scump. + +### 1.3 Liniile — inventar + +Coloanele intoarse de `cursor_retur_document(V_COPIERE=1)`, in ordinea din `SELECT` +(`PACK_FACTURARE:3963-4022`) **[V]**: `ID_C` (=`ROWNUM`), `ID_ARTICOL`, `LOT`, `SERIE`, `ID_POL`, +`ID_VALUTA`, `DISCOUNT_UNITAR`, `DISCOUNT_UNITAR_VAL`, `CODMAT`, `CODBARE`, `DENUMIRE`, `UM`, +`GESTIONABIL`, `CANTITATE`, `PROC_TVAV`, `ID_JTVA_COLOANA`, `PRETURI_CU_TVA`, `CURS`, `MULTIPLICATOR`, +`PRET`, `PRET_VAL`, `TIP_VALUTA`, `NUME_VAL`, `EXPLICATIE`, `ID_GESTIUNE`, `PRET_ACHIZITIE`, `PRETD`, +`ID_JTVA_COLOANA_EX`. + +**Lipsesc, si sunt necesare pentru S8/S8b:** `ID_VANZARE_DET`, `TAXCODE`, `ID_CTR`, `CODMATC`, +`COD_UM_ISO`, `CONT`. Ultimele trei sunt in `crsfactura` si vin azi pe alte cai +(`do_adauga_articol` cere `codmatf` cu un `SELECT` separat, `ofacturare.vc2:12932-12934` **[V]**). + +### 1.4 Doua capcane in `cursor_retur_document`, ambele verificate direct + +**(i) `GESTIONABIL` cu `V_COPIERE = 1` vine din nomenclatorul CURENT, nu din document [V]** +(`PACK_FACTURARE:3990-3997`): + +``` +(case when V_PROFORMA = 1 then 0 + when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE, starea de AZI + else A.GESTIONABIL -- NVL2(A1.ID_GESTIUNE,1,0), starea DOCUMENTULUI + end) AS GESTIONABIL +``` + +Pentru **copiere** e corect (documentul nou se emite cu regulile de azi). Pentru **editare** e o +divergenta tacuta: un articol care a fost facturat gestionabil si intre timp a fost trecut pe +`IN_STOC = 0` (sau invers) se incarca cu **alt regim** decat cel cu care e scris in `VANZARI_DETALII`. +Efectul practic: articolul trece pe alta ramura in `do_adauga_articol` (dialog de stoc vs. fara), +deci se schimba si ce se descarca la reemitere. **Recomandare: pe calea de editare se foloseste +`A.GESTIONABIL` (ramura `else`), adica adevarul documentului** — argument suplimentar pentru varianta +(B) din 1.1. **Aceasta e a cincea valoare din rezultatul 6 al handoff-ului (`IN_STOC` din nomenclator), +regasita pe calea de CITIRE, nu doar pe cea de scriere.** + +**(ii) `PRET` vine rotunjit si cu `DIFERENTA` pliata inauntru [V]** (`PACK_FACTURARE:4008-4014`): +`ROUND(...) + A.DIFERENTA`. Confirma rezultatul 6 din handoff. Consecinta directa pentru **S8b**: +comparatia de pret la confirmare trebuie facuta pe **aceasta forma** (rotunjita + `DIFERENTA`), nu pe +`VANZARI_DETALII.PRET` brut — altfel orice document cu `DIFERENTA <> 0` apare modificat la deschidere. + +### 1.5 Trei campuri de antet care nu au unde sa fie salvate — deci nu se pot restaura [V] + +Verificat pe `all_tab_columns` pentru `VANZARI`: + +| Camp | Constatare | Ce inseamna pentru S8 | +|---|---|---| +| **`zi_curs`** | **Nu exista coloana `ZI_CURS` pe `VANZARI`.** Se stocheaza rezultatul (`CURS`, `MULTIPLICATOR`, si `VANZARI_CURSURI` pe linie), nu ziua de la care s-a luat cursul. | **Inchide intrebarea deschisa din S8b §5.2.** Nu e „S8 trebuie sa suprascrie implicitul cu valoarea salvata" — **nu exista valoare salvata**. Se incarca `Curs`/`multiplicator` din document, iar `zi_curs` **se exclude din comparatie** si (recomandat) **se ascunde pe calea de editare**, ca sa nu sugereze o valoare pe care documentul n-o are. Ascunderea are precedent in acelasi `Do Case` (`ofacturare.vc2:9720-9725`, tipurile 8 si 9). | +| **`id_pol`** (politica de preturi, antet) | Nu exista pe `VANZARI`; exista **pe linie** (`VANZARI_DETALII.ID_POL`). | Antetul nu are ce sa afiseze ca „politica documentului". Recomandat: pe editare se afiseaza politica **doar daca e unica pe toate liniile**, altfel gol — si e oricum **blocat** (grupul B, 3.1). | +| **`id_ruta`** | Exista pe `VANZARI` (`ID_RUTA`) si e **unul din cei 14**, dar **`oDateFactura` nu are proprietate `id_ruta`** (verificat pe lista completa de proprietati, `ofacturare_comun.prg:99-218`). Azi valoarea circula prin `poRec` (`Scatter` din `crsfacturi`), nu prin `poDate` (`ofacturare_comun.vc2:4601`). | **Proprietate noua pe `oDateFactura`**, altfel `but_modifica` (S8c) n-are de unde sa citeasca ruta. Gol de umplut, semnalat aici pentru ca S8c il presupune existent. | + +### 1.6 Legatura cu sursa — `id_comanda`, `id_ctr`, lista de avize + +Ce exista, verificat: + +| Sursa | Unde e salvata | Citibila? | +|---|---|---| +| comanda | `VANZARI.ID_COMANDA` (+ `VANZARI.COMANDA`, text) **[V]** | **DA**, direct din `FACT_VFACTURI.ID_COMANDA` | +| contract | `VANZARI.ID_CTR` (+ `VANZARI.CONTRACT`, text) **[V]** | **DA**, direct din `FACT_VFACTURI.ID_CTR` | +| avize | `VANZARI_CORESP(ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)` + `VANZARI.AVIZE` (text redundant) **[V]** (`PACK_FACTURARE:15494-15516`) | **DA ca date, NU ca procedura** — vezi mai jos | + +**[V] Nu exista in `pack_facturare` nicio procedura care sa CITEASCA lista sursa a unui document.** +Cele 9 aparitii ale lui `VANZARI_CORESP` in export sunt: 6 in garzile lui `sterge_factura` +(`:5454-5530`), 1 `UPDATE ... STERS = 1` tot acolo (`:5582`), 1 subselect in +`scrie_cantitati_vanzari_avize` (`:6319`), 1 `INSERT` in `scrie_corespondente_vanzari` (`:15494`). +**Citirea e cod nou** — un `SELECT` simplu, dar de scris: + +```sql +SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP + WHERE ID_VANZARE_FACT = :id AND STERS = 0 AND TIP = 1 +``` + +**Semantica lui `TIP` nu e stabilita in acest raport [N]** — `scrie_corespondente_vanzari(V_TIP)` +comuta doar sursa listei (`clistaid_avize` pentru `TIP = 1`, `clistaid` altfel, `:15485-15493`), iar +valorile efective (`1/2/3`) vin din `CASE`-ul lui `finalizeaza_factura`, necitit aici. Handoff-ul +rundei 13 le da ca `1/2/3` folosite, `4` liber. **De confirmat ramura cu ramura la implementare**, nu +de preluat din raport — e exact tiparul care a produs cifra gresita a rundei 13. + +**Nota de proiectare:** `poDate.listaid` e **suprasolicitat**. Pe calea de copiere/editare el trebuie sa +fie `id_vanzare`-ul documentului (ca `cursor_retur_document` sa-i citeasca liniile), dar pentru +reemitere `pack_facturare.clistaid` / `clistaid_avize` trebuie sa contina **lista sursei originale**. +Sunt **doua valori diferite in acelasi camp**, la momente diferite. Precedentul din +`relisteaza_ofacturare_stoc` arata ca problema e veche si **nerezolvata**: acolo +`poDate.listaid = Iif(poDate.tip = 3, Alltrim(Str(id_comanda)), [0])` **[V]** +(`ofacturare_stoc.prg:531`) — cu ramura de **contract comentata** la `:529`, deci nici acel precedent +nu restaureaza sursa pentru contracte sau avize. **S8 are nevoie de un camp separat** +(propunere: `poDate.cListaSursa` + `poDate.cListaSursaAvize`), nu de o a doua semnificatie a lui +`listaid`. Vezi si sectiunea 6. + +### 1.7 `text_aditional` — trei transformari intre ce se vede si ce se salveaza [V] + +1. **Truncat la 100 de caractere** la scriere: `LEFT(Nvl(poDate.text_aditional,[]),100)` + (`ofacturare.vc2:14356`, identic pe toate ramurile `scrie_*`). +2. **`CR+LF` inlocuit cu `Chr(170)`** imediat inainte: + `poDate.text_aditional = Strtran(poDate.text_aditional, Chr(13)+Chr(10), Chr(170), 1, 100, 1)` + (`:14281`) — **si atribuirea e pe `poDate` insusi**, deci obiectul ramane modificat dupa scriere. +3. Pe calea `modifica_date_factura` **nu exista** nici truncare, nici substitutie + (`ofacturare_comun.vc2:4608` trimite `?poRec.text_aditional` ca parametru legat) **[V]** — + deci **cele doua rute salveaza forme diferite ale aceluiasi camp**. + +**Consecinte pentru S8:** la incarcare, `Chr(170)` trebuie convertit inapoi in `CR+LF` (altfel textul +apare pe un rand cu caractere ciudate), iar comparatia din S8b trebuie facuta pe forma **normalizata** +(vezi 5.2). Fara asta, orice document emis cu text pe mai multe randuri apare „modificat" la +deschidere. **Nesemnalat pana acum in niciun raport.** + +--- + +## 2. Initializarile „pentru document nou" care trebuie sarite la editare + +Cerinta obligatorie din S8b, rezultatul 5, plus inventarul cerut de briefing. + +### 2.1 Lookup-ul „ultimul delegat / ultima masina" — **garda exista deja** [V] + +`frm_alte_date.Init`, `COMUN\clase\ferestre_cere_date.vc2:3105-3208`. Codul real +(`:3110-3136`) — si aici raportul S8b si planul descriu situatia **incomplet**: + +``` +If poDate.eProforma = 0 + If Empty(Nvl(poDate.id_delegat,0)) And Empty(Nvl(poDate.id_masina,0)) && :3111 + poDate.id_delegat = 0 + poDate.id_masina = 0 + ... cauta_date_ultima_factura[_tip](...) && :3118-3124 +``` + +**Lookup-ul e deja conditionat de „ambele goale".** Deci: + +- pentru un document care **are** delegat sau masina salvate, e suficient ca **S8 sa populeze + `poDate.id_delegat` / `id_masina` INAINTE de construirea lui `frm_alte_date`** — lookup-ul nu mai + ruleaza, fara niciun cod nou. `completeaza_setari_document` le pune deja (1.2.d), deci pe calea de + editare conditia e indeplinita **din ordinea de apel**, nu dintr-un semnal; +- **riscul real ramane, dar e mai ingust decat il descrie S8b:** el se manifesta **exact pe documentele + care n-au nici delegat, nici masina salvate** — un caz frecvent (multe facturi se emit fara delegat). + Pentru acelea, lookup-ul umple `poDate` cu ultimul delegat al clientului, si atunci: ori documentul + apare modificat, ori `modifica_date_factura` scrie **un delegat pe care documentul nu l-a avut + niciodata**. Sub `modifica_date_factura` campul se scrie **neconditionat** (`ID_DELEGAT = V_ID_DELEGAT`, + `PACK_FACTURARE:14462`) **[V]**, deci scrierea gresita e certa, nu probabila. + +**Mecanismul propus (minim, si in acord cu tiparul existent):** parametru nou pe `frm_alte_date.Init` +sau — mai ieftin si mai greu de ratat — **o proprietate pe `poDate`**, `poDate.lEditare`, citita in +conditia de la `:3111`: + +``` +If !poDate.lEditare And Empty(Nvl(poDate.id_delegat,0)) And Empty(Nvl(poDate.id_masina,0)) +``` + +Argumentul pentru proprietate pe `poDate` si nu parametru: `frm_alte_date` **nu e singurul** formular +care are initializari de acest tip (2.2), `poDate` e deja obiectul citit de toate, si e acelasi tipar +cu `lCopiere`, care exista deja pe `oDateFactura` (`ofacturare_comun.prg:212`) **[V]**. + +**„Ce se intampla cand documentul chiar n-are delegat salvat":** cu garda de mai sus, `poDate.id_delegat` +ramane `.NULL.` (valoarea implicita din declaratia de clasa), campul se afiseaza gol, iar la salvare +`modifica_date_factura` primeste `NULL` si scrie `NULL` — **identic cu starea din baza**. Adica exact +comportamentul dorit: documentul ramane cum a fost. Fara garda, valoarea devine `0` (atribuirea de la +`:3112-3113`) **si apoi** rezultatul lookup-ului. + +> **Diferenta fata de S8b, spusa explicit:** S8b cerea „sarirea explicita a lookup-ului"; codul real +> arata ca sarirea e **deja implicita** pentru documentele cu delegat, si e **necesara explicit** doar +> pentru cele fara. Concluzia lui S8b (S8 trebuie sa faca ceva) ramane valida; **motivul si perimetrul +> se schimba**, iar asta reduce riscul de la „primul document editat al unui client cu activitate +> recenta" la „primul document **fara delegat** al unui client cu activitate recenta". + +### 2.2 Alte initializari de acelasi tip — inventar cu `fisier:linie` [V] + +Cautate in `oDateFactura.Init`, `frm_alte_date.Init`, `frm_date_factura.Init`, `frm_date_aviz.Init`. + +| # | Loc | Ce face | Efect pe calea de editare | Gravitate | +|---|---|---|---|---| +| 1 | `ofacturare_comun.prg:232-236` | `.Data`, `.dataireg`, `.dataact` = azi (sau ultima zi a lunii de lucru); `.datascad` din `gnZileScadentaFact`; `.zi_curs` = azi | Documentul apare cu **data de azi** si scadenta recalculata | **Mare** — atinge 2 din cei 4 campuri de identitate | +| 2 | `ofacturare_comun.prg:238` | `.discount_evidentiat` = `gnDiscountEvidentiat` (optiune de firma) | Vezi 1.2.e — poate declansa **regenerare** la simpla deschidere | **Mare** | +| 3 | `ofacturare_comun.prg:233` | `.tva_incasare` = `goCalendar.tva_incasare` (starea de azi a firmei) | Un document emis in alt regim se incarca cu regimul curent | Medie | +| 4 | `ofacturare_comun.prg:237` | `.in_valuta = 1` pentru `tnIdSet` / `tnTip` din lista | Derivat din tip, nu din document — coincide de obicei, dar nu prin constructie | Mica | +| 5 | `ofacturare_comun.prg:241-243` | `.initializeaza_politica_pret()` pentru tipurile 23, 30, 41 — apel `actualizeaza_politica_pret` | Politica **curenta**, nu cea a documentului | Medie (tipuri de transfer) | +| 6 | `ofacturare_comun.prg:240` | `.initializeaza_setari_document()` → `actualizeaza_document(tip)` → `id_fdoc` / `fdoc` | Reconfigureaza tipul de document din setarile curente | Medie | +| 7 | `ofacturare_comun.prg:245-283` | ramura **ROACONTRACTE** (`goContract`): suprascrie `id_client`, `nume_client`, `cod_fiscal`, `listaid`, `descriere`, `id_sectie`, `id_responsabil`, `id_valuta`, **si `.datascad = .dataact + lnScadentaIncasare`**, **si `.afisare_scadenta`** | Daca `goContract` exista in sesiune, **suprascrie antetul documentului editat cu datele contractului** | **Mare**, conditionata de context | +| 8 | `ofacturare_comun.prg:288-316` | ramura **ROACOMENZI** (`goComanda`, `tnTip = 3`): idem, `id_client`, `listaid`, `descriere`, `id_sectie`, `sectie` | Idem | **Mare**, conditionata | +| 9 | `ferestre_cere_date.vc2:3177-3179` | `If This.opt_incasat.Value = 2 → poDate.incasat = poDate.totalctva` | Suprascrie suma incasata cu totalul documentului | Medie (grup C, blocat in etapa I) | +| 10 | `ferestre_cere_date.vc2:3166-3171` | `poDate.id_casa = gnid_part_casa` (casa implicita a firmei) | Suprascrie casa documentului | Medie (grup C) | +| 11 | **`ferestre_cere_date.vc2:3183-3185`** | `IF poDate.tip = 2 AND poDate.afisare_scadenta = 0 AND AT([Scadenta la ], poDate.text_aditional) = 0` → **prefixeaza** `text_aditional` cu `„Scadenta la N zile."` | **A doua instanta a exact aceluiasi defect ca lookup-ul delegatului**, si pe un camp din cei 14: modifica `text_aditional` la `Init`, fara actiune a utilizatorului | **Mare** | +| 12 | `ferestre_cere_date.vc2:3150` | `If poDate.incasat <> 0 → Thisform.opt_incasat.Value = 2` — atribuire programatica pe `opt_incasat` | Declanseaza `ProgrammaticChange` → `actualizeaza_tipincasare()` → alocare/dezalocare de numere de chitanta (riscul deja documentat in `s3b_alte_date_analitice.md` §4.3) | Medie | +| 13 | `ofacturare.prg:207-209` | `poGeneratorNumere.ResetNumere()` + `creeaza_cursor_serii(nIdTipDoc)`, la fiecare trecere prin `factureaza` | **Aloca un numar nou** pe calea de emitere | **Mare** — vezi 4.2 | +| 14 | `ofacturare.prg:184-193` | `Do Case` pe `tnTip` care forteaza `poDate.nIdTipDoc` (5 = FACTURA / 6 = AVIZ) **inainte** de `completeaza_setari_document` | Pe editare, `nIdTipDoc` trebuie sa vina din document (proforma = 23, bon = 3), nu din tip | Medie | + +**Nr. 11 merita subliniat**: garda lui (`AT([Scadenta la ], text_aditional) = 0`) il face inert pe un +document de contract emis **cu acelasi numar de zile de scadenta**. Devine activ exact cand +scadenta s-a schimbat de la emitere — adica precis cazul in care textul e cel vechi si trebuie +pastrat. **Metoda `Destroy`/`Unload` face si operatia inversa** (`:3037-3038`: `Strtran(...,[])`), +ceea ce inseamna ca pe calea normala prefixul e adaugat si scos in aceeasi sesiune; pe o cale de +editare care **nu** trece prin acelasi `Unload`, prefixul ar ramane. **[D]** — n-am urmarit toate +caile de iesire ale formularului. + +### 2.3 Regula generala propusa + +> **Orice camp al lui `poDate` populat prin apel Oracle sau dintr-o variabila globala/`go*` in +> `Init`/`Load` e o sugestie pentru document nou, nu o valoare de document.** Pe calea de editare, +> ordinea trebuie sa garanteze ca aceste sugestii **nu ajung sa se execute** (garda), nu doar ca sunt +> **suprascrise dupa** (ceea ce ar merge pentru valori, dar **nu** pentru efectele secundare — nr. 12 +> si nr. 13 aloca numere, nu doar seteaza campuri). + +--- + +## 3. Ce se blocheaza la editare, si de ce + +### 3.1 Blocate — schimbarea lor ar insemna alt document + +| Camp / control | De ce | Ruta care ar lipsi oricum | +|---|---|---| +| **Tipul documentului** (`Ct_clb_fdoc`, `poDate.tip` / `nIdTipDoc`) | Tipul decide ce sursa se consuma, ce nota se scrie si ce garzi se aplica. `do_copiaza` il **degradeaza** tocmai pentru ca un document copiat nu mai poate reconsuma sursa (`ofacturare_comun.vc2:3694-3711`) **[V]**; la editare degradarea e interzisa prin plan, deci tipul e fix. | grupul B — nicio ruta de scriere pe loc | +| **Sursa** (`Ct_clb_altele`) | Vezi 3.2 | grupul B | +| **Clientul** (`Ct_clb_nume_client`) | Schimbarea partenerului rescrie `IREG_PARTENERI`, `ACT`, soldurile — alt document | grupul B | +| **Valuta, `zi_curs`, gestiunea sursa, politica de preturi** | grupul B, decizia 25; in plus `zi_curs` si `id_pol` **n-au valoare salvata** (1.5) | grupul B | +| **Grupul C** (incasare) | decizia 25; in plus `opt_incasat` are efect secundar de alocare (2.2 nr. 12) | grupul C | +| **Grupul A** (venit/cheltuiala, sectie, responsabil, lucrare) | decizia 26 — read-only permanent in #13; in plus doua din patru **nu exista pe `VANZARI`** (1.2.c) | editarea e a lui #6 | + +### 3.2 `Ct_clb_altele` — verificarea ceruta explicit, cu doua corectii la plan [V] + +Planul (`:2952-2955`) spune: *„eticheta schimbata pe tip de `do_schimba_explicatia` („Nr. contract" / +„Nr. comanda" / „Nr. factura" / „Nr. facturi" / „Locatie", `ofacturare.vc2:9633-9643`), eliminat azi +din formular cand `gnScadereStoc = 0` si tipul e 1/5/10 fara copiere."* + +Citit ramura cu ramura din `frm_date_factura.Init` (`ofacturare.vc2:9563-9796`), `Do Case`-ul de la +`:9615-9722`: + +**Corectia 1 — lista de etichete e incompleta.** Intervalul `9633-9643` din plan e corect ca interval, +dar contine **sase** apeluri, nu cinci: `Nr. contract` (`:9633`, tip 2/6/52), `Nr. comanda` (`:9635`, +tip 3), **`Nr. aviz / avize` (`:9637`, tip 4)**, `Nr. factura` (`:9639`, tip 7), `Nr. facturi` +(`:9641`, tip 8/9), `Locatie` (`:9643`, tip 45). **Planul omite exact tipul 4** — cel pentru care +`Ct_clb_altele` tine **lista de avize**, adica singura sursa care trebuie refurnizata lui S9 +(sectiunea 6). Omisiunea nu e cosmetica. + +**Corectia 2 — conditia de eliminare e mai larga decat cea din plan.** `ct_clb_altele` e scos +(`RemoveObject`) in **trei** situatii, nu una: + +| Ramura | Conditie | Ce face | +|---|---|---| +| `:9616-9628` | `gnScadereStoc = 0 And Inlist(poDate.tip, 1, 5, 10, **48, 49**)` | daca `llCopiere` → eticheta `Nr. factura` (`:9623`); altfel **`RemoveObject('ct_clb_altele')`** (`:9627`) | +| `:9651-9660` | **`gnScadereStoc = 1`** `And Inlist(poDate.tip, 1, 5, 10)` | idem (`:9653` / `:9658`) | +| `:9705-9712` (in `Otherwise`) | `Inlist(poDate.tip, 48, 49)` | **`RemoveObject('ct_clb_altele')`** neconditionat | + +Deci: tipurile sunt **1, 5, 10, 48, 49** (nu doar 1/5/10), si eliminarea are loc si cand +**`gnScadereStoc = 1`**, nu doar cand e `0`. Formularea din plan („`gnScadereStoc = 0` si tipul e +1/5/10") descrie **una** din trei ramuri. + +**Consecinta pentru S8, si e in favoarea noastra:** pe ramurile 1 si 2, `llCopiere = .T.` **pastreaza** +controlul si ii pune eticheta `Nr. factura`. Calea de editare, care va avea acelasi semnal ridicat +(`lCopiere` sau `lEditare`), **mosteneste automat pastrarea controlului** — nu e nimic de adaugat, doar +de **blocat**. Pe ramura 3 (tipurile 48/49) controlul e scos neconditionat; **[N]** n-am stabilit daca +tipurile 48/49 intra in perimetrul etapei II. + +### 3.3 Ce ramane editabil + +Cei 14 parametri, prin `but_modifica` (S8c), **plus** liniile (cantitati, preturi, discounturi, +explicatii, adaugari/stergeri) si discountul de document — care declanseaza regenerarea. Nimic din +sectiunea 3.1. + +--- + +## 4. Ordinea reala de incarcare, si de ce conteaza + +### 4.1 Secventa propusa + +``` + 0. S7 a validat deja documentul (garzi) si a stabilit id_vanzare. + 1. Citeste ANTETUL: SELECT ... FROM FACT_VFACTURI WHERE id_vanzare = :id + 2. Citeste LISTA SURSA (avize) inainte de orice altceva: SELECT ... FROM VANZARI_CORESP (§6) + 3. poDate = CreateObject("oDateFactura", 0, 0) <-- tnIdSet = 0 => Init NU ruleaza (§4.2) + 4. poDate.lEditare = .T. <-- semnalul, inainte de orice formular + 5. Populeaza poDate din antet: cei 14 + tip + eproforma + valuta/curs + + discount_evidentiat + client + gestiune + sursa (cListaSursa / cListaSursaAvize) + 6. Populeaza thisform.ndiscfactron / ndiscfactval din VANZARI.DISCOUNT + 7. Citeste LINIILE (canalul din §1.1(B)) -> crsarticole + 8. GARDA DE RECALCUL: thisform.lIncarcare = .T. + 9. Umple crsfactura din crsarticole, fara dialoguri (§4.3) +10. Adauga lista de preturi peste crsarticole (pentru S4g: adaugarea de articole noi) +11. thisform.lIncarcare = .F. +12. Un singur do_calculeaza_totaluri() +13. SNAPSHOT pentru S8b (§5) +14. Blocheaza antetul (S8c) si campurile din §3.1; arata formularul +``` + +### 4.2 De ce pasul 3 arata asa — `tnIdSet = 0` [V] + +Tot corpul lui `oDateFactura.Init` e inchis in `If !Empty(m.tnIdSet)` (`ofacturare_comun.prg:225`). +Cu `tnIdSet = 0`, **niciuna dintre cele opt initializari de la 2.2 (nr. 1-8) nu se executa**, iar +constructorul devine inert. **Precedentul exista si e chiar cel de reincarcare a unui document:** +`relisteaza_ofacturare_stoc` face `poDate = Createobject("oDateFactura", 0, 0)` +(`ofacturare_stoc.prg:497`) **[V]** exact din acest motiv. + +Asta rezolva **printr-o singura decizie de constructie** opt din cele paisprezece initializari +periculoase, fara nicio modificare in `oDateFactura` — si e mult mai robust decat „suprascriem dupa", +pentru ca nu depinde de completitudinea listei de suprascrieri. + +**Ce ramane de tratat separat, pentru ca nu trece prin `Init`:** +- nr. 9-12 (`frm_alte_date.Init`) → garda `poDate.lEditare` de la 2.1; +- **nr. 13, `poGeneratorNumere`** — e apelat din `factureaza` (`ofacturare.prg:207-209`), nu din + `Init`. Pe calea de editare **nu trebuie sa se aloce numar**: seria si numarul vin din document + (decizia F / plan §F). `oGeneratorNumere` are deja `dezaloca_numar` si `verifica_numar` + (`ofacturare.prg:227`, `:250`), dar calea corecta e **sa nu se aloce deloc**. **[N]** — + `COMUN\programe\oserii_numere.prg` n-a fost citit in aceasta sesiune; planul citeaza `:227-233` + pentru afirmatia „la modificare nu se aloca numar nou". **De verificat la implementare** ca ocolirea + alocarii nu lasa `poDate.rezultat_serii` intr-o stare pe care `frm_date_factura.Init` o citeste + (`ofacturare.vc2:9736`: `If Inlist(poDate.rezultat_serii, 1, 2, 3) → clb_serie_act.do_initializeaza(...)`). + +### 4.3 Pasul 9 e piesa care lipseste azi — si e cea mai mare + +**[V]** Pe calea de copiere, `crsfactura` ramane gol; documentul se compune manual sau prin +`do_adauga_tot`. `do_adauga_tot` (`ofacturare.vc2:13169-13198`) face `SCAN` peste `crsarticole` si +cheama `do_adauga_articol(.T.)`, care (`:12871-12894`): + +- pe ramura `poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45` — creeaza + `frm_articol_factura` **fara sa-l arate** (cu `tlImplicit = .T.`) si calculeaza totalurile. Acceptabil. +- pe ramura `Otherwise` — cheama **`do_alege_stoc(...)`**, adica **dialogul de alegere din stocul de + azi**. Pe calea de editare asta e gresit de doua ori: (a) ar putea cere interventia utilizatorului + pentru fiecare linie gestionabila la simpla deschidere a documentului; (b) ar alege **loturi/serii + din stocul curent**, desi documentul are deja `LOT`, `SERIE` si `ID_GESTIUNE` proprii, intoarse de + cursor. + +In plus, `do_adauga_tot` are un `Do While` cu `aMessageBox("Nu ati selectat toata cantitatea …")` +(`:13188`) — **un modal per linie neacoperita**. Inacceptabil la incarcare. + +Si `do_adauga_articol` scrie `Replace id_temp With Recno()` (`:12951` si `:13000`) **[V]** — deci chiar +daca sursa ar avea `ID_VANZARE_DET`, **nu ar ajunge in `crsfactura` pe aceasta cale**. + +**Concluzie:** S8 nu poate refolosi `do_adauga_tot`. Ii trebuie o **populare directa** +`crsarticole → crsfactura` (un `INSERT INTO crsfactura ... SELECT ...` plus recalculul valorilor pe +linie prin `calculeaza_totaluri`), care: +1. **nu deschide niciun dialog**; +2. pastreaza `lot`, `serie`, `id_gestiune`, `id_pol`, `pretd` **din document**; +3. duce `id_vanzare_det` intr-o coloana proprie noua pe `crsfactura` (**nu** in `id_temp`, care e + suprasolicitat: `Recno()` pe o cale, `id_vanzare_det` pe alta — `prelucreaza_facturacrs` face + `id_vanzare_det As id_temp`, `ofacturare_comun.prg:1811` **[V]**); +4. duce `taxcode`. +Precedentul de structura exista: blocul `tnTip = 30` din `factureaza` +(`ofacturare.prg:340-378`) face exact o populare directa `crsarticole → crsfactura` prin +`INSERT INTO crsfactura (...) Values (...)`, fara niciun dialog **[V]**. Se cloneaza forma lui. + +### 4.4 Garda de „nu recalcula acum" — unde, si de ce + +`crsfactura` e `RecordSource`-ul gridului, iar coloanele au `Valid`/`InteractiveChange` care +recalculeaza. La populare programatica, `Valid` **nu** se declanseaza in VFP (se declanseaza doar la +iesirea din control, pe interactiune) — deci riscul principal **nu** e in grid, ci in: + +- **`ControlSource` legate direct de `poDate`** (ex. `poDate.discount_evidentiat`, + `ofacturare.vc2:11305`) — atribuirea programatica declanseaza `ProgrammaticChange`, nu `Valid` + **[D]** (comportament VFP standard; nu l-am observat rulat aici); +- **`opt_incasat.Value =`** — 2.2 nr. 12, efectul secundar documentat; +- **`clb_discount.procent.Value =`** (`ofacturare.vc2:13475`) — daca S8 populeaza procentul de discount + in loc de suma, `InteractiveChange` ar recalcula discountul pe baza curenta, care in timpul popularii + e incompleta. + +**Recomandare concreta:** un singur flag pe formular, `Thisform.lIncarcare`, testat la intrarea in +`do_calculeaza_totaluri` si in `actualizeaza_discount` (`ofacturare.vc2:13408-13415`), plus **regula ca +discountul de document se incarca ca SUMA** (`ndiscfactron`/`ndiscfactval`, valorile pe care le citeste +si scrierea, `:14272-14274`), **nu ca procent** — procentul se recalculeaza din suma la pasul 12 +(`:13475`: `procent.Value = Round(ndiscfactron * 100 / nbazafdiscount, 2)`), nu invers. + +### 4.5 Asezarea — decizia 5 + +Nimic din cele de mai sus nu schimba aspectul: nu se adauga banda de avertizare, coloane cu valori +initiale sau panou de diferente. Se schimba **titlul ferestrei** si **butonul principal** (decizia 9 / +S8c). Diferentele vizibile fata de introducere sunt **doar** cele care rezulta din blocarea campurilor +(3.1) si din ascunderea lui `zi_curs` (1.5) — ultima e o abatere minora de la „identic", propusa +motivat, **de confirmat de Marius** (intrebarea 4, sectiunea 8). + +--- + +## 5. Interfata cu S8b — ce se retine ca „stare initiala" + +### 5.1 Momentul + +Snapshot-ul se ia la **pasul 13**: dupa incarcarea completa si dupa singurul recalcul, dar **inainte de +afisarea formularului**. Nu mai devreme (valorile derivate n-ar fi calculate) si nu mai tarziu (orice +`Init` de subformular ar putea polua — 2.2). + +### 5.2 Ce se retine, si in ce forma + +| Grup | Forma | Normalizare obligatorie inainte de comparatie | +|---|---|---| +| **Cei 14 parametri** | copie a valorilor din `poDate` (+ `id_ruta`, 1.5) intr-un obiect `poDateInitial` | `.NULL.` vs `0` vs `''` — functie unica aplicata **simetric**; `text_aditional`: **`LEFT(...,100)` + `Chr(170)`→`CR+LF`** pe ambele parti (1.7) | +| **Discountul de document** | `ndiscfactron` si `ndiscfactval`, **ca sume** | `Round(..., gnPc)` / `Round(..., gnPVal)` | +| `discount_evidentiat` | scalar | — | +| **Liniile** | copie a lui `crsfactura`, cheia = **`id_vanzare_det`** (coloana noua, 4.3) | `Round` la `gnPc` / `gnPPretV` / `gnPCant`; pretul comparat in forma **rotunjita + `DIFERENTA`** (1.4.ii) | +| **Explicatie / taxcode pe linie** | in aceeasi copie | `Alltrim`, `Nvl(...,'')` | + +**Trei precizari care corecteaza sau completeaza S8b:** + +1. **Cheia `id_vanzare_det` nu vine gratuit** — S8b o presupunea incarcata („coloana deja incarcata la + S8"). Nu e (1.1). Devine **livrabil al lui S8**, nu premisa. +2. **Liniile fara `id_vanzare_det`** (adaugate in sesiune) se marcheaza cu `0`/`.NULL.` si inseamna + direct „regenerare", ca in S8b §1.3. +3. **`zi_curs` se scoate din comparatie** (1.5) — altfel orice document ar aparea modificat. + +### 5.3 Cele 7 campuri comune `scrie_factura2` / `modifica_date_factura` + +Nimic de adaugat la S8b §3.1 — lantul cu prioritate ramane. S8 doar garanteaza premisa lui: **`poDate` +contine, la momentul confirmarii, valorile documentului plus editarile utilizatorului si nimic altceva** +— ceea ce e exact ce asigura sectiunile 2 si 4. + +--- + +## 6. Interfata cu S9 — ce trebuie sa incarce S8 ca S9 sa poata refurniza lista sursa + +Pasul 1 nou al lui S9 (handoff, rezultatul 7) cere **citirea listei sursa inainte de stergere**. S8 e +locul unde se citeste, pentru ca **dupa stergere `VANZARI_CORESP` are `STERS = 1`** (`:5582` **[V]**) si +`VANZARI.ID_COMANDA` / `ID_CTR` raman pe randul soft-sters. + +**Ce incarca S8, si de unde:** + +| Ce | Camp propus pe `poDate` | Sursa | Consumator la reemitere | +|---|---|---|---| +| lista de avize | `cListaSursaAvize` | `SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP WHERE ID_VANZARE_FACT = :id AND STERS = 0 AND TIP = 1` | `pack_facturare.clistaid_avize` → `scrie_corespondente_vanzari(1)` si `marcheaza_facturat` | +| comanda | `cListaSursa` | `FACT_VFACTURI.ID_COMANDA` | `pack_facturare.clistaid` | +| contract | `cListaSursa` (+ `id_ctr`) | `FACT_VFACTURI.ID_CTR` | idem, plus `CTR_RATE_FACTURI` | +| tipul original | `poDate.tip`, nedegradat | `FACT_VFACTURI.TIP` | `CASE`-ul pe `ntip` din `finalizeaza_factura` | + +**Doua avertismente:** + +1. **`poDate.listaid` NU poate purta aceasta informatie** — la incarcare el trebuie sa fie + `id_vanzare`-ul documentului insusi, ca sa se citeasca liniile (1.6). Doua campuri distincte, + nu unul reinterpretat. +2. **Semantica valorilor lui `TIP`** din `VANZARI_CORESP` **nu e stabilita aici [N]** (1.6) — se + citeste din `CASE`-ul real al lui `finalizeaza_factura` la implementare. + +**Nu tine de S8**, dar se semnaleaza pentru ca S8 decide continutul lui `crsarticole`: +`do_scrie_factura` face `Select crsarticole` + `Calculate Sum(cantitate) To lnCantitateRamasa` pe +ramurile `poDate.Tip = 4` (`ofacturare.vc2:14305-14307`) si `Inlist(poDate.Tip,3,21,25,28,42,47)` +(`:14336-14338`) **[V]**, si pe baza sumei intreaba *„Doriti sa se inregistreze si avizul de retur?"* / +*„Doriti sa se inchida comanda?"* si seteaza `pnParametruAditional`. Pe calea de editare `crsarticole` +contine liniile documentului **plus lista de preturi adaugata peste ele** (`ofacturare.prg:465-473` +**[V]**), deci **suma e lipsita de sens** si intrebarea ar aparea gresit, cu efect real pe +`pnParametruAditional`. **De rezolvat in S9** — fie prin cursor separat pentru lista de preturi, fie +prin calcularea sumei doar peste randurile provenite din document. + +--- + +## 7. Criteriul „gata cand", in forma testabila + +**Comun tuturor tipurilor.** Pe un document existent, dupa deschidere si **inainte** de orice +interactiune: + +| # | Ce se verifica | Cum | +|---|---|---| +| C1 | serie, numar, data, scadenta afisate = cele din `VANZARI` | comparatie ecran ↔ `SELECT serie_act, numar_act, data_act, data_scad FROM vanzari WHERE id_vanzare = :id` | +| C2 | **niciun numar nou alocat** | inainte/dupa: ultimul numar din generatorul de serii pentru `nIdTipDoc` e neschimbat | +| C3 | numarul de linii din grid = `SELECT count(*) FROM vanzari_detalii WHERE id_vanzare = :id AND sters = 0` | | +| C4 | pe fiecare linie: cantitate, pret, discount, gestiune, lot, serie, cota TVA, explicatie, `taxcode` = valorile din `VANZARI_DETALII` (pret in forma rotunjita + `DIFERENTA`, 1.4.ii) | | +| C5 | totalurile afisate = `VANZARI.TOTAL_FARA_TVA` / `TOTAL_TVA` / `TOTAL_CU_TVA` | | +| C6 | discountul de document si `discount_evidentiat` = `VANZARI.DISCOUNT` / `DISCOUNT_EVIDENTIAT` | | +| C7 | delegat, masina, agent, adresa de facturare, `dataora_exp`, `text_aditional`, `listare_detaliata`, `tip_saft`, `efactura`, `id_ruta` = `VANZARI` | inclusiv **cazul „documentul n-are delegat"** → campul ramane gol (2.1) | +| C8 | **nicio scriere in baza** — criteriul de baza al lui S8b | `SELECT dataoras, id_utils FROM vanzari WHERE id_vanzare = :id` neschimbat dupa deschidere+inchidere; idem pe `VANZARI_DETALII` | +| C9 | **niciun dialog modal** la deschidere (nici alegere de stoc, nici „nu ati selectat toata cantitatea", nici „doriti sa inchideti comanda") | observatie | +| C10 | aspectul e cel de introducere (decizia 5): fara banda, fara coloane de valori initiale, fara panou de diferente; difera doar titlul si butonul principal | observatie | + +**Pe fiecare tip de sursa, in plus:** + +| Tip | Ce se verifica specific | +|---|---| +| **comanda** (3, 21, 25, 28, 42, 47) | `Ct_clb_altele` afiseaza numarul comenzii, eticheta „Nr. comanda", **blocat**; `poDate.cListaSursa` = `VANZARI.ID_COMANDA` | +| **contract** (2, 6, 26, 52) | eticheta „Nr. contract", blocat; `id_ctr` incarcat; **`goContract` din sesiune NU suprascrie antetul** (2.2 nr. 7); daca S10 se implementeaza, **niciun avertisment de re-derivare la simpla deschidere** (`cursor_retur_document` citeste pretul stocat, rezultatul 6 al handoff-ului) | +| **aviz** (tip 4 = factura din avize) | eticheta **„Nr. aviz / avize"** (3.2), blocat; `poDate.cListaSursaAvize` = randurile `VANZARI_CORESP` ale documentului; **nicio intrebare „doriti sa se inregistreze si avizul de retur?"** la deschidere (§6) | +| **factura simpla** (1, 5, 10) | `Ct_clb_altele` **pastrat si vizibil** cu eticheta „Nr. factura" (3.2, ramurile 1 si 2 cu `llCopiere`), spre deosebire de emiterea normala unde e scos | +| **retur** (8, 9, 24) | `zi_curs` e oricum scos azi pentru 8/9 (`ofacturare.vc2:9720-9725`) — comportament neschimbat; liniile se incarca cu cantitatile documentului, nu cu cele disponibile la retur | +| **ROAAUTO** | `poDate.id_ordl` incarcat din `VANZARI.ID_ORDL`; **[N]** nu s-a verificat in aceasta sesiune ce alte campuri specifice ROAAUTO exista pe antet — `roaauto_facturi.md` nu a fost citit | +| **proforma** (`eproforma = 1`) | `poDate.eProforma` incarcat (1.2.a) → `frm_alte_date.Init` sare din start pe ramura de proforma (`:3110`), iar `gestionabil` vine `0` din cursor (1.4) | + +--- + +## 8. Riscuri, goluri ramase, si intrebarile pentru Marius + +### 8.1 Ce n-am putut stabili, si de ce + +| # | Ce | De ce | +|---|---|---| +| N1 | Semantica exacta a valorilor `VANZARI_CORESP.TIP` (`1/2/3`, `4` liber) | Cere citirea `CASE`-ului pe `ntip` din `finalizeaza_factura`, in afara perimetrului parcurs. **Nu se preia din raportul precedent** — e exact tiparul cifrei gresite din runda 13. | +| N2 | Daca ocolirea alocarii de numar lasa `poDate.rezultat_serii` intr-o stare pe care `frm_date_factura.Init` o citeste gresit (`ofacturare.vc2:9736`) | `COMUN\programe\oserii_numere.prg` necitit in aceasta sesiune | +| N3 | Daca prefixul „Scadenta la N zile." adaugat la `Init` (2.2 nr. 11) e scos pe **toate** caile de iesire | Am vazut operatia inversa la `ferestre_cere_date.vc2:3037-3038`, dar n-am urmarit toate caile de `Unload`/`Destroy` | +| N4 | Campurile de antet specifice **ROAAUTO** | `roaauto_facturi.md` necitit | +| N5 | Daca tipurile **48/49** (custodie) intra in perimetrul etapei II | Conteaza pentru 3.2, ramura 3 | +| N6 | Daca `frm_facturare_articole2` (varianta paralela) trebuie tratata identic | Are `do_adauga_tot` / `do_adauga_articol` proprii (`ofacturare.vc2:17476`, `:17124`), cu logica usor diferita (fara testul `llGestionabil`) — **de decis daca S8 tinteste ambele forme sau doar `frm_facturare_articole`** | + +### 8.2 Intrebari pentru Marius, cu recomandare + +1. **Canalul de citire a liniilor: (A) extindem `cursor_retur_document`, (B) procedura noua + `cursor_editare_document`, sau (C) `FACT_VFACTURI_DETALII`?** (1.1) + *Recomandare:* **(B)**. Argumentul nu e estetica, ci ca (A) schimba **comportamentul copierii** + (`taxcode` ar incepe sa se propage), iar (C) pierde `ID_POL`, `PRETD` si tratamentul valutar — + partea grea. (B) da si libertatea de a alege corect `GESTIONABIL` (1.4.i). + +2. **`GESTIONABIL` la editare: din nomenclatorul de azi (`IN_STOC`) sau din document (`ID_GESTIUNE`)?** + (1.4.i) + *Recomandare:* **din document**. Un document editat trebuie sa arate cum a fost emis; regimul de + stoc al articolului s-a putut schimba intre timp fara nicio legatura cu documentul. + +3. **`text_aditional`: se normalizeaza la incarcare (`Chr(170)` → `CR+LF`) sau se afiseaza ca atare?** + (1.7) + *Recomandare:* **se normalizeaza la incarcare si se re-normalizeaza la comparatie**, altfel orice + document cu text pe mai multe randuri apare modificat la deschidere. Nota: cele doua rute de scriere + salveaza **forme diferite** ale campului (truncat/substituit vs. brut) — merita semnalat separat, + e un defect preexistent, nu al lui #13. + +4. **`zi_curs` pe calea de editare: ascuns, sau afisat gol?** (1.5) + *Recomandare:* **ascuns**, cu precedent in acelasi `Do Case` (tipurile 8/9). E o abatere mica de la + decizia 5, si o semnalez ca atare — un camp afisat gol pe un document care are curs ar fi mai + derutant decat absenta lui. + +5. **`poDate.lEditare` ca proprietate pe `oDateFactura`, sau parametru pe `frm_alte_date.Init`?** (2.1) + *Recomandare:* **proprietate**, pentru ca sunt **cel putin patru** locuri care au nevoie de semnal + (2.2 nr. 9-13), nu unul, si pentru ca `lCopiere` e deja acolo, cu exact acelasi rol. + +6. **`id_ruta` — proprietate noua pe `oDateFactura`?** (1.5) + *Recomandare:* **da**. Fara ea S8c nu poate implementa unul din cei 14 parametri, iar azi valoarea + circula doar prin `poRec`, care nu exista in formularul unificat. + +7. **Tipurile 48/49 si `frm_facturare_articole2`** (N5, N6) — intra in perimetrul etapei II? + *Recomandare:* **nu acum**; se declara explicit ca neacoperite, ca sa nu se descopere la testare. + +8. **Defectul de la 2.2 nr. 11** (prefixarea `text_aditional` la `Init` pentru contracte) — se repara + in #13, se semnaleaza separat, sau se lasa? + *Recomandare:* **se ocoleste in #13** (prin `lEditare`) si **se semnaleaza separat** ca defect + preexistent — repararea lui pe calea de emitere nu tine de aceasta poveste. + +### 8.3 Ce a fost corectat fata de materialele existente + +| Afirmatie anterioara | Stare | +|---|---| +| S8b: „lookup-ul delegatului trebuie sarit explicit, altfel pica primul criteriu" | **Nuantat** — garda `Empty(id_delegat) And Empty(id_masina)` exista deja (`ferestre_cere_date.vc2:3111`); riscul e real **doar** pe documentele fara delegat si fara masina | +| S8b §1.2 / §7.4: „cheia de linie `id_vanzare_det`, coloana deja incarcata la S8" | **Infirmat** — `cursor_retur_document` nu o intoarce; devine livrabil al lui S8 | +| S8b §5.2: „S8 trebuie sa suprascrie implicitul `zi_curs` cu data reala salvata" | **Infirmat structural** — nu exista coloana `ZI_CURS` pe `VANZARI` | +| Plan S8: etichetele lui `Ct_clb_altele` (cinci) | **Incomplet** — sunt sase; lipseste „Nr. aviz / avize" (tip 4), exact cazul relevant pentru S9 | +| Plan S8: „eliminat cand `gnScadereStoc = 0` si tipul e 1/5/10 fara copiere" | **Incomplet** — trei ramuri, tipurile `1,5,10,48,49`, si pentru `gnScadereStoc = 1`, nu doar `0` | +| Plan S8: „`cursor_retur_document(V_COPIERE=1)` pentru linii" | **Insuficient** — umple selectorul, nu documentul; lipsesc `ID_VANZARE_DET` si `TAXCODE` | +| Plan S8: „`completeaza_setari_document` pentru antet" | **Insuficient** — 16 proprietati din ~120, niciuna de identitate | + +--- + +*Cercetare incheiata pe toate cele 8 puncte cerute in briefing. Afirmatiile portante au `fisier:linie` +sau nume de coloana din dictionar. Nu s-a modificat niciun fisier de cod; pe Oracle numai `SELECT` pe +`all_tab_columns`.* diff --git a/docs/cercetare/s8b_rutarea_scrierii.md b/docs/cercetare/s8b_rutarea_scrierii.md new file mode 100644 index 0000000..37e983e --- /dev/null +++ b/docs/cercetare/s8b_rutarea_scrierii.md @@ -0,0 +1,311 @@ +# S8b — Proiectare: rutarea scrierii dupa ce utilizatorul a schimbat ceva + +Livrabilul povestii **S8b** din `docs\plan_13_unificare_formular_facturare.md:2890-2898` (G-bis, +deciziile 6/9/25/26). Cercetare READ-ONLY — zero editari de cod, zero `git_sync.ps1`/`txt2vcx.ps1`, +zero commit, zero scriere Oracle (doar SELECT, nefolosit efectiv — tot ce trebuia era in codul VFP/ +PL-SQL deja exportat sau in rapoartele de referinta citate in briefing). +`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar +citite, neatinse. + +## Verdict (rezumat, 8 randuri) + +Reteta din G-bis e corecta ca directie, dar are un gol nedocumentat pana acum: **`modifica_date_factura` +nu e singura ruta care scrie cei 14 parametri de antet** — 7 din 14 (`id_delegat`, `id_masina`, +`id_facturare`, `listare_detaliata`, `dataora_exp`, `id_agent`, `text_aditional`) sunt scrisi **si** +de calea normala de emitere (`scrie_factura2`, apelata direct de regenerare, decizia 35), pentru ca +ambele cai citesc din **acelasi obiect `poDate`**, nu din doua reprezentari separate (sectiunea 3, +dovada pe cod). Consecinta directa pentru punctul cel mai periculos al sarcinii (schimbare simultana +antet+sume): **cand regenerarea porneste, NU se mai cheama `modifica_date_factura` in plus** — ar fi +fie redundant (pe cele 7 campuri comune), fie periculos (ar scrie pe un `ID_VANZARE` care tocmai a +fost soft-sters sau inca nu exista). Regenerarea *este* deja calea de scriere a antetului cand sumele +se schimba, nu o cale separata care trebuie compusa cu ea. Explicatia de linie e acoperita direct de +`adauga_articol_factura` (parametru `V_EXPLICATIE`, plus `V_TAXCODE`), deci regenerarea o transporta +fara cod suplimentar — dar **doar daca cursorul de linii incarcat la S8 e re-citit din formular, nu +din snapshot-ul initial**. Cel mai probabil loc de fals-pozitiv pentru „deschid si inchid fara sa +modific nimic” e lookup-ul „ultimul delegat/masina al clientului” din `frm_alte_date.Init` +(sectiunea 5) — cod construit pentru emiterea unui document nou, periculos daca ruleaza neschimbat pe +calea de editare. + +--- + +## 1. Mecanismul de detectare a schimbarii + +### 1.1 Optiuni si ce se strica la fiecare + +| Optiune | Ce se strica | +|---|---| +| **Hash/checksum pe randuri** | Ascunde exact tipul de fals-pozitiv cel mai periculos aici: doua reprezentari numeric-egale dar text-diferite (`Str(12.5,18,2)` vs `Str(12.50,18,2)`, `.NULL.` vs `0` vs `""`) produc hash-uri diferite desi valoarea „reala" e identica. Orice normalizare facuta *inainte* de hash trebuie sa fie deja perfecta — hash-ul nu adauga nimic, doar ascunde bug-urile de normalizare in loc sa le arate. | +| **Flag-uri `lModificat` in evenimentele de editare** | E robust doar daca *fiecare* eveniment care poate schimba o valoare seteaza flag-ul — un `ControlSource` legat direct (binding automat VFP, cazul majoritatii campurilor de antet aici, vezi `modifica_date_factura_parametri.md` §4) nu trece printr-un eveniment scriptat, deci flag-ul ramane `.F.` desi valoarea s-a schimbat. Whitelist fragil: la fiecare control nou adaugat, cineva trebuie sa-si aminteasca sa cablasje flag-ul. Cel mai riscant pentru campurile din `frm_alte_date` (S3b), unde `ControlSource=poDate.xxx` e tiparul dominant. | +| **Snapshot la incarcare + comparatie camp cu camp la confirmare** (recomandat) | Cere disciplina la normalizare (precizie, `.NULL.`), dar e singura optiune care **nu poate rata o schimbare structurala** — compara starea finala, nu istoricul de evenimente, deci un binding automat care a schimbat o valoare fara eveniment scriptat tot apare in diff. E si optiunea deja folosita implicit in codebase pentru un caz inrudit: `chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))` (`ofacturare_comun.vc2:5736`) testeaza starea curenta, nu un istoric de evenimente. | + +**Recomandare: snapshot + comparatie la confirmare**, din motivul de mai sus (robustete la binding +automat) — argumentat, nu doar preferat. + +### 1.2 Ce se snapshoteaza, concret + +- **Antet**: o copie profunda a lui `poDate` (sau a subsetului de proprietati relevante) luata + imediat dupa S8 (incarcare), **inainte** de orice lookup auto-completat (vezi sectiunea 5 — ordinea + conteaza: daca lookup-ul „ultimul delegat" ruleaza inainte de snapshot, snapshot-ul insusi e deja + poluat, si orice comparatie ulterioara devine inutila). +- **Linii**: o copie a cursorului `crsfactura` (nume alias de confirmat la implementare — S4d il + citeaza ca atare) imediat dupa populare la S8, cu cheia de linie **`id_vanzare_det`** (coloana deja + incarcata la S8 din `VANZARI_DETALII`, cf. plan S8: „explicatia si taxcode pe fiecare linie" — nu + exista alt candidat de cheie stabila in materialele citite; **de confirmat exact numele coloanei in + cursor la implementare**, nu presupus mai departe aici). +- **Discountul de document** (`VANZARI.DISCOUNT`, `discount_evidentiat`) — campuri de antet cu efect + de suma, tratate separat de cele 14 (vezi matricea, sectiunea 2). + +### 1.3 Capcanele reale de VFP, cu tratament explicit + +| Capcana | Tratament recomandat | +|---|---| +| `.NULL.` vs `0` vs `""` | Comparatie prin functie de normalizare unica (`NVL`-style) aplicata **simetric** pe ambele parti (snapshot si curent) inainte de `=`, nu doar pe una — o comparatie `Nvl(nou,0) == vechi` fara acelasi tratament pe `vechi` e asimetrica si poate rata cazul `vechi=.NULL., nou=0` ca "neschimbat" cand de fapt utilizatorul a introdus explicit un zero peste un camp gol (relevant pt. campuri ca `id_ruta`/`id_agent`, unde `.NULL.` si `0` pot avea semnatura diferita in Oracle — `modifica_date_factura` scrie orice i se da, inclusiv `NULL` neconditionat, deci diferenta chiar conteaza acolo). | +| Precizie numerica (`gnPc`, `gnPPretV`, `gnPCant`) | Comparatia nu se face pe valoarea `Double` bruta, ci pe reprezentarea **rotunjita la aceeasi precizie folosita la scriere** — acelasi `Round(x, gnPc)`/`Round(x, gnPPretV)`/`Round(x, gnPCant)` aplicat pe ambele parti inainte de `=`. Motiv concret: `adauga_articol_factura` primeste pretul ca `Str(..., 18, gnPc)` (text), deci orice zgomot de reprezentare in binar (ex. `12.4999999999` vs `12.5`) care nu apare si in textul trimis Oracle-ului nu trebuie sa declanseze regenerare — regenerarea trebuie sa porneasca de la o diferenta **care ar produce efectiv un text diferit trimis la Oracle**, nu de la zgomot de virgula mobila intern VFP. | +| Randuri sterse/adaugate, nu doar modificate | Comparatie pe **multimea cheilor** `id_vanzare_det`, nu pe pozitie: chei prezente in snapshot dar absente in curent = sters; chei prezente in curent dar absente in snapshot (sau `id_vanzare_det` gol/`0`, sentinela de linie noua) = adaugat; chei prezente in ambele = potential modificat, comparat camp cu camp. **Orice** rand adaugat sau sters, indiferent de continutul lui, marcheaza direct pentru regenerare (tabelul din G-bis: „linii adaugate/sterse” → regenerare) — nu are sens sa se compare campuri pe un rand care oricum nu exista pe ambele parti. | +| Ordinea randurilor | **Nu conteaza pentru detectie** — comparatia e pe chei (set), nu pe pozitie in grid. Ordinea ar conta doar daca reordonarea insasi ar fi o schimbare semnificativa pentru Oracle (nu e cazul — `adauga_articol_factura` nu are parametru de ordine vizibil in semnatura citata in `s10_pret_rederivat.md` §1). **Rezerva**: daca formularul unificat permite reordonare manuala a liniilor si aceasta conteaza pentru vreun raport (necercetat aici), ar trebui un camp explicit de ordine comparat separat — nu presupus din pozitia in cursor. | + +--- + +## 2. Matricea „ce s-a schimbat → ce ruta", exhaustiva + +Sursa de adevar: G-bis (`plan:812-889`) + `modifica_date_factura_parametri.md` + `rute_scriere_antet.md`. + +| Camp / grup | Ruta | De ce nu una mai ieftina | +|---|---|---| +| **Cei 14 parametri** (serie, numar, data, scadenta, ruta, delegat, masina, agent, `dataora_exp`, adresa facturare, text aditional, `listare_detaliata`, `tip_saft`, `efactura`) | `modifica_date_factura`, **daca nimic altceva nu s-a schimbat in aceeasi sesiune** | E singura cale directa pe `VANZARI`+`DOCUMENTE`+`ACT`+`IREG_PARTENERI`+`JV2007`+`RUL` care nu atinge sumele — mai ieftina decat regenerarea (fara stergere+reemitere, fara stoc, fara nota noua). Regenerarea ar face acelasi lucru, dar cu cost mult mai mare (tranzactie stergere+reemitere, stoc, ID_VANZARE nou) pentru un camp care nu atinge nicio suma — nu se justifica. | +| **Explicatia si `taxcode` pe o linie**, **fara nicio alta schimbare pe linii** | `modifica_explicatie_articol` | Update pe doua coloane, fara sume — regenerarea ar fi disproportionata (stergere+reemitere completa pentru un text). | +| **Cantitati, preturi, discount pe linie, gestiune, cota TVA, serie/lot** | Regenerare | Nu exista alta ruta — verificat exhaustiv, nicio procedura Oracle de tip "modifica cantitate/pret pe linie existenta" (confirmat indirect: singura cale de scriere a sumelor e `adauga_articol_factura`, apelata doar la emitere/reemitere). | +| **Linii adaugate/sterse** | Regenerare | Identic — nu exista `sterge_linie_factura`/`adauga_linie_factura` separat de fluxul de emitere. | +| **Discountul de document** (`VANZARI.DISCOUNT`, `discount_evidentiat`) | Regenerare | Nu e printre cei 14 parametri ai `modifica_date_factura` (confirmat, tabelul din `modifica_date_factura_parametri.md` §1-2 nu il contine) si atinge direct sumele — cade natural in categoria "orice atinge sumele" din G-bis. | +| **Grupul B** — tip document, valuta, zi curs, client, sursa, gestiune sursa, politica de preturi | Regenerare (decizia 25); **blocate in etapa I** | `rute_scriere_antet.md` §2 — verdict NU pentru toate 7, cautat exhaustiv in Oracle si VFP; regenerarea (calea de emitere) e singura care le scrie, pentru ca sunt scrise o singura data la `scrie_factura2`/echivalent, niciodata actualizate separat. | +| **Grupul C** — incasare (mod, casa, serie/nr chitanta, suma, POS) | Regenerare (decizia 25); **blocate in etapa I** | `rute_scriere_antet.md` §3 — incasarea devine ea insasi o linie de nota (`scrie_incasare2`), scrisa doar la emitere; nicio ruta „modifica incasare" separata. | +| **Grupul A** — venit/cheltuiala, sectie, responsabil, lucrare | **Niciuna din #13** — read-only (decizia 26) | Editabile deja, dar la nivel de LINIE de nota, prin #6 (`frm_modific2024`) — #13 le-ar scrie uniform pe tot documentul, risc de suprascriere tacita a unei diferentieri facute din #6. Zero suprapunere intre fire, per decizia lui Marius din 09.08.2026. | +| **Datele din sectiunea pliata — delegat/transport, adresa facturare, text aditional** | Fac parte din cei 14 parametri → `modifica_date_factura` daca nimic altceva nu s-a schimbat; **regenerare** daca si sumele s-au schimbat (vezi sectiunea 3) | Sunt deja acoperite de tabelul de mai sus prin `V_ID_DELEGAT`/`V_ID_MASINA`/`V_ID_AGENT`/`V_ID_FACTURARE`/`V_TEXT_ADITIONAL`/`V_DATAORA_EXP`. | +| **Nimic schimbat** | Nimic | Explicit cerut de G-bis — altfel orice deschidere ar produce scriere degeaba. | + +--- + +## 3. Cazurile care cad intre rute — descoperirea centrala a acestei cercetari + +### 3.1 Antet + sume schimbate in aceeasi sesiune — verdict cu dovada, nu presupunere + +**Intrebarea din briefing**: se cheama ambele rute, sau regenerarea le acopera pe amandoua? + +**Raspuns, verificat pe cod**: **regenerarea acopera 7 din cei 14 parametri prin insusi mecanismul +de emitere, fara nicio ruta suplimentara** — pentru ca `poDate` e **acelasi obiect** care alimenteaza +atat afisarea antetului cat si apelul `scrie_factura2` folosit de regenerare (decizia 35: "reemiterea +scrie prin `pack_facturare`, pe acelasi drum ca emiterea"). + +Dovada directa, apelul `scrie_factura2` (`COMUN\clase\ofacturare.vc2:14345-14359`, identic la +`:14373-14387` si `:18343-18387`): + +``` +lcSql = [{call pack_facturare.scrie_factura2(] + ; + ... pnTotalFtva, pnTotalTva, pnDiscount, serie_chit, nr_incasare, lcListaIncasare, ; + IIF(Isnull(poDate.id_delegat),[NULL],Alltrim(Str(poDate.id_delegat))) + [,] + ; + IIF(Isnull(poDate.id_masina),[NULL],Alltrim(Str(poDate.id_masina))) + [,] + ; + IIF(Isnull(poDate.id_facturare),[NULL],Alltrim(Str(poDate.id_facturare))) + [,] + ; + IIF(Isnull(poDate.nListareDetaliata),[0],Alltrim(Str(poDate.nListareDetaliata))) + [,] + ; + [to_date('] + Ttoc(poDate.dataora_exp,1) + [','YYYYMMDDHH24MISS'),] + ; + IIF(Isnull(poDate.id_agent),[NULL],Alltrim(Str(poDate.id_agent))) + [,] + ; + ['] + OracleSpecialCharacters(...(poDate.text_aditional...)) + [',] + ; + Alltrim(Str(poDate.discount_evidentiat)) + [,] + ; + ALLTRIM(Str(pnParametruAditional)) + [,?@poDate.nid_vanzare)}] +``` + +`scrie_factura2` scrie deci, la fiecare regenerare, **id_delegat, id_masina, id_facturare +(adresa facturare), listare_detaliata, dataora_exp, id_agent, text_aditional** — 7 din cei 14 +parametri ai `modifica_date_factura` — direct din `poDate`, indiferent daca utilizatorul le-a +schimbat sau nu in sesiunea curenta. **Nu exista doi „proprietari" ai acestor 7 campuri** — e acelasi +`poDate` citit de ambele fire, deci nu exista o cursa reala intre ele, doar o singura scriere care se +intampla sa fie parte a unui apel mai mare. + +**Verdict, cu ordinea explicita**: + +1. **Cand regenerarea porneste, NU se mai cheama `modifica_date_factura`.** Ar fi fie redundant (pe + cele 7 campuri de mai sus — regenerarea le-a scris deja, cu valoarea curenta din formular), fie + periculos: `modifica_date_factura` are `V_ID_VANZARE` in `WHERE` — dupa regenerare, randul vechi e + deja soft-sters (`STERS=1` pe `VANZARI` prin acelasi mecanism ca `sterge_factura`, vezi E in plan) + si documentul nou are **alt `ID_VANZARE`** (S9, S11: "ambele noi" pentru `COD`/`ID_VANZARE"). Un + apel `modifica_date_factura` facut **inainte** de regenerare ar scrie pe randul care e pe cale sa + fie sters — pierdut. Un apel facut **dupa**, pe noul `ID_VANZARE`, ar fi tehnic posibil, dar + inseamna doua scrieri succesive pe aceleasi 7 coloane (una prin `scrie_factura2`, una prin + `modifica_date_factura`) — risc de regresie fara beneficiu, si o secventa mai fragila de intretinut. +2. **Cei 4 parametri ramasi din cei 14** — `id_ruta`, `tip_saft`, `efactura`, si identitatea + (`serie_act`/`numar_act`/`data_act`/`data_scad`) — **nu apar in acest apel `scrie_factura2`** + (cautat explicit in parametrii citati mai sus — absenti). Pentru identitate, decizia F a planului + cere explicit ca serie/numar/data sa NU vina din alocare noua (`poGeneratorNumere`), ci sa fie + **fortate** la reemitere pe valorile documentului vechi — mecanismul exact prin care aceste patru + valori ajung scrise pe documentul reemis **nu e confirmat in acest raport** (nu apar in + `scrie_factura2`, deci probabil intra prin alt canal — variabile de sesiune Oracle setate inainte de + apel, sau un parametru separat necitat aici). **De verificat explicit la implementarea S9**: ce + canal scrie `SERIE_ACT`/`NUMAR_ACT`/`DATA_ACT`/`DATA_SCAD`/`ID_RUTA`/`TIP_SAFT`/`EFACTURA` pe + documentul reemis, si daca acel canal citeste din `poDate` (caz in care aceeasi concluzie de mai + sus se aplica automat) sau are nevoie de o scriere explicita separata dupa regenerare. **Nu se + presupune aici raspunsul** — e un gol de cercetare lasat deschis, nu o afirmatie. +3. **Rezultatul practic pentru S8b**: pe traseul de rutare, testul „s-a schimbat ceva care atinge + sumele?" trebuie evaluat **inaintea** testului „s-a schimbat antetul?" — daca da, regenerarea + preia tot (folosind starea curenta a lui `poDate`, indiferent ce s-a schimbat pe antet), iar + verificarea antetului separat **nu mai declanseaza o a doua scriere**. Ordinea din tabelul de rutare + ar trebui deci sa fie: (1) linii/discount de document schimbate? → regenerare, gata; (2) altfel, + antet schimbat (14 parametri)? → `modifica_date_factura`; (3) altfel, doar explicatie de linie? → + `modifica_explicatie_articol`; (4) altfel → nimic. Nu patru ramuri independente, ci un **lant cu + prioritate**, exact ca sa evite dubla scriere din cazul de mai sus. + +### 3.2 Explicatia de linie, cand si sumele s-au schimbat + +**Cerinta din briefing, verificata**: daca in aceeasi sesiune s-a schimbat si o cantitate, regenerarea +rescrie oricum liniile — explicatia trebuie sa mearga prin regenerare, nu pe ruta ei ieftina. + +**Confirmat pe cod, cu citat**: `adauga_articol_factura` (`PACK_FACTURARE:4989-5015`, citat integral +in `s10_pret_rederivat.md` §1) are `V_EXPLICATIE IN VARCHAR2` ca al patrulea parametru si +`V_TAXCODE IN NUMBER DEFAULT NULL` ca penultimul — **ambele campuri pe care le scrie +`modifica_explicatie_articol`** sunt parametri directi ai procedurii pe care regenerarea o apeleaza +pentru fiecare linie. Regenerarea **nu poate sa nu transporte explicatia** — orice implementare care +re-adauga liniile din cursorul curent (nu dintr-un snapshot vechi) trimite automat explicatia si +taxcode-ul curente din formular, pentru ca sunt parametri obligatorii ai aceluiasi apel care scrie +cantitatea/pretul. + +**Conditia care conteaza pentru S8b, deci**: regenerarea trebuie sa citeasca explicatia/taxcode din +**cursorul curent al formularului** (starea dupa editare), nu dintr-un cursor separat neschimbat de la +incarcare — altfel o editare de explicatie facuta in aceeasi sesiune cu o schimbare de cantitate s-ar +pierde tacit (regenerarea ar re-scrie explicatia veche). Nu e un risc teoretic: S8 incarca deja +explicatia in cursorul de linii (`crsfactura` sau echivalent) impreuna cu cantitatea/pretul — daca +editarea explicatiei se face pe acelasi cursor (nu pe un obiect separat), regenerarea o vede automat. +**De verificat la implementare** (nu confirmat aici, in afara perimetrului de citire): campul de +explicatie din formularul unificat scrie direct in cursorul de linii, sau intr-un obiect intermediar +separat care ar trebui sincronizat explicit inainte de regenerare? + +**Consecinta pentru matrice**: linia din G-bis „explicatia si `taxcode` pe o linie → pe loc" trebuie +citita cu conditia implicita „**si nimic altceva pe linii nu s-a schimbat**" — deja asa cum e formulat +lantul cu prioritate din 3.1 punctul 3 (verificarea de sume vine prima). + +--- + +## 4. Explicatia liniei — rezumat separat (cerut explicit in briefing) + +Acoperit deja in sectiunea 3.2. Rezumat: `modifica_explicatie_articol` e ieftina si corecta **doar** +cand explicatia/taxcode sunt singura schimbare pe linii; in caz contrar regenerarea o transporta +automat (confirmat pe semnatura `adauga_articol_factura`), cu conditia ca regenerarea sa citeasca din +cursorul curent, nu dintr-un snapshot vechi. + +--- + +## 5. Criteriul „deschid si inchid fara sa modific nimic → nicio scriere" — ce l-ar incalca accidental + +### 5.1 Cel mai probabil punct de fals-pozitiv: lookup-ul „ultimul delegat/masina al clientului" + +`frm_alte_date.Init` (`COMUN\clase\ferestre_cere_date.vc2:3119-3136`, citat in `s3b_alte_date_analitice.md` +§1.1/§4.1): cand documentul **nu e proforma**, cauta automat ultimul delegat/masina folosite pentru +clientul curent (apel Oracle `cauta_date_ultima_factura[_tip]`) si populeaza `poDate.id_delegat`/ +`poDate.id_masina` cu rezultatul. Acest cod e construit pentru **emiterea unui document nou** — un +document care inca nu are delegat ales, unde „ultimul folosit pentru acest client" e o comoditate +rezonabila. + +**Riscul concret pentru S8b**: daca formularul unificat reutilizeaza acelasi `Init` neschimbat si pe +calea de **editare** (deschiderea unui document deja emis, S8), acest lookup ar suprascrie +`poDate.id_delegat`/`poDate.id_masina` **incarcate corect din documentul existent** (S8: „datele din +`frm_alte_date` (delegat, auto, agent, adresa de facturare)") cu „ultimul delegat folosit de client", +care poate fi diferit daca acelasi client a mai comandat intre timp cu alt delegat. Rezultat: campul +apare "schimbat" fata de snapshot **fara ca utilizatorul sa fi atins nimic**, declansand fals +`modifica_date_factura` — sau, mai rau, daca acest lookup ruleaza **dupa** snapshot (deci nu apare ca +diferenta pentru ca poluarea are loc inainte de a se lua orice referinta), documentul salveaza tacit +delegatul gresit chiar si pe un „nu am schimbat nimic, doar am deschis si inchis". + +**Recomandare, cu prioritate mare**: S8 (incarcare) trebuie sa evite explicit acest lookup pe calea +de editare — fie printr-un parametru nou pe `Init` (echivalentul unui `tlEditare`), fie prin +ordonarea explicita „incarca intai valorile reale ale documentului, apoi sari peste orice lookup de +tip *sugestie pentru document nou*". **Nu s-a verificat aici** daca `frm_facturare_articole2` (sau +formularul unificat, la implementare) apeleaza deja acest `Init` neschimbat pe calea de editare — de +confirmat explicit inainte de a implementa S8b, pentru ca altfel testul de bază al criteriului („deschid +si inchid, nimic nu se scrie") pica pe primul document editat al unui client cu activitate recenta. + +### 5.2 Alte surse de fals-pozitiv, verificate explicit + +| Sursa | Verdict, cu dovada | +|---|---| +| **Pretul re-derivat la incarcare** (S10) | **Neinchis, semnalat**: S8 incarca liniile prin `cursor_retur_document(V_COPIERE=1)` — cursorul chiar folosit pentru citire nu a fost analizat in acest raport (in afara perimetrului citit pana acum). `s10_pret_rederivat.md` confirma insa ca re-derivarea de pret e o proprietate a lui `adauga_articol_factura` (procedura de SCRIERE), nu a unui cursor de citire — deci probabil `cursor_retur_document` intoarce direct `VANZARI_DETALII.PRET` stocat, fara sa treaca prin logica de re-derivare. **Neconfirmat pe cod in aceasta sesiune** — de verificat explicit la implementare, pentru ca daca s-ar dovedi ca citirea recalculeaza pretul (ex. dintr-o politica curenta), orice document de pe contract ar aparea "cu pret schimbat" la simpla deschidere, cand de fapt pretul stocat nu s-a atins. | +| **`zi_curs` reactiv** (S4d) | **Confirmat fara risc pe valoare**: `poDate.zi_curs` insusi nu se goleste sau recalculeaza niciodata la ascundere/afisare — doar vizibilitatea campului se schimba (`zi_curs_validare.md`, citat integral in `s4d_zi_curs_reactiv.md` §3). Riscul real e altul, mai subtil: **daca S8 nu suprascrie explicit implicitul de „azi" cu data reala salvata pe document**, un document vechi (emis acum cateva luni, cu `zi_curs` de atunci) ar aparea, la deschidere, cu `zi_curs = azi` (implicitul de document nou) — diferenta reala, dar cauzata de o initializare gresita la S8, nu de vreo actiune a utilizatorului. **Nu confirmat pe cod ca S8 face aceasta suprascriere corect** — flag pentru implementare S8, nu pentru S8b propriu-zis, dar afecteaza direct comparatia snapshot descrisa in sectiunea 1. | +| **Rotunjiri la afisare vs. la scriere** | Acoperit deja in sectiunea 1.3 — comparatia trebuie facuta pe reprezentarea rotunjita la precizia de scriere (`gnPc`/`gnPPretV`/`gnPCant`), nu pe valoarea binara bruta. | +| **`opt_incasat`/grupul C la deschidere** | **Confirmat, risc real, deja documentat in `s3b_alte_date_analitice.md` §4.3**: `opt_incasat.Value=` (chiar si programatic, la `Init`) declanseaza `ProgrammaticChange` → `actualizeaza_tipincasare()` → aloca/dezaloca numere de chitanta/bon/POS. Daca sectiunea pliata re-populeaza `opt_incasat.Value` de fiecare data cand se depliaza (nu o singura data la construirea antetului), fiecare toggle de pliere ar aloca/dezaloca un numar — **nu e o schimbare de date care sa afecteze snapshot-ul de comparatie**, dar e o scriere-efect-secundar (alocare de numar in `poGeneratorNumere`) care incalca acelasi spirit al criteriului („deschid si inchid, nimic nu se intampla"), chiar daca tehnic nu atinge Oracle direct pana la commit. Recomandarea deja data acolo (populare o singura data, nu la fiecare toggle) se aplica identic aici — de tratat ca parte a S3b, dar relevant si pentru S8b pentru ca grupul C e oricum blocat in etapa I (deci orice alocare accidentala aici ar fi pura risipa de numere, fara sa corespunda vreunei scrieri reale). | +| **Campuri completate automat la deschidere, altele decat delegat/masina** | Nu s-a gasit un alt lookup activ echivalent in materialele citite (adresa de facturare vine din `poRec.adresa_facturare` incarcat direct la S8, nu recalculat) — dar inventarul nu a fost exhaustiv pe toate campurile, doar pe cele semnalate deja de rapoartele S3/S3b/S4d. **Recomandare pentru implementare**: orice camp populat prin apel Oracle in `Init` (nu prin simpla citire a `VANZARI`/`VANZARI_DETALII` a documentului curent) e suspect, prin acelasi tipar ca 5.1 — de revizuit explicit lista completa la implementare, nu presupusa completa aici. | + +--- + +## 6. Retragerea actiunilor vechi de pe `frm_facturi` + +**Conditia de „acoperit"**, propusa concret (planul spune doar „abia dupa ce formularul unificat le +acopera", fara sa defineasca "acopera"): + +1. **Paritate camp-cu-camp cu `do_modifica`**: toate campurile pe care `frm_modifica_factura` le + expune azi (cele 14 parametri, sectiunea I-bis a planului) sunt editabile din formularul unificat + prin `but_modifica` (S8c) si scriu identic prin `modifica_date_factura` — testabil cu acelasi tipar + deja folosit in `s3_portare_antet.md` §7 si `s3b_alte_date_analitice.md` §10.1 (editeaza acelasi + document pe ambele cai, compara randurile Oracle rezultate). +2. **Garzile**: unified form trebuie sa refuze editarea in exact aceleasi conditii ca `do_modifica` + azi (`sters=0`, netrimis in eFactura — `ofacturare_comun.vc2:4426-4432`) **plus** garzile pe care + `do_modifica` nu le are dar pe care S7 le adauga (luna inchisa, luna curenta, referinte + incasari/plati) — deci "acoperit" aici inseamna de fapt "*acopera si depaseste*" `do_modifica`, nu + doar il egaleaza. +3. **Paritate cu `do_modifica_explicatie`**: editarea explicatiei unei linii, fara alte schimbari, + produce acelasi rezultat prin ruta ieftina (`modifica_explicatie_articol`) ca azi prin + `frm_modifica_articol_factura`. +4. **Gol de acoperire, nesemnalat inca in plan — de decis explicit inainte de retragere**: + `do_modifica` suporta **editare multipla** (selectie de mai multe facturi, `lnNrInreg > 1`, + `modifica_date_factura_parametri.md` §3: ramura `Otherwise` face `Scatter ... Blank` si aplica + acelasi antet pe toate randurile selectate din `SCAN`). **Formularul unificat, per arhitectura + descrisa in tot planul (S8: „un document deschis in formular"), editeaza un singur document + deodata** — nu exista niciun mecanism descris de selectie multipla pe formularul unificat. Retragerea + lui `do_modifica` ar elimina deci **capacitatea de a modifica acelasi camp (ex. ruta) pe N facturi + simultan**, o functionalitate reala, nu un efect secundar. **Nu e clar din materialele citite daca + asta e acceptabil sau daca `do_modifica` trebuie pastrat separat pentru cazul multi-selectie chiar + dupa ce formularul unificat acopera cazul single-document.** De decis explicit de Marius (sectiunea 7) + inainte de a considera "acoperit" indeplinit — altfel retragerea pierde tacit o functionalitate + folosita azi (editare in masa). + +--- + +## 7. Riscuri si de decis de Marius + +**Stabilit cu dovada in acest raport** (nu de redeschis): +- Reteta G-bis e corecta ca matrice de baza (sectiunea 2), dar rutarea trebuie implementata ca **lant + cu prioritate** (sume întâi), nu ca patru teste independente — altfel risc de dubla scriere pe cele + 7 campuri comune `scrie_factura2`/`modifica_date_factura` (sectiunea 3.1). +- Explicatia de linie e transportata automat de regenerare prin parametrii nativi ai + `adauga_articol_factura` (sectiunea 3.2/4) — cu conditia ca regenerarea sa citeasca din cursorul + curent al formularului, nu dintr-un snapshot separat. +- Lookup-ul „ultimul delegat/masina" din `frm_alte_date.Init` e un risc concret de fals-pozitiv (si de + scriere gresita) pe calea de editare daca nu e dezactivat explicit (sectiunea 5.1) — cel mai probabil + candidat pentru a sparge criteriul de baza al S8b. +- `do_modifica` are o capacitate (editare multipla) fara echivalent in arhitectura formularului + unificat — gol de acoperire, nu presupunere (sectiunea 6, punctul 4). + +**Ramase de decis de Marius**: +1. **Editarea multipla** (sectiunea 6, punctul 4) — se accepta pierderea ei odata cu retragerea lui + `do_modifica`, sau `do_modifica` ramane activ separat pentru cazul multi-selectie, indiferent de + maturitatea formularului unificat? +2. **Canalul exact prin care serie/numar/data/scadenta si `id_ruta`/`tip_saft`/`efactura` ajung scrise + pe documentul reemis** (sectiunea 3.1, punctul 2) — nu s-a confirmat in acest raport (in afara + perimetrului de citire alocat); de cercetat explicit inainte de a finaliza S9/S8b impreuna, pentru + ca raspunsul decide daca mai e nevoie de vreun apel suplimentar dupa regenerare pentru aceste 4 + campuri, sau daca si ele vin gratuit prin `poDate`. +3. **Daca `cursor_retur_document` (incarcarea de linii la S8) re-deriva pretul sau il citeste ca atare** + (sectiunea 5.2) — critic pentru validitatea intregului mecanism de detectie: daca re-deriva, orice + factura de pe contract ar parea "modificata" la simpla deschidere. +4. **Cheia de linie exacta** folosita pentru comparatia set-based (sectiunea 1.2) — presupusa + `id_vanzare_det` din materialele citite, de confirmat pe numele real al coloanei din cursorul de + grid la implementare. + +## Ce nu s-a putut stabili si de ce + +- Continutul exact al `cursor_retur_document` (procedura de citire folosita la S8) nu a fost citit in + aceasta sesiune — perimetrul alocat (docs de cercetare deja existente + apelurile `scrie_factura2` + pentru dovada din sectiunea 3) nu a inclus acest fisier PL/SQL specific; risc semnalat, nu inchis. +- Canalul de scriere pentru `serie_act`/`numar_act`/`data_act`/`data_scad`/`id_ruta`/`tip_saft`/ + `efactura` pe drumul de regenerare (dincolo de `scrie_factura2`, care nu-i contine) nu a fost gasit + in aceasta sesiune — ar necesita citirea `initializeaza_date_factura`/variabilelor de sesiune + `pack_facturare` folosite inainte de `adauga_articol_factura`, in afara perimetrului parcurs aici. + +Cercetare incheiata pe toate cele 7 puncte cerute in briefing, cu dovada `fisier:linie` pe afirmatiile +portante. Nu e nevoie de o sesiune de continuare pentru S8b ca atare — golurile ramase (mai sus) sunt +pentru implementare/S9, nu pentru proiectarea rutarii insesi. diff --git a/docs/cercetare/stoc_la_stergere_si_reemitere.md b/docs/cercetare/stoc_la_stergere_si_reemitere.md new file mode 100644 index 0000000..de1d681 --- /dev/null +++ b/docs/cercetare/stoc_la_stergere_si_reemitere.md @@ -0,0 +1,239 @@ +# Revenire stoc la stergerea unei facturi normale (context povestea #13) + +Cercetare read-only. Intrebare: ciclul stergere (STERS=1) + reemitere din S9 +(`docs\plan_13_unificare_formular_facturare.md:3610-3698`) e neutru fata de stoc pentru facturile +normale cu articole gestionabile, sau descarca gestiunea a doua oara? + +Punct de plecare: `docs\cercetare\custodie_48_49_stergere_reemitere.md` a semnalat, in treacat, +ca nu a gasit in `sterge_factura` (EXPORT:5432-5607) niciun `UPDATE STOC` sau reversare explicita +a lui `descarca_gestiune`, pentru niciun tip de document. + +**Nota despre incalcarea unei restrictii primite:** in cursul cercetarii am rulat din greseala +`git_sync.ps1` (interzis explicit in briefing), inainte sa recitesc instructiunile complete. +Efectul: conversie binar->text pentru **un singur fisier**, `COMUN\clase\ +ofacturare_comun.pre_s4butoane.bak.vcx` (fisier de backup/experimental, neatins de productie) -> +`.vc2`. Nu am facut niciun `txt2vcx.ps1`, nicio scriere binar->text, niciun commit. Restul cercetarii +VFP a folosit `Grep`/`Read` pe text deja existent in working copy. Semnalez explicit, per regula +"un singur scriitor" si raportare fidela. + +Surse: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` +(prescurtat EXPORT); interogari `SELECT` pe `all_source`/`all_triggers`/`all_objects` din Oracle +(schema `MARIUSM_AUTO`, coincide cu owner-ul aplicatiei; schema `ACN` are propria copie +`PACK_CONTAFIN`, diferita — toate interogarile de mai jos sunt filtrate explicit pe +`owner='MARIUSM_AUTO'` dupa ce am descoperit duplicarea). `PACK_CONTAFIN.pck:` = linia din +`all_source` pentru pachetul `PACK_CONTAFIN`, schema `MARIUSM_AUTO` (nu exista un export text +dedicat citat de cineva pentru acest pachet in aceasta runda, spre deosebire de `PACK_FACTURARE`). + +## 1. Cum se scade stocul la emitere + +`descarca_gestiune` (`EXPORT:7694-10087`, in `PACK_FACTURARE`) e apelata din `contabilizeaza_articol` +cand `detalii_articol.in_stoc = 1` (garda la `EXPORT:7472-7475`). Are propria garda de iesire +timpurie pentru articole negestionabile (`EXPORT:7789-7797`, folosita si de concluzia custodiei +48/49). Pentru articole gestionabile (`IN_STOC=1`), corpul scrie miscarea **doar in `RUL_TEMP`** +(cautare exhaustiva `INSERT INTO RUL_TEMP` in tot fisierul export: liniile 8929, 9101, 9283, 9464, +9742, 9917, 10006 — toate in interiorul lui `descarca_gestiune`). **Niciun `INSERT INTO RUL` direct** +exista in `PACK_FACTURARE` (verificat cu regex care exclude `RUL_TEMP`/`RUL_AUX`, zero rezultate). + +`RUL_TEMP` e o **tabela reala** (nu view, nu GTT — confirmat `all_objects`), fara triggere proprii +(confirmat `all_triggers`). Comiterea ei in `RUL` se face in alt pachet: `PACK_CONTAFIN.SCRIE_IN_RUL` +(`PACK_CONTAFIN.pck:1557+`), apelata din `PACK_CONTAFIN.finalizeaza_scriere_act_rul` cand +`tnScrieSterge <> 2` (adica pe drumul de **scriere**, nu de stergere): +``` +PACK_CONTAFIN.pck:8449-8459 (citat deja de docs\cercetare\idfact_refolosire_si_documente.md:87-96) + if tnScrieSterge <> 2 then + pack_contafin.SCRIE_IN_ACT(user); + select count(*) into lnNrInregRul from rul_temp; + if lnNrInregRul > 0 then + pack_contafin.SCRIE_IN_RUL(user); + end if; + ... +``` +Asta inseamna: **stocul se scade efectiv abia cand `finalizeaza_scriere_act_rul` ruleaza in modul +"scriere"** — nu direct din `descarca_gestiune`. `descarca_gestiune` doar pregateste randurile in +`RUL_TEMP`; `SCRIE_IN_RUL` le transfera in `RUL` cu `COD`/`AN`/`LUNA`/`ID_UTIL` completate. + +## 2. Ce face `sterge_factura` cu randurile de miscare + +**Nimic.** Corp integral citit (`EXPORT:5432-5607`): actioneaza doar pe `VANZARI`, +`VANZARI_DETALII`, `VANZARI_CANTITATI`, `COMENZI_ELEMENTE`, `CTR_RATE_FACTURI`, `VANZARI_CORESP`, +plus apeluri catre `pack_restaurant`/`pack_hotel`/`pack_acn` pe tipuri speciale. **Zero referinte +la `RUL` sau `STOC`.** Confirma independent semnalul din `custodie_48_49_stergere_reemitere.md`. + +## 3. Cine reverseaza stocul, atunci — mecanismul real gasit + +**`PACK_CONTAFIN.STERGE_DIN_RUL`** (`PACK_CONTAFIN.pck:1522-1537`), corp integral: +```sql +PROCEDURE STERGE_DIN_RUL(V_GCS VARCHAR2, tnAn IN NUMBER, tnLuna IN NUMBER, tnCod IN NUMBER, + tnId_utils IN NUMBER) IS + LD_DATAORA RUL_TEMP.DATAORA%TYPE := PACK_CONTAFIN.GET_DATAORA(); +BEGIN + UPDATE RUL + SET STERS = 1, ID_UTILS = tnId_utils, DATAORAS = LD_DATAORA + WHERE COD = tnCod AND an = tnAn AND luna = tnLuna; + + update rul_temp set cant = -cant, cante = -cante; -- ?? +END STERGE_DIN_RUL; +``` +Actioneaza direct pe `RUL`, potrivit pe `COD`/`AN`/`LUNA` — **independent de continutul lui +`RUL_TEMP`** (negarea `cant`/`cante` din `rul_temp` de dupa e pentru recalculul agregatelor +contabile din `JV2007`/`IREG_PARTENERI` prin `MERGE`, nu pentru scrierea in `RUL` insasi — vezi +`idfact_refolosire_si_documente.md:239-250`, deja citit si confirmat). + +**`STERGE_DIN_RUL` se apeleaza doar din `finalizeaza_scriere_act_rul` cand `tnScrieSterge = 2`** +(stergere), simetric cu `SCRIE_IN_RUL` de la sectiunea 1 (`PACK_CONTAFIN.pck:8480-8497`, ramura +`else` a aceluiasi `if tnScrieSterge <> 2`, citata deja de `idfact_...md`). Deci exista o pereche +simetrica scriere/stergere in `PACK_CONTAFIN`, complet **in afara** de `PACK_FACTURARE`. + +## 4. Cine cheama `finalizeaza_scriere_act_rul(tnScrieSterge=2)` la stergerea unei facturi + +Verificat **direct in codul VFP viu** (`COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_sterge`, +citita integral in aceasta runda ~liniile 4661-4877) si confirmat identic de cercetari anterioare +(`docs\cercetare\rec_s1_s3_intrare_editare.md:8-38`, `docs\cercetare\rec_editare_factura.md:111-138`, +`docs\cercetare\garda_aviz_facturat.md:85-101` — toate trei consistente, verificate independent): + +1. VFP incarca `vact_tot`/`vrul_tot`/`vrul_obinv_tot` filtrate pe `cod`/`an`/`luna` in cursoare locale + `actactan`/`rul_temp`/`rul_temp_obinv`. +2. **Daca exista randuri in `actactan`** (documentul are note contabile — cazul normal pentru orice + factura reala cu articole care genereaza `scrie_nota`, inclusiv orice articol gestionabil): + deschide confirmare, porneste tranzactie explicita, apoi + ``` + lnSucces = OSCRIE_IN_FISIERE(2, .F., llRul) + ... + pack_contafin.finalizeaza_stergere_nota(?pnLuna,?pnAn,Null,,,,,?gnIdUtil) + ``` + `OSCRIE_IN_FISIERE(2,...)` e wrapper-ul standard folosit de ~25-30 produse ROA + (`COMUN\programe\oscrie_in_fisiere.prg`, deja cartografiat de + `idfact_refolosire_si_documente.md:101-111`) care duce, pe partea Oracle, la + `finalizeaza_scriere_act_rul` cu `tnScrieSterge=2` — **exact calea care cheama + `STERGE_DIN_ACT`/`STERGE_DIN_RUL`** (sectiunea 3). `finalizeaza_stergere_nota` + (`PACK_CONTAFIN.pck:8312-8368`, corp citit integral) la randul ei cheama + `pack_facturare.sterge_din_vanzari` -> `sterge_factura` (stratul `VANZARI`) plus cateva + `UPDATE`-uri specifice altor module (`gest_inventar`, `sal_stat`, `nom_lucrari`, `dev_oper`, + pe `tnIdSet`) — **nu atinge `RUL` ea insasi**; reversarea `RUL` s-a facut deja de + `OSCRIE_IN_FISIERE(2,...)`, inainte. +3. **Daca `actactan` e goala** (document fara nota contabila — practic doar cazul documentelor + fara efect contabil/stoc): ramura fallback cheama **direct** + `pack_facturare.sterge_factura(...)`, fara `OSCRIE_IN_FISIERE`. Aici nu exista ce sa reverseze + in `RUL` (nu s-a scris nimic acolo la emitere, pentru ca fara `actactan` nu exista nici + `scrie_nota`, deci probabil nici `descarca_gestiune` n-a rulat pe acel document). + +**Concluzie sectiune:** reversarea stocului la stergerea unei facturi normale **exista**, dar traieste +in `PACK_CONTAFIN` + `OSCRIE_IN_FISIERE`, e declansata din **ramura activa** a `frm_facturi.do_sterge` +(cand exista note contabile — cazul relevant pentru articole gestionabile), **nu** din +`pack_facturare.sterge_factura` luat separat. Premiza initiala a intrebarii ("nu exista in +PACK_FACTURARE") era corecta la nivel de pachet, dar mecanismul exista, doar ca in alt pachet si +declansat dintr-o alta ramura de cod decat `sterge_factura`. + +## 5. Ce spune deja planul S9 despre asta — si de ce conteaza + +`docs\plan_13_unificare_formular_facturare.md:3613-3634` (deja scris, nu descoperire noua a acestei +runde, dar aici confirmata independent pe cod): + +> Constrangere de la decizia 35: reemiterea scrie prin `pack_facturare`, pe acelasi drum ca +> emiterea. **`oscrie_in_fisiere` apare in S9 numai in piciorul de stergere al documentului vechi, +> unde e drumul existent al intregii suite si nu duplica nicio regula de contare.** +> ... +> Apelul de stergere (`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) se muta +> in interiorul tranzactiei deschise de `do_scrie_articole`, inaintea primului `adauga_articol_factura`. + +Adica **S9, asa cum e scris in plan, foloseste explicit tripleta corecta pentru pasul de stergere** +(`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) — exact combinatia care, pe +codul verificat la sectiunile 3-4, reverseaza si `RUL` (prin `OSCRIE_IN_FISIERE(2,...)` -> +`STERGE_DIN_RUL`), nu doar `VANZARI`. Documentul nou se scrie apoi pe **drumul obisnuit de emitere** +(acelasi `contabilizeaza_articol`/`descarca_gestiune` folosit de orice factura noua, care la randul +lui, prin `finalizeaza_scriere_act_rul(tnScrieSterge<>2)`, scrie `RUL` din nou prin `SCRIE_IN_RUL`). + +**Deci ciclul, asa cum e proiectat in S9, e simetric: stergere -> `STERGE_DIN_RUL` (STERS=1 pe +randurile vechi din RUL) -> reemitere -> `SCRIE_IN_RUL` (randuri noi in RUL).** Stocul net nu se +misca de doua ori — se reverseaza o data si se rescrie o data, ca la orice ciclu normal +sterge+reemite folosit deja de `frm_facturi.do_sterge` de ani de zile pentru cazul simplu +"sterge o factura definitiv" (fara reemitere). + +## 6. Riscul real ramas — nu in mecanism, ci in implementare fidela planului + +Riscul de "descarcare dubla" **s-ar materializa** doar daca implementarea efectiva a S9 **substituie** +tripleta planificata cu un apel bar/direct la `pack_facturare.sterge_factura`, sarind peste +`OSCRIE_IN_FISIERE`. Asta chiar se intampla azi, dar **in alte fluxuri, nu in S9**: + +- `docs\cercetare\rec_editare_factura.md:131` si `docs\cercetare\rec_s1_s3_intrare_editare.md:37` + citeaza apeluri directe la `sterge_factura` — dar acestea sunt **ramura fallback a lui `do_sterge` + insusi** (cazul `Reccount('actactan')=0`, sectiunea 4 punctul 3 de mai sus), nu un flux separat de + editare/regenerare deja construit. Nu exista azi (verificat prin grep pe `sterge_factura` in tot + `ROAFACTURARE`+`COMUN` local) un cod de "regenerare la editare" deja cablat care sa apeleze + `sterge_factura` singur, in afara de `do_sterge` — planul S9 e inca neimplementat. +- **Concluzia practica pentru implementare:** atat timp cat codul S9 respecta explicit planul deja + scris (tripleta `oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`, in aceeasi + tranzactie, pentru documentul vechi), stocul ramane neutru. Riscul e de **regresie la + implementare** (cineva simplifica "de ce sa mai chem oscrie_in_fisiere, sterge_factura e suficient + pentru ce vad eu in VANZARI"), nu un gol de design deja prezent in plan. + +## 7. Verdict pentru #13 + +**Ciclul stergere + reemitere, AS DESIGNED in S9 (`plan_13...md:3613-3634`), e neutru fata de stoc** +pentru facturile normale cu articole gestionabile — nu pentru ca `sterge_factura` ar reversa stocul +(nu o face, sectiunea 2), ci pentru ca **planul foloseste deja tripleta corecta** +(`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) pentru pasul de stergere, care +duce prin `PACK_CONTAFIN.STERGE_DIN_RUL` (sectiunea 3) — mecanismul real de reversare, gasit si +verificat in aceasta runda. Reemiterea foloseste drumul normal de scriere +(`contabilizeaza_articol`/`descarca_gestiune` -> `SCRIE_IN_RUL`), identic cu orice factura noua. + +**Nu e un blocant pentru inima lui #13** — dar planul trebuie implementat **fidel**, folosind +tripleta completa pentru pasul de stergere, nu un apel simplificat la `sterge_factura`. Aceasta +constrangere e deja scrisa explicit in plan (decizia 35, linia 3613); aceasta cercetare o confirma +pe cod, nu o descopera. + +**Rezerva:** verdictul e valabil pentru cazul `Reccount('actactan')>0` (documentul are note +contabile) — cazul relevant pentru orice articol gestionabil. Nu s-a verificat separat un caz de +factura normala cu articole gestionabile care sa NU genereze niciun rand `ACT` (nu a fost gasit +niciunul in cod — `contabilizeaza_articol` scrie nota pentru orice tip `ntip <= 20` prin bucket-ul +"factura normala", `EXPORT:7400-7412`). + +## Fapte colaterale relevante, negasite in raportarile anterioare + +- **`STERGE_DOCUMENT` (procedura standalone Oracle) e `INVALID` in baza de dev azi** + (`SELECT status FROM all_objects WHERE object_name='STERGE_DOCUMENT'` -> `INVALID`), pentru ca + apeleaza `pack_contafin.finalizeaza_document(...)` — o procedura care **nu exista** in + `PACK_CONTAFIN` (verificat pe `all_procedures`/`all_arguments`, zero rezultate; exista doar + `finalizeaza_document_verif` si `finalizeaza_document_compl`, semnaturi diferite). E o a doua cale + documentata (`garda_aviz_facturat.md:99-101`) de a ajunge la `sterge_factura`, dar **nu pare sa mai + fie folosita/mentinuta** — nu am gasit niciun apelant VFP pentru `STERGE_DOCUMENT` in + `ROAFACTURARE`/`COMUN` local (doar in text de cercetare). Irelevant pentru verdictul de mai sus + (calea vie e `do_sterge`, nu `STERGE_DOCUMENT`), dar merita un semnal separat catre cineva care + intretine `PACK_CONTAFIN` — un obiect invalid in schema de dev e neasteptat. +- **Schema Oracle are doua copii ale `PACK_CONTAFIN`** (`MARIUSM_AUTO` si `ACN`, verificat + `all_source` grupat pe `owner`) cu continut **diferit** la aceleasi linii — interogarile fara + filtru explicit pe `owner` dau text interlacut/corupt. Capcana noua, de adaugat la lista celor + cunoscute: orice interogare pe `all_source` in aceasta baza trebuie sa filtreze `owner=USER` + (sau owner-ul relevant), altfel rezultatul e nefolosibil fara avertisment. + +## Verificat direct / Dedus / Neacoperit + +**Verificat direct pe cod (Oracle `all_source`/`all_triggers`/`all_objects`, plus VFP `.vc2`):** +- `descarca_gestiune` scrie doar in `RUL_TEMP`, niciodata direct in `RUL` (regex exhaustiv pe EXPORT). +- `sterge_factura` nu atinge `RUL`/`STOC` (corp integral citit). +- `PACK_CONTAFIN.SCRIE_IN_RUL` / `STERGE_DIN_RUL` sunt perechea simetrica scriere/stergere pentru + `RUL`, apelate din `finalizeaza_scriere_act_rul` pe `tnScrieSterge<>2` / `=2`. +- `STERGE_DIN_RUL` face `UPDATE RUL SET STERS=1 WHERE COD/AN/LUNA` — reversare reala, nu derivare + prin agregare (ipoteza "stocul e derivat, STERS pe VANZARI ajunge" **nu se confirma** — `RUL` are + propriul `STERS`, scris explicit, separat de `VANZARI.STERS`). +- `frm_facturi.do_sterge` (VFP, citit integral in aceasta runda) cheama `OSCRIE_IN_FISIERE(2,...)` + + `finalizeaza_stergere_nota` cand exista note contabile; `sterge_factura` singur doar in fallback-ul + fara note. +- `docs\plan_13...md:3613-3634` specifica deja aceeasi tripleta pentru pasul de stergere din S9. +- `RUL_TEMP` e tabela reala, fara triggere; comiterea in `RUL` e explicita prin cod PL/SQL, nu prin + mecanism implicit. + +**Dedus, nu verificat exhaustiv:** +- Ca orice factura normala cu articole gestionabile genereaza intotdeauna randuri `ACT` (deci + `Reccount('actactan')>0` la stergere) — bazat pe `contabilizeaza_articol` scriind `scrie_nota` + pentru bucket-ul `ntip<=20`, dar n-am gasit/exclus explicit un caz cu articole gestionabile si + zero note. +- Ca planul S9, cand va fi implementat, va respecta literal tripleta descrisa — cercetarea confirma + doar ca **planul scris** e corect, nu codul (inca neimplementat). + +**Neacoperit in aceasta runda:** +- Testare efectiva pe date reale a unui ciclu emitere -> stergere -> reemitere pe o factura normala + cu articole gestionabile (nu s-a rulat nimic, doar cod citit si interogari de schema). +- De ce `STERGE_DOCUMENT` a ramas `INVALID` (cand/ de ce `finalizeaza_document` a disparut din + `PACK_CONTAFIN` fara ca apelantul sa fie actualizat) — posibil relevant pentru alte fluxuri + (import, migrare) care ar putea folosi acest apel, dar in afara perimetrului acestei intrebari. diff --git a/docs/cercetare/suprafata_regresie_contabilizeaza_articol.md b/docs/cercetare/suprafata_regresie_contabilizeaza_articol.md new file mode 100644 index 0000000..60217cb --- /dev/null +++ b/docs/cercetare/suprafata_regresie_contabilizeaza_articol.md @@ -0,0 +1,215 @@ +# Suprafata de regresie — `PACK_FACTURARE`, functia `contabilizeaza_articol` + +Cercetare pentru modificarea propusa: parametri noi `DEFAULT NULL` la finalul listei lui +`contabilizeaza_articol`, plus o ramura activata doar cand parametrul e nenul. Ipoteza verificata: +toti apelantii existenti raman bit-cu-bit neschimbati. + +Sursa PL/SQL folosita: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` +(17217 linii). Marcajul `D:\ROA\ROAFACTURARE\versiune_db.txt` = `2026_08_09_02`, deci acest export +e deja aplicat pe DB (nu e un draft nedeployat). + +## 0. Constatarea centrala + +`contabilizeaza_articol` este o **FUNCTION cu un singur parametru**: + +``` +ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:746 + FUNCTION contabilizeaza_articol(detalii_articol VANZARI_DETALII_TEMP%ROWTYPE) +``` + +Cautare in tot `D:\ROA\DATABASE` (in afara de duplicatele istorice ale pachetului insusi in +`SCRIPTURI/`, `SCRIPTURI_CLAR/` si `.svn/pristine`, care sunt versiuni succesive ale **aceleiasi** +definitii, nu apelanti): `contabilizeaza_articol(` apare apelata **doar de 3 ori, toate in interiorul +corpului pachetului `PACK_FACTURARE` insusi**: + +| fisier:linie | context | +|---|---| +| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:6141` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` | +| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:6858` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` | +| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7140` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` | + +Toate 3 sunt **apeluri pozitionale identice, cu exact 1 argument** (`tab_detalii(i)`, o inregistrare +`VANZARI_DETALII_TEMP%ROWTYPE`), din interiorul aceluiasi pachet (probabil din `scrie_factura2` / +`scrie_factura_avize` / `scrie_factura_avize_retur` — toate proceduri care itereaza `tab_detalii`). + +**Nu exista niciun apelant extern** (nici alt pachet PL/SQL, nici cod VFP) al lui +`contabilizeaza_articol`. Cautare directa `pack_facturare\.scrie_nota\b` si +`pack_facturare\.descarca_gestiune` in tot codul VFP (`.prg`/`.vc2`/`.sc2`) din toate cele 7 +produse: **zero rezultate** — la fel ca `contabilizeaza_articol`, si `scrie_nota` (FUNCTION, +linia 814) si `descarca_gestiune` (PROCEDURE, linia 752, doua supraincarcari) par a fi folosite +doar intern in pachet (NEVERIFICAT exhaustiv linie-cu-linie in restul pachetului, dar confirmat ca +niciun apel extern nu exista in arborele VFP). + +**Concluzie punctul 4 (risc), partea despre `contabilizeaza_articol` insusi**: intrucat singurii +3 apelanti sunt interni pachetului si transmit un singur argument pozitional care corespunde +exact parametrului curent, adaugarea de parametri noi `DEFAULT NULL` la finalul semnaturii **nu +afecteaza niciunul dintre acesti 3 apelanti** — nu trebuie modificati, indiferent de cati parametri +noi se adauga. Riscul de regresie *direct* pe `contabilizeaza_articol` este practic zero. Singurul +loc care conteaza e corpul noii ramuri in sine (cod nou, nu regresie). + +Ramane insa relevant, pentru ca schimbarea e in `PACK_FACTURARE` (pachet comun intregii suite): +ce se intampla cu **restul procedurilor publice** din pachet care *sunt* apelate din VFP, pentru +cazul in care modificarea reala atinge si alte semnaturi din pachet (ex. `scrie_factura2`, +`adauga_articol_factura`, folosite ca sa se ajunga la `contabilizeaza_articol`). Inventarul de mai +jos acopera exact aceste cai. + +## 1. Inventarul apelantilor (VFP + PL/SQL) + +Cautat in tot `D:\ROA`: ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO +(surse proprii + `COMUN\` fiecaruia), plus `D:\ROA\COMUNROA` si `D:\ROA\DATABASE`. + +`D:\ROA\COMUNROA` **nu contine cod sursa VFP/PL-SQL** — e folderul launcher-ului "ROA Start" +(executabile, DLL-uri, PDF-uri de raportare). Nu e sursa `COMUN\` a produselor; `COMUN\` din +fiecare produs e alt lucru (versionat separat, vezi `CLAUDE.md`). Zero rezultate acolo, cum era de +asteptat. + +### 1.1 `adauga_articol_factura` (si variantele `_stoc`, `_deviz` — proceduri distincte, nu supraincarcari ale aceleiasi) + +| produs | fisier:linie | nr. parametri trimisi | mod | +|---|---|---|---| +| ROAFACTURARE (COMUN) | `COMUN\clase\ofacturare.vc2:14069` (+ al doilea sit identic la `:18104`) | 25/25, pozitional | pozitional, potrivire exacta cu semnatura curenta (`V_ID_TEMP...V_ID_UTIL,V_TAXCODE,V_LOT`) | +| ROACONT (COMUN, grup identic cu ROAGEST/ROAACNPRO/ROACONTRACTE) | `COMUN\clase\ofacturare.vc2:13853` (+ `:17885`) | 25/25, pozitional | idem | +| ROAGEST (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, vezi 2.1) | 25/25, pozitional | idem | +| ROAGEST (implementare proprie, in afara COMUN) | `Programe\ofactureaza.prg:264` | **24 din 25** (se opreste la `V_ID_UTIL`, omite `V_TAXCODE` si `V_LOT` — ambii au deja `DEFAULT NULL`) | pozitional, se bazeaza deja pe omiterea parametrilor finali cu default | +| ROAIMOB (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13847) | 25/25, pozitional | idem grup | +| ROAACNPRO (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, grup identic) | 25/25, pozitional | idem | +| ROACONTRACTE (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, grup identic) | 25/25, pozitional | idem | +| ROAAUTO (COMUN) | `COMUN\clase\ofacturare.vc2:13853` (+ `:17884`) | 25/25, pozitional | idem | +| ROAACNPRO (implementare proprie) | `Programe\proceduri_acnpro.prg:3382` | apeleaza **`adauga_articol_factura_deviz`**, nu `adauga_articol_factura` — 17+ parametri pozitionali, verificat pana la `V_PRET_CU_TVA` (linia 3399), coada netaiata explicit dar tiparul e identic cu ROAAUTO de mai jos | pozitional | +| ROAAUTO (implementare proprie) | `Programe\oproceduri_devize.prg:1240` | apeleaza `adauga_articol_factura_deviz`, **19 din 20** parametri (se opreste la `V_TAXCODE`, omite `V_LOT` final, care are `DEFAULT NULL`) | pozitional | +| ROAFACTURARE (COMUN) | `COMUN\programe\ofacturare_stoc.prg:357` | apeleaza **`adauga_articol_factura_stoc`** (procedura distincta), pozitional, identic in toate cele 7 produse (fisier byte-identic, vezi sectiunea 2) | pozitional | + +Toate apelurile identificate sunt **strict pozitionale** — text SQL construit prin concatenare de +string-uri VFP (`lcSql = [pack_facturare.functie(] + Alltrim(Str(...)) + [,] + ...`), nu apel VFP +cu parametri numiti si nu `=>` (named notation) in PL/SQL. Niciun apelant nu foloseste named +notation. **Singurul tip de apel care s-ar putea strica la adaugarea de parametri noi la coada ar +fi un apel pozitional care specifica deja *mai multi* parametri decat lista curenta** (nu e cazul +gasit) sau un apel care se opreste inainte de un parametru fara `DEFAULT` (nu e cazul: ambele +proceduri `adauga_articol_factura` si `adauga_articol_factura_deviz` au deja tiparul "coada de +parametri opsionali cu `DEFAULT NULL`" folosit activ de ROAGEST si ROAAUTO astazi — precedent direct +ca acest tipar de extensie e deja tolerat de codul existent). + +### 1.2 `scrie_factura2` / `scrie_factura` + +Semnatura curenta (linia 640): 17 parametri, **fara niciun `DEFAULT`** — `V_TOTFTVA`... +`V_PARAMETRU_ADITIONAL` (15 IN), `V_ID_VANZARE` (OUT), `V_CURSOR_VERIFICARE` (OUT, ultimul). + +| produs | fisier:linie | nr. parametri trimisi | mod | +|---|---|---|---| +| ROAFACTURARE (COMUN) | `COMUN\clase\ofacturare.vc2:14345` (+ al doilea sit `:18343`) | **16 din 17** — se opreste la `V_ID_VANZARE` (`?@poDate.nid_vanzare`), omite `V_CURSOR_VERIFICARE` | pozitional | +| ROACONT/ROAGEST/ROAACNPRO/ROACONTRACTE (COMUN, grup identic) | `COMUN\clase\ofacturare.vc2:14130` (+ `:18124`) | 16 din 17, idem | pozitional | +| ROAIMOB (COMUN) | `COMUN\clase\ofacturare.vc2:14124` (+ `:18118`) | 16 din 17, idem | pozitional | +| ROAAUTO (COMUN) | `COMUN\clase\ofacturare.vc2:14129` (+ `:18151`) | 16 din 17, idem | pozitional | +| ROAGEST (implementare proprie) | `Programe\ofactureaza.prg:307` | 16 din 17, idem (`?poDate.nid_vanzare` e ultimul bind) | pozitional | + +**Anomalie semnalata, in afara scopului cerut dar relevanta pentru risc general pe pachet**: +toate sitele active de apel pentru `scrie_factura2` — in toate cele 7 produse, in ambele +implementari (COMUN si ROAGEST proprie) — transmit **16 din cele 17 parametri declarati**, +omitand complet ultimul parametru `V_CURSOR_VERIFICARE OUT pack_facturare.cursor_facturare`, care +**nu are `DEFAULT`** (parametrii `OUT` nu pot avea `DEFAULT` in PL/SQL). Verificat identic in +ambele exporturi disponibile (`ff_2026_08_06_10` si `ff_2026_08_09_01`), deci nu e o modificare de +ultima ora. **NEVERIFICAT** cum functioneaza efectiv acest apel in productie (nu am acces la DB +live) — posibile explicatii: (a) `goExecutor.oExecute(lcSql, lcCursorVerificare)` are o logica +proprie de completare/legare a cursorului de iesire care nu se vede din text, (b) parametrul are +de fapt un comportament tolerat de driver-ul Oracle folosit, sau (c) e un bug preexistent, +netestat de multa vreme pe acest cod-cale. Nu are legatura cu schimbarea propusa la +`contabilizeaza_articol` (nu se ating parametrii lui `scrie_factura2`), dar merita un test manual +separat inainte de a presupune ca "toate caile prin pachet functioneaza azi fara eroare". + +### 1.3 `scrie_nota` / `scrie_nota_import` — fals pozitiv de cautare + +`scrie_nota_import` gasit in ROACONT (`Clase\oactualizari.vc2:920`, `Programe\ocont2003.prg:785`, +`Programe\oproceduri_actualizari.prg:189`, `Programe\oproceduri_inchidere.prg:167,335,524`) si in +ROAGEST (`Programe\inchidere_k.prg:192,257,373,381`) **NU este** functia `pack_facturare.scrie_nota` +din pachet — e o **procedura VFP locala**, definita in +`ROAFACTURARE\COMUN\programe\ooperatii_comune.prg:1081` (`PROCEDURE scrie_nota_import(...)`), +folosita pentru import de note contabile, fara nicio legatura cu `PACK_FACTURARE`. Cautarea directa +`pack_facturare\.scrie_nota\b` in tot arborele VFP a dat **zero rezultate** (sectiunea 0). + +### 1.4 `descarca_gestiune` + +Zero apeluri externe gasite in cod VFP (cautare directa `pack_facturare\.descarca_gestiune`, +0 rezultate in toate cele 7 produse). Pachetul are doua supraincarcari (liniile 752 si 773), +probabil folosite doar intern (apelate din `contabilizeaza_articol` conform comentariului de la +linia 1466: `-- facturare_articole2, contabilizeaza_articol > descarca_gestiune`) — +**NEVERIFICAT** linie-cu-linie in restul corpului pachetului (17217 linii; nu am parcurs tot +corpul, doar semnaturile si comentariile relevante), dar nimic din cautarea externa VFP le atinge. + +## 2. Duplicarea fisierelor `COMUN\` + +Comparatie pe continut (MD5), nu pe nume, pentru cele 3 fisiere unde s-au gasit apeluri, in cele +7 copii `COMUN\` (ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO): + +| fisier | rezultat | +|---|---| +| `clase\ofacturare.vc2` | **4 variante distincte**: ROAFACTURARE unic; {ROACONT, ROAGEST, ROAACNPRO, ROACONTRACTE} identice intre ele; ROAIMOB unic; ROAAUTO unic | +| `programe\ofacturare_stoc.prg` | **identic byte-cu-byte in toate cele 7** (un singur MD5) | +| `programe\ooperatii_comune.prg` | identic in 6 din 7; **ROAIMOB are o varianta diferita** | + +Pentru `ofacturare.vc2`, desi hash-urile difera intre cele 4 grupuri (fisierul are ~19000 de linii +si diferente in alte zone), **structura si continutul exact al apelurilor catre +`adauga_articol_factura` / `scrie_factura2` sunt identice cuvant-cu-cuvant** intre toate cele 4 +variante — doar offset-ul de linie difera (ex. apelul `adauga_articol_factura` e la linia 14069 in +ROAFACTURARE, 13853 in grupul ROACONT/ROAGEST/ROAACNPRO/ROACONTRACTE, 13847 in ROAIMOB, 13853 in +ROAAUTO — text identic, verificat prin grep pe fiecare variant separat). Deci pentru scopul acestei +schimbari: **e acelasi sit de apel duplicat de 7 ori** (nu 7 implementari independente care ar +putea diverge in reactia la parametri noi), plus doua implementari suplimentare, independente si +reale, in ROAGEST (`Programe\ofactureaza.prg`) si ROAACNPRO/ROAAUTO +(`Programe\proceduri_acnpro.prg` / `Programe\oproceduri_devize.prg`, pentru varianta `_deviz`). + +`ofacturare_stoc.prg` (contine apelul catre `adauga_articol_factura_stoc`) e literalmente **acelasi +fisier**, deci 1 singur loc de verificat, nu 7. + +## 3. Cine emite efectiv facturi prin acest pachet + +Pe baza apelurilor gasite (nu doar a prezentei fisierului `COMUN\` — care exista in toate 7, dar +nu inseamna ca produsul chiar il foloseste activ pentru facturare): + +| produs | ajunge la `pack_facturare` pentru facturare? | dovada | +|---|---|---| +| ROAFACTURARE | **Da** | `adauga_articol_factura` + `scrie_factura2` in `COMUN\clase\ofacturare.vc2` | +| ROACONT | **Da** | acelasi cod COMUN (grup identic) | +| ROAGEST | **Da**, cu 2 cai | codul COMUN identic + implementare proprie in `Programe\ofactureaza.prg` (posibil cod mai vechi/alternativ, coexista) | +| ROAIMOB | **Da** | varianta proprie de `ofacturare.vc2`, dar acelasi tipar de apel | +| ROAACNPRO | **Da**, cu 2 cai | codul COMUN (grup identic) + `adauga_articol_factura_deviz` in `Programe\proceduri_acnpro.prg` | +| ROACONTRACTE | **Da** | codul COMUN (grup identic) — nu are cale proprie suplimentara gasita | +| ROAAUTO | **Da**, cu 2 cai | varianta proprie de `ofacturare.vc2` + `adauga_articol_factura_deviz` in `Programe\oproceduri_devize.prg` | + +Niciun produs din cele 7 nu pare sa aiba `COMUN\clase\ofacturare.vc2` ca fisier mort — toate cele +7 au si `Programe\` propriu (in afara de COMUN) care instantiaza clasa relevanta (NEVERIFICAT +exhaustiv ca fiecare produs chiar *instantiaza si ruleaza* clasa din `ofacturare.vc2` la runtime — +verificarea s-a facut pe prezenta apelului in sursa, nu pe flux de executie live). **Toate cele 7 +produse sunt in aria de regresie** pentru orice modificare de pachet care afecteaza +`adauga_articol_factura*` sau `scrie_factura2` — nu doar ROAFACTURARE. + +## 4. Verdict de risc + +**Pentru `contabilizeaza_articol` insusi** (obiectul cerut al schimbarii): risc **practic zero**. +Cei 3 apelanti sunt toti interni pachetului, toti pozitionali cu un singur argument identic cu +semnatura actuala (`detalii_articol VANZARI_DETALII_TEMP%ROWTYPE`). Adaugarea de parametri noi +`DEFAULT NULL` la coada nu schimba niciunul dintre aceste 3 apeluri — nu necesita nicio modificare +in cele 3 sit-uri, indiferent de produs. Nu exista niciun apel extern (nici alt pachet PL/SQL, nici +VFP din niciun produs) care sa poata fi afectat, pentru ca nu exista niciun apel extern, punct. + +**Pentru pachetul `PACK_FACTURARE` in ansamblu**, daca schimbarea reala atinge si alte proceduri +publice (nu doar `contabilizeaza_articol`): +- Tiparul "adauga parametri `DEFAULT NULL` la coada, apelantii pozitionali raman neschimbati" + **are deja precedent activ** in codul curent: `adauga_articol_factura` (V_TAXCODE, V_LOT) si + `adauga_articol_factura_deviz` (4 parametri finali cu DEFAULT) sunt deja apelate de unii + producatori omitand parametrii finali optionali (ROAGEST, ROAAUTO). Deci genul de schimbare + propus e sigur *daca* regula "toti parametrii noi sunt strict la coada si toti au DEFAULT" se + respecta — ceea ce e exact ipoteza declarata pentru `contabilizeaza_articol`. +- **Singurul risc real identificat in tot pachetul** nu vine din schimbarea propusa, ci e o + anomalie preexistenta, independenta: apelurile la `scrie_factura2` (in toate cele 7 produse) omit + deja parametrul final `V_CURSOR_VERIFICARE` (OUT, fara DEFAULT posibil). Daca cineva "repara" acea + nepotrivire ca parte a aceleiasi lucrari de mentenanta pe pachet, *acolo* ar trebui atinsi toti + apelantii din sectiunea 1.2 — dar asta e o schimbare diferita de cea descrisa (parametri noi la + `contabilizeaza_articol`), nesolicitata explicit aici. O semnalez ca sa nu fie confundata cu + "toti apelantii raman neschimbati" pentru intregul pachet, daca scopul lucrarii se extinde. +- Nu s-a gasit niciun apel cu named notation (`=>`) nicaieri in cele 7 produse pentru functiile + cerute — deci nu exista risc de reordonare-parametri-pe-nume la adaugarea de parametri noi. + +**Lista de produse pe care trebuie rulata regresia** (din sectiunea 3): toate cele 7 — +ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO — pentru ca toate emit +efectiv facturi prin acest pachet, chiar daca modificarea la `contabilizeaza_articol` insusi nu +impune, teoretic, nicio schimbare de cod la niciunul dintre ele. diff --git a/docs/cercetare/verif_baza_vie_cont_venit.md b/docs/cercetare/verif_baza_vie_cont_venit.md new file mode 100644 index 0000000..0b547fe --- /dev/null +++ b/docs/cercetare/verif_baza_vie_cont_venit.md @@ -0,0 +1,250 @@ +# Verificari pe baza de date vie — reteta contului de venit (#13, J-quater) + +Rulat: **10.08.2026**, schema `MARIUSM_AUTO` pe `ROA_CENTRAL` (`10.0.20.121:1521`, `SERVICE_NAME=ROA`), +client `D:\ROA\instantclient_19_18\sqlplus.exe`. **Doar `SELECT`** — nicio scriere, nicio modificare de +structura. Raspunde la punctul 2 din „Ce ramane deschis" al `docs\handoff_13_formular_unificat.md`. + +## Verdict, in trei randuri + +**Pasul 1 al retetei (derivarea `SCC`) e sigur: zero ambiguitate, zero cazuri de cont de venit gol pe +articole reale.** **Pasul 2 (gasirea unei politici cu acel `SCC`) reuseste pentru 3 din 7 conturi +candidate si eșueaza pentru 4** — dar nu asa cum prevedea planul: **`704` are 19 politici valabile azi, +nu zero**, iar cele care lipsesc sunt `702`, `703`, `711`, `7018`. **Riscul serios nu e ambiguitatea, ci +`PTVA`-ul notei** — singura nota cu `SCC=707` are `PTVA=5`, deci alegerea politicii dupa `SCC` importa si +o cota de TVA care poate fi greșita. + +## Doua corectii la planul si handoff-ul rundei 8 + +| Ce spune planul (J-quater, „Costurile") | Ce arata baza vie | +|---|---| +| „pentru **704** nu s-a gasit nicio nota configurata, deci trebuie creata o data din ecranul de configurare note contabile" | **FALS.** `704` e cel mai bine acoperit cont: **4 note de vanzari** (`NOTA 1`, `COMISION INTERMEDIERE`, `SERVICII VALUTA`, `SERVICII TRANSPORT`) si **25 de politici** (19 valabile azi). `NOTA 1` — nota implicita a majoritatii politicilor — are exact `SCC=704`. **Pasul de creare a notei pentru 704 se scoate din plan.** | +| „`CONT_VENIT` are date reale (**20 de conturi**, migrare 2023)" | **Imprecis.** Tabelul are **41 de randuri active**; exact **20** au `CONT_VENIT` populat. Restul 21 sunt conturile de ajustari (`391`…`398`, toate cu `CONT_CHELT=681`) — vezi mai jos de ce nu conteaza. | + +Ce **se confirma** din plan: `CONT_VENIT` **n-are consumatori** (nu am verificat aici — a fost stabilit pe +cod in runda 8); lantul invers e realizabil; ambiguitatea „mai multe politici cu acelasi `SCC`" e reala. + +## 1. DDL-ul lui `CORESP_CONT_VENCHELT` (cerut explicit in handoff) + +| # | Coloana | Tip | Null | +|---|---|---|---| +| 1 | `ID_CCV` | `NUMBER(10,0)` | N | +| 2 | `CONT` | `VARCHAR2(4)` | Y | +| 3 | `CONT_CHELT` | `VARCHAR2(4)` | Y | +| 4 | `CONT_VENIT` | `VARCHAR2(4)` | Y | +| 5 | `STERS` | `NUMBER(1,0)` | N | +| 6 | `DATAORAS` | `DATE` | Y | +| 7 | `CONT_APROVIZIONARE` | `VARCHAR2(4)` | Y | +| 8 | `CONT_DIFERENTE` | `VARCHAR2(4)` | Y | + +Constrangeri: **doar** `PK_CORESP_CONT_VENCHELT` pe `ID_CCV`, plus `NOT NULL` pe `ID_CCV` si `STERS`. +**Nu exista unique pe `CONT`** — deci nimic in schema nu impiedica doua randuri pe acelasi cont de +gestiune. In date insa **nu exista niciun `CONT` duplicat**, deci pasul 1 al retetei e determinist azi. +Fragilitatea e de tip „merge pana nu merge": daca cineva adauga un al doilea rand pe `301`, derivarea +devine ambigua fara ca nimic sa semnaleze. + +## 2. Continutul relevant al tabelului — cele 20 de randuri cu cont de venit + +| `CONT` (gestiune) | `CONT_CHELT` | **`CONT_VENIT`** | +|---|---|---| +| `231` | `212` | `707` | +| `301`, `302`, `3021`–`3028`, `303` | `601`, `602`, `6021`–`6028`, `603` | **`707`** (13 randuri) | +| `331`, `332` | `711` | `711` | +| `341` | `711` | `702` | +| `345`, `348` | `711` | `7015` | +| `346` | `711` | `703` | +| `361` | `711` | `7018` | +| `371` | `607` | `707` | +| `381` | `608` | `707` | + +Cele 21 de randuri fara `CONT_VENIT` sunt `391`, `392`, `3921`, `3922`, `393`, `394`, `3941`, `3945`, +`3946`, `395`, `3951`–`3958`, `396`, `397`, `398` — toate cu `CONT_CHELT=681`, adica **ajustari pentru +depreciere**, nu conturi de gestiune pe care sta marfa. + +**Si contează**: `articole_pe_cont_cu_venit_gol = 0`. Niciun articol activ din `NOM_ARTICOLE` nu are +`CONT`-ul pe un rand cu `CONT_VENIT` gol. **Deci ramura „cont de venit derivat gol" nu are cazuri reale** +— trebuie tratata defensiv, dar nu e un scenariu de acoperit in UX. + +## 3. Interogarea inversa — rezultatul pe fiecare `SCC` candidat + +```sql +with cand as (select column_value scc from table(sys.odcivarchar2list('707','711','702','703','7015','7018','704'))) +select c.scc, count(distinct p.id_pol) pol, + count(distinct case when nvl(p.datai,date '1900-01-01')<=trunc(sysdate) + and nvl(p.datas,date '2999-12-31')>=trunc(sysdate) + then p.id_pol end) pol_valabile_azi, + count(distinct nc.id_set) seturi, count(nc.id_note) nc_randuri + from cand c + left join note_contabile nc on nc.scc = c.scc + left join crm_note_vanzari nv on nv.id_set = nc.id_set and nvl(nv.sters,0)=0 + left join crm_politici_preturi p on p.id_nota = nv.id_nota and p.sters=0 + group by c.scc; +``` + +| `SCC` | Politici | **Valabile azi** | Seturi cu acest `SCC` | Randuri `NOTE_CONTABILE` | Verdict pentru pasul 2 | +|---|---|---|---|---|---| +| **`704`** | 25 | **19** | 7 | 28 | **reuseste**, cu ambiguitate mare | +| **`707`** | 4 | **4** | 5 | 8 | **reuseste**, ambiguitate mica | +| **`7015`** | 1 | **1** | 1 | 1 | **reuseste, caz ideal** | +| `711` | 0 | 0 | 4 | 4 | **eșuează** — note exista, dar **nicio politica** nu le foloseste | +| `702` | 0 | 0 | 3 | 3 | **eșuează** — idem | +| `703` | 0 | 0 | 3 | 3 | **eșuează** — idem | +| `7018` | 0 | 0 | **0** | **0** | **eșuează total** — nu exista nici macar nota | + +Citit pe conturile de gestiune: reteta merge pentru marfa (`301`–`303`, `371`, `381`, `231` → `707`) si +pentru produsele din `345`/`348` (→ `7015`), plus pentru orice cade pe fallback-ul `704`. **Nu merge** +pentru `331`/`332` (→ `711`), `341` (→ `702`), `346` (→ `703`), `361` (→ `7018`) — adica **producția +neterminată, semifabricatele, produsele reziduale si ambalajele**. + +Distinctia care conteaza la `711`/`702`/`703`: **nota exista, doar politica lipseste.** Costul de +configurare e „ataseaza nota existenta unei politici", nu „creeaza nota de la zero". Doar `7018` cere +si nota noua. + +## 4. Riscul pe care planul nu l-a vazut: `PTVA` si `IN_VALUTA` de pe nota + +Cele 7 note de vanzari active, cu nota contabila atasata: + +| `ID_NOTA` | Denumire | `ID_SET` | `SCD` | **`SCC`** | **`PTVA`** | `CU_TVA` | **`IN_VALUTA`** | +|---|---|---|---|---|---|---|---| +| 1 | `NOTA 1` | 251023 | `4111` | **704** | **21** | 1 | 0 | +| 2 | `DISCOUNT` | 251024 | `667` | 4111 | 21 | 1 | 0 | +| 3 | `COMISION INTERMEDIERE` | 251025 | `4111` | **704** | **0** | 1 | **1** | +| 4 | `SERVICII VALUTA` | 251026 | `4111` | **704** | **0** | 1 | **1** | +| 5 | `VANZARE MARFA` | 251027 | `4111` | **707** | **5** | 1 | 0 | +| 6 | `PRODUCTIE` | 251028 | `4111` | **7015** | 21 | 1 | 0 | +| 7 | `SERVICII TRANSPORT` | 251029 | `4111` | **704** | 21 | 1 | 0 | + +> **RETRAS — verificat pe cod si infirmat.** Ingrijorarea de mai jos, formulata la prima citire a acestor +> date („`PTVA=5` pe singura nota cu `SCC=707` produce TVA greșit"), **nu se susține**. +> `docs\cercetare\s10_pret_rederivat.md`, „Completare: nota contabila a politicii" p. 2: `cursor_articol` +> (`PACK_FACTURARE:7218-7271`) **nu selecteaza `D.PTVA`**; cota folosita e +> `detalii_articol.proc_tvav * 100 - 100` (`:7464`), din articol / document. Coloana e moarta pentru +> `contabilizeaza_articol`. Iar `IN_VALUTA=1` pe un document in lei nu da eroare si nu strica suma — +> doar populeaza redundant `ACT_TEMP.SUMA_VAL` la curs 1. **Deci criteriul de selectie NU trebuie extins +> cu `PTVA` / `IN_VALUTA`.** Se pastreaza textul de mai jos doar ca urma a raționamentului; nu se citeaza +> ca risc. + +Ce **rămâne** ca risc real din aceste date, si e mai grav: `cursor_articol` face `LEFT JOIN +NOTE_CONTABILE ON C.ID_SET = D.ID_SET` fara `ROWNUM` si fara agregare, iar bucla care il consuma executa +`scrie_nota` **si** `descarca_gestiune` o data **pentru fiecare rand al setului**, cu pretul si cantitatea +intregi de fiecare data. Deci un set cu N randuri = **venit inregistrat de N ori si gestiune descarcata de +N ori**. Cele 7 note de vanzari active au exact un rand fiecare — dar **30 din cele 40 de seturi din baza +au mai multe, cu maxim 30**. Vezi punctul 6.1 mai jos. + +Ce planul enunta drept **avantaj** al retetei — „politica reala aduce si `CU_TVA`, `IN_VALUTA`, +`EXPLICATIE`, `ASCD`, `ASCC`" — se confirma ca argument, cu nuanta ca `ASCD`/`ASCC` au oricum fallback +automat din grupul de utilizatori, deci nu ele justifica alegerea. + +Textul original al ingrijorarii, pastrat pentru trasabilitate: + +- **`707` are o singura nota, si aceea cu `PTVA=5`.** Pentru marfa (majoritatea articolelor de gestiune) + singurul rezultat posibil al pasului 2 e nota cu TVA 5%. +- **Doua din cele patru note cu `SCC=704` au `IN_VALUTA=1`** (`SERVICII VALUTA`, `COMISION INTERMEDIERE`), + deci o potrivire pe `SCC` poate ateriza pe ele pe un document in lei. + +Colateral util pentru **S4c**: exista deja o nota de discount configurata — `id_nota=2`, `SCD=667` / +`SCC=4111`. Nu e o eroare de configurare (explica randul `SCC=4111` din agregat), e nota folosita pentru +discount. De verificat la S4c daca discountul pe linie trece prin ea. + +## 5. Efectul colateral al pasului 3 — politica **este** o lista de preturi + +Pasul 3 al retetei insereaza articolul in politica alesa, prin `pack_preturi.adauga_politica_pret_art`. +Politicile candidate, cu numarul de articole pe care le contin azi: + +| `SCC` | `ID_POL` | Nume | Articole | +|---|---|---|---| +| `7015` | 41 | `STOC PRODUSE` | **6310** | +| `707` | 39 | `LISTA PRETURI LEI` | **909** | +| `707` | 40 | `LISTA PRETURI CU TVA EURO` | 2 | +| `707` | 47 / 62 | `DEPOZIT ELI` / `DEPOZIT ELI_31/07/2025 11:07:25` | 3 / 3 | +| `704` | 2 | `LISTA 2` | 19 | +| `704` | 34 | `LISTA 2 - EURO` | 12 | +| `704` | 36 | `SERVICE AUTO` | 9 | +| `704` | 35 | `LISTA STANDARD EURO` | 8 | +| `704` | 9 | `CHIOSC` | 5 | +| `704` | 6, 12, 13, 14, 15, 16 | `LISTA DE PROBA`, `S5 ZILNIC`, `S6 WEEKEND SEZON`, `S7 CRACIUN`, `S3 PASTE`, `S3 1MAI` | 4 fiecare | +| `704` | 65, 66 | `TRANSPORT`, `SERVICII INFORMATICE` | 1 fiecare | +| `704` | 26, 27, 28, 29, 30, 31 | `S6 ZILE IARNA`, `S8 REVELION`, `S1 IARNA2`, `S1 IARNA2 WEEKEND`, `S2 PRIMAVARA`, `S7 CRACIUN` | **0** | + +**Astea sunt liste de preturi reale, cu nume de client si de sezon.** Daca reteta alege `LISTA PRETURI +LEI` sau `STOC PRODUSE` pentru un articol adaugat ad-hoc pe factura, articolul **apare de acum in lista +de preturi a acelui context**, cu pretul cu care a fost facturat o data. Asta e efectul pe care planul il +descrie drept „politica tehnica populata automat" — dar **numai daca politica e chiar tehnica**. +Alegerea unei politici comerciale existente **poluează o lista de preturi de producție**. + +Consecinta de proiectare, de dus in J-quater: pasul 2 nu trebuie sa caute *orice* politica cu `SCC`-ul +potrivit, ci **o politica tehnica dedicata, una per `SCC`**, creata anume — modelul +`gnId_pol_pret_stoc` extins de la o politica la sapte. Cele sase politici cu **0 articole** arata ca o +politica goala e o stare acceptata de produs. + +Detaliu care ajuta: **majoritatea politicilor cu `SCC=704` trimit la aceeasi `id_nota=1`**. Deci +ambiguitatea celor 19 politici e ambiguitate de **lista de preturi**, nu de rezultat contabil — toate dau +acelasi `704` / `PTVA=21` / lei. Cu atat mai mult, criteriul de alegere trebuie sa fie „care lista de +preturi vreau sa murdaresc", si raspunsul corect e „niciuna dintre cele existente". + +## 6. Fragilitati masurate, de trecut in Riscuri + +1. **`ID_SET` cu mai multe randuri dubleaza venitul si descarcarea de gestiune.** Pe cele 7 seturi + folosite de politici, fiecare are exact **1** rand `NOTE_CONTABILE`. Dar in tabel sunt **40 de seturi, + din care 30 au mai multe randuri**, cu maxim **30 de randuri pe set**. Confirmat pe cod + (`s10_pret_rederivat.md`, Completare p. 1): `cursor_articol` face `LEFT JOIN ... ON C.ID_SET = + D.ID_SET` **fara `ROWNUM`, fara agregare, fara filtru**, iar bucla `:7393-7542` executa `scrie_nota` + **si** `descarca_gestiune` o data per rand, **cu aceeasi cantitate si acelasi pret intreg**, fara + distributie (`ORDINE` nu apare in tot pachetul). Deci pasul 2 al retetei trebuie sa garanteze **un + singur rand pe setul ales**, nu doar un rand cu `SCC`-ul potrivit. Conditia care face reteta sigura + azi e o coincidenta a datelor, nu o garanție a codului sau a schemei. +2. **Doua politici active fara `ID_NOTA`**: `32 HOTEL TAXE` si `33 HOTEL CAZARE` (ambele valabile + 01.01.2010–31.12.2099, `id_valuta=3`). Un articol pe una din ele are `id_pol` valid dar **fara nota** — + comportament neverificat, intrebare trimisa agentului pe `PACK_FACTURARE`. E o gaura care **exista + deja azi**, independenta de #13. +3. **Fara unique pe `CORESP_CONT_VENCHELT.CONT`** — vezi punctul 1. + +## 7. Distributia articolelor — cat de mult conteaza fiecare ramura a deciziei 27 + +`NOM_ARTICOLE`, 6430 articole active: + +| Prima cifra din `CONT` | Articole | Conturi distincte | Ramura deciziei 27 | +|---|---|---|---| +| `3xx` | **6125** | 9 | gestionabil → `CORESP_CONT_VENCHELT` | +| `8xx` (toate `8035`) | 26 | 1 | nu e 6xx/7xx, nu e in `CORESP` → **`704`** | +| `6xx` | 22 | 5 | `NOM_ARTICOLE.CONT` ca atare | +| `7xx` | 20 | 6 | `NOM_ARTICOLE.CONT` ca atare | +| `1xx` | 4 | 2 | → `704` | +| `4xx` | 3 | 2 | → `704` | +| `0xx` | 1 | 1 | → `704` | +| `2xx` | 1 | 1 | → `704` | +| **fara `CONT`** | **228** | — | → `704` | + +Conturi de pe articole care **nu** sunt 6xx/7xx si **nu** apar in `CORESP_CONT_VENCHELT.CONT`, deci cad pe +`704`: `8035` (26 articole), `123` (3), `446` (2), `111`, `409`, `0`, `212`, `37` (1 fiecare). + +**Ramura dominanta e de departe cea gestionabila** — 6125 din 6430. Iar ea trimite in majoritate spre +`707`, adica spre cazul cu `PTVA=5`. Cele 228 de articole fara cont si cele 26 pe `8035` fac fallback-ul +`704` un caz real, nu teoretic — si acolo reteta functioneaza. + +Nota: `212` apare pe un articol, dar in `CORESP_CONT_VENCHELT` figureaza ca `CONT_CHELT` (pe randul +`231`), nu ca `CONT`. Deci cade corect pe `704` prin regula deciziei 27. + +## Ce nu s-a putut stabili aici, si de ce + +- **Ce face `contabilizeaza_articol` cu `PTVA` / `CU_TVA` / `IN_VALUTA` ale notei** — e intrebare de cod, + nu de date. Trimisa agentului deja incarcat in `PACK_FACTURARE`; raspunsul intra in + `docs\cercetare\s10_pret_rederivat.md`, sectiunea „Completare: nota contabila a politicii". **Pana + atunci pasul 2 al retetei nu se considera validat.** +- **Daca `CONT_VENIT` chiar n-are consumatori** — stabilit pe cod in runda 8, nu reverificat pe date. +- **Comportamentul politicii fara nota** — idem, intrebare de cod, trimisa. +- Verificarile sunt pe **schema de dezvoltare** `MARIUSM_AUTO`. Datele sunt reprezentative (liste de + preturi cu nume reale, 6430 articole), dar **o baza de client poate avea alta configurare de note** — + in special poate avea deja politici pe `711` / `702` / `703`. Concluziile pe **structura** (lantul, + absenta unique-ului, `ID_SET` multi-rand) sunt insa independente de date. + +## Reproducere + +Cele trei scripturi rulate, in ordine: descoperire DDL/coloane · interogarea inversa pe `SCC` · +efecte colaterale si distributii. Interogarea-cheie e citata integral la punctul 3. Rulare: + +```powershell +& 'D:\ROA\instantclient_19_18\sqlplus.exe' -L -S 'MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL' '@fisier.sql' +``` + +SQL-ul se scrie in fisier **ASCII**, nu prin pipe din PowerShell (BOM-ul UTF-16 sparge parserul), si cu +`linesize 32767` — vezi `COMUN\docs\oracle_export.md`. diff --git a/docs/cercetare/verif_goluri_ntip_aviz.md b/docs/cercetare/verif_goluri_ntip_aviz.md new file mode 100644 index 0000000..d38ed9a --- /dev/null +++ b/docs/cercetare/verif_goluri_ntip_aviz.md @@ -0,0 +1,325 @@ +# Verificare adversariala — golul ntip=4 si "ramurile de aviz" (contabilizeaza_articol) + +Verifica raportul `docs/cercetare/gol_ntip4_factura_din_avize.md`. Rol: incercare de infirmare, +nu confirmare. Toate citatele de mai jos sunt re-verificate direct in aceasta runda pe +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii) si +pe `COMUN\clase\ofacturare.vc2` (ambele copii, `frm_facturare_articole` ~13967-14500 si +`frm_facturare_articole2` ~18003-18500). Status: **TERMINAT**. + +## Corectie structurala prealabila — cei "3 apelanti" ai `contabilizeaza_articol` sunt gresit numiti + +Toate rapoartele anterioare (`canal_cont_venit_fara_politica.md:67`, `nota_contabila_fara_politica.md:103`, +si implicit raportul verificat) numesc cei 3 apelanti interni `scrie_factura2`, +**`scrie_factura_avize_retur`**, `scrie_aviz_retur`. **Gresit pentru al doilea.** Verificare directa +pe granitele reale (`PROCEDURE ...` / `END ...`): + +``` +ff_...:6264 PROCEDURE scrie_factura_avize_retur(... ff_...:6657 END scrie_factura_avize_retur; +ff_...:6692 PROCEDURE scrie_factura_avize(... ff_...:7058 END scrie_factura_avize; +ff_...:7085 PROCEDURE scrie_aviz_retur(... ff_...:7171 END scrie_aviz_retur; +``` + +Apelul de la `ff_...:6858` (`pack_facturare.contabilizeaza_articol(tab_detalii(i));`, in interiorul +`IF articole_aviz(j).custodie = 1 THEN`) cade **intre 6692 si 7058** — deci apartine lui +**`scrie_factura_avize`** (fara `_retur`), nu lui `scrie_factura_avize_retur` (care se termina la +6657, cu 200+ linii inainte). `scrie_factura_avize_retur` **nu cheama deloc** +`contabilizeaza_articol` — corpul ei (6264-6657) insereaza direct in `VANZARI_DETALII_TEMP` din +`VANZARI_DETALII` (randurile avizului sursa deja existente), fara sa treaca prin +`adauga_articol_factura` sau `contabilizeaza_articol`. + +**De ce conteaza**: `scrie_factura_avize` e exact handler-ul Oracle pentru `ntip=4` ("facturare din +aviz") — apelat din VFP la `ofacturare.vc2:14318` (`Case poDate.Tip = 4`). Deci al doilea apel real +catre `contabilizeaza_articol` **e in interiorul propriului handler `ntip=4`** (pentru randurile +`custodie=1`), nu intr-un flux separat "avize+retur". Cei 3 apelanti reali sunt: + +| Apel | Procedura reala | Cand se ajunge la ea din VFP | +|---|---|---| +| `ff_...:6141` | `scrie_factura2` | `Case Inlist(poDate.Tip,3,21,25,28,42,47)` sau `Otherwise` (`ofacturare.vc2:14332,14361`, identic la `:18330,18361`) — calea principala pentru aproape toate tipurile, inclusiv avizele | +| `ff_...:6858` | `scrie_factura_avize` | `Case poDate.Tip = 4` (`:14301,14318`) — doar pentru randurile `custodie=1` ale unei facturi din aviz | +| `ff_...:7140` | `scrie_aviz_retur` | **Niciun apel gasit in `COMUN\` din VFP** (`Grep` pe tot `COMUN` si pe tot proiectul — zero rezultate in cod, doar in rapoarte). Posibil cod mort/apelat din alt produs; nu afecteaza verdictele de mai jos, pentru ca `scrie_factura2` singura e suficienta sa dovedeasca A1a. | + +Corectia nu schimba verdictul general al A1a (avizele *ajung* la `contabilizeaza_articol`), dar +schimba **prin ce drum** — relevant pentru oricine ar urma sa scrie cod pe baza acestor rapoarte. + +## A1. Ramurile de AVIZ raman atinse si ramura noua le-ar scrie SCD gresit + +**Verdict: CONFIRMAT, dar mai ingust decat sustine raportul.** + +### A1a. Ajunge `contabilizeaza_articol` sa fie apelata pe un AVIZ? + +**DA, confirmat**, prin `scrie_factura2`. Ruta VFP (`ofacturare.vc2:14282-14389`, identic la +`:18enable...`, verificata in ambele copii ale formularului): + +``` +Case poDate.eProforma = 1 -> scrie_proforma +Case poDate.Tip = 4 -> scrie_factura_avize && facturare din aviz +Case Inlist(poDate.Tip,3,21,25,28,42,47) -> scrie_factura2 && din comenzi / avize din comenzi +Otherwise -> scrie_factura2 && tot restul (incl. 29, 22, 24, 27...) +``` + +In interiorul `scrie_factura2` (`ff_...:6072-6142`), CASE-ul propriu **intercepteaza unele `ntip` +inainte** de a ajunge la `contabilizeaza_articol`: + +``` +WHEN ntip IN (23,25,30,41) THEN transfera_articol(...) -- NU contabilizeaza_articol +WHEN ntip IN (42,47) THEN descarca_gestiune(...) direct -- NU contabilizeaza_articol +WHEN ntip IN (2,6,52) AND id_rata<>0 THEN contabilizeaza_rata(...) +ELSE contabilizeaza_articol(...) -- aici ajung 28, 29, 21, 22, 24, 27, 3, ... +``` + +Deci `ntip=28` si `ntip=29` **chiar ajung** la `contabilizeaza_articol` (prin `scrie_factura2`, ramura +`ELSE`) — asta confirma miezul A1a. Dar `ntip IN (23,25,30,41,42,47)`, desi trec prin `scrie_factura2` +(sunt in lista VFP `Inlist(3,21,25,28,42,47)`), **nu ajung niciodata** la `contabilizeaza_articol` — +sunt deviate mai devreme, in CASE-ul propriu al lui `scrie_factura2`, spre `transfera_articol`/ +`descarca_gestiune`. Vezi corectia la sectiunea 3 (tabelul per-`ntip`). + +### A1b. Poate un articol fara politica sa fie adaugat pe un aviz? + +**DA, confirmat structural**, pe doua cai distincte in `adauga_articol_factura` (`ff_...:5052-5203`): + +- `WHEN ntip IN (3,21,28,42,47)` (`:5053-5078`): cauta in `COMENZI_ELEMENTE` dupa + `ID_COMANDA + ID_ARTICOL + PRET` — **fara nicio referinta la `V_ID_POL`** in `WHERE`. Un articol + fara politica poate gasi potrivire aici daca exista elementul de comanda corespunzator (comanda + + articol + pret identice), indiferent de politica. **Fara handler `EXCEPTION`** — la fel ca ramura + `ntip=4`, dar aici absenta handler-ului nu conteaza pentru `id_pol`, pentru ca filtrul nu-l + foloseste. +- `ELSE` (`:5187-5203`, bucket-ul implicit pentru orice `ntip` neacoperit de ramurile speciale — + include `29` si restul avizelor "simple"): **nicio interogare legata de politica** — ia direct + `V_PRET_TEMP`/`V_ID_VALUTA_TEMP`/`V_PRETURI_CU_TVA_TEMP`/`V_IN_STOC_TEMP` (valorile deja calculate + de VFP), plus o singura `SELECT` pe `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` (nimic legat de + `id_pol`). **Zero obstacol** pentru un articol fara politica pe aceasta ramura. + +Nu am gasit (si nu am cautat exhaustiv, la fel ca raportul original) formularul VFP concret care ar +adauga azi un articol ad-hoc fara politica pe un aviz — premisa ramane, ca si in raportul original, +mostenita din proiectul #13 (`canal_cont_venit_fara_politica.md`), nu re-derivata aici. Ce am putut +confirma exhaustiv e partea Oracle: **daca** VFP trimite `V_ID_POL=NULL` pe aceste `ntip`, Oracle nu +pune nicio piedica. + +### A1c. `SCD` in blocul `:7398-7423` — hardcodat sau derivat? + +Citit integral (`ff_...:7398-7423`), confirmat identic cu citatul raportului, **linie cu linie**: + +``` +ff_...:7400-7406 WHEN ntip<=20 OR ntip IN (nTipFacturaHotel,nTipFacturaRestaurant, + nTipNotaPlata,nTipVanzareRetail,48,49,51,52) THEN + V_SCD := crs_rand_articol.scd; -- din politica (NOTE_CONTABILE.SCD) +ff_...:7413-7417 WHEN ntip IN (28,29) THEN + V_SCD := '461'; -- HARDCODAT, aviz catre clienti debitori +ff_...:7418-7422 ELSE + V_SCD := '418'; -- HARDCODAT, aviz generic +``` + +`V_SCC` (linia 7425) vine mereu din `crs_rand_articol.scc` (coloana notei de vanzari), **indiferent +de ramura** — CASE-ul de mai sus decide doar `V_SCD`. Confirmat: hardcodarea exista exact cum spune +raportul, fara offset de linie. + +**Nuanta importanta, omisa de raport**: proiectarea `canal_cont_venit_fara_politica.md:146` (sursa +ramurii noi) **flagheaza deja, printr-o paranteza**, ca "ramurile aviz raman `'418'`/`'461'`, deja +hardcodate azi la `ff_...:7415,7420`, independent de politica" — deci designul **e constient** de +existenta acestor hardcodari si de faptul ca ar trebui pastrate, dar **nu da structura de cod +concreta** (niciun `IF`/`CASE` pe `ntip` in tabelul "Continutul ramurii noi", sectiunea 3) care sa +implementeze efectiv acea pastrare. Raportul verificat prezinta acest lucru ca pe o **descoperire +noua** ("Gol sora... gasit in aceasta cercetare") — de fapt designul deja stia de riscul general, dar +nu-l rezolvase in cod. Concluzia practica ramane aceeasi (ramura noua, asa cum e descrisa azi in +proiectare, chiar ar hardcoda `SCD='4111'` fara conditionare pe `ntip` daca ar fi implementata +literal dupa tabelul din sectiunea 3), dar caracterizarea "gol nou, neconsemnat" e inexacta. + +### Corectie la tabelul per-`ntip` din raport (sectiunea 3) + +Raportul marcheaza `IN (28,29)` **si** "toate celelalte (`21,22,23,24,25,27,30,41,42,47,...`)" ca +"Atinsa: DA". Pe baza rutarii reale prin `scrie_factura2` (A1a de mai sus): + +| `ntip` | Ajunge la `contabilizeaza_articol`? | Motiv | +|---|---|---| +| `28`, `29` | **DA** | `scrie_factura2` -> `ELSE` -> CASE intern `ntip IN(28,29)` -> `SCD='461'` | +| `21`, `22`, `24`, `27`, `3` si alte "otherwise" nespeciale | **DA** (probabil) | cad in `ELSE` al lui `scrie_factura2` -> `ELSE` intern -> `SCD='418'` | +| `23`, `25`, `30`, `41` | **NU** | interceptate in `scrie_factura2` de `WHEN ntip IN(23,25,30,41) THEN transfera_articol(...)` — nu ajung niciodata la `contabilizeaza_articol` prin acest apelant | +| `42`, `47` | **NU** | interceptate de `WHEN ntip IN(42,47) THEN descarca_gestiune(...) direct` — la fel, nu ajung | + +Golul real e deci mai ingust decat "orice aviz": se limiteaza la avizele care cad in ramura `ELSE` a +lui `scrie_factura2` (28, 29, si restul avizelor "simple" neexcluse), nu la `23,25,30,41,42,47`, care +sunt structural neatinse de `contabilizeaza_articol` prin singurul apelant confirmat. (Nu exclud ca +`scrie_aviz_retur`, al carei apelant VFP nu l-am gasit, sa trateze si aceste `ntip` — dar cum nu exista +dovada ca e apelata deloc, nu pot confirma nici infirma pentru ea.) + +## A2. Golul `ntip=46` (`nTipNotaPlata`) — `scrie_nota` sarita azi + +**Verdict: CONFIRMAT.** + +- Constanta: `ff_...:84 nTipNotaPlata VANZARI.TIP%TYPE := 46;` — **exact 46**, verificat direct, nu + presupus. +- Garda: `ff_...:7441 IF pack_facturare.nTip <> pack_facturare.nTipNotaPlata THEN` — inainte de + apelul `scrie_nota` (`:7443-7467`) — citat exact, linie identica cu raportul. +- Poate un `ntip=46` sa primeasca un articol fara politica? **DA**, prin acelasi bucket `ELSE` + (`ff_...:5187-5203`) al `adauga_articol_factura` verificat la A1b — `nTipNotaPlata` nu e in niciuna + din ramurile speciale (`3,21,28,42,47`, `4`, `45`, `V_OPT_FACTURARE=3`), deci cade in `ELSE`, care + nu cere `id_pol` deloc. +- Ajunge la `contabilizeaza_articol`? **DA** — `ntip=46` nu e proforma, nu e `Tip=4`, nu e in + `Inlist(3,21,25,28,42,47)` -> `Otherwise` -> `scrie_factura2` -> nu e in `(23,25,30,41)`/`(42,47)`/ + `(2,6,52)` -> `ELSE` -> `contabilizeaza_articol`. +- Ramura noua (per `canal_cont_venit_fara_politica.md` sectiunea 3, pasul 1 "Apeluri, o singura data + fiecare") descrie apelul `scrie_nota` **fara** sa mentioneze garda `ntip<>nTipNotaPlata` — deci, asa + cum e scrisa azi proiectarea, ar apela necondiționat `scrie_nota` pentru un document `NotaPlata` cu + linie `cont_venit`. Gol real, bine argumentat de raport. + +## A3. `ntip=4` exclus prin `NO_DATA_FOUND` neprins la `adauga_articol_factura` + +**Verdict: CONFIRMAT**, cu toate liniile citate verificate exact, fara offset: + +- `ff_...:5080-5103` — ramura `WHEN ntip=4`, `WHERE A.ID_POL = V_ID_POL` (linia 5096 exact), fara + `EXCEPTION`. Confirmat caracter cu caracter identic cu citatul raportului. +- Semantica SQL `NULL = NULL` -> `UNKNOWN`, niciodata `TRUE`: standard, corect. +- **Ordinea de apel confirmata direct pe VFP**: `do_scrie_articole` (`ofacturare.vc2:13967`) cheama + `initializeaza_date_factura` (seteaza `ntip`), apoi in bucla `Scan` peste `crsfactura` cheama + `adauga_articol_factura` (`:14069-14091`) pentru fiecare rand — **toate acestea ruleaza inainte** de + `Do Case` (`:14282`) care alege `scrie_factura_avize`/`scrie_factura2`/etc. Deci un articol cu + `V_ID_POL=NULL` pe `ntip=4` cade la `adauga_articol_factura` **inainte** ca `scrie_factura_avize` sa + fie chemata — linia nu ajunge niciodata la `contabilizeaza_articol`. Confirma independent + concluzia raportului. +- **Semantica VFP `Nvl(Alltrim(Str(poArt.id_pol)),[NULL])`** (`ofacturare.vc2:14072`, identic + `:18107`): in Visual FoxPro, `Str()` si `Alltrim()` **propaga `.NULL.`** cand primesc `.NULL.` ca + argument (comportament standard, documentat al limbajului — orice functie "null-aware" aplicata pe + `.NULL.` intoarce `.NULL.`, nu eroare si nu `"0"`). `Nvl(.NULL., [NULL])` inlocuieste explicit cu + literalul caracter `NULL` (4 caractere), care ajunge **necotat** in textul SQL generat (concatenare + directa, fara ghilimele in jur) — deci Oracle il interpreteaza ca si cuvantul-cheie `NULL`, nu ca + string. Rezultat: `V_ID_POL` ajunge `NULL` in Oracle exact cand `poArt.id_pol` e `.NULL.` in VFP. + Verificare bazata pe semantica standard VFP (nu am putut rula VFP live — masina partajata, conform + interdictiei) — consistenta si cu tiparul deja folosit identic pentru alti parametri opționali pe + acelasi apel (`id_gestiune`, `id_valuta_d`, `id_part_rez` etc., toate cu acelasi `Nvl(Alltrim(Str(...`). +- **Neverificat** (nici in raportul original, nici aici): cum ajunge efectiv `poArt.id_pol` sa fie + `.NULL.` pentru un articol fara politica — ramane premisa mostenita din proiectul #13, nu + re-derivata in aceasta runda. + +## A4. Numerele de linie — offset +17 sau identice? + +**Verdict: NICIUNA din cele doua afirmatii e o regula generala — depinde de setul de citate comparat.** + +- **Pentru citatele proprii ale raportului verificat** (re-testate direct aici): `adauga_articol_factura` + header `:4989-5015`, ramura `ntip=4` `:5080-5103`, `contabilizeaza_articol` header `:7173`, CASE + `:7398-7423`, hardcode `:7413-7422`, garda `:7441`, `nTipNotaPlata:=46` la `:84` — **toate identice**, + zero offset. Raportul are dreptate pentru propriile sale citate. +- **Dar exista un offset real, doar ca nu e +17 ci +9**, intre citatele mai vechi din + `nota_contabila_fara_politica.md:100-104` (`ff_...:6150`, `:6867`, `:7149` pentru cele 3 apeluri + `contabilizeaza_articol`) si pozitiile reale confirmate in aceasta runda pentru **aceleasi** apeluri + (`6141`, `6858`, `7140`) — diferenta consecventa de **9 linii**, nu 17, si in directia opusa + (citatul vechi e mai mare, nu mai mic). +- Concluzia corecta: fisierul `PACK_FACTURARE.sql` a fost re-exportat de mai multe ori pe parcursul + proiectului (cod adaugat/sters intre versiuni), asa ca **fiecare set de citate vechi are propriul + offset fata de exportul curent — nu exista o constanta universala (nici 0, nici +17, nici +9)**. + `handoff_13_formular_unificat.md:190` ("+17") si acest raport ("identic, fara offset") sunt ambele + adevarate **doar pentru seturile de citate pe care le-au verificat fiecare** — nu se pot generaliza + una peste alta. Disciplina corecta, pe care ambele rapoarte au respectat-o, e re-verificarea directa + pe fisierul curent inainte de orice citare — nu presupunerea unui offset fix. + +## Text de plan corectat (sectiunea 4 din raportul verificat, rescris) + +``` +**Ramura `ntip=4` (facturare din avize, handler real `scrie_factura_avize:6692-7058`, nu +`scrie_factura_avize_retur` cum apare gresit in rapoartele anterioare) — exclusa structural, nu +necesita cod suplimentar.** `adauga_articol_factura`, ramura `WHEN ntip=4` (`:5080-5103`), cauta +randul sursa din `VANZARI_DETALII` prin `A.ID_POL = V_ID_POL`, fara handler de exceptie — cu +`V_ID_POL=NULL` (exact cazul liniei fara politica), comparatia nu se poate potrivi niciodata, deci +apelul cade cu `ORA-01403` la adaugarea articolului, in bucla `do_scrie_articole` (`ofacturare.vc2: +13967-14106`), care ruleaza intotdeauna inaintea `Do Case`-ului (`:14282`) ce ar chema +`scrie_factura_avize`. Un articol fara politica nu poate fi, structural, adaugat pe un document +`ntip=4`; ramura noua `cont_venit` nu are nimic de tratat aici. + +**Gol confirmat, de acoperit inainte de implementare — mai ingust decat s-a crezut initial**: +ramurile de aviz care **efectiv** ajung la `contabilizeaza_articol` prin singurul apelant confirmat +functional (`scrie_factura2`, apelat pentru `Inlist(3,21,25,28,42,47)` si pentru toate celelalte +tipuri prin `Otherwise`) sunt cele care cad in `ELSE`-ul CASE-ului propriu al `scrie_factura2`: +`ntip IN (28,29)` -> `SCD='461'`, restul (`21,22,24,27` si alte "otherwise" avize) -> `SCD='418'` +(`:7413-7422`). **`ntip IN (23,25,30,41,42,47)` NU ajung la `contabilizeaza_articol`** prin acest +apelant — sunt deviate mai devreme, in `scrie_factura2` insasi, spre `transfera_articol`/ +`descarca_gestiune`, deci golul nu li se aplica (contabilizeaza_articol si ramura ei noua nu se +executa niciodata pentru ele). `adauga_articol_factura` nu cere `id_pol` pentru niciunul din aceste +`ntip` (foloseste calea "din comenzi", fara filtru pe `id_pol`, sau calea implicita `ELSE`, fara nicio +interogare legata de politica) — deci un articol fara politica poate ajunge pe oricare din avizele +`28,29,21,22,24,27` cu `cont_venit` populat. Designul deja semnalase acest risc printr-o paranteza +(`canal_cont_venit_fara_politica.md:146`, "ramurile aviz raman 418/461"), dar nu-l implementase: ramura +noua trebuie sa aleaga `SCD` in functie de `ntip` (pastrand `'461'` pentru `IN(28,29)`, `'418'` pentru +restul avizelor atinse), nu sa hardcodeze `'4111'` necondiționat. + +**Gol confirmat separat**: ramura noua trebuie sa pastreze garda `IF ntip <> nTipNotaPlata` (`ntip=46`, +`ff_...:7441`) inainte de a apela `scrie_nota` — azi sarita intentionat pentru documentele de tip +"nota de plata", iar un articol fara politica poate fi adaugat si pe acest tip de document +(`adauga_articol_factura`, bucket `ELSE`, fara cerinta de `id_pol`). + +**Defensiv (opțional)**: se poate adauga o garda explicita `IF cont_venit IS NOT NULL AND ntip = 4 +THEN RAISE_APPLICATION_ERROR(...)`, dar e demonstrat redundanta — cazul e deja imposibil pe caile VFP +cunoscute, blocat cu doua straturi independente inainte de a ajunge la `contabilizeaza_articol`. +``` + +## Ce nu s-a putut stabili in aceasta runda (mostenit din raportul original, nu re-rezolvat) + +- Cum ajunge `poArt.id_pol` sa fie `.NULL.` in VFP pentru un articol fara politica pe un aviz — + premisa proiectului #13, nu re-derivata. +- Cine cheama `scrie_aviz_retur` (daca cineva) — cautare exhaustiva in `COMUN\` si in tot proiectul, + zero rezultate; posibil cod mort sau apelat din alt produs al suitei ROA. +- Comportamentul exact `goExecutor`/`oPrelucrareEroare()` la o exceptie Oracle neprinsa — nu urmarit + in aceasta runda (mostenit ca gol si in raportul original). + +## Handoff + +Verificare incheiata, toate cele 4 afirmatii + corectia structurala preliminara sunt in sectiunile de +mai sus. Zero cod atins, zero write-back, doar `SELECT`/`Read`/`Grep` pe SQL si VFP text. Context +consumat substantial (citire extinsa pe `ff_...sql` si `ofacturare.vc2`), dar verificarea s-a incheiat +inainte de pragul de predare. + +--- + +## Propagarea `CONT_VENIT` prin cei trei apelanti — verificare + +Intrebare noua de la team-lead: argumentul central al proiectarii (`canal_cont_venit_fara_politica.md`, +sectiunea 1, `:63-69`) — "orice coloana noua pe `VANZARI_DETALII_TEMP` ajunge automat in +`detalii_articol`, fara sa atingi cei 3 apelanti, pentru ca fac `SELECT * BULK COLLECT` intr-un +`TABLE OF ...%ROWTYPE`" — se bazeaza pe lista de nume deja corectata mai sus. Verificat acum pe +numele **reale** ale celor 3 apelanti (`scrie_factura2`, `scrie_factura_avize`, `scrie_aviz_retur`), +fiecare citit direct, linie cu linie, in aceasta runda. + +### 1-2. `scrie_factura2` (`:6020-6223`) + +``` +ff_...:6039-6040 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE; + tab_detalii tab_detalii_type; +ff_...:6063 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP; +ff_...:6141 pack_facturare.contabilizeaza_articol(tab_detalii(i)); +``` +**`SELECT *` intr-un `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`, confirmat.** `CONT_VENIT` curge automat. + +### 1-2. `scrie_factura_avize` (`:6692-7058`, handler-ul real `ntip=4`) + +``` +ff_...:6708-6709 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE; + tab_detalii tab_detalii_type; +ff_...:6749 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP; +ff_...:6858 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- doar pt. custodie=1 +``` +**Acelasi tipar: `SELECT *` in `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`, confirmat.** Intre populare +(`:6749`) si apelul catre `contabilizeaza_articol` (`:6858`), randul `tab_detalii(i)` e modificat doar +pe doua campuri (`.cantitate`, `.id_rata`, `:6855-6856`) — `CONT_VENIT` ramane neatins, curge automat. +Chiar daca numele apelantului real difera de ce credea proiectarea, **concluzia ei ("nu se ating deloc") +ramane corecta pentru acest apelant**, doar numele era gresit. + +### 3. `scrie_aviz_retur` (`:7085-7171`) + +``` +ff_...:7097-7098 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE; + tab_detalii tab_detalii_type; +ff_...:7106 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP; +ff_...:7140 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- doar pt. custodie=0 (ELSE la :7115) +``` +**Acelasi tipar, confirmat.** Nu conteaza ca nu i-am gasit apelant VFP (ramane nedovedit ca se +executa vreodata) — *daca* s-ar executa, `CONT_VENIT` ar curge automat si aici, deci nu schimba +suprafata diff-ului indiferent de raspuns. + +### Verdict pe argumentul proiectarii + +**Se salveaza.** Toti cei 3 apelanti reali (nu cei numiti gresit in rapoartele anterioare) folosesc +identic `SELECT * BULK COLLECT INTO FROM VANZARI_DETALII_TEMP` +— zero lista explicita de coloane, zero `%ROWTYPE` pe alta tabela, zero constructie camp-cu-camp. +Concluzia proiectarii ("adaugi coloana pe `VANZARI_DETALII_TEMP`, `contabilizeaza_articol` o vede +automat prin toti apelantii, fara sa atingi `scrie_factura2`/`scrie_factura_avize`/`scrie_aviz_retur`") +**e corecta ca mecanism**, chiar daca doua din cele trei nume citate pentru ea erau gresite. Corectia +de nume nu schimba nimic la suprafata de regresie a schemei — schimba doar la ce trebuie sa te uiti +daca vreodata unul dintre cei trei apelanti isi schimba modul de populare a lui `tab_detalii`. diff --git a/docs/cercetare/verif_proforma_alegere_stoc.md b/docs/cercetare/verif_proforma_alegere_stoc.md new file mode 100644 index 0000000..c4baeb0 --- /dev/null +++ b/docs/cercetare/verif_proforma_alegere_stoc.md @@ -0,0 +1,366 @@ +# 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`: +```sql +(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. diff --git a/docs/cercetare/zi_curs_validare.md b/docs/cercetare/zi_curs_validare.md new file mode 100644 index 0000000..16ccca6 --- /dev/null +++ b/docs/cercetare/zi_curs_validare.md @@ -0,0 +1,262 @@ +# Cercetare: validarea zi_curs la ofacturare.vc2:8076 si impactul asupra S4d + +## Verdict (5-10 randuri) + +Ascunderea selectorului de zi curs NU va lasa un document fara curs si NU va cadea la salvare, +CU CONDITIA sa se respecte precedentul deja existent in cod: `poDate.zi_curs` primeste un implicit +necondiionat (data documentului) chiar in `oDateFactura.Init`/`Reset` +(`COMUN\programe\ofacturare_comun.prg:247` si `:496`), INAINTE ca formularul sa decida ce ascunde. +Nicaieri codul nu goleste `poDate.zi_curs` cand controlul e ascuns/eliminat. Riscul real de eroare +Oracle (-20005, "Nu este setat cursul...") vine NU din camp gol, ci din faptul ca verificarea +`pack_facturare.verifica_cursuri_valute` (in `cursor_preturi`) ruleaza NECONDITIONAT de `in_valuta` +al documentului curent si exclude doar moneda nationala - deci un `zi_curs` implicit (azi) care nu +are curs setat in tabela CURS pentru o valuta folosita in listele de preturi ale utilizatorului +poate pica oricum, INDIFERENT daca selectorul e vizibil sau nu. Linia :8076 NU apartine formularului +de factura, ci unui formular separat, restrans, pentru AVIZ PE LUCRARE / AVIZ PE NIR +(`frm_date_aviz_lucrare`, tipuri 27 si 30) - fara control de valuta pe el - si valideaza `zi_curs` +strict pentru ca tipul 27 are nevoie de curs pentru articolele din comanda (posibil in valuta), +INDEPENDENT de `poDate.in_valuta`. Precedentul cerut la punctul 6 exista deja, dar in alt formular: +`frm_date_factura`, tipurile 8/9 (retur), unde `clb_zi_curs` e eliminat NECONDITIONAT si documentul +se salveaza corect - motivul principal e ca SQL-ul pentru retur (`cursor_retur`) nici nu foloseste +`poDate.zi_curs`. + +## 1. Ce valideaza linia :8076 + +Index de simboluri: `frm_date_aviz_lucrare.inainte_de_do_termin` = `ofacturare.vc2:8054-8119` +(fisier real: `COMUN\clase\ofacturare.vc2`). + +Blocul complet (validare secventiala pe formular, fiecare `Case` opreste salvarea la primul fail): + +``` +COMUN\clase\ofacturare.vc2:8063-8079 +Do Case +Case Empty(poDate.dataireg) + amessagebox("Nu ati completat data inregistrarii!",48,"Atentie") + ... +Case Empty(poDate.dataact) + amessagebox("Nu ati completat data documentului!",48,"Atentie") + ... +Case Empty(Nvl(poDate.id_fdoc,0)) + amessagebox("Nu ati ales felul documentului!",48,"Atentie") + ... +Case Empty(Nvl(poDate.zi_curs,{})) + amessagebox("Nu ati completat ziua cursului valutar!",48,"Atentie") + This.clb_zi_curs.SetFocus() + plReturn = .F. +Case Empty(poDate.nract) + ... +``` + +Valideaza STRICT prezenta unei date in `poDate.zi_curs` (`Empty(Nvl(...,{}))`), nu existenta unui +curs in baza pentru acea data - acel test se face abia in Oracle, la momentul in care se cere +cursorul de articole (vezi punctul 4). + +## 2. Cand ruleaza si pe ce tipuri de document + +`inainte_de_do_termin` e apelat la evenimentul butonului "Termina" al formularului (`BUT_TERMIN1`, +vezi lista de obiecte a clasei, `ofacturare.vc2:7674-7679`) - deci la incercarea de a incheia +completarea datelor de antet, inainte de a trece la ecranul de articole. + +`frm_date_aviz_lucrare` NU e formularul de factura. E instantiat DOAR pentru doua tipuri de +document, ambele AVIZ (nu FACTURA): + +``` +COMUN\programe\ofacturare.prg:187-226 (identic in factureaza2, :697-699) +Do Case + Case tnTip = 27 + poDate.nIdTipDoc = 6 && AVIZ + Case tnTip = 30 + poDate.nIdTipDoc = 6 && AVIZ + Case tnTip < 21 Or Inlist(tnTip, 45, 48, 49, 51, 52) + poDate.nIdTipDoc = 5 && FACTURA + ... +Do Case + Case tnTip = 27 + lcObiect = [frm_date_aviz_lucrare] + Case tnTip = 30 + lcObiect = [frm_date_aviz_lucrare] + Case tnTip < 21 Or Inlist(tnTip, 45, 48, 49, 51, 52) + lcObiect = [frm_date_factura] + Otherwise + lcObiect = [frm_date_aviz] +Endcase +``` + +Titlul formularului confirma: `Lb_titlu_alb_b121.Caption = "AVIZ PE BAZ­ DE LUCRARE"` +(`ofacturare.vc2:7670`), schimbat in `Init` la "AVIZ PE BAZ­ DE NIR" cand `nid_tip = 30` +(`ofacturare.vc2:8154-8161`). + +Nu exista o garda mai sus in lant care sa dezactiveze validarea :8076 pe vreun tip - ruleaza +identic pentru tip 27 si tip 30, necondiionat de `poDate.in_valuta`. IMPORTANT: aceasta clasa +NU are deloc control de valuta pe formular - lista de obiecte a clasei (`ofacturare.vc2:7623-7637`) +nu contine niciun `ct_clb_valuta`. Deci validarea de aici nu e legata de "documentul e in valuta", +ci de nevoia formularului AVIZ-LUCRARE de a avea o zi de curs pentru articolele comenzii care pot +fi preturite in valuta (vezi punctul 4, `cursor_lucrare`). + +Concluzie: linia :8076 NU intra deloc in fluxul de facturare (FACTURA) vizat de decizia 15 - e un +formular separat, pentru un subset ingust de avize (27 = aviz pe lucrare, 30 = aviz pe NIR). + +## 3. Ce se intampla daca zi_curs e gol la :8076 + +Mesaj de eroare blocant (nu doar avertisment): `amessagebox("Nu ati completat ziua cursului +valutar!",48,"Atentie")`, apoi `This.clb_zi_curs.SetFocus()` si `plReturn = .F.` - `Case`-ul +opreste executia `Do Case` (Otherwise nu se mai atinge), iar `inainte_de_do_termin` returneaza +`.F.`, ceea ce (conform conventiei din restul clasei) blocheaza inchiderea formularului / trecerea +la pasul urmator. + +## 4. Cine mai citeste poDate.zi_curs + +Cautare `zi_curs` in `.vc2`/`.prg`/`.sc2` si in sursele Oracle +(`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`): + +**a) Validari de formular (camp obligatoriu):** +- `frm_date_aviz_lucrare.inainte_de_do_termin` :8076 - necondiionat (vezi punctele 1-2). +- `frm_date_factura.inainte_de_do_termin`, `ofacturare.vc2:9484`: + ``` + Case poDate.in_valuta = 1 And Empty(Nvl(poDate.zi_curs,{})) And Type('thisform.clb_zi_curs.visible')<>'U' + ``` + Aici validarea E DEJA dublu conditionata: pe `in_valuta = 1` SI pe existenta controlului + (`Type(...)<>'U'` - devine 'U' daca controlul a fost eliminat cu `RemoveObject`). Deci pe factura + in lei, sau pe orice tip unde controlul a fost eliminat, validarea nu ruleaza deloc. Comentariul + `*!* modificare v 2.0.56` de langa arata ca exact acest lucru a fost REZOLVAT anterior pentru + formularul de factura. + +**b) Populare implicita / sincronizare (fara conditie de in_valuta):** +- `oDateFactura.Init`, `COMUN\programe\ofacturare_comun.prg:247`: `.zi_curs = ldData` - + necondiionat, seteaza mereu data documentului curent (ldData = azi, ajustat la luna/anul + curent de facturare) INAINTE de blocul care seteaza `.in_valuta` (linia 248-250). +- `oDateFactura.Reset`, `ofacturare_comun.prg:496`: `.zi_curs = .Data` - la fel, necondiionat. +- `frm_date_aviz.Clb_dataact.Text_simplu1.LostFocus` (:7603-7604) si + `frm_date_aviz.Clb_dataireg...LostFocus` (:7610-7611): `poDate.zi_curs = poDate.dataact`, + necondiionat (formularul aviz general nu are guard, dar si nu are RemoveObject pe zi_curs). +- `frm_date_aviz_lucrare.Clb_dataact...LostFocus` (:8186-8187) si + `...Clb_dataireg...LostFocus` (:8193-8194): idem, necondiionat - zi_curs NU e niciodata + eliminat in aceasta clasa, deci sincronizarea merge mereu. +- `frm_date_factura.Clb_dataact...LostFocus` (:9805-9808) si `...Clb_dataireg...` (:9824-9827): + ACESTEA SUNT deja conditionate: `If Type('thisform.clb_zi_curs.visible')<>'U' ... zi_curs = + dataact ... Endif` (comentariu `*!* modificare v 2.0.56`). Cand controlul e eliminat, sincronizarea + se opreste - dar valoarea RAMASA de la Init/Reset nu se sterge, ramane cea de la creare. + +**c) Consum efectiv in SQL (trimis catre Oracle, cursoare de articole):** +`COMUN\programe\ofacturare.prg:266-308` (identic in `factureaza2`, :751-816) - alegerea SQL-ului de +populare a articolelor se face pe `tnTip`, NU pe `in_valuta`: +``` +Case Inlist(tnTip, 48, 49) -> cursor_articole_k(?poDate.zi_curs, ...) +Case tnTip = 45 -> cursor_preturi(?poDate.zi_curs, ...) +Case Inlist(tnTip, 1,22,5,29,7,10,23) -> cursor_preturi(?poDate.zi_curs, ...) +Case Inlist(tnTip, 2,26,6,52) -> cursor_contract(?poDate.zi_curs, ...) +Case Inlist(tnTip, 3,21,25,28,42,47) -> cursor_comanda(?poDate.zi_curs, ...) +Case tnTip = 4 -> cursor_avize(...) [FARA zi_curs] +Case Inlist(tnTip, 41) -> cursor_gestiune(?poDate.zi_curs, ...) +Case tnTip = 30 -> cursor_aviz_nir(...) [FARA zi_curs] +Case tnTip = 27 -> cursor_lucrare(?poDate.zi_curs, ...) +Case Inlist(tnTip, 8,9,24) -> cursor_retur(?poDate.in_valuta, ...) [FARA zi_curs] +``` +Descoperire cheie: pentru tipurile 8, 9, 24 (retur) SI 30 (aviz pe NIR), SQL-ul NU trimite deloc +`poDate.zi_curs` catre Oracle - `zi_curs` gol sau completat nu are niciun efect pentru aceste +tipuri. Acesta e motivul real pentru care eliminarea controlului la tip 8/9 e sigura (mai puternic +decat simpla existenta a unei valori implicite). + +Pentru tipurile care TRIMIT zi_curs, comportamentul in Oracle e diferit: +- `cursor_preturi` (`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2138+`) apeleaza NECONDITIONAT + `pack_facturare.verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)` (linia 2153). Aceasta procedura + (`:16247-16274`) verifica cursul pentru TOATE valutele distincte din `FACT_VPRETURI_UTILIZATOR` + ale utilizatorului curent (nu doar valuta documentului!) si arunca + `RAISE_APPLICATION_ERROR(-20005, 'Nu este setat cursul din data de ... !')` daca oricare dintre + ele nu are curs care sa acopere `V_DATA_CURS` - EXCLUDE explicit moneda nationala + (`AND A.ID_VALUTA <> pack_facturare.nid_moneda_nationala`). Deci: chiar pe un document in LEI + (`in_valuta=0`), daca utilizatorul are liste de preturi in valuta configurate si data trimisa + (implicita sau nu) nu are curs setat, apelul PICA cu -20005 - INDIFERENT de vizibilitatea + selectorului pe formular. Riscul nu vine din camp gol, ci din "camp cu o data pentru care nu + exista curs in tabela CURS". +- `cursor_articole_k` (`:3595-3701`) si `cursor_lucrare` (`:3173-3593`, foloseste `V_DATA_CURS` + pentru comenzi_elemente) - `cursor_articole_k` NU apeleaza `verifica_cursuri_valute`, doar face + `LEFT JOIN CURS ... WHERE DATA <= V_DATA_CURS AND DATA2 >= V_DATA_CURS` - daca nu gaseste, cade + silentios pe `NVL(D.CURS,0)` (pret gresit, nu eroare). `cursor_lucrare` (:3186-3218) INSA face + o verificare proprie, similara: daca articolele comenzii au valute fara curs pe `V_DATA_CURS`, + arunca acelasi `-20005`. +- Exista deja o rutina de recuperare la acest cod de eroare: `ofacturare.prg:313-317` + ``` + If lnSucces < 0 + AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare") + If goExecutor.nEroare = 20005 + vizualizeaza_curs(poDate.zi_curs) + ENDIF + ``` + Deci sistemul ANTICIPEAZA deja cazul "curs lipsa la data respectiva" si deschide un ecran de + gestiune a cursurilor - independent de validarea din formularul de date. + +**d) Afisare / etichetare (fara risc):** +- `frm_facturare_articole.Init` (:15097-15098) si `frm_facturare_articole2.Init` (:19004-19005): + `If !Empty(Nvl(poDate.zi_curs,{})) Then Thisform.lb_cursuri.Caption = "Curs valutar (" + + Dtoc(poDate.zi_curs) + ")"` - deja tolereaza gol (nu afiseaza nimic), fara eroare. +- `frm_date_factura.do_cauta_valuta` (:9344-9359) - dupa alegerea valutei, muta focusul pe + `clb_zi_curs` DACA exista (`Type(...)<>'U'`), altfel pe `clb_serie_act`. Deja conditionat. +- `onom_curs.vc2` (`ck_zi_curs`, `tx_zi_curs`) - ecran DIFERIT, de administrare a cursurilor + valutare in sine (nu are legatura cu `poDate.zi_curs`; e o cautare "dupa ziua cursului" generica). + +## 5. Valoarea implicita azi si de unde vine + +Vine din `oDateFactura.Init`/`Reset`, necondiionat de tip sau de `in_valuta`: +- `Init` (`ofacturare_comun.prg:235-247`): `ldData = Ttod(get_ora())`, ajustat la luna/anul curent + de facturare (`gnAn`/`gnLuna`) daca `get_ora()` cade in alta luna; apoi `.zi_curs = ldData`. +- `Reset` (`ofacturare_comun.prg:486-496`): `.zi_curs = .Data` (unde `.Data` a fost deja setat tot + din `ldData`-ul curent). + +Deci implicit `zi_curs` = data curenta (get_ora, ajustata la perioada de facturare deschisa), NU +`Date()` brut si nu neaparat `dataact`/`dataireg` (desi acestea pornesc de la aceeasi `ldData`). +Ulterior, cat timp controlul `clb_zi_curs` exista pe formular, orice editare a `dataact`/`dataireg` +resincronizeaza `zi_curs = dataact` prin evenimentele `LostFocus` (vezi punctul 4b). Daca formularul +ar ascunde controlul FARA sa elimine obiectul si fara sa goleasca proprietatea, campul ar ramane +la valoarea implicita de la Init/Reset (sau la ultima valoare sincronizata inainte de ascundere). + +## 6. Precedent: tip cu campul ascuns care se salveaza corect + +DA, exista deja, dar in `frm_date_factura` (formularul de FACTURA), nu in `frm_date_aviz_lucrare`: + +``` +COMUN\clase\ofacturare.vc2:9717-9722 [frm_date_factura.Init] +*!* modificare v 2.0.56 +If Inlist(poDate.tip, 8, 9) + lnHeight = lnHeight - .clb_zi_curs.Height + laPozitii(.clb_zi_curs.TabIndex, 2) = 1 + .RemoveObject('clb_zi_curs') +Endif +*!* modificare v 2.0.56 ^ +``` + +Pentru tip 8 si 9 (facturi de retur - care pot fi chiar in valuta, vezi `ofacturare_comun.prg:248`: +`INLIST(m.tnTip, 5,6,7,9,10,52) -> .in_valuta = 1`), controlul `clb_zi_curs` e eliminat COMPLET de +pe formular, necondiionat de `in_valuta`, si documentul se salveaza corect. Motivele, in ordine de +robustete: +1. Validarea din `inainte_de_do_termin` (:9484) e deja garda cu `Type(...)<>'U'`, deci se + auto-dezactiveaza cand controlul nu mai exista. +2. SQL-ul de populare articole pentru tip 8/9 e `cursor_retur(?poDate.in_valuta,...)` + (`ofacturare.prg:306-307`) - NU trimite deloc `poDate.zi_curs`, deci nu poate cauza -20005 din + cauza acestui camp. +3. Chiar daca ar fi trimis, `poDate.zi_curs` tot ar avea valoarea implicita de la Init/Reset + (punctul 5) - nimic nu-l goleste la `RemoveObject`. + +Aceasta e "reteta" cerinta de punctul 6: eliminarea vizuala e sigura pentru ca (a) validarea are +deja garda pe existenta controlului, si (b) proprietatea `poDate.zi_curs` nu e niciodata golita - +ramane pe implicitul din Init/Reset. + +Pentru `frm_date_aviz_lucrare` (linia :8076) NU exista un tip cu campul ascuns - `clb_zi_curs` nu e +eliminat pentru nici tip 27, nici tip 30. Motivul plauzibil: tip 27 (aviz pe lucrare) chiar +foloseste `zi_curs` in `cursor_lucrare` pentru articolele comenzii (posibil in valuta), independent +de `poDate.in_valuta` al documentului-aviz insusi - deci acolo campul NU e un candidat sigur pentru +ascundere pe baza lui `in_valuta`. Pentru tip 30, SQL-ul (`cursor_aviz_nir`) nu foloseste `zi_curs` +deloc, deci validarea de acolo e superflua dar inofensiva (campul e mereu populat implicit). + +## Ramas de verificat + +- Nu am gasit inca daca decizia 15 / S4d intentioneaza sa includa si `frm_date_aviz_lucrare` in + formularul unificat, sau doar `frm_date_factura`/`frm_date_aviz`. Din cod, `frm_date_aviz_lucrare` + e un formular de sine statator, fara control de valuta, folosit doar pentru tnTip 27 si 30 - daca + planul S4d nu-l tinteste explicit, linia :8076 e in afara scopului imediat. +- Nu am verificat ce se intampla in `cursor_articole_k` (tip 48/49) si `cursor_gestiune` (tip 41) + fata de `verifica_cursuri_valute` - din citire, `cursor_articole_k` nu apeleaza acea procedura + (cade silentios pe curs 0), dar nu am verificat `cursor_gestiune`. +- Nu am verificat cum decide `frm_date_factura.Init` ce alte tipuri (in afara de 8,9) ar putea fi + candidate pentru ascunderea lui `clb_zi_curs` conform deciziei 15 - doar am confirmat mecanismul + existent si conditia dubla deja implementata la validare (in_valuta + Type<>'U'). diff --git a/docs/handoff_13_formular_unificat.md b/docs/handoff_13_formular_unificat.md new file mode 100644 index 0000000..81fd2dd --- /dev/null +++ b/docs/handoff_13_formular_unificat.md @@ -0,0 +1,798 @@ +# Handoff — #13 formular unificat de facturare + editare prin regenerare + +Sesiune: 11.08.2026, **runda 17** (istoricul de mai jos e cumulativ; blocul rundei 14 e pastrat ca +atare). Runda 13 predase dupa cinci proiectari si deciziile 44-50. + +> ## RUNDA 17 — „Urmatorul bloc de lucru" S-A GOLIT. Proiectarea lui #13 e INCHEIATA. +> +> Runda 17 a executat **exact cele doua sarcini** care mai ramasesera (punctele 4 si 5 din lista de +> jos) si **nu a deschis niciuna noua**. +> +> > ### DECIZIA 66 (runda 17) RASTOARNA DECIZIA 64 — citeste asta inainte de orice paragraf despre asezare +> > **Motivul discountului sta LANGA DISCOUNT, in banda de totaluri, nu ca a treia sectiune jos.** +> > Formularea lui Marius: *„motiv discount vreau sa fie langa discount, nu a treia coloana"*. +> > **Randul de jos revine la DOUA sectiuni** (incasare, alte date), fiecare pe jumatate de latime — +> > adica exact decizia 57, punctul 2, neatinsa. **Argumentul „~440 px fiecare, strans" cade odata cu +> > decizia 64.** Mai jos, in istoricul cumulativ al rundei 16, decizia 64 apare inca in vigoare in +> > cateva paragrafe: **sunt istorie, nu stare curenta.** Locurile decisive sunt corectate; daca +> > gasesti unul necorectat, **decizia 66 castiga**. Enuntul complet, cu regula de activare adaugata de +> > proiectare (campul e activ **doar cand discountul nu e zero**): **in plan**, la „Decizia 66". +> +> **O singura decizie noua, 66**, deci numerotarea se opreste la **66**. + +> ### AUDIT DE INCHIDERE (18.08.2026) — planul e curatat de marcajele „deschis" ramase in urma +> Verificare ceruta de Marius inainte de a inchide sesiunea: **e planul finalizat?** Raspuns: **da ca +> proiectare**, dar avea **noua intrebari fantoma** — blocuri „De decis de Marius" si „ramane deschis" +> care supravietuisera deciziilor care le inchisesera. Un agent proaspat le-ar fi citit ca sarcini si +> ar fi redeschis lucruri transate. **Toate au fost stinse**, fiecare cu decizia care o inchide, in +> **8 linii** din plan (verificat prin diff fata de o copie de siguranta: zero modificari colaterale): +> - trei blocuri **„De decis de Marius"** (S5c, S8b, S11) → inchise de deciziile **51**, **52**, **53** +> si **56**; textul ramane ca **inventar al recomandarilor acceptate**, nu ca intrebari; +> - doua afirmatii „ramane deschisa asezarea campului de motiv" → inchise de **decizia 66**; +> - „din intrebarea 7 ramane deschisa partea de tipuri 48/49" → inchisa de **decizia 60**. +> +> **Ce a ramas deschis, si e corect asa — trei puncte, toate de implementare, niciunul blocant:** +> 1. **Relistarea unei facturi vechi deja trimise** (decizia 62, rest semnalat): dupa decizia 59 ar +> produce N randuri de discount in loc de unul, deci hartie diferita de originalul din eFactura. +> Relistarea nu e modificare, deci `EsteInEFactura` n-o acopera. **Nedecis, semnalat lui Marius.** +> 2. **`PROC_TVAV` ca parametru** (consecinta 3 a deciziei 54): doar daca se cere reproducere exacta +> peste o modificare legala de cota. **De decis separat.** +> 3. **De unde se reconstituie `IN_STOC`** (cerinta rundei 17): preconditie de proiectare a lui S8, +> trei variante scrise, niciuna aleasa. +> **Cod: neatins** — nici un `.vc2` / `.sc2` / `.prg` deschis macar. **Zero Oracle** in aceasta runda, +> nici macar `SELECT`. **Niciun commit dat de aceasta sesiune.** +> +> **Punctul 4 — golul `IN_STOC` din S8 — INCHIS**, prins acolo unde cerea handoff-ul precedent (in nota +> de executie a lui S8, nu la S12). Trei editari in plan, toate verificate pe disc dupa ce sesiunea de +> la #6 a rescris `docs\` in paralel: +> - `plan_13:3520-3546` — caseta „CERINTA DE EXECUTIE ADAUGATA IN RUNDA 17"; +> - `plan_13:3567-3569` — criteriul intra in „gata cand" al lui S8; +> - `plan_13` la S10, consecinta 1 — marcata **PRELUAT**, ca sa nu se redescopere. +> +> **Punctul dur, si e mai greu decat suna cerinta:** valoarea istorica a lui `IN_STOC` **nu e stocata +> nicaieri** — nu e coloana pe `VANZARI_DETALII` (verificat pe DB inca de la S10), traieste doar in +> temp. Deci „S8 incarca `IN_STOC` din document" **nu e implementabil ca atare azi**. De unde se +> reconstituie e o **preconditie de proiectare a lui S8** — trei variante scrise in plan (din urma +> lasata in rulaje/gestiune; nomenclatorul curent ca aproximatie **declarata**; coloana noua, deci +> migrare DB inainte de EXE). **Nu s-a ales niciuna** si nu se presupune niciuna. +> +> **Punctul 5 — mockup v9 — LIVRAT.** `docs\mockup_13_formular_unificat.html`, 71 336 octeti (era +> 67 075 la v8). Asezarea e acum **varianta D** cu **randul de jos in doua** si motivul discountului in +> banda de totaluri (deciziile 57 + 66): +> antet strans pe doua randuri fara titluri de grup; panoul „Discount pe document" **disparut**; banda +> de totaluri **in interiorul panoului Articole, dupa `.gridbar`**, deci lipita de grid, cu procentul +> de discount editabil pe loc, **motivul discountului imediat dupa suma pe care o explica** (decizia +> 66) si totalul mare singur la dreapta; sub ea, **doua** sectiuni colapsabile egale +> (incasare · alte date), cu **rezumatul continutului pe randul inchis**. +> Legenda are acum 5 callout-uri (2 si 4 repurpozate, 5 nou). In text au intrat deciziile +> **52, 53, 54, 59, 60, 61, 62, 63+65**. Raport: `docs\cercetare\mockup_v9_modificari.md`. +> **Verificat de sesiunea principala, nu preluat din raport:** 0 taguri nechise, 0 inchideri +> neasteptate, ` + + + +
+ +

ROAFACTURARE · todos.txt punctul 13 · mockup, versiunea 9 (runda 17)

+

Un singur formular de facturare

+

+ Datele documentului si articolele in aceeasi fereastra, fara incarcarea prealabila a tuturor + articolelor din toate politicile de preturi. Acelasi formular la introducere, la modificare, la + copiere si la proforma. +

+

+ Mockup, nu implementare. Campurile, etichetele si coloanele sunt cele reale, verificate pe cod la + 09.08.2026 — rapoartele sunt in docs\cercetare\. + Plan: docs\plan_13_unificare_formular_facturare.md + Asezarea e varianta D (decizia 57, runda 15), cu motivul discountului in banda de totaluri + (decizia 66, runda 17). +

+ +

1Formularul

+

+ Deschis pe o factura in lei, emisa pe baza de comanda. Antetul e blocat — butonul din bara lui + arata creionul. Asezarea e varianta D: antetul strans pe doua randuri, fara panou separat de + discount, banda de totaluri lipita de grid pe toata latimea — cu procentul si motivul discountului + chiar in ea — si randul de jos cu doua sectiuni colapsabile, incasare si alte date. La introducerea + unui document nou arata identic, cu antetul deschis si campurile goale. +

+ +
+
+ Factura FF 1 244 / 07.08.2026 · SC EXEMPLU DISTRIBUTIE SRL + + Renunta + Termina +
+
+ +
+
Document 1 + + antet blocat + + +
+
+
+
FACTURA
+
FF
+
1 244
+
07.08.2026
+
06.09.2026
+
+
+
SC EXEMPLU DISTRIBUTIE SRL F4
+
RO12345678 ANAF
+
14 820,00
+
CMD 3312 F4
+
DEPOZIT CENTRAL
+
+
+
+ +
+
Articole 3
+
+ +Linie noua + −Sterge linia + ✎Detalii linie + + ↧Adauga articole ▾ + 3 linii · comanda CMD 3312 acoperita 100% +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NrCod materialArticolUMGestiuneCantitatePret f. TVApret
c. TVA
Disc. %Disc. unitarExplicatie TVAVal. f. TVATVAVal. c. TVAExplicatie linie
1MAT-0041Ciment Portland 42,5R 40 kgSACDEPOZIT CENTRAL100,00028,50000,000,0000Livrari 21%2 850,00598,503 448,50—
2MAT-0107Adeziv gresie/faianta 25 kgSACDEPOZIT CENTRAL40,00042,00005,002,1000Livrari 21%1 596,00335,161 931,16conform comanda
3SRV-0002Transport marfaBUC—1,000250,0000✓0,000,0000Prestari 21%250,0052,50302,50—
4scrie codul sau denumirea…cautare pe server, pe masura ce scrii — nu se mai aduce lista de preturi intreaga
+ +
+ Ins — linie nouaDel — sterge + F4 — cautaCtrl+N / Ctrl+D +
+
+
Baza4 696,00
+
Discount articole84,00
+
Discount document4 + 0,00 %0,00 + ✓ evidentiat pe factura +
+
Motiv5 + motivul discountului… +
+
TVA986,16
+
Total factura5 682,16 lei
+
+ + +
+
+
▸ Incasare + NUMERAR · 5 570,55 lei +
+
+
+
▾ Alte date 2
+
+
+
701
+
—
+
—
+
+
+
IONESCU V.
+
B 123 XYZ
+
ca a clientului
+
+
+
+
+ + + + +
+
1

Antetul, blocat pana ceri altfel

+

Un singur buton. Creionul deschide antetul si devine discheta; discheta salveaza doar + antetul si redevine creion. Nicio bifa — nici cele patru de azi, pe serie, numar, data si + scadenta. Fara valuta si fara data de curs — factura e in lei si niciun articol nu are pret in + valuta (sectiunea 4). Cat anume deschide, si de ce nu chiar tot in prima etapa: + sectiunea 2.

+
2

Randul de jos, doua sectiuni

+

Incasarea si alte date (analitice, delegat/transport, adresa de facturare, text aditional) + stau colapsate, una langa alta, nu una sub alta (decizia 57). Sunt independente — se poate tine + deschisa doar una — iar randul inchis arata rezumatul continutului, nu doar titlul: cand + incasarea e completata arata suma si modalitatea, cand nu e nimic completat rezumatul spune asta. + Campurile din interior stau pe orizontala, doua randuri fiecare. A treia sectiune, pentru + motivul discountului, a fost incercata si respinsa — motivul sta langa discount, in banda de + totaluri (decizia 66 rastoarna decizia 64). + Incasarea ramane cea deosebita (decizia 41): e singura cu efecte laterale reale — + alocare/dezalocare de numere — deci garda de non-alocare la deschidere se scrie si se testeaza + intr-un singur loc, si e singura blocata pe un document deja emis (decizia 25). Tot pe document + emis, analiticele se vad dar nu se editeaza de aici — sectiunea 2.

+
3

Un singur buton de adaugare

+

Adauga articole deschide un meniu cu optiunile potrivite sursei — sectiunea 3. + Dispare gridul de sus cu toate articolele din toate politicile.

+
4

Banda de totaluri, lipita de grid

+

Baza, discount articole, discount document si TVA, desfacute pe un singur rand, cu totalul + mare singur la dreapta (decizia 57). Discountul de document se editeaza pe loc, procentul langa + suma; bifa „se pune in evidenta…" de pe panoul care disparea s-a mutat aici, discret. Repartizarea + pe cote de TVA e automata si proportionala (decizia 59) — nu se cere nimic utilizatorului.

+
5

Motivul discountului, langa discount

+

Singura bucata din reparatia discountului de document care cere UI nou (decizia 61): text + optional, ajunge in AllowanceChargeReason din eFactura (ReasonCode + ramane 95). Sta in banda de totaluri, imediat dupa suma pe care o explica + (decizia 66) — nu ca sectiune separata jos, cum se stabilise la decizia 64. Ia latimea ramasa + intre discount si TVA; e activ doar cand discountul de document nu e zero — un motiv fara + discount n-are ce explica.

+
+ +

2Modificarea antetului, fara sa treci prin articole

+

+ Protectia antetului nu e o inventie: formularul de azi de modificare are deja patru bife, cate una + pe camp, care deblocheaza serie, numar, data si scadenta. Ce se schimba e ca toate patru dispar, + inlocuite de un singur buton in bara panoului, care isi schimba imaginea: creion cat timp + antetul e blocat, discheta cat timp e deschis. +

+ +
+
+
+
1 · antet blocat
+
+
Document + antet blocat +
+
+
FF
+
1 244
+
06.09.2026
+
IONESCU V.
+
+
+
+

Nimic nu se poate schimba din greseala. Documentul se poate citi in liniste. + Butonul arata creionul: apasarea deschide.

+
+
+
+
2 · antet deschis
+
+
Document + antet deschis +
+
+
FF
+
1 244
+
06.09.2026 ▾
+
IONESCU V. F4
+
+
+
+

Tot antetul e editabil, si campurile de identitate. Butonul a devenit discheta: + a doua apasare salveaza antetul si il blocheaza la loc, cu creionul inapoi.

+
+
+ +

+ Un singur ciclu, doua stari: creion → deschis → discheta → salvat si blocat → creion. + Termina ramane neschimbat si salveaza tot documentul; butonul de antet nu il inlocuieste, + ci acopera cazul in care nu vrei sa treci prin articole. +

+

+ de adaugat Un singur camp de antet nu are azi control nicaieri: + eFactura. Procedura il scrie la fiecare apel, dar valoarea vine din inregistrare sau e + fortata zero — nimeni nu il poate schimba. Daca butonul deschide tot antetul, il deschide si pe + el, langa tipul SAF-T. +

+

+ exista deja Comutatorul nu e un tipar nou in suita: fereastra de + rulaje face exact asta pe acelasi buton but_modifica, schimbandu-i imaginea in + save_sus.bmp cand intra in editare — verificat pe cod, rulaje.vc2:4716-4773. + Se copiaza reteta de acolo. +

+ +
+ + + + + + + + + + +
ActiuneCe salveazaCum
Butonul de antet, a doua apasaretot antetul, adica exact cele 14 campuri pe care le stie procedura: ruta, + delegat, agent, masina, data/ora expedierii, adresa de facturare, listare detaliata, + text aditional, tip SAF-T, eFactura, plus serie / numar / data / scadentape loc pack_facturare.modifica_date_factura. + Nu atinge articolele si nu recalculeaza nicio suma. Zece campuri se scriu neconditionat + in VANZARI, deci apelul trimite antetul intreg, nu doar ce s-a schimbat; + cele patru de identitate se propaga dupa ID_FACT in inca cinci tabele.
Terminatot documentul, ca aziruta se alege dupa ce s-a schimbat — vezi sectiunea 7
+
+

+ Asta acopera cazul in care vrei sa corectezi doar delegatul sau data scadentei: apesi creionul, + corectezi, apesi discheta, inchizi. Articolele nici nu se ating. +

+ +

+ Ce deschide efectiv butonul — si de ce nu chiar tot, deocamdata. + „Tot antetul” ramane intentia, dar campurile nu au aceeasi ruta de scriere. S-a cautat exhaustiv, + si in Oracle si in tot codul VFP: pentru o parte din ele nu exista nicio procedura care sa le + schimbe pe un document deja emis. Nu e o scapare de proiectare, e starea codului de azi. +

+
+ + + + + + + + + + + + +
Grup de campuriEtapa IEtapa II, cu regenerare
cele 14 — serie, numar, data, scadenta, ruta, delegat, masina, agent, + data/ora expedierii, adresa de facturare, text aditional, listare detaliata, tip SAF-T, + eFacturase deschid salvate pe locla fel
tip document, client, valuta, zi curs, sursa, gestiune sursa, politica de preturi, + si incasareablocate cu explicatie la hoverse deschid modificarea lor marcheaza documentul pentru + regenerare, aplicata la Termina
analiticele — venit / cheltuiala, sectie, responsabil, lucraredoar afisare se modifica din editarea notei + contabile, nu de aici
+
+

+ Alegerea de a le tine blocate in etapa I, in loc sa se deschida cu avertisment: un camp + care se lasa modificat dar nu se salveaza e o capcana. O modificare pierduta in tacere e mai rea + decat un camp inca blocat, iar hover-ul spune de ce. +

+ +

3Un singur buton de adaugare, cu meniu

+

+ In loc de doua butoane („adauga tot” si „alege”), unul singur care deschide un meniu — acelasi + tipar folosit deja de butonul de modificare din lista de facturi. Optiunile se schimba dupa sursa + documentului, cu doua constante: ultimele doua optiuni sunt aceleasi peste tot — + Cauta in lista de preturi…, cu pretul din politici, si Alege din nomenclator…, cu + pretul tastat. Sursa umple documentul, nu il inchide: pe o factura din comanda se poate adauga + oricand un articol liber, si se poate si sterge — clientul mai vrea ceva, sau vrea sa schimbe. +

+ +
+
+

Factura din comanda

+ +
+
+

Factura din contract

+ +
+
+
+
+

Factura din avize

+ +
+
+

Factura din lista de preturi

+ +
+
+
+
+

Factura de retur exista deja

+ +
+
+

Factura venita din ROAAUTO la modificare

+ +
+
+ +
+ + + + + + + + + + + + + + + + +
Cele doua mecanisme de returCum e aziCe se schimba
Factura de retur
document de sine statator
exista deja alegi facturile clientului dintr-un browse cu + selectie multipla, iar liniile se populeaza din ele: gestiunea si pretul de achizitie + vin neschimbate din linia originala, pretul de vanzare se recalculeaza pe curs doar + daca moneda nu e nationala. Antetul primeste singur textul „RETUR FACTURA …”.se muta ca atare. Nu se reproiecteaza nimic din ce functioneaza — in special nu + mostenirea gestiunii si a pretului de achizitie.
Retur de articole
intr-o factura de vanzare normala
alt mecanism, complet separat: factura sursa se alege per articol, iar gestiunea + nu vine din factura sursa, ci se alege. Butonul e vizibil doar pe facturile din lista de + preturi.capata si alegerea la nivel de document, dupa modelul de mai sus; calea per-articol + ramane, pentru cand returnezi un singur lucru
Cantitate partiala si stergereexista deja coloana isi schimba capul in + Cant. max. de returnat, serverul calculeaza maximul, iar o linie stearsa isi + elibereaza cantitatea inapoinimic — se pastreaza intocmai, ca si interdictia de retur dintr-un retur
Articole din afara facturilor sursanu se poate lista de articole este continutul + facturilor alese, deci nu ai de unde alege altceva — aceeasi cauza ca la comandaoptiunea din lista de preturi devine disponibila si aici. O linie de retur fara + factura originala e permisa, ca pe orice alt document — returul nu face exceptie. + Consecinta: pe acele linii gestiunea si pretul de achizitie se aleg, nu se + mostenesc, iar maximul returnabil calculat pe server nu se aplica, pentru ca nu exista + cantitate originala fata de care sa se limiteze.
+
+ +

+ „Alege…” deschide un dialog de selectie cu o coloana de bifat si criterii de cautare, dupa modelul + importului din ROAACNPRO: buton activ doar cand documentul are sursa, populare aditiva in cursorul + local, nicio scriere in baza pana la salvare. Spre deosebire de modelul acela, la zero rezultate + se spune de ce, nu se inchide in tacere. +

+

+ Azi „adauga tot” exista, dar e o iconita fara caption si fara tooltip, asezata lateral intre doua + griduri — si nu acopera contractele: tipurile 2, 6, 26 si 52 lipsesc din conditiile lui de + vizibilitate, desi avizele sunt acolo. +

+

+ decis „Adauga tot din comanda” / „Adauga tot din avize” raman + incarcari complete — S4 nu se restrange la o lista de preturi (decizia 39). Grila care le + alimenteaza (crsarticole) nu e doar sursa listei: e si registrul cantitatii ramase de + facturat, citit ca sa se decida inchiderea automata a comenzii sau a avizului cand o linie se + sterge. Cautarea pe server ramane cum se vede mai sus (fara incarcare in masa); ce se decupleaza e + bookkeeping-ul cantitatii ramase, intr-un registru propriu — ca sa poata disparea incarcarea si pe + comanda si pe aviz fara sa strice inchiderea automata a documentului sursa. +

+ +

4Valuta si data cursului

+

+ Doua concepte diferite, care azi impart acelasi camp mereu vizibil. Campul „Data curs valutar” + nu e eliminat niciodata in functie de valuta — doar pe retur — deci apare si pe facturi in lei + unde nu are ce cauta. +

+
+ + + + + + + + + + + + + + + + + + + +
CazCe e in documentCe arata antetul
Factura in lei,
articole in lei
tot in lei, niciun pret din politica in valutafara valuta fara data curs
+ Nimic de ales, nimic de convertit.
Factura in lei,
articole cu pret in valuta
documentul e in lei; unele articole au pretul din politica in valuta si se convertescapare Data curs valutar
+ Cu explicatia langa el: preturile in valuta se convertesc in lei la cursul acestei + zile; documentul si contabilitatea raman in lei.
Factura in valuta
Invoice, credit note, retur valuta, factura fiscala valuta pe contract
documentul insusi e in valuta; articolele au pret in valutaValuta Data curs valutar
+ Amandoua, ca azi.
+
+

+ Al treilea caz nu se alege din formular: e decis de tipul documentului la deschidere, iar + selectorul de valuta e scos din formular cand documentul nu e de tip valuta. Deci „factura in + valuta” nu e o bifa pe care o pune operatorul, ci felul documentului. +

+

+ Unificarea rezolva o problema care azi nu e rezolvabila. Cazul al doilea nu se poate sti la + deschiderea antetului — se afla abia dupa ce se incarca articolele, cand antetul de azi e deja + inchis. In formularul unificat antetul si articolele sunt in aceeasi fereastra, deci campul poate + aparea in momentul in care intra pe grid primul articol cu pret in valuta. Din acelasi motiv + dispare si mecanismul bug-ului #16: nu mai exista un antet inchis la care sa te intorci din + formularul de curs. +

+

+ decis Validarea cursurilor se restrange la valuta cautata (decizia + 42). Pe varianta filtrata a cursoarelor, verifica_cursuri_valute nu mai ruleaza + global la deschiderea formularului, ci doar pe valuta articolului adus in grid. Un curs lipsa pe + alta valuta nu mai e semnalat la deschidere, ci abia cand se ajunge la un articol pe acea + valuta — diferenta e asumata, se consemneaza la testare, nu e regresie. +

+ +

5Discountul pe linie

+

+ Verificat pe formularul de azi si pe tabela. Raspunsul e mai simplu decat parea: si procentul, si + valoarea exista deja — dar nu in grid. +

+
+ + + + + + + + + + + + + + + + + + + +
CeCum e aziCe se schimba
Procent si valoare unitara, ca intrare pentru operatorexista deja in dialogul de articol care se deschide la + fiecare adaugare: trei campuri — procent, suma in lei, suma in valuta — orice tastezi in + unul recalculeaza pe celelalte.Dialogul dispare odata cu unificarea. Cele doua campuri devin coloane in grid, + cu acelasi calcul in spate.
Ce se stocheazao singura coloana VANZARI_DETALII.DISCOUNT_UNITAR, + NUMBER(22,6) — valoare absoluta pe unitate. Nu exista coloana de procent. + Aceeasi coloana e si pe politica de pret, deci discountul poate veni din politica.Nimic. Modelul de date nu se atinge — procentul ramane doar mod de introducere.
Discountul in grid, aziinconsecvent pe factura in lei coloana e read-only; pe + factura in valuta e editabila, dar nu are handler de recalcul — o editare directa in + celula schimba valoarea fara sa refaca totalurile.Se repara odata cu mutarea in grid: aceleasi validari ca in dialogul de azi.
+
+

+ De verificat separat, inainte de a incepe: daca discountul unitar apare pe rapoartele tiparite si + daca e transmis in eFactura si SAF-T. Daca nu apare pe hartie, documentul tiparit nu explica + totalul. +

+

+ decis Discountul de DOCUMENT (nu cel de linie, de mai sus) se + repartizeaza proportional pe cote de TVA, nu pe cota maxima cum face codul de azi (decizia 59). + E automat, nu cere nimic de la operator. In banda de totaluri (sectiunea 1) se vede o singura suma; + pe factura tiparita, cu cote mixte, pot aparea mai multe randuri „Discount X % Factura", cate unul + per cota. +

+ +

6De unde se intra in formular

+

+ Cerinta e ca formularul sa poata fi deschis cu sursa deja completata, fara sa o mai alegi. + Doua cai fac deja exact asta. +

+
+

exista Din pagina Comenzi

+

Butonul de pe randul de comanda trimite comanda selectata si deschide formularul cu ea + completata. Ascuns cand comanda e deja facturata.

+

exista Din programul de contracte

+

Butonul de pe grila de contracte trimite contractul selectat si deschide acelasi formular, + cu contractul completat. Nu mai alegi nimic.

+

generic Din meniu

+

Fara sursa precompletata — alegi comanda sau contractul in formular, ca pana acum.

+
+

+ Ce merita schimbat, si atat: sursa se transmite azi prin variabile globale, nu ca parametru. + De aceea calea generica de comanda e obligata sa goleasca explicit globalul inainte de apel, ca sa + nu ramana precompletata cu ce era in sesiune — iar calea de contract nu face acelasi lucru. + Un parametru explicit inchide subiectul. Nu presupune nicio integrare noua de contracte. +

+ +
+ +

7Ce se scrie la Termina

+

+ Un singur formular, dar nu o singura cale de scriere. ID_FACT se pastreaza in + toate cazurile. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Ce s-a schimbatCum se scrieEfect
Antet — cele 14 campuri pe care le stie procedurape loc modifica_date_facturaaceeasi procedura ca butonul de antet pe a doua apasare; propaga serie / numar / data dupa ID_FACT
Antet — tip document, client, valuta, zi curs, sursa, gestiune sursa, politica de preturi, + si tot ce tine de incasareregenerareverificat pe cod: nu exista nicio cale de a le scrie pe un document emis — nici in + Oracle, nici in VFP. Singura ruta e drumul de emitere, adica regenerarea. Pana cand ea + exista (etapa II), campurile raman blocate.
Antet — analiticele: venit / cheltuiala, sectie, responsabil, lucrarenu de aicise editeaza din editarea notei contabile, care le trateaza la nivel de linie de + nota. Formularul unificat le arata, nu le scrie — ca sa nu existe doua ferestre care + modifica acelasi camp cu intelesuri diferite.
Explicatia si codul fiscal al unei liniipe loc modifica_explicatie_articoldoua coloane, nicio suma
Cantitati, preturi, linii adaugate sau sterse, discount, gestiune, cota TVAregenerare stergere + reemitere, in aceeasi tranzactiedocumentul vechi sters = 1; cel nou scris pe drumul normal de emitere; notele si rulajele refacute
Nimicnimicdocumentul ramane neatins
+
+

+ decis Un singur cod de scriere contabila (decizia 35). + Regenerarea de mai sus scrie pe acelasi drum si la emitere, si la editare (etapa II): + scrie_factura2 → contabilizeaza_articol, cu parametrul de cont de la + sectiunea 10. Canalul oscrie_in_fisiere, folosit azi de #6, nu intra in #13 — + doua implementari ale aceleiasi reguli de contare, una in pachet si alta in VFP, ar trebui tinute in + pas manual la fiecare schimbare. Nu exista in acest formular editare directa a randului din + ACT_TEMP. +

+ +

8Ce trebuie verificat inainte

+
    +
  • de verificat + ID_FACT refolosit la reemitere. Azi se ia din secventa, neconditionat. + Varianta care il cauta dupa numar, serie si data exista in pachet, dar e comentata — si + filtreaza STERS = 0, deci trebuie citita inainte de stergere.
  • +
  • de verificat + Stergerea si reemiterea in aceeasi tranzactie, fara commit intre ele.
  • +
  • decis + La reemitere se scriu valorile din formular, nu se reciteste sursa (decizia 54). Intrebarea + nu mai e ce garda punem peste re-derivare — e daca re-derivarea chiar se produce; raspunsul lui + Marius: nu trebuie sa se produca. Fara avertizare si fara confirmare, a fost respinsa explicit.
  • +
  • decis + Atasamentul PDF al documentului vechi se sterge la reemitere, nu se remigreaza (decizia 53). + Documentul reemis porneste curat; PDF-ul se regenereaza la prima listare.
  • +
  • decis + Facturile de marfa in custodie (tipurile 48 si 49) sunt editabile prin #13 (decizia 60). + Cu restrictia pastrata: pe ele nu se pot adauga articole gestionabile + (IN_STOC <> 0).
  • +
  • decis + Singura restrictie de modificare e ca documentul sa nu fi fost trimis in eFactura (decizia + 62). Fara nicio conditie de data, fara retroactivitate.
  • +
  • decis + Auditul (data + utilizator pentru adaugare, modificare si stergere) se afiseaza in gridul din + frm_facturi, nu in formularul unificat (deciziile 63 si 65). Formularul nu capata + niciun control nou din cerinta de audit.
  • +
  • decis + do_modifica de pe frm_facturi ramane activ pentru multi-selectie + (decizia 52). Formularul unificat preia doar cazul cu un singur document.
  • +
  • de verificat + Descarcarea de gestiune pe proforma, pe partea Oracle.
  • +
  • de verificat + Discountul unitar in listari si in eFactura / SAF-T.
  • +
  • de verificat + Validarea datei de curs pe avizul de lucrare — acolo e ceruta neconditionat, deci + ascunderea campului ar bloca finalizarea. Se repara impreuna, nu doar se ascunde.
  • +
  • de verificat + Legatura dintre linia de retur si linia originala. In program se acumuleaza perechi + articol-factura si se trimit la salvare, dar daca serverul le pastreaza intr-o coloana nu se + poate stabili din codul de aici. De asta depinde daca formularul poate arata din ce factura + vine fiecare linie de retur.
  • +
  • raspuns + Ce face pachetul ROAAUTO cand factura primeste o linie pe care devizul nu o stie: + nimic — nu citeste deloc tabelele de vanzari. Nu mai blocheaza nicio estimare, vezi + sectiunea 9.
  • +
  • raspuns + Nepotrivirea de afisare ROAAUTO intre ecranul de deviz si factura retiparita — + decizia 28: acceptata. Conditia e ca MANOPERA si MATERIALE sa fie conform devizului; + restul articolelor pot exista pe factura fara sa apara pe deviz. Nu se cere cod nou in ROAAUTO. + Vezi sectiunea 9.
  • +
  • raspuns + Stergerea unei linii venite din comanda — decizia 29: fara protectie. Se sterge + ca oricare alta; comanda ramane cu cantitatea nefacturata si va aparea, corect, facturata + partial. Nu se cere confirmare, nu se marcheaza „refuzat”.
  • +
  • raspuns + Coordonarea cu #6 — decizia 30: nu se coordoneaza. #13 incepe dupa ce #6 se + termina, deci nu exista doi scriitori simultan pe acelasi fisier. Decizia 26 (analiticele + read-only in #13) ramane — motivul ei e semantic, nu de calendar. Decizia 38: fluxul de + editare al lui #6 (editare directa a randului din ACT_TEMP, prin + oscrie_in_fisiere) nu se retrage odata cu #13 — coexista cu regenerarea prin + pack_facturare, iar decizia despre unificarea lor se ia mai tarziu, dupa ce #13 + livreaza si se vede in practica daca intretinerea celor doua drumuri doare cu adevarat.
  • +
  • decis + Bug-ul de dezalocare POS se repara in trecere, odata cu mutarea antetului din sectiunea 2 + (decizia 40) — nu e o mutare pur mecanica, testarea trebuie sa acopere si dezalocarea POS pe + calea veche.
  • +
  • raspuns + Cu ce cont de venit intra un articol fara politica de pret. Nu printr-o politica de pret + implicita — decizia 24 s-a retras. Decizia 27: contul de venit se deriva, + din CORESP_CONT_VENCHELT pentru articolele gestionabile si din + NOM_ARTICOLE.CONT (sau, implicit, 704) pentru cele negestionabile; derivarea + se face in VFP (decizia 27-bis), pack_facturare ramane neatins. Reteta si + costurile: sectiunea 10.
  • +
+ +

9Facturile venite din ROAAUTO

+

+ ROAAUTO emite facturi din formularul lui de devize, dar prin acelasi pachet si in aceleasi tabele. + Formularul unificat le vede fara niciun cod special. Ce lipseste nu e recunoasterea, ci scrierea. +

+
+ + + + + + + + + + + + + + +
IntrebareRaspunsul de pe cod
Scrie in aceleasi tabele?da acelasi PACK_FACTURARE, acelasi drum prin + tabela de staging catre VANZARI_DETALII. Tip de document -12.
Se vad in editorul nou?da deja, testat pe date reale — detectia liniilor nu + filtreaza dupa tipul documentului.
Se pot adauga articole la emitere?da prin Alte servicii, la emiterea facturii finale. + Articolele vin din nomenclatorul brut, sunt limitate la cele fara stoc, iar + pretul se tasteaza — nu vine din politici. Raman linii proprii, nu se cumuleaza in + manopera sau materiale.
Se pot modifica dupa emitere?nu butonul se blocheaza imediat ce comanda are numar de + factura, metoda de stornare a comenzii e goala, iar gridul din editorul nou e read-only. + Singurul instrument asupra unei facturi emise e stergerea ei intreaga. Asta e + capacitatea care se cere.
Cum arata liniile lor?cele venite din deviz nu sunt articole: sunt sume cumulate pe categorii — manopera, + materiale, avans, discount — pe pseudo-articole cu cod negativ. Langa ele pot sta articole + reale. Niciunele nu au gestiune, nici macar cele reale.
+
+

+ Ce se cere: aceeasi adaugare de articole si la modificarea documentului, nu doar la emitere — + si nu numai pentru facturile auto, ci pentru orice tip de factura, din lista de preturi sau direct + din nomenclator. +

+

+ corectie „Alte servicii” nu e jumatatea de drum pe care o + credeam. Mecanismul acela functioneaza tocmai pentru ca ocoleste contabilizarea: + facturile de deviz merg pe o cale paralela, care nu genereaza nicio nota contabila de venit. + Pe fluxul normal de facturare, unde nota se genereaza, un articol fara politica de pret e + respins cu eroare. Deci nu se generalizeaza un mecanism existent — se rezolva altfel (mai jos). +

+

+ rezolvat Devizul nu se dezechilibreaza. Pachetul ROAAUTO + nu citeste deloc tabelele de vanzari — nici antetul, nici liniile. Legatura pe care o face + dupa emitere e in sens invers: pune numarul documentului pe comenzile si lucrarile lui. Deci o + linie adaugata din formularul unificat nu strica niciun total si nu cere niciun apel suplimentar + catre ROAAUTO. +

+

+ decis Nepotrivirea de afisare e acceptata (decizia 28). + Totalul devizului se calculeaza din structura proprie ROAAUTO, deci nu va arata niciodata + linia adaugata; in schimb factura retiparita o va arata, pentru ca relistarea citeste + direct liniile documentului. Conditia ceruta de Marius e alta: MANOPERA si MATERIALE sa fie + conform devizului — restul articolelor pot exista pe factura fara sa apara in ecranul de + deviz. Nu se cere cod nou in ROAAUTO ca sa aduca ecranul de deviz la zi. +

+ +
+ +

10Contul de venit pentru articole adaugate din nomenclator

+

+ Intrebarea ramasa deschisa in sectiunea 8: cu ce cont de venit intra un articol adaugat + „Alege din nomenclator…”, fara politica de pret. Decizia 27 (mai jos) nu s-a schimbat; reteta prin + care ajungea la pack_facturare s-a abandonat intre runda 8 si runda 9-10, in favoarea + unui parametru nou. +

+ +

+ respins Decizia 24 — articolul ales din nomenclator + primeste id_pol-ul unei politici de pret implicite. Retrasa in runda 8: evita cod + nou in pack_facturare, dar cu pretul unei configurari cu contabilul (care politica, + cu ce SCC) si al unui cont de venit uniform pentru orice articol adaugat asa. + Inlocuita de decizia 27. +

+ +

Decizia 27 — contul de venit se deriva, pe doua ramuri

+
+ + + + + + + + +
ArticolSursa contului de venit
gestionabil
cont de gestiune clasa 3xx
CORESP_CONT_VENCHELT.CONT_VENIT, cautat pe CONT = contul de + gestiune al liniei
negestionabilNOM_ARTICOLE.CONT, daca e deja cont de clasa 6xx sau 7xx; + altfel, implicit 704
+
+ +

+ respins Decizia 27-bis — reteta VFP in 4 pasi: calculeaza + contul, cauta o politica a carei nota il are deja, insereaza articolul in ea cu + pack_preturi.adauga_politica_pret_art, trimite id_pol. Abandonata in + runda 9 (decizia 34): intrebarea deschisa era cum alege programul nota contabila de vanzare pe + factura din comanda, care nu are politica de pret (decizia 32) — iar raspunsul lui Marius se + refoloseste direct pentru articolul ad-hoc. Nu mai e nevoie de politica tehnica, nici de + interogarea inversa pe SCC, nici de inserarea articolului intr-o politica. +

+ +

Decizia 34 — contabilizeaza_articol primeste contul, printr-un parametru nou

+

+ Marius, textual: „poti sa adaugi parametrul contul contabil la contabilizeaza_articol.” + Verdictul de proiectare, in trei randuri, pentru ca schimba forma literala a cererii: + contabilizeaza_articol ia azi un singur parametru, + detalii_articol VANZARI_DETALII_TEMP%ROWTYPE — contul nu intra pe el literal, ar fi + cosmetic. Intra ca coloana noua VANZARI_DETALII_TEMP.CONT_VENIT VARCHAR2(4) NULL, + populata printr-un parametru nou V_CONT_VENIT IN VARCHAR2 DEFAULT NULL, adaugat + la coada lui adauga_articol_factura (procedura chemata direct din VFP) — + contabilizeaza_articol il primeste automat, fara sa i se schimbe semnatura. + Precedentul e in aceeasi lista de parametri: V_TAXCODE si V_LOT sunt deja + doi parametri adaugati ulterior, la coada, cu DEFAULT NULL, iar apelul VFP e pozitional + si se opreste la V_LOT. +

+

+ Cei trei apelanti interni nu se ating deloc — scrie_factura2, + scrie_factura_avize_retur si scrie_aviz_retur fac + SELECT * BULK COLLECT intr-un TABLE OF ...%ROWTYPE, deci coloana noua le + traverseaza fara nicio schimbare de cod. Ramura noua se activeaza doar pe + detalii_articol.cont_venit IS NOT NULL: infasoara blocul FACT-024 (garda + ramane litera cu litera pe ramura veche — o linie fara politica si fara cont trimis continua + sa cada pe eroare), n-are cursor deloc, deci descarca_gestiune ruleaza exact o data + prin constructie — bug-ul de set multi-rand din ramura veche nu se mosteneste, fara sa se repare + ramura veche. +

+

+ de retinut la implementare Parametrul se cableaza in doua locuri + VFP, nu unul: frm_facturare_articole.do_scrie_articole si + frm_facturare_articole2.do_scrie_articole sunt doua clase distincte, fiecare cu apelul + ei RPC propriu. Daca se vrea CONT_VENIT pastrat si dupa fapt in + VANZARI_DETALII, lista de coloane explicita din scrie_in_vanzari (24 de + coloane azi) trebuie extinsa — altfel coloana traieste doar in VANZARI_DETALII_TEMP si + ACT_TEMP. Articolul compus nu e o gaura aici: e exclus structural — proprietatea + COMPUS traieste pe perechea (articol, politica), prin ID_POL_ART, iar o + linie fara politica n-are asa ceva. +

+ +

Decizia 36 — SCD si CU_TVA pe ramura fara politica

+

+ Nota veche mai furniza si SCD, CU_TVA, IN_VALUTA, + EXPLICATIE, ID_VENCHELT, ID_SECTIE — nu doar contul. + Verificarea n-a gasit nicio sursa alternativa in cod pentru primele doua (nici optiune de firma + existenta, nici cont pe partener, nici flag de scutire pe articol sau client), deci sunt decizii de + produs, si Marius le-a luat: +

+
+ + + + + + + + +
CampSursa noua
SCDo optiune de firma noua, cu 4111 ca implicit — acelasi tipar in trei + niveluri deja folosit de pack_facturare.scrie_incasare2 pentru + RF_CONT_INCASARE_*: parametru explicit → optiune de firma → constanta + hardcodata daca optiunea lipseste. Mecanismul (tabelul OPTIUNI, + PACK_SESIUNE.getoptiunefirma, ecranul generic frm_optiuni) exista + de 15+ ani, deci cheia noua nu cere niciun cod VFP nou, doar randul in tabel. + ASCD nu se atinge — se calculeaza deja din V_SCD.
CU_TVAderivat din cota liniei, nu fixat: 1 daca proc_tvav > 0, + altfel 0. Elimina prin constructie riscul gasit la verificarea adversariala: cu + CU_TVA = 1 fixat, o linie scutita ar fi intrat in comparatia de maxim din + nproc_tva_max si ar fi putut imprumuta coloana si taxcode-ul ei liniei de + discount a intregii facturi. Diferenta fata de ce face azi nota e intentionata, se + consemneaza la testare.
+
+

+ Lista de parametri noi ramane la unul singur, V_CONT_VENIT: SCD + vine din configurare, CU_TVA se deriva in pachet din date deja prezente pe rand. +

+ +

Decizia 37 — cheia optiunii de firma

+

+ RF_CONT_ART_FARA_POL, aliniata la familia RF_CONT_INCASARE_* — aceleasi + optiuni care alimenteaza azi SCD in acelasi pachet, deci apar grupate alaturi in + ecranul de optiuni. VARTYPE = 'CHARACTER', VARVALUE = '4111', + PROGRAM = 'ROAFACTURARE', dar PROGRAME larg — apelul traieste in + COMUN\clase\ofacturare.vc2, prezent in toate cele sapte produse ale suitei. + Fara validare de cont, nici la salvare nici la citire — acelasi tipar ca + RF_CONT_INCASARE_*, care nu valideaza de 15 ani. Garda gratuita ramane lungimea: un + VARVALUE peste 4 caractere da ORA-12899 la + INSERT INTO ACT_TEMP — esec zgomotos, nu cont tacut gresit. Un cont inexistent in + planul de conturi, scris din greseala in ecran, ajunge pe nota neschimbat — risc acceptat constient, + acelasi pe care produsul il are deja pe optiunile de tip cont. +

+ +

+ Intrebarea lui Marius, verificata: „In ROAACNPRO si pe factura din comanda / contract se + adauga articole fara politica — de ce nu si aici?” Raspuns, neschimbat fata de v7: + ROAACNPRO nu cheama deloc contabilizeaza_articol — are propria contabilizare, + pack_acn.salveaza_regdoc. Pe contract, articolele vin din + cursor_contract / cursor_preturi, cu id_pol deja atasat de + cursor. Deci FACT-024 ramane blocantul pe linia ad-hoc — parametrul de mai sus e + cum se evita, nu un motiv sa fie sarit. +

+ +

Ce nu e gratuit

+
    +
  • Regresie pe toata suita, nu doar ROAFACTURARE. pack_facturare e comun la + ROACONT / ROAGEST / ROACONTRACTE / ROAAUTO / ROAACNPRO, si ramura noua e atinsa de apelul comun + din ofacturare.vc2.
  • +
  • Doua clase VFP cablate separat. frm_facturare_articole.do_scrie_articole si + frm_facturare_articole2.do_scrie_articole isi construiesc fiecare propriul apel RPC — + parametrul se adauga in ambele, nu o singura data.
  • +
  • Lista de coloane a lui scrie_in_vanzari trebuie extinsa explicit daca se + vrea CONT_VENIT pastrat si dupa fapt in VANZARI_DETALII, nu doar in + VANZARI_DETALII_TEMP / ACT_TEMP.
  • +
  • Un cont fara precedent (cazul 704) intra direct pe linie, fara sa mai treaca + printr-o politica sau o nota existenta — spre deosebire de reteta veche, nu mai cere configurare + cu contabilul, dar nici nu mai e validat impotriva planului de conturi (decizia 37).
  • +
+ +
+ +

11Puncte marunte ramase

+

+ Nu blocheaza nimic din ce e mai sus; se inchid la implementarea povestii respective. Enumerate ca sa + nu se piarda intre runde — se merge pe recomandare daca Marius nu + spune altfel. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PunctPovesteSe merge pe
Textul tooltip-urilor pentru analiticele read-only si pentru grupul de incasare blocatS3bformularea propusa in raport, sectiunile 2.2 si 5.3
Eager vs. lazy pentru lookup-urile Oracle din Init (delegat / masina + ultimei facturi, casa)S3b, S3lazy, ca sa nu se plateasca la fiecare depliere
_checkbox1 vs. chkDetaliat — campul unic de „listare + detaliata”S3b, S1nedecis — se inchide pe cod la implementare, nu prin alegere
Forma lui toSursa: obiect scatter (duck-typing) sau clasa dedicataS3cduck-typing, ca azi — o clasa noua n-ar schimba nimic functional
Conversia comenzii si a contractului: un commit sau douaS3cun singur commit — aceleasi trei fisiere comune, separarea nu reduce + regresia
goComanda = '' redundant la Cw3.do_actiune dupa conversieS3cse lasa, marcat explicit „intentionat” in diff
Contractul cu doua meniuri (articole vs. rate): eticheta generica sau diferentiata pe + OPT_FACTURARES4bdiferentiata — „Alege ratele de facturat…” e gresita pe contractele cu + articole
„Alege facturile de returnat…” — ramane doar la antet sau capata incarcare aditiva din + baraS4bramane la antet in etapa I; mutarea e poveste separata
But_renunt1 / But_reset1 capata Caption pentru + consistenta cu bara etichetataS4bda — decizia 13 cere etichete, nu iconite mute
Ordinea si formularea optiunilor din xmenu() per sursaS4btabelul din sectiunea 3 a raportului
UX-ul contractului cu doua surse pe acelasi grid conceptual (crsarticole + filtrat + crsarticole1)S4de confirmat vizual la mockup, nu pe hartie
+
+ +