@@ -1,65 +1,58 @@
# Flux: Achiziție din import (NIR import)
# Flux: Achiziție din import (NIR import)
Lanțul complet (descoperit la sesizarea „achiziție import", 07.2026):
- **Intrare**: `Clase\gestiuni.vcx` (`gestiuni.vc2:2045-2171` ) → meniu „Achizitie din 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` .
Achizitie interna" → `DO achizitie_import WITH <id_set>, llIntern IN ointroduceri.prg` .
Id-uri de set: **208/209 = import** , **220/221 = intern** (perechi pe tip de operație) .
Id-uri de set: **208/209 = import** , **220/221 = intern** .
- **`achizitie_import` ** (`COMUN\programe\ointroduceri.prg:1435-1617` ):
- **`achizitie_import` ** (`COMUN\programe\ointroduceri.prg:1435-1617` ):
- antetul facturii = `poAct` (SCATTER din `actactan` , structura din `pmenu.prg:281-307` ,
- antetul facturii = `poAct` (SCATTER din `actactan` , structură `pmenu.prg:281-307` , tabelul
tabelul `ACT` ) — o singură pereche `id_valuta` + `curs` pe act;
`ACT` ) — o singură pereche `id_valuta` + `curs` pe act;
- liniile de notă = cursorul ** `introdc` ** (din view `vnote_contabile` filtrat pe `id_set` ),
- 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
fiecare rând moștenește `id_valuta` /`curs` /`dataact` /`tva_incasare` din `poAct` ; rândurile
în perechi **impar = linia de bază, par = linia de TVA** ;
merg în perechi **impar = linia de bază, par = linia de TVA** ;
- cursorul ** `jtva_coloane2` ** (explicații TVA, cu rând 0 gol) — sursa dropdown-ului de explicație;
- cursorul ** `jtva_coloane2` ** (explicații TVA, rând 0 gol) = sursa dropdown-ului de explicație;
- deschide forma ** `import_nota` ** (`COMUN\clase\ointroduceri.vcx` ). Atenție:
- deschide forma ** `import_nota` ** (`COMUN\clase\ointroduceri.vcx` ). ** `import_nota_original` **
** `import_nota_original` din același .vcx NU e instanțiat nicăieri — e copie de rezervă.**
din același .vcx NU e instanțiat nicăieri — copie de rezervă.
- **Modelul de documente**: un NIR de import = factura principală de achiziție (marfă și/sau
- **Modelul de documente**: un NIR de import = factura principală (marfă și/sau imobilizări;
imobilizări; valuta/cursul ei = `poAct.id_valuta` /`poAct.Curs` , referința de conversie) + opțional
valuta/cursul ei = `poAct.id_valuta` /`poAct.Curs` , referința de conversie) + opțional factură de
factura de transport (poate fi altă valută/curs) + opțional factura de taxe vamale + DVI
transport (poate fi altă valută/curs) + opțional factură de taxe vamale + DVI (TVA-urile fiecărei
(declarația vamală, cu TVA-urile fiecărei facturi, plătite în lei). Toate devin linii în `introdc` ,
facturi, plătite în lei). Toate devin linii în `introdc` , apartenența pe document = per rând
apartenența la document fiind per rând: `nract` /`serie_act` /`id_fact` ( rândurile au și `id_valuta` /
( `nract` /`serie_act` /`id_fact` ; rândurile au și `id_valuta` /`curs` proprii, dar codul istoric
`curs` proprii, dar istoric codul folosește doar `poAct.Curs` ).
folosește doar `poAct.Curs` ).
- **`import_nota` ** (nota contabilă a facturii) : grila pe `introdc` ; transport/alte taxe se introduc
- **`import_nota` **: grilă pe `introdc` ; transport/taxe = linii de notă suplimentare cu flagurile
ca linii de notă suplimentare cu flagurile `in_valuta` (suma e în valută) și `participa_valuta`
`in_valuta` (suma e în valută) și `participa_valuta` (linia intră în valoarea de intrare a
(linia intră în valoarea de intrare a mărfii). `do_executa` calculează procentele de repartizare
mărfii). `do_executa` calculează procentele de repartizare (`explicatia4` /`explicatia5` );
(stocate în `explicatia4` /`explicatia5` ); `inainte_de_do_termin` sumează bazele
`inainte_de_do_termin` sumează bazele (liniile în lei se împart la `poAct.Curs` ) și lansează
(lei/valută, liniile în lei se împart la `poAct.Curs` ) și lansează ** `import_nir` ** cu
** `import_nir` ** cu `procent_lei/procent_val/ncurs` .
`procent_lei/procent_val/ncurs` .
- **`import_nir` **: `do_adauga` → `viz_catalog_articole()` (`oproceduri_articole.prg:62` , câmp
- **`import_nir` ** (grila de articole): `do_adauga` → `viz_catalog_articole()`
`in_stoc` ="Gestionabil") filtrat pe conturile `scd` din notă; preț de intrare = preț valută ×
( `o proceduri_articole.prg:62` , câmpul `in_stoc` = „Gestionabil") filtrat pe conturile `scd` din
`procent_val` , apoi × `ncurs` × `procent_lei` (transportul „umflă" procentele peste 100).
notă; prețul de intrare per articol = preț valută × `procent_val` , apoi × `ncurs` × `procent_lei`
Diferența reziduală → linia „DIFERENTE". `do_modiparam` = preluare articole din XLS (creează
(transportul „umflă" procentele peste 100). Diferența reziduală devine linia „DIFERENTE" .
articole noi prin `pack_preturi.adauga_articol` , cont implicit 371) .
`do_modiparam` = preluare articole din XLS (creează articole noi prin `pack_preturi.adauga_articol` ,
- **Salvare**: `oscrie_in_fisiere` → `ACT_TEMP` /`RUL_TEMP` → pachet Oracle ** `PACK_CONTAFIN` **
cont implicit 371).
(`SCRIE_IN_ACT` , `SCRIE_IN_RUL` , `SCRIE_IN_STOC` ). `SCRIE_IN_RUL` inserează în `RUL` **exact**
- **Salvare**: `oscrie_in_fisiere` → tabelele temporare `ACT_TEMP` /`RUL_TEMP` → pachetul Oracle
`RUL_TEMP` (nu derivă din ACT) — filtrarea de rulaje se face în clientul VFP. Sursă pachet:
** `PACK_CONTAFIN` ** (`SCRIE_IN_ACT` , `SCRIE_IN_RUL` , `SCRIE_IN_STOC` , …). `SCRIE_IN_RUL` inserează
`COMUN\docs\PACK_CONTAFIN.pck` (`COMUN\docs\oracle_export.md` ).
în `RUL` **exact** conținutul `RUL_TEMP` (nu derivă din ACT) — orice filtrare de rulaje se face în
- **Articole negestionabile** (`nom_articole.in_stoc=0` ): model canonic în clasa ** `nir` ** (NIR
clientul VFP. Sursa pachetului: `COMUN\docs\PACK_CONTAFIN.pck` (vezi `COMUN\docs\oracle_export.md` ).
obișnuit) — articolul intră normal în grilă, iar `inainte_de_do_termin` face
- **Articole negestionabile** (`nom_articole.in_stoc = 0` ): modelul canonic e în clasa ** `nir` **
`Delete From rul_temp Where in_stoc=0` (valoarea rămâne doar pe nota contabilă). Din v2.11.6
(NIR-ul obișnuit, același `ointroduceri.vcx` ), mecanism din 2009 — articolul intră normal în g rilă
`import_nir` e aliniat la același model, cu cursor temporar `crsNegest` care readaugă rându rile
și participă la calcule, iar în `inainte_de_do_termin` , chiar înainte de scriere, se face
șterse în grilă dacă `oscrie_in_fisiere` eșuează. `rul_temp` are coloana `in_stoc` din view-ul
`Delete From rul_temp Where in_stoc = 0` (valoarea rămâne doar pe nota contabilă, nu ajunge î n
sursă `vrul` ; la `do_adauga` se umple prin `GATHER NAME loArt` , la `do_modiparam` (XLS) di n
`RUL` /`STOC` ). Din v2.11.6 `import_nir` e aliniat la același model, cu un cursor temporar
select pe `nom_articole` .
(`crsNegest` ) care readaugă rândurile șterse în grilă dacă `oscrie_in_fisiere` eșuează.
- **Explicație TVA**: dropdown `Grid1.cExplicatieTva.Combo1` (RowSource `jtva_coloane2` ); variantă
Cursorul `rul_temp` are coloana `in_stoc` pentru că e creat din view-ul `vrul` (`where 1=2 ` );
cu căutare = `frm_modific2024.do_modifica_explicatie_tva` (`omodificari.vcx ` ) →
la `do_adauga` coloana se umple prin `GATHER NAME loArt` din cursorul catalogului, la
`caut_explicatie_tva()` (`ocautare.prg:1934` , view `vjtva_coloane` , întoarce DOAR
`do_modiparam` (XLS) explicit din selectul pe `nom_articole` .
`id_jtva_coloana, denumire, cota_tva` ). Capcane: `introdc.ptva` ține **cota brută** (21), în
- **Explicație TVA**: dropdown istoric `Grid1.cExplicatieTva.Combo1` (RowSource `jtva_coloane2` );
registru jurnal `proc_tva` =(cota+100)/100; comutarea 4427/401 pe linia de TVA = SEEK în
modelul alternativ cu formular de căutare = `frm_modific2024.do_modifica_explicatie_tva `
`cJtvaCol4427 `.
(`omodificari.vcx` ) → `caut_explicatie_tva()` (`ocautare.prg:1934` , view `vjtva_coloane` ,
întoarce DOAR `id_jtva_coloana, denumire, cota_tva` ). Capcane: în `introdc.ptva` se ține **cota
brută** (21), pe când în registru jurnal `proc_tva` = (cota+100)/100; comutarea 4427/401 pe linia
de TVA se face prin SEEK în cursorul `cJtvaCol4427` .
Căutarea generică: `cauta_alfa()` (`COMUN\programe\cauta_alfa.prg` ) → forma `cauta_alfa_form_plus` .
Căutare generică: `cauta_alfa()` (`COMUN\programe\cauta_alfa.prg` ) → forma `cauta_alfa_form_plus` .
## Structura reală a ș ablonului `vnote_contabile` (verificat 11.07.2026)
## Ș ablon `vnote_contabile`
Cursorul `introdc` NU pornește cu „2 linii" — se încarcă cu ** toate** perechile-șablon din
Cursorul `introdc` NU pornește cu „2 linii" — se încarcă cu toate perechile-șablon din
`vnote_contabile` pentru `id_set` . Numărul de linii pe baza de dev (`MARIUSM_AUTO` ):
`vnote_contabile` pentru `id_set` . Pe dev (`MARIUSM_AUTO` ): **208=10 linii, 209=10, 220=10,
**208 = 10 linii, 209 = 10, 220 = 10, 221 = 6** . Cele 10 linii ale setului de import (208) =
221= 6**. Cele 10 linii ale setului 208 = **5 perechi pre-alocate (bază+TVA) = 5 sloturi** :
**5 perechi pre-alocate (bază+TVA) = 5 sloturi de document** :
| Pereche (ordine) | SCD/SCC bază | SCD/SCC TVA | Ce e |
| Pereche (ordine) | SCD/SCC bază | SCD/SCC TVA | Ce e |
|---|---|---|---|
|---|---|---|---|
@@ -69,205 +62,178 @@ Cursorul `introdc` NU pornește cu „2 linii" — se încarcă cu **toate** per
| 4 (7-8) | 3028/446 | 4426/446 | vamă/DVI (pe dev) |
| 4 (7-8) | 3028/446 | 4426/446 | vamă/DVI (pe dev) |
| 5 (9-10) | 3028/446 | 4426/446 | slot repetat (gol) |
| 5 (9-10) | 3028/446 | 4426/446 | slot repetat (gol) |
Model CONT2000: în loc să adaugi rânduri, ai **sloturi goale pre-alocate** pe care le completezi.
Model CONT2000: sloturi goale pre-alocate, nu rânduri adăugate. Factura principală = perechea 1;
Factura principală = perechea 1 (2 linii); restul sunt sloturi pentru facturi suplimentare (3 marfă)
restul = sloturi pentru facturi suplimentare (3 marfă) + DVI (2). Un „document" = o pereche cu date.
+ DVI (2). Un „document" = o pereche cu date.
**Corecție importantă (Marius, verificat pe bază reală de producție):** conturile 446 din setul 208
**Conturile 446 din setul 208 de pe dev NU sunt reprezentative** — pe producție DVI-ul vamă
de pe dev **NU sunt reprezentative** . În realitate și DVI-ul vamă folosește **4426 = 401** (nu 446).
folosește **4426= 401** (nu 446). `vnote_contabile` e configurabil per firmă, nu hardcoda conturile:
`vnote_contabile` e **configurabil per firmă** , deci șablonul variază — nu hardcoda conturile, ci
clonează perechea-șablon reală a setului (un singur pattern 3028/401 + 4426/4427 sau 4426/401
clonează perechea-șablon reală a setului. Practic: **un singur pattern de conturi** (bază 3028/401 +
acoperă și marfă și DVI — DVI diferă doar prin partener/unde e plătit TVA).
TVA 4426/4427 sau 4426/401) acoperă și marfă, și DVI (DVI diferă doar prin partener/unde e plătit
TVA, nu prin conturi).
## DECIZIE 11.07.2026 — pivot la „Direcția A" (rescriere flux introducere)
## Plan: pivot la „Direcția A" (rescriere flux introducere, scop M2, neimplementat )
Marius a decis să se **abandoneze modelul cu 10 sloturi pre-alocate** . Noul model (de implementat
Model țintă, abandonează cele 10 sloturi pre-alocate:
într-o sesiune viitoare, e scop **M2** , mai mare decât runda 1 M1/M3/M6):
1. ** `achizitie_import` ** : `introdc` pornește **gol** (0 rânduri de document) ; se păstrează perechea-
1. `achizitie_import` : `introdc` pornește **gol** ; se păstrează perechea-șablon (bază+TVA, conturi
șablon (bază+TVA, cu conturile reale din `vnote_contabile` ale setului) ca ** sursă de clonare** .
reale din `vnote_contabile` ) doar ca sursă de clonare.
Nu se mai încarcă cele 10 sloturi.
2. Buton „Adaugă factură" + dialog: clonează perechea-șablon, o completează, o adaugă în
2. **Buton „Adaugă factură" + dialog (M2)** : clonează perechea-șablon, o completează cu datele
`introdc` . Folosit și pentru factura principală, și pentru DVI (radio-ul furnizor/DVI/fără
facturii, o adaugă în `introdc` . Se folosește **și pentru factura principală** , și pentru DVI
schimbă doar partenerul/unde e plătit TVA, nu conturile).
(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
3. **M1 (culori)** : rămâne cum e — fiecare factură adăugată = o pereche = un document = o culoare
(F1, F2, …), cheie `serie+nr+id_fdoc` .
(F1, F2, …), incremental, cheie `serie+nr+id_fdoc` . Codul M1 deja aplicat merge neschimbat pe
acest model.
Avertismente: a tinge `COMUN` (blast radius: 22 `.pjx` referă `ointroduceri` ) și ** schimbă
Atinge `COMUN` (22 `.pjx` referă `ointroduceri` ) și schimbă comportamentul pentru toți userii de
comportamentul pentru toți userii de import** (nu mai văd sloturi pre-completate) — testare atentă,
import (nu mai văd sloturi pre-completate) — testare atentă.
dare în funcțiune deliberată.
## Model curent `import_nota` (după rundele 8– 12, 07.2026, necomis)
## Model curent `import_nota`
- **`doc_key` e unic per document**: prefix `nr_doc` (contor intern) + câmpurile de business
- **`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
(`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 mai
(partener/nract/4426) dar **păstrează doc_key-ul facturii-mamă** — identitatea nu se amestecă
amestecă între documentele care partajează același nr. DVI (cauza istorică a: explicație TVA
între documente care partajează același nr. DVI.
scrisă pe T-ul altui document, suma T=0, ștergere în grup, propagare `copiaza_valoare` între
documente străine).
- **Spargerea principalei** (`sparge_document` /`creeaza_rand_s` ): grupare `rul_temp` pe
- **Spargerea principalei** (`sparge_document` /`creeaza_rand_s` ): grupare `rul_temp` pe
`cont+acont` ; analiticul articolului merge direct în `ascd` al rândului S (fallback
`cont+acont` ; analiticul articolului merge direct în `ascd` al rândului S (fallback
`assign_analitic` doar când nu e furnizat).
`assign_analitic` doar când nu e furnizat).
- **Procentele per rând** (`explicatia4` /`explicatia5` = coloanele " Procent lei/valuta"): scrise
- **Procentele per rând** (`explicatia4` /`explicatia5` = „ Procent lei/valuta"): scrise de
de `sincronizeaza()` după spargere (nu de `do_executa` , care nu mai scrie procente), numitor =
`sincronizeaza()` după spargere (nu de `do_executa` ), numitor = baza documentului principal
baza documentului principal (`This.nbazaprincipala_lei/_val` , calculate î n `recalculeaza` ).
(`This.nbazaprincipala_lei/_val` , di n `recalculeaza` ). Principala=100.00; celelalte = pondere
Principala = 100.00; celelalte = pondere lei și valută (convertit la `oact.Curs` dacă e în lei).
lei/ valută (convertit la `oact.Curs` dacă e în lei).
- **Ștergere/copiere**: `do_sterge` = meniu Da(document, pe doc_key)/Nu(doar linia)/Cancel;
- **Ș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`
`do_copiaza_nota` (buton „Copiază") = același meniu; copiază doar B+T cu `nr_doc` /`doc_key` noi
noi (S/D se regenerează la sincronizare). Capcană rezolvată acolo: `Calculate` mută pointerul
(S/D se regenerează la sincronizare). `Calculate` mută pointerul la EOF → poziția sursă se
la EOF → poziția sursă se salvează/restaurează înainte de `Scatter` .
salvează/restaurează înainte de `Scatter` .
- **Rândul TOTAL** de sub grila notelor: `TxtFtvaLei` /`TxtFtvaVal` (fără TVA) lângă
- **Rândul TOTAL** sub grila notelor: `TxtFtvaLei` /`TxtFtvaVal` (fără TVA) lângă `Text2` /`Text3`
`Text2` /`Text3` (cu TVA), setate în `actualizeaza_banda` .
(cu TVA), setate în `actualizeaza_banda` .
- **Test e2e de referință**: `COMUN\utile\Teste\test_scenariu_dvi_complet.prg` (scenariul complet
- **Test e2e de referință**: `COMUN\utile\Teste\test_scenariu_dvi_complet.prg` (3 documente pe
cu 3 documente pe același DVI + articole 212/1 și 371/4 + ștergere/copiere).
același DVI + articole 212/1 și 371/4 + ștergere/copiere).
- **Valori manuale protejate (r16) **: `mod_manual=1` se setează și pe rândul T (Valid-urile
- **Valori manuale protejate**: `mod_manual=1` se setează și pe rândul T (Valid-urile
`cSuma` /`cSumaVal` ); `do_executa` nu rescrie T manual , `sparge_document` nu-l șterge și nu
`cSuma` /`cSumaVal` ); `do_executa` nu-l rescrie, `sparge_document` nu-l șterge/duplică pe aceeași
creează duplicat pe aceeași cotă (`ptva` ). Nimic nu resetează `mod_manual` pe rând existent.
cotă (`ptva` ). Nimic nu resetează `mod_manual` pe rând existent.
- **`nOldVal` unic (r15) **: o singură proprietate pentru valoarea la intrarea în celulă
- **`nOldVal` unic**: o singură proprietate pentru valoarea la GotFocus — Valid/LostFocus ies
(GotFocus) — Valid/LostFocus ies devreme la valoare nemodificată (`cSuma` , `cSumaVal` ,
devreme la valoare nemodificată (`cSuma` , `cSumaVal` , `cCotaTva` , `cPretFactura` ).
`cCotaTva` , `cPretFactura` ). ` do_calculeaza_diferente` restaurează poziția `rul_temp` de la
`do_calculeaza_diferente` restaurează poziția `rul_temp` de la intrare; `do_reface`
intrarea în procedură (`tnRecNo` nu mai poziționează); `do_reface` repoziționează explicit.
repoziționează explicit.
- **Totaluri articole (r17) **: `_grdfooter1` (clasa `_grdfooter` din `_grd_base.vcx` ) atașat la
- **Totaluri articole**: `_grdfooter1` (clasa `_grdfooter` din `_grd_base.vcx` ) atașat la
`GridArt` la finalul `Init` ; sume pe `cValoareValutaFactura` /`cValoareValutaCalculat` /
`GridArt` la finalul `Init` ; sume pe `cValoareValutaFactura` /`cValoareValutaCalculat` /
`cValoareLeiCalculat` din `rul_temp` , recalculate în `actualizeaza_banda` +
`cValoareLeiCalculat` din `rul_temp` , recalculate în `actualizeaza_banda` +
`do_calculeaza_diferente` . Vechile `Clb_leiftva/leitva/valftva/valtva` (dublau totalurile
`do_calculeaza_diferente` . Totalurile de sub grila notelor rămân din `introdc` ; Diferențe lei =
notelor) au fost eliminate de pe `import_nota` . Totalurile de sub grila notelor rămân din
bază note − Σ articole.
`introdc` ; Diferențe lei = bază note − Σ articole .
- **Test TVA manual + footer**: `COMUN\utile\Teste\test_manual_tva_footer.prg` .
- **Test scenariul TVA manual + footer**: `COMUN\utile\Teste\test_manual_tva_footer.prg` .
- **TVA pe creditori și la secundare**: `sparge_document` regrupează T-urile nemanuale ale
- **TVA pe creditori și la secundare (r30)**: `sparge_document` regrupează T-urile nemanuale ale
documentelor secundare pe creditorii (scc/ascc) rândurilor S proprii, proporțional cu sumele S
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
(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
(fără S-uri: împărțire egală 1/N); `uneste_document` reunește T-urile sparte când documentul
revine la B. Armarea r esincronizării din `cScc/cAscc.Valid` se face pe garda `nOldVal` (nu pe
revine la B. R esincronizarea 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`
comparația cu câmpul — grila poate scrie câmpul înaintea Valid-ului, ex. Enter) + gardă
contra Valid-urilor re-declanșate de regenerare. Test: `COMUN\utile\Teste\test_tva_secundare.prg` .
`lInSync` contra Valid-urilor re-declanșate de regenerare. Test:
`COMUN\utile\Teste\test_tva_secundare.prg` .
## TVA DVI cu valută proprie + discount financiar (07-08.2026)
## TVA DVI cu valută proprie + discount financiar
- **Două coloane noi client-side pe `introdc` ** (fără migrare Oracle): `rand_dvi` (identitate —
- **Două coloane client-side pe `introdc` ** (fără migrare Oracle): `rand_dvi` (identitate — 1 pe
1 pe rândul T scris de ramura DVI a `do_adauga_factura` ) și `valuta_proprie` (autonomie — 1
rândul T scris de ramura DVI a `do_adauga_factura` ) și `valuta_proprie` (1 când acel rând T are
când acel rând T are valută/curs diferite de ale documentului, setat la creare sau din grilă).
valută/curs diferite de ale documentului, setat la creare sau din grilă). Nu s-a refolosit
Nu s-a refolosit `mod_manual` — e supraîncărcat semantic (`sparge_document` face match și p e
`mod_manual` — e supraîncărcat semantic (`sparge_document` face match pe el pentru alt e
el pentru alte scopuri, ar fi dat duplicate/omisiuni de rânduri T).
scopuri, ar da duplicate/omisiuni de rânduri T).
- **Rând „protejat"** = `Nvl(valuta_proprie,0)=1` (helper unic `rand_protejat()` , folosit
- **Rând „protejat"** = `Nvl(valuta_proprie,0)=1` (helper `Thisform. rand_protejat()` ). Ocolit de
peste tot ca `Thisform.rand_protejat()` ). E ocolit de `copiaza_valoare` (câmpurile valutare nu
`copiaza_valoare` (câmpurile valutare nu se propagă pe el, câmpurile de identitate nu se propagă
se propagă pe el, câmpurile de identitate nu se propagă pe orice rând `rand_dvi=1` ) ,
pe orice rând `rand_dvi=1` ), `do_executa` , `recalc_tva_document` , `uneste_document` ,
`do_executa` , `recalc_tva_document` , `uneste_document` , `sparge_document` (vezi mai jos)
`sparge_document` și handlerele de grilă `cCurs` /`cInValuta` /`cValuta` (editare locală, fără
și handlerele de grilă `cCurs` /`cInValuta` /`cValuta` (editare LOCALĂ, fără
propagare). Re-alegerea valutei documentului pe rândul protejat resetează `valuta_proprie=0`
propagare). Re-alegerea valutei documentului pe rândul protejat resetează `valuta_proprie=0`
(revert — reintră în propagări ). Un DVI cu valuta facturii rămâne `valuta_proprie=0` și
(revert). Un DVI cu valuta facturii rămâne `valuta_proprie=0` și urmează factura la propagări.
urmează factura la propagări, identic cu azi.
- **Spargerea T-ului protejat** (`sparge_tva_protejat` , din `sparge_document` ): când TOATE
- **Spargerea T-ului protejat** (`sparge_tva_protejat` , apelată din `sparge_document` ): când
rândurile T ale documentului sunt protejate, TVA-ul facturii e integral cel de pe DVI — nu se
TOATE rândurile T ale documentului sunt protejate, TVA-ul facturii e integral cel de pe DVI —
fabrică rânduri T din rândul de bază. Varianta `loRandT = loBaza` dădea TVA dublă (rândul
nu se mai fabrică rânduri T din rândul de bază. Varianta inițială (`loRandT = loBaza` ) dădea
protejat rămânea, plus un T nou cu conturile bazei în valuta facturii). Acum totalul protejat se
**TVA dublă** : rândul protejat rămânea, plus un T nou cu conturile bazei (371 în loc de 4426 ),
sparge pe cotele articolelor (pondere `bază_cotă × cotă` , rest de rotunjire pe |suma| maximă ),
în valuta facturii. Acum tot alul protejat se sparge pe cotele articolelor (pondere
păstrând prin `Gather` identitatea vamală/v aluta proprie/`rand_dvi` /`valuta_proprie` . O singură
`bază_cotă × cotă` , rest de rotunjire pe |suma| maximă), păstrând prin `Gather` identitatea
cotă = niciun rând atins.
vamală, valuta proprie și `rand_dvi` /`valuta_proprie` — deci rândurile rezultate rămân
- **Discount financiar pe factură**: un rând `tip_rand='G'` (401=767, suma=discount fără TVA),
protejate. O singură cotă = niciun rând atins (ieșire devreme). Analogul lui B → S, dar pe T.
același `doc_key` /`nr_doc` cu factura, `participa_valuta=.F.` . Marfa (3028) și prețurile
- **Discount financiar pe factură**: un singur rând `tip_rand='G'` (401=767, suma = discountul
articolelor rămân la valoarea integrală — discountul schimbă doar baza TVA: `T=(b-d)*p/100` ,
fără TVA), același `doc_key` /`nr_doc` cu factura, `participa_valuta=.F.` . Marfa (3028) și
sold 401 = `b + (b-d)*p/100 - d` . Ordine la creare: B → G → T. Excluse din bazele
prețurile articolelor rămân pe valoarea integrală — discountul schimbă doar baza de calcul a
TVA/diferență/spargere prin filtrele deja existente pe `Inlist(tip_rand,'B','S')` /`'T'` — un
TVA-ului: `T = (b-d)*p/100` , sold 401 = `b + (b-d)*p/100 - d` . Ordinea la creare e B → G → T.
singur loc nu filtra pe `tip_rand` (suma valută din footer, `do_executa` ), a primit
Excluse structural din bazele TVA/diferență/spargere prin filtrele deja existente pe
`And tip_rand<>'G'` . Punct unic de adevăr: `disc_document(doc_key, tlValuta)` . Se scade DIRECT
`Inlist(tip_rand,'B','S')` /`'T'` — un singur loc nu filtra pe `tip_rand` (suma în valută di n
în `do_executa` /`recalc_tva_document` (același `doc_key` ), dar se REPARTIZEAZĂ prin factor î n
footer, `do_executa` ) și a primit `And tip_rand<>'G'` .
`sparge_document` (bazele vin din `crsGrupCota` , nu din `suma_doc` ). Factorul e clampat la 0
Punct unic d e a devăr: `disc_document(doc_key, tlValuta)` . Se scade DIRECT în `do_executa` și
(discountul poat e depăși baza articolelor când rămâne rest pe rândul de diferențe → altfel TVA
`recalc_tva_document` (același `doc_key` , exact), dar se REPARTIZEAZĂ prin factor în
negativă). `suma_doc` /`suma_doc_val` rămân BRUTE (`sparge_document` repartizează toată baza pe
`sparge_document` , unde bazele vin din `crsGrupCota` (articole), nu din `suma_doc` . Factorul e
S, `verifica_sincronizare` compară `Suma(S)` cu `suma_doc` ) . `do_adauga_factura` citeșt e
clampat la 0: validarea din dialog compară discountul cu suma facturii, care poate fi mai mare
`toDlg.disc_baza_lei` sub gardă `Type(...)<>'U'` — apelabil programatic cu `toDlg` minimal
decât baza articolelor când rămâne rest pe rândul de diferențe → altfel ar ieși TVA negativă.
(import e-Factura/XLS). Limită cunoscută: rândurile create ulterior de
`suma_doc` /`suma_do c_val` rămân BRUTE (`sparge_document` repartizează toată baza pe rândurile
`sparge_document` /`recal c_t va_document` se adaugă la finalul cursorului — ordinea vizuală B/G/T
S, iar `verifica_sincronizare` compară `Suma(S)` cu `suma_doc` ).
nu mai e garantată (grila nu are sortare ).
`do_adauga_factura` citește `toDlg.disc_baza_lei` sub gardă `Type(...)<>'U'` — e apelabil
programatic cu un `toDlg` minimal (import e-Factura/XLS), iar `Nvl` singur nu apără de o
proprietate inexistentă.
Limită cunoscută: rândurile create ulterior de `sparge_document` /`recalc_tva_document` se
adaugă la finalul cursorului, deci după sincronizare 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
- **Bifa „Valuta" pe secțiunea TVA DVI** (`chkDviInValuta` ): pornește după factură (bifată când
factura e în valută) și rămâne pe alegerea manuală prin `ldviinvalutadirty` , ca `linvalutadirty`
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-ului și forțează `nDviCurs=1` ,
la bază) . Scoasă → ascunde valuta/cursul/TVA-ul valutar al DVI, forțează `nDviCurs=1` ,
`nDviTvaVal=0` . Condiția de r amură din `recalc_tva` trebuie să includă bifa, nu doar
`nDviTvaVal=0` . R amura din `recalc_tva` trebuie să includă bifa, nu doar
`dvi_valuta_efectiva()` — aceasta din urmă întoarce valuta facturii și nu știe de bifă, deci
`dvi_valuta_efectiva()` ( întoarce valuta facturii, nu știe de bifă — fără ea se scrie
fără ea se scrie `dvi_tva_val` nenul împreună cu `dvi_in_valuta=.F.` . Cazul nou „factură în
`dvi_tva_val` nenul cu `dvi_in_valuta=.F.` ) . „Factură în valută, DVI doar în lei" dă
valută, DVI doar în lei" dă `dvi_valuta_proprie=1` .
`dvi_valuta_proprie=1` .
- **Concordanța cotelor pe factură multi-cotă**: rândurile S se generează pe `cont+analitic+cota_tva`
- **Concordanța cotelor pe factură multi-cotă**: rândurile S se generează pe
(doar la documentul principal) și primesc cota articolelor lor — `creeaza_rand_s` are parametrul
`cont+analitic+cota_tva` (doar la documentul principal), primesc cota articolelor —
`tnCota` , care scrie `ptva` + `id_jtva_coloana` /`explicatie_tva` prin `gaseste_jtva_cota` (familia =
`creeaza_rand_s` are parametrul ` tnCota` , scrie `ptva` + `id_jtva_coloana` /`explicatie_tva` prin
coloana bazei). Rândul G se sparge la fel, proporțional cu bazele pe cotă (`sparge_discount` , apelat
`gaseste_jtva_cota` (familia = coloana bazei). Rândul G se sparge la fel, proporțional pe bazele
din `sparge_document` imediat după `calc_baze_cota_creditor` ), păstrând totalul discountului;
pe cotă (`sparge_discount` , din `sparge_document` după `calc_baze_cota_creditor` ), păstrând
e idempotentă (rec alc ulează din suma rândurilor G exis tente) . Fără astea, baza (S) și discountul (G)
tot alul discountului; idempo tentă . Fără astea, baza (S) și discountul (G) rămâneau pe cota
rămâneau pe cota facturii în timp ce T-urile erau deja sparte pe cotele articolelor — în jurnalul de
facturii în timp ce T-urile erau deja sparte pe cotele articolelor — jurnalul de TVA ieșea cu
TVA baza ieșe a pe altă cotă decât TVA-ul. Articolele fără cotă proprie preiau cota documentului
baz a pe altă cotă decât TVA-ul. Articolele fără cotă proprie preiau cota documentului înainte de
înainte de grupare (altfel apare un grup tranzitoriu „cota 0") .
grupare .
- **`nOldVal` e NUMERIC**: coloanele caracter din grila notelor (`cScc` /`cAscc` ) folosesc `cOldVal` .
- **`nOldVal` e NUMERIC**: coloanele caracter din grilă (`cScc` /`cAscc` ) folosesc `cOldVal` . O
O singură proprietate partajată între coloane numerice și caracter dădea „Operator/operand type
singură proprietate partajată dădea „Operator/operand type mismatch" în `cSuma.Text1.Valid`
mismatch" în `cSuma.Text1.Valid` când Valid se re-declanșa fără GotFocus (refresh de grilă din
când Valid se re-declanșa fără GotFocus. `cSuma` /`cSumaVal.Valid` au primit și garda `lInSync` .
`sincronizeaza` ). `cSuma` /`cSumaVal.Valid` au primit și garda `lInSync` , ca `cScc` /`cAscc` .
- **Două reprezentări ale „RON"**: rândul din picker (`caut_valuta` /`vnom_valute` ) e REAL cu
- **Două reprezentări ale „RON"** — capcană reală, ușor de reintrodus: rândul din picker
`id_valuta` nenul (`moneda_nationala=1` ); sentinela „fără valută" din `introdc` /`ACT` e
( `caut _valuta` /`vnom_valute` ) e un rând REAL cu `id_valuta` nenul (`moneda_nationala=1` );
`id _valuta=0` , `nume_val=''` . Comparațiile trebuie pe valuta EFECTIVĂ
sentinela „fără valută" din `introdc` /`ACT` e `id_valuta=0` , `nume_val=''` . Comparațiile
(`Empty(nume) Or Upper(nume)=='RON'` ), nu pe id brut — altfel `dvi_valuta_proprie` ar da fals 1.
trebuie făcute pe valuta EFECTIVĂ (`Empty(nume) Or Upper(nume)=='RON'` ), nu pe id brut — o
- **`copiaza_valoare` și rândul DVI — gardă în ambele sensuri**: rândul T al DVI păstrează
factură fără valută + RON ales expli cit pe DVI sunt ambele „fără valută", dar o comparație
`doc_key` -ul facturii-mamă, de ci propagarea pe grup îl include. Trebuie sărit ca țintă
de id- uri brute le-ar vedea ca diferite (`dvi_valuta_proprie` ar da fals 1).
(identitatea fact urii nu-l atinge) și ca sursă (numărul/data/partenerul vamal nu ajung pe
- **`copiaza_valoare` și rândul DVI — gardă în ambele sensuri**: rândul T al DVI-ului păstrează
factură) — fără garda pe sursă, o trecere prin celula „Nr" a rândului DVI scria numărul DVI
`doc_key` -ul facturii-mamă, deci propagarea pe grup îl include. Trebuie sărit și ca **țintă**
peste toată factura (B/G/S) și, la Terminat, în `ACT` /`RUL.nract` . Rândul D scapă (își moștenește
(identitatea facturii nu-l atinge) și ca **sursă** (numărul/data/partenerul vamal nu ajung p e
antetul o singură dată, la creare). `copiaza_valoare` nu face refresh de grilă — coruperea e
factură). Fără garda pe sursă, o simplă trecere prin celula „Nr" a rândului DVI scria numărul
invizibilă până la salvare. Propagarea de partener are flux propriu
DVI-ului peste toată factura (B/G/S) și, la Terminat, în `ACT` și `RUL.nract` . Rândul D scapă,
(`Grid1.cPartC.Text1.Valid` ), aceeași gardă.
pentru că își moștenește antetul o singură dată, la creare. `copiaza_valoare` nu face refresh de
- **`ControlSource` calificat la runtime**: `This.Parent.ControlSource` întoarce `introdc.nract` ,
grilă, deci pe ecran rămân valorile vechi — coruperea e invizibilă până la salvare.
nu `nract` . Orice `Inlist` /comparație pe nume de câmp cu valoarea din Valid-urile de grilă
Propagarea de partener are flux propriu (`Grid1.cPartC.Text1.Valid` ), cu aceeași gardă.
trebuie să normalizeze întâi (taie prefixul până la ultimul punct) — altfel garda arată corect
- **`ControlSource` -ul unei coloane de grid e CALIFICAT la runtime**: `This.Parent.ControlSource`
scrisă și nu se activează niciodată. Test:
întoarce `introdc.nract` , nu `nract` (cum apare în `.vc2` ). Orice `Inlist` /comparație pe nume de
câmp cu valoarea primită 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ă. Regresie:
`COMUN\utile\Teste\achizitie_import\test_diag_controlsource_calificat.prg` .
`COMUN\utile\Teste\achizitie_import\test_diag_controlsource_calificat.prg` .
## Ștergere note — model istoric pe paritate ( înlocuit de rundele de mai sus)
## Ștergere note — model VECHI pe paritate, înlocuit de `doc_key`
- Butonul `But_sterge1` (clasa `but_sterge` ) șterge nota curentă = **perechea bază+TVA** , de p e
- `But_sterge1` (clasa `but_sterge` ) șterge perechea bază+TVA curentă, de pe oricare lini e
oricare din cele două linii (`lnBaza = Recno() - Iif(Mod(Recno(),2)=0,1,0)` , apoi `Delete Next 2` ).
(`lnBaza = Recno() - Iif(Mod(Recno(),2)=0,1,0)` , apoi `Delete Next 2` ), prin
Dispatch-ul butonului trece prin `inainte_de_do_sterge` , condiționat de `lactiv4` — setat `.T.`
`inainte_de_do_sterge` sub `lactiv4` (`.T.` în `Init` ).
în `Init` .
- Ștergere logică (`SET DELETED ON` , `roagest.prg:28` ) — Recno-urile rămân, paritatea impar/par
- Ștergerea e **logică** , nu fizică: `SET DELETED ON` (roagest.prg:28) ascunde perechea din grid și
se păstrează.
din to ate `Scan` /`Sum` -urile fluxului, iar Recno-urile rămase nu se schimbă → paritatea impar/par
- Capcană generală VFP: niciod ată `ZAP` /recreare pe cursorul-sursă al unui grid — grid-ul își
se păstrează (perechile se șterg mereu împreună, deci și `Skip` -urile peste liniile șterse cad
pierde sursa și rămâne blank.
corect). `Append Blank` ulterior continuă tot pe paritate corectă.
- **Capcană generală VFP: niciodată `ZAP` (sau închidere/recreare) pe cursorul-sursă al unui
grid** — Zap închide și recreează cursorul, grid-ul își pierde sursa și rămâne blank. Varianta
„copiez rândurile rămase + Zap + Append la loc" e greșită exact din acest motiv.
## Familia e xplicațiilor TVA pe factură multi-cotă (07.2026)
## E xplicații TVA pe factură multi-cotă
- **Perechea bază/TVA se citește din nomenclator** (`jtva_coloane2.id_tva` ), iar trecerea de la o
- **Perechea bază/TVA din nomenclator** (`jtva_coloane2.id_tva` ); trecerea între cote în aceeași
cotă la alta în aceeași familie se face pe **semnătura coloanei** (`coloana_jc` fără cifre:
familie = pe **semnătura coloanei** (`coloana_jc` fără cifre: `FO21B` →`FOB` , `FO21T` →`FOT` ,
`FO21B` →`FOB` , `FO21T` →`FOT` ) — ` gaseste_jtva_cota` . Potrivirea pe primele 2 caractere confunda
`gaseste_jtva_cota` ) . Potrivirea pe primele 2 caractere confunda baza cu TVA.
coloana de bază cu cea de TVA.
- **Alinierea T → S** (`aliniaza_tva_la_baza` , final `sparge_document` ): fiecare T primește
- **Alinierea T → S** (`aliniaza_tva_la_baza` , la finalul `sparge_document` ): fiecare rând T
perechea de TVA a S-ului de aceeași cotă, doar dacă explicația S-ului chiar are cota rândului.
primește perechea de TVA a rândului S de aceeași cotă, dar numai dacă explicația S-ului chiar ar e
Fără ea, o explicație din altă serie pe S (ex. `CE11CTB` ) muta T-urile în seria CE, în timp c e
cota rândului (altfel nu există pereche validă — cazul nomenclatorului incomplet). Fără ea, o
S se re-alinia la familia documentului (`FO` ) la resincronizare.
explicație aleasă din altă serie pe rândul S (ex. `CE11CTB` ) muta rândurile T în seria CE, în timp
- **`expl_manual` ** (coloană client-side, lângă `mod_manual` ): 1 când utilizatorul alege explicit
ce S-ul se re-alinia la familia documentului (`FO` ) la resincronizare — bază și TVA pe rubrici
explicația pe un rând T; rândurile marcate nu se aliniază. Nu s-a refolosit `mod_manual`
diferite în jurnalul de TVA.
(înseamnă „nu recalcula suma", ar fi înghețat și sumele). Peste regenerarea T-urilor eticheta se
- **`expl_manual` ** (coloană client-side pe `introdc` , lângă `mod_manual` ): 1 când uti liz atorul
păstrează prin snapshot pe cotă (`crsExplT` , luat înainte de ștergere, reap lic at după); la
alege explicit explicația pe un rând T (`aplica_explicatie_tva` / combo-ul din grilă); rândurile
`sparge_tva_protejat` rămâne doar pe cota rândului original.
marcate nu se aliniază. Nu s-a refolosit `mod_manual` — acela înseamnă „nu-mi recalcula suma"
- **Gol de nomenclator la 11%**: importul de bunuri nu avea perechea 11% (`FO11B` /`FO11T` =
și ar fi înghețat și sumele. Peste regenerarea rândurilor T eticheta se păstrează prin snapshot
236/237, adăugate 30.07.2026). Fără pereche, `sparge_document` lasă în `cmesajsync` „Documentul
pe cotă (`crsExplT` , luat înainte de ștergere, reaplicat după); la `sparge_tva_protejat` rămâne
< fdoc > are marfă la < cotă > %, dar nu există explicație TVA de < cotă > % în seria '< serie > '" și
doar pe cota rândului original, cotele apărute din spargere se aliniază normal.
finalizarea rămâne blocată; utilizatorul poate ocoli alegând manual o explicație de 11% din altă
- **Golul de nomenclator la 11%**: importul de bunuri nu avea perechea de 11% (`FO11B` /`FO11T` =
serie.
236/237, adăugate 30.07.2026). Fără pereche, `sparge_document` lasă în `cmesajsync` mesajul
- **`tlIntern` în `achizitie_import` **: `Createobject('IMPORT_nota', tlIntern)` primește
„Documentul < fdoc > are marfă la < cotă > %, dar nu există explicație TVA de < cotă > % în seria
parametrul, dar codul din clasă citește `Thisform.lIntern` — o referință la `tlIntern` dintr-o
'< serie > '" și finalizarea rămâne blocată; utilizatorul poate ocoli lipsa alegând manual o
metodă a formularului prinde variabila PRIVATE a apelantului doar cât timp acesta e pe stivă
explicație de 11% din altă serie .
(în `inainte_de_do_termin` era deja ieșit) .
- **`tlIntern` în `achizitie_import` **: `Createobject('IMPORT_nota', tlIntern)` primește parametrul
procedurii, dar codul din clasă trebuie să citească `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ă
- **Fixture-uri de test**: cele 32 de teste din `COMUN\utile\Teste\achizitie_import` își creează
singure `introdc` prin `CREATE CURSOR` — orice coloană nouă folosită în SQL-ul din clasă trebuie
`introdc` prin `CREATE CURSOR` — orice coloană nouă folosită în SQL-ul din clasă trebuie
adăugată și acolo, altfel apare „SQL: Column '< X > ' is not found".
adăugată și acolo, altfel „SQL: Column '< X > ' is not found".