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

554 lines
36 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 = <<gnIdUtil>> and p.id_pol = <<tnIdPol>>
```
```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 <data curentă între valabilitatea politicii>) 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`.