# Flux: Achiziție din import (NIR import)

- **Intrare**: `Clase\gestiuni.vcx` (`gestiuni.vc2:2045-2171`) → meniu „Achizitie din import /
  Achizitie interna" → `DO achizitie_import WITH <id_set>, llIntern IN ointroduceri.prg`.
  Id-uri de set: **208/209 = import**, **220/221 = intern**.
- **`achizitie_import`** (`COMUN\programe\ointroduceri.prg:1435-1617`):
  - antetul facturii = `poAct` (SCATTER din `actactan`, structură `pmenu.prg:281-307`, tabelul
    `ACT`) — o singură pereche `id_valuta`+`curs` pe act;
  - liniile de notă = cursorul **`introdc`** (din view `vnote_contabile` filtrat pe `id_set`),
    fiecare rând moștenește `id_valuta`/`curs`/`dataact`/`tva_incasare` din `poAct`; rândurile
    merg în perechi **impar = linia de bază, par = linia de TVA**;
  - cursorul **`jtva_coloane2`** (explicații TVA, rând 0 gol) = sursa dropdown-ului de explicație;
  - deschide forma **`import_nota`** (`COMUN\clase\ointroduceri.vcx`). **`import_nota_original`**
    din același .vcx NU e instanțiat nicăieri — copie de rezervă.
- **Modelul de documente**: un NIR de import = factura principală (marfă și/sau imobilizări;
  valuta/cursul ei = `poAct.id_valuta`/`poAct.Curs`, referința de conversie) + opțional factură de
  transport (poate fi altă valută/curs) + opțional factură de taxe vamale + DVI (TVA-urile fiecărei
  facturi, plătite în lei). Toate devin linii în `introdc`, apartenența pe document = per rând
  (`nract`/`serie_act`/`id_fact`; rândurile au și `id_valuta`/`curs` proprii, dar codul istoric
  folosește doar `poAct.Curs`).
- **`import_nota`**: grilă pe `introdc`; transport/taxe = linii de notă suplimentare cu flagurile
  `in_valuta` (suma e în valută) și `participa_valuta` (linia intră în valoarea de intrare a
  mărfii). `do_executa` calculează procentele de repartizare (`explicatia4`/`explicatia5`);
  `inainte_de_do_termin` sumează bazele (liniile în lei se împart la `poAct.Curs`) și lansează
  **`import_nir`** cu `procent_lei/procent_val/ncurs`.
- **`import_nir`**: `do_adauga` → `viz_catalog_articole()` (`oproceduri_articole.prg:62`, câmp
  `in_stoc`="Gestionabil") filtrat pe conturile `scd` din notă; preț de intrare = preț valută ×
  `procent_val`, apoi × `ncurs` × `procent_lei` (transportul „umflă" procentele peste 100).
  Diferența reziduală → linia „DIFERENTE". `do_modiparam` = preluare articole din XLS (creează
  articole noi prin `pack_preturi.adauga_articol`, cont implicit 371).
- **Salvare**: `oscrie_in_fisiere` → `ACT_TEMP`/`RUL_TEMP` → pachet Oracle **`PACK_CONTAFIN`**
  (`SCRIE_IN_ACT`, `SCRIE_IN_RUL`, `SCRIE_IN_STOC`). `SCRIE_IN_RUL` inserează în `RUL` **exact**
  `RUL_TEMP` (nu derivă din ACT) — filtrarea de rulaje se face în clientul VFP. Sursă pachet:
  `COMUN\docs\PACK_CONTAFIN.pck` (`COMUN\docs\oracle_export.md`).
- **Articole negestionabile** (`nom_articole.in_stoc=0`): model canonic în clasa **`nir`** (NIR
  obișnuit) — articolul intră normal în grilă, iar `inainte_de_do_termin` face
  `Delete From rul_temp Where in_stoc=0` (valoarea rămâne doar pe nota contabilă). Din v2.11.6
  `import_nir` e aliniat la același model, cu cursor temporar `crsNegest` care readaugă rândurile
  șterse în grilă dacă `oscrie_in_fisiere` eșuează. `rul_temp` are coloana `in_stoc` din view-ul
  sursă `vrul`; la `do_adauga` se umple prin `GATHER NAME loArt`, la `do_modiparam` (XLS) din
  select pe `nom_articole`.
- **Explicație TVA**: dropdown `Grid1.cExplicatieTva.Combo1` (RowSource `jtva_coloane2`); variantă
  cu căutare = `frm_modific2024.do_modifica_explicatie_tva` (`omodificari.vcx`) →
  `caut_explicatie_tva()` (`ocautare.prg:1934`, view `vjtva_coloane`, întoarce DOAR
  `id_jtva_coloana, denumire, cota_tva`). Capcane: `introdc.ptva` ține **cota brută** (21), în
  registru jurnal `proc_tva`=(cota+100)/100; comutarea 4427/401 pe linia de TVA = SEEK în
  `cJtvaCol4427`.

Căutare generică: `cauta_alfa()` (`COMUN\programe\cauta_alfa.prg`) → forma `cauta_alfa_form_plus`.

## Șablon `vnote_contabile`

Cursorul `introdc` NU pornește cu „2 linii" — se încarcă cu toate perechile-șablon din
`vnote_contabile` pentru `id_set`. Pe dev (`MARIUSM_AUTO`): **208=10 linii, 209=10, 220=10,
221=6**. Cele 10 linii ale setului 208 = **5 perechi pre-alocate (bază+TVA) = 5 sloturi**:

| Pereche (ordine) | SCD/SCC bază | SCD/SCC TVA | Ce e |
|---|---|---|---|
| 1 (1-2) | 3028/401 | 4426/4427 | marfă CE 21% + TVA |
| 2 (3-4) | 3028/401 | 4426/4427 | slot repetat (gol) |
| 3 (5-6) | 3028/401 | 4426/4427 | slot repetat (gol) |
| 4 (7-8) | 3028/446 | 4426/446 | vamă/DVI (pe dev) |
| 5 (9-10) | 3028/446 | 4426/446 | slot repetat (gol) |

Model CONT2000: sloturi goale pre-alocate, nu rânduri adăugate. Factura principală = perechea 1;
restul = sloturi pentru facturi suplimentare (3 marfă) + DVI (2). Un „document" = o pereche cu date.

**Conturile 446 din setul 208 de pe dev NU sunt reprezentative** — pe producție DVI-ul vamă
folosește **4426=401** (nu 446). `vnote_contabile` e configurabil per firmă, nu hardcoda conturile:
clonează perechea-șablon reală a setului (un singur pattern 3028/401 + 4426/4427 sau 4426/401
acoperă și marfă și DVI — DVI diferă doar prin partener/unde e plătit TVA).

## Plan: pivot la „Direcția A" (rescriere flux introducere, scop M2, neimplementat)

Model țintă, abandonează cele 10 sloturi pre-alocate:

1. `achizitie_import`: `introdc` pornește **gol**; se păstrează perechea-șablon (bază+TVA, conturi
   reale din `vnote_contabile`) doar ca sursă de clonare.
2. Buton „Adaugă factură" + dialog: clonează perechea-șablon, o completează, o adaugă în
   `introdc`. Folosit și pentru factura principală, și pentru DVI (radio-ul furnizor/DVI/fără
   schimbă doar partenerul/unde e plătit TVA, nu conturile).
3. M1 (culori) rămâne neschimbat: fiecare factură adăugată = o pereche = un document = o culoare
   (F1, F2, …), cheie `serie+nr+id_fdoc`.

Atinge `COMUN` (22 `.pjx` referă `ointroduceri`) și schimbă comportamentul pentru toți userii de
import (nu mai văd sloturi pre-completate) — testare atentă.

## Model curent `import_nota`

- **`doc_key` unic per document**: prefix `nr_doc` (contor intern) + câmpuri business
  (`calc_doc_key`). Rândul T al unei facturi cu TVA pe DVI poartă câmpurile vamei
  (partener/nract/4426) dar **păstrează doc_key-ul facturii-mamă** — identitatea nu se amestecă
  între documente care partajează același nr. DVI.
- **Spargerea principalei** (`sparge_document`/`creeaza_rand_s`): grupare `rul_temp` pe
  `cont+acont`; analiticul articolului merge direct în `ascd` al rândului S (fallback
  `assign_analitic` doar când nu e furnizat).
- **Procentele per rând** (`explicatia4`/`explicatia5` = „Procent lei/valuta"): scrise de
  `sincronizeaza()` după spargere (nu de `do_executa`), numitor = baza documentului principal
  (`This.nbazaprincipala_lei/_val`, din `recalculeaza`). Principala=100.00; celelalte = pondere
  lei/valută (convertit la `oact.Curs` dacă e în lei).
- **Ștergere/copiere**: `do_sterge` = meniu Da(document, pe doc_key)/Nu(doar linia)/Cancel;
  `do_copiaza_nota` (buton „Copiază") = același meniu; copiază doar B+T cu `nr_doc`/`doc_key` noi
  (S/D se regenerează la sincronizare). `Calculate` mută pointerul la EOF → poziția sursă se
  salvează/restaurează înainte de `Scatter`.
- **Rândul TOTAL** sub grila notelor: `TxtFtvaLei`/`TxtFtvaVal` (fără TVA) lângă `Text2`/`Text3`
  (cu TVA), setate în `actualizeaza_banda`.
- **Test e2e de referință**: `COMUN\utile\Teste\test_scenariu_dvi_complet.prg` (3 documente pe
  același DVI + articole 212/1 și 371/4 + ștergere/copiere).
- **Valori manuale protejate**: `mod_manual=1` se setează și pe rândul T (Valid-urile
  `cSuma`/`cSumaVal`); `do_executa` nu-l rescrie, `sparge_document` nu-l șterge/duplică pe aceeași
  cotă (`ptva`). Nimic nu resetează `mod_manual` pe rând existent.
- **`nOldVal` unic**: o singură proprietate pentru valoarea la GotFocus — Valid/LostFocus ies
  devreme la valoare nemodificată (`cSuma`, `cSumaVal`, `cCotaTva`, `cPretFactura`).
  `do_calculeaza_diferente` restaurează poziția `rul_temp` de la intrare; `do_reface`
  repoziționează explicit.
- **Totaluri articole**: `_grdfooter1` (clasa `_grdfooter` din `_grd_base.vcx`) atașat la
  `GridArt` la finalul `Init`; sume pe `cValoareValutaFactura`/`cValoareValutaCalculat`/
  `cValoareLeiCalculat` din `rul_temp`, recalculate în `actualizeaza_banda` +
  `do_calculeaza_diferente`. Totalurile de sub grila notelor rămân din `introdc`; Diferențe lei =
  bază note − Σ articole.
- **Test TVA manual + footer**: `COMUN\utile\Teste\test_manual_tva_footer.prg`.
- **TVA pe creditori și la secundare**: `sparge_document` regrupează T-urile nemanuale ale
  documentelor secundare pe creditorii (scc/ascc) rândurilor S proprii, proporțional cu sumele S
  (conservă totalul T, inclusiv TVA tastat la DVI); `recalc_tva_document` aplică aceeași pondere
  (fără S-uri: împărțire egală 1/N); `uneste_document` reunește T-urile sparte când documentul
  revine la B. Resincronizarea din `cScc/cAscc.Valid` se armează pe garda `nOldVal` (nu pe
  comparația cu câmpul — grila poate scrie câmpul înaintea Valid-ului, ex. Enter) + gardă
  `lInSync` contra Valid-urilor re-declanșate de regenerare. Test:
  `COMUN\utile\Teste\test_tva_secundare.prg`.

## TVA DVI cu valută proprie + discount financiar

- **Două coloane client-side pe `introdc`** (fără migrare Oracle): `rand_dvi` (identitate — 1 pe
  rândul T scris de ramura DVI a `do_adauga_factura`) și `valuta_proprie` (1 când acel rând T are
  valută/curs diferite de ale documentului, setat la creare sau din grilă). Nu s-a refolosit
  `mod_manual` — e supraîncărcat semantic (`sparge_document` face match pe el pentru alte
  scopuri, ar da duplicate/omisiuni de rânduri T).
- **Rând „protejat"** = `Nvl(valuta_proprie,0)=1` (helper `Thisform.rand_protejat()`). Ocolit de
  `copiaza_valoare` (câmpurile valutare nu se propagă pe el, câmpurile de identitate nu se propagă
  pe orice rând `rand_dvi=1`), `do_executa`, `recalc_tva_document`, `uneste_document`,
  `sparge_document` și handlerele de grilă `cCurs`/`cInValuta`/`cValuta` (editare locală, fără
  propagare). Re-alegerea valutei documentului pe rândul protejat resetează `valuta_proprie=0`
  (revert). Un DVI cu valuta facturii rămâne `valuta_proprie=0` și urmează factura la propagări.
- **Spargerea T-ului protejat** (`sparge_tva_protejat`, din `sparge_document`): când TOATE
  rândurile T ale documentului sunt protejate, TVA-ul facturii e integral cel de pe DVI — nu se
  fabrică rânduri T din rândul de bază. Varianta `loRandT = loBaza` dădea TVA dublă (rândul
  protejat rămânea, plus un T nou cu conturile bazei în valuta facturii). Acum totalul protejat se
  sparge pe cotele articolelor (pondere `bază_cotă × cotă`, rest de rotunjire pe |suma| maximă),
  păstrând prin `Gather` identitatea vamală/valuta proprie/`rand_dvi`/`valuta_proprie`. O singură
  cotă = niciun rând atins.
- **Discount financiar pe factură**: un rând `tip_rand='G'` (401=767, suma=discount fără TVA),
  același `doc_key`/`nr_doc` cu factura, `participa_valuta=.F.`. Marfa (3028) și prețurile
  articolelor rămân la valoarea integrală — discountul schimbă doar baza TVA: `T=(b-d)*p/100`,
  sold 401 = `b + (b-d)*p/100 - d`. Ordine la creare: B → G → T. Excluse din bazele
  TVA/diferență/spargere prin filtrele deja existente pe `Inlist(tip_rand,'B','S')`/`'T'` — un
  singur loc nu filtra pe `tip_rand` (suma valută din footer, `do_executa`), a primit
  `And tip_rand<>'G'`. Punct unic de adevăr: `disc_document(doc_key, tlValuta)`. Se scade DIRECT
  în `do_executa`/`recalc_tva_document` (același `doc_key`), dar se REPARTIZEAZĂ prin factor în
  `sparge_document` (bazele vin din `crsGrupCota`, nu din `suma_doc`). Factorul e clampat la 0
  (discountul poate depăși baza articolelor când rămâne rest pe rândul de diferențe → altfel TVA
  negativă). `suma_doc`/`suma_doc_val` rămân BRUTE (`sparge_document` repartizează toată baza pe
  S, `verifica_sincronizare` compară `Suma(S)` cu `suma_doc`). `do_adauga_factura` citește
  `toDlg.disc_baza_lei` sub gardă `Type(...)<>'U'` — apelabil programatic cu `toDlg` minimal
  (import e-Factura/XLS). Limită cunoscută: rândurile create ulterior de
  `sparge_document`/`recalc_tva_document` se adaugă la finalul cursorului — ordinea vizuală B/G/T
  nu mai e garantată (grila nu are sortare).
- **Bifa „Valuta" pe secțiunea TVA DVI** (`chkDviInValuta`): pornește după factură (bifată când
  factura e în valută), rămâne pe alegerea manuală prin `ldviinvalutadirty` (ca `linvalutadirty`
  la bază). Scoasă → ascunde valuta/cursul/TVA-ul valutar al DVI, forțează `nDviCurs=1`,
  `nDviTvaVal=0`. Ramura din `recalc_tva` trebuie să includă bifa, nu doar
  `dvi_valuta_efectiva()` (întoarce valuta facturii, nu știe de bifă — fără ea se scrie
  `dvi_tva_val` nenul cu `dvi_in_valuta=.F.`). „Factură în valută, DVI doar în lei" dă
  `dvi_valuta_proprie=1`.
- **Concordanța cotelor pe factură multi-cotă**: rândurile S se generează pe
  `cont+analitic+cota_tva` (doar la documentul principal), primesc cota articolelor —
  `creeaza_rand_s` are parametrul `tnCota`, scrie `ptva`+`id_jtva_coloana`/`explicatie_tva` prin
  `gaseste_jtva_cota` (familia = coloana bazei). Rândul G se sparge la fel, proporțional pe bazele
  pe cotă (`sparge_discount`, din `sparge_document` după `calc_baze_cota_creditor`), păstrând
  totalul discountului; idempotentă. Fără astea, baza (S) și discountul (G) rămâneau pe cota
  facturii în timp ce T-urile erau deja sparte pe cotele articolelor — jurnalul de TVA ieșea cu
  baza pe altă cotă decât TVA-ul. Articolele fără cotă proprie preiau cota documentului înainte de
  grupare.
- **`nOldVal` e NUMERIC**: coloanele caracter din grilă (`cScc`/`cAscc`) folosesc `cOldVal`. O
  singură proprietate partajată dădea „Operator/operand type mismatch" în `cSuma.Text1.Valid`
  când Valid se re-declanșa fără GotFocus. `cSuma`/`cSumaVal.Valid` au primit și garda `lInSync`.
- **Două reprezentări ale „RON"**: rândul din picker (`caut_valuta`/`vnom_valute`) e REAL cu
  `id_valuta` nenul (`moneda_nationala=1`); sentinela „fără valută" din `introdc`/`ACT` e
  `id_valuta=0`, `nume_val=''`. Comparațiile trebuie pe valuta EFECTIVĂ
  (`Empty(nume) Or Upper(nume)=='RON'`), nu pe id brut — altfel `dvi_valuta_proprie` ar da fals 1.
- **`copiaza_valoare` și rândul DVI — gardă în ambele sensuri**: rândul T al DVI păstrează
  `doc_key`-ul facturii-mamă, deci propagarea pe grup îl include. Trebuie sărit ca țintă
  (identitatea facturii nu-l atinge) și ca sursă (numărul/data/partenerul vamal nu ajung pe
  factură) — fără garda pe sursă, o trecere prin celula „Nr" a rândului DVI scria numărul DVI
  peste toată factura (B/G/S) și, la Terminat, în `ACT`/`RUL.nract`. Rândul D scapă (își moștenește
  antetul o singură dată, la creare). `copiaza_valoare` nu face refresh de grilă — coruperea e
  invizibilă până la salvare. Propagarea de partener are flux propriu
  (`Grid1.cPartC.Text1.Valid`), aceeași gardă.
- **`ControlSource` calificat la runtime**: `This.Parent.ControlSource` întoarce `introdc.nract`,
  nu `nract`. Orice `Inlist`/comparație pe nume de câmp cu valoarea din Valid-urile de grilă
  trebuie să normalizeze întâi (taie prefixul până la ultimul punct) — altfel garda arată corect
  scrisă și nu se activează niciodată. Test:
  `COMUN\utile\Teste\achizitie_import\test_diag_controlsource_calificat.prg`.
- **Rebindarea unei coloane de grilă din `Init`**: VFP califică `Column.ControlSource` cu aliasul
  o singură dată, la legarea grilei; o atribuire ulterioară rămâne exact cum a fost scrisă. Un nume
  de câmp necalificat se afișează și se scrie programatic corect, dar celula nu acceptă tastare.
  Atribuie mereu `This.<grid>.RecordSource + ".<camp>"`. Test:
  `COMUN\utile\Teste\achizitie_import\test_repro_pret_lei_intern.prg`.

## Ștergere note — model VECHI pe paritate, înlocuit de `doc_key`

- `But_sterge1` (clasa `but_sterge`) șterge perechea bază+TVA curentă, de pe oricare linie
  (`lnBaza = Recno() - Iif(Mod(Recno(),2)=0,1,0)`, apoi `Delete Next 2`), prin
  `inainte_de_do_sterge` sub `lactiv4` (`.T.` în `Init`).
- Ștergere logică (`SET DELETED ON`, `roagest.prg:28`) — Recno-urile rămân, paritatea impar/par
  se păstrează.
- Capcană generală VFP: niciodată `ZAP`/recreare pe cursorul-sursă al unui grid — grid-ul își
  pierde sursa și rămâne blank.

## Explicații TVA pe factură multi-cotă

- **Perechea bază/TVA din nomenclator** (`jtva_coloane2.id_tva`); trecerea între cote în aceeași
  familie = pe **semnătura coloanei** (`coloana_jc` fără cifre: `FO21B`→`FOB`, `FO21T`→`FOT`,
  `gaseste_jtva_cota`). Potrivirea pe primele 2 caractere confunda baza cu TVA.
- **Alinierea T → S** (`aliniaza_tva_la_baza`, final `sparge_document`): fiecare T primește
  perechea de TVA a S-ului de aceeași cotă, doar dacă explicația S-ului chiar are cota rândului.
  Fără ea, o explicație din altă serie pe S (ex. `CE11CTB`) muta T-urile în seria CE, în timp ce
  S se re-alinia la familia documentului (`FO`) la resincronizare.
- **`expl_manual`** (coloană client-side, lângă `mod_manual`): 1 când utilizatorul alege explicit
  explicația pe un rând T; rândurile marcate nu se aliniază. Nu s-a refolosit `mod_manual`
  (înseamnă „nu recalcula suma", ar fi înghețat și sumele). Peste regenerarea T-urilor eticheta se
  păstrează prin snapshot pe cotă (`crsExplT`, luat înainte de ștergere, reaplicat după); la
  `sparge_tva_protejat` rămâne doar pe cota rândului original.
- **Gol de nomenclator la 11%**: importul de bunuri nu avea perechea 11% (`FO11B`/`FO11T` =
  236/237, adăugate 30.07.2026). Fără pereche, `sparge_document` lasă în `cmesajsync` „Documentul
  <fdoc> are marfă la <cotă>%, dar nu există explicație TVA de <cotă>% în seria '<serie>'" și
  finalizarea rămâne blocată; utilizatorul poate ocoli alegând manual o explicație de 11% din altă
  serie.
- **`tlIntern` în `achizitie_import`**: `Createobject('IMPORT_nota', tlIntern)` primește
  parametrul, dar codul din clasă citește `Thisform.lIntern` — o referință la `tlIntern` dintr-o
  metodă a formularului prinde variabila PRIVATE a apelantului doar cât timp acesta e pe stivă
  (în `inainte_de_do_termin` era deja ieșit).
- **Fixture-uri de test**: cele 32 de teste din `COMUN\utile\Teste\achizitie_import` își creează
  `introdc` prin `CREATE CURSOR` — orice coloană nouă folosită în SQL-ul din clasă trebuie
  adăugată și acolo, altfel „SQL: Column '<X>' is not found".
