Files
roafacturare/docs/propunere_runda4_note_sincronizare_tva.md
Marius Mutu ca3c5d7eea docs: runda 6 - cercetari, propuneri si conventia mediului Oracle
Cercetarile si propunerile rundelor 4-6 pe editarea facturii emise. Starea
rundei 6 si ce s-a stabilit intra in progres.md.

Nou: docs/conventii_mediu_oracle.md - pe dev/test se lucreaza numai cu
ROA_CENTRAL si schemele CONTAFIN_ORACLE si MARIUSM_AUTO; schema ACN nu se
foloseste. Scripturile de pachet sunt necalificate, deci schema tinta o
decide sirul de conectare, nu fisierul.

roafacturare.pj2 regenerat de git_sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013gYCNE26G1G7UoGu2ju7aS
2026-08-20 16:35:03 +03:00

287 lines
14 KiB
Markdown

# Runda 4 - totalul notelor, dialogul de sincronizare, explicatia TVA, codul de TVA
Diagnostic pe semnalarile din proba pe factura din aviz (document fara rulaje).
Fiecare punct: constatarea, dovada (fisier:linie), propunerea.
## Stare: aplicat in text si scris in binar, necomis
Decizii luate: la explicatia TVA - **varianta B** (dialogul `caut_explicatie_tva`, cu
corelarea codului SAF-T si filtrarea pe cota liniei); la punctul 2 - **fara** avertisment
suplimentar la salvare.
| ce | unde | write-back |
|---|---|---|
| 1. Total note se reface la orice recalculare a notei | `COMUN\clase\omodificari.vc2` | facut, `.vcx` la zi |
| 3. capete de coloana dinamice + labelul de explicatii eliminat | `COMUN\clase\omodificari.vc2` | facut, `.vcx` la zi |
| 4. codul de TVA nu mai vine ca Memo | `COMUN\programe\ofacturare_editare.prg` | n/a (`.prg`) |
| 5. explicatia TVA pe linia de articol + corelare taxcode | ambele fisiere | facut, `.vcx` la zi |
| 6. salvarea liniilor existente pastreaza toate campurile editabile | `COMUN\programe\ofacturare_editare.prg` | n/a (`.prg`) |
Diff-uri pentru review: `docs\diff_runda4_omodificari.patch`,
`docs\diff_runda4_ofacturare_editare.patch`. **Necomis** - nici git, nici SVN.
Verificat dupa write-back: textul reconvertit din `.vcx` e identic cu `.vc2` (0 diferente),
zero caractere corupte.
---
## 1. "Total note" nu se actualizeaza cand modific valoarea notei
**Nu e din cauza rulajelor.** Bara de totaluri se recalculeaza doar din evenimente de pe
partea de **articole**, niciodata din grila de note.
`ActualizeazaBaraTotaluri` (`COMUN\clase\omodificari.vc2:12959`) calculeaza `nTotalLiniiRon`
/ `nTotalNetRon` si apeleaza `ActualizeazaVerdictActRul` (`:13001`), care pune
`This.nTotalActRon` - adica exact valoarea afisata in "Total note"
(`txtTotalActArt.ControlSource = "thisform.nTotalActRon"`, `:12878-12880`).
Apelurile ei in productie sunt doar acestea:
| linie | context |
|---|---|
| `omodificari.vc2:13488` | `calculeaza_valori_articol` (editare in grila de articole) |
| `omodificari.vc2:14920` | `Show` (o data, la deschidere) |
| `omodificari.vc2:16604` | `pgfArticole.PAGE3.Activate` (la intrarea pe pagina Articole) |
| `omodificari.vc2:16698` | `txtDiscountArt.Valid` |
| `omodificari.vc2:16946` | `frm_sincronizare_articole.inainte_de_do_termin` |
| `ofacturare_editare.prg:1023` | editorul de articole din nota |
Editarea sumei pe randul de nota trece prin `Grid1.Column9.Text1.Valid`
(`omodificari.vc2:16044`), care apeleaza `Thisform.CalculeazaTotal()` -
`calculeazatotal` (`:13458`) recalculeaza numai `This.nSuma` (totalul setului de note) si
face `txtSuma.Refresh()`. Bara de jos ramane cu valoarea de la ultimul eveniment de articol.
Bara se reimprospateaza indirect daca iesi si reintri pe pagina Articole (`PAGE3.Activate`) -
de aceea uneori pare ca "s-a actualizat singura".
### Propunere 1 - o linie
La finalul lui `calculeazatotal` (`omodificari.vc2:13458-13475`), dupa
`Select (m.lcSelect)`:
```foxpro
This.ActualizeazaBaraTotaluri()
```
`ActualizeazaBaraTotaluri` are deja garda proprie la inceput
(`IF !This.lAreArticoleVanzari OR !Used('tvd') OR !Used('tvanz') OR Reccount('tvanz') = 0`),
deci pe notele fara articole nu face nimic. `calculeazatotal` e apelata din 8 locuri
(inclusiv `Show`, `:14878`), toate momente in care bara oricum trebuie sa fie corecta.
Alternativa mai ingusta (doar `Grid1.Column9.Text1.Valid`) acopera doar suma, nu si
stergerea/adaugarea de randuri de nota - nu o recomand.
---
## 2. Nu a aparut mesajul de verificare la salvare
**Aici da, e din cauza rulajelor** - si e comportament voit.
`SemnaturaDivergenteSincronizare` (`omodificari.vc2:14805`) iese devreme cu semnatura goala
cand nu exista randuri active in `trul`:
```foxpro
*!* fara rulaje nu exista cu ce compara: propunerea ar marca toate articolele ca "Semnalare"
SELECT trul
COUNT FOR Nvl(sters,0) <> 1 TO lnRanduriRul
IF m.lnRanduriRul = 0
... RETURN m.lcSemnatura && ''
```
iar declansatorul de la salvare (`:14449-14458`) porneste dialogul numai daca semnatura e
nevida si difera de cea de la deschidere. Deci: document fara rulaje -> niciun dialog.
Acelasi lucru il spune si verdictul din bara: *"nu se aplica (documentul nu are rulaje)"*
(`:13089`).
**Important, ca sa nu ramana o asteptare gresita:** mecanismul de sincronizare compara
**rulaje vs articole**, niciodata **note vs articole**. Chiar daca documentul ar fi avut
rulaje, modificarea sumei de pe randul de nota nu ar fi deschis dialogul - divergenta
note/articole e semnalata exclusiv informativ, prin verdictul din bara
(`ActualizeazaVerdictActRul`, `:13001`), si acolo, la 305.00 vs 306.00, ar fi trebuit sa
scrie "divergent" imediat dupa editare. Nu a scris-o din cauza punctului 1.
**Nicio schimbare de comportament** aici, decis: dialogul de sincronizare n-are ce aplica
fara rulaje, iar avertismentul simplu la salvare nu se face. Punctul 1, odata reparat, aduce
singurul semnal care lipsea (verdictul devine "divergent" pe loc).
---
## 3. Dialogul de sincronizare: coloane clare, fara labelul de explicatii
Starea de acum (`omodificari.vc2:16703` - `frm_sincronizare_articole`):
- capete de coloana fixe: `Cantitate veche` / `Cantitate noua` / `Pret vechi` / `Pret nou`
(`:16838-16867`);
- `lblAvertisment` (`:16871-16882`) explica ce inseamna: *"Vechi = valorile care se
salveaza acum; Nou = valorile din sursa aleasa mai sus..."*.
Semantica reala, verificata in `ConstruiestePropunereSincronizare`
(`ofacturare_editare.prg:707-710`): `vechi = tinta` (ce se suprascrie),
`nou = sursa` (de unde se preia). Directia se alege din `optDirectie` (`:16888`):
`RUL_SURSA` (rulajul e sursa) sau `ARTICOLE_SURSA`.
### Propunere 3 - capete dinamice pe directie + eliminarea labelului
In `ConstruiesteEnumerare` (`:16913`), care ruleaza deja la deschidere si la fiecare
schimbare de directie, se seteaza captiunile:
| coloana | `RUL_SURSA` | `ARTICOLE_SURSA` |
|---|---|---|
| 2 (`cCantVecheProp`) | `Cantitate in factura (se inlocuieste)` | `Cantitate in rulaj (se inlocuieste)` |
| 3 (`cCantNouaProp`) | `Cantitate in rulaj (se preia)` | `Cantitate in factura (se preia)` |
| 4 (`cPretVechiProp`) | `Pret in factura (se inlocuieste)` | `Pret in rulaj (se inlocuieste)` |
| 5 (`cPretNouProp`) | `Pret in rulaj (se preia)` | `Pret in factura (se preia)` |
Se sterge `lblAvertisment`; singura informatie din el care nu intra in capete este
*"daca renuntati, salvarea continua neschimbata"* - aceea exista deja ca ToolTip pe
`But_renunt1` (`:16752`, "Inchide fara sa schimbe nimic - salvarea continua"). Propun sa o
mut in ToolTipText-ul gridului sau sa o las doar pe buton; spune tu care variantă.
Ajustari de layout necesare: `HeaderHeight` 22 -> 32 (capetele au `WordWrap = .T.`, doua
randuri), latimile coloanelor 2-5 de la 88 la ~108, si gridul se poate inalta cu ~36 px
in locul labelului eliminat.
**Atentie la write-back:** `frm_sincronizare_articole` e clasa in `omodificari.vc2`, deci
modificarea e pe text + `txt2vcx.ps1`, nu in IDE.
---
## 4. Alegerea codului de TVA arata "Memo"
Confirmat, si cauza e exact cea spusa.
`ArticoleNotaEditor.CautaCodTva` (`COMUN\programe\ofacturare_editare.prg:1094-1100`):
```foxpro
lcSelect = [select taxname, procent_taxa, taxcode from vsaft_taxtable]
RETURN cauta_alfa(m.lcSelect, [1=2], [], [taxname], [taxname,procent_taxa], ;
[Alegeti codul de TVA], [Denumire,Procent], [], .F., [1=1], [taxname], 1, 0, [taxcode])
```
`cauta_alfa` trimite select-ul mai departe la `gencursor` (`COMUN\programe\cauta_alfa.prg:151`),
care il pune pe `SELECTCMD`-ul unui CursorAdapter legat de `goConn.nHandle`
(`COMUN\programe\gencursor.prg:27-31`) - **deci e SQL Oracle, executat pe server**.
`taxname` din view depaseste latimea declarata de 254 de caractere, iar driverul mapeaza
coloana pe Memo; grila din `cauta_alfa_form` afiseaza literalmente "Memo".
**Latimea declarata, nu cea reala** - asta e detaliul care conteaza. Definitia view-ului
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2022\03\ff_2022_03_14_01_COMUN_SAFT.sql:566-582`):
```sql
create or replace view vsaft_taxtable as
select ...
substr(a.taxcode || '-' || a.descriere, 1, 250) as taxname,
```
cu `descriere VARCHAR2(250)` in tabela (`ff_2022_03_08_01_COMUN_SAFT.sql:28`). Concatenarea
`taxcode || '-' || descriere` are latime declarata ~291 (NUMBER convertit implicit + 1 +
250), iar **`SUBSTR` nu micsoreaza latimea declarata a rezultatului** - ramane cea a
intrarii. De aceea `substr(...,1,250)` deja existent in view nu a rezolvat nimic, si de
aceea nici un `substr(taxname,1,200)` in clientul nostru nu ar rezolva singur: driverul tot
ar vedea o coloana > 254 si tot ar da Memo. Trebuie **`CAST`**, care e singurul care fixeaza
latimea declarata.
`LEFT` nu exista in Oracle - `SUBSTR` e idiomul folosit deja in cod
(`COMUN\programe\updateserver.prg:1357`, `substr(a.adresa,1,250) as adresa`).
### Propunere 4 - CAST, in tabela derivata
Nu merge simplu la nivelul de sus: `deca_baza.afisare` (`COMUN\clase\decabaza.vc2:36-73`)
concateneaza `WHERE` si `ORDER BY` **la acelasi nivel de query**, iar Oracle nu accepta
aliasul de coloana in `WHERE` (ORA-00904). Filtrul de cautare si ordinea sunt ambele pe
`taxname` (parametrii 4 si 11 din apel), deci forma sigura e:
```foxpro
lcSelect = [select * from (select cast(substr(taxname,1,200) as varchar2(200)) as taxname, ] + ;
[procent_taxa, taxcode from vsaft_taxtable)]
```
Restul apelului ramane neschimbat. De testat dupa aplicare: deschiderea dialogului, cautarea
"incepe cu" pe denumire (verifica ca `WHERE` merge pe aliasul din tabela derivata) si
returnarea corecta a `taxcode`.
**Varianta alternativa, doar pe client:** `cauta_alfa` primeste ca parametru 3 o
`CURSORSCHEMA` (acum `[]`, vezi apelul). Trecand
`[taxname C(200), procent_taxa N(6,2), taxcode N(6)]` s-ar forta tipul in cursorul VFP fara
sa se atinga SQL-ul. Merge, dar leaga apelul de structura exacta a view-ului; prefer CAST-ul.
**De reparat separat, daca vrei:** acelasi `substr` fara CAST din view il mosteneste si
`typename` (`:569`) - orice alt loc care afiseaza `vsaft_taxtable.typename` are aceeasi
problema. Nu am verificat unde se mai foloseste.
**Nota:** celelalte alegeri de taxcode (randul de nota, `omodificari.vc2:3926` / `:8491`,
si rulajele, `rulaje.vc2:5289`) citesc cursorul **local** `saft_taxtable`, unde `taxname` e
C(250) - acolo nu apare Memo. Problema e strict pe calea Oracle din `CautaCodTva`.
---
## 5. Explicatia TVA in editarea articolelor
Lipseste, si se poate adauga - campul exista deja in cursor.
`tvd` are `id_jtva_coloana I NULL` de la creare (`omodificari.vc2:14735`) si e completat
din articol la adaugare (`AdaugaLinieTvdDinArticol`, `:13144`). Ce lipseste:
1. **coloana in grila** `grdArticoleFactura` - coloanele actuale sunt 1-15
(`:12345-12454`), fara nimic pe `id_jtva_coloana`;
2. **editorul** - `ArticoleNotaEditor` (`ofacturare_editare.prg:1060-1090`) trateaza doar
`denumire`, `nume_gestiune`, `nume_val` si taxcode.
Sablon existent, de copiat: randul de rulaj face exact asta -
afisare prin `Iif(Seek(trul.id_jtva_coloana,'crsJtvaTemp','id_jtva'),crsJtvaTemp.denumire,'')`
(`omodificari.vc2:9253`) si editare prin combo cu
`RowSource = "select denumire, id_jtva_coloana from crsJtvaTemp order by denumire into cursor crsJtvaTempX"`
(`:9652-9655`). Randul de nota foloseste dialogul `caut_explicatie_tva`
(`COMUN\programe\ocautare.prg:3174`), apelat din `do_modifica_explicatie_tva`
(`omodificari.vc2:14049`), care pune `id_jtva_coloana`, `explicatie_tva` si `proc_tva`.
### Aplicat - varianta B (dialogul `caut_explicatie_tva`)
1. **Coloana** `cExplicatieTvaArt` in `grdArticoleFactura` (ColumnCount 15 -> 16), readonly,
care afiseaza denumirea prin
`Iif(Seek(Nvl(tvd.id_jtva_coloana,0),'crsJtvaTemp','id_jtva'),crsJtvaTemp.denumire,'')` -
acelasi tipar ca pe randul de rulaj. **Coloana e ultima in grila**, dupa Valoare: ordinea
naturala e cea a indicilor, iar mutarea ei langa Taxcode ar fi cerut `ColumnOrder` pe toate
celelalte. Daca o vrei langa Taxcode, e o a doua iteratie, doar de asezare.
2. **Text1.GotFocus** pune explicit `Thisform.pccontrol = 'tvd.id_jtva_coloana'` - coloana are
ControlSource expresie, deci `Alltrim(This.ControlSource)` (tiparul celorlalte coloane) n-ar
fi dat un nume de camp utilizabil.
3. **`ArticoleNotaEditor.CautaExplicatieTva`** apeleaza `caut_explicatie_tva` cu filtrul de cota
calculat din linie: `Round((Nvl(tvd.proc_tvav,0) - 1) * 100, 2)` - `proc_tvav` e in forma
1.21, exact ca `tact.proc_tva` la randul de nota. Cota 0/goala = lista completa.
`tlTipEx` ramane gol (toate explicatiile cu `id_jtva_coloana > 0`), ca in ramura `Else` a
randului de nota; daca vrei restrangerea la TVA exigibil din JV, e un parametru in plus.
4. **Corelarea codului SAF-T**: alegerea scrie `id_jtva_coloana` si `proc_tvav`
(`(cota_tva + 100) / 100`), apoi cheama `UpdateExplicatieSAFTArt` - metoda noua in
`frm_modific2024`, copie a lui `UpdateExplicatieSAFTRul` cu `tvd` in loc de `trul`
(acelasi `GetTaxCodeIdPart`, aceeasi garda `gl406`). La final `calculeaza_valori_articol`,
ca valoarea liniei si bara de totaluri sa urmeze noua cota.
---
## 6. Defect gasit pe drum: salvarea pierde tacut alegerile de pe liniile existente
`ScrieArticoleFacturaEditate` (`ofacturare_editare.prg:471`) actualiza liniile deja salvate
**doar** cu `sters`, `cantitate`, `pret`, `pret_cu_tva`. Restul campurilor apareau doar in
INSERT-ul liniilor noi. Consecinta: pe o linie existenta, codul de TVA ales din nomenclator,
gestiunea, valuta, articolul, pretul de achizitie si discountul se pierdeau la salvare fara
niciun mesaj - defect care exista **inainte** de explicatia TVA, nu introdus de ea.
UPDATE-ul acopera acum toate campurile pe care grila si nomenclatoarele le pot schimba:
`pret_achizitie`, `proc_tvav`, `discount_unitar`, `id_articol`, `id_gestiune`, `id_valuta`,
`id_jtva_coloana`, `taxcode`, `cont`.
---
## De probat
1. **Total note**: modifica suma pe un rand de nota -> "Total note" se schimba pe loc, iar
verdictul trece pe "divergent" daca nu mai bate cu articolele.
2. **Cod TVA**: pe pagina Articole, coloana Taxcode + butonul de modificare -> lista trebuie
sa arate denumirile, nu "Memo"; cautarea "incepe cu" pe denumire trebuie sa filtreze.
3. **Explicatie TVA**: pe o linie cu cota 21%, lista trebuie sa contina doar explicatiile de
21%; dupa alegere, coloana Taxcode se schimba singura.
4. **Persistenta**: alege cod TVA / explicatie TVA pe o linie **deja salvata**, salveaza,
redeschide - valorile trebuie sa fie acolo.
5. **Dialogul de sincronizare** (document cu rulaje): capetele se schimba cand comuti directia,
iar legenda rosie de jos nu mai exista.