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
12 KiB
S5 — helper VFP ScrieArticoleFacturaEditate (COMUN\programe\ofacturare_editare.prg)
Implementare, nu doar cercetare. Singurul fisier atins: COMUN\programe\ofacturare_editare.prg
(.prg sursa directa, fara binar, fara write-back). Diff: docs\diff_s5_helper_scriere.patch.
1. Ce s-a facut
CreeazaCursorArticoleGol(deci siCreeazaCursorTvdGol): adaugateid_vanzare_set I NULLsipret_achizitie N(14,4) NULL, imediat dupasters, inainte dedenumire— aceeasi pozitie ca in view-ulVVANZARI_ARTICOLEextins deff_2026_08_09_02_COMUN_VVANZARI_ARTICOLE.sql(verificat pe disc, coloanele intra la coada listei proprii tabelei, inainte de join-uri).IncarcaArticoleFactura: nicio modificare de cod — foloseste dejaselect v.*, na.in_stoc from vvanzari_articole v ...(:299), deci cele doua coloane noi ale view-ului ajung automat intvdprinv.*in momentul in care view-ul e aplicat in Oracle. Extinderea cursorului gol (mai sus) e suficienta pentru catvdsa poarte ambele coloane si pe ramura de eroare/fallback.- A doua definitie a cursorului,
omodificari.vc2:14571(ramura ROACONT/ROAGEST,CREATE CURSORduplicat literal) NU a fost atinsa — confirmarea ceruta explicit. Trebuie tinuta sincron manual de agentul care lucreaza pe.vc2, altfeltvdare structuri diferite dupa aplicatie. - Functie noua
ScrieArticoleFacturaEditate(tnIdVanzare, tcAliasArticole, tcAliasVanzare)— implicit'tvd'/'tvanz'. Patru pasi, in ordinea impusa de decizia 38 (marcheaza tot, invie ce ramane, insereaza, recalculeaza), faraCOMMIT/ROLLBACK, fara inchidere de cursoare, salveaza/restaureaza workarea siRecno()pe alias.
2. Recalculul de totaluri (pasul 4) — LAMURIT de team-lead, acum in helper
Livrarea initiala nu includea apelul la recalculeaza_totaluri_vanzari in helper, motivat de
rec_s5_cale_scriere_vfp.md 3.3 ("apelul trebuie facut din VFP, ca pas separat, dupa helper") si de
secventa din decizia 38 care arata doua apeluri distincte la nivelul apelantului. Team-lead a
corectat: acea schita era la nivel de apelant, nu contractul final; apelul intra in helper tocmai ca
sa ramana un singur punct de scriere (altfel cele doua puncte de intrare, ofacturare_comun.vc2 si
comun.vc2, trebuie amandoua sa-si aminteasca sa-l cheme, cu riscul de a uita unul).
Implementat acum: pasul 4, ultimul, dupa INSERT-uri, gardat de IF m.llSucces:
begin pack_facturare.recalculeaza_totaluri_vanzari(<tnIdVanzare>, <discount sau NULL>); end;
- discountul se citeste din
<tcAliasVanzare>.discount(primul rand, garantat unic de garda de no-op) imediat la inceputul functiei, inainte de pasii 1-3 (pozitia in cursorul de articole nu intra in conflict, sunt cursoare separate). - daca
discounte.NULL.in cursor, se trimite literalNULLin apelul PL/SQL (pastreaza discountul curent din baza) — nu0, care l-ar sterge. Daca are valoare, se trimite prinAlltrim(Str(...,18,4)). - niciun
UPDATE VANZARI SET DISCOUNTseparat — scrierea discountului ramane exclusiv in responsabilitatea procedurii Oracle, primit ca parametru, exact cum cere decizia 41. - acelasi tratament de eroare ca la ceilalti pasi:
goExecutor.oExecuta,.F.la esec, fara mesaj propriu, faraCOMMIT/ROLLBACK.
3. PRET_ACHIZITIE pe linia noua — INCHIS: se citeste din cursor, nu se calculeaza in helper
Premisa initiala a deciziei 40 ("se preia din nomenclator / stoc") s-a dovedit gresita si a fost
revizuita de Marius, dupa constatarea de mai jos. Decizia finala: valoarea o introduce
utilizatorul, intr-o coloana noua editabila in gridul de pe PAGE3 (omodificari.vc2, doar pe
liniile noi — alt agent, nu se atinge aici). Helperul doar citeste pret_achizitie din cursorul
de articole (Nvl(pret_achizitie,0)), fara niciun lookup Oracle propriu.
Constatarea care a schimbat decizia (SELECT read-only in MARIUSM_AUTO, pastrata aici ca sa nu
se redescopere):
NOM_ARTICOLEnu are nicio coloana de pret de achizitie curent — singura coloana cu "PRET" sau "ACHIZ" in nume ePRETACHCTVA(NUMBER,DEFAULT 0), care e un flag ("pretul de achizitie contine TVA?"), nu o valoare — confirmat prin utilizarea ei inpack_facturare.scrie_fact_aviz_custodie(docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:10116, 10168-10169, 10209), unde e citita alaturi deV_PRET_ACHIZITIE(parametru separat), niciodata ca sursa a lui.- La emitere,
pret_achizitienu vine dintr-o coloana statica — vine fie din cursorul de stoc VFP (ofacturare_stoc.prg:221,a.Pret As pret_achizitiedinrul_temp, adica pretul lotului de stoc ales), fie din cursorul de articole selectate la alegerea din gestiune (ofacturare.vc2:13822/13849,poArticol.pret_achizitie = Pretdincrsartselectate), fie e re-derivat in Oracle inadauga_articol_factura(docs\ff_...PACK_FACTURARE.sql:5020, 5093-5134) — numai pe ramura restaurant (ntip=45) dinCRM_POLITICI_PRET_ART.PRETFTVA+ procent de adaos; pe toate celelalte ramuri ramane valoarea primita ca parametru din VFP. Niciuna din aceste cai exista in fluxul de editare: liniile noi la editare se adauga prinCreeazaPoArticolNouTvd(ofacturare_editare.prg:356-457), care fixeazaid_gestiune = -1000sigestionabil = 0necondtionat — adica editarea NU deschide dialogul de alegere din stoc, deci n-are un lot/gestiune reala de unde sa citeasca pretul. STOC(verificat structura,all_tab_columns): tabela periodicaAN, LUNA, ID_ARTICOL, ID_GESTIUNE, PRET, PRETV, CANTS, ...— un rand pe lot/perioada/gestiune, aceeasi granularitate caRUL. Nu exista un "pret curent" unic fara o formula de agregare (ce perioada, ce gestiuni).
Ce e implementat acum, in ScrieArticoleFacturaEditate: la pasul INSERT al liniei noi,
PRET_ACHIZITIE = Nvl(<tcAliasArticole>.pret_achizitie, 0), citit direct din cursor (coloana
adaugata la punctul 1) — fara STOC, fara NOM_ARTICOLE, fara functie ajutatoare. La UPDATE-ul
liniilor pastrate, coloana ramane omisa din SET, exact ca in specificatia initiala (pe liniile
existente nu e editabila, valoarea persistata nu se atinge).
4. Coloanele NOT NULL din VANZARI_DETALII — verificat, punctul NEVERIFICAT din cercetare e inchis
SELECT column_name, nullable, data_default FROM all_tab_columns WHERE owner='MARIUSM_AUTO' AND table_name='VANZARI_DETALII' AND nullable='N' (read-only, MARIUSM_AUTO/ROA_CENTRAL):
| Coloana | DATA_DEFAULT |
Tratament in INSERT |
|---|---|---|
ID_VANZARE_DET |
— | nu se scrie, vine din trigger TRG_VANZARI_DET_BEFOINS |
ID_VANZARE |
— | scris explicit (tnIdVanzare) |
PRET |
— | scris explicit (din tvd.pret) |
VALIDAT |
0 |
nescris, ramane DEFAULT 0 |
STERS |
0 |
nescris, ramane DEFAULT 0 (linie noua = activa) |
DIFERENTA |
0 |
nescris, ramane DEFAULT 0 |
CUSTODIE |
0 |
nescris, ramane DEFAULT 0 |
DESCARCAT |
0 |
nescris, ramane DEFAULT 0 |
Toate cele cinci NOT NULL fara valoare din trigger au DEFAULT 0 la nivel de coloana — nu e
nevoie sa fie enumerate in INSERT. Singurele NOT NULL care trebuie scrise explicit sunt
ID_VANZARE si PRET, deja acoperite de contract.
5. SQL generat, cate un exemplu per categorie
Pasul 1 — marcheaza tot sters (tnIdVanzare = 12345, gnIdUtil = 7):
update vanzari_detalii set sters = 1, id_utils = 7, dataoras = sysdate where id_vanzare = 12345 and sters = 0
Pasul 2 — linie pastrata (id_vanzare_det = 98765, cantitate = 2.5, pret = 10.5,
pret_cu_tva = 0):
update vanzari_detalii set sters = 0, cantitate = 2.500, pret = 10.5000, pret_cu_tva = 0, id_utils = 7, dataoras = sysdate where id_vanzare_det = 98765
Pasul 3 — linie noua (id_articol = 111, cantitate = 1, pret = 20, pret_cu_tva = 0,
proc_tvav = 1.19, discount_unitar = 0, id_gestiune = -1000, cont gol -> NULL, id_valuta
.NULL. -> NULL, id_jtva_coloana = 3, serie/lot goale -> NULL, explicatie = "test 'x'",
taxcode .NULL. -> NULL, pret_achizitie tastat de utilizator in grid = 15.2300):
insert into vanzari_detalii (id_vanzare, id_articol, cantitate, pret, pret_cu_tva, proc_tvav, discount_unitar, id_gestiune, cont, id_valuta, id_jtva_coloana, serie, explicatie, taxcode, lot, pret_achizitie, id_utils, dataoras) values (12345,111,1.000,20.0000,0,1.1900,0.0000,-1000,NULL,NULL,3,NULL,'test ''x''',NULL,NULL,15.2300,7,sysdate)
(OracleSpecialCharacters a dublat apostroful din explicatie.)
Pasul 4 — recalculul totalurilor (tnIdVanzare = 12345, tvanz.discount = 5 — cazul cu valoare):
begin pack_facturare.recalculeaza_totaluri_vanzari(12345,5.0000); end;
Cazul tvanz.discount .NULL. (pastreaza discountul curent din baza):
begin pack_facturare.recalculeaza_totaluri_vanzari(12345,NULL); end;
6. Ce a ramas netestat, si de ce
- Nimic rulat — nu era in scope ("NU rulezi teste care scriu in baza"). Verificarea de mai sus
(coloane
NOT NULL, tipuri, existenta view-ului) a fost facuta prinSELECT-uri read-only inMARIUSM_AUTO, nu prin executarea functiei. - Textul SQL generat pentru cei 4 pasi (sectiunea 5) e verificat manual, pe exemple, nu prin rularea
functiei cu un
goExecutormock. - Restul e netestabil headless din motivele deja consemnate in
rec_s5_cale_scriere_vfp.mdsectiunea 7 (ordonarea fata deactualizeaza_vanzaricere Oracle real; grid-ul de pe PAGE3 care va scriepret_achizitiein cursor nu exista inca — depinde de agentul peomodificari.vc2).
7. Rezumat pentru revizuire
Ambele puncte semnalate initial ca neconfirmate au fost lamurite de team-lead/Marius:
- Recalculul de totaluri — intra in helper, ca pasul 4 (sectiunea 2).
PRET_ACHIZITIE— nu se mai calculeaza in helper; se citeste din cursor, unde va fi scrisa de utilizator prin coloana noua din grid (sectiunea 3).
Toata functionalitatea (structura cursorului, cei 4 pasi de scriere, coloanele NOT NULL) e
implementata conform contractului final, fara ambiguitate ramasa in acest fisier.
8. Corectii din revizuirea team-lead
1. Garda de no-op extinsa cu Reccount(tcAliasArticole) = 0 — schimbare de fond fata de cercetare.
rec_s5_cale_scriere_vfp.md sectiunea 6 spunea explicit ca un cursor de articole gol cu
tnIdVanzare > 0 nu e no-op ("s-au sters toate liniile"), plecand de la premisa ca un cursor gol
apare doar prin stergerea tuturor liniilor de catre utilizator. Team-lead a aratat, cu dovada din cod,
ca premisa e incompleta: IncarcaArticoleFactura (ofacturare_editare.prg:305-309) intoarce .T.
cu cursor gol si la esec Oracle:
IF lnSucces < 0
AMESSAGEBOX(goExecutor.cEroare,0+16,"Eroare")
CreeazaCursorArticoleGol(m.lcAlias)
RETURN .T.
ENDIF
Helperul nu poate distinge "utilizatorul a sters tot" de "incarcarea a picat" — ambele ajung cu
tvd gol si .T. de la apelul de incarcare. Fara garda, al doilea caz ar duce, prin pasul 1
(marcheaza tot sters, fara nimic de inviat la pasul 2), la golirea tacuta a facturii la un simplu esec
de retea/Oracle la deschiderea formularului. Corectat: Reccount(m.lcAliasArt) = 0 s-a adaugat la
garda initiala de no-op — cursorul gol nu mai scrie nimic. Cazul legitim (utilizatorul chiar sterge
toate liniile) e oricum oprit mai devreme de validarea din inainte_de_do_termin (alt agent,
omodificari.vc2), inainte ca helperul sa fie apelat.
2. UPDATE-ul liniilor pastrate (pasul 2) ancorat si pe id_vanzare. WHERE id_vanzare_det = ...
nu avea garda pe document. Adaugat and id_vanzare = <tnIdVanzare> — cost zero, inchide posibilitatea
teoretica de scriere intr-un alt document daca aliasul ar ajunge vreodata sa contina o linie straina
de tnIdVanzare.
Nimic altceva schimbat: ordinea pasilor, NULL pe discount, omiterea PRET_ACHIZITIE din SET-ul
de UPDATE, OracleSpecialCharacters, restaurarea Recno()/workarea raman ca in runda anterioara.