Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git, nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de cercetare pe care se sprijina modificarile din cod. O stergere acolo era definitiva. Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele fisierelor de test la care se refereau. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
36 KiB
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ă
-- 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
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:
-- 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):
-- 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
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_polde 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_articolnu e apelată pe ruta comandă, ci altă procedură" — fals pentruCOMENZI(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_articolcare 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_pole nenul dar articolul nu e membru,id_polnul 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:
- Ce arată comandă/contract, tehnic: operatorul nu alege niciodată
id_polpentru un articol anume — alege o politică (una dintre cele la care are drept, viaUTILIZATORI_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_artfiltreazăid_pol_art/id_pol IS NOT NULLla 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ă. - Acest tipar exact e deja documentat, ca fezabil, în
coresp_cont_venchelt.mdsecțiunea „e)" / J-quater punctul 3 — RPCpack_preturi.adauga_politica_pret_art(deja folosit în producție,ofacturare.vc2:15551-15587) pentru „asigură apartenența", plus trimiterea luiid_polneschimbat laadauga_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ă. - 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 pecaut_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_POLnu ajunge la cont gol, ci la eroare"). - „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șteid_pol-ul unei politici de pret implicite. RETRASĂ în runda 8... prețul lui era o întrebare de configurare cu contabilul (care politică, cu ceSCC, 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 cereSCCdiferențiat după clasa contului de gestiune al articolului (707/711/702/703/7015/7018/704). - Deci ce rămâne de făcut e exact pasul central din rețeta în 4 pași: nu „cum trimit un
id_polvalid la Oracle fără să atingpack_facturare" (asta e deja rezolvat, și demonstrat funcțional de comandă/contract) — ci „care politică, cu careSCC, 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_rataexistă deja, dar e legată strict deVANZARI_DETALII_TEMPcu semantică de „rată de contract" (detalii_rata.id_ctrobligatoriu în interogare) — nu e apelabilă pentru un rând de articol obișnuit (id_articolpopulat,id_polgol) fără o funcție nouă, analogă, înpack_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 laadauga_articol_factura) — tot cod nou înPACK_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_ratae 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 înscrie_nota, fără ocolul prinCRM_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
-- 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):
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>>
-- 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
-- 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:
-- 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)
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:
-- 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_ARTgol — dedus din structura JOIN acursor_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 cuid_pol_artgol — nu am urmărit codul de grid dinROACONTRACTE/ofacturare.vc2pentru acest caz specific. - Dacă un operator cu mai multe grupuri de politici active poate primi articole duplicate cu
id_poldiferit încrsarticole(secțiunea C) — plauzibil din structura JOIN-ului (fărăDISTINCTpea1.id_util), neverificat pe date reale. - Eșantionul
CTR_ARTICOLEe mic (27 rânduri) în baza de dezvoltare — raportul 6/27 fărăid_pol_arte 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_ARTdupă ce comanda a fost creată?) nu e stabilită, și nici comportamentul real la facturare (FACT-024/ORA-01403aș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ânduriACTcuSCCpopulat cumulat pe documentele aferente — nu am identificat ce tip de document e (nu apare înDo Casedinofacturare.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, constrangereaSYS_C0015376("ID_POL" IS NOT NULL) plusFK_COMENZI_ELEMENTE_002. In date: 7108 randuri total, 0 cuID_POLnul, 0 cuID_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_NOTAsi ajung la unSCC.
Doua observatii noi, care nu erau in raport:
- Politica
7 DISCOUNTe o politica tehnica de rutare contabila, folosita in producție. Apare pe 870 de linii de comanda si trimite la nota2 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 decatgnId_pol_pret_stoc(care e o singura politica pentru tot stocul). Tiparul „politica tehnica per scop contabil" nu trebuie inventat: exista. - 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 cuFACT-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.