# 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`.