Files
roafacturare/docs/cercetare/s4g_adaugare_articole_modificare.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
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
2026-08-11 22:17:17 +03:00

29 KiB

S4g — Adaugarea de articole la modificarea oricarui document (proiectare)

Proiectare, nu implementare. Zero fisiere de cod atinse, zero write-back, zero commit. Pe Oracle doar SELECT. Continua planul docs\plan_13_unificare_formular_facturare.md liniile 2494-2530 si cercetarile canal_cont_venit_fara_politica.md (sectiunea "Proiectarea parametrului de cont contabil"), parametru_cont_contabilizeaza_articol.md, coresp_cont_venchelt.md, optiune_firma_cont_debit.md.

STARE: cercetare/proiectare incheiata.

Sursa PL/SQL verificata direct in aceasta runda: D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql (17217 linii, confirmat wc -l). Cod VFP citit: COMUN\clase\ofacturare.vc2, COMUN\programe\ofacturare_comun.prg (ambele permise). Neatinse: COMUN\clase\ofacturare_comun.vc2, COMUN\programe\ofacturare_editare.prg (perimetrul sesiunii #6).


0. Premisele deja decise (nu se redeschid)

  • Povestea: a doua sursa de articole in meniu, "Alege din nomenclator...", disponibila si la modificarea oricarui document deja emis, inclusiv auto (tip = -12, dar livrat separat, dupa).
  • Decizia 20: din nomenclator se ofera si gestionabile, si negestionabile — nu se copiaza filtrul in_stoc = 0 al lui ROAAUTO.
  • Decizia 21: contul de gestiune are fallback, nu NULL, nu refuz — mecanism separat de cel de aici (priveste id_gestiune/Cont de gestiune al liniei, nu contul de venit), presupus deja rezolvat cand derivarea din sectiunea 3 porneste.
  • Decizia 27 (transportul inlocuit de decizia 34): contul de venit se deriva in VFP — CORESP_CONT_VENCHELT pentru gestionabile (pe contul de gestiune al liniei), NOM_ARTICOLE.CONT daca e 6xx/7xx pentru negestionabile, altfel 704.
  • Decizia 34: transportul e parametru nou, direct — nu prin id_pol/politica tehnica. Ocolul prin pack_preturi.adauga_politica_pret_art e abandonat.
  • Decizia 35: acelasi drum serveste si editarea prin regenerare — nu se proiecteaza a doua ruta de contare.
  • Decizia 36 (noua, 10.08.2026): SCD pe ramura fara politica nu e hardcodat '4111' literal — vine dintr-o optiune de firma (getoptiunefirma), cu 4111 ca implicit dublu (rand in OPTIUNI
    • fallback hardcodat in PL/SQL), tiparul deja folosit de RF_CONT_INCASARE_* in acelasi pachet.
  • "Alte servicii" ROAAUTO ocoleste complet contabilizeaza_articol — exceptie, nu jumatate de mecanism.
  • Regula deja adoptata in #13: liniile libere nu intra deloc in crsarticole — APPEND BLANK + combosql direct in crsfactura, ca id_c sa ramana 0 si do_sterge sa fie no-op prin constructie (verificat aici ca acelasi tipar se aplica si campului id_pol, sectiunea 4).
  • Ordine: intai ROAFACTURARE, apoi tip = -12.

1. Punctul central: parametru vs politica, cine castiga

1.1 Ce face azi codul, verificat direct pe sursa (nu preluat din rapoarte)

contabilizeaza_articol (ff_...:7173-7547) primeste un singur parametru, detalii_articol VANZARI_DETALII_TEMP%ROWTYPE. Primul lucru pe care il face (:7275-7302):

BEGIN
  SELECT COMPUS, ID_POL_ART
    INTO V_COMPUS, V_ID_POL_ART
    FROM VCRM_POLITICI_PRET_ART
   WHERE ID_ARTICOL = detalii_articol.id_articol
     AND ID_POL = detalii_articol.id_pol;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    ...
    RAISE_APPLICATION_ERROR(-20000, 'Articolul ... nu este definit in politica de preturi ... (FACT-024)');
END;

Daca detalii_articol.id_pol e NULL, comparatia ID_POL = NULL nu se potriveste niciodata — NO_DATA_FOUND garantat, FACT-024 garantat. Acelasi rezultat daca id_pol e populat dar articolul nu e in acea politica. cursor_articol (:7218-7271), care face tot lucrul real (scrie_nota, descarca_gestiune, discount), e filtrat pe exact aceeasi cheie (A.ID_POL = detalii_articol.id_pol AND A.ID_ARTICOL = detalii_articol.id_articol, :7270-7271) — daca SELECT INTO de mai sus a picat in NO_DATA_FOUND, cursorul ar gasi tot zero randuri (aceeasi cauza). Deci punctul de intrare in politica e unic si dublu-verificat (o data prin exceptie explicita, o data structural prin cursor).

Design-ul deja facut (canal_cont_venit_fara_politica.md:132-138) propune sa infasoare toata functia intr-un switch la nivel de metoda:

IF detalii_articol.cont_venit IS NOT NULL THEN
  <ramura noua: SCC := cont_venit, SCD := optiune de firma (decizia 36), fara cursor, fara SELECT INTO de mai sus>
ELSE
  <tot codul de azi, neschimbat, inclusiv SELECT INTO + FACT-024 + cursor_articol>
END IF;

Aceasta e deja, prin constructie, varianta "parametrul castiga necondiionat cand e populat" — cand cont_venit nu e NULL, executia nu se mai uita deloc la id_pol/politica, indiferent daca acestea ar fi rezolvat sau nu. Team-lead-ul cere explicit sa se decida asta ca punct de proiectare, nu sa ramana o consecinta implicita a formei codului — mai jos analiza celor 3 variante realiste.

1.2 Cele 3 variante

A. Parametrul castiga mereu cand e populat (= design-ul existent, ca atare).

  • Ce se strica: daca, dintr-un motiv oarecare (bug VFP, sau — mai realist — decizia 35: la regenerare, daca logica de derivare a lui cont_venit ar rula necondiionat pe toate liniile documentului, inclusiv cele scrise initial prin "Cauta in lista de preturi..." cu id_pol real), o linie ajunge la Oracle cu ambele populate, SCC-ul politicii reale e inlocuit tacut cu contul derivat generic, SCD cu optiunea de firma (posibil diferita de NOTE_CONTABILE.SCD al notei reale), ASCD/ASCC/EXPLICATIE/CU_TVA cu variantele generice ale ramurii noi. Fara nicio eroare — factura se emite, dar cu alta contare decat cea configurata prin politica. Cel mai periculos tip de defect: tace si trece verificarea vizuala (suma corecta, cont plauzibil).

B. Politica castiga mereu cand id_pol e populat (indiferent daca rezolva sau nu).

  • Ar cere schimbarea conditiei de switch din cont_venit IS NOT NULL in ceva de forma id_pol IS NULL AND cont_venit IS NOT NULL — adica: daca exista id_pol pe linie, mergi pe ramura veche necondiionat, chiar daca acel id_pol nu rezolva (caz FACT-024 de azi).
  • Ce se strica: exact scenariul in care mecanismul nou ar fi cel mai util — o linie cu id_pol populat gresit/stale (nu neaparat imposibil, doar improbabil in fluxul normal, vezi 1.3) — ar continua sa primeasca FACT-024 desi VFP a trimis explicit un cont de rezerva. Varianta cea mai fragila: defineste "castigatorul" pe prezenta unui camp, nu pe rezolvarea lui.

C. Parametrul e folosit doar cand politica nu rezolva (fallback real, nu switch pe camp).

  • Cere ca SELECT INTO ... FROM VCRM_POLITICI_PRET_ART (1.1) sa ramana necondiionat, exact ca azi (deja rulat pe fiecare linie, cost zero suplimentar), dar EXCEPTION WHEN NO_DATA_FOUND sa verifice cont_venit: daca e populat, ramura noua; daca nu, RAISE FACT-024 ca azi. Semantic corect — politica, cand exista si rezolva, nu e niciodata inlocuita tacut; parametrul e strict ce a fost gandit sa fie: o plasa pentru cazul in care politica lipseste.
  • Cost real: restructurare interna a functiei, nu doar un IF la inceput. Codul de dupa blocul EXCEPTION verifica azi IF V_COMPUS = 1 THEN ... ELSE <deschide cursor_articol> END IF (:7305,7391) — V_COMPUS ramane NULL daca s-a intrat pe EXCEPTION, deci "cade" oricum pe ramura ELSE care deschide cursor_articol (gaseste zero randuri, nu declanseaza nimic, dar nici ramura noua). Trebuie introdus un flag explicit (V_ARE_POLITICA/similar) propagat din interiorul EXCEPTION pana la punctul de decizie, marind suprafata modificata fata de varianta A (care atinge doar granita functiei, cu restul intact).

1.3 Ar putea sa apara vreodata, in fluxul normal, ambele populate?

Verificat direct in aceasta runda (nu presupus): crsfactura (cursorul VFP din care se scrie orice linie) are id_pol N(20) Null (COMUN\programe\ofacturare_comun.prg:1775) — camp nullable, deci la APPEND BLANK (mecanismul liniilor libere, confirmat de S4e ca acelasi gest se foloseste si pentru "Alege din nomenclator...") ramane .NULL. implicit, nu 0. La scriere, poArt.id_pol (scatter din randul respectiv) ajunge in apelul RPC ca Nvl(Alltrim(Str(poArt.id_pol)),[NULL]) (ofacturare.vc2:14072, identic la :18107) — literal SQL NULL cand campul e .NULL.. O linie "din nomenclator" trimite deci id_pol = NULL la Oracle prin constructie, nu doar prin conventie de utilizare — cursorul de cautare al nomenclatorului (caut_articol, ocautare.prg, deja confirmat de coresp_cont_venchelt.md sectiunea 9d) nu are coloana id_pol in output, deci nu exista niciun punct in care combosql-ul ar putea popula acest camp cu o valoare reala pentru o astfel de linie.

Ramane un singur scenariu real de ambiguitate: regenerarea (decizia 35). La regenerare, toate liniile documentului (cele scrise initial prin "Cauta in lista de preturi...", cu id_pol real, si cele adaugate prin "Alege din nomenclator...", fara id_pol) trec din nou prin acelasi drum de scriere. Daca logica VFP care calculeaza cont_venit (sectiunea 3) ar rula necondiionat pe toate liniile din grid, in loc sa fie conditionata explicit de "linia nu are id_pol", ar produce exact combinatia periculoasa din varianta A. Asta nu e un risc Oracle — e un risc de implementare VFP, dar exact tipul de risc pe care design-ul Oracle trebuie sa nu-l agraveze printr-un switch care il face invizibil.

1.4 Recomandare

Niciuna dintre cele 3 variante pure, ci varianta A (deja proiectata, cea mai simpla) plus o garda explicita pe combinatia ambigua, nu o rezolvare tacita in orice directie:

IF detalii_articol.cont_venit IS NOT NULL THEN
  IF detalii_articol.id_pol IS NOT NULL THEN
    RAISE_APPLICATION_ERROR(-20000,
      'Articolul ' || detalii_articol.id_articol ||
      ' are simultan politica de pret (' || detalii_articol.id_pol ||
      ') si cont de venit calculat — conflict netratat (FACT-0xx)');
  END IF;
  <ramura noua, ca in design>
ELSE
  <tot codul de azi, neschimbat>
END IF;

Motivare, in ordine:

  1. Combinatia nu trebuie sa apara niciodata in fluxul normal (1.3) — id_pol ramane NULL prin constructie pentru orice linie scrisa prin gestul "linie libera". Daca totusi apare, e un semnal ca ceva e stricat in populate-ul liniei (VFP a trimis ambele campuri, posibil din cauza logicii de regenerare descrisa la 1.3) — situatie de bug de investigat, nu de "rezolvat" silentios.
  2. Variantele B si C rezolva combinatia in cate o directie fixa — ambele ascund o eroare de date in loc sa o semnaleze: B ar putea reintroduce FACT-024 pe o linie unde VFP a oferit deja o solutie; A fara garda ar inlocui tacut o politica reala. O garda explicita e singurul comportament care nu presupune ca stie mai bine decat datele ce s-a intamplat.
  3. Cost de implementare minim: un singur IF suplimentar, la intrarea in ramura deja proiectata — nu restructureaza funcia (spre deosebire de varianta C), nu schimba conditia switch-ului principal (spre deosebire de B).
  4. Compatibilitate/regresie zero pe apelantii de azi: cand cont_venit e NULL (toti apelantii existenti, care nu cunosc inca acest parametru), executia intra direct pe ELSE — garda nu se evalueaza niciodata, comportamentul e identic bit-cu-bit cu azi. Cand cont_venit e populat si id_pol e NULL (cazul intentionat, "din nomenclator"), garda trece nevazuta, ramura noua ruleaza normal.

Numele codului de eroare (FACT-0xx in schita de mai sus) ramane de ales de Marius, distinct de FACT-024 (care ramane, neschimbat, pentru cazul "niciuna din cele doua" — linie fara politica si fara cont).


2. Suprafata de schimbare pe Oracle

Deja proiectata si verificata adversarial in canal_cont_venit_fara_politica.md si parametru_cont_contabilizeaza_articol.md; rezumat + completarea cu decizia 36 si garda de la sectiunea 1.4:

  1. Coloana noua: VANZARI_DETALII_TEMP.CONT_VENIT VARCHAR2(4) NULL (fara NOT NULL, fara CHECK — acelasi tipar ca ACT_TEMP.SCC). Optional, recomandat pentru trasabilitate: VANZARI_DETALII.CONT_VENIT, plus extinderea listei explicite de coloane din scrie_in_vanzari (PACK_FACTURARE:13705-13757, confirmat ca listeaza 24 coloane explicit, nu SELECT * — de extins cu o a 25-a daca se alege trasabilitatea).
  2. Parametru nou pe adauga_articol_factura (ff_...:4989-5015), la coada, dupa V_LOT: V_CONT_VENIT IN VARCHAR2 DEFAULT NULL — apelul VFP existent (pozitional, se opreste la V_LOT, confirmat la doua locuri, ofacturare.vc2:14069-14091 si :18089-18114, doua clase distincte cu aceeasi metoda, nu o duplicare) ramane neschimbat, echivalent cu "trimite NULL". Plumbing identic cu V_CONT/V_CONT2, o coloana in plus in INSERT INTO VANZARI_DETALII_TEMP (ff_...:5222-5282).
  3. contabilizeaza_articol insasi NU primeste parametru nou — ramane VANZARI_DETALII_TEMP%ROWTYPE; coloana noua ajunge automat prin SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP (:6063), fara nicio schimbare la cei 3 apelanti interni (scrie_factura2, scrie_factura_avize_retur, scrie_aviz_retur).
  4. Ramura noua in contabilizeaza_articol, cu garda de la sectiunea 1.4:
    • SCC := detalii_articol.cont_venit.
    • SCD (decizia 36): PACK_SESIUNE.getoptiunefirma('FACT_SCD_ARTFPRET'), cu fallback hardcodat '4111' cand optiunea lipseste/e goala — optiune noua in tabelul OPTIUNI (VARTYPE='CHARACTER', script de migrare idempotent, tiparul exact al RF_CONT_INCASARE_*, deja folosit in acelasi pachet, optiune_firma_cont_debit.md sectiunea 3.4). Ramurile de aviz raman '418'/'461', neschimbate.
    • ASCD/ASCC din GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD/V_SCC) — acelasi fallback deja folosit necondiionat pe ramurile de aviz azi.
    • EXPLICATIE din detalii_articol.explicatia (parametru deja existent, V_EXPLICATIE).
    • ID_VENCHELT/ID_SECTIE din pack_facturare.nid_venchelt/nid_sectie_stoc (fallback de sesiune deja folosit ca prioritate azi).
    • IN_VALUTA din pack_facturare.nin_valuta (parametru obligatoriu, fara DEFAULT, mereu curent — nu o presupunere, sursa solida).
    • CU_TVA: hardcodat 1, cu efect colateral real si masurat, nu "inofensiv" — parametru_cont_contabilizeaza_articol.md sectiunea 2b arata ca pe combinatia specifica "linie fallback cu TVA 0% + discount global pe factura + acea linie e prima/singura vazuta" valoarea forteaza nproc_tva_max/nid_jtva_coloana pentru toata linia de discount a facturii. Ramane "de confirmat cu Marius", cu argumentul mai tare decat in propunerea initiala.
    • Apeluri o singura data (nu in bucla): scrie_nota(...), descarca_gestiune(...) (garda identica, nscadere_stoc=1 AND id_gestiune<>-1000 AND in_stoc=1), discount (garda identica).
    • Articole compuse (V_COMPUS=1) exclus structural — o linie fara id_pol nu poate avea ID_POL_ART, deci intrebarea "e compus" nu se poate pune pe aceasta ramura (confirmat pe schema view-ului, nu presupus).
  5. Ordinea de deploy: DB inainte de EXE. Parametrul nou are DEFAULT NULL, deci EXE-ul vechi ruland pe DB-ul nou functioneaza neschimbat (nu trimite parametrul). EXE-ul nou ruland pe DB-ul vechi (fara coloana/parametru) ar esua la primul apel cu al 27-lea argument — deci EXE-ul nou nu poate merge inaintea migrarii DB. Ordine standard, fara surpriza.
  6. Regenerarea (decizia 35): initializeaza_date_factura reseteaza starea de sesiune (DELETE FROM VANZARI_DETALII_TEMP, nid_act := 0) la fiecare emitere — nimic din ramura noua citeste vreo stare presupunand "documentul e nou". Calea Oracle e identica la regenerare, cu o exceptie separata, in alt pachet: reemiterea cu acelasi ID_FACT ar da azi ORA-00001 pe PK_DOCUMENTE in PACK_CONTAFIN.SET_IDFACT — problema deja proiectata separat (idfact_refolosire_si_documente.md), nu intersecteaza si nu invalideaza design-ul de aici, dar ramane o bucata de lucru distincta, necesara pentru ca regenerarea sa fie completa.
  7. Gol neadresat inca: ramura ntip = 4 ("factura din avize", :7520-7537) apeleaza scrie_fact_aviz_custodie in loc de descarca_gestiune+discount — design-ul nu specifica ce se intampla pe fallback aici. In practica improbabil (o linie de aviz sursa fara politica n-ar fi ajuns la factura pe acest drum, adauga_articol_factura:5096 cere deja A.ID_POL = V_ID_POL pe avizul sursa), dar de exclus explicit inainte de implementare, nu de presupus tacit.

3. Derivarea contului in VFP

3.1 Sursele, in ordine (decizia 27)

  1. Articol gestionabil (are id_gestiune valid, in_stoc=1): CORESP_CONT_VENCHELT.CONT_VENIT, cautat pe contul de gestiune al liniei (poArt.Cont, camp deja gathered pe poArticol in ambele metode do_scrie_articole, confirmat la ofacturare.vc2:12945,13835 si echivalentele in frm_facturare_articole2). Cheia de join e mereu CONT (nu ID_GESTIUNE, nu TIP_DOC) — confirmat pe 4+ consumatori Oracle reali ai tabelului (pack_vin, pack_devize, gestiune_pack_gest_import, un raport din 2026), niciodata pentru CONT_VENIT insa — aceasta propunere ar fi primul consumator real al coloanei, desi populata din 2023 (20 conturi de gestiune uzuale: 301-303/3021-3028, 331/332, 341, 345/346/348, 361, 371, 381). View-ul VFP-safe e VCORESP_CONT_VENCHELT (STERS=0 deja filtrat in view).
  2. Articol negestionabil, sau gestionabil dar corespondenta nu rezolva (rand lipsa pentru acel CONT): NOM_ARTICOLE.CONT, doar daca primul caracter e 6 sau 7 (verific_cont nu restrange campul la o clasa, deci poate contine legitim orice cont valid, inclusiv 6xx/7xx pe un articol negestionabil).
  3. Fallback final: literal '704'. Fara reteta de cod deja scrisa in pack_facturare pentru aceasta valoare, dar cu un precedent independent de "704 = cont implicit client" in alt subsistem (anaf_efactura.vc2:8376, cconte = 704) — nu o inventie, dar cod nou.

3.2 Unde sta codul si cand ruleaza

Locul natural: chiar in do_scrie_articole (ambele clase, frm_facturare_articole si frm_facturare_articole2, confirmat ca 2 locuri distincte de atins, nu 1), imediat inainte de construirea textului RPC pentru fiecare linie, conditionat explicit de Empty(Nvl(poArt.id_pol,0)) — nu la momentul adaugarii liniei in grid. Doua motive:

  • Regenerarea (decizia 35) re-parcurge toate liniile la fiecare scriere; derivarea la momentul scrierii (nu la adaugare) garanteaza ca valoarea reflecta starea curenta a corespondentelor/ nomenclatorului, nu una inghetata la momentul in care linia a fost adaugata initial in grid.
  • Conditionarea explicita pe id_pol gol (nu pe "linia vine din nomenclator" ca marcaj separat) e chiar garda ceruta la sectiunea 1.3-1.4: o linie cu id_pol populat nu trebuie sa primeasca niciodata o valoare pe cont_venit, indiferent de sursa ei — un singur punct de decizie, in oglinda exacta cu conditia pe care Oracle o va verifica la randul lui (sectiunea 1.4).

3.3 Ce se intampla cand derivarea esueaza

  • Corespondenta nu are rand pentru acel CONT (gestiune fara corespondenta configurata): cade pe pasul 2 (NOM_ARTICOLE.CONT), nu pe eroare.
  • NOM_ARTICOLE.CONT nu incepe cu 6/7 (cont de gestiune sau alt tip pe un articol negestionabil, configurare atipica): cade pe pasul 3 (704), nu pe eroare.
  • Niciodata NULL/gol trimis la Oracle pentru o linie "din nomenclator" — ultimul pas e un literal, nu o interogare care poate esua. Aceasta e proprietatea care face garda de la 1.4 inofensiva pe fluxul normal: cont_venit e intotdeauna populat cand id_pol e gol, deci ramura noua din Oracle ruleaza mereu cand ar trebui, iar FACT-024 (ramura veche) nu mai poate fi atinsa de o linie din nomenclator dupa implementare — dispare exact problema pe care povestea o rezolva.
  • Nu e proiectata aici (ramane de decis la implementare, in afara acestei povesti): daca vreo validare suplimentara ar trebui sa verifice ca valorile derivate (CONT_VENIT din corespondenta, NOM_ARTICOLE.CONT) sunt conturi valide in planul de conturi al anului curent — azi verific_cont exista ca mecanism (oproceduri_comune.prg:2389-2415) dar nu e cablat automat pe acest drum nou.

4. Fluxul in formularul unificat

  1. Utilizatorul deschide un document deja emis la modificare (frm_modific2024/formularul unificat, in afara perimetrului #6/omodificari.vc2, care nu se atinge aici) si alege din meniu "Alege din nomenclator..." (a doua sursa, langa "Cauta in lista de preturi...", ambele reduse la acelasi gest UI conform S4b/S4e).
  2. Gestul UI: Select crsfactura / APPEND BLANK (tiparul deja in productie la frm_facturare_articole2.But_nou1.do_adauga, ofacturare.vc2:17118-17122), focus pe celula de cautare, combosql legat pe cursorul filtrat pe nomenclator (caut_articol/echivalent, fara coloana id_pol in output — confirmat, sectiunea 1.3).
  3. crsfactura.id_c ramane 0 (valoarea implicita a campului N(20) fara Null, la APPEND BLANK) — valoare pe care Oracle nu o produce niciodata pentru id_c. do_sterge (potriveste pe id_c) e no-op prin constructie pe aceasta linie, fara nicio modificare de cod — exact regula deja adoptata in #13, reconfirmata aici pentru sursa "nomenclator" (S4e o stabilise pentru sursa "lista de preturi pe comanda"; acelasi cursor de scriere crsfactura, acelasi mecanism, doar alt cursor de cautare in fata).
  4. crsfactura.id_pol ramane .NULL. (camp N(20) Null, ofacturare_comun.prg:1775), pentru ca sursa de cautare (nomenclator) nu are aceasta coloana in output — confirmat direct in aceasta runda (sectiunea 1.3), nu presupus.
  5. Utilizatorul completeaza cantitate/pret (campuri editabile inline pe grid, tipar deja existent).
  6. La do_scrie_articole (salvarea documentului), pentru fiecare linie cu Empty(Nvl(poArt.id_pol,0)), se ruleaza derivarea din sectiunea 3 si se populeaza al 27-lea argument pozitional al apelului RPC catre adauga_articol_factura cu valoarea calculata; pentru restul liniilor (cele cu id_pol populat, "din lista de preturi"), argumentul ramane NULL — comportament identic cu azi.
  7. Documentul se salveaza; pe Oracle, contabilizeaza_articol ruleaza ramura noua (sectiunea 1-2) pentru liniile fara politica, ramura veche neschimbata pentru restul.
  8. Editarea (regenerare, decizia 35): acelasi do_scrie_articole, aceeasi conditie pe id_pol, acelasi rezultat — nu exista o a doua cale de contare pentru liniile deja existente pe document.

5. Ce ramane in afara (partea auto)

  • tip = -12 (facturare auto, ROAAUTO) — livrat separat, dupa ce mecanismul de mai sus e stabil pe documentele ROAFACTURARE (ordinea deja decisa in plan).
  • "Alte servicii" din ROAAUTO ramane exceptia ei — ocoleste complet contabilizeaza_articol, nu foloseste si nu va fi migrata sa foloseasca acest mecanism ca parte a acestei povesti; daca se decide vreodata unificarea, e o poveste separata.
  • Gridul read-only din frm_modific2024 (al #6) — S4g incepe dupa ce #6 se termina (decizia 30), nicio proiectare de aici nu presupune sau modifica acel perimetru.
  • Validarea de cantitate/stoc pentru o linie liberă la modificare (analogul sectiunii 6 din s4e_lista_preturi_pe_sursa.md, dar pentru nomenclator, nu lista de preturi) — nu re-proiectata aici, acelasi gol semnalat deja de S4e se aplica identic si pe sursa "nomenclator"; de rezolvat cu acelasi mecanism (varianta (b), reutilizare do_verifica_articol cu poArticol explicit).

6. Teste minime

Pe langa cele deja listate in canal_cont_venit_fara_politica.md sectiunea 6 si nota_contabila_fara_politica.md, specifice acestei povesti:

  1. Factura normala cu politica, parametru NULL — verifica ACT identic cu azi (regresie de baza).
  2. Aviz cu articol adaugat din nomenclator (fara politica) — SCD ramane '461'/'418' (ramurile de aviz raman hardcodate independent de ramura noua/veche), nu getoptiunefirma.
  3. ntip = 46 (daca exista pe fluxul de modificare vizat) — scrie_nota nu se cheama pe acea ramura; de confirmat ca ramura noua nu e atinsa deloc pentru acest tip.
  4. Articol gestionabil din nomenclator, cu corespondenta configurata — un singur rand ACT, SCC = CORESP_CONT_VENCHELT.CONT_VENIT pentru contul de gestiune al liniei, SCD = optiunea de firma (sau 4111 daca optiunea lipseste), linie TVA scrisa separat.
  5. Articol negestionabil din nomenclator, fara corespondenta aplicabila — SCC = NOM_ARTICOLE.CONT daca incepe cu 6/7, altfel '704'.
  6. Articol negestionabil (id_gestiune=-1000) cu cont_venit populat — descarca_gestiune NU ruleaza.
  7. Discount pe o linie din nomenclator — foloseste V_ASCD/V_CU_TVA calculate in ramura noua.
  8. Document in valuta (nin_valuta=1) cu linie din nomenclator — IN_VALUTA=1 pe nota, SUMA_VAL completat.
  9. Linie fara politica si fara cont derivat (nu ar trebui sa se poata construi prin UI, dar de testat direct pe Oracle) — FACT-024 tot apare, regresie negativa: garda nu s-a slabit.
  10. Linie cu ambele populate (construita direct la nivel de apel Oracle, nu prin UI — testeaza garda de la sectiunea 1.4, nu fluxul normal) — noua eroare explicita apare, nu o rezolvare tacita in nicio directie.
  11. Regenerare pe un document mixt (o linie din lista de preturi cu id_pol, o linie din nomenclator fara id_pol) — dupa regenerare, prima linie tot cu SCC din politica, a doua tot cu SCC derivat; niciuna nu trece pe ramura celeilalte.
  12. id_c si do_sterge (mostenit din tiparul S4e, de re-verificat pentru sursa nomenclator): o linie din nomenclator adaugata la modificare, apoi stearsa inainte de salvare — no-op pe crsarticole, fara efect asupra vreunei linii reale a documentului.

7. Riscuri si de decis de Marius

  1. Garda explicita pe combinatia id_pol + cont_venit ambele populate (sectiunea 1.4) — e o recomandare de proiectare a acestui raport, nu o decizie deja luata de Marius; codul exact al erorii si numarul FACT-0xx raman de ales.
  2. CU_TVA hardcodat 1 — are un efect colateral masurat (nu doar teoretic), prin nproc_tva_max, pe combinatia specifica linie-scutita + discount global de factura. De confirmat explicit, nu de presupus inofensiv.
  3. Decizia 36 (SCD prin optiune de firma) — numele exact al cheii (FACT_SCD_ARTFPRET propus), daca se adauga validare de cont la citire (niciun precedent existent nu valideaza), si PROGRAME (restrans la ROAFACTURARE sau extins ca RF_CONT_INCASARE_*) raman decizii deschise in optiune_firma_cont_debit.md sectiunea 4.
  4. Ramura ntip=4/"factura din avize" pe fallback (sectiunea 2 punctul 7) — probabil imposibil de atins prin acest drum (avizul sursa cere deja id_pol), dar nu exclus explicit prin cod sau test — de confirmat inainte de implementare.
  5. Suprafata de regresie in restul suitei (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB) pentru adauga_articol_factura — pe baza cercetarilor existente, aceste produse folosesc proceduri separate (_deviz/_stoc) sau alt pachet complet (pack_acn), deci risc asteptat zero, dar cautarea directa in COMUN-urile lor pentru un apel simplu la adauga_articol_factura( nu s-a terminat in nicio runda anterioara — de re-rulat inainte de implementare, nu de presupus incheiata.
  6. Validarea de cantitate/stoc pentru linia libera la modificare (sectiunea 5) — gol mostenit de la S4e, nu inchis aici, de rezolvat cu acelasi mecanism pe ambele surse (lista de preturi si nomenclator).
  7. Trasabilitatea CONT_VENIT pe VANZARI_DETALII (sectiunea 2 punctul 1) — optionala pentru ca mecanismul sa functioneze, dar fara ea coloana nu se pastreaza dupa fapt; de decis daca merita extinderea listei explicite de coloane din scrie_in_vanzari.
  8. idfact_refolosire_si_documente.md — obstacol real pentru ca regenerarea (decizia 35) sa fie completa (reemitere cu acelasi ID_FACT), in PACK_CONTAFIN, nu in pack_facturare — nu blocheaza design-ul de aici, dar e o bucata de lucru separata, necesara inainte ca "editare = regenerare" sa functioneze end-to-end.

STARE / CE RAMANE

Cercetare/proiectare incheiata pentru toate cele 6 puncte cerute in brief, cu punctul central (sectiunea 1) tratat explicit ca decizie de proiectare (nu doar consecinta implicita a codului existent deja schitat in rundele anterioare). Nicio editare de cod, niciun git_sync.ps1/ txt2vcx.ps1, niciun commit, nicio scriere pe Oracle. Context consumat moderat in aceasta runda — nu a fost necesara predarea de mijloc de sesiune.

Ce nu s-a putut inchide complet, de reluat separat:

  • Cautarea exhaustiva a apelantilor adauga_articol_factura simplu in restul suitei (risc 5, sectiunea 7) — de re-rulat, nu s-a terminat in nicio runda anterioara din lipsa de timp, nu din cauza unei erori.
  • Confirmarea explicita a lui Marius pe hardcodarile/optiunile ramase deschise (CU_TVA=1, numele cheii de optiune, garda de la sectiunea 1.4) — sunt recomandari argumentate, nu decizii finale.