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
187 lines
12 KiB
Markdown
187 lines
12 KiB
Markdown
# 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.
|