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

19 KiB
Raw Permalink Blame History

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_adaugaviz_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_fisiereACT_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: FO21BFOB, FO21TFOT, 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 are marfă la <cotă>%, dar nu există explicație TVA de <cotă>% în seria ''" ș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 '' is not found".