Files
roagest/docs/flux-achizitie-import.md

249 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# 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`.
- **Rândul D (diferență 6xx)** e clonat prin `Scatter`/`Gather` din rândul de bază, deci
`recalc_diferente` trebuie să golească explicit tot ce ține de TVA: `id_jtva_coloana`, `ptva`,
`cu_tva` și `taxcode` (cod SAF-T). Codul de taxă stă doar pe liniile de bază, TVA și discount.
`oscrie_in_fisiere.prg:260` scrie `NULL` în `ACT` pentru `taxcode` gol.
## 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".