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
This commit is contained in:
186
docs/cercetare/rec_s5_helper_scriere.md
Normal file
186
docs/cercetare/rec_s5_helper_scriere.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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 si `CreeazaCursorTvdGol`): adaugate `id_vanzare_set I NULL` si
|
||||
`pret_achizitie N(14,4) NULL`, imediat dupa `sters`, inainte de `denumire` — aceeasi pozitie ca in
|
||||
view-ul `VVANZARI_ARTICOLE` extins de `ff_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 deja `select v.*, na.in_stoc from
|
||||
vvanzari_articole v ...` (`:299`), deci cele doua coloane noi ale view-ului ajung automat in `tvd`
|
||||
prin `v.*` in momentul in care view-ul e aplicat in Oracle. Extinderea cursorului gol (mai sus) e
|
||||
suficienta pentru ca `tvd` sa poarte ambele coloane si pe ramura de eroare/fallback.
|
||||
- **A doua definitie a cursorului, `omodificari.vc2:14571` (ramura ROACONT/ROAGEST, `CREATE CURSOR`
|
||||
duplicat literal) NU a fost atinsa** — confirmarea ceruta explicit. Trebuie tinuta sincron manual de
|
||||
agentul care lucreaza pe `.vc2`, altfel `tvd` are 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), fara `COMMIT`/`ROLLBACK`, fara inchidere de cursoare,
|
||||
salveaza/restaureaza workarea si `Recno()` 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 `discount` e `.NULL.` in cursor, se trimite literal `NULL` in apelul PL/SQL (pastreaza
|
||||
discountul curent din baza) — **nu** `0`, care l-ar sterge. Daca are valoare, se trimite prin
|
||||
`Alltrim(Str(...,18,4))`.
|
||||
- niciun `UPDATE VANZARI SET DISCOUNT` separat — 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, fara `COMMIT`/`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_ARTICOLE` **nu are nicio coloana de pret de achizitie curent** — singura coloana cu "PRET" sau
|
||||
"ACHIZ" in nume e `PRETACHCTVA` (`NUMBER`, `DEFAULT 0`), care e un **flag** ("pretul de achizitie
|
||||
contine TVA?"), nu o valoare — confirmat prin utilizarea ei in
|
||||
`pack_facturare.scrie_fact_aviz_custodie` (`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:10116,
|
||||
10168-10169, 10209`), unde e citita alaturi de `V_PRET_ACHIZITIE` (parametru separat), niciodata ca
|
||||
sursa a lui.
|
||||
- La emitere, `pret_achizitie` **nu vine dintr-o coloana statica** — vine fie din cursorul de stoc VFP
|
||||
(`ofacturare_stoc.prg:221`, `a.Pret As pret_achizitie` din `rul_temp`, adica pretul lotului de stoc
|
||||
ales), fie din cursorul de articole selectate la alegerea din gestiune
|
||||
(`ofacturare.vc2:13822/13849`, `poArticol.pret_achizitie = Pret` din `crsartselectate`), fie e
|
||||
re-derivat in Oracle in `adauga_articol_factura` (`docs\ff_...PACK_FACTURARE.sql:5020,
|
||||
5093-5134`) — numai pe ramura restaurant (`ntip=45`) din `CRM_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 prin
|
||||
`CreeazaPoArticolNouTvd` (`ofacturare_editare.prg:356-457`), care fixeaza
|
||||
**`id_gestiune = -1000` si `gestionabil = 0`** necondtionat — 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 periodica `AN, LUNA, ID_ARTICOL,
|
||||
ID_GESTIUNE, PRET, PRETV, CANTS, ...` — un rand pe lot/perioada/gestiune, aceeasi granularitate ca
|
||||
`RUL`. **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`):
|
||||
```sql
|
||||
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`):
|
||||
```sql
|
||||
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`):
|
||||
```sql
|
||||
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):
|
||||
```sql
|
||||
begin pack_facturare.recalculeaza_totaluri_vanzari(12345,5.0000); end;
|
||||
```
|
||||
Cazul `tvanz.discount` `.NULL.` (pastreaza discountul curent din baza):
|
||||
```sql
|
||||
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 prin `SELECT`-uri read-only in
|
||||
`MARIUSM_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 `goExecutor` mock.
|
||||
- Restul e netestabil headless din motivele deja consemnate in `rec_s5_cale_scriere_vfp.md` sectiunea
|
||||
7 (ordonarea fata de `actualizeaza_vanzari` cere Oracle real; grid-ul de pe PAGE3 care va scrie
|
||||
`pret_achizitie` in cursor nu exista inca — depinde de agentul pe `omodificari.vc2`).
|
||||
|
||||
## 7. Rezumat pentru revizuire
|
||||
|
||||
Ambele puncte semnalate initial ca neconfirmate au fost lamurite de team-lead/Marius:
|
||||
1. **Recalculul de totaluri** — intra in helper, ca pasul 4 (sectiunea 2).
|
||||
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.
|
||||
Reference in New Issue
Block a user