Files
roagest/docs/propuneri_achizitie_import.md
Marius Mutu ca21bfe7a2 Docs: flux achizitie import, propuneri sesizare, mod de lucru Claude
- docs/propuneri_achizitie_import.md: modificarile propuse (cu cod) pentru
  sesizarea achizitie import (explicatie TVA prin cautare, valute/cursuri
  per document, conturi 2xx la articole, negestionabile fara RULAJE)
- docs/flux-achizitie-import.md: notita durabila cu lantul fluxului
- docs/CLAUDE.md: modul de lucru (propuneri pentru VFP IDE, export Oracle)
- .gitignore: docs/local/ (credentiale neversionate)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 23:18:03 +03:00

17 KiB
Raw Blame History

Propuneri de modificare — Sesizare ACHIZIȚIE IMPORT

Fișier de lucru (se șterge/arhivează după implementare). Modificările se aplică în VFP IDE (clasele sunt în binare .vcx); după aplicare se rulează vcx2txt.ps1 pentru refresh cache.

Context flux (detalii durabile în docs/flux-achizitie-import.md): gestiuni.vc2:2045-2171DO achizitie_import WITH 208/209/220/221, llIntern IN ointroduceri.prgointroduceri.prg:1435-1617 construiește poAct (din ACT/actactan), cursorul liniilor de notă introdc, cursorul jtva_coloane2, apoi CREATEOBJECT('IMPORT_nota') (:1599). import_nota.inainte_de_do_termin lansează import_nir (grila de articole → rul_temp), iar salvarea finală trece prin oscrie_in_fisierePACK_CONTAFIN.SCRIE_IN_ACT/SCRIE_IN_RUL/SCRIE_IN_STOC. import_nota_original (ointroduceri.vc2:8269) NU e instanțiat nicăieri — e copie de rezervă, nu se modifică. Liniile de mai jos se referă la cache-ul text D:\ROA\UTIL\foxbin2prg\_textcache_roagest\comun\clase\ointroduceri.vc2 (stadiul de la 08.07.2026, cu implementarea ta începută la punctul 1 deja inclusă).


1. Explicație TVA prin formular de căutare (în loc de dropdown)

Stadiu: ai făcut deja în import_nota: Column4 (cExplicatieTva).CurrentControl = "Text1", ai adăugat Text1 cu DblClick/InteractiveChangedo_modifica_explicatie_tva (:7628-7697), copiat după frm_modific2024.do_modifica_explicatie_tva (omodificari.vc2:12829). Combo1 + Combo1.Valid rămân în clasă, dezactivate (decizie: păstrate ca fallback).

Probleme găsite în versiunea ta actuală (comparat cu originalul Combo1.Valid, :7962-8027, a cărui logică trebuie păstrată identic):

# Problema Detaliu
A lnPtva = (locauta.cota_tva + 100) / 100 e greșit Formula vine din registru jurnal, unde câmpul e proc_tva (1.21). În introdc.ptva, originalul Combo1.Valid salvează cota brută (lnPtva = jtva_coloane2.cota_Tva, ex. 21). Cu formula ta: (a) ptva primește 1.21 în loc de 21 → calculele de TVA din cSuma.Text1.Valid ies greșite; (b) testul If lnPtva = 0 pentru cota 0% nu mai e adevărat niciodată (0% → 1), deci ramura de golire a liniei de TVA nu se mai execută.
B lnId_tva = locauta.id_tva — proprietatea nu există caut_explicatie_tva (ocautare.prg:1951) selectează doar id_jtva_coloana, denumire, cota_tva din vjtva_coloane. Obiectul întors NU are .id_tva → eroare la runtime. id_tva trebuie citit din jtva_coloane2 după LOCATE (cursorul e deja deschis în acest flux, ointroduceri.prg:1586-1594, și conține coloana ID_TVA). LOCATE-ul există deja la tine (:7647-7648) dar nu-i folosești rezultatul și nu verifici FOUND().
C lnTipEx = iif(introdc.scd='4428',2,1) evaluat pe linia greșită Pe linia de bază (impară) scd e contul de marfă (3xx/2xx), niciodată 4428 → mereu tlTipEx=1. 4426/4428 e pe linia pereche de TVA (para). Trebuie să te uiți la linia următoare când ești pe linia de bază. Alternativă mai simplă: introdc.tva_incasare (moștenit din poAct, vezi ointroduceri.prg:1558) — IIF(introdc.tva_incasare=1, 2, 1).
D Guard pe anulare Store "" To loCauta în caut_explicatie_tva + acces direct .id_jtva_coloana poate da eroare dacă utilizatorul închide căutarea. Adaugă Vartype(loCauta) <> 'O' în test.

Cod propus (înlocuiește integral import_nota.do_modifica_explicatie_tva):

PROCEDURE do_modifica_explicatie_tva
	*!* cautare explicatie TVA prin formular (model: frm_modific2024 din omodificari.vcx),
	*!* pastrand integral logica originala din Grid1.cExplicatieTva.Combo1.Valid
	*!* (linia de baza + linia pereche de TVA + comutarea 4427/401)
	LOCAL lnId, lnPtva, lnId_tva, lnId_jtva_coloanaTVA, lnCota_tva, lcExplicatie_TVA
	LOCAL lcExplicatieTVA, lnRec, lnTipEx, loCauta

	Select introdc
	lnRec = Recno()

	* tipul exigibil(1)/neexigibil(2) se decide dupa contul liniei de TVA (linia para);
	* pe linia de baza (impara) scd e contul de marfa, deci ma uit la linia urmatoare
	If Mod(lnRec,2) <> 0 And lnRec < Reccount('introdc')
		Skip
		lnTipEx = Iif(Alltrim(scd) == '4428', 2, 1)
		Go lnRec
	Else
		lnTipEx = Iif(Alltrim(scd) == '4428', 2, 1)
	Endif

	loCauta = caut_explicatie_tva(Nvl(introdc.id_jtva_coloana,-1),,,m.lnTipEx)

	If Vartype(loCauta) <> 'O' Or Empty(Nvl(loCauta.id_jtva_coloana,0))
		Return
	Endif

	* ATENTIE: in introdc.ptva se salveaza COTA BRUTA (ex. 21), ca in Combo1.Valid.
	* NU (cota+100)/100 ca in registru jurnal - acolo campul e proc_tva, alta semantica.
	lnId            = loCauta.id_jtva_coloana
	lnPtva          = loCauta.cota_tva
	lcExplicatieTVA = loCauta.denumire

	* id_tva (perechea pentru linia de TVA) NU vine din caut_explicatie_tva
	* (selectul intoarce doar id_jtva_coloana, denumire, cota_tva) - il iau din jtva_coloane2
	Select jtva_coloane2
	Locate For id_jtva_coloana = lnId
	If !Found()
		Return
	Endif
	lnId_tva = jtva_coloane2.id_tva

	Select introdc
	Replace id_jtva_coloana With lnId, ptva With lnPtva, explicatie_tva With lcExplicatieTVA
	If Mod(lnRec,2)<>0 && linie impara BAZA
		Thisform.UpdateExplicatieSAFT()
		If lnPtva = 0
			Select introdc
			Replace cu_tva With 0
			Skip
			Replace id_jtva_coloana With 0, ptva With 0, explicatie_tva With '', cu_tva With 0
		Else && linia para TVA
			Select jtva_coloane2
			Locate For id_jtva_coloana = lnId_tva
			If Found()
				lnId_jtva_coloanaTVA = jtva_coloane2.id_jtva_coloana
				lnCota_tva           = jtva_coloane2.cota_Tva
				lcExplicatie_TVA     = jtva_coloane2.denumire

				* PENTRU TAXARE INVERSA SI COMUNITATEA EUROPEANA: 4426 = 4427 PE LINIA DE TVA, ALTFEL 4426 = 401
				Select introdc
				Replace cu_tva With 1
				Skip
				Replace id_jtva_coloana With lnId_jtva_coloanaTVA, ptva With lnCota_tva, ;
					explicatie_tva With lcExplicatie_TVA, cu_tva With 1
				If Seek(lnId_jtva_coloanaTVA, "cJtvaCol4427", "id_jtva")
					Replace scc With '4427' In introdc
				Else
					Replace scc With '401' In introdc
				Endif
				Thisform.UpdateExplicatieSAFT()
			Endif
		Endif
	Endif

	Thisform.grid1.cSuma.teXT1.Valid
ENDPROC

Recomandări suplimentare:

  • În Grid1.cExplicatieTva.Text1.InteractiveChange, după apel, adaugă This.Value = introdc.explicatie_tva — altfel caracterul tastat (care a declanșat evenimentul) rămâne în buffer-ul textbox-ului și poate suprascrie explicația la părăsirea celulei:
    PROCEDURE Grid1.cExplicatieTva.Text1.InteractiveChange
    	Thisform.do_modifica_explicatie_tva()
    	This.Value = introdc.explicatie_tva
    ENDPROC
    
  • De testat mapping-ul tlTipEx: comentariile din caut_explicatie_tva (ocautare.prg:1936-1942) spun 1=JC exigibil / 2=JC neexigibil, dar filtrele din DO CASE (:1962-1979) arată invers la prima citire (case 1 filtrează id_jtva_neex is not null). Testează cu un document normal și unul cu TVA la încasare (4428) și, dacă lista afișată e cea greșită, inversează 1↔2 în lnTipEx.
  • Testează explicit: cotă 21%, cotă 0% (scutit), taxare inversă/CE (trebuie scc='4427' pe linia de TVA), TVA la încasare (4428), anulare din formularul de căutare, apel de pe linia de TVA (para) direct.

2. Valute și cursuri diferite pentru transport / alte taxe

Modelul de documente al fluxului (clarificat de Marius): un NIR de import cuprinde

  • factura principală de achiziție (marfă și/sau imobilizări/alte articole) — valuta și cursul ei sunt cele din antet: poAct.id_valuta / poAct.Curs; ea rămâne referința în care se convertește tot;
  • opțional factura de transport — poate fi în altă valută, cu alt curs;
  • opțional factura de taxe vamale (comisionar/vamă) — de regulă în lei, dar poate fi și în valută;
  • Declarația vamală de import (DVI) — cu TVA-urile aferente fiecărei facturi (plătite în lei în vamă).

Toate ajung ca linii în același cursor introdc, iar apartenența unei linii la un document e DEJA modelată per rând prin nract / serie_act / id_fact (ointroduceri.prg:1554-1561). Tot per rând există deja și coloanele id_valuta și curs (moștenite din poAct la construire) — dar codul folosește peste tot poAct.Curs/Thisform.oact.Curs în loc de valorile rândului. Deci nu trebuie schimbată structura: (a) faci editabile valuta+cursul, logic per document (aceleași valori pe toate liniile cu același nract); (b) folosești cursul rândului în calcule. La copierea în actactan (→ ACT_TEMPACT), cursul per rând se salvează deja natural.

Modificări propuse (toate în import_nota):

(a) UI — în VFP IDE: adaugă în Grid1 două coloane noi lângă cSumaVal:

  • cValutaControlSource = "introdc.id_valuta", cu un Text1 read-only care afișează codul valutei și DblClick care deschide căutarea pe NOM_VALUTE (există caut_valuta/echivalent în ocautare.prg; dacă nu, un mic SELECT id_valuta, nume_val FROM nom_valute + cauta_alfa). La selectare, propagă pe toate liniile aceluiași document (același nract), inclusiv pe linia pereche de TVA:
    LOCAL lnNract
    lnNract = introdc.nract
    Update introdc Set id_valuta = loCauta.id_valuta Where nract = m.lnNract
    * cursul se cere/ia imediat dupa schimbarea valutei
    
  • cCursControlSource = "introdc.curs", editabil numeric (4 zecimale), doar pe linii cu in_valuta = 1; la Valid, propagă la fel pe liniile cu același nract. Opțional: dacă există funcție de curs BNR pe dată (caută getcurs/curs_valutar în COMUN), propune automat cursul la data documentului respectiv (introdc.dataact), nu la data facturii principale.

Liniile facturii principale rămân needitabile pe aceste coloane (păstrează poAct.id_valuta/poAct.Curs) — doar liniile celorlalte documente (transport, taxe vamale) au voie curs propriu. Liniile de TVA din DVI sunt în lei (in_valuta = 0) și rămân pe mecanismul existent (suma, fără curs propriu).

(b) Conversia — formula generală. Contribuția unei linii la baza în valuta facturii principale:

linia in valuta (in_valuta=1):  baza_val += ROUND(suma_val * introdc.curs / poAct.Curs, gnPval)
linia in lei    (in_valuta=0):  baza_val += ROUND(suma / poAct.Curs, gnPval)      && neschimbat

Formula cu introdc.curs e corectă și pentru liniile în valuta principală (curs identic ⇒ raport 1), deci poate înlocui uniform sumările existente.

Locuri de modificat:

  1. inainte_de_do_termin (ointroduceri.vc2:7699+), sumările de baze (:7792-7806):
    * inainte:  Sum suma_val For ... in_valuta = 1 And participa_valuta To lnBazaVal
    * dupa:     Sum Round(suma_val * curs / poAct.Curs, gnPval) For ... in_valuta = 1 And participa_valuta To lnBazaVal
    
    (idem pentru lnTvaVal; ramurile in_valuta = 0 cu Round(suma/poAct.Curs, gnPval) rămân neschimbate).
  2. Verificarea lei↔valută la editare (ointroduceri.vc2:8214-8221, cSumaVal/cSuma Valid):
    * inainte:  lnSumaDinVal = Round(suma_val * Thisform.oact.Curs, gnPC)
    * dupa:     lnSumaDinVal = Round(suma_val * introdc.curs, gnPC)
    
  3. do_executa (:7560-7598, calculul procentelor stocate în explicatia4/explicatia5) — verifică fiecare loc unde apare poAct.Curs/oact.Curs și înlocuiește cu cursul rândului unde se convertește o sumă a rândului; poAct.Curs rămâne doar unde se convertește totalul în valuta principală.
  4. Copierea în actactan (:7737-7782): rândurile în lei primesc deja Curs=0, id_valuta=0 (:7754) — neschimbat; rândurile în valută își duc acum automat cursul propriu (corect).
  5. Afișaj: lb_curs (:7882-7883) rămâne cursul facturii principale; opțional adaugă în caption „(liniile pot avea curs propriu)".

Ce NU se schimbă: repartizarea pe articole în import_nir (:6276-6316) — lucrează cu procent_lei/procent_val derivate din bazele de mai sus și cu ncurs = poAct.Curs pentru prețul în lei al articolelor; odată ce bazele sunt corecte, restul rămâne valid.

Testare: factură USD + transport EUR + taxă în lei; verifică: NIR-ul valoric (lei și valută), linia „DIFERENTE" (:6329-6364) să rămână nesemnificativă, nota contabilă echilibrată (verificare_note_contabile), jurnalul de cumpărări.


3. Conturi 2xx (nu doar 3xx) la căutarea articolelor

Filtrul e în import_nir (nu import_nota), în două locuri:

(a) do_adauga (butonul „Caută articol", ointroduceri.vc2:5949):

* inainte:
SELECT DISTINCT scd FROM actactan WHERE LEFT(ALLTRIM(scd),1) = '3' AND suma <> 0 INTO CURSOR tconturi
* dupa:
SELECT DISTINCT scd FROM actactan WHERE INLIST(LEFT(ALLTRIM(scd),1),'2','3') AND suma <> 0 INTO CURSOR tconturi

Lista rezultată intră în lcFiltru_init = [inactiv = 0 and cont in (...)] (:5958) transmis la viz_catalog_articole() — nimic altceva de schimbat acolo.

(b) do_modiparam (preluarea din XLS, ointroduceri.vc2:6177): aceeași înlocuire. Atenție la contul implicit '371' pentru articole noi (:6215, :6230) — rămâne valid ca default al Inputbox-ului, utilizatorul poate tasta alt cont; nu necesită modificare, dar verifică fluxul.

Opțional (de discutat cu contabilul): în loc de tot 2xx, restrânge la clasele relevante (ex. exclude 28x amortizări, 29x ajustări): ... WHERE (LEFT(ALLTRIM(scd),1) = '3' OR INLIST(LEFT(ALLTRIM(scd),2),'20','21','22','23','26','27')) AND suma <> 0.


4. Articolele negestionabile 2xx să nu se salveze în RULAJE

Constatări:

  • Câmpul din nomenclator: nom_articole.in_stoc (1 = „Gestionabil"), prezent în cursorul întors de viz_catalog_articole (oproceduri_articole.prg:91-107) — deci disponibil fără query suplimentar.
  • Azi NU există nicio filtrare: tot ce e bifat în catalog intră în cursorul NIR → rul_tempPACK_CONTAFIN.SCRIE_IN_RUL care face INSERT INTO RUL (SELECT ... FROM RUL_TEMP) (PACK_CONTAFIN.pck:1896-2015). Filtrarea client-side e suficientă — pachetul scrie doar ce primește în RUL_TEMP.

Modificare propusă — import_nir.do_adauga, în SCAN FOR bifa = 1 (ointroduceri.vc2:5989):

LOCAL lcSarite
lcSarite = ''

SELECT (lcCrsArt)
SCAN FOR bifa = 1

	SCATTER NAME loArt

	* articolele negestionabile cu cont 2xx nu intra pe NIR/rulaje -
	* valoarea lor ramane doar pe linia de nota contabila (contul 2xx e deja debitat)
	IF NVL(loArt.in_stoc,1) = 0 AND LEFT(ALLTRIM(loArt.cont),1) = '2'
		lcSarite = lcSarite + IIF(EMPTY(lcSarite),'',', ') + ALLTRIM(loArt.denumire)
		LOOP
	ENDIF

	* ... restul SCAN-ului neschimbat ...
ENDSCAN

IF !EMPTY(lcSarite)
	amessagebox("Urmatoarele articole sunt negestionabile (cont 2xx) si NU au fost adaugate pe NIR" + ;
		" (valoarea ramane doar pe nota contabila): " + lcSarite, 0+64, "Atentie")
ENDIF

Și în do_modiparam (XLS): la căutarea articolului după codmat (:6193), adaugă in_stoc în select (select id_articol, denumire, in_stoc from nom_articole ...) și aplică același skip + mesaj înainte de Append Blank în rul_temp (:6246-6249).

Atenții:

  • Dacă vrei regula pentru orice articol negestionabil (nu doar 2xx), scoate condiția pe cont — sesizarea cere explicit doar 2xx, așa că am păstrat-o.
  • Liniile de notă 2xx aferente acestor articole nu trebuie să participe la repartizarea valorii pe articolele rămase: la introducerea notei, debifează participa_valuta (coloana cParticipaValuta) pe acele linii, altfel valoarea lor umflă procent_lei/procent_val și deci prețul de intrare al mărfurilor 3xx. De verificat operațional; se poate automatiza ulterior (warning în do_executa când o linie 2xx are participa_valuta = 1).
  • Plasă de siguranță opțională în import_nir.inainte_de_do_termin, înainte de oscrie_in_fisiere (:6424): verifică rul_temp contra nom_articole.in_stoc = 0 (un SELECT prin goExecutor pe lista de id_articol) și șterge rândurile găsite sau avertizează. Utilă doar dacă apar și alte căi de umplere a grilei.

Ordine recomandată de aplicare și testare

  1. Punctul 3 (2 linii) + Punctul 4 (localizat) — testabile imediat pe un NIR de probă.
  2. Punctul 1 — corectează cele 4 probleme (AD) din implementarea ta; testele de la secțiunea 1.
  3. Punctul 2 — cel mai amplu; întâi calculele (b), apoi UI (a); testele valutare de la secțiunea 2.

După fiecare rundă de editare în IDE: Build + rulare + vcx2txt.ps1 pentru refresh cache, ca să pot verifica codul aplicat. La final: intrare în changelog_roagest.txt (bump versiune, tag :modificare:/:nou:).