Files
roafacturare/docs/plan_06_s4_proiectare.md
Marius Mutu d9f5ca4226 docs: planurile, proiectarile si rapoartele de lucru intra in versionare
Folderul docs\ era pana acum in afara oricarui control de versiuni - nici git,
nici SVN - desi contine planurile pe puncte, proiectarile si rapoartele de
cercetare pe care se sprijina modificarile din cod. O stergere acolo era
definitiva.

Fisierele intermediare (handoff-uri intre sesiuni, diff-uri deja aplicate) au
fost sterse inainte, nu versionate: ce era durabil in ele a intrat in antetele
fisierelor de test la care se refereau.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN8snvkk94KuhWwoXUUey3
2026-08-11 22:17:17 +03:00

44 KiB

Proiectare S4/S4b — pagina de articole factura in frm_modific2024

Cercetare + proiectare pentru plan_06_editare_factura.md, story S4 (pagina noua de articole) si S4b (helpere/totaluri/verificari). NU e implementare — propunere supusa aprobarii lui Marius.

Stare de plecare (verificata, nu presupusa): S1-S3 sunt deja implementate si testate (docs\progres.md, sectiunea "#6, runda 1"). Actiunea frm_facturi.do_editare_factura (COMUN\clase\ofacturare_comun.vc2:3727-3869) si afisjurcom.do_modifica (COMUN\clase\comun.vc2:2222-2563) sunt cele doua puncte de intrare reale, ambele deschid deja frm_modific2024 pe cursoarele tact/trul/trul_obinv. Tot ce descrie documentul de fata se adauga peste acest cod existent, fara sa-l modifice decat unde e explicit spus (D, E).

Deciziile lui Marius pe intrebarile din F (08.08.2026) — au prioritate fata de recomandarile din text

  • F.1 — respinsa recomandarea "strict facturi". Pagina se aplica pe orice rand din VANZARI, deci si pe avize. Tot ce spune A.3 despre restrangerea la lista de TIP de facturi se reciteste in cheia asta.
  • F.2 — premisa din C.1 e gresita. Contul nu e 4111 peste tot: avizele folosesc 418. In plus, nota contine randuri de discount si poate contine note adaugate manual de utilizator. Deci filtrul fix SCD='4111' nu e o regula, ci o potrivire pe un singur document. Regula corecta se stabileste in docs\cercetare\rec_suma_act.md (cercetare in curs), si C.1 se rescrie dupa ea. Pana atunci, nu implementa indicatorul pe formula din C.1.
  • F.3 — raspuns de la Marius: in RUL pot fi si linii cu diferente de pret, cand pretul de vanzare din factura difera de cel din stoc — doar pentru marfa tinuta la pret de vanzare. Deci suma bruta din RUL nu e comparabila prin constructie cu totalul documentului (pe cod=1140888: 10 randuri RUL pentru 4 linii, 4476.28 fata de 1924.59). O bara de totaluri care le compara direct ar semnala "desincronizat" permanent pe orice document cu marfa la pret de vanzare. Subsetul comparabil se stabileste in docs\cercetare\rec_suma_act.md.
  • F.4 — varianta A (bara de totaluri sub grid, permanent vizibila).
  • F.5 — alegere globala a directiei de sincronizare, pe document, nu per linie.
  • Separat, din decizia 18: liniile din seturi se trateaza ca orice alta linie.
  • Transfer si custodie (23, 25, 30, 41, 27, 42, 47): pagina apare, dar fara bara de totaluri — nu bara goala cu mesaj, ci fara ea. Pe aceste tipuri nu exista suma comparabila (transferurile merg pe cont de stoc, custodia nu genereaza randuri ACT per articol).
  • Tipul 51 (ROAACNPRO) foloseste 4111 — deci divergenta gasita in cercetare are alta cauza decat contul; prima suspiciune e filtrarea pe cod fara an+luna.
  • Comparatia stricta e imposibila prin constructie: ACT nu marcheaza originea randului, deci un rand adaugat manual nu se distinge de unul generat. Bara de totaluri arata cifrele si diferenta, fara verdict automat de eroare.
  • Garda pe id_set se scoate de tot (decizia 24) — premisa ei a picat.
  • Documente mixte (decizia 25): suma RUL se corecteaza cu valoarea liniilor nestocate din VANZARI_DETALII, marcata in bara ca ajustata.
  • RUL are formula comparabila (nu mai e doar informativ): SUM(CANT*PRETVTVA) + SUM(CANTE*PRETVTVA WHERE ID_TIP_RULAJ <> 3). C.1 se rescrie dupa docs\cercetare\rec_suma_act.md, care are si propunerile concrete de corectie la final.

Ipoteza lui Marius de verificat, cu efect asupra codului deja scris: id_set + 5 la discount ar fi un marcaj tranzitoriu consumat inainte de ACT, nu o valoare persistata — caz in care garda pe id_set din do_editare_factura (runda 3) e construita pe o premisa falsa.

A. Ce se poate face concret pe frm_modific2024

A.1 Structura curenta a pgfArticole si a celor doua pagini de rulaje

Clasa frm_modific2024 incepe la COMUN\clase\omodificari.vc2:6375. Pageframe-ul:

  • Nu are PageCount propriu setat in ADD OBJECT 'pgfArticole'... (:8627-8641) — mosteneste PageCount = 2 din clasa de baza _pageframe (COMUN\clase\_baza.vc2:496). Cele doua pagini sunt configurate doar prin proprietatile PAGE1.Caption/ForeColor/Name si PAGE2.Caption/ForeColor/Name in acelasi bloc ADD OBJECT.
  • PAGE1 contine _grdfooter1 (:8644-8655, footer cu sume pe coloane, csumcolumns=...) si grdRulaje (:8657-..., ColumnCount=63, RecordSource="trul", ReadOnly=.F.).
  • PAGE2 are aceeasi structura pe trul_obinv/grdRulajeObinv (confirmat prin indexul de metode: pgfArticole.PAGE2.grdRulajeObinv.*, omodificari.vc2:15159-15424), nu am recitit blocul ADD OBJECT complet (nu era necesar — tiparul e identic cu PAGE1, doar alt cursor).
  • Init (:13551-13660): primeste Lparameters tnIdSet, tlNotaNoua, toBackupXML, toSet, tlVizualizare; seteaza This.nid_set, lEditare/lVizualizare/lModificare/lVerificare. Zona :13610-13643 ascunde but_nou1/but_sterge1 si face grid1 readonly pe seturi speciale (id_set 25000-25099 sau o lista fixa) — logica veche, fara legatura cu facturile de vanzare (acele id_set-uri sunt pentru note "cu scadere", nu pentru pagina noua).
  • Activate (:12238-12243): la prima activare cheama resize_grid1().
  • resize_grid1() (:13680-13688) si afiseaza_rulaje() (:12245-12281) folosesc deja this.pgfArticole.Visible ca switch — dar e un toggle manual, comandat de utilizator (butonul but_afiseaza_rulaje, colapseaza/expandeaza TOT pageframe-ul ca sa faca loc pentru grid1), nu o decizie "documentul e de tip X". Nu e mecanismul de folosit pentru afisarea conditionata a PAGE3.
  • Show (:13719-13775) e unde se leaga efectiv footerele de sume la grid-uri: this.pgfArticole.page1._grdfooter1.attachtogrid(...) +.calcTotal(), identic pentru PAGE2 (:13748-13752) si unde se ascunde automat tot pageframe-ul cand nu exista deloc rulaje (:13764-13770, If Reccount('trul')=0 And Reccount('trul_obinv')=0 Then This.afiseaza_rulaje()).

A.2 Mecanica adaugarii PAGE3

PageCount nu e o proprietate care se citeste o singura data — poate fi modificata la runtime, si exact acest tipar exista deja in codebase, la un alt pageframe: comun.vc2:10521-10531 (frm_...Init) face THISFORM.pgcod.PageCount = n+1 + .Pages(n+1).Caption = 'Gestiuni' cand conditia e adevarata, altfel PageCount = n (pagina ramane ascunsa, pentru ca nu exista in colectia activa de pagini). Acesta e mecanismul recomandat pentru PAGE3, nu .Visible pe pagina (VFP pageframe Page are Visible, dar codebase-ul nu-l foloseste nicaieri pentru show/hide conditionat — a fost cautat explicit, 0 rezultate Page.*Visible in COMUN\clase).

Propunere concreta:

  • In definitia clasei, ADD OBJECT 'pgfArticole' capata PageCount = 3 explicit (nu mai ramane pe default-ul 2) + PAGE3.Caption = "Articole factura", PAGE3.Name = "PAGE3", plus grid-ul nou pgfArticole.PAGE3.grdArticoleFactura (dupa modelul grdRulaje) si un _grdfooter propriu (pgfArticole.PAGE3._grdfooter1), la fel ca PAGE1/PAGE2.
  • La runtime, in Show (langa blocul :13764-13770, dupa acelasi principiu): daca documentul curent nu e o factura de vanzare (vezi A.3), This.pgfArticole.PageCount = 2 — PAGE3 dispare din colectia activa de pagini, comportament identic cu azi pentru orice alt tip de nota. Cand este factura, PageCount ramane 3 (valoarea din clasa) si se leaga footerul nou: this.pgfArticole.page3._grdfooter1.attachtogrid(...) + .calcTotal(), exact ca la PAGE1/PAGE2.
  • Consecinta directa: pentru orice document care nu e factura (99.9% din utilizarile din ROAGEST/ROACONT), PageCount scade inapoi la 2 la fiecare deschidere — clasa arata byte-cu-byte ca azi, nicio schimbare vizuala sau de comportament. Asta e conditia ceruta explicit in plan (S4: "pagina se afiseaza doar cand documentul curent are rand in vanzari... altfel formularul arata exact ca azi") si raspunde direct la riscul cel mai mare listat in plan.

A.3 Cum decide formularul ca documentul curent are randuri in VANZARI

Comparatia celor trei variante, asa cum a cerut misiunea. Concluzia (varianta 3) nu se schimba fata de versiunea anterioara a acestei sectiuni — se schimba doar filtrul aplicat dupa ea, cf. [DECIS — decizia 19 din progres.md] mai jos:

  1. Test pe id_set — respins. Intervalele facturilor si ale avizelor din COMUN\docs\tipuri_documente_facturare.md se suprapun aproape complet (facturi: 25000-25009, 25042-25048, 25051, 50100 + variantele "cu scadere" +10; avize: 25020-25029, 25040-25041, 25046 + variantele +10 — ambele in acelasi interval brut 25000-25099). Un test de forma Between(tnIdSet,25000,25099) (deja folosit in Init, :13610-13612, dar pentru alt scop) ar prinde si avizele, nu doar facturile. Documentul insusi semnaleaza o coliziune nerezolvata pe 25051 (punctul 3 din capcanele acelui fisier) — nu e o baza solida pentru o decizie care controleaza afisarea/ascunderea unei pagini cu bani.
  2. Flag pasat de apelant — respins ca mecanism principal. afisjurcom.do_modifica (ROACONT/ROAGEST, registrul jurnal) e cod generic pentru orice tip de nota; azi nu stie si nu are motiv sa stie ca documentul curent are randuri in VANZARI (asta a fost motivul pentru care garda eFactura si cea de referinte au trebuit extrase in COMUN\programe\ la S1, nu lasate in frm_facturi). A cere unui al doilea apelant sa afle si sa transmita acest flag ar duplica exact interogarea pe care oricum trebuie sa o facem undeva, cu riscul ca un al treilea apelant viitor sa uite s-o transmita corect.
  3. SELECT in vanzari dupa cod — recomandat. tact (cursorul pe care se leaga deja formularul, incarcat de IncarcaCursoareModificareNota, COMUN\programe\ofacturare_editare.prg:27-140) contine coloana cod din vact_tot (filtrul WHERE ... cod = tnCod de la :53 confirma coloana). tact.cod se poate citi oricare ar fi workarea curenta (referinta calificata pe alias), deci nu conteaza care apelant a deschis formularul — informatia necesara e deja in cursorul pe care oricum formularul il primeste prin contract. Un singur SELECT tip, id_vanzare FROM vanzari WHERE cod = <tact.cod> (rulat o singura data, la deschidere) da simultan raspunsul la "are rand in vanzari?" si, daca da, tip-ul exact — necesar in Runda 4 pentru tratamentul special al transferului/custodiei (decizia 22, mai jos).

Recomandare: detectia se face in interiorul frm_modific2024 (nu in apelanti), intr-o metoda noua apelata din Init sau din Show (inaintea blocului de la A.2), care citeste tact.cod, interogheaza vanzari o singura data, si populeaza trei proprietati noi pe formular: This.lAreArticoleVanzari, This.nIdVanzare si This.nTipVanzare (tip-ul documentului din vanzari). Asta inseamna ca PAGE3 apare automat din ambele puncte de intrare fara nicio modificare in do_editare_factura sau afisjurcom.do_modifica — exact arhitectura ceruta de plan ("extinderea clasei comune, nu cod apelant nou").

Numele lAreArticoleVanzari (nu lEsteFacturaVanzare, cum se numea in versiunea anterioara a acestei sectiuni) e ales deliberat: sub decizia 19 de mai jos flagul devine adevarat si pe avize, transferuri si custodie, nu doar pe facturi — numele vechi ar fi mintit. nTipVanzare se retine separat, tot din acelasi SELECT, pentru ca Runda 4 (decizia 22: transfer/custodie afiseaza pagina, dar fara bara de totaluri) are nevoie sa stie tipul exact, nu doar "are/nu are randuri".

[DECIS — decizia 19 din progres.md] Filtrul de mai sus nu se restrange la lista de TIP de factura — pagina apare pe orice rand din VANZARI, deci si pe avize, transfer intre subunitati, transfer pe lucrare si custodie. Varianta "strict facturi" (recomandarea acestei sectiuni intr-o versiune anterioara, cand decizia inca nu fusese luata) a fost respinsa explicit de Marius. Consecinte pentru restul lucrarii:

  • Garda eFactura/S1 ramane specifica facturilor si nu se extinde — pe avize/transfer/custodie pur si simplu nu se aplica azi, pentru ca acele tipuri de document nu trec pe acolo.
  • Scrierea (E, S5) si bara de totaluri (C.1, C.3) raman gatate separat, dupa nTipVanzare: transfer/custodie (decizia 22) primesc pagina, dar fara bara.
  • frm_modific2024 deschis din afisjurcom.do_modifica (registrul jurnal ROACONT/ROAGEST) capata aceeasi pagina pe orice document cu rand in VANZARI, nu doar pe facturi — de retinut la testarea riscului D, care azi verifica doar cazul "document care nu e deloc in VANZARI".

A.4 Lantul inainte_de_do_termin — unde intra validarile noi

omodificari.vc2:13357-13549. Ce face azi, pe scurt:

  • :13360-13367 reseteaza filtrul pe tact, completeaza id_set gol pe tact/trul/trul_obinv.
  • :13369-13386 verificare generica de completare cont/analitic/partener (verificare_note_contabile('tact',...), oOperatii_comune), sarita pentru seturile speciale (id_set 99998/90024).
  • :13389-13402 (doar gnAn >= 2013): avertisment pe combinatia de conturi 4426-4428/ 4428-4427 fara sa fi folosit optiunea dedicata, apoi This.VerificaAvertizareExigibilizareTVA() (:13909-14083, verificare separata, neatinsa de aceasta propunere).
  • RETURN m.llRet — daca oricare pas a esuat, formularul nu inchide (butonul Termina ramane blocat pana la corectare).

Punctul de agatare pentru validarile noi ale lui S4b: chiar inainte de RETURN m.llRet (:13404), un bloc nou gatat de This.lAreArticoleVanzari (A.3) — de exemplu, garda "nu lasa utilizatorul sa iasa cu buton=1 daca a marcat sincronizarea ca necesara dar n-a confirmat-o explicit" (detaliu in C). Nu inlocuieste nimic din ce exista azi — se adauga dupa validarile generice, cu acelasi tipar (If m.llRet Then ... Endif).

B. Cursorul de articole

B.1 Sursa si momentul incarcarii

Confirmat in docs\cercetare\rec_cale_vanzari_detalii.md: scrierea la editare merge direct in VANZARI_DETALII (fara VANZARI_DETALII_TEMP, care e GTT populata doar la emitere). Simetric, citirea pentru formular trebuie sa vina direct din VANZARI_DETALII, nu din vreo tabela temp.

Propunere: o functie noua in COMUN\programe\ofacturare_editare.prg (alaturi de IncarcaCursoareModificareNota, acelasi stil de cod — goExecutor.oExecute, gestiune de eroare simetrica), de exemplu:

FUNCTION IncarcaArticoleFactura
	LPARAMETERS tnIdVanzare
	* incarca header-ul (1 rand, cursor tvanz) si liniile active (cursor tvd) pentru factura data
	* tvd/tvanz raman deschise READWRITE - apelantul (frm_modific2024) le foloseste si le inchide
ENDFUNC

Apelata din interiorul frm_modific2024 (Init/Show, dupa ce A.3 a stabilit This.lAreArticoleVanzari = .T. si This.nIdVanzare), nu din apelanti — acelasi motiv ca la A.3: zero cod nou in do_editare_factura/afisjurcom.do_modifica pentru partea de citire.

tvanz (1 rand, header): id_vanzare, cod, discount (singurul camp editabil din antet, decizia 17 din progres.md) + campurile needitabile utile ca referinta vizuala (total_fara_tva, total_tva, total_cu_tva — valorile vechi, denormalizate, afisate readonly langa totalurile live din S4b, nu suprascrise decat de S5 la salvare).

tvd (liniile), coloane din VANZARI_DETALII (lista completa in rec_cale_vanzari_detalii.md, sectiunea 2.2) — subsetul relevant editarii: id_vanzare_det, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, discount_unitar, id_gestiune, cont, id_valuta, id_jtva_coloana, serie, explicatie, taxcode, lot, sters. Filtrul de incarcare: WHERE id_vanzare = :tnIdVanzare AND sters = 0 (liniile deja sterse nu se mai arata — simetric cu tact, care si el filtreaza sters=0 la incarcare).

B.2 Marcaje de stare, fara concept nou fata de restul clasei

Clasa foloseste deja doua idiomuri simple pentru starea liniilor din tact, ambele reutilizabile ca atare pentru tvd, fara sa inventam un al treilea:

  • Sters = flag pe randul existent, nu stergere fizica din cursor — exact cum tact/trul reprezinta deja STERS ca o coloana obisnuita (rescrisa la scriere, nu DELETEd din cursor). tvd.STERS (coloana deja prezenta in VANZARI_DETALII) se flipuieste in cursor la actiunea "sterge linie"; la scriere (D), un rand cu STERS=1 care avea STERS=0 la incarcare devine un UPDATE ... SET STERS=1.
  • Adaugat = id_vanzare_det = 0 — acelasi sentinel folosit deja in do_adauga (omodificari.vc2:12685, loadd.id_act = 0 pentru randurile noi din tact, inainte de Append Blank/Gather). La scriere, orice rand din tvd cu id_vanzare_det = 0 e un INSERT (PK-ul real vine automat din SEQ_VANZARI_DETALII, confirmat in rec_cale_vanzari_detalii.md sectiunea 1.3/3.2 — nu trebuie generat in VFP).
  • Modificat — singurul marcaj cu adevarat nou necesar, pentru ca "a fost atins" nu se poate deduce din id_vanzare_det/STERS. Propunere: o coloana logica _modificat (prefix _, convenabil pentru un camp de lucru care nu exista in tabela reala — verifica totusi ca VFP nu interpreteaza gresit numele; alternativ lModificat), setata .T. din handler-ele Valid/ InteractiveChange ale coloanelor editabile din grid — acelasi tipar folosit deja de clasa pe trul (pgfArticole.PAGE1.grdRulaje.cCant.Text1.Valid, omodificari.vc2:14906-14911, si restul handler-elor Valid/When/InteractiveChange din acelasi grid). La scriere, un rand cu id_vanzare_det > 0 si _modificat = .T. e un UPDATE; fara flag, randul nu se atinge (evita UPDATE-uri inutile pe linii doar rasfoite).

Acest model evita complet o alternativa mai grea (snapshot + diff intre cursorul original si cel curent) care ar fi introdus un concept nou, fara sa aduca vreun beneficiu fata de flag-urile deja folosite in clasa pentru tact.

C. Helperele si verificarile

C.1 Ce inseamna "suma comparabila" — regula pe tip de document, verificata pe date reale

Corectie fata de versiunea anterioara a acestei sectiuni (premisa ei — filtru fix SCD='4111' — a fost respinsa explicit de Marius, F.2 mai jos). Cercetarea completa, cu toate interogarile pe cele trei scheme si sursele exacte pe cod, e in docs\cercetare\rec_suma_act.md; ce urmeaza e concluzia ei. Doua completari ulterioare, verificate separat pe MARIUSM_AUTO (08.08.2026), sunt in docs\cercetare\rec_cele_41_facturi.md: derivarea an/luna si cauza reala a divergentei pe cod=1138989. Nu se reimplementeaza nicio formula fiscala in VFP — recomandarea ramane cea de dinainte: "suma din VANZARI_DETALII" se ia direct din calculeaza_total_fara_tva_fact/ calculeaza_total_tva_fact (Oracle, aceleasi functii care vor rula la salvarea din S5), niciodata recalculata in VFP.

Nu exista un filtru fix de cont pentru "suma din ACT". Contul de debit al liniei depinde de tipul documentului (PACK_FACTURARE.pck, contabilizeaza_articol decide contul la :7390-7415):

Grup de TIP Cont debit (linie) Comparabil cu TOTAL_CU_TVA?
Facturi (1,2,3,5,7,8,9,10,43,44,45,46,48,49,51,52 fara rata) 4111 (empiric stabil pe 3 scheme) DA
Factura din aviz (tip 4) 4111, discount direct pe el cu semn negativ DA
Vanzare pe rate/contract (2,6,52 cu id_rata<>0) din NOTE_CONTABILE, legat de CONTRACTE DA
Avize catre clienti debitori (28, 29) 461 (hardcodat) DA
Restul avizelor (21,22,24,26) 418 (hardcodat) DA
Transfer intre subunitati (23,25,30,41) cont de STOC, nu de client NU
Transfer pe lucrare (27) cont de STOC NU
Custodie (42,47) — (descarca_gestiune nu scrie rand ACT per articol) NU
ROAACNPRO (51) 4111, confirmat stabil de Marius cont OK, dar comparatie nesigura — vezi mai jos

Filtrul pe ACT cere cod + an + luna, niciodata cod singur. Dovada: cod=1140632 are un rand de achizitie straina (OCR furnizor) in an=2026,luna=1 si nota de vanzare reala in an=2026,luna=2, cu acelasi cod reutilizat intre module. IncarcaCursoareModificareNota( tnCod, tnAn, tnLuna, ...) (COMUN\programe\ofacturare_editare.prg:27-140) filtreaza deja corect — orice interogare noua din S4b trebuie sa foloseasca acelasi tipar de filtru complet, nu doar cod.

an/luna nu se deriva din VANZARI.DATA_ACT — trebuie luate din contextul notei deja incarcate, niciodata recalculate din antet. Pe 703 documente VANZARI (MARIUSM_AUTO, docs\cercetare\rec_cele_41_facturi.md): 542 au nota in luna din DATA_ACT, 78 (11%) au nota intr-o alta luna, 83 n-au deloc randuri ACT pe cod. Exemplu: cod=1138989 are VANZARI.DATA_ACT = 01-JAN-19, dar cele 12 randuri ACT sunt in an=2019, luna=3 (dataact=31-MAR-19) — un filtru cod + an(data_act) + luna(data_act) ar intoarce zero randuri. Pe datele de test cazurile sunt concentrate pe tip=51 (ROAACNPRO, DATA_ACT sablon 01-JAN-19), deci nu e dovedit tipar general de productie — dar consecinta de implementare e reala: an/luna se iau din cursorul tact/actactan deja incarcat de IncarcaCursoareModificareNota (apelata cu an/luna explicite), niciodata recalculate din VANZARI.DATA_ACT. Pentru cele 78 de documente cu luna divergenta, do_editare_factura raspunde azi "Nu exista nota contabila pentru aceasta factura" si refuza editarea — comportament sigur, dar de consemnat ca limitare cunoscuta.

Discountul de document intra NET (debit minus credit), nu ca SUM(SCD=cont) simplu. scrie_discount (PACK_FACTURARE.pck:12859-13057) scrie discountul pe sensul OPUS liniei de vanzare: pe facturi normale (tip<=20 sau in (44,45,46,43,48,49,51,52)) discountul e SCD='667'/SCC='4111', adica pe credit fata de contul de client — un SUM(SUMA) WHERE SCD='4111' simplu il ignora complet. Suma corecta e soldul net, aceeasi formula pe care aplicatia insasi o foloseste la auto-verificarea de la emitere (verifica_total_document, PACK_FACTURARE.pck:16073-16145):

SUM(CASE WHEN SCD = :cont THEN SUMA
         WHEN SCC = :cont AND SCD NOT IN ('5311','5314','5121','5125','5126') THEN -SUMA
         ELSE 0 END)

(pe factura din aviz, tip=4, discountul e deja direct pe 4111 cu semn negativ, deci formula neta da acelasi rezultat ca un SUM simplu pe acel tip — nu strica nimic sa se aplice uniform pe toate tipurile comparabile din tabel.)

RUL are acum o formula comparabila (nu mai ramane doar informativ):

SUM(CANT*PRETVTVA) + SUM(CANTE*PRETVTVA WHERE ID_TIP_RULAJ <> 3)

Randurile "in plus" observate initial (10 randuri RUL pentru 4 linii, suma bruta 4476.28 in loc de 1924.59 pe cod=1140888) vin din perechile de diferenta de pret, generate de PACK_FACTURARE.descarca_gestiune pentru marfa/produse tinute la pret de vanzare (V_CONT IN ('371','357') AND V_TIP_GESTIUNE=6, :9361-9540; produse/ambalaje similar, :9641-9818) cand pretul de vanzare inregistrat in stoc difera de cel facturat efectiv (V_PRETV_ORIG <> V_PRETV). Fiecare diferenta scrie o pereche marcata ID_TIP_RULAJ=3 (:7745): un rand cu CANTE>0 la pretul VECHI din stoc (de exclus din suma) si unul cu CANT>0 la pretul REAL facturat (de inclus). Formula de mai sus, aplicata pe cod=1140888, da exact 1924.59 = TOTAL_CU_TVA.

Documente mixte (decizia 25): liniile nestocate nu au deloc rand RUL. descarca_gestiune sare complet peste articolele cu NOM_ARTICOLE.IN_STOC=0 (:7783-7789) — nu scrie nimic in RUL pentru ele (confirmat pe productie, cod=1397106: linia de "SERVICII TRANSPORT" lipseste integral din RUL). Pe orice document cu linii stocate SI nestocate, suma RUL de mai sus subestimeaza sistematic documentul cu exact valoarea liniilor nestocate. Corectie: suma RUL se aduna cu suma liniilor IN_STOC=0 din VANZARI_DETALII, iar bara de totaluri marcheaza explicit ca cifra e ajustata (nu doar RUL brut).

Tipuri fara suma comparabila — nu se afiseaza bara, nu se da verdict (decizia 22): transfer intre subunitati (23, 25, 30, 41) si transfer pe lucrare (27) merg pe cont de stoc, nu de client; custodia (42, 47) nu scrie niciun rand ACT per articol. Pe niciunul din aceste tipuri nu exista ce compara — tratament explicit ("pagina apare, bara nu"), nu o eroare de raportat. Pe tip=50 (marcat "in lucru" in pachet), acelasi tratament, prin analogie.

ROAACNPRO (tip 51): cauza divergentei pe cod=1138989 gasita — nota e DUBLATA, nu e cazul deciziei 9. Cele 12 randuri ACT (toate in aceeasi an/luna, deci nu e capcana de filtrare de mai sus; toate pe 4111, zero 411) sunt doua blocuri de cate 6, iar al doilea e exact 2x primul, rand cu rand (docs\cercetare\rec_cele_41_facturi.md): blocul A insumeaza exact TOTAL_CU_TVA = 13895.45 (0.01 lei rotunjire), blocul B e dublul lui, iar suma totala peste toate cele 12 randuri e 41686.35 = 3x totalul — exact raportul semnalat initial. Regula pentru suma din ACT se inchide si pe acest document: nu e o exceptie a regulii, e o nota postata de doua ori — o anomalie reala de date pe care un indicator corect trebuie s-o semnaleze, nu s-o ascunda. Nu e printre cele 41 de facturi de la decizia 9 (#8/S9) — acelea sunt toate STERS=1, iar cod=1138989 are STERS=0 si o linie activa; presupunerea anterioara ca ar fi "aceeasi familie" nu se sustine. Ramane deschis, separat: cele 12 randuri ACT explica antetul VANZARI, dar linia unica din VANZARI_DETALII (2468.21) nu reconciliaza cu niciuna din cele doua cifre — asta chiar ramane neexplicat, si e un argument in plus pentru caracterul informativ (nu verdict automat) al comparatiei ACT vs VANZARI_DETALII de mai jos.

Comparatia ramane informativa, niciodata verdict automat de eroare. ACT nu are nicio coloana care sa marcheze originea randului (omodificari.vc2:12653-12701, do_adauga) — un rand adaugat manual de utilizator (posibil, decizia F.1: pagina apare pe orice document din VANZARI) e indistinctibil, dupa salvare, de unul generat automat la emitere. Indicatorul din S4b ramane deci informativ pe subset bine definit, niciodata sursa unica de adevar pentru un verdict de eroare.

Gradul de incredere: regula de mai sus (cont pe tip + filtru cod+an+luna + sold net) e verificata pe 360 de documente, 12 tipuri de document, 3 scheme (MARIUSM_AUTO date de test, ROMFAST@ROA_ROMFAST client real, VENDING productie) — ~97.5% potrivire exacta (docs\cercetare\rec_suma_act.md, sectiunea "Concluzie C"). cod=1138989 (ROAACNPRO) nu mai e un caz neexplicat — cauza e gasita (nota dublata, de mai sus) — dar un indicator automat tot ar semnala divergenta pe el, corect, pentru ca e o anomalie reala de date. Restul cazurilor raman deschise, nu ascunse: transfer/custodie (necomparabile prin design, de mai sus), 5 documente ROMFAST fara nicio nota ACT scrisa, si un document VENDING in valuta (cod=1165566, tip=9) cu o diferenta de 5454.12 lei neexplorata — niciunul din aceste cazuri nu infirma regula, dar niciunul nu trebuie prezentat ca "inchis" fara nuanta de mai sus.

C.2 Ce inseamna "live" vs "la ultima salvare"

Cele trei surse nu pot fi comparate coerent "in timp real" cat timp utilizatorul tasteaza intr-o celula din grid, pentru ca ACT/RUL/VANZARI_DETALII reala raman la valoarea de dinaintea editarii curente pana la Salveaza. Propunere clara, ca sa nu induca fals sentiment de precizie:

  • Subtotal pe linie, in grid (cantitate x pret, cu/fara TVA dupa flag) — calcul simplu, live, in VFP, la fiecare InteractiveChange/Valid (acelasi tipar ca calculeaza_valori_rul, omodificari.vc2:12519-12588, aplicat pe trul). Nu implica formula fiscala complexa (fara discount de document, fara rotunjiri de agregare), deci riscul de divergenta e neglijabil si local, vizibil imediat de utilizator.
  • Cele trei totaluri de control (RUL / VANZARI_DETALII / ACT) raman "la ultima stare persistata" — se recalculeaza (interogare Oracle) la deschiderea paginii si dupa fiecare Salveaza reusit, nu la fiecare tasta. Rolul lor real (asa cum reiese din problema descrisa de Marius) e sa arate ca un document editat anterior a ramas nesincronizat, nu sa dea un preview in timp real al editarii curente — editarea curenta oricum nu poate fi "corecta" pana nu trece prin acelasi calcul Oracle care va rula la salvare.

C.3 Indicatorul de stare a sincronizarii

Un label vizibil pe PAGE3 (langa footerul de totaluri), 3 stari: verde/OK (toate sumele aplicabile coincid, in limita rotunjirii de 2 zecimale — pe tipurile fara suma comparabila, transfer/custodie, bara nici nu apare, cf. C.1), galben/atentie (VANZARI_DETALII si ACT coincid, dar cifra din RUL e ajustata pentru linii nestocate sau documentul e de un tip cu comparatie nesigura, ex. ROAACNPRO — cf. C.1), rosu/desincronizat (VANZARI_DETALII si ACT NU coincid — semnalul real ca ceva nu s-a propagat corect, singurul caz in care indicatorul trebuie sa opreasca vizual atentia utilizatorului). Recalculat la deschidere si dupa fiecare salvare reusita (C.2).

C.4 Actiunea explicita de sincronizare

Cerinta lui Marius: directia (rulaj->articol sau articol->rulaj) o alege utilizatorul, niciodata implicit. Propunere de flux (schita UI in F, intrebarea de UX):

  1. Buton "Verifica sincronizarea" (sau automat la deschidere, doar afisare) — ruleaza C.1/C.3, populeaza un cursor de diferente tvd_diff (id_articol, cantitate_rul, cantitate_vd, pret_rul, pret_vd, ...) doar pentru randurile unde difera.
  2. Daca exista diferente, buton "Propune sincronizare" deschide un dialog/grid cu liniile afectate si valorile vechi/noi, pe ambele directii posibile (utilizatorul alege per-sesiune care parte e sursa — RUL sau VANZARI_DETALII —, nu per-linie individual, ca sa evite o combinatie inconsistenta).
  3. Confirmarea aplica modificarile doar in cursorul in memorie (tvd sau trul, dupa directie) — nu scrie nimic in Oracle pana la Termina/Salveaza. Consistent cu principiul "nimic nu se aplica silentios si nimic nu se declanseaza automat la do_termin" din plan.
  4. Refuzul propunerii nu modifica nimic — utilizatorul poate corecta manual, linie cu linie, in oricare din cele doua griduri.

C.5 Linii adaugate/sterse fara corespondent in RUL

O linie noua in tvd (id_vanzare_det=0) nu are, prin definitie, niciun rand RUL corespunzator — nu exista "vechi" de comparat. Propunere: astfel de linii sunt automat excluse din comparatia C.1/C.3 (nu pot fi "desincronizate", pentru ca n-au fost niciodata sincronizate) si marcate separat in UI ("linie noua, fara rulaj — se creeaza la salvare" / "linie stearsa"). Simetric pentru liniile sterse (STERS=1 in tvd): nu mai intra in suma "curenta" din C.2, dar raman vizibile (tacuate/ strikethrough) pana la salvare, ca utilizatorul sa vada ce a marcat pentru stergere inainte sa confirme.

C.6 Ce NU se poate verifica automat

  • Corelatia RUL <-> VANZARI_DETALII linie-cu-linie (nu agregat) — ramane nesigura: formula din C.1 e verificata ca sumă pe tot documentul, nu mapeaza un rand RUL anume pe o linie anume din VANZARI_DETALII. Suma agregata (C.1) are acum formula verificata; ce nu se poate face e legatura 1-la-1 intre randuri.
  • Corectitudinea contabila a notei dupa editare (echilibrul debit=credit, alegerea corecta a conturilor) — ramane acoperita de validarile generice deja existente in inainte_de_do_termin (A.4), care nu se ating.
  • Impactul asupra eFactura/SAFT dupa editare — in afara scopului #6 (garda S1 blocheaza deja editarea facturilor trimise in eFactura).

D. Riscul asupra registrului jurnal

omodificari.vc2 e in COMUN si serveste si afisjurcom/registrul jurnal ROACONT/ROAGEST. Ce poate regresa, concret, si cum se limiteaza:

  1. PageCount schimbat global pe clasa — daca noul PageCount=3 din definitia clasei nu e readus la 2 la runtime pentru non-facturi (A.2), PAGE3 ar aparea (goala sau cu date gresite) pe orice nota din ROACONT/ROAGEST. Mitigare: testul din A.2 ruleaza necontional in Show, inaintea oricarei afisari, si defaultul din clasa e explicit "1 pas de siguranta" (daca testul crapa/nu ruleaza, PageCount ramane 3 din clasa — deci testul TREBUIE sa aiba un Catch/else care forteaza PageCount=2, nu invers). De verificat explicit la implementare: comportamentul pe eroare al noii interogari Oracle (A.3) trebuie sa fie "ascunde PAGE3", nu "las-o vizibila".
  2. Interogarea noua din A.3 (SELECT ... FROM vanzari WHERE cod=...) ruleaza la FIECARE deschidere a formularului, inclusiv pentru note care n-au nicio legatura cu facturarea — cost suplimentar mic (un SELECT indexat pe cod), dar trebuie sa fie garantat sa nu blocheze deschiderea pe eroare de retea/Oracle. Mitigare: acelasi tipar defensiv ca IncarcaCursoareModificareNota (ofacturare_editare.prg:58-61) — pe eroare, se comporta ca "nu e factura" (ascunde PAGE3), nu propaga eroarea in sus si nu blocheaza formularul.
  3. Cursoarele noi (tvd/tvanz) nu trebuie sa interfereze cu tact/trul/trul_obinv — nume de alias distincte, verificate ca nu exista deja in cod (tvd/tvanz cautate, 0 rezultate azi). Grid-ul nou trebuie sa aiba ControlSource calificat complet pe fiecare coloana (COMUN\docs\capcana_grid_controlsource.md) — capcana confirmata activa exact in acest scenariu (3+ griduri pe acelasi formular: grid1/grdRulaje/grdRulajeObinv/noul grid).
  4. Scrierea (nu doar afisarea): partea cea mai sensibila. Scrierea noua in VANZARI_DETALII (E, S5) trebuie sa fie strict gatata de This.lAreArticoleVanzari, apelata din apelanti (do_editare_factura/afisjurcom.do_modifica) DOAR dupa ce pasul existent finalizeaza_modificare_nota a reusit deja — deci pe orice nota fara rand in VANZARI, pasul nou nu se executa niciodata (flag-ul e .F.), cod identic cu azi.
  5. gridextra1.setup() (:13729) — salveaza/restaureaza preferinte per-grid, cheie SYS(1272, grid). Grid-ul nou (pgfArticole.PAGE3.grdArticoleFactura) e complet nou, deci nu are preferinte salvate de niciun utilizator — capcana din capcana_grid_preferinte_utilizator.md (coloana noua intr-un grid EXISTENT ajunge la coada) nu se aplica la infiintare, doar daca se adauga o coloana ulterior. Neverificat: daca gridextra1 inregistreaza automat orice grid nou de pe formular sau necesita inregistrare explicita — de confirmat direct in gridextras.vc2 la implementare, nu presupus aici.

Cum se testeaza D: un caz de test obligatoriu (deja in S8 din plan) e "document care nu e factura, deschis din registrul jurnal — pagina de articole nu apare si comportamentul e identic cu azi". Recomand completarea lui cu: (a) o rulare explicita cu Oracle temporar indisponibil pe interogarea din A.3 (verifica gracious degradation, punctul 2 de mai sus), (b) o comparatie byte-cu-byte a XML-ului salvat de salveazaxml (:13690-13717) inainte/dupa modificare, pe un document non-factura, ca sa confirme ca noul cod n-a atins deloc acel drum.

E. Impartirea in runde de implementare

Ordonat pe risc, cu rezultat testabil la finalul fiecarei runde. S1-S3 (deja facute) raman runda 0.

Runda 1 — PAGE3 doar afisare, fara scriere (depinde doar de VFP + Oracle read-only, nu de S5)

  • A.2 (PageCount/Caption/grid nou) + A.3 (detectie factura, in frm_modific2024) + B (incarcare tvanz/tvd, read-only).
  • Grid needitabil (ReadOnly=.T. pe toate coloanele), fara buton de salvare separat pe pagina.
  • Gata cand: PAGE3 apare pe orice document cu rand in VANZARI (facturi, avize, transfer, custodie — decizia 19), din ambele puncte de intrare, arata liniile corecte; pe orice document fara rand in VANZARI dispare complet (testul de risc D).
  • Nu depinde de S5. Se poate livra si testa independent.

Runda 2 — editare in memorie, fara scriere in Oracle

  • Grid editabil (B.2, marcaje _modificat/sters/id_vanzare_det=0), dialogul per-linie (reutilizarea frm_articol_factura, vezi nota tehnica de mai jos), adaugare/stergere linie in cursor, discount de antet editabil in tvanz.
  • Subtotalul live pe linie (C.2, calcul simplu VFP).
  • Gata cand: utilizatorul poate adauga/sterge/modifica linii in grid, vede subtotaluri live; Renunta lasa totul neschimbat; Termina nu scrie inca nimic in VANZARI_DETALII (doar nota contabila, ca azi).
  • Nu depinde de S5 — poate rula complet pe VFP, testabil headless fara risc de scriere Oracle.

Runda 3 — S5 (Oracle) + scrierea reala

  • Procedura recalculeaza_totaluri_vanzari (Oracle, rec_s5_oracle_vanzari.md sectiunea B) + procedurile de UPDATE/INSERT/soft-DELETE pe VANZARI_DETALII (rec_cale_vanzari_detalii.md sectiunea 4, Varianta B).
  • Scrierea efectiva din VFP: apel nou, simetric in ambii apelanti, gatat de Omodif.lAreArticoleVanzari, dupa finalizeaza_modificare_nota reusit (D.4).
  • Gata cand: dupa Termina, VANZARI_DETALII/VANZARI reflecta editarea; S7 (rotunjire la reeditare) verificat pe acest flux.
  • Depinde de Runda 2 (cursorul editat trebuie sa existe) si de S5/S6 din planul general.

Runda 4 — S4b complet (helpere/verificari)

  • C.1-C.6: totalurile de control (interogare directa a functiilor Oracle existente, nu formula VFP), indicatorul de stare, actiunea explicita de sincronizare.
  • Gata cand: criteriile din plan_06 S4b (indicator vizibil, propunere enumerata, refuzul nu modifica nimic).
  • Depinde de Runda 3 — comparatia cu ACT/VANZARI_DETALII n-are sens pana nu exista scriere reala de comparat.

Nota tehnica pentru Runda 2: dialogul per-linie recomandat e reutilizarea frm_articol_factura (COMUN\clase\ofacturare.vc2:2315-..., deschis azi din frm_facturare_articole.do_modifica, ofacturare.vc2:13746-13844, model documentat si in plan_06_editare_factura.md "Ce preda #7 catre S6"). Dialogul citeste PRIVATE poArticol (setat de apelant inainte de Createobject) si Lparameters tnCantitate, tlAscunde — fara plafon (decizia 15), tnCantitate se transmite cu o valoare santinela mare (ex. 999999999), nu se recalculeaza din stoc. poArticol asteptat de dialog are un set bogat de proprietati calculate (valftva, vval*, etc. — vezi lista completa la ofacturare.vc2:13835-13840), diferite de coloanele brute din VANZARI_DETALII; adaptorul nou trebuie sa populeze campurile de baza (pret_achizitie, cantitate, id_articol, cont, id_gestiune, proc_tvav, pretftva/pretctva dupa flag, discount_unitar, id_valuta, ...) din tvd, apoi sa cheme aceeasi functie VFP calculeaza_totaluri(poArticol) (apelata deja la :13875 pentru randuri noi) ca sa deriveze restul — nu se reinventeaza formula, se refoloseste exact ca la compunere. Ramura do_alege_stoc (redeschiderea dialogului de gestiuni, folosita azi doar la compunere) nu se foloseste in #6 — decizia "fara verificare de stoc" (15) inseamna ca toate liniile, gestionabile sau nu, trec prin acelasi dialog simplu.

F. Intrebari pentru Marius

  1. [DECIS — decizia 19 din progres.md, vezi A.3] Nu strict facturi: pagina apare pe orice rand din VANZARI, deci si pe avize, transfer si custodie. Varianta "strict facturi" recomandata initial in A.3 a fost respinsa explicit de Marius.

  2. [REZOLVAT — vezi C.1] Contul nu e fix 4111: regula e pe tip de document (facturi 4111, avize 418, avize catre clienti debitori 461), verificata pe 360 de documente / 12 tipuri / 3 scheme (docs\cercetare\rec_suma_act.md). Raman deschise, fara sa infirme regula: 5 documente ROMFAST fara nicio nota ACT si un document VENDING in valuta (cod=1165566) cu diferenta neexplorata.

  3. [REZOLVAT — vezi C.1] Regula gasita: perechile cant/cante marcate ID_TIP_RULAJ=3 sunt randuri de diferenta de pret (PACK_FACTURARE.descarca_gestiune), de exclus randul cu pretul vechi din stoc. Formula SUM(CANT*PRETVTVA) + SUM(CANTE*PRETVTVA WHERE ID_TIP_RULAJ<>3) reproduce exact totalul pe documente cu toate liniile stocate; pe documente mixte se corecteaza cu liniile nestocate (C.1).

  4. UX-ul concret al zonei de totaluri si al indicatorului — trei variante, cu recomandare:

    Varianta A (recomandata) — bara de totaluri sub grid-ul PAGE3, indicator ca punct colorat + text:

    +-----------------------------------------------------------------------+
    | [Articole factura]  Discount document: [____] %                      |
    | +---------------------------------------------------------------+     |
    | | Articol | Cant | Pret | Cu TVA | ...                          |     |
    | | ...                                                           |     |
    | +---------------------------------------------------------------+     |
    | Total ACT (contabil):         1924.59 lei                             |
    | Total VANZARI_DETALII:        1924.59 lei                             |
    | Total RUL (formula C.1):      1924.59 lei          [*] Sincronizat    |
    |                                     [Verifica sincronizare] [Propune] |
    +-----------------------------------------------------------------------+
    

    Exemplu cod=1140888 (C.1): dupa formula corecta, cele trei sume coincid — nu mai e un caz care sa ilustreze o divergenta. Pentru un document mixt (linii stocate + nestocate, decizia 25), randul RUL se marcheaza explicit ca ajustat, de exemplu:

    | Total RUL (ajustat, +linii nestocate): 1170.00 lei                    |
    

    Simplu, aliniat cu _grdfooter1 deja existent pe PAGE1/PAGE2 (acelasi loc, acelasi stil vizual).

    Varianta B — indicator langa caption-ul paginii (PAGE3.Caption = "Articole factura ⚠" sau cu iconita), totalurile doar la cerere (buton "Arata totaluri de control" care deschide un dialog separat). Mai compact, dar ascunde informatia pana la un click — risc sa nu fie observat.

    Varianta C — culoare de fond pe intreaga pagina (rosu deschis) cand desincronizat, fara text explicit pana la deschiderea dialogului de sincronizare. Cel mai putin verbose, dar ambiguu (utilizatorul nu stie CE e desincronizat fara sa deschida dialogul).

    Recomand A — respecta convenția UX (conventie_ux_formulare.md: informatia de control trebuie vizibila, nu ascunsa dupa un click) si reutilizeaza tiparul deja vizual familiar din PAGE1/PAGE2 (footer de sume sub grid).

  5. Dialogul de propunere de sincronizare (C.4) — schita:

    +---------------------------------------------------------+
    | Propunere sincronizare (sursa: Articole factura -> Rulaj)|
    | +-------------------------------------------------------+|
    | | Articol      | Rulaj (vechi) | Articol (nou) |         ||
    | | Piesa X      | cant=2 pret=100 | cant=3 pret=110 |     ||
    | | Piesa Y      | -- (fara rulaj) | cant=1 pret=50  |     ||
    | +-------------------------------------------------------+|
    |         [Alege directia: Articol->Rulaj | Rulaj->Articol]|
    |                          [Aplica in memorie]  [Renunta]  |
    +---------------------------------------------------------+
    

    De confirmat daca alegerea directiei e un singur radio-button global (recomandat, C.4 punctul 2) sau daca Marius vrea control per-linie (mai flexibil, mult mai complex de implementat si de explicat utilizatorului — nerecomandat pentru complexitatea/beneficiul).

Ce nu am putut verifica

  • Daca gridextra1.setup() inregistreaza automat grid-uri noi de pe formular (D.5).
  • Comportamentul _grdfooter/attachtogrid pe un grid gol (0 randuri) la prima deschidere a PAGE3 — nu a fost testat, doar citit codul PAGE1/PAGE2 ca precedent.

(Cele trei puncte legate de formula ACT/RUL care erau listate aici — divergenta 903.53 vs 1924.59, regula perechilor cant/cante, stabilitatea contului 4111 — s-au rezolvat prin cercetarea din docs\cercetare\rec_suma_act.md si sunt acum in C.1 / F.2 / F.3.)