Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0113YoHU5anRC7gbtgHvE6UY
19 KiB
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 dinactactan, structurăpmenu.prg:281-307, tabelulACT) — o singură perecheid_valuta+curspe act; - liniile de notă = cursorul
introdc(din viewvnote_contabilefiltrat peid_set), fiecare rând moșteneșteid_valuta/curs/dataact/tva_incasaredinpoAct; 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_originaldin același .vcx NU e instanțiat nicăieri — copie de rezervă.
- antetul facturii =
- 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 înintrodc, apartenența pe document = per rând (nract/serie_act/id_fact; rândurile au șiid_valuta/cursproprii, dar codul istoric folosește doarpoAct.Curs). import_nota: grilă peintrodc; transport/taxe = linii de notă suplimentare cu flagurilein_valuta(suma e în valută) șiparticipa_valuta(linia intră în valoarea de intrare a mărfii).do_executacalculează procentele de repartizare (explicatia4/explicatia5);inainte_de_do_terminsumează bazele (liniile în lei se împart lapoAct.Curs) și lanseazăimport_nircuprocent_lei/procent_val/ncurs.import_nir:do_adauga→viz_catalog_articole()(oproceduri_articole.prg:62, câmpin_stoc="Gestionabil") filtrat pe conturilescddin 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 prinpack_preturi.adauga_articol, cont implicit 371).- Salvare:
oscrie_in_fisiere→ACT_TEMP/RUL_TEMP→ pachet OraclePACK_CONTAFIN(SCRIE_IN_ACT,SCRIE_IN_RUL,SCRIE_IN_STOC).SCRIE_IN_RULinserează înRULexactRUL_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 clasanir(NIR obișnuit) — articolul intră normal în grilă, iarinainte_de_do_terminfaceDelete From rul_temp Where in_stoc=0(valoarea rămâne doar pe nota contabilă). Din v2.11.6import_nire aliniat la același model, cu cursor temporarcrsNegestcare readaugă rândurile șterse în grilă dacăoscrie_in_fisiereeșuează.rul_tempare coloanain_stocdin view-ul sursăvrul; lado_adaugase umple prinGATHER NAME loArt, lado_modiparam(XLS) din select penom_articole. - Explicație TVA: dropdown
Grid1.cExplicatieTva.Combo1(RowSourcejtva_coloane2); variantă cu căutare =frm_modific2024.do_modifica_explicatie_tva(omodificari.vcx) →caut_explicatie_tva()(ocautare.prg:1934, viewvjtva_coloane, întoarce DOARid_jtva_coloana, denumire, cota_tva). Capcane:introdc.ptvaține cota brută (21), în registru jurnalproc_tva=(cota+100)/100; comutarea 4427/401 pe linia de TVA = SEEK încJtvaCol4427.
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:
achizitie_import:introdcpornește gol; se păstrează perechea-șablon (bază+TVA, conturi reale dinvnote_contabile) doar ca sursă de clonare.- 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). - 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_keyunic per document: prefixnr_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): gruparerul_temppecont+acont; analiticul articolului merge direct înascdal rândului S (fallbackassign_analiticdoar când nu e furnizat). - Procentele per rând (
explicatia4/explicatia5= „Procent lei/valuta"): scrise desincronizeaza()după spargere (nu dedo_executa), numitor = baza documentului principal (This.nbazaprincipala_lei/_val, dinrecalculeaza). Principala=100.00; celelalte = pondere lei/valută (convertit laoact.Cursdacă 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 cunr_doc/doc_keynoi (S/D se regenerează la sincronizare).Calculatemută pointerul la EOF → poziția sursă se salvează/restaurează înainte deScatter. - Rândul TOTAL sub grila notelor:
TxtFtvaLei/TxtFtvaVal(fără TVA) lângăText2/Text3(cu TVA), setate înactualizeaza_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=1se setează și pe rândul T (Valid-urilecSuma/cSumaVal);do_executanu-l rescrie,sparge_documentnu-l șterge/duplică pe aceeași cotă (ptva). Nimic nu reseteazămod_manualpe rând existent. nOldValunic: o singură proprietate pentru valoarea la GotFocus — Valid/LostFocus ies devreme la valoare nemodificată (cSuma,cSumaVal,cCotaTva,cPretFactura).do_calculeaza_diferenterestaurează pozițiarul_tempde la intrare;do_refacerepoziționează explicit.- Totaluri articole:
_grdfooter1(clasa_grdfooterdin_grd_base.vcx) atașat laGridArtla finalulInit; sume pecValoareValutaFactura/cValoareValutaCalculat/cValoareLeiCalculatdinrul_temp, recalculate înactualizeaza_banda+do_calculeaza_diferente. Totalurile de sub grila notelor rămân dinintrodc; 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_documentregrupează 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_documentaplică aceeași pondere (fără S-uri: împărțire egală 1/N);uneste_documentreunește T-urile sparte când documentul revine la B. Resincronizarea dincScc/cAscc.Validse armează pe gardanOldVal(nu pe comparația cu câmpul — grila poate scrie câmpul înaintea Valid-ului, ex. Enter) + gardălInSynccontra Valid-urilor re-declanșate de regenerare. Test:COMUN\utile\Teste\test_tva_secundare.prg. - Rândul D (diferență 6xx) e clonat prin
Scatter/Gatherdin rândul de bază, decirecalc_diferentetrebuie să golească explicit tot ce ține de TVA:id_jtva_coloana,ptva,cu_tvașitaxcode(cod SAF-T). Codul de taxă stă doar pe liniile de bază, TVA și discount.oscrie_in_fisiere.prg:260scrieNULLînACTpentrutaxcodegol.
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 ado_adauga_factura) șivaluta_proprie(1 când acel rând T are valută/curs diferite de ale documentului, setat la creare sau din grilă). Nu s-a refolositmod_manual— e supraîncărcat semantic (sparge_documentface match pe el pentru alte scopuri, ar da duplicate/omisiuni de rânduri T). - Rând „protejat" =
Nvl(valuta_proprie,0)=1(helperThisform.rand_protejat()). Ocolit decopiaza_valoare(câmpurile valutare nu se propagă pe el, câmpurile de identitate nu se propagă pe orice rândrand_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ânevaluta_proprie=0și urmează factura la propagări. - Spargerea T-ului protejat (
sparge_tva_protejat, dinsparge_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ă. VariantaloRandT = loBazadă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 (ponderebază_cotă × cotă, rest de rotunjire pe |suma| maximă), păstrând prinGatheridentitatea 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șidoc_key/nr_doccu 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 peInlist(tip_rand,'B','S')/'T'— un singur loc nu filtra petip_rand(suma valută din footer,do_executa), a primitAnd tip_rand<>'G'. Punct unic de adevăr:disc_document(doc_key, tlValuta). Se scade DIRECT îndo_executa/recalc_tva_document(acelașidoc_key), dar se REPARTIZEAZĂ prin factor însparge_document(bazele vin dincrsGrupCota, nu dinsuma_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_valrămân BRUTE (sparge_documentrepartizează toată baza pe S,verifica_sincronizarecomparăSuma(S)cusuma_doc).do_adauga_facturaciteștetoDlg.disc_baza_leisub gardăType(...)<>'U'— apelabil programatic cutoDlgminimal (import e-Factura/XLS). Limită cunoscută: rândurile create ulterior desparge_document/recalc_tva_documentse 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ă prinldviinvalutadirty(calinvalutadirtyla bază). Scoasă → ascunde valuta/cursul/TVA-ul valutar al DVI, forțeazănDviCurs=1,nDviTvaVal=0. Ramura dinrecalc_tvatrebuie să includă bifa, nu doardvi_valuta_efectiva()(întoarce valuta facturii, nu știe de bifă — fără ea se scriedvi_tva_valnenul cudvi_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_sare parametrultnCota, scrieptva+id_jtva_coloana/explicatie_tvapringaseste_jtva_cota(familia = coloana bazei). Rândul G se sparge la fel, proporțional pe bazele pe cotă (sparge_discount, dinsparge_documentdupă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. nOldVale NUMERIC: coloanele caracter din grilă (cScc/cAscc) folosesccOldVal. O singură proprietate partajată dădea „Operator/operand type mismatch" încSuma.Text1.Validcând Valid se re-declanșa fără GotFocus.cSuma/cSumaVal.Validau primit și gardalInSync.- Două reprezentări ale „RON": rândul din picker (
caut_valuta/vnom_valute) e REAL cuid_valutanenul (moneda_nationala=1); sentinela „fără valută" dinintrodc/ACTeid_valuta=0,nume_val=''. Comparațiile trebuie pe valuta EFECTIVĂ (Empty(nume) Or Upper(nume)=='RON'), nu pe id brut — altfeldvi_valuta_propriear 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, înACT/RUL.nract. Rândul D scapă (își moștenește antetul o singură dată, la creare).copiaza_valoarenu face refresh de grilă — coruperea e invizibilă până la salvare. Propagarea de partener are flux propriu (Grid1.cPartC.Text1.Valid), aceeași gardă.ControlSourcecalificat la runtime:This.Parent.ControlSourceîntoarceintrodc.nract, nunract. OriceInlist/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.ControlSourcecu 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 mereuThis.<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(clasabut_sterge) șterge perechea bază+TVA curentă, de pe oricare linie (lnBaza = Recno() - Iif(Mod(Recno(),2)=0,1,0), apoiDelete Next 2), prininainte_de_do_stergesublactiv4(.T.înInit).- Ș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_jcfă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, finalsparge_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 refolositmod_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ă); lasparge_tva_protejatră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_documentlasă încmesajsync„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înachizitie_import:Createobject('IMPORT_nota', tlIntern)primește parametrul, dar codul din clasă citeșteThisform.lIntern— o referință latlInterndintr-o metodă a formularului prinde variabila PRIVATE a apelantului doar cât timp acesta e pe stivă (îninainte_de_do_terminera deja ieșit).- Fixture-uri de test: cele 32 de teste din
COMUN\utile\Teste\achizitie_importîși creeazăintrodcprinCREATE CURSOR— orice coloană nouă folosită în SQL-ul din clasă trebuie adăugată și acolo, altfel „SQL: Column '' is not found".